STM8S 红外解码+低功耗处理

NEC协议

红外遥控的编码目前广泛使用的是:NEC ProtocolPWM(脉冲宽度调制)和 PhilipsRC-5 ProtocolPPM(脉冲位置调制),下面是 NEC 协议的特征:

  1. 8 位地址和 8 位指令长度(控制码);
  2. 地址和命令 2 次传输(确保可靠性)
  3. PWM 脉冲位置调制,以发射红外载波的占空比代表 “0” 和 “1” ;
  4. 载波频率为 38Khz
  5. 位时间为 1.125ms2.25ms

NEC码的位定义:

  • 一个脉冲对应 560us 的连续载波
  • 一个逻辑 1 传输需要 2.25ms560us脉冲+1680us 低电平=2240us)
  • 一个逻辑 0 的传输需要 1.125ms560us 脉冲+560us 低电平=1120us)

红外接收头端的信号为:

  • 收到脉冲的时候为低电平,在没有脉冲的时候为高电平(即与发出的NEC码反向)
  • 逻辑 1 ( 560us 低+1680us 高)
  • 逻辑 0 ( 560us 低+560us 高)
    NEC码位定义

NEC 遥控指令的数据格式为:同步码头地址码地址反码控制码控制反码。从红外接收头端的角度来看:

  1. 同步码:由一个 9ms 的低电平和一个 4.5ms 的高电平组成
  2. 地址码地址反码:8位数据,遥控器的地址码要与红外接收头端的地址码要对应?
  3. 控制码控制反码:8位数据,键值相关
  4. 按照低位在前高位在后的顺序发送
  5. 采用反码是为了增加传输的可靠性(可用于校验)

以正点原子的STM32开发板为例,配带的遥控器的地址码为0x00:
红外波形

  • 地址码为0
  • 控制码为0x15(从接收端来看)
  • 连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平+97.94ms 高电平组成=110ms),如果在一帧数据发送完毕之后,按键仍然没有放开,则发射重复码,即连发码,可以通过统计连发码的次数来标记按键按下的长短/次数。

项目实践中的NEC码

一般给出的码值表是从发送端的角度来看的,而客户码实际上就是地址码+地址反码这一项(不一定构成反码),也就是说如果从接收端接收到的波形来看,83F4对应的是C12F(先发低位,再发高位);OK键15对应A8
遥控器码值

对应原理图

对应的原理图如下:
在这里插入图片描述

软件设计思路一

仿正点原子的做法,该方式不适合要做低功耗模式的需求:

  • 开启定时器对应通道输入捕获功能,默认上升沿捕获。定时器的计数频率为1MHz,自动装载值为10000,也就是溢出时间为10ms
  • 开启定时器 输入捕获中断溢出/更新中断,当捕获到上升沿产生捕获中断,当定时器计数溢出,产生更新中断 。
  • 当捕获到上升沿的时候,设置捕获极性为下降沿捕获(为下次捕获下降沿做准备),然后设置定时器计数值为0(清空定时器),同时设置变量RmtSta的为4值为1,标记已经捕获到上升沿
  • 当捕获到下降沿的时候,读取定时器的值赋给变量Dval,然后设置捕获极性为上升沿捕获(为下次捕获上升沿做准备),同时对变量RmtSta的位4进行判断:如果RmtSta位4为1,说明之前已经捕获到过上升沿,那么对Dval进行判断,300-800(560)之间,说明接收到的是数据0;1400-1800(1680)之间说明接收到的数据为1;2200-2600(2500),说明是连发码;4200-4700(4500)说明为同步码;分析后甚至相应的标志位
  • 如果是定时器发生溢出中断,那么分析,如果之前接收到了同步码,并且是第一次溢出,标记完成一次按键信息采集(有两种情况,情况1:接收完码后一直处于高电平后产生溢出;情况2:处于连发码中97.94ms高电平这段时间产生溢出,正点原子例程用是否>130ms来判断是处于两种情况中的哪一种)
  • 接收码值的时候采用的是左移的方式,跟接收端看到的波形数值是一致的,所以是遥控器码值的反码

