Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)
Linux嵌入式驱动开发02——驱动编译到内核
Linux嵌入式驱动开发03——杂项设备驱动(附源码)
Linux嵌入式驱动开发04——应用层和内核层数据传输
Linux嵌入式驱动开发05——物理地址到虚拟地址映射
Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写
Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)
Linux嵌入式驱动开发08——字符设备(步步为营)
Linux嵌入式驱动开发09——平台总线详解及实战
Linux嵌入式驱动开发10——设备树开发详解
Linux嵌入式驱动开发11——平台总线模型修改为设备树实例
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作
Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)
Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)
Linux嵌入式驱动开发15——等待队列和工作队列
Linux嵌入式驱动开发16——按键消抖实验(内核定时器)
Linux嵌入式驱动开发17——输入子系统
Linux嵌入式驱动开发18——I2C通信
input事件结构体的定义如下,在Linux内核的include\linux\input.h文件中
struct timeval {
__kernel_time_t tv_sec; /* seconds,32bit */
__kernel_suseconds_t tv_usec; /* microseconds,32bit */
};
struct input_event {
struct timeval time; //时间
__u16 type; //事件类型
__u16 code; //事件键值
__s32 value; //值
};
可以看到,每一个input事件都有一个时间,包含32位的秒,32位的微妙
此外还有16位的type(事件类型),16位的code(事件键值),32位的value(值)
好,现在又有疑惑,type、code、value具体是什么?
type
type是指事件类型,在include\linux\input.h文件中定义了一系列的事件类型
#define EV_SYN 0x00 //同步事件,用于分隔事件
#define EV_KEY 0x01 //按键事件,例如按键、鼠标按键、触摸屏按键
#define EV_REL 0x02 //相对位置事件,常见于鼠标坐标
#define EV_ABS 0x03 //绝对位置事件,常见于触摸屏坐标
一个设备可以有多种类型的事件,例如鼠标点击按键时会上报按键事件,移动时会上报相对位置事件
code
code指事件的键值,在事件类型中的子事件,每一个事件类型都有其对应的一系列键值
例如按键事件,那么你这个按键表示按键1还是按键2还是鼠标左键
例如绝对位置事件,那么你上报的这个事件是指X轴还是Y轴
在include\linux\input.h文件中定义了一系列的事件键值
按键事件的键值
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
...
#define KEY_A 30
#define KEY_S 31
#define KEY_D 32
...
相对位置事件的键值
#define REL_X 0x00 //x轴
#define REL_Y 0x01 //y轴
#define REL_Z 0x02 //z轴
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
绝对位置事件
#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
...
#define ABS_PRESSURE 0x18
...
用到的重点就是input_dev结构体,先找到结构体所在的目录
/include/linux/input.c
test_dev = input_allocate_device();
test_dev->name = "test_key";
__set_bit(EV_KEY, test_dev->evbit);//支持按键
__set_bit(KEY_1, test_dev->keybit);//支持哪些按键
/*注册输入设备*/
ret = input_register_device(test_dev);
/*超时处理函数*/
static void timer_function(unsigned long data)
{
int value;
value = !gpio_get_value(gpio_name); //获取gpio值
input_report_key(test_dev, KEY_1, value); //上报按键数据
input_sync(test_dev); //上报一个同步事件
}
没有安装模块前查看信息
ls /dev/input/
cat /proc/bus/input/devices
cat /proc/bus/input/devices
ls /dev/input/
设备驱动也可以看到多出来了一个event1,这就是我们添加的输入设备
然后来验证输入设备是否工作正常
hexdump /dev/input/event1
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
int value;
struct input_event test_event;
fd = open("/dev/event1", O_RDWR); // 打开节点时候触发open函数
if(fd < 0){
perror("open error\n"); // perror在应用中打印
return fd;
}
while(1){
read(fd, &test_event, sizeof(test_event));
if(test_event.type == EV_KEY){
printf("type is %#x\n", test_event.type);
printf("code is %#x\n", test_event.code);
printf("value is %#x\n", test_event.value);
}
}
return 0;
}
但是在板子上运行后,出现错误,显示open error
回头看app的代码,发现open路径出现错误,正确的应该是
/dev/input/event1
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct input_dev *test_dev;
struct device_node *test_device_node;
int gpio_name; // gpio编号
int irq; // 中断号
static void timer_function(unsigned long data);
/* 使用DEFINE_TIMER宏
* 第一个参数:变量名
* 第二个参数:超时处理函数
* 第三个参数:传递给超时处理函数的参数
* 第四个参数:到点时间,一般在启动定时器前需要重新初始化
* */
DEFINE_TIMER(test_timer, timer_function, 0, 0);
/*超时处理函数*/
static void timer_function(unsigned long data)
{
int value;
value = !gpio_get_value(gpio_name); //获取gpio值
input_report_key(test_dev, KEY_1, value); //上报按键数据
input_sync(test_dev); //上报一个同步事件
}
/*中断处理函数*/
irq_handler_t test_key_handle(int irq, void *args) // 中断处理函数
{
printk("test_key_handle ok!!!\n");
test_timer.expires = jiffies + msecs_to_jiffies(20); // 设置定时时间定时20ms
add_timer(&test_timer); // 启动定时器
return IRQ_HANDLED; // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}
/*probe函数*/
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
printk("beep_probe ok!!!\n");
/*间接获取设备节点信息*/
/********查找指定路径的节点***********/
test_device_node = of_find_node_by_path("/test_key"); // 节点名字叫做test_key
if(test_device_node == NULL) {
printk("of_find_node_by_path error!!!\n");
return -1;
}else {
printk("of_find_node_by_path ok!!!\n");
printk("test_device_node name is %s\n", test_device_node->name);
}
gpio_name = of_get_named_gpio(test_device_node, "gpios", 0); //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
if(gpio_name < 0) {
printk("of_get_named_gpio error!!!\n");
return -1;
}else{
printk("of_get_named_gpio ok!!!\n");
}
gpio_direction_input(gpio_name); // 因为是模拟按键,方向设置成输入模式
// irq = gpio_to_irq(gpio_name); // 通过gpio函数获取中断号,参数是gpio编号
/* irq_of_parse_and_map,通过设备树中interrupts获取中断号
* 第一个参数:设备树节点
* 第二个参数:索引值,这里只有一个,所以是0
* */
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d\n", irq);
/* request_irq
* 第一个参数:中断号,
* 第二个参数:中断处理函数,
* 第三个参数:中断标志(边沿触发方式),
* 第四个函数:中断名字,现在按键要检测按下和弹起的状态,所以双边沿触发
* 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数
*/
ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "test_key", NULL);
if(ret < 0) {
printk("request_irq failed!!!\n");
return -1;
}else{
printk("request_irq successful!!!\n");
}
test_dev = input_allocate_device();
test_dev->name = "test_key";
__set_bit(EV_KEY, test_dev->evbit);//支持按键
__set_bit(KEY_1, test_dev->keybit);//支持哪些按键
/*注册输入设备*/
ret = input_register_device(test_dev);
if(ret < 0) {
printk("input_register_device failed!!!\n");
goto error_input_register; // 失败时候应该是直接释放
}else{
printk("input_register_device successful!!!\n");
}
return 0;
error_input_register:
input_unregister_device(test_dev);
}
const struct platform_device_id beep_id_table = {
.name = "keys",
};
int beep_remove(struct platform_device *pdev){
printk("beep_remove ok!!!\n");
return 0;
}
const struct of_device_id of_match_table_test[] = {
{.compatible = "keys"},
{} // 不写会提示警告
};
struct platform_driver beep_device = {
.probe = beep_probe, // 这个probe函数其实和 device_driver中的是一样的功能,但是一般是使用device_driver中的那个
.remove = beep_remove, // 卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
.driver = {
.owner = THIS_MODULE,
.name = "keys",
.of_match_table = of_match_table_test, // 最优先匹配of_match_table其次是id_table最后是name
}, // 内置的device_driver 结构体
.id_table = &beep_id_table,
};
static int beep_driver_init(void)
{
int ret = 0;
printk("beep_driver_init ok!!!\n"); // 在内核中无法使用c语言库,所以不用printf
ret = platform_driver_register(&beep_device);
if(ret < 0){
printk("platform_driver_register error!!!\n");
return ret;
}else{
printk("platform_driver_register ok!!!\n");
}
return 0;
}
static void beep_driver_exit(void)
{
printk("beep_driver_exit bye!!!\n");
free_irq(irq, NULL);
del_timer(&test_timer);
input_unregister_device(test_dev);
platform_driver_unregister(&beep_device);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可