当前位置:   article > 正文

【正点原子Linux连载】 第三十九章 Linux红外接收驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南_linux红外驱动

linux红外驱动

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第三十九章 Linux红外接收驱动实验

红外遥控是我们常见的一种无线收发设备,具有抗干扰能力强,功耗低,成本低,易实现等优点。被很多电子设备特别是家用电器广泛采用,如电视遥控、空调遥控等。红外遥控的发射电路是采用红外发光二极管来发出经过调制的红外光波;红外接收的电路由红外接收二极管、三极管或者硅光电池组成,把发出来的红外光经过转换变成相对应的电信号,再发送到后置放大器中。目前为止,红外遥控协议已多达十种, 如: RC5、 SIRCS、 Sy、 RECS80、Denon、NEC、Motorola、Japanese、SAMSWNG 和 Daewoo 等。目前 RK 平台也只支持 NEC 编码的红外协议。
  • 1

39.1 红外实现原理简介
红外遥控的编码使用的是:NEC Protocol的PWM机制,PWM有三种工作模式:reference mode, one-shot mode 和 continuousmode. 红外遥控器就采用 reference mode,这种模式下 PWM 可以捕获输入高低电平的宽度,并产生中断,CPU接收到中断后去相应的寄存器读取。NEC协议的特征如下:
◎8位地址和8位命令长度。
◎地址和命令传输两次,以提高可靠性。
◎PWM脉冲位置调制,以发射红外载波的占空比表“0”和“1”。
◎载波频率为38KHz。
◎位时间1.125ms或2.25ms。
NEC协议使用比特的脉冲距离编码,每个脉冲是一个560us的连续载波,一个逻辑“1”传输时间为2.25ms(560us脉冲+1680us低电平),一个逻辑“0”的传输时间为1.125ms(560us脉冲+560us低电平)。遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们接收头端收到的信号为:逻辑“1”是560us低加1680us高,而逻辑“0”是560us低+560us高。
1、NEC协议时序图
在这里插入图片描述

图39.1.1 NEC协议时序图
从图39.1.1得知,首先发送9ms的AGC的高脉冲信号,接着发送4.5ms的起始低电平,然后是地址和命令,地址和命令发送两次,第二次是第一次的反码。因此地址和命令加起来是四个字节,分别是地址码、地址反码、命令码、命令反码。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。
2、重复码
在这里插入图片描述

图39.1.2 重复码
从图39.1.2得知,一条命令只会发送一次的,即使遥控器上的按键一直按下,每次110ms就会发送一个重复码。重复码由9ms的AGC高脉冲和2.25的低电平及560us的高电平组成。
3、逻辑电平
在这里插入图片描述

图39.1.3 逻辑电平
从图39.1.3得知,逻辑“1”是由560us的高电平加1.69ms的低电平脉冲组成;逻辑“0”则是由560us高电平加560us的低电平脉冲组成。
39.2 硬件原理图分析
红外模块原理图如图39.2.1所示:
在这里插入图片描述

图39.2.1 红外模块原理图
从图39.2.1可以看出,LF0038GKLL-1的DATA引脚连到到了ATK-DLRK3568的PWM7_IR接口上,引脚为GPIO0_C6。
39.3 红外遥控器IR驱动编写
39.3.1 修改设备树
红外遥控的驱动使用的是PWM7接口,所以驱动基本不用我们写,都是内核里有现成的,使用PWM的脉冲去测量高低电平的长度,从而计算出相应的红外值,并注册一个input子系统,我们的工作只需要在设备树提供对应的设备节点和配置红外值即可。
1、PWM7控制器节点信息
PWM的基础知识点这里就不再讲述了,可以看二十三章节PWM驱动实验章节。打开rk3568.dtsi文件,找到名为“pwm7”的设备节点,内容如下。
示例代码 39.3.1 PWM7 节点

1   pwm7: pwm@fe6e0030 {
2       compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";
3       reg = <0x0 0xfe6e0030 0x0 0x10>;
4       interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>,
5                <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
6       #pwm-cells = <3>;
7       pinctrl-names = "active";
8       pinctrl-0 = <&pwm7_pins>;
9       clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>;
10      clock-names = "pwm", "pclk";
11      status = "disabled";
12  };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

