• 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    理解一下这段代码

    1. 分配input_dev 并设置参数
    vs1838_input_dev = devm_input_allocate_device(&pdev->dev);
    vs1838_input_dev->name = "vs1838";
    vs1838_input_dev->phys = "vs1838";
    
    • 1
    • 2
    • 3
    1. 能产生哪类事件
      设备驱动通过set_bit()告诉input子系统它支持哪些事件
    set_bit(EV_KEY, vs1838_input_dev->evbit);
    set_bit(EV_REP, vs1838_input_dev->evbit);
    
    • 1
    • 2

    主要的事件类型包括

    按键说明
    EV_SYN0x00事件间的分割标志,有些事件可能会在时间和空间上产生延续,比如持续按住一个按键。为了更好地管理这些持续的事件,EV_SYN 用以将他们分割成一个个的小的数据包
    EV_KEY0x01按键事件
    EV_REL0x02相对值,如光标移动,报告的是相对最后一次位置的偏移
    EV_ABS0x03绝对值,如触摸屏和操纵杆,它们工作在绝对坐标系统
    EV_MSC0x04不能匹配现有的类型,这相当于当前暂不识别的事件。比如在 Linux系统中按下键盘中针对 Windows 系统的“一键杀毒”按键,将会产生该事件
    EV_SW0x05显示设备开关状态
    EV_LED0x11用于控制设备上的 LED 灯的开关,比如按下键盘的大写锁定键,会同时产生 ”EV_KEY” 和 ”EV_LED” 两个事件
    EV_SND0x12显示声音效果
    EV_REP0x14重复
    EV_FF0x15力反馈事件
    EV_PWR0x16电源事件
    EV_FF_STATUS0x17力反馈状态事件
    EV_MAX0x1f
    EV_CNT(EV_MAX+1)

    在这里插入图片描述

    1. 能产生哪些事件 ,0xff类似于掩码,表示按键值可以0~0xff。
    memset(vs1838_input_dev->keybit, 0xff, sizeof(vs1838_input_dev->keybit));
    
    • 1
    1. 注册input_dev

    这些操作完成后,就会存在一个新的event文件。存在于/dev/input路径下
    在这里插入图片描述

    input_register_device(vs1838_input_dev);
    
    • 1

    最后,在获得到正确的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);	
    
    • 1
    • 2
    • 3

    并且我们的程序可以很简单。
    以下是完整代码

    #include 
    #include 
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    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();
    #else
    		vs1838_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");
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261

    只需要注册驱动,注册中断即可,不再需要字符设备那些内容,就可以实现一个驱动输入。
    再次重复一下
    注册完成后,会在/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 \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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    找到了一个能识别的遥控器,是一个机顶盒的遥控器,
    在这里插入图片描述
    其他的遥控器,还是会报错,数据帧的校验通不过,
    编译好用户程序开始测试
    在这里插入图片描述
    没问题,能够收到code。
    在这里插入图片描述

    注意事项

    在系统上报数据的时候,这个val,如果超过0xff就无法上报,所以只取了低字节。

    val=val&0xff;
    input_event(vs1838_input_dev, EV_KEY, val, 1);
    
    • 1
    • 2

    结束语

    夫人带孩子回去给姥姥过生日了,突然时间就边得多了起来,不过也感觉无聊了,没有那个整天叫你的孩子,于是抓紧时间学习学习。
    今天突然有了一个想法,就是以后看能不能有时间的话,再去读一次大学,换个专业学习些新的知识,不过这是个美好的想法,估计也很难实现,可能下半生还是要继续奔波劳苦了。
    在这里插入图片描述
    今天听到了一个损人的称呼,你真是三分之一的哪吒。有人知道为啥吗?在这里插入图片描述

  • 相关阅读:
    【王道】计算机组成原理第三章存储系统(三)
    中文分词库-jieba
    关于编程本质那些事
    antd Carousel 重写dot样式
    tensorflow的GPU加速计算
    OpenHarmony-4.0-Release 源码编译记录
    C++小病毒
    超标量处理器设计 姚永斌 第3章 虚拟存储器 --3.1~3.2 小节摘录
    用 Gaussian Process 建模 state-action 空间相关性,加速 Multi-Fidelity RL
    JavaScript管理HTMLDOM元素(增删改查)
  • 原文地址:https://blog.csdn.net/baidu_19348579/article/details/126053736