内核版本         :  Linux 3.10.14

rc红外接收类型:  GPIO 类型的NEC红外编码

本章内容

1)rc体系结构分析

2) 分析红外platform_driver平台驱动框架

3)分析内核自带的NEC红外解码过程

4) 修改内核自带的NEC红外解码BUG,实现按键重复按下

下章内容

1)自己创建一个红外platform_device平台设备

2)试验

在分析之前,先来复习下NEC红外编码的发送波形(在后面分析NEC解码会用到)

基本数据格式如下:

如果一直按住一个按钮时,会每隔100ms一直发送引导重复码.

一个完整的数据波形如下所示:

1.rc体系结构分析

rc相关文件位于kerneldriversmediac

1.1首先来看kerneldriversmediacMakefile

如上图所示,由于我们板子上的红外接收编码是NEC格式,并且是GPIO类型

所以Make menuconfig配置宏:

->Device Drivers-> Multimedia support (MEDIA_SUPPORT [=y])-> Remote controller decoders (RC_DECODERS [=y])

[*] Enable IR raw decoder forthe NEC protocol//选择NEC协议, ,使CONFIG_IR_NEC_DECODER=y

->Device Drivers-> Multimedia support (MEDIA_SUPPORT [=y])-> Remote Controller devices (RC_DEVICES [=y])

[*] GPIO IR remote control//选择GPIO接收类型,使CONFIG_IR_GPIO_CIR=y

1.2然后在driversmediackeymaps里存了各种不同的键映射文件

先来看看driversmediackeymapsMakefile:

如上图所示,可以看到把keymaps文件夹里的文件全部包含了.

它们用途在于:

1)当内核解码后,通过我们红外平台设备的dev.platform_data里map_name成员去匹配这些文件.

其中红外平台设备platform_data对应的结构体为:

structgpio_ir_recv_platform_data {int gpio_nr; //红外接收管对应的管脚

bool active_low; //数据是否低电平有效u64 allowed_protos;//该红外允许接收的编码协议,比如有NEC, SANYO, RC5等,可以填0,表示支持所有

const char *map_name;//该红外接收管对应的键值映射表名,内核会通过该名字去匹配keymaps文件夹里的编码对应的文件.从而注册该文件的键值映射表,以后解出来的编码则去找该键值映射表};

2)找到对应的文件,然后便通过该文件里的rc_map_list匹配编码

我们以rc-trekstor.c文件为例,该文件内容如下所示:

3)如果匹配到支持接收的编码,便会上报input事件按键.

PS:在下章创建红外平台设备时,会详细讲解如何使用

2.分析红外platform_driver平台驱动框架

我们选择的是CONFIG_IR_GPIO_CIR宏,所以接下来分析GPIO类型的rc驱动框架,该宏对应的驱动文件为:

2.1 分析gpio-ir-recv.c的init入口函数

如上图所示,其中module_platform_driver()宏定义位于platform_device.h

最终module_platform_driver(gpio_ir_recv_driver)展开后等于:

static int __init gpio_ir_recv_driver_init(void)

{return platform_driver_register(&gpio_ir_recv_driver);

}

module_init(gpio_ir_recv_driver_init);//…

该平台驱动的.name定义如下所示:

#define GPIO_IR_DRIVER_NAME "gpio-rc-recv"

所以我们后面创建红外platform_device平台设备时, .name也要写成"gpio-rc-recv"

2.2 分析gpio-ir-recv.c的probe函数

PS:在probe函数里,主要是获取平台设备pdev->dev.platform_data内容.该内容在1.2小结讲解过了.

代码如下:

static int gpio_ir_recv_probe(struct platform_device *pdev)

