基于树莓派rasberrypi 5的LED驱动开发LED驱动代码详细解读0.驱动开发的操作流程0.1 明确硬件的需求0.2 选择驱动的模型0.3 写设备树0.4 搭建驱动框架0.5 在 probe 里完成硬件初始化0.6 实现操作函数open /read/write /ioctl0.7 写 Makefile Kconfig编译配置0.8 编译驱动.ko 文件0.9 加载驱动 调试1.0 写应用程序测试APP1 头文件代码解读1.1 内核驱动模块1.2 文件系统1.3 内核与应用层数据交换1.4 内核中GPIO的接口API提供2 宏定义3 驱动接口实现4 内核模块的加载5 完整代码LED驱动代码详细解读嵌入式Linux驱动内容的主要的工作内容是拿到一块硬件板子首先下载一个开源驱动对开源驱动进行移植能够让其在硬件板子上执行接着就是调试代码对其中不必要的功能进行裁剪对自己产品的功能进行新增最后整体调试。常见的驱动有LED 驱动、按键驱动、PWM 驱动、I2C 传感器驱动、SPI 屏幕驱动、驱动移植、BSP 开发核心就是调试驱动代码裁剪内核增加新的功能事实上还是接触软件多。0.驱动开发的操作流程0.1 明确硬件的需求用的是哪个 GPIO / I2C / SPI / 中断高电平有效还是低电平有效要不要供电、复位、时钟硬件怎么工作时序、协议这决定了我的驱动代码怎么写实际上是描述硬件工作的逻辑。0.2 选择驱动的模型字符设备LED、按键、简单外设平台设备platform_driver最常用配合设备树内核子系统LED 子系统、输入子系统、I2C 子系统字符设备最简单、最原始适合入门、简单 GPIO、自定义接口平台设备标准、通用、配合设备树企业90% 驱动都用它内核子系统内核已经写好框架不用自己写字符设备、直接用字符设备自己注册主设备号、自己实现 file_operations、自己创建设备节点 /dev/xxx、自己管理 open/read/write0.3 写设备树compatible匹配名gpiosinterruptsregI2C/SPI 地址pinctrl引脚配置电源、时钟、复位0.4 搭建驱动框架头文件of_device_id 匹配表和 DTS 匹配probe 函数硬件初始化核心remove 函数卸载清理file_operationsopen/read/write/ioctl入口出口函数module_init/exitMODULE_LICENSE(“GPL”)0.5 在 probe 里完成硬件初始化从设备树获取 GPIO / 中断 / 地址申请硬件资源初始化硬件供电、复位、配置模式注册字符设备 / 创建设备节点注册中断如果需要0.6 实现操作函数open /read/write /ioctlopen初始化read读数据传感器、按键write写控制LED、继电器ioctl复杂控制模式、参数0.7 写 Makefile Kconfig编译配置Makefile控制如何编译成 .koKconfig让驱动出现在 make menuconfig0.8 编译驱动.ko 文件模块编译生成 .ko动态加载内置编译直接编译到内核开机自动运行0.9 加载驱动 调试有没有匹配设备树probe 是否执行有无内核崩溃oops/panic硬件是否正常工作1.0 写应用程序测试APP应用层通过 /dev/xxx 节点控制驱动openwritereadclose测试功能是否正常亮灭、数据、中断、稳定性。1 头文件代码解读1.1 内核驱动模块#includelinux/module.h#include命令是把某个文件下的代码复制到当前的代码空间这里就是把linux内核下的linux文件夹下的module.h的文件的代码复制到当前的代码里面。这是因为在这个内核模块module.h文件中定义了驱动的入口和驱动出口以及一些开源协议、作者、描述module_init(xxx);// 驱动入口module_exit(xxx);//驱动出口MODULE_LICENSE(GPL);// 开源协议必须写MODULE_AUTHOR(xx);// 作者MODULE_DESCRIPTION();// 描述1.2 文件系统#includelinux/fs.h同理#include命令是把某个文件下的代码复制到当前的代码空间这里就是把linux内核下的linux文件夹下的fs.h的文件的代码复制到当前的代码里面。这个头文件主要定义了文件系统主要是应用层与驱动之间的交互通过文件系统提供字符设备驱动核心功能让你能注册 /dev/xxx 设备节点(应用层)提供 open/read/write 对应的内核接口(驱动层)structfile_operations;// 驱动灵魂结构体register_chrdev();// 注册字符设备unregister_chrdev();// 注销字符设备1.3 内核与应用层数据交换#includelinux/uaccess.h应用层与内核层之间不能直接进行内存访问必须通过函数之间的复制copy_from_user();// 应用 → 内核写数据copy_to_user();// 内核 → 应用读数据1.4 内核中GPIO的接口API提供在内核中提供对硬件的操作的GPIO接口gpio_request();// 申请GPIOgpio_free();// 释放GPIOgpio_direction_output();// 设置为输出gpio_set_value();// 写 1/0 控制亮灭以上四个头文件我们可以得到内核与硬件层以及应用层以及驱动层之间相互不直接联系必须增加头文件来间接建立他们的联系。2 宏定义宏定义驱动名称、主设备号、连接的引脚。// 宏定义 #defineDEVICE_NAMEled_driver// 驱动名称#defineMAJOR_NUM240// 主设备号#defineLED_GPIO_PIN17// LED 接在 BCM GPIO173 驱动接口实现实现从应用层到底层硬件的驱动接口通过gpio.h文件中的GPIO的API建立硬件与驱动之间的接口,通过文件系统建立驱动与应用层之间的接口两者贯穿最终建立了从硬件到上层应用的接口。// 驱动接口实现 // 应用 open(/dev/led) 时调用staticintled_open(structinode*inode,structfile*file){printk(LED Driver Opened\n);return0;}// 应用 close 时调用staticintled_release(structinode*inode,structfile*file){printk(LED Driver Closed\n);return0;}// 应用 write 时调用核心控制亮灭staticssize_tled_write(structfile*file,constchar__user*buf,size_tsize,loff_t*ppos){unsignedcharvalue;// 从用户空间读取 1 字节数据0 或 1if(copy_from_user(value,buf,1)){return-EFAULT;}// 根据值控制 GPIO 电平if(value1){gpio_set_value(LED_GPIO_PIN,1);// 亮}else{gpio_set_value(LED_GPIO_PIN,0);// 灭}returnsize;}// 文件操作集合 staticstructfile_operationsfops{.ownerTHIS_MODULE,.openled_open,.writeled_write,.releaseled_release,};4 内核模块的加载在这里写内核中驱动模块的加载与退出以及相关的协议// 驱动入口加载时执行 staticint__initled_init(void){intret;// 1. 注册字符设备retregister_chrdev(MAJOR_NUM,DEVICE_NAME,fops);if(ret0){printk(register_chrdev failed\n);returnret;}// 2. 申请 GPIO 资源retgpio_request(LED_GPIO_PIN,LED_PIN);if(ret0){printk(gpio_request failed\n);unregister_chrdev(MAJOR_NUM,DEVICE_NAME);returnret;}// 3. 设置为输出模式默认低电平灭gpio_direction_output(LED_GPIO_PIN,0);printk(LED Driver Initialized Successfully\n);return0;}// 驱动出口卸载时执行 staticvoid__exitled_exit(void){// 灭灯gpio_set_value(LED_GPIO_PIN,0);// 释放 GPIOgpio_free(LED_GPIO_PIN);// 注销字符设备unregister_chrdev(MAJOR_NUM,DEVICE_NAME);printk(LED Driver Exited\n);}// 注册入口/出口module_init(led_init);module_exit(led_exit);// 必须加的协议与描述MODULE_LICENSE(GPL);MODULE_AUTHOR(Embedded Linux);MODULE_DESCRIPTION(Standard LED Character Device Driver);5 完整代码#1.确定硬件的工作逻辑(主要看CPU和挂载的硬件)-使用GPIOGPIO输出高电平亮输出低电平灭连接在引脚17上 #2.选择驱动模型(字符模型、平台模型、内核子系统)这边先不用设备树直接进行字符模型#includelinux/module.h// 内核模块核心头文件提供了#includelinux/fs.h// 文件操作register_chrdev提供了#includelinux/uaccess.h// 用户-内核数据拷贝copy_from_user提供了#includelinux/gpio.h// GPIO 操作 API提供了// 宏定义 #defineDEVICE_NAMEled_driver// 驱动名称#defineMAJOR_NUM240// 主设备号#defineLED_GPIO_PIN17// LED 接在 BCM GPIO17// 驱动接口实现 // 应用 open(/dev/led) 时调用staticintled_open(structinode*inode,structfile*file){printk(LED Driver Opened\n);return0;}// 应用 close 时调用staticintled_release(structinode*inode,structfile*file){printk(LED Driver Closed\n);return0;}// 应用 write 时调用核心控制亮灭staticssize_tled_write(structfile*file,constchar__user*buf,size_tsize,loff_t*ppos){unsignedcharvalue;// 从用户空间读取 1 字节数据0 或 1if(copy_from_user(value,buf,1)){return-EFAULT;}// 根据值控制 GPIO 电平if(value1){gpio_set_value(LED_GPIO_PIN,1);// 亮}else{gpio_set_value(LED_GPIO_PIN,0);// 灭}returnsize;}// 文件操作集合 staticstructfile_operationsfops{.ownerTHIS_MODULE,.openled_open,.writeled_write,.releaseled_release,};// 驱动入口加载时执行 staticint__initled_init(void){intret;// 1. 注册字符设备retregister_chrdev(MAJOR_NUM,DEVICE_NAME,fops);if(ret0){printk(register_chrdev failed\n);returnret;}// 2. 申请 GPIO 资源retgpio_request(LED_GPIO_PIN,LED_PIN);if(ret0){printk(gpio_request failed\n);unregister_chrdev(MAJOR_NUM,DEVICE_NAME);returnret;}// 3. 设置为输出模式默认低电平灭gpio_direction_output(LED_GPIO_PIN,0);printk(LED Driver Initialized Successfully\n);return0;}// 驱动出口卸载时执行 staticvoid__exitled_exit(void){// 灭灯gpio_set_value(LED_GPIO_PIN,0);// 释放 GPIOgpio_free(LED_GPIO_PIN);// 注销字符设备unregister_chrdev(MAJOR_NUM,DEVICE_NAME);printk(LED Driver Exited\n);}// 注册入口/出口module_init(led_init);module_exit(led_exit);// 必须加的协议与描述MODULE_LICENSE(GPL);MODULE_AUTHOR(Embedded Linux);MODULE_DESCRIPTION(Standard LED Character Device Driver);