1.NEC标准
红外遥控遵循NEC标准,且具有以下三种基本状态:
- 空闲状态:红外LED不亮,接收头输出高电平。
- 发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平。
- 发送高电平:红外LED不亮,接收头输出高电平。
接收流程可以通过如下状态机实现:
NEC编码说明如下图所示:
2.代码实现:外部中断的初始化
首先,由于红外遥控对灵敏度要求极高,我们需要使用外部中断完成对红外信号的捕获,下面是外部中断的中断结构图:
红外遥控需要对应的中断触发方式为下降沿触发,IT0=1。
IE0为中断产生的标志位,在初始化时清零,IE0=0。
EX0表示是否开启中断,EX0=1。
EA表示所有中断的总开关,EA=1。
PX0表示中断优先级,0为高优先级,1为低优先级,PX0=1。
完整的初始化代码如下。
1 | void Int0_Init(){ |
3.代码实现:外部中断的中断函数
外部中断的中断号为0,因此定义如下中断函数:
1 | void Int0_Routine() interrupt 0 |
我们需要定义一个unsigned char型数组:IR_Data[4],用于存放地址码、地址码反码、命令码、命令反码。总位宽为32位:
1 | unsigned char IR_Data[4]; |
在后续的处理中,为了方便,我们需要一个变量IR_pData来定位数据当前应该存放的位置。IR_pData的取值范围为0~31,
当IR_pData∈[0,7]时, 对应IR_Data[0]
当IR_pData∈[8,15]时, 对应IR_Data[1]
当IR_pData∈[16,23]时, 对应IR_Data[2]
当IR_pData∈[24,31]时, 对应IR_Data[3]
即:IR_Data[IR_pData/8]可以实现完美对应:
1 | unsigned char IR_pData; |
然后定义IR_Address与IR_Command用于存放当前32位数据中的地址码与命令字,我们可以通过IR_GetAddress()与IR_GetCommand()函数将IR_Address与IR_Command返回给主函数,这样主函数就能根据不同的地址码和命令执行不同的代码了:
1 | unsigned char IR_Address; |
最后,还需要定义两个标志位,用于判断一整个32位数据是否接收完成与当前帧是否为Repeat。我们可以通过IR_GetRepeatFlag与IR_GetDataFlag将RepeatFlag与DataFlag返回给主函数,这样主函数就能根据不同的状态执行不同的代码了。
1 | unsigned char IR_DataFlag; |
接下来就要正式写信号处理的过程,处理的过程可以参照第一节中的状态机:
在解码过程与接收Start与Repeat信号的过程中,我们通过定时器记录每一次执行的时间(也就是两个下降沿之间的时间间隔),通过时间间隔的长短就能分辨出当前信号表示0还是1,也就实现了解码的过程。
3.1空闲状态(IR_State=0)
空闲状态表示当前不处于信号解码或寻找信号的状态,此时需要将定时器的初始计数值清零,然后打开定时器:
1 | if(IR_State==0) //空闲状态 |
3.2寻找Start或Repeat(IR_State=1)
首先读取定时器当前的计数值,然后将计数值清零。接下来根据Start与Repeat下降沿间隔的不同来区分不同的信号,如果是Start则转到解码状态,如果是Repeat则关闭定时器,转回空闲状态并将RepeatFlag置1。
如果都不是表示接收到了错误信号,重新寻找Start或Repeat。
1 | else if(IR_State==1) //寻找Start或Repeat |
3.3解码状态(IR_State=2)
首先依然是读取定时器当前的计数值,然后将计数值清零。接下来根据0与1的下降沿间隔的不同来区分不同的信号,并将当前数据写入IR_Data[]中。
先给出代码:
1 | else if(IR_State==2){ //开始解码 |
下面讲解一下接收信号0与接收信号1后的两句赋值语句,先以接收信号0为例:
1 | IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); |
IR_Data[IR_pData/8]:
表示当前位数对应数组中的第几个。
例如:IR_pData=3时,对应IR_Data[0],IR_pData=8时,对应IR_Data[1]
&=~:
0&1=0,1&1=1;与1的操作可以保持原数据不变。
0&0=0,1&0=0;与0的操作可以实现”写0”(清零)的效果。
例如:IR_Data[IR_pData/8]&=~(0x01),先将0x01取反,得0b 1111 1110,可以实现将0写入最低位而不改变其他位
IR_pData%8:
IR_pData%8∈[0,7],可以表示当前char的第几位。
将上述组合起来:IR_Data[IR_pData/8]&=~(0x01<<IR_pData%8),就可以实现对32位进行循环赋值了。
同理,接收信号1的代码为:
1 | IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); |
|=:
0|1=1,1|1=1;或1的操作可以实现”写1”的效果。
0|0=0,1|0=1;或0的操作可以保持原数据不变。
例如:IR_Data[IR_pData/8]|=(0x01),0x01=0b 0000 0001,可以实现将1写入最低位而不改变其他位。
4.代码实现:红外遥控
红外遥控模块需要调用Timer0与Int0,通过读取外部中断触发后对IR_Address与IR_Command的修改,完成与主函数的对接功能。
需要编写的函数如下:
1 | void IR_Init(); |
这些代码都相对简单,因此直接给出:
1 | void IR_Init(){ |
需要注意的是,由于外部中断函数中需要用到IR_Address、IR_Command等变量,因此外部中断函数也需要写在红外遥控模块中。
5.代码实现:主函数
在主函数中,我们可以通过判断IR_GetDataFlag与IR_GetRepeatFlag函数的返回值是否为1,确定当前是否接收到了一份完整的32位数据或是一份重复指令。然后根据返回获得的Address与Command,执行不同的指令:
1 | while(1){ |
普中开发板对应的红外遥控器各按键对应的命令码如下(16进制):