{struct gpio_rc_dev *gpio_dev;struct rc_dev *rcdev;const struct gpio_ir_recv_platform_data *pdata =pdev->dev.platform_data;

//获取gpio_ir_recv_platform_data结构体

intrc;//… …

if (pdata->gpio_nr < 0) //判断管脚有效性

return -EINVAL;

gpio_dev= kzalloc(sizeof(structgpio_rc_dev), GFP_KERNEL);if (!gpio_dev)return -ENOMEM;

rcdev=rc_allocate_device();if (!rcdev) {

rc= -ENOMEM;gotoerr_allocate_device;

}

rcdev->priv =gpio_dev;

rcdev->driver_type =RC_DRIVER_IR_RAW;

rcdev->input_name =GPIO_IR_DEVICE_NAME;

rcdev->input_phys = GPIO_IR_DEVICE_NAME "/input0";

rcdev->input_id.bustype =BUS_HOST;

rcdev->input_id.vendor = 0x0001;

rcdev->input_id.product = 0x0001;

rcdev->input_id.version = 0x0100;

rcdev->dev.parent = &pdev->dev;

rcdev->driver_name =GPIO_IR_DRIVER_NAME;if (pdata->allowed_protos)

rcdev->allowed_protos = pdata->allowed_protos;elsercdev->allowed_protos = RC_BIT_ALL; //allowed_protos==0,表示支持所有协议类型

rcdev->map_name = pdata->map_name ?: RC_MAP_EMPTY;

gpio_dev->rcdev =rcdev;

gpio_dev->gpio_nr = pdata->gpio_nr;

gpio_dev->active_low = pdata->active_low;

rc= gpio_request(pdata->gpio_nr, "gpio-ir-recv"); //申请IO管脚

if (rc < 0)gotoerr_gpio_request;

rc= gpio_direction_input(pdata->gpio_nr); //设置为输入

if (rc < 0)gotoerr_gpio_direction_input;

rc=rc_register_device(rcdev);if (rc < 0) {

dev_err(&pdev->dev, "failed to register rc device");gotoerr_register_rc_device;

}

platform_set_drvdata(pdev, gpio_dev);

rc= request_any_context_irq(gpio_to_irq(pdata->gpio_nr),

gpio_ir_recv_irq,

IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"gpio-ir-recv-irq", gpio_dev);//创建gpio_ir_recv_irq中断函数,为上下沿触发

return 0;//… …}

接下来,我们来看看gpio_ir_recv_irq()函数,看看如何实现解码的

2.3 分析gpio-ir-recv.c的gpio_ir_recv_irq函数

static irqreturn_t gpio_ir_recv_irq(int irq, void *dev_id)

{struct gpio_rc_dev *gpio_dev =dev_id;intgval;int rc = 0;enum raw_event_type type =IR_SPACE;//默认定义类型为IR_SPACE (红外接收的间隔信号)

gval= gpio_get_value_cansleep(gpio_dev->gpio_nr); //获取GPIO的值

if (gval < 0)gotoerr_get_value;if (gpio_dev->active_low) //低电平有效gval= !gval; //取反

if (gval == 1)

type= IR_PULSE; //收到的是脉冲信号

rc= ir_raw_event_store_edge(gpio_dev->rcdev, type); //通过内核时间,计算出当前波形的持续时间,并保存

if (rc < 0)gotoerr_get_value;

ir_raw_event_handle(gpio_dev->rcdev); //启动内核解码对应的线程,来处理波形err_get_value:returnIRQ_HANDLED;

}

接下来分析ir_raw_event_handle()函数如何处理波形的.

2.4 gpio_ir_recv_irq ()->ir_raw_event_handle()函数

该函数如下所示:

如上图所示,最终会唤醒一个线程,该线程对应的函数为ir_raw_event_thread():

static int ir_raw_event_thread(void *data)

{struct ir_raw_handler *handler;

… …

list_for_each_entry(handler,&ir_raw_handler_list, list)

//ir_raw_handler_list: 存储内核里注册的各个解码协议ir_raw_handler结构体,比如NEC, SANYO, RC5等handler->decode(raw->dev, ev); //调用解码函数… …

};

2.5 接下来,我们看看解码文件是如何添加到ir_raw_handler_list表的

由于我们选择的是NEC协议(CONFIG_IR_NEC_DECODER=y),所以以/drivers/media/rc/ir-nec-decoder.c为例

1)首先查看ir-nec-decoder.c的init函数:

如上图所示,可以看到通过ir_raw_handler_register()来注册.

2) 然后ir_raw_handler_register()里,则将该nec_handler添加到ir_raw_handler_list表:

