本实验用STM32F4来实现
红外遥控不能隔墙、抗干扰强。
对流程不感兴趣可以直接看代码
NEC码的时序
大致是接收到引导码(9ms 低电平+ 4.5ms 高电平) + 地址码+反码(校验用)+数据+数据反码,这时已经接收到了完整的数据了,之后如果不松手,就会有9ms低电平+2.5ms的高电平+0.56ms低电平+97ms左右的高电平,如果还是按下的状态,同样会有这一周期的时序发生。以此判断按下几次
注意一点!这是发送方的时序,接收方的时序是反向的,也就是他发高电平而我们收到的是低电平!而且我们只要测高电平就行了。
一个码是0还是1,是通过一个载波电平+不同的信号电平来表达的,给上图
例如位0是0.56msLow+0.56msHigh组成的,供1.125ms,位1是0.56ms L+1.68ms H 这就表达一个位的信号,切记!接收到的是反向的,接下来通过测量高电平脉宽就可以获得信号了。
这里STM32使用输入捕获测量脉宽(单定时器输入模式),还有个PWM双输入模式。
先通过流程图(参考正点原子)更形象地表达给大家表达一下程序实现吧!
这里大致说一下,定义一个状态字节来存取当前解码的状态。通过捕获CC中断和UP溢出中断进行操作状态位,其实CC中断才是用来接收数据的,而UP中断用来判断重复按下时间是否超时,已经数据是否接收完成。
进入CC中断后,判断脉宽时间,是0?是1?是引导码还是重复码
捕获中断
//<读取高电平时间来获取数据>
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留
//[4]:标记上升沿是否已经被捕获
//[3:0]:溢出计时器
u8 RmtSta = 0;//遥控状态位
u16 CntVal; //计数器的值 0-10000
u32 RmtDat = 0; //接收到的数据 地址码-地址反码-数据码-数码反码
u8 RmtCnt = 0; //重复按下次数
外部变量定义如上
#define REMOTE_IC_TIMx TIM1
#define RMT_IN PAin(8)
//捕获中断--检测按键值
void TIMx_CC_IRQHandler(void)
{
if(TIM_GetITStatus(REMOTE_IC_TIMx,TIM_IT_CC1))
{
if(RMT_IN) //此时处于高电平
{
REMOTE_IC_TIMx->CCER |= (1<<1); //等下降沿
REMOTE_IC_TIMx->CNT = 0; //计数器清0开始计数--1us一次
RmtSta |= (1<<4); //上升沿已捕获,开始捕获下降沿
}
else //此时处于低电平
{
CntVal = REMOTE_IC_TIMx->CCR1; //读取捕获寄存器-获取高电平时间
REMOTE_IC_TIMx->CCER &= (~(1<<1)); //转上升沿
if(RmtSta & (1<<4)) //已经有过上升沿
{
if(RmtSta & (1<<7)) //引导码接收到
{
//三种码的判断
if((CntVal > 300)&&(CntVal < 800)) //560us阈值 --0
{
RmtDat <<= 1;
}
else if((CntVal > 1400)&&(CntVal < 1800))//1680us阈值 --1
{
RmtDat <<= 1;
RmtDat++;
}
else if((CntVal > 2200)&&(CntVal < 2600))//2500us阈值 --重复按下
{
RmtCnt++;
RmtSta &= 0xF0;//清空溢出次数,等待下次重复按下
}
}
else if((CntVal > 4200) && (CntVal < 4700)) //4.5ms的引导码,开始接收数据
{
RmtSta |= (1<<7);
RmtCnt = 0;//重复次数清空
}
}
RmtSta &= (~(1<<4));//清空上升沿标志
}
}
TIM_ClearITPendingBit(REMOTE_IC_TIMx,TIM_IT_CC1);
}
只有在引导码收到才能判断是0是1还是重复码。由于捕获中断是单输入模式,无法判断是上升沿还是下降沿。但是如果当前引脚为高电平,那么刚才接收到的肯定是上升沿,以此判断。在接收引导码后,判断计数器阈值(计数器+1 = 1us),典型的1.68是1,0.56是0,2.5是重复按下。数据保存于RmtDat(32bit)中.
溢出中断
//更新中断--检测重复按下
void TIMx_UP_IRQHandler(void)
{
if(TIM_GetITStatus(REMOTE_IC_TIMx,TIM_IT_Update))
{
if(RmtSta & (1<<7)) //在收到引导码的前提下,检测是否重复按下
{
//超过10ms后,可能进入重复按下事件,停止检验按键时间,
//等待下次上升沿计算是引导码还是重复码
RmtSta &= (~(1<<4));
if((RmtSta & 0x0F )== 0x00) RmtSta |= (1<<6);//如果前面从未溢出,说明得到的是数据码
if((RmtSta & 0x0F) < 14) //小于14*10ms,说明可能还会有重复码到来
{
RmtSta++;
}
else //超时了,不可能有重复码到来
{
RmtSta &= 0b01110000;//清除引导位和溢出值,重新接收
}
}
}
TIM_ClearITPendingBit(REMOTE_IC_TIMx,TIM_IT_Update);
}
溢出一次是10ms,所以如果红外遥控按下,是不可能溢出的!因为按下就会有脉冲,有脉冲就会清0,最大计数到4.5ms<10ms,所以如果到了溢出中断,那就是接收完成或者压根没按下或者是进入重复模式了。
为节省篇幅,上面每一句都有很详细的解释。
校验数据进行输出
//扫描遥控器按下
//return 键值
u8 Remote_Scan(void)
{
u8 sta = 0;
u8 tmp1 = 0,tmp2 = 0;
if(RmtSta & (1<<6)) //接收到了完整按键值,开始校验数据
{
tmp1 = RmtDat >> 24; //地址码
tmp2 = RmtDat >> 16; //地址反码
if((tmp1 == (u8)~tmp2) && (tmp1 == REMOTE_ID))
{
tmp1 = RmtDat >> 8;
tmp2 = RmtDat;
if(tmp1 == (u8)~tmp2) sta = tmp1; //返回键值
}
if((sta == 0) || ((RmtSta & 0x80) == 0)) RmtSta &= ~(1<<6),RmtCnt=0;//此时已经松开了
}
return sta;
}
校验很简单,就是地址码=地址反码取反,数据码=数据码取反。特别记得在取反符号前定要加强制类型转化。
对于地址码,几乎每个遥控器都是固定的,而键值码(数据码)是不固定的,从0~255.
部分其他代码
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_Period = (10000-1);
TIM_TimeBaseInitStructure.TIM_Prescaler = (168-1); //1us
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(REMOTE_IC_TIMx,&TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
//输入捕获
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 3;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
这部分代码也分享一下。GPIO就是复用成定时器x。然后使能CC1(注意看清楚引脚)和UP中断。中断向量就是CC>UP的优先级
我的测试代码:
p1:
RemoteData = Remote_Scan();
if(RemoteData)
{
if(last == RemoteData) goto p1;
printf("\r\nRemoteData:%d",RemoteData);
printf("\r\nCNT:%d",RmtCnt);
last = RemoteData;
}
红外解码当初看了下原理感觉没啥,做起来也是够折腾的- -!!但是也不难。