基于总线设备驱动模型的按键读取驱动程序

news/2024/10/4 17:26:33

本次实验基于总线设备驱动模型实现按键驱动程序的编写,给上层应用程序提供检测按键是否按下的操作接口,上层应用根据按键是否按下控制led的亮灭。所以上层应用程序会同时使用led和按键的驱动接口,但是对于下层驱动而言,这二者是分离的,因此只需要专注于编写按键驱动程序就可以了。
在正点原子imx6ull开发板上按键原理图如下:

按键KEY0接到的引脚名字是uart1_cts,查找芯片参考手册第28章(GPIO章节),找到下面这个表

从表中找到GPIO1_IO18这一项,写着UART1_CTS_B,跟原理图上引脚名一致,说明这是按键Key0对应的引脚。我们要读取按键是否按下,因此我们需要把UART1_CTS_B这个引脚也即GPIO1_IO18设置为GPIO功能(GPIO功能代指普通的输入输出功能)。与led驱动程序的步骤类似,我们需要查看GPIO章节中有关引脚的原理图,其涉及到IOMUXC控制器、CMM时钟控制器,以及相关的GPIO控制器。
(1)CCM时钟控制器

在CCM控制器章节找到CCGR1寄存器的CG13位域控制GPIO1模块的时钟使能,接着在CCM章节的寄存器映射小节找到寄存器地址(20C_406Ch)及各位域的配置方式

(2)IOMUC复用控制器
在IOMUXC控制器章节找到复用寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B(20E_008Ch)

(2)GPIO控制器
需要将GPIO设置为输入功能,因此涉及GDIR和PSR寄存器,其中GDIR将GPIO配置为输入,PSR寄存器用于读取引脚输入状态

在GPIO章节找到上表可以查看到GPIO1相关的寄存器

这里插入话题GPIO是什么,为什么叫GPIO:
GPIO全称是General Purpose Input / Output 通用目的输入输出。既然是通用目的(General Purpose),那说明它可以用于不同的功能,比如有些引脚输出能力强,可以作为输出,这时候它就被用作最一般的输出功能,但是并不意味着它只能做普通输入输出引脚,它也可以复用为其它功能(具体查看芯片手册)。又比如本次实验中按键接到的引脚名字叫UART1_CTS,说明它的主要功能是用于UART串口,但是不代表不能用于输入,本实验就用它作为按键输入。芯片就是通过输入/输出数据来提供功能的,所有引脚都是输入/输出数据,也因此才叫IO,但是同一个引脚可能可以被复用为多种功能,比如UART,PWM,SPI,因此它是通用的,也即General Purpose,所以合起来才叫GPIO。更细致地分类解释的话,所有的引脚都具有输入/输出功能,它们都属于GPIO,但是如果某一个引脚只是单纯的用于输入/输出,比如LED的引脚GPIO1_IO03,这个引脚具有较强的输出能力,一般就用于输出,并且这个引脚属于GPIO1组的第3个IO引脚,那么这个引脚就命名为GPIO1_IO03;再比如按键Key0的引脚一般情况下输出能力较弱,会作为UART串口功能,那么这个引脚就命名UART1_CTS,但是它实际上是GPIO1组的第18个IO引脚,因此它是GPIO1_IO18,但是命名为UART1_CTS。

分析完硬件和寄存器部分后,开始写驱动代码,分为三部分:设备树、下层驱动代码,上层驱动框架

1、设备树
在根节点下添加如下节点

点击查看代码
mykey0 {compatible = "key0_driver";pin = <GROUP_PIN(1, 18)>;pin_reg = <0x20C406C	/*CCM_CCGR1*/0x20E008C	/*IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B*/0x209C004	/*GPIO1_GDIR*/0x209C008	/*GPIO1_PSR*/	>;};
在上面的设备树节点中,compatible用于匹配驱动,其余的pin和pin_reg是自定义属性,保存引脚的相关寄存器信息,可以在驱动代码里面的probe函数里读取设备树中的属性信息来获取寄存器地址。

2、包含platform_driver结构体的代码
这部分就是下层涉及具体硬件的操作函数,除了填充platform_driver结构体的内容外,还要向上层提供具体设备的操作函数,比如硬件的初始化、控制,同时在probe函数中调用上层接口为每一个匹配的设备注册文件视图。具体的代码如下:

