一、红外遥控概述
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机和手机系统中。
我们的MS1824/MS1825/MS1851等产品普遍默认都是使用物理按键控制相关功能,现用MS8006开发红外遥控功能替代物理按键,由于目前红外普遍采用采用NEC编码即载波为38Khz的PWM(脉冲宽度调制)的方式实现遥控发送接收的。所以MS8006的红外按键代码也是按照PWM方式进行解码的。
二、红外编码规则
编码规则分为五个部分,按编码顺序分别是引导码、用户码、数据码、数据反码、重复简码构成,本程序中引导码为4.5ms或9ms低电平+4.5ms高电平,引导码之后就是16位用户码,用户码和数据码以及数据反码的格式是一致的,即0.57ms低电平+0.56ms高电平代表数据“0”位,0.57ms低电平+1.67ms高电平代表数据“1”位,重复简码即是当某个按键持续按下时只有第一组是全码,后面就是简码代替了,根据实际需要本程序中没有设计检测重复简码的部分,所以在红外发射端有无重复码都不会影响接收端对全码的识别。
下图为编码波形实例
例如遥控器数据的编码如下:
发射端用户码为(由左至右):00110101 01001101
数据码1:10000000 对应反码:01111111
数据码2:01000000 对应反码:10111111
数据码3:11000000 对应反码:00111111
数据码4:00100000 对应反码:11011111
数据码5:10100000 对应反码:01011111
数据码6:01100000 对应反码:10011111
数据码7:01010000 对应反码:10100000
提示:上述示例编码是从引导码后从左边至右边按照顺序写入0或1,而0和1的编码规则在前面已经详细描述。
三、硬件实现
由于只对原有电路加上一个红外接收管(1838B)即可,所以并没有硬件和原理图的改变,只需另外引出一个中断引脚接至接收管即可,这里用MS8006的GPIO D4做为红外接收线脚,并在软件中设置为外部中断输入。
四、软件部分
1、软件实现原理
利用现在普遍采用的红外接收头1838B作为红外信号采集并将其OUT引脚接至MS8006的中断引脚,因为一旦有正确信号进来需要立即执行,否则会错过引导码或其他问题导致遥控不灵敏等现象,MS8006所有GPIO引脚均可设置为外部中断源,外部中断设置为下降沿触发,然后启动计时器计时本次下降沿到下一下降沿触发的时间再结合编码规则部分的说明完成对遥控发射端的信号解码,定时器用MS8006的TIM10 basetime通用定时器。
2、红外解码程序说明
特别注意:以下程序中IR_data容易混乱,有一种方法可以更好的理解它,它是实际的定时器计时时间,是真实存在的一个可变的变量,程序中很多判断它的时间大于或小于某个数值,把这个数值只是看作一个数值并且和IR_data变量无关的确定后不可变的固定的数值即可,比如高电平时间是2.24ms,那么可以判断IR_data大于2ms即可认为是高电平,因为IR_data此时真正数值是2.24,是大于2的,所以可以判断为真。判断低电平时由于低电平时间比高电平短的,所以在1.14ms左右就重新进入中断了,是等不到2ms的,所以可以只要是小于2.4ms就先默认此位是低电平,当能检测到大于2ms时才判为高电平,大于的数值可根据需要选择,但一定要选大于1.14小于2.24的值,这里选得是2,为了兼容和减少误码率,前后都预留了点时间
VOID GPIOD_IRQHandler(VOID) //红外接收头OUT脚接在port D4并
//将此引脚设置为外部中断,ms8006 GPIOD中断号为3
{
static UINT8 Flag_end=0;
static UINT8 IR_num = 0;
UINT8 i; //变量定义
HAL_GPIO_EXTI_IRQHandler(IR_PORT,IR_PIN); //清除外部中断标志
TIM10->CR |= BASETIM_CR_INTEN;
TIM10->CR |= BASETIM_CR_TR;
NVIC_EnableIRQ(TIM10_IRQn); //使能定时器10,开始计时
__disable_irq(); //关闭总中断
if(IR_time <= 4) //滤除前端杂波,定时器定时50us,所
{ //以滤除50x4=200us的杂波
IR_data=0;
IR_num=0;
IR_time=0;
}
if(IR_time > 170 && IR_time < 300) //引导码的识别,引导码的低电
//平时间+高电平时间,为了更好的兼容性所以设置大于50x170=8.5ms小
//于15ms,通常引导码有两种,一种是4.5ms低电平+4.5ms高电平,另一种
//是4.5ms低电平+9ms高电平,此种算法可兼容两种方式
{
Flag_ir_start = 1; //此时引导码正确,所以开始解码数据的标志位置1
IR_num = 0; //用于计数,一般引导码后会有32个二进制数
//值组成用户码和数据码以及数据反码,此变量用于统计读取数据的个数
IR_data=0; //此变量用于存放读取到的数据,变量类型为非符32位
}
else if(IR_time >= 410) //当引导码大于20.5ms时则判断失败,不再
//读取数据,还有一点好处是这句话可以取消对重复简码的识别,提高兼容性
{
Flag_ir_start = 0; // 引导码读取失败,则不读取数据,标志位置0
IR_num = 0;
IR_data=0;
}
if((Flag_ir_start==1)&&(IR_time<=48)) /*标志位置1且两下降沿间隔
小于2.4ms时说明数据正确,根据编码规则只要大于或等于2.24ms即可,
此举为了提高兼容性和减少误码率(由于环境影响,实际可能在2.24ms上下波动)*/
{
IR_data<<=1; //将上次的数据先左移一位,低位再默认置0
if(IR_time>=40) //这里是大于2ms时就判断为高电平,因为按
//照规则低电平应该为1.14ms左右就进入下一个下降沿了所以是达不到
//2ms的,即能达到2ms的都是高电平了,而且实际上高电平时间为
//2.24ms肯定大于2ms的
{
IR_data |= (0x01<<0);//如果是高电平就将默认置0的低位再置1
}
IR_num++; //共有32个位,这里自加计数
if(IR_num > 31) //当IR_num大于31时,其实此时就是32了,
//就结束一组遥控码的读取并开始解码
{
Flag_end = 1; //结束标志,防止每次进中断时都能进入
//解码函数,必须读取完一组遥控码之后才能解码
Flag_ir_start=0; //此时开始读取标志置0,即当有新的引
//导码时才会读取新的一组遥控码
IR_time = 0; //计时变量清0
}
}
if(Flag_end) //结束读码进入解码算法
{
TIM10->CR &= 0xDF;
TIM10->CR &= 0x7F;
NVIC_DisableIRQ(TIM10_IRQn); //先将定时器关掉,直至下次
//读码时再次定时器打开重新计
//时,避免不用定时器还在计时
//容易造成计时混乱等错误
Inf_Rec_Buffer[0] = (UINT8)((IR_data>>0)&0xff);
Inf_Rec_Buffer[1] = (UINT8)((IR_data>>8)&0xff);
Inf_Rec_Buffer[2] = (UINT8)((IR_data>>16)&0xff);
Inf_Rec_Buffer[3] = (UINT8)((IR_data>>24)&0xff);
//以上四句是将读取到的32位数据分成4组8位数据
//分别存放在四个数组中便于对数据的识别和后面的使用
for(i=0;i<4;i++)
Inf_Rec_Buffer[i] = (UINT8)(~(reverse8(Inf_Rec_Buffer[i])));
//根据需要分别将4组数据的高低位全部颠倒然后再取反
#if 1
LOG1("Buffer[0]=",Inf_Rec_Buffer[0]);
LOG1("Buffer[1]=",Inf_Rec_Buffer[1]);
LOG1("Buffer[2]=",Inf_Rec_Buffer[2]);
LOG1("Buffer[3]=",Inf_Rec_Buffer[3]);
#endif //这里是用串口打印出解码成功后的数值,因为后面
//使用的就是此时对应的数值
if(Inf_Rec_Buffer[0] == (UINT8)(~Inf_Rec_Buffer[1]))
//如果数据码和数据反码取反后的数值是相等说明读
//码和解码是正确的,可进行赋值等使用
{
g_u8_Inf_Rec = Inf_Rec_Buffer[0]; //用低8位即图一中
//的数据反码作为遥控控制指令
key_switch(); //后面会介绍此函数,数据反码作为指令控制相应功能
}
Flag_end = 0; //将结束读码的标志置0,以便于读取下一组遥控码
IR_data=0; //此时本组遥控数据已经读完,置0继续存放下一组
IR_num=0; //计数变量也置0,为读取下一组做准备
}
__enable_irq(); //两次下降沿之间的数据分析完成,将中断
//再打开准备下一个下降沿
IR_time = 0; //将时间变量清0,也是为下一个两个下降沿之间的时间计时做准备
}
3、高低位逆转算法说明
短短几句话,算法也比较简单,可自行理解,原理大概就是先将8位数据分成2个4位同时调换中间两位,然后两边两位,最后将高4位和低4位反过来即可完成颠倒,比如c = 0B01011101(0X5D),那么return出来就是0B10111010(0XBA)
UINT8 reverse8( UINT8 c )
{
c = ( c & 0x55 ) << 1 | ( c & 0xAA ) >> 1;
c = ( c & 0x33 ) << 2 | ( c & 0xCC ) >> 2;
c = ( c & 0x0F ) << 4 | ( c & 0xF0 ) >> 4;
return c;
}
4、key_switch遥控按键指令识别函数说明
这个函数主要是将解码后的遥控数据用于实际中,可以当成按键来使用,按键是识别按键对应pin脚的值,这里只不过是以8位的数据来作为识别的而已,由于需求不一样,下面只列举两个按键值作为说明,特别注意的是用的是留个按键,当某个值对应一个PIN_KEYn会被置为0给当作按键的识别,其实就是在模拟机械按键,但此时应将其他的PIN值置1,因为这里不能像真的机械按键那样会弹起自动置1,需要软件手动设置的,还有最后有一个对0xff的判断,这个是当没有遥控器按下的时候读取到的遥控码全是0经过上文的反码后为0xff,这时的所有PIN_KEYn均为1,模拟没有机械按键按下的状态
VOID key_switch(VOID)
{
UINT8 g_u8_Inf_status = 0;
if(g_u8_Inf_Rec != g_u8_Inf_status)
{
g_u8_Inf_status = g_u8_Inf_Rec;
switch(g_u8_Inf_status)
{
case 0x01:
PIN_KEY1 = 0;
PIN_KEY2 = 1;
PIN_KEY3 = 1;
PIN_KEY4 = 1;
PIN_KEY5 = 1;
PIN_KEY6 = 1;
break;
case 0x02:
PIN_KEY2 = 0;
PIN_KEY1 = 1;
PIN_KEY3 = 1;
PIN_KEY4 = 1;
PIN_KEY5 = 1;
PIN_KEY6 = 1;
break;
case 0xff:
PIN_KEY1 = 1;
PIN_KEY2 = 1;
PIN_KEY3 = 1;
PIN_KEY4 = 1;
PIN_KEY5 = 1;
PIN_KEY6 = 1;
break;
default:
break;
}
}
}
关于物理按键长按和短按的说明可参考我另一篇文章单片机按键长按短按检测MS8006示例代码说明
五、总结
现在越来越多的手机设备也可以作为红外发射器,而且能够匹配更多的家用电器等支持红外遥控的设备,无线遥控用途很广泛。
关于MS8006信息请找孙工13866788906