嵌入式驱动开发案例实例过程
编写LED字符设备驱动实现Linux下控制LED灯的亮灭。
总结编写字符设备驱动的详细步骤
- 先搭建驱动框架:
- 头文件
- 入口函数
- 出口函数
- 此时先不要写入口和出口
- 各种该:
- 该声明的声明
- 该定义的定义
- 该初始化的初始化
- 先搞硬件后搞软件【变量】
- 填充入口和出口
- 先写注释
- 后塞代码【体力活】
- 最后编写各个接口函数
编写驱动程序 led_drv.c‘’
/************************************************************************* > File Name: led_drv.c > Author: > Mail: > Created Time: 2019年10月09日 星期三 20时03分33秒 ************************************************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/cdev.h> //struct cdev #include <linux/fs.h> //struct file_operations #include <mach/platform.h> //声明描述LED硬件信息的数据结构 struct led_resource{ char *name;//名称 int gpio;//gpio 编号 }; //定义初始化硬件信息对象 static struct led_resource led_info[] = { { .name = "LED1", .gpio = PAD_GPIO_C + 12 }, { .name = "LED2", .gpio = PAD_GPIO_C + 17 }, { .name = "LED3", .gpio = PAD_GPIO_C + 11 }, { .name = "LED4", .gpio = PAD_GPIO_B + 26 } }; //open device func //return -0 success -1-fail static int led_open(struct inode *inode, struct file *file) { int i = 0; for(i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_set_value(led_info[i].gpio, 0); printk("%s:打开第%d个灯。\n", __func__, i+1); } return 0; } //close device func static int led_close(struct inode *inode, struct file *file) { int i = 0; for(i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_set_value(led_info[i].gpio, 1); printk("%s:关闭第%d个灯。\n", __func__, i+1); } return 0; } //定义初始化硬件操作接口对象 static struct file_operations led_fops = { .open = led_open,//open device .release = led_close //close device }; //定义字符设备对象 static struct cdev led_cdev; //定义设备号对象 static dev_t dev; //init device func static int led_init(void) { //申请GPIO资源,配置为输出:1 int i; for(i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_request(led_info[i].gpio, led_info[i].name); gpio_direction_output(led_info[i].gpio, 1); } //申请设备号, alloc_chrdev_region(&dev, 0, 1, "myled"); printk("major:%d, minor:%d\n", MAJOR(dev), MINOR(dev)); //初始化字符设备对象,添加硬件操作接口 cdev_init(&led_cdev, &led_fops); //想内核中注册字符设备对象 cdev_add(&led_cdev, dev, 1); return 0; } //exit device func static void led_exit(void) { int i = 0; //从内核中卸载字符设备对象 cdev_del(&led_cdev); //释放设备号 unregister_chrdev_region(dev, 1); //输出1,释放GPIO资源,GPIO输出1为置空 for(i = 0; i < ARRAY_SIZE(led_info); i++) { gpio_set_value(led_info[i].gpio, 1); gpio_free(led_info[i].gpio); } } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
led_test.c
/************************************************************************* > File Name: led_test.c > Author: > Mail: > Created Time: 2019年10月09日 星期三 21时15分21秒 ************************************************************************/ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int fd; while(1){ fd = open("/dev/myled", O_RDWR); if(fd < 0) { printf("open led device failed.\n"); return -1; } sleep(3); close(fd); } return 0; }
Makefile
obj-m += led_drv.o all: make -C /home/ww/ww/ARM/kernel SUBDIRS=$(PWD) modules clean: make -C /home/ww/ww/ARM/kernel SUBDIRS=$(PWD) clean
遇到的问题
共享库libgcc_s.so.1找不到的问题
- 解决方法:添加相关共享库
在交叉编译工具下找到相应的共享库添加到文件系统下的/lib下。
//查看执行文件需要的依赖共享库 arm-cortex_a9-linux-gnueabi-readelf -d led_test Dynamic section at offset 0x754 contains 25 entries: 标记 类型 名称/值 0x00000001 (NEEDED) 共享库:[libgcc_s.so.1] 0x00000001 (NEEDED) 共享库:[libc.so.6] 0x0000000c (INIT) 0x8420 0x0000000d (FINI) 0x8690 0x00000019 (INIT_ARRAY) 0x10748 0x0000001b (INIT_ARRAYSZ) 4 (bytes) 0x0000001a (FINI_ARRAY) 0x1074c 0x0000001c (FINI_ARRAYSZ) 4 (bytes) 0x00000004 (HASH) 0x818c 0x00000005 (STRTAB) 0x82a4 0x00000006 (SYMTAB) 0x81d4 0x0000000a (STRSZ) 224 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000015 (DEBUG) 0x0 0x00000003 (PLTGOT) 0x10844 0x00000002 (PLTRELSZ) 56 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x83e8 0x00000011 (REL) 0x83e0 0x00000012 (RELSZ) 8 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffe (VERNEED) 0x83a0 0x6fffffff (VERNEEDNUM) 2 0x6ffffff0 (VERSYM) 0x8384 0x00000000 (NULL) 0x0
lsmod: /proc/modules: No such file or directory
- 解决方法:修改启动脚本
/etc/init.d/rcS内容,解决/proc下的设备挂载问题。
/bin/mount -n -t ramfs /var /bin/mount -n -t ramfs ramfs /tmp /bin/mount -n -t sysfs none /sys /bin/mount -n -t ramfs none /dev /bin/mkdir /var/tmp /bin/mkdir /var/modules /bin/mkdir /var/run /bin/mkdir /var/log /bin/mkdir -p /dev/pts /bin/mkdir -p /dev/shm /sbin/mdev -s /bin/mount -a echo /sbin/mdev > /proc/sys/kernel/hotplug echo "" > /proc/sys/kernel/hotplug mount -n -o mode=0755 -t tmpfs tmpfs /dev mknod /dev/console c 5 1 mknod /dev/null c 1 3 echo "starting the hotplug events dispatcher udevd" udevd --daemon echo "synthesizing initial hotplug events" udevtrigger udevsettle --timeout=300 mkdir /dev/pts mount -t devpts devpts /dev/pts mkdir /dev/shm echo "hello world"
printk打印不显示问题
- 内核显示级别设置问题,修改启动参数部分的内核显示等级为console = … debug即可。
有时调试内核模块,打印信息太多了,可以通过修改/proc/sys/kernel/printk文件内容来控制。
默认设置是7 4 1 7
cat /proc/sys/kernel/printk
7 4 1 7
该文件有四个数字值,它们根据日志记录消息的重要性,定义将其发送到何处。关于不同日志级别的更多信息,请查阅syslog(2)联机帮助。上面显示的4个数据分别对应:控制台日志级别:优先级高于该值的消息将被打印至控制台默认的消息日志级别:将用该优先级来打印没有优先级的消息最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)默认的控制台日志级别:控制台日志级别的缺省值 数值越小,优先级越高
查看申请到的设备号及创建相关设备文件
cd /home/drivers insmod led_drv.ko //调用入口函数 cat /proc/devices //查看申请到的主设备号 mknod /dev/myled c 主设备号 0 //创建设备文件,代表LED0 ./led_test //open device fail 测试失败 cd /home/drivers insmod led_drv.ko //调用入口函数 cat /proc/devices //查看申请到的主设备号 character devices://当前系统支持的字符设备,主设备号和设备名称 1 mem 5 /dev/tty 5 /dev/console 5 /dev/ptmx ... 244 myled //LED驱动申请到的主设备号就是244,设备名称为myled ... mknod /dev/myled c 主设备号 0 //创建设备文件,它代表LED设备。 ./led_test //再次执行