3.接下来,我们来分析ir_nec_decode()解码函数如何解码的.

3.1分析ir_nec_decode()解码函数

static int ir_nec_decode(struct rc_dev *dev, structir_raw_event ev)

{struct nec_dec *data = &dev->raw->nec;

u32 scancode;

u8 address, not_address, command, not_command;bool send_32bits = false;if (!(dev->enabled_protocols & RC_BIT_NEC)) //判断协议是否支持

return 0;//… …

switch (data->state) {caseSTATE_INACTIVE:if (!ev.pulse)break;if (eq_margin(ev.duration, NEC_HEADER_PULSE, NEC_UNIT * 2)) { //判断ev.duration 是否等于9ms头引导码data->is_nec_x = false; //标记当前格式不是NECX编码格式data->necx_repeat = false;

}else if (eq_margin(ev.duration, NECX_HEADER_PULSE, NEC_UNIT / 2)) //另一种不常见的NECX引导码data->is_nec_x = true; //标记是NECX编码格式

else

break;

data->count = 0;

data->state = STATE_HEADER_SPACE; //进入判断引导码间隔值,是4.5ms还是2.25ms ?

return 0;caseSTATE_HEADER_SPACE:if(ev.pulse)break;if (eq_margin(ev.duration, NEC_HEADER_SPACE, NEC_UNIT)) { //如果ev.duration=4.5ms 间隔引导码data->state = STATE_BIT_PULSE; //进入解析32bit模式

return 0;

}else if (eq_margin(ev.duration, NEC_REPEAT_SPACE, NEC_UNIT / 2)) { //如果ev.duration=2.5ms ,表示重复引导码

if (!dev->keypressed) { //dev->keypressed是松开的,则放弃(这里有BUG,后面会分析到)IR_dprintk(1, "Discarding last key repeat: event after key up");

}else{

rc_repeat(dev);//dev->keypressed是未松开,则上报事件IR_dprintk(1, "Repeat last key");

data->state =STATE_TRAILER_PULSE;

}return 0;

}break;case STATE_BIT_PULSE: //接收数据位的脉冲数据

if (!ev.pulse)break;if (!eq_margin(ev.duration, NEC_BIT_PULSE, NEC_UNIT / 2)) //不等于0.56ms,则忽略掉

break;

data->state = STATE_BIT_SPACE; //等于0.56ms,接下来进入STATE_BIT_SPACE,开始解析数据bit

return 0;caseSTATE_BIT_SPACE:if(ev.pulse)break;if (data->necx_repeat && data->count == NECX_REPEAT_BITS &&geq_margin(ev.duration,

NEC_TRAILER_SPACE, NEC_UNIT/ 2)) { //解析NECX编码格式IR_dprintk(1, "Repeat last key");

rc_repeat(dev);

data->state =STATE_INACTIVE;return 0;

}else if (data->count >NECX_REPEAT_BITS)

data->necx_repeat = false;

data->bits <<= 1;if (eq_margin(ev.duration, NEC_BIT_1_SPACE, NEC_UNIT / 2)) //1.68ms 数据1data->bits |= 1;else if (!eq_margin(ev.duration, NEC_BIT_0_SPACE, NEC_UNIT / 2)) //既不等于1.68ms,也不等于0.56ms,则是无效数据

break;

data->count++;if (data->count == NEC_NBITS) //data->count == 32,则表示数据接收完成data->state =STATE_TRAILER_PULSE;elsedata->state =STATE_BIT_PULSE;return 0;caseSTATE_TRAILER_PULSE:if (!ev.pulse)break;if (!eq_margin(ev.duration, NEC_TRAILER_PULSE, NEC_UNIT / 2))break;

data->state =STATE_TRAILER_SPACE;return 0;caseSTATE_TRAILER_SPACE:if(ev.pulse)break;if (!geq_margin(ev.duration, NEC_TRAILER_SPACE, NEC_UNIT / 2))break;

address= bitrev8((data->bits >> 24) & 0xff);

not_address= bitrev8((data->bits >> 16) & 0xff);

command= bitrev8((data->bits >> 8) & 0xff);

not_command= bitrev8((data->bits >> 0) & 0xff);if ((command ^ not_command) != 0xff) { //解析数据IR_dprintk(1, "NEC checksum error: received 0x%08x",

data->bits);

send_32bits= true;

}if(send_32bits) {/*NEC transport, but modified protocol, used by at

* least Apple and TiVo remotes*/scancode= data->bits;

IR_dprintk(1, "NEC (modified) scancode 0x%08x", scancode);

}else if ((address ^ not_address) != 0xff) {/*Extended NEC*/scancode= address << 16 |not_address<< 8 |command;

IR_dprintk(1, "NEC (Ext) scancode 0x%06x", scancode);

}else{/*Normal NEC*/scancode= address << 8 |command;

IR_dprintk(1, "NEC scancode 0x%04x", scancode);

}if (data->is_nec_x)

data->necx_repeat = true;

rc_keydown(dev, scancode,0); //通过scancode编码来上报按键事件data->state =STATE_INACTIVE;return 0;

}//… …}