定时器捕获 + 外部中断(低功耗) 的方式时,会产生问题,猜测是在软件优先级一样的情况下,硬件优先级(根据中断向量表判断)高的外部中断常常打断了定时器的捕获,所以造成了有时解不了码的问题。

remote.h代码

#ifndef _REMOTE_H_
#define _REMOTE_H_

#include "stm8s.h"

#define REMOTE_IDH  0x0C
#define REMOTE_IDL  0xF3
extern u8 RmtCnt;//按键按下的次数
void Clk_Init(void);
void Remote_Init(void);
u8 Remote_Scan(void);

#endif

remote.c代码

#include "remote.h"

//选用16MHz的HSI作为主时钟源
void Clk_Init(void){

    CLK_DeInit();//设置为默认值
    CLK_HSICmd(ENABLE);//启用HSI
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);//HSI分频
    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);//CPU分频
}

//初始化PD3为浮空输入
void IR_GPIO_Init(void){

    GPIO_DeInit(GPIOD);//恢复指定端口
    GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_IN_FL_NO_IT);//设置PD3为浮空输入无中断
    
}

void TIM2_Init(void){
    
    TIM2_DeInit();
    TIM2_TimeBaseInit(TIM2_PRESCALER_16, 10000); //最大10ms溢出,1us加1,只有向上计数
    TIM2_ARRPreloadConfig(ENABLE);
    //TIM2_ClearFlag(TIM2_FLAG_UPDATE);//清除更新标志
    TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE);//允许更新/溢出中断
    //Send_Str("TIM2_Cmd before \n");
    TIM2_Cmd(ENABLE);//开启定时器
    //Send_Str("TIM2_Cmd after \n");
    //TIM2_UpdateRequestConfig(TIM2_UPDATESOURCE_REGULAR);//中断源选择为只有溢出才能触发
    //TIM2_GenerateEvent(TIM2_EVENTSOURCE_UPDATE);//产生更新事件,不触发中断(此处即更新了预分频器)
    
    enableInterrupts(); //开启中断
    
    //CH2、上升沿捕获、IC2映射在TI2FP2上、捕获预分频为1、无滤波器且采样率=主时钟频率
    //TIM2_PWMIConfig(TIM2_CHANNEL_2, TIM2_ICPOLARITY_RISING, TIM2_ICSELECTION_DIRECTTI, TIM2_ICPSC_DIV1, 0x00);
    //CH2、上升沿捕获、IC2映射在TI2FP2上、捕获预分频为1、无滤波器且采样率=主时钟频率
    TIM2_ICInit(TIM2_CHANNEL_2, TIM2_ICPOLARITY_RISING, TIM2_ICSELECTION_DIRECTTI, TIM2_ICPSC_DIV1, 0x00);
    TIM2_ITConfig(TIM2_IT_CC2, ENABLE);//捕捉比较2中断使能
    TIM2_ClearITPendingBit(TIM2_IT_CC1);
    TIM2_ClearFlag(TIM2_FLAG_CC1);
    //TIM2_CCxCmd(TIM2_CHANNEL_2, ENABLE);//输入捕获/比较输出使能
    
}

//TIM2为输入捕获
void Remote_Init(){
  
    IR_GPIO_Init();
    TIM2_Init();
    //printf("Remote Init \n");
}


//RmtSta来标识遥控器接收状态
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留	
//[4]:标记上升沿是否已经被捕获								   
//[3:0]:溢出计时器
u8 	RmtSta=0;	  	  
u16 Dval;		//下降沿时计数器的值
u32 RmtRec=0;	//红外接收到的数据	   		    
u8  RmtCnt=0;	//按键按下的次数	

/**
  * @brief Timer2 更新/上溢出/刹车 中断程序.
  * @param  None
  * @retval None
  */
