前文回顾

《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;
}

理解一下这段代码

  1. 分配input_dev 并设置参数
vs1838_input_dev = devm_input_allocate_device(&pdev->dev);
vs1838_input_dev->name = "vs1838";
vs1838_input_dev->phys = "vs1838";
  1. 能产生哪类事件
    设备驱动通过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)

  1. 能产生哪些事件 ,0xff类似于掩码,表示按键值可以0~0xff。
memset(vs1838_input_dev->keybit, 0xff, sizeof(vs1838_input_dev->keybit));
  1. 注册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驱动开发(十)---树莓派输入子系统学习(红外接收)相关推荐

  1. linux驱动开发 - 07_pinctrl 和 gpio 子系统实战

    文章目录 1 gpio 子系统 API 函数 2 gpio 相关的 OF 函数 3 程序编写 3.1 修改设备树文件 3.2 添加 LED 设备节点 3.3 LED 灯驱动程序编写 3.4 编写测试 ...

  2. 【Linux驱动开发】010 pinctrl子系统

    一.前言 上一章我们编写了基于设备树的 LED 驱动,但是驱动的本质还是没变,都是配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没啥区别.本章我们就来学习一下如何借助 pinctr ...

  3. 《正点原子嵌入式linux驱动开发指南V1.4》学习笔记

    文章目录 一.开发环境搭建 1.1 安装交叉编译工具链 1.2 代码镜像烧写工具 1.3 Ubuntu下所有的设备文件都在目录"/dev"里面 1.4 Ubuntu 搭建TFTP服 ...

  4. Linux 驱动开发 十:《Devicetree Specification》翻译

    一.设备树 1.概述 DTSpec 指定了一个称为设备树的结构来描述系统硬件.引导程序将设备树加载到客户端程序的内存中,并将指向设备树的指针传递给客户端. 本章描述了设备树的逻辑结构,并指定了一组用于 ...

  5. Linux驱动开发(十八)---网络(网卡)驱动学习

    前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...

  6. Linux驱动开发(十五)---如何使用内核现有驱动(显示屏)

    前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...

  7. Linux驱动开发(外传)---驱动开发调试方法

    前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...

  8. 嵌入式linux驱动开发实战教程,嵌入式Linux驱动开发实战视频教程

    嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统) 适合人群:高级 课时数量:109课时 用到技术:嵌入式 Linux 涉及项目:驱动开发.看门狗技术.触摸屏.视频采集 咨 ...

  9. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用

    文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...

最新文章

  1. 2016 - 1- 21 - RunLoop使用(2016-1-24修改一次)(2016 - 1 - 24 再次修改)
  2. linux内核kconfig objs,linux内核中Kconfig和Makefile 详解
  3. Verilog初级教程(21)Verilog中的延迟控制语句
  4. 【CyberSecurityLearning 68】python 编写exp
  5. linux c之信号signal处理机制
  6. python中print的用法_python中print用法
  7. 嗨淘V12刷任务点赞系统源码手动派单版本
  8. c语言查看cpu温度代码_很多程序员都不知道,C语言中还有“快类型”和“小类型”...
  9. 剑指Offer之翻转单词顺序列
  10. linux初次入门学习小结
  11. utilities —— 常见宏定义
  12. tuxedo错误码6_TUXEDO启动常见错误和解决方法
  13. 给机器学习面试者的十项建议 | 面试官角度
  14. 在IDEA中使用Maven详细教程
  15. 地理中经纬度的英文名称
  16. 计算机中桌面中不显示U盘图标,电脑桌面右下角不能显示u盘图标解决方法
  17. 【老生谈算法】matlab在材料力学中的应用
  18. 面向对象方式爬取知乎鸡汤
  19. elasticsearch知识库
  20. 网络经商22条赚钱法则

热门文章

  1. java中文件流close_Java FileInputStream close()方法与示例
  2. 关于VS兼容WPS和OFFICE的问题
  3. Python-Django毕业设计自行车租赁管理系统(程序+Lw)
  4. SWMM从入门到实践教程 02 快速入门案例的绘制
  5. 【零知ESP8266教程】快速入门8-读取模拟数值
  6. html获取cookiejs,javascript设置和获取cookie的方法实例详解
  7. 《东北往事——黑道风云20年》
  8. Markdown土拨鼠学习计划
  9. 【高数】高数第三章节——微分中值洛必达泰勒单调性与凹凸性作图弧微分与曲率
  10. 面试题之银行业务系统