STM32自学笔记-8-红外遥控

开发板上有个红外接收器,是和F103的PB9相连,也配了一个遥控器,来学习红外遥控的原理,并且实际操作一下。

STM32自学笔记-8-红外遥控_第1张图片

HS0038是一款红外接收头,当收到遥控器发过来的信号后,通过REMOTE_IN传输到PB9口,我们做一个输入捕获,把波形还原,即可解码遥控器的信号了,然后用串口将遥控器对应信号的按键信息打印出来。原理如此,下面实操。

NEC协议红外遥控器的通信采用了38KHz的载波信号,位时间为1.125ms或2.25ms:发射时逻辑1为0.56ms脉冲(即38KHz的方波)+1.68ms的低电平,逻辑0为0.56ms脉冲+0.56ms低电平。而遥控接收头在收到脉冲时为低电平,在没有脉冲的时候为高电平,所以接收头端收到的信号为:逻辑1=0.56ms低+1.68ms高;逻辑0=0.56ms低+0.56ms高
NEC协议的数据格式为:同步码头、地址码、地址反码(即按位取反)、控制码、控制反码。其中同步码为一个9ms的低电平和一个4.5ms的高电平组成。地址和控制码都是8位。

PB9对应的是TIM4-CH4,把它设置为Input Capture direct mode,并且开启中断,选择prescaler位71,其他默认选择,generate code。

根据上面NEC的数据格式,我们可以只计算捕获高电平的时间,即560us高电平代表0,1680高电平代表1,4.5ms高电平代表同步码(也叫引导码)。
STM32CubeMX中设置TIM4的prescaler为71,则计数1代表1us,接下去就可以利用计数器来计时。并且一定要开启TIM4的中断。Generate Code之后参考了正点原子的代码
首先是两个中断回调函数:

uint8_t IRSta = 0; //接受状态,[0:3]溢出计数器,[4]标记上升沿是否已经被捕获,[5]保留,[6]得到了一个按键的全部信息,[7]收到了引导码标志
uint16_t Dval; //高电平计数器的值,根据此判断高低电平的时间
uint32_t IRData = 0;  //红外接收的数据

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)  //定时器更新中断回调
{
	if(htim->Instance == TIM4)
	{
		if(IRSta & 0x80) //[7]=1,即收到了引导码标志
		{
			IRSta &=~0x10;  //取消上升沿被捕获标记[4] 
			if((IRSta&0x0F) == 0x00)  //即低4位均为0
				IRSta |=1<<6;  //将[6]置1,标记已经得到一次按键的全部信息
			if((IRSta & 0x0F)<14)  //还未溢出
				IRSta ++;
			else
			{
				IRSta &=~(1<<7); //引导标志置0
				IRSta &= 0xF0; //溢出计数器清空
			}
		}
	}
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM4)
	{
		if(IR_IN)			//capture了上升沿
		{
			TIM_RESET_CAPTUREPOLARITY(&htim4, TIM_CHANNEL_4);   //清除捕获上升沿
			TIM_SET_CAPTUREPOLARITY(&htim4, TIM_CHANNEL_4, TIM_ICPOLARITY_FALLING); //开始捕获下降沿
			__HAL_TIM_SET_COUNTER(&htim4, 0);  //清空定时器值
			IRSta |= 0x10;   //[4]置1,即标志上升沿已捕获
		}
		else  //捕获下降沿
		{
			Dval = HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_4);   //下降沿计数
			TIM_RESET_CAPTUREPOLARITY(&htim4,TIM_CHANNEL_4);   //清除捕获下降沿
			TIM_SET_CAPTUREPOLARITY(&htim4,TIM_CHANNEL_4,TIM_ICPOLARITY_RISING);  //开始捕获上升沿
			if(IRSta & 0x10)  //如果完成了一次高电平捕获,接下来看是否有引导码
			{
				if(IRSta & 0x80)  //接收了引导码
				{
					if(Dval>300 && Dval<800)   //560us
					{
						IRData <<= 1;  //左移一位
						IRData |= 0;   //高电平560us,表示0
					}
					else if(Dval>1400 && Dval<1800)  //1680us
					{
						IRData <<=1;
						IRData |= 1;  //高电平1680us,表示1
					}
					else if(Dval>2200 && Dval<2600)  //2.5ms
					{
						keyCount ++;
						IRSta &= 0xF0;
					}
				}
				else if(Dval>4200 && Dval<4700)  //4.5ms高电平,引导码
				{
					IRSta |=1<<7; //[7]置1,引导码
					keyCount = 0;   
				}
			}	
		IRSta &=~(1<<4); //清空[4],即高电平计数结束
		}
	}
}