3.2接下来分析ir_nec_decode ()->rc_keydown()如何通过scancode编码来上报按键事件

void rc_keydown(struct rc_dev *dev, intscancode, u8 toggle)

{

unsignedlongflags;

u32 keycode= rc_g_keycode_from_table(dev, scancode); //从键映射表里找到编码对应的键值

spin_lock_irqsave(&dev->keylock, flags);if(keycode){ //如果找到键值ir_do_keydown(dev, scancode, keycode, toggle);//上报按键事件

if (dev->keypressed) { //如果是按下,则启动timer_keyup定时器, IR_KEYPRESS_TIMEOUT(20ms)后上报key松开事件dev->keyup_jiffies = jiffies +msecs_to_jiffies(IR_KEYPRESS_TIMEOUT);

mod_timer(&dev->timer_keyup, dev->keyup_jiffies);

}

}else{

dev->last_scancode = 0;

dev->last_toggle = 0;

dev->last_keycode = 0;

}

spin_unlock_irqrestore(&dev->keylock, flags);

}

上个函数里的dev->timer_keyup定时器对应的函数为ir_timer_keyup(),该函数会去调用一次ir_do_keyup()函数,上报key松开事件,该函数如下:

如上图所示,我们发现dev->keypressed = false,这就是解码函数出现的BUG:

1)比如当遥控器当按下按键时,会上报一次按键按下事件,并启动20ms定时器,用来自动上报按键自动按起事件,并标记dev->keypressed = false.

2)然后,如果遥控器一直按下不松手的话,会隔110ms发送一次9ms+2.25ms重复引导码

3)然后内核将会调用ir_nec_decode()进行解码2.25ms

4. 修改ir_nec_decode()函数

接下来,我们修改ir_nec_decode()函数,实现按键重复按下,并实现rc_map->repeat_key.

为什么要实现rc_map->repeat_key?

因为rc_map->scan里存储的键值表仅仅表示可支持按下的按键, 而rc_map->repeat_key里存储的才是表示可重复按下的按键.

修改后的代码如下所示:

static int ir_nec_decode(struct rc_dev *dev, structir_raw_event ev)

