基本原理:-toc" style="margin-left:40px;">一、基本原理:一、基本原理:一、基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理
二、NEC编码
三、时序图
四、解码
五、程序实现
一、基本原理:一、基本原理:一、基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理:基本原理
红外光波由红外发光二级管发射;
接收器由红外接收二极管、三极管或硅光电池组成,将发射器发射的红外接收转换为相应的电信号。
我们如何确定我们按了什么按钮?NEC协议可以解决按下的键。
二、NEC编码
采用红外遥控器NEC编码规则:
- NEC 载波频率为 38Khz
- 引导码:9ms 高电平 4.5ms 低电平
- 0码 :0.56 ms 高电平 0.56 ms 低电平
- 1 码 : 0.56ms 高电平 1.68 ms 低电平
- 结束码 :0.56ms 高电平
- 数据帧格式:引导码 识别码 识别码反码 键值 键值反码 结束码
- 重复帧格式:9ms 高电平 2.25ms低电平 结束位 总共110个结束码ms左右
- 整个完整数据格式:引导码 识别码 识别码反码 键值 键值反码 结束码 空闲时间 9ms 高电平 2.25ms低电平 结束位 结束码 重复码循环~~~
- 高位在前,也就是说,首先收到的是高位数据
三、时序图
根据 NEC 该帧数据的状态实际上可以从编码格式中看出:
四、遥控解码
然后解码只需获得每个波形的高低电平时长,就可以知道收到的数据是1还是0,最后读取整个数据。解码思路分为四个部分:
1.通常使用定时器获得每个波形高低电源。初始化时,定时器通道配置为上升边缘捕获。当捕获到上升边缘时,计数器等晰0,然后将捕获极性改为下降沿捕获。当第二次捕获中断时,捕获到下降边缘并记录此时间,即整个高电平时间,解码逻辑只捕获高电平时间。
(如果想读取低电平时间,可以跳过第一次上升捕获后的动作,在下降捕获时计数器等0,下一次捕获上升时读取时间。
2.第二部分就根据中断服务函数里面,触发中断时捕获的时间,并根据NEC编码规则判断时长是0还是1。.68 ms 高电平为1,0.56 ms 高电平为0,2.25ms重复码为高电平。
3.如何判断数据收集?是否有重复码,或者重复码何时收集?可在初始化定时设置定时器10ms根据溢出中断一次NEC协议可以知道,由于会计数器在每次上升和捕获时都会等待清算0,因此整个数据不会溢出一次中断,因此当溢出中断一次时,数据将被收集。
4.那么,如果你想判断重复码什么时候完成呢?同样的想法,因为上面提到的数据码和重复码之间有一段空闲时间, 10ms溢出中断一次,测试空闲时间应为30ms左右,只需设置溢出中断标志,每次触发加1,上升沿捕获时清除0,如果溢出中断标志>3,就代表40ms内部没收过高的电平,说明数据已经接收,没有重复码。根据NEC重复码协议,我们10ms溢出中断一次,判断当收到重复码时,如果溢出中断标志>9点,代表重复码收集。
五、程序实现
程序已经调试,移植只需要更改文件IO口就够了。头文件remote.h
#ifndef _REMOTE_H #define _REMOTE_H #include "stm32f10x.h" ///程序使用TIM4_CH4,移植时只需要在这里更改IO口就可以了 #define IR_TIME TIM4 ///时钟 #define TIM_Channel_x TIM_Channel_4 //配置输入捕获通道,根据具体的 GPIO 来配置 #define RCC_APBxPeriphClockCmd RCC_APB1PeriphClockCmd //TIMx时钟使能函数,TIM在APBx? #define TIMx_LCK RCC_APB1Periph_TIM4 //TIMx的时钟 #define IR_GPIOx_LCK RCC_APB2Periph_GPIOB ////红外输入IO口的时钟 #define IR_GPIOx GPIOB ////红外输入IO口 #define IR_IO GPIO_Pin_9 // 中断相关宏定义 #define TIM_IT_CCx TIM_IT_CC4 //中断捕获 #define TIMx_IRQn TIM4_IRQn //设触发中断源,中断源不同,IRQn_Type #define TIMx_IRQHandler TIM4_IRQHandler &bsp; //TIMx的触发中断服务函数
// 捕获信号极性函数宏定义
#define TIM_GetCapturex TIM_GetCapture4 //对应寄存器CRRx,获取寄存器的值,该寄存器用来存储捕获发生时, TIMx_CNT的值
#define TIM_OCxPolarityConfig TIM_OC4PolarityConfig //OCxPolarity即TIMX通道X的极性,上升下降中心对齐
#define NVIC_PriorityGroup_x NVIC_PriorityGroup_2 //设置中断组2,( 2:2 )
#define IR_ID 0x80 //遥控器ID识别码
typedef struct {
uint8_t FinishFlag; // 捕获高电平结束标志位
uint8_t StartFlag; // 捕获开始标志位
uint16_t CrrValue; // 捕获寄存器的值
uint16_t Period; // 自动重装载寄存器更新标志,产生了多少次中断
uint8_t DataFlag; // 收到引导码标志
uint16_t Data; // 接收到的数据
} TIM_ValueTypeDef;
void Remote_Init(void); //红外初始化
void TIMx_IRQHandler(void); //定时器TIMx中断服务函数
void Remote_Scan( u8 str ); //遥控按键处理
#endif /* _REMOTE_H */
C文件remot.c,可以把中断服务处理函数中的接收数据处理那部分拿出来单独做一个函数,只不过这样就不能识别码—反码,数据码—反码,收到就判断一次对不对了,要全部收取然后>>移位判断了。最好中断服务函数还是短些好点。
#include "remote.h"
void Remote_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef Tim_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd( IR_GPIOx_LCK, ENABLE ); //使能端口x时钟
RCC_APBxPeriphClockCmd( TIMx_LCK, ENABLE ); //使能TIMx时钟
GPIO_InitStructure.GPIO_Pin = IR_IO; //输入IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( IR_GPIOx, &GPIO_InitStructure ); //初始化I0口
/*--------------------时基结构体初始化-----------------------------*/
Tim_TimeBaseStructure.TIM_Period = 10000; //设定计数器自动重装值 最大10ms溢出
Tim_TimeBaseStructure.TIM_Prescaler = 72; //预分频器,1M的计数频率,1us加1.
Tim_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分频系数
Tim_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
//Tim_TimeBaseStructure.TIM_RepetitionCounter = 0; //重复计数设置,高级计时器才有
TIM_TimeBaseInit( IR_TIME, &Tim_TimeBaseStructure ); //初始化 TIMx定时器
/*--------------------输入捕获结构体初始化--------------------------*/
TIM_ICStructure.TIM_Channel = TIM_Channel_x; //配置输入捕获的通道,根据具体的 GPIO 来配置
TIM_ICStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //捕获输入通道选择
TIM_ICStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //不分频,每个变化沿都捕获
TIM_ICStructure.TIM_ICFilter = 0; //被捕获的信号的滤波系数
TIM_ICInit( IR_TIME, &TIM_ICStructure ); //初始化 定时器输入捕获
/*--------------------中断优先级配置--------------------------------*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_x ); //设置中断组为 0
NVIC_InitStructure.NVIC_IRQChannel = TIMx_IRQn; //设中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级1级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能IRQ通道
NVIC_Init( &NVIC_InitStructure ); //初始化NVIC寄存器
TIM_ClearFlag( IR_TIME, TIM_IT_Update|TIM_IT_CCx ); // 清除更新和捕获中断标志位
TIM_Cmd( IR_TIME, ENABLE ); //使能定时器x
TIM_ITConfig( IR_TIME, TIM_IT_Update|TIM_IT_CCx, ENABLE ); // 开启更新和捕获中断
}
u8 IRdatas = 0; //接收到数据次数
u8 IRval = 0; //接收到的按键值
u8 RmtCnt = 0; //按键重复按下的次数
u8 Remote = 0; //遥控按键处理值,全局变量
TIM_ValueTypeDef TIM_Values; //初始化结构体
void TIMx_IRQHandler(void) //定时器x中断服务程序
{
if ( TIM_GetITStatus( IR_TIME, TIM_IT_Update ) != RESET )
{
if ( TIM_Values.DataFlag == 1 ) //是否接收到了引导码
{
if( TIM_Values.Period > 3 ) //如果4次溢出中断(40ms)没收到数据,则要么开始收重复码,要么数据收完了
{
if( RmtCnt == 0 || TIM_Values.Period > 9 ) //如果收到了领导码,且在4次溢出中间没有收到重复码,则判断收完数据,清0标志退出
TIM_Values.DataFlag = 0; //或者收到重复码,且中间90ms没再触发中断,则判断收完数据,清0退出
}
TIM_Values.Period++;
}
}
if ( TIM_GetITStatus( IR_TIME, TIM_IT_CCx ) != RESET ) //发生了上升沿或者下降沿事件?
{
if ( TIM_Values.StartFlag == 0 ) //第一次上升沿捕获
{
TIM_SetCounter( IR_TIME, 0 ); //清0计数器
TIM_OCxPolarityConfig( IR_TIME, TIM_ICPolarity_Falling ); //设置为下降沿捕获
TIM_Values.CrrValue = 0; //捕获值清0
TIM_Values.StartFlag = 1; //开始下降沿捕获
TIM_Values.Period = 0; //自动重装载寄存器清0
}
else //第二次捕获,下降沿捕获
{
TIM_Values.CrrValue = TIM_GetCapturex( IR_TIME ); //获取通道4 捕获寄存器的值
TIM_OCxPolarityConfig( IR_TIME, TIM_ICPolarity_Rising ); //设置为上升沿捕获
TIM_Values.StartFlag = 0; //开始标志复0,重新判断捕获上升沿
TIM_Values.FinishFlag = 1; //标记完成1次捕获流程
if ( TIM_Values.FinishFlag == 1 ) //判断是否完成一次捕获流程
{
if ( TIM_Values.DataFlag == 1 ) //是否接收到了引导码
{
if ( TIM_Values.CrrValue > 300 && TIM_Values.CrrValue < 800 ) //560为标准值,560us
{
TIM_Values.Data <<= 1; //左移一位
TIM_Values.Data |= 0; //接收到0
IRdatas++; //接收到的数据次数加1.
}
else if ( TIM_Values.CrrValue > 1400 && TIM_Values.CrrValue < 1800 ) //1680为标准值,1680us
{
TIM_Values.Data <<= 1; //左移一位
TIM_Values.Data |= 1; //接收到1
IRdatas++; //接收到的数据次数加1
}
/*这是 NEC 码规定的110ms连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平
+97.94ms 高电平组成),如果在一帧数据发送完毕之后,按键仍然没有放开,则发射重复码,
即连发码,可以通过统计连发码的次数来标记按键按下的长短/次数。结束码 :0.56ms 高电平*/
else if ( TIM_Values.CrrValue > 2100 && TIM_Values.CrrValue < 2500 ) //得到按键键值增加的信息 2250为标准值2.25ms
{
if( RmtCnt > 3 ) //防止松开慢而误收到重复码,而抛弃前3次收到的重复码
IRdatas++; //接收到的数据次数加1
RmtCnt++; //按键次数增加1次
}
}
else if ( TIM_Values.CrrValue > 4200 && TIM_Values.CrrValue < 4700 ) //4500为标准值4.5ms
{
TIM_Values.DataFlag = 1; //成功接收到了引导码,数据开始标志为1
IRdatas = 0; //等于1时收到引导码
RmtCnt = 0; //清除按键次数计数器
}
}
/*---------------------------------------接收数据处理---------------------------------------*/
switch ( IRdatas )
{
// case 8: //接收完特征码
// if( TIM_Values.Data != IR_ID ) //如果接收到的特征码和使用遥控器不相同,则数据全部清0重来
// {
// IRdatas = 0; //接收数据次数清0
// TIM_Values.DataFlag = 0; //开始接收数据标志复位为0
// }
// TIM_Values.Data = 0; //接收到的数据清0,只需要数据码
// break;
case 16: //接收完特征反码
//if ( (u8)~TIM_Values.Data != IR_ID ) //如果知道遥控ID,想只能特定遥控器控制,则用这句与上面一句
if ( (u8)(TIM_Values.Data>>8) != (u8)~(TIM_Values.Data&0xff) ) //如果特征码和收到的特征反码,比较发现不同,则数据全部清0重来
{
IRdatas = 0; //接收数据次数清0
TIM_Values.DataFlag = 0; //开始接收数据标志复位为0
}
TIM_Values.Data = 0; //接收到的数据清0
break;
case 24: //接收完数据码
IRval = TIM_Values.Data; //把数据码存到IRval
TIM_Values.Data = 0; //接收到的数据清0,准备接收数据反码
break;
case 32: //接收完数据反码
if ( IRval != (u8)~TIM_Values.Data ) //如果数据码和接收到的数据反码不同,则数据码清0,重新开始
{
IRdatas = 0; //接收数据次数清0
IRval = 0;
}
TIM_Values.Data = 0; //接收到的数据清0,准备接收下一次中断数据
Remote = IRval; //把收到的按键值赋值给全局变量 Remote
IRdatas = 33; //赋值为33防止在结束码时再进入中断,触发32的判断,导致数据清0
break;
case 34: //重复码,如果想x个重复码算收到1次重复按键,就把34+x
Remote = IRval;
IRdatas = 33; //重新赋值回33,防止重复按键次数太多,导致数值溢出,且不需要写后面的switch选择
break;
}
}
}
TIM_ClearITPendingBit( IR_TIME, TIM_IT_Update | TIM_IT_CCx ); //清除中断标志位
}
//移植时根据不同的遥控按键值,更改case
void Remote_Scan( u8 str ) //遥控按键处理
{
Remote = 0;
switch( str )
{
case 0XA2:
//
break;
case 0X68:
//
break;
case 0X30:
//LCD_ShowString( 25, 55,"I MISS YOU" ); // 显示字符串
break;
case 0X18:
//LCD_ShowString( 30, 70,"I SEE YOU" ); // 显示字符串
break;
case 0x7A:
//LCD_Image( 0, 0, 240, 135, imageLoge );
break;
case 0x10:
//LCD_Image( 100, 140, 60, 58, imageLoge2 );
break;
case 0x38:
//LCD_ShowNum( 100, 55, time, sizeof(time)-1 );
//time++;
break;
case 0x5A:
//LED_TOGGLE( 2 );
break;
case 0x42:
//LED_GPIO_Confing();
//time = 0;
//LCD_ShowNum( 100, 55, time, sizeof(time)-1 );
break;
}
}
main主函数
#include "stm32f10x.h"
#include "remote.h"
extern u8 Remote;
int main(void)
{
Remote_Init(); //初始化红外接收
while(1)
{
if( Remote != 0 ) //如果变量里面有值则代表收到遥控按键了,如果有遥控按键值为0就需要改Remote的初始值和这里的判断了
{
Remote_Scan( Remote ); //遥控按键处理
}
}
}
PS:同样都是一点点学习,该程序逻辑不知道为什么使用高级定时器就不行了,初始化配置都是改正确的,该问题还没解决。还有后续应该也会增加详细的程序实现逻辑导图,方便后续学习的人可以按程序实现逻辑导图自己写出来,而不是看着代码直接CV或者看着写。你我也要共同努力向前走吖。2022.4.23