红外遥控是我们常见的一种无线收发设备,具有抗干扰能力强,功耗低,成本低,易实现等优点。被很多电子设备采用,如电视遥控、空调遥控等。
红外遥控的发射电路是采用红外发光二极管来发出经过调制的红外光波;红外接收的电路由红外接收二极管、三极管或者硅光电池组成,把发出来的红外光经过转换变成相对应的电信号,再发送到后置放大器中。
目前为止,红外遥控协议已多达十种,如:RC5、SIRCS、Sy、RECS80、Denon、NEC、Motorola、Japanese、SAMSWNG 和 Daewoo 等。目前 RK 平台只支持 NEC 编码的红外协议。
红外遥控的编码使用的是 NEC Protocol 的 PWM 机制,PWM 有三种工作模式:reference mode, one-shot mode 和 continuous mode. 红外遥控器就采用 reference mode,这种模式下 PWM 可以捕获输入高低电平的宽度,并产生中断,CPU 接收到中断后去相应的寄存器读取。NEC 协议的特征如下:
NEC 协议使用比特的脉冲距离编码,每个脉冲是一个 560us 的连续载波,一个逻辑“1”传输时间为 2.25ms(560us 脉冲+1680us 低电平),一个逻辑“0”的传输时间为 1.125ms(560us 脉冲+560us 低电平)。
遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们接收头端收到的信号为:逻辑“1”是 560us 低加 1680us 高,而逻辑“0”是 560us 低 +560us 高。
上图首先发送 9ms 的 AGC 的高脉冲信号,接着发送 4.5ms 的起始低电平,然后是地址和命令,地址和命令发送两次,第二次是第一次的反码。因此地址和命令加起来是四个字节,分别是地址码、地址反码、命令码、命令反码。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。
上图显示,一条命令只会发送一次的,即使遥控器上的按键一直按下,每次 110ms 就会发送一个重复码。重复码由 9ms 的 AGC 高脉冲和 2.25 的低电平及 560us 的高电平组成。
逻辑“1”是由 560us 的高电平加 1.69ms 的低电平脉冲组成;逻辑“0”则是由 560us 高电平加 560us 的低电平脉冲组成。
RK 平台 pwm driver 目录如下:
/kernel/drivers/input/remotectl/rockchip_pwm_remotectl.c
/kernel/drivers/pwm/pwm-rockchip.c
红外接收器有很多,如 LF0038GKLL-1、HS0038B、VS1838B 等,博主手里 RK3399 带的是 HS0038B,硬件原理图如下:
红外遥控的驱动使用的是 PWM 接口,所以驱动基本不用写,都是内核里现成的,使用 PWM 的脉冲去测量高低电平的长度,从而计算出相应的红外值,并注册一个 input 子系统,我们的工作只需要在设备树提供对应的设备节点和配置红外值即可。
dts 配置如下,挂在 pwm3 下:
&pwm3 {
status = "okay";
interrupts = <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH 0>;
compatible = "rockchip,remotectl-pwm";
remote_pwm_id = <3>;
handle_cpu_id = <0>;
ir_key0 {
rockchip,usercode = <0xff40>;
rockchip,key_table =
<0xb2 KEY_POWER>,
<0xe5 KEY_HOME>,
<0xbd KEY_BACK>,
<0xba KEY_MENU>,
<0xf4 KEY_UP>,
<0xf1 KEY_DOWN>,
<0xef KEY_LEFT>,
<0xee KEY_RIGHT>,
<0xf2 KEY_ENTER>,
<0xf0 KEY_REPLY>,
<0xea KEY_VOLUMEUP>,
<0xe3 KEY_VOLUMEDOWN>,
<0xbc KEY_MUTE>,
<0xfe KEY_1>,
<0xfd KEY_2>,
<0xfc KEY_3>,
<0xfb KEY_4>,
<0xfa KEY_5>,
<0xf9 KEY_6>,
<0xf8 KEY_7>,
<0xf7 KEY_8>,
<0xb6 KEY_9>,
<0xff KEY_0>,
<0xed KEY_BACKSPACE>,
<0xf3 115>,
<0xe7 114>,
<0xaf KEY_POWER>,
<0x8b KEY_VOLUMEUP>,
<0xb9 KEY_VOLUMEDOWN>;
};
};
handle-cpu-id 代表 ir 中断在第 0 个 CPU 上处理,RK3399 是四核。
rockchip,usercode 表示 NEC 里面的地址。
这里将红外编码值映射成 Linux 下标准键。
系统启动时 rockchip_pwm_remotectl.c 根据 dts 的配置会注册如下设备:
查看中断节点,红外的中断如下,博主用手机开关几次空调,发现开发板的红外中断也有触发:
执行下面的指令开启 kernel 红外信息打印。如下图所示:
echo 1 > sys/module/rockchip_pwm_remotectl/parameters/code_print
rockchip_pwm_remotectl.c
static const struct of_device_id rk_pwm_of_match[] = {
{ .compatible = "rockchip,remotectl-pwm"},
{ }
};
MODULE_DEVICE_TABLE(of, rk_pwm_of_match);
static struct platform_driver rk_pwm_driver = {
.driver = {
.name = "remotectl-pwm",
.of_match_table = rk_pwm_of_match,
#ifdef CONFIG_PM
.pm = &remotectl_pm_ops,
#endif
},
.remove = rk_pwm_remove,
};
module_platform_driver_probe(rk_pwm_driver, rk_pwm_probe);
static int rk_pwm_probe(struct platform_device *pdev)
{
struct rkxx_remotectl_drvdata *ddata;
struct device_node *np = pdev->dev.of_node;
ddata = devm_kzalloc(&pdev->dev, sizeof(struct rkxx_remotectl_drvdata),GFP_KERNEL);
ddata->state = RMC_PRELOAD;
ddata->temp_period = 0;
ddata->base = devm_ioremap_resource(&pdev->dev, r);
platform_set_drvdata(pdev, ddata);
num = rk_remotectl_get_irkeybd_count(pdev);
if (num == 0) {
pr_err("remotectl: no ir keyboard add in dts!!\n");
......
}
ddata->maxkeybdnum = num;
remotectl_button = devm_kzalloc(&pdev->dev,num*sizeof(struct rkxx_remotectl_button),GFP_KERNEL);
if (!remotectl_button) {
pr_err("failed to malloc remote button memory\n");
......
}
input = devm_input_allocate_device(&pdev->dev);
if (!input) {
pr_err("failed to allocate input device\n");
......
}
input->name = pdev->name;
input->phys = "gpio-keys/remotectl";
input->dev.parent = &pdev->dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
ddata->input = input;
ddata->input = input;
irq = platform_get_irq(pdev, 0);
if (ret < 0) {
dev_err(&pdev->dev, "cannot find IRQ\n");
goto error_pclk;
}
ddata->irq = irq;
ddata->wakeup = 1;
of_property_read_u32(np, "remote_pwm_id", &pwm_id);
ddata->remote_pwm_id = pwm_id;
DBG("remotectl: remote pwm id=0x%x\n", pwm_id);
of_property_read_u32(np, "handle_cpu_id", &cpu_id);
ddata->handle_cpu_id = cpu_id;
DBG("remotectl: handle cpu id=0x%x\n", cpu_id);
rk_remotectl_parse_ir_keys(pdev);
tasklet_init(&ddata->remote_tasklet, rk_pwm_remotectl_do_something,(unsigned long)ddata);
ret = input_register_device(input);
input_set_capability(input, EV_KEY, KEY_WAKEUP);
device_init_wakeup(&pdev->dev, 1);
enable_irq_wake(irq);
setup_timer(&ddata->timer, rk_pwm_remotectl_timer,(unsigned long)ddata);
wake_lock_init(&ddata->remotectl_wake_lock,WAKE_LOCK_SUSPEND, "rockchip_pwm_remote");
cpumask_clear(&cpumask);
cpumask_set_cpu(cpu_id, &cpumask);
irq_set_affinity(irq, &cpumask);
ret = devm_request_irq(&pdev->dev, irq, rockchip_pwm_irq,IRQF_NO_SUSPEND, "rk_pwm_irq", ddata);
return ret;
}
本文分享自 嵌入式Linux系统开发 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!