RK3568 的 PWM 节点的 compatible 属性为“rockchip,rk3568-pwm”和“”rockchip,rk3328-
pwm,我们可以在 linux 内核源码中搜索这两个字符串就可以找到 PWM 驱动文件,这个文件
为:drivers/pwm/pwm-rockchip.c。
2、向PWM7节点追加信息
rk3568.dtsi文件中已经有了“pwm7”节点,这个节点默认是disabled的,还不能直接使用,但是rk3568-evb.dtsi文件已经帮我们使能了pwm7这个节点,rk3568-evb.dtsi文件是RK3568板级通用设备树文件,通常被板级设备树文件所包含。
示例代码 39.3.2 PWM7 引脚信息

1  &pwm7 {
2   status = "okay";
3  
4   compatible = "rockchip,remotectl-pwm";
5   remote_pwm_id = <3>;
6   handle_cpu_id = <1>;
7   remote_support_psci = <0>;
8   pinctrl-names = "default";
9   pinctrl-0 = <&pwm7_pins>;
10 
11  ir_key1 {
12      rockchip,usercode = <0x4040>;
13      rockchip,key_table =
14          <0xf2   KEY_REPLY>,
......
36  };
37 
38  ir_key2 {
39      rockchip,usercode = <0xff00>;
40      rockchip,key_table =
41          <0xf9   KEY_HOME>,
.....
71  };
72 
73  ir_key3 {
74      rockchip,usercode = <0x1dcc>;
75      rockchip,key_table =
76          <0xee   KEY_REPLY>,
......
103 };
104 };
  • 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
第2行,status改为okay,也就是使能PWM7。
第4行,compatible属性为“rockchip,remotectl-pwm”,我们可以在Linux源码中搜索这个字符串就可以找到本章实验重点的PWM-IR驱动文件,这个文件为drivers/input/remotectl/rockchip_pwm_remotectl.c。
第6行,handle-cpu-id代表ir中断在第2个CPU上处理,四核系统是0~3。
第8行,必须设置为“default”,不然会无法产生中断。
第9行,pwm7的引脚配置。
3、追加适配ATK-DLRK3568的PWM7节点信息
rk3568-linux-dtsi是RK3568板级通用设备树文件,所以我们需要在rk3568-atk-evb1-ddr4-v10.dtsi文件里面添加专门适配ATK-DLRK3568的设备树配置信息。rk3568-atk-evb1-ddr4-v10.dtsi内容如下所示:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

示例代码 39.3.3 PWM7 引脚信息

1 &pwm7 {
2   /delete-node/ ir_key2;
3   /delete-node/ ir_key3;
4 
5   ir_key1 {
6       /* 正点原子红外遥控器按键定义: 一共20个按键 */
7       rockchip,usercode = <0xFF00>;
8       rockchip,key_table =
9           <0xba KEY_POWER>,
10          <0xb9 KEY_UP>,
11          <0xb8 KEY_HOME>,
12          <0xbb KEY_REWIND>,
13          <0xbf KEY_PLAYPAUSE>,
14          <0xbc KEY_FASTFORWARD>,
15          <0xf8 KEY_VOLUMEDOWN>,
16          <0xea KEY_DOWN>,
17          <0xf6 KEY_VOLUMEUP>,
18          <0xe9 KEY_1>,
19          <0xe6 KEY_2>,
20          <0xf2 KEY_3>,
21          <0xf3 KEY_4>,
22          <0xe7 KEY_5>,
23          <0xa1 KEY_6>,
24          <0xf7 KEY_7>,
25          <0xe3 KEY_8>,
26          <0xa5 KEY_9>,
27          <0xbd KEY_0>,
28          <0xb5 KEY_BACKSPACE>;
29  };
30 };
  • 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
第2、3行,表示删除掉rk3568-evb.dtsi文件中的ir_key2和ir_key3节点。
第7行,rockchip,usercode表示NEC里面的地址。
第9~28行,表示红外编码值映射成Linux下标准键。
  • 1
  • 2
  • 3

39.3.2遥控器驱动源码代码分析
我们简单分析一下Linux内核自带的RK3568 遥控器驱动源码,驱动文件说了,是rockchip_pwm_remotectl.c这个文件。打开这个文件,可以看到,这是一个标准的平台设备驱动文件,有如下所示:
示例代码 39.3.2.1 RK3568 遥控器平台驱动