接下去定义一个Remote_Scan()函数,即根据扫描得到的时长获取地址码、控制码,并计算反码和真实扫描的反码进行对比,还有一个遥控器ID(即地址码)的校验,这里正点原子的REMOTE_ID为0,即地址码为0。具体代码如下:

uint8_t Remote_Scan(void)
{
	uint8_t sta=0;
	uint8_t t1, t2;
	if(IRSta &(1<<6))  //[6]=1,得到一个按键的信息
	{
		t1 = IRData >> 24;  //地址码
		t2 = (IRData >> 16) & 0xFF;  //地址反码
		if((t1==(uint8_t)~t2) && t1 == REMOTE_ID)  //校验t1是否为t2的反码
		{
			t1 = IRData >> 8;  //控制码
			t2 = IRData;  //控制码反码
			if(t1==(uint8_t)~t2)   //再次校验是否反码
				sta = t1;  //t1即收到的控制码
		}
		if((sta==0)||((IRSta&0X80)==0))//按键数据错误/遥控已经没有按下了
		{
		 	IRSta&=~(1<<6);//清除接收到有效按键标识
		}
	}
	return sta;
}

其他的代码基本无需更改,**但需要在MX_TIM4_Init(void)最后添加两个语句

  HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_4);   //开启通道4的捕获(中断方式)
  __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);   //更新使能中断

main函数的while(1)循环里使用switch case语句来判断读到的控制码是多少,根据控制码显示出按的遥控器哪个键。正点原子的实例是通过LCD显示屏来显示,而我改写了串口输出。

测试的时候发现按一次按键会输出很多次同样的keystr,猜想是按键的时间有点长,在正点原子的例子上体现不出来,所以我在获取key之后再延时了400ms(这个值可以去多次试)再进入判断语句,可以按一次键,串口输出一次结果。

while (1)
  {
	key=Remote_Scan();
	HAL_Delay(400);   //这个Delay很有必要
	if(key)
	{
		printf("Code of the IR is: %0.8X \r\n", key);
		switch(key)
		{
			    case 0:str="ERROR";break;			   //其实不可能为0,为0进不来
				case 162:str="POWER";break;	    
				case 98:str="UP";break;	    
				case 2:str="PLAY";break;		 
				case 226:str="ALIENTEK";break;		  
				case 194:str="RIGHT";break;	   
				case 34:str="LEFT";break;		  
				case 224:str="VOL-";break;		  
				case 168:str="DOWN";break;		   
				case 144:str="VOL+";break;		    
				case 104:str="1";break;		  
				case 152:str="2";break;	   
				case 176:str="3";break;	    
				case 48:str="4";break;		    
				case 24:str="5";break;		    
				case 122:str="6";break;		  
				case 16:str="7";break;			   					
				case 56:str="8";break;	 
				case 90:str="9";break;
				case 66:str="0";break;
				case 82:str="DELETE";break;	
		}
		printf("func of the code is: %s\r\n", str);
		
	}
	else 
	{
		HAL_Delay(10);
	}

或者也可以调整时钟频率,改小一点,再试试结果。

你可能感兴趣的:(arm,单片机,stm32,嵌入式硬件,mcu)