【TINY4412】LINUX移植笔记:(18)设备树BEEP驱动

环境

宿主机 : 虚拟机 Ubuntu 16.04 LTS / X64
目标板[底板]: Tiny4412SDK - 1506
目标板[核心板]: Tiny4412 - 1412
LINUX内核: 4.12.0
交叉编译器: arm-none-linux-gnueabi-gcc(gcc version 4.8.3 20140320)
日期: 2017-9-9 20:30:09
作者: SY

简介

例程使用PWM控制开发板上的蜂鸣器,使用linux内核的通用pwm-beep模块实现。

设备树

1
2
3
4
5
6
7
8
9
10
11
&pwm {
samsung,pwm-outputs = <0>;
pinctrl-0 = <&pwm0_out>;
pinctrl-names = "default";
status = "okay";
};
buzzer {
compatible = "pwm-beeper";
pwms = <&pwm 0 1000000000 0>;
};

其中pwms含义参考

1
2
3
4
5
6
7
8
9
10
11
12
13
struct pwm_device *
of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args)
{
//第一个参数表示第几个pwm
pwm = pwm_request_from_chip(pc, args->args[0], NULL);
//第二个参数表示周期
pwm->args.period = args->args[1];
//第三个参数表示电平翻转,取决于电路板设计,是高电平鸣叫还是低电平鸣叫
if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED)
pwm->args.polarity = PWM_POLARITY_INVERSED;
}

源文件

实现pwm-beep主要依赖于./driver/input/misc/pwm-beeper.c

首先执行probe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int pwm_beeper_probe(struct platform_device *pdev)
{
//该函数获取一个pwm设备
beeper->pwm = devm_pwm_get(dev, NULL);
//初始化工作队列,工作队列起作用时调用 pwm_beeper_work
INIT_WORK(&beeper->work, pwm_beeper_work);
//从设备树读取蜂鸣器固定鸣叫频率,如果找不到使用默认值1000HZ
error = device_property_read_u32(dev, "beeper-hz", &bell_frequency);
if (error) {
bell_frequency = 1000;
dev_dbg(dev,
"failed to parse 'beeper-hz' property, using default: %uHz\n",
bell_frequency);
}
//收到输入事件时,执行 pwm_beeper_event
beeper->input->event = pwm_beeper_event;
beeper->input->close = pwm_beeper_close;
//注册为输入子系统,将在 /dev/input/eventx 生成设备对象,读写该设备就可以操作蜂鸣器
error = input_register_device(beeper->input);
}

应用层读写设备时,pwm_beeper_event函数被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static int pwm_beeper_event(struct input_dev *input,
unsigned int type, unsigned int code, int value)
{
struct pwm_beeper *beeper = input_get_drvdata(input);
if (type != EV_SND || value < 0)
return -EINVAL;
switch (code) {
case SND_BELL:
value = value ? beeper->bell_frequency : 0;
break;
case SND_TONE:
break;
default:
return -EINVAL;
}
if (value == 0)
beeper->period = 0;
else
beeper->period = HZ_TO_NANOSECONDS(value);
if (!beeper->suspended)
schedule_work(&beeper->work);
return 0;
}

只要蜂鸣器没有挂起,将执行schedule_work,工作队列起作用后,调用pwm_beeper_work

1
2
3
4
5
6
7
8
9
10
static void pwm_beeper_work(struct work_struct *work)
{
struct pwm_beeper *beeper = container_of(work, struct pwm_beeper, work);
unsigned long period = READ_ONCE(beeper->period);
if (period)
pwm_beeper_on(beeper, period);
else
pwm_beeper_off(beeper);
}

这样,蜂鸣器就可以鸣叫。

1
2
3
4
Device Drivers --->
Input device support --->
[*] Miscellaneous devices --->
<*> PWM beeper support

测试

1
2
[ 2.690985] pwm-beeper beep: beep supply amp not found, using dummy regulator
[ 2.697795] input: pwm-beeper as /devices/platform/beep/input/input0

可以看出蜂鸣器作为内核的输入事件0,查看设备:

1
2
3
4
5
6
7
8
9
10
[root@TINY4412:~]# cat proc/bus/input/devices
I: Bus=0019 Vendor=001f Product=0001 Version=0100
N: Name="pwm-beeper"
P: Phys=pwm/input0
S: Sysfs=/devices/platform/beep/input/input0
U: Uniq=
H: Handlers=kbd event0
B: PROP=0
B: EV=40001
B: SND=6

dev目录对应的设备

1
2
[root@TINY4412:~]# ls /dev/input/event0
/dev/input/event0

应用程序

测试蜂鸣器需要写一个应用程序,通过用户态访问蜂鸣器。

应用程序包含两个文件,一个是用户态源文件,一个是Makefile文件。 这两个文件可以放在任意位置编译。

app_beep.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
* beep driver for tiny4412
*
* Copyright (c) 2017
* Author: SY <1530454315@qq.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define EV_SND 0x12
#define SND_BELL 0x01
#define SND_TONE 0x02
struct input_event {
struct timeval time;
unsigned short int type;
unsigned short int code;
signed int value;
};
static void help(void)
{
printf("Usage: ./app_beep <duty>");
}
int main(int argc, char **argv)
{
if (argc < 2) {
help();
return 1;
}
int fd = open("/dev/input/event0", O_RDWR);
if (fd < 0) {
perror("[open]");
return 1;
}
int duty = atoi(argv[1]);
struct input_event event;
event.type = EV_SND;
event.code = SND_BELL;
event.value = duty;
if (write(fd, &event, sizeof(event)) < 0) {
perror("[write]");
goto error;
}
close(fd);
printf("done!\n");
return 0;
error:
close(fd);
return 1;
}

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Linux modules compile
# Author : SY
# Time : 2017-8-10 22:29:24
#############################################################################
CC = arm-none-eabi-gcc
CFLAGS += -Wall -std=gnu99
TARGET = app_beep
OBJS = app_beep.o
INSTALL_DIR = /opt/fs/rootfs/rootfs/tmp/
$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) -o $@ $<
clean:
rm -rf *.o

