江苏建设官方网站,珠海婚恋网站建设市场分析,资讯网站建设流程,股票海选公司用什么网站#x1f431;作者#xff1a;一只大喵咪1201 #x1f431;专栏#xff1a;《Linux驱动》 #x1f525;格言#xff1a;你只管努力#xff0c;剩下的交给时间#xff01; 目录 #x1f36e;设备树模型的LED驱动#x1f369;设备树文件#x1f369;驱动程序 #x1… 作者一只大喵咪1201 专栏《Linux驱动》 格言你只管努力剩下的交给时间 目录 设备树模型的LED驱动设备树文件驱动程序 应用层读取按键值查询方式休眠唤醒方式poll方式异步通知方式 查询方式实现按键驱动编程 总结 设备树模型的LED驱动
目前有三种方式来写LED驱动程序
最简单的驱动模型——硬件操作绑定在驱动函数中。总线驱动模型。设备树驱动模型。
下面设备树驱动模型来实现一下LED驱动程序该模型主要分为两部分设备树文件和驱动程序。
设备树文件 如上图所示设备树文件在设备树中增加Big-Miaomi-LED0和Big-Miaomi-LED1两个设备节点 compatible属性属性值都是BigMiaomi,LED_Driver。 pin属性属性值是各自节点所用GPIO组和引脚编号组成的32位整数。 如果在设备树节点里使用reg属性内核在生成对应的platform_device时reg属性会被转换成IORESOURCE_MEM类型的资源。 如果在设备树节点里使用interrputs属性内核在生成对应的platform_device时interrupts属性会被转换成IORESOURCE_IRQ类型的资源。
但是本喵写的Big-Miaomi-LED节点中属性名是pin该属性名是本喵自己定义的不在内核自动转换资源类型的的命名范围内。
所以就不能从转换后的platform_device结构体中的resources数组中获得引脚资源了具体获取方式编程时候再说。 如上图然后在内核目录中使用make dtbs指令编译设备树文件转换为内核认识的dtb文件。
驱动程序
驱动程序在总线驱动模型的基础上进行修改驱动层的上层不用动只需要改变下层中的部分代码 如上图所示由于现在支持了设备树所以需要初始化platform_deiver结构体中driver成员里的of_match_table成员这是一个struct of_device_id类型的数组。
所以需要定义一个struct of_device_id类型的数组名为BigMiaomi_LEDs
只用platform_device和platform_driver匹配规则中优先级最高的compatible属性来匹配。只支持LED设备所以compatible属性只有一个值。
compatible属性的值必须和设备树中要支持节点的compatible属性值相同才能匹配成功。
然后就是在匹配成功以后会自动调用paltform_driver中的probe函数在该函数中原本是从paltform_device的resources数组中获取硬件资源但是此时不能这样干了 如上图所示probe函数在该函数中首先要获取pin资源
设备树中的pin属性没有被转换到resources数组中但是在第一次转换为device_node里的properties中是有该属性的。 从匹配成功的platform_device中得到当前节点的device_node结构体指针of_node。 使用of_property_read_32函数从np指向的当前节点deivce_node中的properties里找到pin属性并且以32位整数的方式读取该属性的value值。 将表示引脚资源的32位属性值放入到记录引脚资源的全局数组g_ledpins中。
获取到引脚资源后的其他操作和总线模型中相同也是要使用led_class_create_device在/dev目录下创建设备节点。 设备树文件中的设备节点内核加载后并不会在/dev目录下创建相应的文件它不属于文件字符设备文件系统。 如上图代码所示既然probe中的获取引脚资源的方式变了那么在remove中获取引脚资源的方式和其他处理也要做出相应变化
从要移除设备节点的device_node中获取引脚资源led_pin。遍历存放引脚资源的全局数组找到要移除的节点移除后将对应的值修改为-1。最后判断一下是否该类型的设备节点全部移除了如果存放引脚资源的全局数组中所有值都成了-1则说明全部移除了。
此时整个驱动程序就修改完毕了相比于总线驱动模型只是在获取引脚资源的方式上做了改变。 如上图所示Makfile文件只需要编译驱动层上层led_drv.c和下层chip_led_opr.c即可board_A.c不用再参与编译了。
因为引脚资源不再由board_A.c中的platform_device结构体提供了。引脚资源由设备树文件提供由内核将设备节点转换为platform_device结构体。 如上图所示将在Linux服务器中编译好的dtb设备树文件和led_drv.ko及chip_led_opr.ko驱动文件还有led_drv_test测试文件拷贝到网络根文件系统中。
在开发板上将dtb设备树文件拷贝到/boot目录下然后重启开发板. 如上图所示在/sys/firmware/devicetree/base/路径下存在Big-Miaomi-LED0和Big-Miaomi-LED1两个设备节点这是我们在设备树文件中添加的两个节点此时加载到了内核中。然后使用insmod led_drv.ko和insmod chip_led_opr.ko安装驱动程序。 如上图所示此时执行测试程序在命令行中输入./led_drv_test /dev/BigMiaomi_LED0 on内核打印信息现实操作了GPIO3_1。
应用层读取按键值
应用层读取按键值有4种方式
查询方式休眠-唤醒方式poll方式异步通知方式
无论使用哪个方式都需要有按键驱动程序通过这四种方式可以掌握一些驱动的基本技能中断、休眠、唤醒、poll等机制。
这些基本技能是驱动开发的基础其他大型驱动复杂的地方是它的框架及设计思想但是基本技能就只有这些。
查询方式 如上图所示查询方式的驱动模型这种方式最简单这里并不考虑驱动层中的架构只看驱动层所做的工作。
在驱动程序中构造并注册一个file_operations结构体里面提供对应的drv_open和drv_read函数当应用层调用open系统调用时在驱动层的drv_open函数中配置相应的引脚为输入引脚。
当应用层调用read系统调用时在驱动层的drv_read函数中读取该GPIO引脚的寄存器把引脚的状态返回给应用层。 读取引脚状态时直接返回寄存器中的值没有其他多余的动作。 休眠唤醒方式 如上图所示休眠唤醒方式的驱动模型在驱动层中的drv_open函数中除了要把GPIO设置为输入引脚还有注册GPIO的中断处理函数。
当应用层调用read系统调用时在驱动层的drv_read驱动函数中
如果有按键数据则直接返回给应用层。如果没有按键数据则应用层的APP在内核态休眠。
当用户按下按键时GPIO中断被触发导致drv_open中注册的中断服务程序被执行在中断服务程序中
记录按键数据。唤醒休眠中的应用层APP。
应用层的APP被唤醒以后继续在内核态运行即执行驱动层代码把中断服务程序中记录的按键数据返回给应用层的APP。 没有读取到数据时就会休眠直到有按键数据到来才被唤醒。 poll方式
上面的休眠-唤醒方式存在一个缺点如果用户一直没有按下按键那么应用层的APP就永远休眠阻塞不再执行了所以可以给APP定个闹钟这就是poll方式 如上图所示poll驱动模型poll是应用层实现多路转接的系统调用接口在驱动层的file_operations结构体中同样有一个poll函数指针 如上图所示file_operations结构体的定义所以当应用层的APP调用poll系统调用时会调用到驱动层该结构体中poll函数指针指向的函数。
所以需要我们在驱动层去定义poll函数指针指向的函数使得整个驱动层符合poll驱动模型。驱动层总体步骤为
注册file_operations结构体里面提供openreadpoll等驱动层的函数。应用层APP调用open时驱动层的drv_open会将GPIO设置为输入引脚并且注册中断处理函数。应用层APP调用poll/select时意图是查询按键数据是否就绪并且可以指定一个超时时间 当按键数据就绪时驱动层的poll向应用层返回就绪状态APP继续使用read读取按键数据。当按键数据没有就绪时驱动层的poll就会在内核态休眠一段时间。
当APP被唤醒时有两种情况
在休眠期间硬件按键被按下按键数据就绪。超时时间到了硬件按键仍然没有按下按键数据没有就绪。
被唤醒后进行判断如果是数据就绪被唤醒则调用read从按键的寄存器中读取按键数据如果是超时被唤醒则不调用read去读取了。 poll/select起到监视事件就绪的作用驱动层的drv_poll都会告诉应用层APP所监视事件的状态。APP根据驱动层告知的事件状态进行下一步动作。 异步通知方式 如上图所示异步通知方式在该模型中应用层在打开要操作的设备时要调用fcntl设置其fd的FASYNC标志此时会调用驱动层的drv_fasync函数 如上图所示在file_operations结构体中也有一个fsync函数指针在该模型中该指针指向的函数只需要记录当前进程的PID。
除了设置给fd设置FASYNC表示异步通知外还需要使用signal系统调用注册信号处理函数my_func。
此时该模型的处理步骤为
APP调用open配置GPIO引脚为输入方式并注册中断服务函数。APP调用fcntl设置fd指向的文件为异步通知方式并且注册信号处理函数.当硬件按键被按下时中断服务程序会给记录下来的进程PID表示的进程发送信号信号递达后执行注册的my_func信号处理函数。在信号处理函数中调用read来读取按键数据此时必然是有按键数据的。 在没有按键按下时APP正常执行当按键按下后立刻去读取按键数据使得应用层实现了中断的处理方式。 我们的驱动程序可以实现上述 4 种提供按键驱动的方法但是驱动程序不应该限制APP使用哪种方法。 这就是驱动设计的一个原则只提供能力不提供策略。
就是说APP想用哪种方法都行驱动程序都可以提供但是驱动程序不能限制APP使用哪种方法。
查询方式实现按键驱动
前面介绍了按键的四种驱动模型但是由于后面三种都涉及到中断方面的知识而到目前为止本喵还没有介绍驱动程序中的中断所以这里先仅用查询方式实现一下按键驱动程序 如上图所示采用简单的驱动层分层模型来实现查询方式的按键驱动层数
应用层open/read系统调用和驱动层的drv_open/drv_read通过file_operations结构体来建立联系。驱动层上层的drv_open/drv_read和驱动层下层的board_button_init/board_button_read通过button_opr结构体连建立联系。
驱动层下层的board_button_init/board_button_read由具体的单板提供
驱动层下层的board_button_init根据设备号确定哪个按键并将GPIO配置为输入引脚。驱动层下层的board_button_read根据设备号确定哪个按键并读取对应寄存器中的值返回引脚电平。
驱动层上层 如上图所示在button_operations.h中定义button_operations结构体
count表示按键设备个数init驱动层下层提供的初始化按键设备方法。read驱动层下层提供的读取按键状态方法。 如上图所示在button_drv.c中创建file_operations结构体并且用drv_open和drv_read初始化open和read函数指针
在drv_open函数中使用p_button_opr结构体中的init根据次设备号进行初始化。在drv_read函数中使用p_button_opr结构体中的read根据次设备号读取按键状态。 将读取到的按键状态level拷贝到用户层缓冲区。 如上图所示在入口函数button_init中使用register_chrdev向内核中注册file_operations结构体并且获得主设备号。还要创建button_class设备类来提供设备信息。
在出口函数button_exit中销毁设备类button_class并且使用unregister_chrdev函数从内核中将前面注册的file_operations移除。 如上图由于p_button_opr结构体指针是由驱动层下层提供的所以驱动层上层要提供一个register_button_operations函数给下层让下层向上层注册p_button_opr结构体。
在注册时还要将所有按键设备使用device_create在文件系统中创建设备节点文件。在卸载时使用device_destroy将文件系统中的所有按键设备文件移除掉。
由于下层在使用这两个函数时会用到上层的button_class类所以这两个函数需要使用EXPORT_SYMBOL导出给下层供下层先使用。 如上图最后完善一下设备信息告诉内核哪个是入口函数哪个是出口函数并且声明该驱动程序使用GPL协议。
驱动层下层 如上图所示在驱动层下层的board_button.c文件中创建button_operations结构体并进行初始化
count按键设备有两个。init初始化按键设备的函数board_button_init。read读取按键设备状态的函数board_button_read。
在入口函数中使用驱动层上层提供的register_button_operations函数将下层创建的my_button_oprs结构体对象注册到上层供上层使用下层提供的初始化和读取数据的方法。
在出口函数汇中使用上层提供的unregister_button_operations函数移除在文件系统中创建的设备节点文件。
最后完善一下设备信息。
对于驱动层下层重点在于初始化和读取数据函数的实现 如上图所示IMX6ULL按键的电路原理图
KEY1与GPIO5_1相连按键按下时是低电平(0)未按下时是高电平(1)。KEY2与GPIO4_IO14相连按键按下时是低电平(0)未按下时是高电平(1)。
使能GPIO 如上图所示使能GPIO的寄存器
CCM_CCGR1物理地址是0x020C406C其中的[31,30]控制GPIO5的使能但是这里保留了GPIO5默认使能CCM_CCGR3物理地址是0x020C4074其中的[13,12]控制GPIO的使能当这两个比特位为11时GPIO4使能。
选择GPIO模式 如上图所示IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1寄存器
物理地址是0x0229000C。MUX_MODE这四个bit为101时表示GPIO5_IO01引脚用作通用GPIO。 如上图所示IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B寄存器
物理地址是0x020E01B0。MUX_MODE这四个bit为0101时表示GPIO4_IO14引脚用作通用GPIO。
设置GPIO方向 如上图所示内存映射表
GPIO5该组寄存器的基地址是0x020AC000。GPIO4该组寄存器的基地址是0x020A8000。 如上图所示GPIO所有寄存器的内存映射表以GPIO4为例
一共8个寄存器每组GPIO都是这样。从DR寄存器开始到EDGE_SEL寄存器结束地址从低到高每个寄存器所占4个字节。
所以定义一个结构体来描述GPIO组中的所有寄存器 如上图所示结构体用该结构体创建gpio5和gpio4结构体对象来操作相应的GPIO。 如上图所示GDIR寄存器
对于GPIO5_1将gpio5-gdir的bit1设置为0表示输入。对于GPIO4_14将gpio4-gdir的bit14设置为0表示输入。
读取按键状态 如上图所示PSR寄存器
对于GPIO5_1gpio5-psr的bit1为0表示按键按下为低电平为1表示按键没有按下为高电平。对于GPIO4_14gpio4-gdir的bit14为0表示按键按下为低电平为1表示按键没有按下为高电平。
编程 如上图所示在驱动层下层board_button.c中将用到的寄存器全部定义出来并且创建gpio4和gpio5两个结构体变量来表示GPIO。
board_button_init 如上图所示初始化函数中
将所有涉及到的寄存器都在内存中映射相应的虚拟地址只映射一次。 其中GPIO组进行整体映射大小为struct imx6ull_gpio结构体的大小。 根据次设备号对GPIO口进行初始化控制相关寄存器。 使能GPIO组设置引脚模式为通用GPIO设置方向为输入。
board_button_read 如上图所示读取按键数据的函数根据次设备号确定读取gpio5还是gpio4中的psr寄存器然后返回该寄存器中的值。
应用层测试函数 如上图应用层测试函数在测试的时候命令行中输入./button_test /dev/BigMiaomi_button0或者./button_test /dev/BigMiaomi_button1在mian函数中会使用read系统调用去获取按键状态最终会调用驱动层下层的board_button_read函数。
如果打印1表示按键没有按下。如果打印0表示按键按下。 如上图所示Makefile文件中make以后
会生成button_test可执行程序用来测试。会生成button_drv.ko和board_button.ko两个模块文件用来安装驱动程序。 如上图所示在开发板上安装两个按键的驱动程序可以看到在./dev目录下有BigMiaomi_button0和BigMiaomi_button1两个设备节点。 如上图所示在开发板上执行测试程序
未在开发板上按下KEY1和KEY2两个按键时打印出的值是1表示高电平和电路逻辑相符。按下开发板上按下KEY1和KEY2两个按键时打印出的值是0表示低电平和电路逻辑相符。
总结
要会使用设备树向内核中注册设备节点并且会对驱动程序做相应的修改。除此之外要知道APP读取按键的四种方式以及实现简单的APP按键驱动程序编程。