title: STM32红外接收分析
date: -06-18 00:45:12
tags:
categories: STM32学习记录
红外遥控原理分析
对于红外遥控,一般都不会陌生,我们身边就有很多采用红外遥控的设备,例如绝大多数的电视,空调,都使用的是红外遥控原理的遥控器,配上红外接收管接收遥控信号。
红外线(Infrared)是频率介于微波与可见光之间的电磁波,波长在1mm到760纳米(nm)之间,频率比红光低的不可见光。
红外发射端一般使用红外LED,接收端使用红外接收二极管,利用光电效应,经过滤波和功率放大,形成能够承载信号的数字量。红外线发射管在LED封装行业中主要有三个常用的波段,如下850NM、875NM、940NM。850NM波长的主要用于红外线监控设备,875NM主要用于医疗设备,940NM波段的主要用于红外线控制设备。一般的红外遥控器使用的发射管是940nm波长,当然,我们中学做实验在气垫导轨上用到的光电门,也是这种红外光,这个波长的红外光人眼虽然看不见,但是相机的CMOS传感器是可以捕捉到的,具体验证方法,用手机开启相机,对着空调或者电视遥控器的发射管,按下遥控器,就可以看见屏幕里粉色的灯珠。
再来谈一谈红外遥控的方式是怎么传递信息的。大多数用于遥控的红外信号,采用NEC编码的方式。将用于驱动红外LED的电平信号,经过调制,形成某个高频信号的幅度、相位、频率等参量变化的过程,用一个信号去装载另一个信号。比如我们的红外遥控信号要发送的时候,先经过38K调制,如图所示:
经过这样的调制信号,我们就可以用它来装载我们需要的信号。比如对于NEC编码,规定发射信号有以下4种:
起始码:用于判断起始信号逻辑0逻辑1重复码:如果遥控器有连按功能,那么发完一次完整的信号后,按键松开前都会循环发送重复码
我们由于红外传输的是一种数字信号,所以我们可以使用脉宽调制(JPWM)的方式,用不同的电平时间区分这4种信息。具体的不同就是这样:
起始码:9ms低电平 + 4.5ms高电平逻辑0: 560us + 560us逻辑1: 560us + 1680us重复码 : 9ms低电平 + 2.5ms高电平
如果按照NEC编码的功能来区分,以正常的时序,就有以下几种码:
起始码用户地址码(8bit)+用户地址反码(8bit)数据码(8bit)+数据反码(8bit)如果连按,之后会一直发送重复码
器件准备
STM32核心板或开发板,有板载红外接收管最好一个红外遥控器(可以用空调或者电视遥控器代替)一个红外接收管,最好是带有放大电路的,但是不要使用带有解调芯片的。如果可以,最好是有一个逻辑分析仪或者示波器,可以提前对未知的红外遥控设备进行直观的解码程序编写
程序编写分为2个部分,分别是接收程序和发送程序,为了学习标准的NEC编码,我们使用标准遥控器,先通过编写接收程序,来体验红外编码的时序,有一个直观的理解之后,再尝试编写发送代码。
红外接收有3个引脚,5V,GND,以及信号引脚,先将接收模块连接逻辑分析仪,注意不要忘了共地,打开逻辑分析仪,再软件中设置上升沿和下降沿捕获,然后按下遥控器(可以是空调遥控器,我这里用专用红外遥控器的数字1键演示),然后逻辑分析仪就会捕获到一次信号,如下图:
这样,我们就得到了红外遥控器上按下数字1的红外信号,根据上面的原理分析,这个信号就很容易理解了:首先是一个起始码,有9ms低电平,4.5ms高电平,紧接着是8位地址码,这里是0(0000 0000),相应的8位地址反码就是255(1111 1111),紧接着是8位数据码,这里是162(1010 0010),相应8位数据反码93(0101 1101),然后经过一段时间高电平,接收到了重复码,有9ms低电平和2.5ms高电平,如果按住不放,之后就一直循环接收到重复码。
是不是一目了然?其实对我来说,这是迫不得已,因为我买的遥控器,资料非常有限,只给了一个电气参数的PDF,编码表也没有,例程更没有,无奈我只能动用逻辑分析仪,弄清楚了每个按键的编码值,顺便分析了一下电平时间和时序,算是做一个铺垫吧。
我要在小车上用红外接收,需要用到的按键,有下面一些:
接收程序思路分析
NEC编码解码的实质,其实是对电平时间的精确读取,我不禁想到了超声波模块。对了!一切都是那么的熟悉,定时器的捕获模式,刚好满足这种需求。
设置低电平捕获,一旦接收到信号,进入捕获中断,改为高电平捕获,捕捉到高电平时,读取计数值,再根据时基,就得到了低电平的时间,获取高电平时间的方法也是一样然后根据时间的范围,设置一些标志位,来判断数据起始位,然后获取地址码,地址反码,数据码,数据反码然后校验,将地址码与地址反码的取反进行比较,再与设定的地址码进行比较,如果都相等则表示地址数据无误,并且控制设备无误,继续下一步将数据码与数据反码的取反进行比较,相等则表示数据无误,再赋值给需要接收键值的变量。
由于我是应用在小车上的,本来是设计成蓝牙遥控,这样就可以拓展出红外遥控了,C8T6一共有4个定时器的资源,TIM1用于PWM模式,驱动两个电机,TIM2和TIM4用于读取2个AB相编码器,TIM3用于超声波模块的输入捕获,那么要想红外遥控,只能废掉超声波避障了。
timer.c&timer.h
根据红外接收的工作特性,应该接收到一次低电平,程序进入中断,我们开始判断是否是起始位,因此,初始化程序应该配置为Input Capture输入捕获模式,捕获极性为低电平捕获。
#include "timer.h"#include "delay.h"TIM_HandleTypeDef htim3;//定时器3句柄void Remote_Init(void){TIM_IC_InitTypeDef TIM3_CH3_IC_Initure;//定时器初始化句柄htim3.Instance=TIM3;//选择定时器:定时器3htim3.Init.Prescaler=(72-1); //预分频器:72分频,1M的计数频率,1um周期htim3.Init.CounterMode=TIM_COUNTERMODE_UP; //计数方向:向上计数htim3.Init.Period=10000; //自动装载值:最大计时10mshtim3.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频:不分频HAL_TIM_IC_Init(&htim3);//进行初始化//初始化TIM1输入捕获参数TIM3_CH3_IC_Initure.ICPolarity=TIM_ICPOLARITY_RISING; //捕获极性:上升沿捕获TIM3_CH3_IC_Initure.ICSelection=TIM_ICSELECTION_DIRECTTI;//交错映射:直接映射到TI3上TIM3_CH3_IC_Initure.ICPrescaler=TIM_ICPSC_DIV1;//输入捕获分频:不分频TIM3_CH3_IC_Initure.ICFilter=0x03; //硬件滤波:设置8个定时器时钟周期滤波HAL_TIM_IC_ConfigChannel(&htim3,&TIM3_CH3_IC_Initure,TIM_CHANNEL_3);//通道选择:配置TIM4通道3HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_3); //开始捕获TIM3的通道3__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE); //使能更新中断}//Input Capture 输入捕获MSP函数void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim){GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_TIM3_CLK_ENABLE(); //使能TIM3时钟__HAL_RCC_GPIOB_CLK_ENABLE();//开启GPIOB时钟GPIO_Initure.Pin=GPIO_PIN_0; //PB0GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; //复用输入GPIO_Initure.Pull=GPIO_PULLUP;//上拉GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速HAL_GPIO_Init(GPIOB,&GPIO_Initure);HAL_NVIC_SetPriority(TIM3_IRQn,1,3); //设置中断优先级,抢占优先级1,子优先级3HAL_NVIC_EnableIRQ(TIM3_IRQn); //开启ITM4中断}
接下来,我们实际上有两种中断,一种是捕获成功进入捕获中断,另一种未捕获,达到计数重装值,进入更新溢出中断,方便起见,在中断服务函数中写HAL库的中断处理总函数(HAL库就是香),在函数内部会自动进入各自的回调函数:
void TIM3_IRQHandler(void){HAL_TIM_IRQHandler(&htim3);//HAL库定时器处理函数}
由于在处理过程中需要有各种标志的判断,定义一个储存状态的8位变量会方便许多:
//[7]:收到了引导码标志//[6]:得到了一个按键的所有信息//[5]:保留//[4]:标记上升沿是否已经被捕获 //[3:0]:溢出计时器u8 RmtSta=0;//状态标志变量
然后,我们继续分析,起始码中的9ms低电平其实没有必要判断,作为一个触发的信号就好,因为我们完全可以通过后面的4.5ms高电平,就得到一个起始码,然后逻辑电平0和1的区别,也在于高电平的时间长短,甚至重复码的特征也是2.5ms的高电平,那么代码的主要思路就很确定了:上升沿捕获中断中设置计时起点,在下降沿捕获中断期间判断计数值,得到高电平持续的时间,就可以在下降沿中断期间做出判断,是起始码还是重复码,是逻辑1还是逻辑0。
因此有必要设置一个存储计数值的变量,表示高电平时间,本质是读取CCR4寄存器,所以是16位:
u16 Dval;//上升沿期间,计数器增加的值
再定义一个储存所有有效逻辑电平的变量,8+8+8+8一共32位:
u32 RmtRec=0;//红外接收到的数据
如果需要记录连按,那么就需要一个计数变量,一般情况不是必须的:
u8 RmtCnt=0;//按键按下的次数
接下来就是中断回调函数:
//定时器更新(溢出)中断回调函数void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){if(htim->Instance==TIM3){if(RmtSta&0x80)//检测第[7]位,如果收到引导码{RmtSta&=~0X10;//清零第[4]位,清除上升沿捕获标志if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;//标记已经完成一次按键的键值信息采集if((RmtSta&0X0F)<14)RmtSta++;else{RmtSta&=~(0x80);//清空起始标识RmtSta&=~(0x0F);//清空溢出计数器} }}}//定时器输入捕获中断回调函数void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断发生时执行{if(htim->Instance==TIM3){if(DATA_PIN)//读取引脚状态,如果引脚高电平,说明是上升沿捕获{TIM_RESET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_3); //清除捕获极性TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_3,TIM_ICPOLARITY_FALLING);//配置捕获极性,设置为下降沿捕获__HAL_TIM_SET_COUNTER(&htim3,0);//清空定时器计数值 RmtSta|=0X10;//设置第[4]位,标记上升沿已经被捕获}else //下降沿捕获{Dval=HAL_TIM_ReadCapturedValue(&htim3,TIM_CHANNEL_3);//读取输入捕获的计数值TIM_RESET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_3); //清除捕获极性TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_3,TIM_ICPOLARITY_RISING);//配置捕获极性,设置为上升沿捕获if(RmtSta&0X10)//完成一次上升沿捕获 {if(RmtSta&0X80)//接收到了引导码{if(Dval>300&&Dval<800)//收到逻辑0,560us{RmtRec<<=1;RmtRec|=0; }else if(Dval>1400&&Dval<1800)//收到逻辑1,1680us{RmtRec<<=1;RmtRec|=1;}else if(Dval>2200&&Dval<2600)//收到重复码,2.5ms{RmtCnt++; //按键次数增加1次RmtSta&=~(0x0F);//清空溢出计数器}}else if(Dval>4200&&Dval<4700)//收到起始码,4.5ms{RmtSta|=0x80;//设置第[7]位,表示成功接收到了引导码RmtCnt=0;//清除按键次数计数器} }RmtSta&=~(0x10);//清除上升沿捕获标志}}}
所有的操作我都打上了详细的注释,下面我按照正常接收一帧有效数据来过一遍这个时序,单片机如何读到数据,存入RmtRec变量中的:
首先起始码,9ms低电平,由于是下降沿,不会触发中断,然后4.5ms高电平,触发中断,判断为上升沿捕获,标记上升沿捕获,然后重新设置下降沿捕获,计数值清零,这时便开始记录高电平的持续时间。
if(DATA_PIN)//读取引脚状态,如果引脚高电平,说明是上升沿捕获{TIM_RESET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_3); //清除捕获极性TIM_SET_CAPTUREPOLARITY(&htim3,TIM_CHANNEL_3,TIM_ICPOLARITY_FALLING);//配置捕获极性,设置为下降沿捕获__HAL_TIM_SET_COUNTER(&htim3,0);//清空定时器计数值 RmtSta|=0X10;//设置第[4]位,标记上升沿已经被捕获}
一旦捕获到下降沿,便重新设置上升沿捕获,然后读取CCR4寄存器,存入Dval变量,由于设置的是72分频,计数值就是1um的数量,判断这个变量的所在区间,就可以得到收到的是什么。如果是起始码,就把相应的起始码标志位置1。如果判断标志位已经收到起始码,就判断逻辑码和重复码。如果是逻辑码,就将Rec变量左移,如果是1,就进行与操作,如果是0,可以不操作,因为右移自动补0。如果是重复码,就把重复计数加1。
if(RmtSta&0X10)//完成一次上升沿捕获 {if(RmtSta&0X80)//如果已经收到引导码{if(Dval>300&&Dval<800)//收到逻辑0,560us{RmtRec<<=1;RmtRec|=0; }else if(Dval>1400&&Dval<1800)//收到逻辑1,1680us{RmtRec<<=1;RmtRec|=1;}else if(Dval>2200&&Dval<2600)//收到重复码,2.5ms{RmtCnt++; //按键次数增加1次RmtSta&=~(0x0F);//清空溢出计数器}}else if(Dval>4200&&Dval<4700)//收到引导码,4.5ms{RmtSta|=0x80;//设置第[7]位,表示成功接收到了引导码RmtCnt=0;//清除按键次数计数器} }
如果超时,从逻辑分析仪的数据来看,只有一种可能,就是每次数据之间的上升沿时间会超过我设定的10ms自动重装时间,也就是说,会有下降沿中断无法达到,于是触发了溢出中断回调函数,这个中断就可以作为接收结束的标志,在里面进行标志位的复位操作即可。Sta变量的0-3位,作为计数位使用,可以计数0-15次,对于我的红外遥控器如果是连按的话,通常在15个重装时间150ms内会有重复码,如果15次还没有低电平出现,就视为松开了按键,需要清空起始标志和计数位,等待下一次按下按键。
//定时器更新(溢出)中断回调函数void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){if(htim->Instance==TIM3){if(RmtSta&0x80)//检测第[7]位,如果收到引导码{RmtSta&=~0X10;//清零第[5]位,清除上升沿捕获标志if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;//标记已经完成一次按键的键值信息采集if((RmtSta&0X0F)<14)RmtSta++;else{RmtSta&=~(0x80);//清空起始标识RmtSta&=~(0x0F);//清空溢出计数器} }}}
接下来,一共32位数据就存入了Rec变量中,由于存入数据时采用左移的方式,那么右移32-8=24位,与0xFF得到地址码,右移32-16=16位,得到地址反码,右移8位,得到数据码,直接与0xFF就是数据反码。然后,验证遥控用户码是否等于宏定义的地址码,并且取出从第24位开始的8位数据码,return返回,判断这个值,做相应的控制操作,就完成了。
u8 Remote_Scan(void){u8 sta=0; u8 temp1,temp2;if(RmtSta&(1<<6))//得到一个按键的所有信息了{temp1=RmtRec>>24;//得到地址码temp2=(RmtRec>>16)&0xff;//得到地址反码 if((temp1==(u8)~temp2)&&temp1==REMOTE_ID)//检验遥控地址码,遥控器发送的地址码要和这里宏定义的相同才匹配 {temp1=RmtRec>>8;temp2=RmtRec; if(temp1==(u8)~temp2)sta=temp1;//键值正确 } if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了{RmtSta&=~(1<<6);//清除接收到有效按键标识RmtCnt=0;//清除按键次数计数器}}return sta;}void read_yaokong(void){u8 key;key = Remote_Scan();switch (key){case 24: Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0; break;//前进case 74: Flag_Qian=0,Flag_Hou=1,Flag_Left=0,Flag_Right=0; break;//后退case 16: Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0; break;//左转case 90: Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=1; break;//右转case 56: Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=0; break;//停止case 162: mode_flag = 0; break;//遥控模式case 98: mode_flag = 1; break;//CCD循迹模式case 226: mode_flag = 2; break;//超声波避障模式}}
然后补上头文件即可完成全部的代码。
#ifndef __TIMER_H#define __TIMER_H#include "sys.h"#define DATA_PIN PBin(0)//红外数据输入脚#define REMOTE_ID 0 extern u8 RmtCnt; //按键按下的次数void Remote_Init(void);//红外传感器接收头引脚初始化u8 Remote_Scan(void);void read_yaokong(void);#endif
显然从逻辑分析仪看,我的红外遥控器发送的地址码是0x00,所以直接宏定义为0即可。
如果觉得《STM32红外接收分析》对你有帮助,请点赞、收藏,并留下你的观点哦!