INTERRUPT_HANDLER(TIM2_UPD_OVF_BRK_IRQHandler, 13){
  

    if(RmtSta&0x80){//RmtSta[7]是否置1,则上次有数据(引导码)被接收到了
    	
        RmtSta&=~0X10;//取消上升沿已经被捕获标记(将RmtSta[4]置0)
        if((RmtSta&0X0F)==0X00)//对RtmSta[3:0]进行判断,为0代表第一次溢出
            RmtSta|=1<<6;	//标记已经完成一次按键的键值信息采集(将RmtSta[6]位置1)
        if((RmtSta&0X0F)<14){//高电平持续时间<130ms,可能还处于连发码中的97.94ms高电平阶段
            RmtSta++;
        }else{//如果超过130ms,就表示遥控器上按键已经松开了
        
            RmtSta&=~(1<<7);					//清空引导标识
            RmtSta&=0XF0;						//清空计数器	
        }								 	   	
   }
   TIM2_ClearFlag(TIM2_FLAG_UPDATE);//清除更新标志
   TIM2_ClearITPendingBit(TIM2_IT_UPDATE);
   
}
 
 

/**
  * @brief Timer2 捕获/比较 中断程序.
  * @param  None
  * @retval None
  */
INTERRUPT_HANDLER(TIM2_CAP_COM_IRQHandler, 14){
    
    BitStatus bs;
    bs = GPIO_ReadInputPin(GPIOD, GPIO_PIN_3);//读PD3的输入电平状态
    if (bs){//上升沿捕获
        TIM2_OC2PolarityConfig(TIM2_OCPOLARITY_LOW);//改为下降沿捕获
        TIM2_SetCounter(0x0000);//清空定时器的值
        RmtSta|=0X10;//RmtSta[4]位置1 标记上升沿已经被捕获 
        
    }else{//下降沿捕获
        Dval = TIM2_GetCounter();//读取CCR2也可以清CC2IF捕获标志位
        TIM2_OC2PolarityConfig(TIM2_OCPOLARITY_HIGH);//改为上升沿捕获
        if(RmtSta&0x10){//完成一次高电平捕获(如果RmtSta[4]为1)
            
            if(RmtSta&0X80){//接收到了引导码(如果RmtSta[7]为1)
                
                if(Dval>300&&Dval<800){			//560为标准值,560us(NEC中的0位)
                
                    RmtRec<<=1;					//将RmtRec这个数左移一位.这里采用的是低位在前,高位在后的算法
                    RmtRec|=0;					//接收到0
                    
                }else if(Dval>1400&&Dval<1800){	//1680为标准值,1680us(NEC中的1位)
                
                    RmtRec<<=1;					//左移一位.
                    RmtRec|=1;					//接收到1
                    
                }else if(Dval>2200&&Dval<2600){	//2500为标准值2.5ms,得到按键键值增加的信息(连发码)
                
                    RmtCnt++; 					//按键次数增加1次
                    RmtSta&=0XF0;				//清空计时器
                    
                }
            }else if(Dval>4200&&Dval<4700){		//4500为标准值4.5ms(引导码)
                RmtSta|=1<<7;					//RmtSta[7]位置1,标记成功接收到了引导码
                RmtCnt=0;						//清除按键次数计数器
                
            }
       }
       RmtSta&=~(1<<4);//RmtSta[4]位置0,即清除上升沿捕获标志
       
    }
    TIM2_ClearITPendingBit(TIM2_IT_CC2);
    
}

//处理红外键盘
//返回值:
//0,没有任何按键按下
//其他,按下的按键键值.
u8 Remote_Scan(void){        
	u8 sta=0;       
    u8 t1,t2;  
   
	if(RmtSta&(1<<6))//得到一个按键的所有信息了(RmtSta[7]位是否为1)
	{ 
	    t1=RmtRec>>24;			//得到地址码(u32的RmtRec码右移24位,只保留地址码的8位)无符号
	    t2=(RmtRec>>16)&0xff;	//得到地址反码(右移16位后只保留低8位)
 	    if((t1==REMOTE_IDH)&&t2==REMOTE_IDL)//检验遥控识别码(ID)及地址 
	    { 
	        t1=RmtRec>>8;//得到控制码
	        t2=RmtRec; 	//得到控制反码
	        if(t1==(u8)~t2)sta=t1;//键值正确
		}   
		if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了
		{
		 	RmtSta&=~(1<<6);//清除接收到有效按键标识(RmtSta[7]位设为1)
			RmtCnt=0;		//清除按键次数计数器
		}
	}  
    return sta;
}