{struct nec_dec *data = &dev->raw->nec;

u32 scancode=0;

u8 address, not_address, command, not_command;bool send_32bits = false;static int es9038_c28=0,es9038_c29=0,es9038_c30=0,es9038_c31=0;if (!(dev->enabled_protocols &RC_BIT_NEC))return 0;if (!is_timing_event(ev)) {if(ev.reset)

data->state =STATE_INACTIVE;return 0;

}

IR_dprintk(2, "NEC decode started at state %d (%uus %s)",

data->state, TO_US(ev.duration), TO_STR(ev.pulse));switch (data->state) {caseSTATE_INACTIVE:if (!ev.pulse)break;if (eq_margin(ev.duration, NEC_HEADER_PULSE, NEC_UNIT * 2)) {

data->is_nec_x = false;

data->necx_repeat = false;

}else if (eq_margin(ev.duration, NECX_HEADER_PULSE, NEC_UNIT / 2))

{ data->is_nec_x = true;

}else

break;

data->count = 0;

data->state =STATE_HEADER_SPACE;return 0;caseSTATE_HEADER_SPACE:if(ev.pulse)break;if(eq_margin(ev.duration, NEC_HEADER_SPACE, NEC_UNIT)) {data->state =STATE_BIT_PULSE;return 0;

}else if (eq_margin(ev.duration, NEC_REPEAT_SPACE, NEC_UNIT / 2)) { //处理重复编码

data->state =STATE_TRAILER_SPACE;

IR_dprintk(1, "Discarding last key repeat: event after key up");return 0;

}else

break;caseSTATE_BIT_PULSE:if (!ev.pulse)break;if (!eq_margin(ev.duration, NEC_BIT_PULSE, NEC_UNIT / 2))break;

data->state =STATE_BIT_SPACE;return 0;caseSTATE_BIT_SPACE:if(ev.pulse)break;if (data->necx_repeat && data->count == NECX_REPEAT_BITS &&geq_margin(ev.duration,

NEC_TRAILER_SPACE, NEC_UNIT/ 2)) {

IR_dprintk(1, "Repeat last key");

rc_repeat(dev);

data->state =STATE_INACTIVE;return 0;

}else if (data->count >NECX_REPEAT_BITS)

data->necx_repeat = false;

data->bits <<= 1;if (eq_margin(ev.duration, NEC_BIT_1_SPACE, NEC_UNIT / 2))

data->bits |= 1;else if (!eq_margin(ev.duration, NEC_BIT_0_SPACE, NEC_UNIT / 2))break;

data->count++;if (data->count ==NEC_NBITS)

data->state =STATE_TRAILER_SPACE;elsedata->state =STATE_BIT_PULSE;return 0;caseSTATE_TRAILER_SPACE:

{struct rc_map *rc_map = &dev->rc_map;struct rc_map_table *repeat_key = rc_map->repeat_key;

unsignedint repeat_size = rc_map->repeat_size; //获取 repeat_size,是否有支持重复按下的按键scancode=data->bits;if (!ev.pulse)break;if (!eq_margin(ev.duration, NEC_TRAILER_PULSE, NEC_UNIT / 2))break;

printk("NEC scancode=0x%x",scancode);if(!scancode)break;if (data->is_nec_x)

data->necx_repeat = true;

rc_keydown(dev, scancode,0); //上报事件

if(repeat_key){int i = 0;while(repeat_size){if(scancode ==repeat_key[i].scancode){break;

}

repeat_size--;

i++;

}if(repeat_size==0) //repeat_size==0,表示没找到有支持重复按键,则清空data->bitsdata->bits = 0; }elsedata->bits = 0;return 0;

}

}

IR_dprintk(1, "NEC decode failed at count %d state %d (%uus %s)",

data->count, data->state, TO_US(ev.duration), TO_STR(ev.pulse));

data->state =STATE_INACTIVE;return -EINVAL;

}

接下来下章,自己创建一个红外platform_device平台设备

创建红外platform_device平台设备步骤为:

1) 创建一个platform_device设备,其中.name= "gpio-rc-recv",并注册设备

2)在driversmediackeymaps里创建一个名字为rc-my-text.c键值映射文件

未完待续~

