在上一章讲解了添加 LED 灯驱动的整个流程和测试结果,这一章在来看一下蜂鸣器的驱动,蜂鸣器和
LED 灯的驱动其实是一样的,都是控制 GPIO 引脚输出高低电平,在本章继续学习一下蜂鸣器的驱动,也算
是在巩固一遍驱动的添加流程。
37.1 蜂鸣器设备注册流程 蜂鸣器设备注册流程
和 LED 灯驱动注册一样,蜂鸣器注册流程也分为下面几步:
5 硬件原理图分析,确定控制 LED 的 GPIO 信息。
6 根据 GPIO 信息在设备树文件中添加 pinctrl 信息
7 在设备树中创建 LED 的设备节点,并加入 GPIO 信息
8 编写 LED 设备驱动程序
接下来根据上面这四步来添加一下蜂鸣器的设备驱动。
2 37.2 蜂鸣器硬件原理图分析
蜂鸣器一端接 VSYS 电压(3.3V),另一端接控制引脚,只不过多了一个三极管。控制引脚为:SNVS_TAMPER1。
3 37.3 修改设备树文件 修改设备树文件
37.3.1 添加 pinctrl 信息
在 i.MX6UL 终结者开发板中使用 SNVS_TAMPER1 这个引脚来控制蜂鸣器设备。打开 topeet_emmc_4_3.dts
文件在 iomux 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_beep”的子节点,具体内容如下:
pinctrl_beep: gpio-beep {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep */
>;
};
在 pinctrl_beep 节点中,将 SNVS_TAMPER1 引脚复用为 GPIO5_IO01,宏定义
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 在 arch/arm/boot/dts/imx6ull-pinfunc-snvs.h 文件中。
37.3.2 添加 beep 设备节点
在 topeet_emmc_4_3.dts 文件下,在根节点“/”下创建 LED 节点,节点名为“beep”,具体内容如下:
1 beep {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "beep";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_beep>;
7 beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
8 status = "okay";
9 };
第 6 行,pinctrl-0 属性设置蜂鸣器所使用的 GPIO 对应的 pinctrl 节点。
第 7 行,beep-gpio 属性指定了蜂鸣器所使用的 GPIO。
接下来就是检查蜂鸣器使用的GPIO引脚SNVS_TAMPER1有没有被其他pinctrl节点使用,在检查GPIO5_IO01
这个 GPIO 有没有被其他外设使用,如果有的话都屏蔽掉。
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的 topeet_emmc_4_3.dtb
文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“beep”节点是否存在,如果存
在的话就说明设备树基本修改成功(具体作用还要驱动验证),结果如下图所示:
37.4 编写蜂鸣器驱动程序 编写蜂鸣器驱动程序
本实验例程路径:i.MX6UL 终结者光盘资料/06_Linux 驱动例程/03_beep
蜂鸣器的驱动程序和 LED 的驱动程序基本一致,创建 beep.c 文件。具体内容如下:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #include
16 #include
17
18 #define BEEP_CNT 1 /* 设备号个数 */
19 #define BEEP_NAME "beep" /* 名字 */
20 #define BEEPOFF 0 /* 关蜂鸣器 */
21 #define BEEPON 1 /* 开蜂鸣器 */
22
23
24 /* beep 设备结构体 */
25 struct beep_dev{
26 dev_t devid; /* 设备号 */
27 struct cdev cdev; /* cdev */
28 struct class *class; /* 类 */
29 struct device *device; /* 设备 */
30 int major; /* 主设备号 */
31 int minor; /* 次设备号 */
32 struct device_node *nd; /* 设备节点 */
33 int beep_gpio; /* beep 所使用的 GPIO 编号 */
34 };
35
36 struct beep_dev beep; /* beep 设备 */
37
38 /*
39 * @description : 打开设备
40 * @param – inode : 传递给驱动的 inode
41 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
42 * 一般在 open 的时候将 private_data 指向设备结构体。
43 * @Return : 0 成功;其他 失败
44 */
45 static int beep_open(struct inode *inode, struct file *filp)
46 {
47 filp->private_data = &beep; /* 设置私有数据 */
48 return 0;
49 }
50
51 /*
52 * @description : 向设备写数据
53 * @param - filp : 设备文件,表示打开的文件描述符
54 * @param - buf : 要写给设备写入的数据
55 * @param - cnt : 要写入的数据长度
56 * @param - offt : 相对于文件首地址的偏移
57 * @return : 写入的字节数,如果为负值,表示写入失败
58 */
59 static ssize_t beep_write(struct file *filp, const char __user *buf,
60 size_t cnt, loff_t *offt)
61 {
62 int retvalue;
63 unsigned char databuf[1];
64 unsigned char beepstat;
65 struct beep_dev *dev = filp->private_data;
66
67 retvalue = copy_from_user(databuf, buf, cnt);
68 if(retvalue < 0) {
69 printk("kernel write failed!rn");
70 return -EFAULT;
71 }
72
73 beepstat = databuf[0]; /* 获取状态值 */
74
75 if(beepstat == BEEPON) {
76 gpio_set_value(dev->beep_gpio, 1); /* 打开蜂鸣器 */
77 } else if(beepstat == BEEPOFF) {
78 gpio_set_value(dev->beep_gpio, 0); /* 关闭蜂鸣器 */
79 }
80 return 0;
81 }
82
83 /*
84 * @description : 关闭/释放设备
85 * @param - filp : 要关闭的设备文件(文件描述符)
86 * @return : 0 成功;其他 失败
87 */
88 static int beep_release(struct inode *inode, struct file *filp)
89 {
90 return 0;
91 }
92
93 /* 设备操作函数 */
94 static struct file_operations beep_fops = {
95 .owner = THIS_MODULE,
96 .open = beep_open,
97 .write = beep_write,
98 .release = beep_release,
99 };
100
101 /*
102 * @description : 驱动入口函数
103 * @param : 无
104 * @return : 无
105 */
106 static int __init beep_init(void)
107 {
108 int ret = 0;
109
110 /* 设置 BEEP 所使用的 GPIO */
111 /* 1、获取设备节点:beep */
112 beep.nd = of_find_node_by_path("/beep");
113 if(beep.nd == NULL) {
114 printk("beep node not find!rn");
115 return -EINVAL;
116 } else {
117 printk("beep node find!rn");
118 }
119
120 /* 2、 获取设备树中的 gpio 属性,得到 BEEP 所使用的 GPIO 编号 */
121 beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
122 if(beep.beep_gpio < 0) {
123 printk("can't get beep-gpio");
124 return -EINVAL;
125 }
126 printk("led-gpio num = %drn", beep.beep_gpio);
127
128 /* 3、设置 GPIO5_IO01 为输出,并且输出低电平,默认关闭 BEEP */
129 ret = gpio_direction_output(beep.beep_gpio, 0);
130 if(ret < 0) {
131 printk("can't set gpio!rn");
132 }
133
134 /* 注册字符设备驱动 */
135 /* 1、创建设备号 */
136 if (beep.major) { /* 定义了设备号 */
137 beep.devid = MKDEV(beep.major, 0);
138 register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);
139 } else { /* 没有定义设备号 */
140 alloc_chrdev_region(&beep.devid, 0, BEEP_CNT, BEEP_NAME);
141 beep.major = MAJOR(beep.devid); /* 获取分配号的主设备号 */
142 beep.minor = MINOR(beep.devid); /* 获取分配号的次设备号 */
143 }
144 printk("beep major=%d,minor=%drn",beep.major, beep.minor);
145
146 /* 2、初始化 cdev */
147 beep.cdev.owner = THIS_MODULE;
148 cdev_init(&beep.cdev, &beep_fops);
149
150 /* 3、添加一个 cdev */
151 cdev_add(&beep.cdev, beep.devid, BEEP_CNT);
152
153 /* 4、创建类 */
154 beep.class = class_create(THIS_MODULE, BEEP_NAME);
155 if (IS_ERR(beep.class)) {
156 return PTR_ERR(beep.class);
157 }
158
159 /* 5、创建设备 */
160 beep.device = device_create(beep.class, NULL, beep.devid, NULL,
161 BEEP_NAME);
162 if (IS_ERR(beep.device)) {
163 return PTR_ERR(beep.device);
164 }
165
166 return 0;
167 }
168
169 /*
170 * @description : 驱动出口函数
171 * @param : 无
172 * @return : 无
173 */
174 static void __exit beep_exit(void)
175 {
176 /* 注销字符设备驱动 */
177 cdev_del(&beep.cdev); /* 删除 cdev */
178 unregister_chrdev_region(beep.devid, BEEP_CNT); /* 注销设备号 */
179
180 device_destroy(beep.class, beep.devid);
181 class_destroy(beep.class);
182 }
183
184 module_init(beep_init);
185 module_exit(beep_exit);
186 MODULE_LICENSE("GPL");
187 MODULE_AUTHOR("topeet");
5 37.5 编写应用测试程序 编写应用测试程序
创建应用测试程序 beep_test.c,内容如下:
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8
9 #define BEEPOFF 0
10 #define BEEPON 1
11
12 /*
13 * @description : main 主程序
14 * @param - argc : argv 数组元素个数
15 * @param - argv : 具体参数
16 * @return : 0 成功;其他 失败
17 */
18 int main(int argc, char *argv[])
19 {
20 int fd, retvalue;
21 char *filename;
22 unsigned char databuf[1];
23
24 if(argc != 3){
25 printf("Error Usage!rn");
26 return -1;
27 }
28
29 filename = argv[1];
30
31 /* 打开 beep 驱动 */
32 fd = open(filename, O_RDWR);
33 if(fd < 0){
34 printf("file %s open failed!rn", argv[1]);
35 return -1;
36 }
37
38 databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
39
40 /* 向/dev/beep 文件写入数据 */
41 retvalue = write(fd, databuf, sizeof(databuf));
42 if(retvalue < 0){
43 printf("BEEP Control Failed!rn");
44 close(fd);
45 return -1;
46 }
47
48 retvalue = close(fd); /* 关闭文件 */
49 if(retvalue < 0){
50 printf("file %s close failed!rn", argv[1]);
51 return -1;
52 }
53 return 0;
54 }
beep_test.c 的文件内容和 gpioled_test.c 的内容差不多,都是对文件的打开、关闭、写操作。
6 37.6 编译运行测试 编译运行测试
37.6.1 编译蜂鸣器驱动文件
同 LED 驱动文件一样,创建 Makefile 文件,将 obj-m 的值改为 beep.o,Makefile 文件内容如下:
KERNELDIR := /home/topeet/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := beep.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
首先我们在终端输入两个命令(设置两个环境变量):
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
然后执行“make”命令编译模块,编译完成生成 beep.ko,如下图所示:
37.6.2 编译应用测试程序
输入如下命令编译应用测试程序:
arm-linux-gnueabihf-gcc -o beep_test beep_test.c
编译完成后,会生成 beep_test 可执行文件。如下图所示:
37.6.3 运行测试
启动开发板,将编译好的 beep.ko 驱动模块和 beep_test 应用测试文件拷贝到/lib/modules/4.1.15 目录下(检
查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话需要自行创建一下。开发板
中使用的是光盘资料里面提供的 busybox 文件系统,光盘资料的“i.MX6UL 终结者光盘资料 8_开发板系统
镜像 3_文件系统镜像 1_Busybox 文件系统”目录下)。输入下面命令加载模块:
depmod
modprobe beep
驱动加载成功后,显示下面的信息:
然后使用应用测试程序来验证一下驱动是否正确,输入下面的命令打开蜂鸣器:
./beep_test /dev/beep 1 //打开蜂鸣器
查看开发板上的蜂鸣器是否有响声,如果有鸣叫声说明驱动正常工作。
然后输入下面命令关闭蜂鸣器:
./beep_test /dev/beep 0 //关闭蜂鸣器
正常情况下,蜂鸣器停止鸣叫。
使用下面的命令卸载模块:
rmmod beep //卸载模块
|