欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

STM32L475裸机例程学习红外接收实验

程序员文章站 2022-06-09 08:58:05
...

红外遥控实验

红外遥控简介

红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设计红外线遥控器时,不必要像无线电遥控器那样,每套(发射器和接收器)要有不同的遥控频率或编码(否则,就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用电器上普及红外线遥控提供了极大的方面。由于红外线为不可见光,因此对环境影响很小,再由于红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。

红外遥控的编码目前广泛使用的是:NEC Protocol 的 PWM(脉冲宽度调制)和 Philips RC-5 Protocol 的 PPM(脉冲位置调制)。

ALIENTEK 阿波罗 STM32L425 开发板配套的遥控 器使用的是 NEC 协议,其特征如下: 1、8 位地址和 8 位指令长度;
2、地址和命令 2 次传输(确保可靠性)
3、PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
4、载波频率为 38Khz;
5、位时间为 1.125ms 或 2.25ms;

NEC 码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到 的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。 NEC 遥控指令的数据格式为:**同步码头、地址码、地址反码、控制码、控制反码。同步码 由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。**采用反码是为了增加传输的可靠性(可用于校验)。

设计思路

1)通过TIM定时器捕获上升&下降沿,同时开启定时器获得上升下降沿之间的时间。
2)通过判断上升下降沿之间的时间判断传输的是逻辑0还是逻辑1并存储在寄存器内。
3)读取寄存器内的值,判断红外信号。

在RECEPTION.c 文件中主要包含了4个函数
①配置函数
②捕获中断函数
③溢出中断函数
④红外信号判断函数

配置函数

TIM_HandleTypeDef htim3;
/**
  * @brief TIM3 Initialization Function
  * @param None
  * @retval None
  */
void Reception_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_IC_InitTypeDef sConfigIC ;

  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 79;// 80M/80= 1M
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 10000; // Tout = 10000/1m =10ms
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
//	HAL_TIM_Base_Init(&htim3);
	HAL_TIM_IC_Init(&htim3);
	
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
	HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);

  sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;//????
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0x03;
  HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_4);
	HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_4);
	__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);   //使能更新中断 
}

其中:

  htim3.Init.Prescaler = 79;// 80M/80= 1M
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 10000; // Tout = 10000/1m =10ms
  • precescaler表示的是tick一次的值,即1/1M=6106s1/1M=6*10^{-6}s, period表示tick的次数加起来的周期,即周期为10ms。
    STM32L475裸机例程学习红外接收实验
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
  • 这段函数配置内部时钟为TIM计时器的输入时钟,在使用stmcube时选择internal clock会自动生成这段代码。实际上如果不添加这段代码,也会默认使用内部时钟。所以在例程中这段代码没有体现,可有可无。
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_4);
__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);  
  • 使能更新中断,这两个使能函数在自动生成时是没有的,需要自己添加。

捕获中断函数

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断 
{
	if(htim->Instance==TIM3)
	{
		if(RDATA)
		{
				TIM_RESET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_4);  //一定要先清除原来的设置!!
				TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_4, TIM_ICPOLARITY_FALLING); //CC1P=1 设置为下降沿捕获
				__HAL_TIM_SET_COUNTER(&htim3, 0); //清空定时器值
				RmtSta |= 0X10;					//标记上升沿已经被捕获
		}
		else
		{
				TIM_RESET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_4);  //一定要先清除原来的设置!!
				TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_4, TIM_ICPOLARITY_RISING); //CC1P=1 设置为下降沿捕获
				Dval = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_4); //读取CCR1也可以清CC1IF标志位
				if(RmtSta&0x10)
				{
					if(RmtSta&0x80)//接收到了引导码
					{
									if(Dval > 200 && Dval < 900)//Dval是以us为单位 因为我们在设置分频系数的时候为80M/(79+1)=1M
									{
										RmtRec<<=1;
										RmtRec|=0;//接收到一个低电平
									}
									else if(Dval > 1200 && Dval < 2000)
									{
										RmtRec<<=1;
										RmtRec|=1;
									}
									else if(Dval > 2000 && Dval < 3000)//得到按键键值增加的信息 2500为标准值2.5ms
									{
										RmtCnt++;
										RmtSta &= 0XF0;	//清空计时器
									}
					}
					else if(Dval > 4000 && Dval < 5000)
						{
							RmtSta |= 1 << 7;	//标记成功接收到了引导码
							RmtCnt=0;
					}
				}
		RmtSta &= ~(1 << 4);//清空上升沿捕获
		}
	}
}