1 static const struct of_device_id rk_pwm_of_match[] = {
2   { .compatible =  "rockchip,remotectl-pwm"},
3   { }
4 };
5 
6 MODULE_DEVICE_TABLE(of, rk_pwm_of_match);
7 
8 static struct platform_driver rk_pwm_driver = {
9   .driver = {
10      .name = "remotectl-pwm",
11      .of_match_table = rk_pwm_of_match,
12 #ifdef CONFIG_PM
13      .pm = &remotectl_pm_ops,
14 #endif
15  },
16  .remove = rk_pwm_remove,
17 };
18
19 module_platform_driver_probe(rk_pwm_driver, rk_pwm_probe);
20
21 MODULE_LICENSE("GPL");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
第2行,当设备树pwm节点的compatible属性值为“rockchip,remotectl-pwm”的话就会匹配此驱动。
第19行,当设备树节点和驱动匹配以后,rk_pwm_probe函数就会执行。
在看rk_pwm_probe函数之前先来看下rkxx_remotectl_drvdata结构体,这个结构体是瑞芯微官方创建的针对瑞芯微芯片的遥控器结构体,这个结构体会贯穿整个PWM-IR驱动,起到了非常重要的作用。rkxx_remotectl_drvdata结构体内容如下:
  • 1
  • 2
  • 3

示例代码 39.3.2.2 rkxx_remotectl_drvdata结构体

1 struct rkxx_remotectl_drvdata {
2   void __iomem *base;
3   int state;
4   int nbuttons;
5   int scandata;
6   int count;
7   int keynum;
8   int maxkeybdnum;
9   int keycode;
10  int press;
11  int pre_press;
12  int irq;
13  int remote_pwm_id;
14  int handle_cpu_id;
15  int wakeup;
16  int support_psci;
17  int pwm_pwrkey_capture;
18  unsigned long period;
19  unsigned long temp_period;
20  int pwm_freq_nstime;
21  int pwrkey_wakeup;
22  struct input_dev *input;
23  struct timer_list timer;
24  struct tasklet_struct remote_tasklet;
25  struct wake_lock remotectl_wake_lock;
26 };
  • 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
第2行,base寄存器基地址,用于访问硬件寄存器。
第5行,扫描到的位置,命令码为2字节。
第8行,获取最大的红外键盘的数量,原子的只适配了一个红外遥控按键ir_key1,因此这个值为1。
第10行,检查是否有按键按下,有的话置1。
第11行,恒为0。
  • 1
  • 2
  • 3
  • 4
  • 5

此结构体的主要是包含了用户码、命令码、和遥控器所需要的基本参数信息。
下面来简单分析一下rk_pwm_probe函数,内容如下所示(有缩减):
示例代码 39.3.2.3rk_pwm_pribe函数

1  static int rk_pwm_probe(struct platform_device *pdev)
2  {
3   struct rkxx_remotectl_drvdata *ddata;
4   struct device_node *np = pdev->dev.of_node;
......
25  ddata = devm_kzalloc(&pdev->dev, sizeof
(struct rkxx_remotectl_drvdata), GFP_KERNEL);
26
27  if (!ddata) {
28      dev_err(&pdev->dev, "failed to allocate memory\n");
29      return -ENOMEM;
30  }
31  ddata->state = RMC_PRELOAD;
32  ddata->temp_period = 0;
33  ddata->base = devm_ioremap_resource(&pdev->dev, r);
34  if (IS_ERR(ddata->base))
35      return PTR_ERR(ddata->base);
......
66  platform_set_drvdata(pdev, ddata);
67  num = rk_remotectl_get_irkeybd_count(pdev);
68  if (num == 0) {
69      pr_err("remotectl: no ir keyboard add in dts!!\n");
70      ret = -EINVAL;
71      goto error_pclk;
72  }
73  ddata->maxkeybdnum = num;
74  remotectl_button = devm_kzalloc(&pdev->dev,
75                  num * sizeof(struct rkxx_remotectl_button),
76                  GFP_KERNEL);
77  if (!remotectl_button) {
78      pr_err("failed to malloc remote button memory\n");
79      ret = -ENOMEM;
80      goto error_pclk;
81  }
82  input = devm_input_allocate_device(&pdev->dev);
83  if (!input) {
84      pr_err("failed to allocate input device\n");
85      ret = -ENOMEM;
86      goto error_pclk;
87  }
88  input->name = pdev->name;
89  input->phys = "gpio-keys/remotectl";
90  input->dev.parent = &pdev->dev;
91  input->id.bustype = BUS_HOST;
92  input->id.vendor = 0x524b;
93  input->id.product = 0x0006;
94  input->id.version = 0x0100;
95  ddata->input = input;
96  irq = platform_get_irq(pdev, 0);
97  if (irq < 0) {
98      dev_err(&pdev->dev, "cannot find IRQ\n");
99      goto error_pclk;
100 }
101 ddata->irq = irq;
102 ddata->wakeup = 1;
103 of_property_read_u32(np, "remote_pwm_id", &pwm_id);
104 pwm_id %= 4;
105 ddata->remote_pwm_id = pwm_id;
106 if (pwm_id > 3) {
107     dev_err(&pdev->dev, "pwm id error\n");
108     goto error_pclk;
109 }
......
114 rk_remotectl_parse_ir_keys(pdev);
115 tasklet_init(&ddata->remote_tasklet, rk_pwm_remotectl_do_something,
116          (unsigned long)ddata);
......
126 ret = input_register_device(input);
127 if (ret)
128     dev_err(&pdev->dev, "register input device err, ret=%d\n", ret);
129 input_set_capability(input, EV_KEY, KEY_WAKEUP);
130 device_init_wakeup(&pdev->dev, 1);
131 enable_irq_wake(irq);
132 timer_setup(&ddata->timer, rk_pwm_remotectl_timer, 0);
133 wake_lock_init(&ddata->remotectl_wake_lock,
134            WAKE_LOCK_SUSPEND, "rockchip_pwm_remote");
135 cpumask_clear(&cpumask);
136 cpumask_set_cpu(cpu_id, &cpumask);
137 irq_set_affinity(irq, &cpumask);
138 ret = devm_request_irq(&pdev->dev, irq, rockchip_pwm_irq,
139                IRQF_NO_SUSPEND, "rk_pwm_irq", ddata);
140 if (ret) {
141     dev_err(&pdev->dev, "cannot claim IRQ %d\n", irq);
142     goto error_irq;
143 }
......
170 return ret;
171}
  • 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

