Linux驱动开发(十)---树莓派输入子系统学习(红外接收)
前文回顾
《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
继续宣传一下韦老师的视频
70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】
这次借助学习红外遥控的学习,学习一下输入子系统的相关知识。
硬件相关
用的是一个vs1838的模块接收,开发的就是接收程序的驱动,发送用普通遥控器即可。
用到的协议是NEC协议,简单介绍一下
NEC编码的一帧(通常按一下遥控器按钮所发送的数据)由引导码、地址码及数据码组成,,如下图所示,把地址码及数据码取反的作用是加强数据的正确性。
引导码及数据的定义如下图所示,当一直按住一个按钮的时候,会隔110ms左右发一次引导码(重复),并不带任何数据
这些不是今天的重点,所以只需要了解即可。
连接的方法,就是三根线,数据,VCC和GND,还是用我们熟悉的GPIO17
设备树
这个设备树与按键的设备树一样,只有一个GPIO作为输入使用。
通过在GPIO上设置中断,捕获中断的时间,然后根据前面的NEC协议,来解析出每个字节的数据,并进行校验。
当然,这也不是今天的重点。
驱动中的重点内容
模块挂载,卸载这些就不说了,都一样。主要是在probe函数中,不再去注册设备,而是改为增加对输入子系统的使用。
static int vs1838_probe(struct platform_device *pdev)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 获得硬件信息 */vs1838_data_pin = gpiod_get(&pdev->dev, NULL, 0);if (IS_ERR(vs1838_data_pin)){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);}irq = gpiod_to_irq(vs1838_data_pin);request_irq(irq, vs1838_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "vs1838", NULL);/* 输入系统的代码 *//* 参考: drivers\input\keyboard\gpio_keys.c *//* A. 分配input_dev */vs1838_input_dev = devm_input_allocate_device(&pdev->dev);/* B. 设置input_dev */vs1838_input_dev->name = "vs1838";vs1838_input_dev->phys = "vs1838";/* B.1 能产生哪类事件 */__set_bit(EV_KEY, vs1838_input_dev->evbit);__set_bit(EV_REP, vs1838_input_dev->evbit);/* B.2 能产生哪些事件 *///__set_bit(KEY_0, vs1838_input_dev->keybit);memset(vs1838_input_dev->keybit, 0xff, sizeof(vs1838_input_dev->keybit));/* C. 注册input_dev */input_register_device(vs1838_input_dev);return 0;
}
理解一下这段代码
- 分配input_dev 并设置参数
vs1838_input_dev = devm_input_allocate_device(&pdev->dev);
vs1838_input_dev->name = "vs1838";
vs1838_input_dev->phys = "vs1838";
- 能产生哪类事件
设备驱动通过set_bit()告诉input子系统它支持哪些事件
set_bit(EV_KEY, vs1838_input_dev->evbit);
set_bit(EV_REP, vs1838_input_dev->evbit);
主要的事件类型包括
按键 | 值 | 说明 |
---|---|---|
EV_SYN | 0x00 | 事件间的分割标志,有些事件可能会在时间和空间上产生延续,比如持续按住一个按键。为了更好地管理这些持续的事件,EV_SYN 用以将他们分割成一个个的小的数据包 |
EV_KEY | 0x01 | 按键事件 |
EV_REL | 0x02 | 相对值,如光标移动,报告的是相对最后一次位置的偏移 |
EV_ABS | 0x03 | 绝对值,如触摸屏和操纵杆,它们工作在绝对坐标系统 |
EV_MSC | 0x04 | 不能匹配现有的类型,这相当于当前暂不识别的事件。比如在 Linux系统中按下键盘中针对 Windows 系统的“一键杀毒”按键,将会产生该事件 |
EV_SW | 0x05 | 显示设备开关状态 |
EV_LED | 0x11 | 用于控制设备上的 LED 灯的开关,比如按下键盘的大写锁定键,会同时产生 ”EV_KEY” 和 ”EV_LED” 两个事件 |
EV_SND | 0x12 | 显示声音效果 |
EV_REP | 0x14 | 重复 |
EV_FF | 0x15 | 力反馈事件 |
EV_PWR | 0x16 | 电源事件 |
EV_FF_STATUS | 0x17 | 力反馈状态事件 |
EV_MAX | 0x1f | |
EV_CNT | (EV_MAX+1) |
- 能产生哪些事件 ,0xff类似于掩码,表示按键值可以0~0xff。
memset(vs1838_input_dev->keybit, 0xff, sizeof(vs1838_input_dev->keybit));
- 注册input_dev
这些操作完成后,就会存在一个新的event文件。存在于/dev/input路径下
input_register_device(vs1838_input_dev);
最后,在获得到正确的button值之后,需要产生通知,val是按键值,上报一个确认并且上报一个停止
input_event(vs1838_input_dev, EV_KEY, val, 1);
input_event(vs1838_input_dev, EV_KEY, val, 0);
input_sync(vs1838_input_dev);
并且我们的程序可以很简单。
以下是完整代码
#include <linux/module.h>
#include <linux/poll.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/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/version.h>
#include <linux/input.h>static struct gpio_desc *vs1838_data_pin;
static int irq;
static unsigned int vs1838_data = 0; static u64 vs1838_edge_time[100];
static int vs1838_edge_cnt = 0;static struct input_dev *vs1838_input_dev;/* 0 : 成功, *val中记录数据* -1: 没接收完毕* -2: 解析错误*/
int vs1838_parse_data(unsigned int *val)
{u64 tmp;unsigned char data[4];int i, j, m;/* 判断是否重复码 */if (vs1838_edge_cnt == 4){tmp = vs1838_edge_time[1] - vs1838_edge_time[0];if (tmp > 8000000 && tmp < 10000000){tmp = vs1838_edge_time[2] - vs1838_edge_time[1];if (tmp < 3000000){/* 获得了重复码 */*val = vs1838_data;return 0;}}}/* 接收到了66次中断 */m = 3;if (vs1838_edge_cnt >= 68){/* 解析到了数据 */for (i = 0; i < 4; i++){data[i] = 0;/* 先接收到bit0 */for (j = 0; j < 8; j++){/* 数值: 1 */ if (vs1838_edge_time[m+1] - vs1838_edge_time[m] > 1000000)data[i] |= (1<<j);m += 2;}}/* 检验数据 */data[1] = ~data[1];if (data[0] != data[1]){printk("%s %s line %d, %x, %x, %x\n", __FILE__, __FUNCTION__, __LINE__, data[0], data[1], ~data[1]);return -2;}data[3] = ~data[3];if (data[2] != data[3]){printk("%s %s line %d, %x, %x, %x\n", __FILE__, __FUNCTION__, __LINE__, data[2], data[3], ~data[3]);return -2;}vs1838_data = (data[0] << 8) | (data[2]);*val = vs1838_data;return 0;}else{/* 数据没接收完毕 */return -1;}
}static irqreturn_t vs1838_isr(int irq, void *dev_id)
{unsigned int val;int ret;//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0))vs1838_edge_time[vs1838_edge_cnt++] = ktime_get_boottime_ns();
#elsevs1838_edge_time[vs1838_edge_cnt++] = ktime_get_boot_ns();
#endif/* 判断超时 */if (vs1838_edge_cnt >= 2){if (vs1838_edge_time[vs1838_edge_cnt-1] - vs1838_edge_time[vs1838_edge_cnt-2] > 30000000){/* 超时 */vs1838_edge_time[0] = vs1838_edge_time[vs1838_edge_cnt-1];vs1838_edge_cnt = 1; return IRQ_HANDLED; // IRQ_WAKE_THREAD;}}ret = vs1838_parse_data(&val);if (!ret){/* 解析成功 */vs1838_edge_cnt = 0;//printk("get ir code = 0x%x\n", val);val=val&0xff;/* D. 输入系统: 上报数据 */input_event(vs1838_input_dev, EV_KEY, val, 1);input_event(vs1838_input_dev, EV_KEY, val, 0);input_sync(vs1838_input_dev); //input_event(vs1838_input_dev, EV_SYN, 0, 0);}else if (ret == -2){/* 解析失败 */vs1838_edge_cnt = 0;}return IRQ_HANDLED; // IRQ_WAKE_THREAD;
}/* 1. 从platform_device获得GPIO* 2. gpio=>irq* 3. request_irq*/
static int vs1838_probe(struct platform_device *pdev)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 获得硬件信息 */vs1838_data_pin = gpiod_get(&pdev->dev, NULL, 0);if (IS_ERR(vs1838_data_pin)){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);}irq = gpiod_to_irq(vs1838_data_pin);request_irq(irq, vs1838_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "vs1838", NULL);/* 输入系统的代码 *//* 参考: drivers\input\keyboard\gpio_keys.c *//* A. 分配input_dev */vs1838_input_dev = devm_input_allocate_device(&pdev->dev);/* B. 设置input_dev */vs1838_input_dev->name = "vs1838";vs1838_input_dev->phys = "vs1838";/* B.1 能产生哪类事件 */set_bit(EV_KEY, vs1838_input_dev->evbit);set_bit(EV_REP, vs1838_input_dev->evbit);/* B.2 能产生哪些事件 *///__set_bit(KEY_0, vs1838_input_dev->keybit);memset(vs1838_input_dev->keybit, 0xff, sizeof(vs1838_input_dev->keybit));/* C. 注册input_dev */input_register_device(vs1838_input_dev);return 0;
}static int vs1838_remove(struct platform_device *pdev)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);input_unregister_device(vs1838_input_dev);free_irq(irq, NULL);gpiod_put(vs1838_data_pin);return 0;
}static const struct of_device_id ask100_vs1838[] = {{ .compatible = "pgg,vs1838" },{ },
};/* 1. 定义platform_driver */
static struct platform_driver vs1838_driver = {.probe = vs1838_probe,.remove = vs1838_remove,.driver = {.name = "myvs1883_drv",.of_match_table = ask100_vs1838,},
};/* 2. 在入口函数注册platform_driver */
static int __init vs1838_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = platform_driver_register(&vs1838_driver); return err;
}/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数* 卸载platform_driver*/
static void __exit vs1838_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);platform_driver_unregister(&vs1838_driver);
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(vs1838_init);
module_exit(vs1838_exit);MODULE_LICENSE("GPL");
只需要注册驱动,注册中断即可,不再需要字符设备那些内容,就可以实现一个驱动输入。
再次重复一下
注册完成后,会在/dev/input/下出现一个新的event1,这个就是用户侧使用的文件。
实测
用户侧代码
int main(int argc, char **argv)
{int fd;//unsigned int data;struct input_event data;int i;/* 1. 判断参数 */if (argc != 2) {printf("Usage: %s <dev>\n", argv[0]);return -1;}/* 2. 打开文件 */
// fd = open(argv[1], O_RDWR | O_NONBLOCK);fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}while (1){if (read(fd, &data, sizeof(data)) == sizeof(data)){printf("get IR code :");printf(" Type: [0x%x],", data.type);printf(" Code: [0x%x],", data.code);printf(" Val : [0x%x]\n", data.value);}else {printf("get IR code: -1\n");}//sleep(5);}close(fd);return 0;
}
找到了一个能识别的遥控器,是一个机顶盒的遥控器,
其他的遥控器,还是会报错,数据帧的校验通不过,
编译好用户程序开始测试
没问题,能够收到code。
注意事项
在系统上报数据的时候,这个val,如果超过0xff就无法上报,所以只取了低字节。
val=val&0xff;
input_event(vs1838_input_dev, EV_KEY, val, 1);
结束语
夫人带孩子回去给姥姥过生日了,突然时间就边得多了起来,不过也感觉无聊了,没有那个整天叫你的孩子,于是抓紧时间学习学习。
今天突然有了一个想法,就是以后看能不能有时间的话,再去读一次大学,换个专业学习些新的知识,不过这是个美好的想法,估计也很难实现,可能下半生还是要继续奔波劳苦了。
今天听到了一个损人的称呼,你真是三分之一的哪吒。有人知道为啥吗?
Linux驱动开发(十)---树莓派输入子系统学习(红外接收)相关推荐
- linux驱动开发 - 07_pinctrl 和 gpio 子系统实战
文章目录 1 gpio 子系统 API 函数 2 gpio 相关的 OF 函数 3 程序编写 3.1 修改设备树文件 3.2 添加 LED 设备节点 3.3 LED 灯驱动程序编写 3.4 编写测试 ...
- 【Linux驱动开发】010 pinctrl子系统
一.前言 上一章我们编写了基于设备树的 LED 驱动,但是驱动的本质还是没变,都是配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没啥区别.本章我们就来学习一下如何借助 pinctr ...
- 《正点原子嵌入式linux驱动开发指南V1.4》学习笔记
文章目录 一.开发环境搭建 1.1 安装交叉编译工具链 1.2 代码镜像烧写工具 1.3 Ubuntu下所有的设备文件都在目录"/dev"里面 1.4 Ubuntu 搭建TFTP服 ...
- Linux 驱动开发 十:《Devicetree Specification》翻译
一.设备树 1.概述 DTSpec 指定了一个称为设备树的结构来描述系统硬件.引导程序将设备树加载到客户端程序的内存中,并将指向设备树的指针传递给客户端. 本章描述了设备树的逻辑结构,并指定了一组用于 ...
- Linux驱动开发(十八)---网络(网卡)驱动学习
前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...
- Linux驱动开发(十五)---如何使用内核现有驱动(显示屏)
前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...
- Linux驱动开发(外传)---驱动开发调试方法
前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...
- 嵌入式linux驱动开发实战教程,嵌入式Linux驱动开发实战视频教程
嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统) 适合人群:高级 课时数量:109课时 用到技术:嵌入式 Linux 涉及项目:驱动开发.看门狗技术.触摸屏.视频采集 咨 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用
文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...
最新文章
- 2016 - 1- 21 - RunLoop使用(2016-1-24修改一次)(2016 - 1 - 24 再次修改)
- linux内核kconfig objs,linux内核中Kconfig和Makefile 详解
- Verilog初级教程(21)Verilog中的延迟控制语句
- 【CyberSecurityLearning 68】python 编写exp
- linux c之信号signal处理机制
- python中print的用法_python中print用法
- 嗨淘V12刷任务点赞系统源码手动派单版本
- c语言查看cpu温度代码_很多程序员都不知道,C语言中还有“快类型”和“小类型”...
- 剑指Offer之翻转单词顺序列
- linux初次入门学习小结
- utilities —— 常见宏定义
- tuxedo错误码6_TUXEDO启动常见错误和解决方法
- 给机器学习面试者的十项建议 | 面试官角度
- 在IDEA中使用Maven详细教程
- 地理中经纬度的英文名称
- 计算机中桌面中不显示U盘图标,电脑桌面右下角不能显示u盘图标解决方法
- 【老生谈算法】matlab在材料力学中的应用
- 面向对象方式爬取知乎鸡汤
- elasticsearch知识库
- 网络经商22条赚钱法则
热门文章
- java中文件流close_Java FileInputStream close()方法与示例
- 关于VS兼容WPS和OFFICE的问题
- Python-Django毕业设计自行车租赁管理系统(程序+Lw)
- SWMM从入门到实践教程 02 快速入门案例的绘制
- 【零知ESP8266教程】快速入门8-读取模拟数值
- html获取cookiejs,javascript设置和获取cookie的方法实例详解
- 《东北往事——黑道风云20年》
- Markdown土拨鼠学习计划
- 【高数】高数第三章节——微分中值洛必达泰勒单调性与凹凸性作图弧微分与曲率
- 面试题之银行业务系统