linux红外遥控进程,46.Linux-分析rc红外遥控平台驱动框架,修改内核的NEC解码函数BUG(1)...相关推荐

  1. linux临时启动进程命令,Linux常用命令(一)服务控制及优化启动过程

    Linux常用命令(一) 服务控制及优化启动过程 一.Red hat系统开机引导过程 1.linux操作系统的引导过程一般包括以下几个阶段:开机自检.MBR引导.GRUB菜单.加载Linux内核.in ...

  2. Linux访问其他进程空间,Linux环境进程间通信系列(五):共享内存

    共享内存可以说是最有用的进程间通信方式,也是最快的 IPC 形式.两个不同进程 A . B 共享内存的意思是,同一块物理内存被映射到进程 A . B 各自的进程地址空间.进程 A 可以即时看到进程 B ...

  3. linux保存动态进程信息,Linux 命令 - top: 动态显示进程信息

    命令格式 top -hv | -abcHimMsS -d delay -n iterations -p pid [, pid ...] 命令参数 -a 根据内存的使用排序. -b 以批处理模式操作. ...

  4. linux查看睡眠进程,关于 Linux 进程的睡眠和唤醒 ,来看这篇就够了~

    1 Linux 进程的睡眠和唤醒 在 Linux 中,仅等待 CPU 时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING.一旦一个运行中的进程时 ...

  5. linux下的进程创建,Linux下进程的创建

    这篇文章主要是讲解到Linux进程的控制,包括程序和进程.守护进程.守护进程的出错处理. 1.程序和进程 程序(program)是存放在磁盘文件中的可执行文件,程序的执行实例被称为进程(process ...

  6. 三星 摄像头 linux,基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)...

    FIMC这个名字应该是从S5PC1x0开始出现的,在s5pv210里面的定义是摄像头接口,但是它同样具有图像数据颜色空间转换的作用.而exynos4412对它的定义看起来更清晰些,摄像头接口被定义为F ...

  7. 驱动框架2——内核驱动框架中LED的基本情况、初步分析

    以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除. 一.内核驱动框架中LED的基本情况 1.相关文件 (1)drivers/leds目录 驱动框架规定的LED这种硬件的驱动应该待的地方. (2 ...

  8. linux子进程父进程例子,linux 子进程访问父进程

    问题分析 ECS Linux 系统下 Apache 的默认工作模式是 prefork MPM,使用多个子进程,每个子进程只有一个线程.每个进程在某个确定的时间只能维持一个连接,效率高,但内存占用量比较 ...

  9. linux dup跨进程使用,linux下dup/dup2函数的用法

    系统调用dup和dup2能够复制文件描述符.dup返回新的文件文件描述符(没有用的文件描述符最小的编号).dup2可以让用户指定返回的文件描述符的值,如果需要,则首先接近newfd的值,他通常用来重新 ...

最新文章

  1. fluent二维叶型仿真_ICEM划分嵌套网格之二维圆柱绕流
  2. [ARM-assembly]-A64的load/store指令总结
  3. 129. Sum Root to Leaf Numbers
  4. mysql中的所有类型_mysql中常用的数据类型
  5. python中char的用法_如何从C++返回char **并使用cType在Python中填充它?
  6. 提高数据库的查询速率及其sql语句的优化问题
  7. excel转置怎么操作_技能篇:Excel数据处理小技巧
  8. 移动硬盘提示格式化怎么办怎么恢复数据?
  9. UBound 函数 (Visual Basic)
  10. 【火车票】心蓝抢票软件
  11. 武汉大学计算机学院跳楼,武汉大学一男生跳楼身亡,自杀原因反思:徒劳教育往往跟父母有关...
  12. c罗说什么语言,C罗会说几种语言? 揭金球奖给梅西内马尔当翻译趣事
  13. 通过WEB链接打开QQ窗体,实现与陌生人交谈的目的
  14. 得物 App H5秒开优化实战
  15. linux下exit()与_exit()的区别
  16. makefile指定头文件路径_Linux应用编程之quot;一文搞定Makefilequot;(下)
  17. 企业做培训直播,也不要忘记品牌营销!
  18. 新手上路 Vue腾讯云开发实战项目 问卷调查
  19. 宽带换了新的账号怎么连接服务器地址,换宽带后路由器怎么设置
  20. 1007 Problem G

热门文章

  1. Github Actions - 实现百度贴吧自动签到
  2. 晶联讯12864液晶+STM32+HAL库 IO模拟SPI成功实现显示。
  3. 安装torchvision-0.12.0+cu113版本
  4. HTML初学者--列表的简单制作
  5. 不要在翻译中迷失:如何进行网站本地化-20150105早读课
  6. 使用IDEA第一次open或者import新的gradle项目出现Refresh gradle project卡死的附解决办法
  7. 从互动直播到在线抓娃娃,实时视频超低延迟架构的思考与实践
  8. java fail 方法_java中的fail是什么意思
  9. java火星坐标与真实坐标的转换
  10. 微信小程序+百度AI OCR二代身份证识别