main.c代码

#include "stm8s.h"
#include "stdio.h"
#include "remote.h"
#include "uart.h"
#include "led.h"
#include "delay.h"
#include "gpio_init.h"

void main(void)
{
    
    u8 key;
    //u8 *str=0;
    Clk_Init();//主时钟初始化
    //UART_Init();//初始化串口设置 
    //LED_init();
    init_TIM1();
    Remote_Init();//IR解码涉及相关的初始化
    Default_GPIO_Init();
	//printf("all init \n");
    
    
    while(1){
        if(!GPIO_ReadInputPin(GPIOC, GPIO_PIN_6)){
            key=Remote_Scan();
            if(key&&RmtCnt==1){
                //printf("Number of key repeats:%d \n",RmtCnt);
                switch(key){
                    case 0:
                      //printf("str=ERROR \n");
                      break;
                    case 0x8A:
                      //printf("Press Power \n");
                      GPIO_WriteReverse(GPIOD, GPIO_PIN_5);
					  //LED_Reverse();
                      Delay100ms(20);//这里是delay.c中的函数
					  //LED_Reverse();
                      GPIO_WriteReverse(GPIOD, GPIO_PIN_5);
                      break;
                }    
            }

        }
        
    }
    
}

软件设计思路二

采用外部中断的方式来做,适合做低功耗模式,但没做对连发码处理。

  • 具体思路:将IR接收脚设置为下降沿触发,解码的整个过程都在中断服务函数里搞,高低电平的持续时间是在中断函数中通过等待IR接收脚的电平状态翻转得到的。
  • 接收码值的时候采用的是右移的方式,所以得到和遥控器的码值一致
  • stm8在进入中断服务函数时,不用对标志位进行清零,会自动进行清零
    在这里插入图片描述

remote.h代码

#ifndef _REMOTE_H_
#define _REMOTE_H_

#include "stm8s.h"

#define REMOTE_IDH  0x30
#define REMOTE_IDL  0xCF

extern u8 IR_Data[4];//存放红外线解码接收的数据
extern bool IR_State;//FALSE表示未接收到数据,TRUE表示接收到数据

void Clk_Init(void);
void Remote_Init(void);
u8 getKey(void);

#endif

remote.c代码

#include "remote.h"

u8 IR_Data[4];//记录地址码、地址反码、控制码、控制反码
bool IR_State;//TRUE表示接收完一个码值,FALSE表示没接收完
BitStatus NEC_IR;//对应IR接收脚的电平状态,后面没用上
u16 x;//保存定时器的数值

//选用1MHz的HSI作为主时钟源
void Clk_Init(void){

    CLK_DeInit();//设置为默认值
    CLK_HSICmd(ENABLE);//启用HSI
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);//HSI分频 fMASTER
    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV16);//CPU分频 fCPU
}

//初始化PD3为浮空输入
void IR_GPIO_Init(void){

    EXTI_DeInit();
    GPIO_DeInit(GPIOD);//恢复指定端口
    //GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_IN_FL_NO_IT);//设置PD3为浮空输入无中断
    GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_IN_FL_IT);//设置PD3为浮空输入开启中断
    EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_ONLY);//仅下降沿触发
   
}

void TIM2_Init(void){
    
    TIM2_DeInit();
    TIM2_TimeBaseInit(TIM2_PRESCALER_16, 65535); //最大10ms溢出,1us加1,只有向上计数;计数最大值对应自动装载值
    TIM2_ARRPreloadConfig(ENABLE);//直到更新事件后,ARR预装载寄存器的值才会被拷贝到影子寄存器中
    
    //TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE);//允许更新/溢出中断
    //TIM2_Cmd(ENABLE);//开启定时器
    //enableInterrupts(); //开启中断
}