函数逻辑流程图
STM32L475裸机例程学习红外接收实验
简单来说基本逻辑就是
(1)捕获引导码,确认红外线信号开始传输。

if(RmtSta&0x80)//接收到了引导码

(2)捕获上升沿和下降沿并通过定时器的使能判断信号的逻辑(或者是连续按键信号)

if(Dval > 200 && Dval < 900)//Dval是以us为单位 因为我们在设置分频系数的时候为80M/(79+1)=1M
									{
										RmtRec<<=1;
										RmtRec|=0;//接收到一个低电平
									}
									else if(Dval > 1200 && Dval < 2000)
									{
										RmtRec<<=1;
										RmtRec|=1;
									}

这里有一个小细节是,下降沿捕获函数内有一个判断条件是上升沿标记标志,即下降沿捕获函数的前提是必须有一个上升沿在之前,这样可以保证时钟数据的正确性。

if(RmtSta&0x10)//判断是否接收到上升沿

(3)清除上升沿捕获,等待下次捕获上升沿。
(注意:此时引导码只被标记但是未被清除,这个标记值在溢出中断中得到清除,稍后解释)

溢出中断函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM3)
    {
        if(RmtSta & 0x80) //上次有数据被接收到了(引导码)
        {
            RmtSta &= ~0X10;		
					//取消上升沿已经被捕获标记

            if((RmtSta & 0X0F) == 0X00)RmtSta |= 1 << 6; //标记已经完成一次按键的键值信息采集

            if((RmtSta & 0X0F) < 14)RmtSta++;

            else
            {
                RmtSta &= ~(1 << 7); //清空引导标识
                RmtSta &= 0XF0;	//清空计数器
            }
        }
    }
}

函数逻辑流程图
STM32L475裸机例程学习红外接收实验
溢出的定义
STM32L475裸机例程学习红外接收实验
当溢出计时器统计到时间达到了溢出值,将会进入到溢出中断。
首先,判断是否接收到了引导码,由此来判断一个红外信号是否还在传输过程中。

if(RmtSta & 0x80) //上次有数据被接收到了(引导码)

如果没有接收到引导码,退出溢出中断,无事发生。
如果接收到了引导码,说明一个红外信号是否还在传输过程中,此时:
对**溢出标记(RmtSta的后四位)**进行判断,一般来说第一次进入溢出标记的信号标记都是0,此时会直接标记得到一条完整信息进入到信号输出函数中去(因为一条红外信号的传输一般是不会超过10ms的),而溢出标记也会随之加1,同时如果在溢出中断增加的过程中接收到了连续按键信号的话,在捕获中断函数中我们可以看到,函数将溢出标记置零,当溢出标记已经达到14次而仍然没有捕获到下降沿,将强行将引导标识清除并将溢出标记置0。此时如果信号传输未完成,信号输出函数的校验码会出现问题并输出error。
红外信号判断函数

u8 Remote_Scan(void)
{
    u8 sta = 0;
    u8 t1, t2;

    if(RmtSta & (1 << 6)) //得到一个按键的所有信息了
    {
        t1 = RmtRec >> 24;			//得到地址码
        t2 = (RmtRec >> 16) & 0xff;	//得到地址反码

        if((t1 == (u8)~t2) && t1 == REMOTE_ID) //检验遥控识别码(ID)及地址
        {
            t1 = RmtRec >> 8;
            t2 = RmtRec;

            if(t1 == (u8)~t2)sta = t1; //键值正确
        }

        if((sta == 0) || ((RmtSta & 0X80) == 0)) //按键数据错误/遥控已经没有按下了(引导码被清除了)
        {
            RmtSta &= ~(1 << 6); //清除接收到有效按键标识
            RmtCnt = 0;		//清除按键次数计数器
        }
    }

    return sta;
}