头文件button_rsc.h

点击查看代码
#ifndef __BUTTON__RSC__H
#define __BUTTON__RSC__H#define GROUP_PIN(g, p) ((g << 16) | (p))   //转换成引脚编号
#define PIN(x)  (x & 0xffff)    //获取引脚属于哪一个
#define GROUP(x)    ((x >> 16) & 0xffff)    //获取引脚属于哪一组struct button_resource{int group_pin;  //[31:16]存放组,[15:0]存放//定义变量存放涉及到的寄存器的地址unsigned int CCM_CCGR1;unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;unsigned int GDIR;unsigned int DR; unsigned int PSR;
};struct button_registers_after_map{unsigned int* CCM_CCGR1;unsigned int* IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;unsigned int* GDIR;unsigned int* DR; unsigned int* PSR;
};#endif

头文件button_opr.h

点击查看代码
#ifndef __BUTTON_OPR__H
#define __BUTTON_OPR__Hstruct button_operations{int (*init) (unsigned int which);    //初始化引脚,which表示引脚编号int (*read) (unsigned int which);   //读取引脚电平状态,which表示引脚编号
};#endif

头文件button_drv.h

点击查看代码
#ifndef __BUTTON_DRV_H__
#define __BUTTON_DRV_H__#include "button_opr.h" //如果在board_button.c中button_drv头文件的包含在button_opr前面的话,这里就需要先包含button_opr.h,否则会有警告,因为struct button_operations定义在button_opr.h
void button_device_register(int minor);
void button_device_unregister(int minor);
void register_button_operations(struct button_operations *button_opr);#endif

board_button.c文件

点击查看代码
/*
下层按键驱动程序,实现具体的硬件操作
(1)实现按键GPIO引脚的操作函数,包括初始化函数、读取按键状态函数,通过结构体指针形式向上层注册
(2)实现platform_driver结构体,从设备树生成的platform_device获取寄存器资源,并提供匹配和卸载时的probe和remove函数,还有其他属性
(3)实现驱动入口/出口函数
*/#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "asm/delay.h"
#include "linux/ioport.h"
#include "linux/mod_devicetable.h"
#include "linux/of.h"
#include "linux/platform_device.h"
#include "asm/io.h"
#include <linux/delay.h>#include "button_rsc.h"
#include "button_drv.h"
#include "button_opr.h"int cur_counts = 0;
struct button_resource g_buttons[10];
struct button_registers_after_map g_buttons_map_addr[10];/*
构造操作函数结构体,为上层驱动框架提供实体操作函数
*/
int button_init(unsigned int which)    //初始化引脚,which表示引脚编号
{/*根据which初始化GPIO,其中which是次设备号,也是g_buttons中保存资源结构体的下标*/unsigned int val;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);//地址映射if(!g_buttons_map_addr[which].CCM_CCGR1){g_buttons_map_addr[which].CCM_CCGR1 = ioremap(g_buttons[which].CCM_CCGR1, 4);g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B = ioremap(g_buttons[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B, 4);g_buttons_map_addr[which].GDIR = ioremap(g_buttons[which].GDIR, 4);g_buttons_map_addr[which].PSR = ioremap(g_buttons[which].PSR, 4);}//使能时钟val = *g_buttons_map_addr[which].CCM_CCGR1;val |= (0x3 << 26);*(g_buttons_map_addr[which].CCM_CCGR1) = val;/*配置GPIO1_IO18为GPIO模式*/val = *g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;val &= (~0xf);  //低四位先清零val |= (0x5);   //设置为GPIO模式*(g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B) = val;/*设置GPIO1_IO18为输出*/*(g_buttons_map_addr[which].GDIR) &= ~(0x1 << 18);return 0;
}int button_read(unsigned int which)  //读取引脚电平状态,which表示引脚编号
{// printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);if(!(*(g_buttons_map_addr[which].PSR) & (1 << 18))){//udelay(10000);  //延时10ms  参数太大,udelay不允许,改为mdelaymdelay(10);if(!(*(g_buttons_map_addr[which].PSR) & (1 << 18)))return 0;}return 1;
}struct button_operations button_oprs = {.init = button_init,.read = button_read,
};int board_button_probe(struct platform_device *pdev)
{struct device_node *np;int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);//获取device_node结构体np = pdev->dev.of_node;if(!np){printk("device_node error\n");return -1;}//获取寄存器信息err = of_property_read_u32(np, "pin", &g_buttons[cur_counts].group_pin);err = of_property_read_u32_index(np, "pin_reg", 0, &g_buttons[cur_counts].CCM_CCGR1);err = of_property_read_u32_index(np, "pin_reg", 1, &g_buttons[cur_counts].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B);err = of_property_read_u32_index(np, "pin_reg", 2, &g_buttons[cur_counts].GDIR);err = of_property_read_u32_index(np, "pin_reg", 3, &g_buttons[cur_counts].PSR);//创建设备节点,使用上层驱动提供的函数button_device_register(cur_counts);//节点数加1cur_counts++;return 0;
}int board_button_remove(struct platform_device *pdev)
{int i;int err;struct device_node* np;int button_pin;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);//获取device_node结构体np = pdev->dev.of_node;err = of_property_read_u32(np, "pin", &button_pin);for(i = 0; i < cur_counts; i++){if(g_buttons[i].group_pin == button_pin){button_device_unregister(i);g_buttons[i].group_pin = -1;break;}}for(i = 0; i < cur_counts; i++){if(g_buttons[i].group_pin != -1){break;}}if(i == cur_counts){cur_counts = 0;}return 0;
}static struct of_device_id mykeys[] = {{.compatible = "key0_driver"},{},
};struct platform_driver button_drv = {.probe = board_button_probe,.remove = board_button_remove,.driver = {.name = "key",.of_match_table = mykeys,},
};static int __init button_driver_init(void)
{int err;err = platform_driver_register(&button_drv);    //注册平台驱动//调用上层接口注册、向上层驱动提供操作函数register_button_operations(&button_oprs);return 0;
}static void __exit button_driver_exit(void)
{platform_driver_unregister(&button_drv);
}module_init(button_driver_init);
module_exit(button_driver_exit);MODULE_LICENSE("GPL");