//TIM2为输入捕获
void Remote_Init(){
  
    IR_GPIO_Init();
    TIM2_Init();
    //printf("Remote Init \n");
}

//函数功能:测量高电平时间
u16 IR_GetTime_H(void){
    
    TIM2_SetCounter(0);//清空定时器的值
    TIM2_Cmd(ENABLE);//开启定时器
    while(GPIO_ReadInputPin(GPIOD, GPIO_PIN_3)){}//等待高电平结束
    x=TIM2_GetCounter();
    TIM2_Cmd(DISABLE);//关闭定时器
    return x;
    
}

//函数功能:测量低电平时间
u16 IR_GetTime_L(void){
    
    TIM2_SetCounter(0);//清空定时器的值
    TIM2_Cmd(ENABLE);//开启定时器
    while(!GPIO_ReadInputPin(GPIOD, GPIO_PIN_3)){}//等待低电平结束
    x=TIM2_GetCounter();
    TIM2_Cmd(DISABLE);//关闭定时器
    
    return x;
}

u8 getKey(void){
    u8 key=0;
    if((IR_Data[0]==REMOTE_IDH)&&IR_Data[1]==REMOTE_IDL){
        if(IR_Data[2]==(u8)~IR_Data[3]) key=IR_Data[2];//键值正确
    }
    return key;
}

//外部中断PD中断服务函数
INTERRUPT_HANDLER(EXTI_PORTD_IRQHandler, 6){
    
    u16 time;
    u8 i,j,data=0;
    
    //stm8自动清除中断标志位,所以此处没写
    
    
    //NEC_IR = GPIO_ReadInputPin(GPIOD, GPIO_PIN_3);
    
    time=IR_GetTime_L();//得到低电平时间
    if(time<7000||time>10000)return; //标准时间: 9000us
    time=IR_GetTime_H();得到高电平时间
    if(time<3000||time>5500)return;  //标准时间4500us
    
    
    //正式解码NEC协议
    for(i=0;i<4;i++)
    {
        for(j=0;j<8;j++)
        {
             time=IR_GetTime_L();       //得到低电平时间
             if(time<400||time>700)return;    //标准时间: 560us
             
             time=IR_GetTime_H();       //得到高电平时间
             if(time>1400&&time<1800)         //数据1 1680us
             {
                data>>=1;
                data|=0x80;
             }
             else if(time>400&&time<700)   //数据0 560us
             {
                data>>=1;
             }
             else return;
        }
        IR_Data[i]=data; //存放解码成功的值
    }
    
    //解码成功
    IR_State=TRUE;
}

main.c代码

#include "stm8s.h"
#include "stdio.h"
#include "remote.h"
#include "uart.h"
#include "led.h"
#include "delay.h"
#include "gpio_init.h"


void main(void)
{
    u8 key;
    //bool Power_Over_Flag=FALSE;
    //u8 *str=0;
    Clk_Init();//主时钟初始化
    
    //LED_init();
    init_TIM1();
    Remote_Init();//IR解码涉及相关的初始化
    UART_Init();//初始化串口设置
    enableInterrupts(); //开启中断
    
    Default_GPIO_Init();
	printf("all init \n");
    
    while(1){
       
        if(!GPIO_ReadInputPin(GPIOC, GPIO_PIN_6)){
            
            if(IR_State){
                IR_State=FALSE;
                key=getKey();
                switch(key){
                    case 0:
                        printf("other remote \n");
                        break;
                    case 0x51:
                        printf("Press Power \n");
                        GPIO_WriteReverse(GPIOD, GPIO_PIN_5);
                        //LED_Reverse();
                        Delay100ms(20);
                        //LED_Reverse();
                        GPIO_WriteReverse(GPIOD, GPIO_PIN_5);
                        //Power_Over_Flag=TRUE;
                        break;
                
                }
            }
            
        }
        halt();
    }
    
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值