• 注册 / 登录
  • 切换到窄版
  • 查看: 2286|回复: 0

    STM32定时器实现红外接收与解码

    [复制链接]

    676

    主题

    690

    帖子

    6808

    积分

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    6808
    发表于 2023-3-31 11:49:28 | 显示全部楼层 |阅读模式

    路线栈欢迎您!

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    一、前言

    NEC为红外遥控最常用的编码,红外载波频率为38KHz,其协议小巧简单,非常适合家电设备的控制。其他的还有 Phillips(RCA)的RC-5和RC-6,但那只是IR协议的少数。

    二、NEC协议

    红外遥控是一种比较常用的通讯方式,目前红外遥控的编码方式中,应用比较广泛的是NEC协议。

    NEC协议的特点如下:

    • 载波频率为38KHz;
    • 8位地址和8位指令长度;
    • 地址和命令2次传输(确保可靠性);
    • PWM脉冲位置调制,以发射红外载波的占空比代表“0”和“1”。
    其逻辑1与逻辑0的表示,如下图所示:

    1.png

    可以看到,逻辑1的位时间为2.25ms,脉冲时间560us;逻辑0的位时间为1.12ms,脉冲时间560us。

    一个完整的NEC数据包如下:

    2.png

    首次发送的是9ms高电平+4.5ms低电平,为引导码。

    接下来是8bit的地址码+8bit地址码的反码+8bit命令码+8bit命令码的反码。

    以上是一个正常的数据包,但可能存在一种情况:当长按某个键时,发送的是以110ms为周期的重复码,如下图所示:

    3.png

    重复码由9ms高电平和2.25ms的低电平以及560us的高电平组成。

    三、解码程序       

    在上面的图中可以看到,逻辑1和逻辑0的位时间是不同的,占空比也不同。所以我们可以根据位时间的长短来解码,也可以根据占空比的不同(1/2或1/4)来解码,或者二者同时作为解码条件,这里我们介绍根据位时间来解码。

    需要注意的是,很多红外一体接收头为了提高接受灵敏度。输入高电平,其输出的是相反的低电平。

    下图为示波器实际捕获的一组数据:

    4.png

    可以看到,空闲时为高电平,引导码为9ms低电平+4.5ms高电平。根据位时间解码的话,我们就不必关系高低电平各自的时间,只需关系总时间就行,即:引导码为13.5ms,逻辑1为2.25ms,逻辑0为1.12ms。
    首先,用STM32CubeMx配置定时器。系统时钟等的配置这里不在赘述,参考其它教程。
    这里使用TIM3的Channel1作为捕获通道,配置如下:

    5.png

    •  定时器时钟为内部时钟;
    •  Channel1配置为输入捕获模式;
    •  分频系数为63,因为系统时钟为64M,这样定时器实际时钟为64/(63+1)=1M,主要是为了程序中方便计算;
    •  捕获方式为下降沿捕获;
    •  最后别忘了打开定时器的中断。

    6.png

    最后一步,生成代码。在生成的TIM3中断函数中,屏蔽生成的中断处理还是,添加自己的解码程序如下:


    1. uint32_t TIM3_Over_Cnt = 0;//tim3溢出次数
    2. uint32_t TIM3_Sum_Cnt = 0;//两次下降沿之间的时间间隔
    3. uint32_t cnt0 = 0;
    4. uint8_t IR_Data[60];

    5. void TIM3_IRQHandler(void)
    6. {
    7.   /* USER CODE BEGIN TIM3_IRQn 0 */

    8.   /* USER CODE END TIM3_IRQn 0 */
    9. //  HAL_TIM_IRQHandler(&htim3);
    10.   /* USER CODE BEGIN TIM3_IRQn 1 */
    11.     if(__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE))      //定时器溢出中断
    12.     {
    13.         __HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);    //清除中断标记
    14.         TIM3_Over_Cnt++;
    15.     }
    16.     cnt0 = __HAL_TIM_GET_COUNTER(&htim3);
    17.     TIM3_Sum_Cnt = (TIM3_Over_Cnt << 16) + cnt0;//获取计数器的值
    18.     __HAL_TIM_SetCounter(&htim3,0);//清零重新计数
    19.     TIM3_Over_Cnt = 0;//清零重新计数

    20.     if (__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_CC1) != RESET)//TIM3CH1捕获中断
    21.     {
    22.        if(StartRevFlag == 1)//接收到引导码,开始解码
    23.        {
    24.          if(TIM3_Sum_Cnt > 36000)//大于36ms认为是结束
    25.          {
    26.            RevComplete = 1;//解码完成
    27.            IR_Tick = 0;
    28.          }
    29.          else if(RevComplete == 0)
    30.          {
    31.            if(TIM3_Sum_Cnt > 1000 && TIM3_Sum_Cnt < 1300)//1ms~1.3ms认为是低电平
    32.             IR_Data[IR_Idx] = 0;
    33.            else  if(TIM3_Sum_Cnt > 2100 && TIM3_Sum_Cnt < 2400)//2.1ms~2.4ms认为是高电平
    34.             IR_Data[IR_Idx] = 1;
    35.            else //接收错误,重新开始
    36.              StartRevFlag = 0;
    37.            IR_Idx++;
    38.            if(IR_Idx > 59)
    39.              IR_Idx = 59;
    40.         }

    41.        }
    42.        else
    43.        {
    44.          if(TIM3_Sum_Cnt > 13000 && TIM3_Sum_Cnt < 14000)//13~14ms引导码
    45.          {
    46.            StartRevFlag = 1;
    47.          }
    48.          IR_Tick = 0;
    49.          RevComplete = 0;//解码完成标志置零
    50.          IR_Idx = 0;//有效解码位
    51.          TIM3_Over_Cnt = 0;
    52.          TIM3_Sum_Cnt = 0;//定时器计数清零
    53.        }
    54.         __HAL_TIM_CLEAR_IT(&htim3, TIM_IT_CC1);
    55.     }

    56.   /* USER CODE END TIM3_IRQn 1 */
    57. }
    复制代码

    解码程序根据每次捕获下降沿之间的间隔判断是引导码还是逻辑1或逻辑0,接收到引导码之后,再开始将解码的数据保存下来,最后通过也是时长来判断解码结束。这里没有判断重复码,有兴趣的小伙伴可以自己加上。

    中断函数中,只是将每一位解码并保存,最后还需要在主程序中组合成字节并判断处理。


    1. void IR_Rev()
    2. {
    3.   uint8_t num = IR_Idx / 8;
    4.   uint8_t IRValue[8];

    5.   if(RevComplete == 1 && StartRevFlag == 1 && IR_Tick > 20)
    6.   {
    7.     if(num > 7)
    8.       num = 7;

    9.     for(uint8_t j=0;j<num;j++)//将每一位解码数据组合成字节数据
    10.     {
    11.       for(uint8_t i = 0;i< 8;i++)
    12.       {
    13.         IRValue[j] = IRValue[j]>>1;
    14.         if(IR_Data[j*8+i])
    15.           IRValue[j] |= 0x80;
    16.       }
    17.     }
    18.     if(IRValue[0] == 0x00 && IRValue[1] == 0xFF)//地址码正确
    19.     {
    20.       switch(IRValue[2])//判断数据码
    21.       {
    22.         case 0x46:
    23.           KeyValue = S_key_Menu;
    24.           break;
    25.         case 0x43:
    26.           KeyValue = S_key_Set;
    27.           break;
    28.         case 0x40:
    29.           KeyValue = S_key_Rst;
    30.           break;
    31.         case 0x15:
    32.           KeyValue = S_key_Down;
    33.           break;
    34.         case 0x09:
    35.           KeyValue = S_key_Up;
    36.           break;
    37.       }
    38.     }
    39.     StartRevFlag = 0;
    40.     RevComplete = 0;
    41.     IR_Tick = 0;
    42.   }
    43. }
    复制代码
    作者:Mr张工

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    小黑屋|路丝栈 ( 粤ICP备2021053448号 )

    GMT+8, 2024-12-22 19:42 , Processed in 0.051570 second(s), 21 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表