3、上层驱动框架

点击查看代码
/*
按键驱动程序上层框架,主要实现:
(1)file_operations结构体内容实现
(2)添加驱动入口/出口函数,在入口函数注册驱动,在出口函数撤销驱动
(3)提供下层驱动向上层注册的接口:创建设备节点、删除设备节点、下层向上层注册按键相关的操作函数
*/#include "asm/memory.h"
#include "asm/uaccess.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/kdev_t.h"
#include "linux/printk.h"
#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <asm/io.h>#include "button_opr.h"
int major;  //主设备号
struct class* button_class; //设备类
struct button_operations* p_button_opr;void button_device_register(int minor)
{device_create(button_class, NULL, MKDEV(major, minor), NULL, "key_%d", minor);
}void button_device_unregister(int minor)
{device_destroy(button_class, MKDEV(major, minor));
}void register_button_operations(struct button_operations* button_opr)
{p_button_opr = button_opr;
}EXPORT_SYMBOL(button_device_register);
EXPORT_SYMBOL(button_device_unregister);
EXPORT_SYMBOL(register_button_operations);int button_open(struct inode * node, struct file * file)
{int which;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);which = iminor(node);p_button_opr->init(which);return 0;
};ssize_t button_read (struct file *file, char __user *user, size_t count, loff_t *start)
{struct inode *node;unsigned int which;int ret;int err;// printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);node = file_inode(file);which = iminor(node);ret = p_button_opr->read(which);err = copy_to_user(user, &ret, 1);return ret;
};int button_close (struct inode *node, struct file *file)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}struct file_operations button_opr = {.owner = THIS_MODULE,.open = button_open, .read = button_read,.release = button_close};static int __init button_init(void)
{/*驱动入口函数(1)注册驱动;(2)创建类*/int err;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(major, "button_driver", &button_opr);button_class = class_create(THIS_MODULE, "button_class");err = PTR_ERR(button_class);if (IS_ERR(button_class)){unregister_chrdev(major, "button_driver");return -1;}return 0;
}static void __exit button_exit(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);class_destroy(button_class);unregister_chrdev(major, "button_driver");
}module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");

4、应用程序,按键按下灯亮,再按下灯灭,如此反复