第25行,调用函数devm_kzalloc函数为rkxx_remotectl_drvdata结构体来申请内存。
第31~33,初始化rkxx_remotectl_drvdata结构体。
第66行,将设备设置为平台设备的私有数据。
第67行,调用瑞芯微平台上的函数,来获取红外键盘的数量。
第82行,申请input_dev,因为红外遥控属于一个input子系统
第88~94行,初始化input_dev。
第96行,调用platform_get_irq函数获取中断号
第103行,调用of_property_read_u32函数从设备树中获取remote_pwm_id属性值。
第114行,rk_remotectl_parse_ir_keys函数主要是解析红外遥控器的按键事件和按键映射等处理。
第115行,tasklet_init函数调用rk_pwm_remotectl_do_something回调函数处理软中断的任务。
第130~134行,主要是中断唤醒功能的初始化和使能,定时器的初始化和设置内核定时器,调用rk_pwm_remotectl_timer回调函数设置触发时间等。
135~137行,主要是将中断绑定到设备树指定的CPU1上,提高中断处理的局部性,增加缓存的命中率。
至此,红外遥控的驱动我们就简单的分析完了。
39.4 红外遥控测试
39.4.1 驱动注册
ATK-DLRK3568出厂系统默认是已经启动红外遥控的驱动了,驱动注册成功之后会在input生成红外模块对应的设备文件。用户层就是通过读取此文件来获取输入事件信息的。可以输入如下命令查看:
cat /proc/bus/input/devices
结果如图39.4.1所示:
在这里插入图片描述

图39.4.1.1input目录信息
从图39.4.1.1可以看出,event0文件就是红外模块对应的设备文件。
39.4.2 红外中断
当我们的红外遥控驱动注册成功之后,可以通过/proc/interrupts文件来检查一下对应的中断有没有被注册上,输入如下命令:
cat /proc/interrupts
结果如图39.4.2.1所示:
在这里插入图片描述

图39.4.2.1 proc/interrupts文件内容
从图39.4.2.1可以看出,rk_pwm_irq中断已经存在了,触发方式为电平触发。使用红外遥控器往板载的红外接收按下按键,然后重新刷新一下中断表,会发现有中断的数据会发生变化。
39.4.3 运行测试
如果你手上有红外遥控器,如空调的都行。执行下面的指令开启红外信息打印。本次使用格力空调红外遥控器与正点原子红外遥控器模块测试,如下图所示:
echo 1 > sys/module/rockchip_pwm_remotectl/parameters/code_print
在这里插入图片描述

图39.4.3.1 红外遥控数据

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/664429
推荐阅读
相关标签
  

闽ICP备14008679号