NEC红外遥控协议理解与实现
在上个链接里转载了NEC标准的远程控制协议,家电的遥控器基本遵守这个标准。
红外发射管有2个管脚,发送的是经过38KHz时钟调制过的信号。例如下图使用PWM产生一个等占空时钟信号用于调制。
接收管收下来的信号已经经过了解调,可以直接连接系统的外部中断脚。
下面通过逻辑分析仪来实际测量一下。
随便找了个红外遥控器,测量power键按下后的波形。首先是信号发送侧。
可以看到,0秒开始是一个按键动作,0.11秒后的那个波形是一个repeat,展开:
把波形重叠的部分展开,就可以看到这个38KHz的调制时钟
如果持续按下遥控器上的按键,那么就会发送连续的repeat信号,发送的间隔也基本满足协议上指出的110ms
下面是接收侧
可以看到信号被解调了,也就是说重叠的部分变成了低电平。
最后通过编写协议分析插件的方式,来描述如何通过程序来理解上面的波形。
我使用的逻辑分析软件是 Saleae Logic 1.1.15 编译环境是Microsoft Visual Studio 2008,编译时需要SaleaeAnalyzerSdk-1.1.14。完整的代码从这里下载。
分析的方法是测量两个信号下降沿之间的时间长度,这里先定义一些时间参数。
/* Timing define , unit : ms*/
#defineSTART_LOW_TIMING 9000
#defineSTART_HIGH_TIMING 4500
#defineREPEAT_HIGH_TIMING 2250
#defineLOGIC_ONE_TIMING (562*3)
#defineLOGIC_ZERO_TIMING (562*1)
#defineDATA_LOW_TIMING (562*1)
从逻辑分析仪测量的结果,可以发现发射器给出的信号并不是非常的精确,所以我们需要定义误差范围。
/* Timing Margin , unit : ms*/
#definedelta 20
在下面代码里,通过API函数AdvanceToNextEdge来获取下一个信号发生变化的采样点,如果对应的采样点是低电平,则表示下跳沿,这时和前一个下跳沿采样点的时间做差,按照采样频率换算成时间间隔。再根据上面定义的时间常量来判断这是一个START标记、REPEAT标记、逻辑1、逻辑0还是无效的信号。对于逻辑1和0的情况,需要通过移位来整理成32bits的有效数据,这里要特别注意协议里规定,先发送的是LSB,后发送的MSB。
voidIRNECAnalyzer::WorkerThread()
{
U64per_sample = 0;
U64cur_sample = 0;
U64starting_sample = 0;
U64differ = 0;
char action = state_down;
U8fail = 0;
U64data = 0;
U32code = 0;
U8count = 0;
U8data_f = 0;
mResults.reset(new IRNECAnalyzerResults( this, mSettings.get() ) );
SetAnalyzerResults(mResults.get() );
mResults->AddChannelBubblesWillAppearOn(mSettings->mInputChannel );
mSampleRateHz= GetSampleRate();
mSerial= GetAnalyzerChannelData( mSettings->mInputChannel );
if( mSerial->GetBitState() == BIT_LOW )
mSerial->AdvanceToNextEdge();
for(;;){
mSerial->AdvanceToNextEdge();
cur_sample= mSerial->GetSampleNumber();
//只处理时钟的下跳沿
if(mSerial->GetBitState() == BIT_LOW){
differ= (cur_sample - per_sample)*1000000/mSampleRateHz;
//判断是否是REPEAT信号 if(((START_LOW_TIMING+REPEAT_HIGH_TIMING-delta*6)<differ)&&((differ)<(START_LOW_TIMING+REPEAT_HIGH_TIMING+delta*6))){
action=state_repeat;
fail=0;
}else //判断是否是START信号
if(((START_LOW_TIMING+START_HIGH_TIMING-delta*6)<differ)&&((differ)<(START_LOW_TIMING+START_HIGH_TIMING+delta*6))){
action=state_start;
fail=0;
}else//判断是否是逻辑1if(((LOGIC_ONE_TIMING+DATA_LOW_TIMING-delta*2)<differ)&&((differ)<(LOGIC_ONE_TIMING+DATA_LOW_TIMING+delta*2))){
action=state_data;
data_f=1;
fail=0;
}else //判断是否是逻辑0if(((LOGIC_ZERO_TIMING+DATA_LOW_TIMING-delta*2)<differ)&&((differ)<(LOGIC_ZERO_TIMING+DATA_LOW_TIMING+delta*2))){
action=state_data;
data_f=0;
fail=0;
}else{//否则为错误信号
fail=1;
}
if(fail==0){
switch (action){
case state_start:
code= 0;
count= 0;
starting_sample= cur_sample;
AddFrame(per_sample,cur_sample, 0, FStart);
break;
case state_data:
data_f= data_f << count;
code= code | data_f;
count++;
if(count == 8){
count= 0;
AddFrame(starting_sample,cur_sample, code, FData);
code= 0;
starting_sample= cur_sample;
}
break;
case state_repeat:
AddFrame(per_sample,cur_sample, data, FRepeat);
break;
default:
break;
}
}
per_sample= cur_sample;
}
}
}
加载上面的插件后,可以看到分析的结果
所以如果将这份代码放在板卡上运行,首先应该将接收器的信号接到处理器的外部中断管脚,然后注册一个下跳沿触发的快速中断。然后最通常的情况你需要再注册一个标准的输入设备,映射一下遥控器码字和按键事件的对应关系就可以了。