点击查看代码
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(int argc, char** argv)
{int fd_button, fd_led;char val;char state;/*判断参数*/if(argc != 3){printf("Usage: %s <buton_dev> <led_dev>\n", argv[0]);}fd_button = open(argv[1], O_RDWR);fd_led = open(argv[2], O_RDWR);if(fd_button == -1 || fd_led == -1){printf("can not open file %s\n", argv[1]);return -1;}/*读文件, 控制led灯*/state = 0;write(fd_led, &state, 1);while(1){read(fd_button, &val, 1);if(val == 0){usleep(10000);read(fd_button, &val, 1);if(val == 0){state = ~state;}write(fd_led, &state, 1);// printf("led state: %d", state);sleep(1);}}close(fd_button);close(fd_led);return 0;
}

至此,按键驱动程序完成。本次驱动的编写与led驱动程序类似,可以看作是一次巩固。

使用按键控制led亮灭看似很简单的功能,但是以此为基础探究Linux的知识却可以很深入。比如使用中断方式判断按键是否按下、灯和按键是两个事件,可以引申出进程线程,进而基于此实现进程线程的通信、同步,消息机制、互斥等等。而且任何嵌入式设备功能的开发,本质都是操作IO操作寄存器,和点灯、按键没有本质的区别,只不过框架稍微复杂一些,关键在linux的那些知识。
接下来要深入学习pinctrl和gpio子系统,这部分在嵌入式linux开发中使用率很高,属于提供工作效率的工具,应该要掌握,预计花费1-2个月的时间学习掌握。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/28901.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

线~段~树

点击查看代码 #include<bits/stdc++.h> #define lson id<<1 #define rson id<<1|1 using namespace std; const int N=1e6+12000; struct node{int l,r,num;int ma,mi; }tr[N<<2]; int a[N]; int n,m; string str; int ans=0; int from,to; void build…

2024 年 5 月 7 日 周二 晴 常(324 字)

正文早上两头跑应付工作时,客户部的同事说我像被吸干了阳气。没办法啊,觉没睡够不就应该这样吗…… 休息好了肯定不这样。另外,才知道这周六补班,那一瞬间有些想死(笑。文竹的末端叶子好像还是没有变绿呢。有些担心。或许应该有点耐心?鱼儿的手机似乎坏了,于是也买了一个…

一种光电容积波PPG 转换到心电图ECG进行房颤检测的神经网络模型

具体的软硬件实现点击 http://mcu-ai.com/ MCU-AI技术网页_MCU-AI人工智能 光电体积描记法(PPG)是一种经济有效的非侵入性技术,利用光学方法测量心脏生理学。 PPG 在健康监测领域越来越受欢迎,并用于各种商业和临床可穿戴设备。与心电图(ECG)相比,PPG 并没有提供实质性的…

使用Selenium做网站登录的免验证

我发现,我已经三年多没有更新博客了。这几年一直感觉没什么可写的,工作上没遇到的问题python的不多,主要是前端页面上遇到的问题,感觉写起来比较困难,一写就要贴上去很多代码,还没什么必要,不贴又说不明白,所以干脆不写了。今年换了工作,开始研究新玩意儿了——爬虫。…

线程基本概念

1.进程与线程 1.1 进程进程是资源分配的单位,系统在运行时会为每个进程分配不同的内存区域 1.2 线程线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。一个Java应用程序java.exe,其实至少有三个线程:main()主线程(受异常影响),gc()…

在Gitlab Runner中调用Web Api写入Directory.Build.props需要的版本号文件

摘要 本文介绍了在Windows上的Gitlab Runner,如何调用web api更新版本号定义文件。 PowerShell function Update-Version {param ([string]$WEB_API_URL,[string]$NAMESPACE,[string]$VERSION_ID)echo "能生成或写入.props文件的web api的网站地址:"$WEB_API_URL …

.Net Core中使用RabbitMQ

开发中经常用到发布订阅的功能,之前一直用的Redis,使用过程中也出现了一些问题,后来换了RabbitMQ,用上去更顺手,简单记录一下。 正文开始: RabbitMQ是一个开源的,基于AMQP(Advanced Message Queuing Protocol)协议的完整的可复用的企业级消息队,RabbitMQ可以实现点对点,发…