Linux内核红外遥控子系统LIRC.docx
《Linux内核红外遥控子系统LIRC.docx》由会员分享,可在线阅读,更多相关《Linux内核红外遥控子系统LIRC.docx(14页珍藏版)》请在冰豆网上搜索。
Linux内核红外遥控子系统LIRC
LinuxInfraredRemote
ControlSystem
修订历史
版本
日期
作者
描述
0.1
2011-10-12
杜昌彬
初稿
1.红外遥控基础知识
1.1红外遥控简介
红外遥控协议有有很多,比如RC-5,RC-6,NEC,SIRC等,不过协议都比较简单,基本上都是以脉冲宽度或脉冲间隔来编码。
当遥控器上按下按键时,遥控器逻辑单元会产生一个完整的逻辑脉冲波形,这个波形上包含了遥控命令的信息,他是红外传输的基带信号。
这个波形被送到遥控器的调制单元,经调制单元调制成高频的红外电磁波信号,并由发光二极管发射出去。
如下图的左边模块。
红外遥控的信号的产生和接收
红外电磁波信号现在一般使用一体化接收头接收,接收头同时完成了信号的解调和放大,其输出信号就是红外的基带脉冲信号。
解调后的信号可直接送入信号处理器中由处理器对脉冲波形进行解码,也就是将经编码的脉冲信号翻译成逻辑数字。
根据不同的控制协议,解码方式不同。
红外接收头,一跟线用于输出脉冲信号,其他两根是电源线和地线
1.2红外遥控协议
下面已sony的SIRC协议为列说明。
编码
SIRC协议使用脉冲宽度对每一比特位进行编码,编码规则如下:
SIRC协议编码
首先,每一个脉冲后跟一个固定宽为600微秒的间隔,而每一个脉冲便是一个逻辑数字,并由脉冲的宽度决定是0还是1:
脉冲宽度1200微秒表示逻辑1,宽度600微秒表示逻辑0.
幀格式
当按下遥控器上的按键时,遥控器会发送一个命令信号,这个信号就是一个幀,它包含了命令字段和地址(设备)字段,以及扩展字段。
当按住按键不放时,遥控器会不断的发送这一命令信号,直到松开。
SIRC协议的幀格式有12位、15位、20位三种,如下所示:
一个幀以一个起始标志(图中的红色)开始,它是一个2400微秒的脉冲并跟一个间隔。
之后是7字节的命令字段(图中的橙色),这个字段用于识别按下了遥控器上的哪个按键;然后是地址字段(图中的蓝色),用于识别控制的是什么类型的设备;对于20位宽格式,还有一个扩展字段,用于传输其他信息。
址值和设备类型映射表
一种命令值和具体按键的映射表
示例:
Sony电视遥控器上“音量-”按键对应的脉冲波形
对于脉冲波形的解码,一般用一个专门的硬件单元完成,也可以在CPU中利用如GPIO等检测接收器输出的波形然后使用软件的方式解码,但这种方式效率显然很低。
2.Linux对红外遥控的支持
Linux上通过LIRC子系统对红外控制提供支持,它包含几个部分:
lirc核心、协议原始脉冲解码器、按键映射表、红外输入设备驱动。
LIRC代码在:
drivers/media/IR
2.1.协议原始脉冲解码器模块
解码器模块实现用软件的方法对原始脉冲进行解码。
解码器用一个ir_raw_handler结构表示。
structir_raw_handler
{
structlist_headlist;
int(*decode)(structinput_devinput_dev,structir_raw_eventevent);/*解码函数*/
int(*raw_register)(structinput_devinput_dev);/*注册函数*/
int(*raw_unregister)(structinput_devinput_dev);/*卸载函数*/
};
解码器通过注册和卸载函数:
intir_raw_handler_register(structir_raw_handler*ir_raw_handler)
voidir_raw_handler_unregister(structir_raw_handler*ir_raw_handler)
当注册解码器时,ir_raw_handler的raw_register函数被调用,所以可在其进行一些解码器初始化工作。
相应的,卸载时raw_unregister函数被调用。
所有注册的解码器放在一个全局链表ir_raw_handler_list中,lirc会便利每个解码器对报告的波形进行解码,注意,此时当任何一个解码器返回一个错误,后面的解码器不会被执行,所以不要将不使用的解码器模块同时加载到内核中。
解码器的主体就是decode函数,lirc核心会将驱动报告的每个脉冲一次一次的传递到decode函数,而decode函数的实现就是一个状态机,每一次输入导致进入下一状态,直到一次解码完成,然后返回起始状态进行下次解码。
再LIRC中,每个脉冲(包括脉冲间隔)用一个ir_raw_event结构表示:
structir_raw_event
{
unsignedpulse:
1;/*是脉冲还是间隔*/
unsignedduration:
31;/*宽度,以ns为单位,一个0ns的脉冲表示重新开始解码*/
};
lirc中对于脉冲宽度的比较使用eq_margin()、geq_margin()函数,它允许宽度值在二分之一单元上下波动。
boolgeq_margin(unsignedd1,unsignedd2,unsignedmargin)
booleq_margin(unsignedd1,unsignedd2,unsignedmargin)
解码器的完整实现可参考sonysirc解码器实现:
ir-sony-decoder.c
2.2.按键映射表模块
按键映射模块都放在keymaps目录下。
不同的遥控器有不同的按键映射,按键映射模块的作用就是将扫描码与Linuxinput系统标准事件对应起来。
映射表的注册和卸载:
intir_register_map(structrc_keymap*map)
voidir_unregister_map(structrc_keymap*map)
按键映射模块的主体就是一个ir_scancode结构数组,每个元素是一对按键映射。
2.3.红外输入设备驱动
红外输入设备驱动负责向LIRC核心报告脉冲或直接报告扫描码事件。
红外输入设备用ir_input_dev结构描述:
structir_input_dev
{
structdevicedev;/*device*/
char*driver_name;/*Nameofthedrivermodule*/
structir_scancode_tablerc_tab;/*scan/keytable*/
unsignedlongdevno;/*devicenumber*/
conststructir_dev_props*props;/*Deviceproperties*/
structir_raw_event_ctrl*raw;/*forrawpulse/spaceevents*/
structinput_dev*input_dev;/*theinputdeviceassociatedwiththisdevice*/
/*keyinfo-neededbyIRkeycodehandlers*/
spinlock_tkeylock;/*protectsthebelowmembers*/
boolkeypressed;/*currentstate*/
unsignedlongkeyup_jiffies;/*whenshouldthecurrentkeypressbereleased?
*/
structtimer_listtimer_keyup;/*timerforreleasingakeypress*/
u32last_keycode;/*keycodeoflastcommand*/
u32last_scancode;/*scancodeoflastcommand*/
u8last_toggle;/*toggleoflastcommand*/
};
设备注册和卸载:
intir_input_register(structinput_dev*dev,constchar*map_name,
conststructir_dev_props*props,constchar*driver_name)
voidir_input_unregister(structinput_dev*input_dev)
注册流程:
(1)分配一个input设备,input_allocate_device();
(2)对input设备进行一些初始化设置,但事件掩码不需要设置;
(3)将分配的input设备结构的地址作为参数调用ir_input_register(),以后与lirc核心的交互都是通过这个input设备结构的地址进行的。
ir_input_register()函数的map参数指定要使用的按键映射表,所有映射表定义在rc-map.h中,比如RC_MAP_RC5_TV。
props参数可以为NULL,但若要使用解码器模块对原始脉冲解码(比如无法直接从硬件获得扫描码时),则要设置。
比如:
staticstructir_dev_propsirc_props={
.driver_type=RC_DRIVER_IR_RAW,/*指定需要软件解码*/
.allowed_protos=IR_TYPE_SONY,
};
input_dev=input_allocate_device();
if(!
irc_input_dev){
ret=-ENOMEM;
gotoerr_input_allocate_device;
}
input_dev->name="IRC";
ret=ir_input_register(input_dev,RC_MAP_RC5_TV,&irc_props,NULL);
if(ret){
gotoerr_ir_input_register;
}
input_dev->rep[REP_DELAY]=400;
input_dev->rep[REP_PERIOD]=33;
对于可直接从硬件读取到扫描码的设备,可用以下函数包括扫描码事件:
voidir_keydown(structinput_dev*dev,intscancode,u8toggle)
对于只能获得原始脉冲的设备,需先调用下面函数报告每个脉冲和脉冲间隔:
intir_raw_event_store(structinput_dev*input_dev,structir_raw_event*ev);
intir_raw_event_store_edge(structinput_dev*input_dev,enumraw_event_typetype)
第一个函数要求驱动自己填充ir_raw_event结构,并且在报告第一个脉冲前,需要调用ir_raw_event_reset()函数重置解码器,当驱动认为一个完成的波形已报告完毕后,调用ir_raw_event_handle()启动解码;第二个函数自动生成一个ir_raw_event结构,脉冲宽度根据前后两次调用ir_raw_event_store_edge()函数的时间间隔字段计算,并且会自动调用ir_raw_event_reset()和ir_raw_event_handle()函数。
type参数指定是何脉冲。
enumraw_event_type
{
IR_SPACE=(1<<0),
IR_PULSE=(1<<1),
IR_START_EVENT=(1<<2),
IR_STOP_EVENT=(1<<3),
};
其他两个驱动常用的接口:
voidir_repeat(structinput_devdev)/*重复上次按键*/
u32ir_g_keycode_from_table(structinput_devinput_dev,u32scancode)/*获得扫描码对应的按键*/
2.4.LIRC对按住按键时重复事件的处理
首先,重复是自动的,它使用了input子系统的REP功能。
当第一次向lirc报一个扫描码事件时,lirc会向input子系统报告相应的按键事件,并启动一个定时器,该定时器在超时后会上报对应的按键松开事件;之后若在250ms(这个值等于input子系统的rep延时的默认值)内该设备又报告了同一个扫描码,这时只是将定时器再推后250ms,并不报告新的按键事件,也就是说按键的重复由input系统处理。
这种设计主要是考虑到遥控器的限制,有的遥控器没有按键按下和松开之分(虽然在按下和松开时都有脉冲,但没有区分字段,而且有时可能会丢失信号)。
3.附使用GPIO接收遥控命令驱动代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineGPIO_IRC_MAPRC_MAP_SONY_SIRC12_TV
//#defineDEBUG
staticstructinput_dev*irc_input_dev;
staticstructtimer_listirc_timer;
staticatomic_tf_restart;
externu64gpt_get_cycles(void);
externunsignedlonggpt_delta_to_ns(u64delta);
staticvoidgpio_irc_raw_event_reset()
{
ir_raw_event_reset(irc_input_dev);
atomic_set(&f_restart,1);
}
staticvoidgpio_irc_timer_func(unsignedlongdata)
{
structir_raw_eventev;
/*endpulse*/
ev.pulse=false;
ev.duration=~0u;
ir_raw_event_store(irc_input_dev,&ev);
ir_raw_event_handle(irc_input_dev);
gpio_irc_raw_event_reset();
#ifdefDEBUG
printk("gpio_ircrestartevent\n");
#endif
}
staticirqreturn_tgpio_irc_rx_irq(intirq,void*dev_id)
{
staticbooltriger_falling=1;
staticunsignedlonglast;
structir_raw_eventev;
if(atomic_read(&f_restart)){
atomic_set(&f_restart,0);
}else{
ev.pulse=!
triger_falling;
ev.duration=gpt_delta_to_ns((unsignedlong)gpt_get_cycles()-last);
ir_raw_event_store(irc_input_dev,&ev);
}
last=(unsignedlong)gpt_get_cycles();
if(triger_falling)
set_irq_type(irq,IRQF_TRIGGER_RISING);
else
set_irq_type(irq,IRQF_TRIGGER_FALLING);
triger_falling=!
triger_falling;
mod_timer(&irc_timer,jiffies+HZ/50);
returnIRQ_HANDLED;
}
staticstructir_dev_propsgpio_irc_props={
.driver_type=RC_DRIVER_IR_RAW,
.allowed_protos=IR_TYPE_SONY,
};
staticintgpio_irc_probe(structplatform_device*pdev)
{
intgpio,gpio_irq;
intret;
gpio=pdev->resource[0].start;
gpio_irq=gpio_to_irq(gpio);
if(gpio_request(gpio,"gpio_irc")){
dev_err(&pdev->dev,"rxpinnotavailable\n");
return-1;
}
gpio_direction_input(gpio);
setup_timer(&irc_timer,gpio_irc_timer_func,gpio_irq);
irc_input_dev=input_allocate_device();
if(!
irc_input_dev){
ret=-ENOMEM;
gotoerr_input_allocate_device;
}
irc_input_dev->name="MXCGPIOIRreceiver";
ret=ir_input_register(irc_input_dev,GPIO_IRC_MAP,&gpio_irc_props,NULL);
if(ret){
pr_err("gpio_irc:
ir_input_register()failed\n");
gotoerr_ir_input_register;
}
irc_input_dev->rep[REP_DELAY]=400;
irc_input_dev->rep[REP_PERIOD]=33;
gpio_irc_raw_event_reset();
ret=request_irq(gpio_irq,gpio_irc_rx_irq,0,"gpio_irc",pdev);
if(ret){
dev_err(&pdev->dev,"unabaletorequestirq\n");
gotoerr_request_irq;
}
set_irq_type(gpio_irq,IRQF_TRIGGER_FALLING);
return0;
err_request_irq:
ir_input_unregister(irc_input_dev);
err_ir_input_register:
input_free_device(irc_input_dev);
err_input_allocate_device:
gpio_free(gpio);
returnret;
}
staticintgpio_irc_remove(structplatform_device*pdev)
{
intgpio,gpio_irq;
del_timer_sync(&irc_timer);
gpio=pdev->resource[0].start;
gpio_irq=gpio_to_irq(gpio);
free_irq(gpio_irq,NULL);
gpio_free(gpio);
ir_input_unregister(irc_input_dev);
input_free_device(irc_input_dev);
return0;
}
staticintgpio_irc_suspend(structplatform_device*pdev,pm_message_tstate)
{
return0;
}
staticintgpio_irc_resume(structplatform_device*pdev)
{
return0;
}
staticstructplatform_driverirc_driver={
.driver={
.name="gpio_irc",
},
.probe=gpio_irc_probe,
.remove=gpio_irc_remove,
.suspend=gpio_irc_suspend,
.resume=gpio_irc_resume,
};
staticintgpio_irc_init(void)
{
returnplatform_driver_register(&irc_driver);
}
staticvoidgpio_irc_exit(void)
{
platform_driver_unregister(&irc_driver);
}
late_initcall_sync(gpio_irc_init);
module_exit(gpio_irc_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("infraredremotecontrolthroughgpio");