调用make时,提示:

1
2
3
4
5
6
arm-none-eabi-gcc -Wall -std=c99 -o app_beep app_beep.o
/opt/gcc-arm-none-eabi-5_4-2016q3/bin/../lib/gcc/arm-none-eabi/5.4.1/../../../../arm-none-eabi/lib/libc.a(lib_a-exit.o): In function `exit':
exit.c:(.text.exit+0x2c): undefined reference to `_exit'
/opt/gcc-arm-none-eabi-5_4-2016q3/bin/../lib/gcc/arm-none-eabi/5.4.1/../../../../arm-none-eabi/lib/libc.a(lib_a-openr.o): In function `_open_r':
openr.c:(.text._open_r+0x24): undefined reference to `_open'
/opt/gcc-arm-none-eabi-5_4-2016q3/bin/../lib/gcc/arm-none-eabi/5.4.1/../../../../arm-none-eabi/lib/libc.a(lib_a-sbrkr.o): In function `_sbrk_r':

网上找到答案:exit.c:(.text+0x18): undefined reference to `_exit’ when using arm-none-eabi-gcc

添加字段--specs=nosys.specs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Linux modules compile
# Author : SY
# Time : 2017-8-10 22:29:24
#############################################################################
CC = arm-none-eabi-gcc
CFLAGS += -Wall -std=gnu99 --specs=nosys.specs
TARGET = app_beep
OBJS = app_beep.o
INSTALL_PATH = /opt/fs/rootfs/rootfs/tmp/
$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) -o $@ $<
install:
chmod 755 $(TARGET)
cp $(TARGET) $(INSTALL_PATH)
clean:
rm -rf *.o $(TARGET)
1
2
root@ubuntu:/opt/temp/temp/beep/app# make
root@ubuntu:/opt/temp/temp/beep/app# make install

使用NFS挂载Linux,在开发板执行

1
2
[root@TINY4412:/tmp]# ./app_beep 1000
Segmentation fault

应该是内存越界等问题,在本地测试

1
2
3
root@ubuntu:/opt/temp/temp/beep/app# gcc -g app_beep.c -o app_beep
root@ubuntu:/opt/temp/temp/beep/app# ./app_beep 1000
Done!

没毛病啊!估计是编译器的问题,修改Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@ubuntu:/opt/temp/temp/beep/app# more Makefile
# Linux modules compile
# Author : SY
# Time : 2017-8-10 22:29:24
#############################################################################
CC = arm-none-linux-gnueabi-gcc
CFLAGS += -Wall -std=gnu99
TARGET = app_beep
OBJS = app_beep.o
INSTALL_PATH = /opt/fs/rootfs/rootfs/tmp/
$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) -o $@ $<
install:
chmod 755 $(TARGET)
cp $(TARGET) $(INSTALL_PATH)
clean:
rm -rf *.o $(TARGET)

重新编译,安装

1
2
3
4
5
6
root@ubuntu:/opt/temp/temp/beep/app# make
arm-none-linux-gnueabi-gcc -Wall -std=gnu99 -c -o app_beep.o app_beep.c
arm-none-linux-gnueabi-gcc -Wall -std=gnu99 -o app_beep app_beep.o
root@ubuntu:/opt/temp/temp/beep/app# make install
chmod 755 app_beep
cp app_beep /opt/fs/rootfs/rootfs/tmp/

测试

1
2
[root@TINY4412:/tmp]# ./app_beep 1000
done!

蜂鸣器叫了!果然是编译器问题,查找资料交叉编译工具链

arm-none-eabi-gcc

(ARM architecture,no vendor,not target an operating system,complies with the ARM EABI)
用于编译 ARM 架构的裸机系统(包括 ARM Linux的 boot、kernel,不适用编译 linux 应用 Application),一般适合 ARM7、Cortex-M 和 Cortex-R 内核的芯片使用,所以不支持那些跟操作系统关系密切的函数,比如fork(2),他使用的是 newlib 这个专用于嵌入式系统的C库。

说的非常清楚,不适合编译应用程序!!! 以后统一使用编译器arm-none-linux-gnueabi-gcc

虽然蜂鸣器正常驱动,但是修改占空比没效果,查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
static int pwm_beeper_event(struct input_dev *input,
unsigned int type, unsigned int code, int value)
{
switch (code) {
case SND_BELL:
value = value ? beeper->bell_frequency : 0;
break;
case SND_TONE:
break;
default:
return -EINVAL;
}
}

原来的 codeSND_BELL,因此设置的频率始终是beeper->bell_frequency,默认为bell_frequency = 1000; 需要修改:

1
2
#define SND_TONE 0x02
event.code = SND_TONE;

这样就可以自定义周期,自定义周期范围:0 ~ 1000000000

重新测试 OK !

参考

Linux如何查看与/dev/input目录下的event对应的设备

基于S3C2440的Linux-3.6.6移植——PWM蜂鸣器驱动

SY wechat
扫一扫,用手机访问本站