STM32:红外遥控接收模块

目录

一、基本原理:

二、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低电平 + 结束位 + 结束码,总共110ms左右
  • 整个完整数据格式:引导码 + 识别码 + 识别码反码 + 键值 + 键值反码 + 结束码+一段空闲时间+9ms 高电平 + 2.25ms低电平 + 结束位 + 结束码+重复码循环~~~
  • 高位在前,即首先收到的是高位的数据

注:本文使用的接收头,电平性与协议相反。所以,当捕获到一个 9ms 低电平 + 4.5ms 高电平时,即收到一个引导码。数据码到重复码中间的空闲时间经过测试是有的,之前直接弄的时候以为没有,找的资料也没怎么提到过,测试空闲时长应该是30ms左右,具体多少我也不清楚。

三、时序图

        根据 NEC 的编码格式,可以实际看出这一帧数据的状态:

四、遥控解码

        那么解码就只需要获取每个波形高低电平时长,就可以知道收到的数据是1还是0,最后读取整个数据即可。解码思路分为四个部分:

        1.获取每个波形高低电平时长利用定时器去获取,初始化时,将定时器通道配置为上升沿捕获,当捕获到一个上升沿时,计数器等清0,接着将捕获极性改为下降沿捕获。当第二次捕获中断时,捕获到下降沿,并记录此时时间,这个时间就是整个高电平的时间,该解码逻辑为只捕获高电平时间。

        (如果想读取低电平时间,可以跳过第一次上升沿捕获后的动作,在下降沿捕获时计数器等清0,下一次捕获上升沿时读取时间。)


        2.第二部分就根据中断服务函数里面,触发中断时捕获的时间,并根据NEC编码规则判断该次时长代表的是0还是1。1.68 ms 高电平为1,0.56 ms 高电平为0,2.25ms高电平为重复码。


        3.如何判断数据收完了?重复码有没有,或者说重复码又什么时候收完?可以在初始化定时,设置定时器10ms溢出中断一次,根据NEC协议可以知道,因为每次上升沿捕获时都会计数器等清0,那么收完整个数据都不会溢出中断一次,所以当溢出中断一次就代表数据收完了。


        4.那么如果想判断重复码又什么时候收完?同样的思路,由于上面说的数据码和重复码中间有段空闲,而 10ms溢出中断一次,测试空闲时长应该是30ms左右,那么只需要设置溢出中断标志,每次触发都加1,在上升沿捕获时清0,那么如果溢出中断标志>3,就代表40ms内没收到过高电平了,那么就说明数据接收完了,且没有重复码的产生。根据NEC重复码的协议,我们10ms溢出中断一次,为了接收全部的重复码则判断当收到重复码时,如果溢出中断标志>9时,就代表重复码收完了。


注:本文使用的接收头,电平极性与协议相反。即NEC协议规定是0.56ms 高电平 + 1.68 ms 低电平为1,该接收头收到的是0.56ms 低电平 + 1.68 ms 高电平。本文使用的是只读取高电平时间来判断数据,还有种思路是直接读取整个的高低电平时间来判断,代码实现思路都是一样的。

五、程序实现

        程序都调试过了,移植只需要改头文件的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                      //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-1);                           //设定计数器自动重装值 最大10ms溢出  
    Tim_TimeBaseStructure.TIM_Prescaler = (72-1);                           //预分频器,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

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值