返回列表 发新帖我要提问本帖赏金: 150.00元(功能说明)

[MM32生态] 基于MM32在红外抄表及红外遥控器中的应用

[复制链接]
 楼主| xld0932 发表于 2022-3-5 10:31 | 显示全部楼层 |阅读模式
<
本帖最后由 xld0932 于 2022-3-5 10:31 编辑

#申请原创#   @21小跑堂

在之前的分享中有介绍基于MM32IrDA红外通讯功能,IrDA其本身不具体载波通讯的功能,在干扰较大或者远距离通讯时,会有明显的不足;所以本文分享了红外通讯的另外一种实现方式:红外载波通讯。常用的红外载波频率有36kHz38kHz40kHz等等,对于红外接收头来说,当接收到载波信号时会解析成低电平,当没有载波信号时会解析成高电平,通过高低电平的组合切换,实现数据位传输,从而实现数据通讯。本文包含如下几个小节的内容:
  • 基于MM32红外抄表通讯实现
  • 基于MM32红外遥控器NEC解码实现
  • 基于MM32红外遥控器NEC编码实现
  • 基于MM32带自学功能的红外遥控器

1、基于MM32红外抄表通讯实现

在典型的红外抄表电路中,常用到38kHz频率的载波信号来实现串口通讯功能;载波通讯的调制电路有很多,本文使用的是基于或门的标准调制方式,如下图所示:

967606222c3ce7a9e5.png

LED为红外发射管,或门的2引脚一直保持38kHz的波形输入,当或门的1引脚UART_TX输出高电平时,或门的3引脚输出将不会受到2引脚输入的是38kHz高低电平的影响,会一直保持输出高电平,此时红外发射管处于截止状态;而当或门的1引脚UART_TX输出低电平时,或门的3引脚的输出电平状态将跟随2引脚电平状态的变化而变化,此时红外发射管则会发送出38kHz频率的波形,用被红外接收管检测到。

上图的原理图中,我们串联了一个1k欧姆的限流电阻,这个在实际的应用时需要做相应的调整,用于调节红外发射管的工作电流,直接影响到发射管的功率和传输距离。

我们先来看一下UART传输一个8位字节长度数据的时序图,如下所示;在开始数据传输时,会先传输一个比特位的低电平,用于起始位功能,接下来是数据位第0位到第7位依次传输,最后会传输一个比特位的高电平,用于停止位功能;其中传输每一比特位(包括起始位、数据位和停止位)的传输时间,都是相等的,由串口通讯波特率决定的;如果9600bps的波特率,所表示的含意是在1秒的时间位,可传输9600个比特位,这样算下来每一个比特位所占用的时间为1/9600秒,约一个比特位的传输需要104.17微秒。

999516222c4128efb5.png

其次我们来看一下选用的红外接收头的电气特性,如下图所示;可以从下图中看出,当红外发射头发送38kHz频率的载波信号时,红外接收头解析得到的是低电平;当红外发射头不发送载波信号时,红外接收头解析得到的高电平。

115426222c421a0730.png

所以结合UART发送时序、IR接收头的特性、以及发送头或门的设计电路,可以使用TIM定时器配置一个标准的38kHz频率的PWM波形输出到或门的2引脚;然后初始化UART,配置成收发模式,波特率设置为4800,此时在初始化完成后的TX电平为高电平,通过或门电路后,红外发射头处于截止状态,相对的红外接收头也没有接收到载波信号,处于高电平状态;在通过串口发送起始位时,TX电平变成低电平,正好导致或门输出了38kHz频率波形,对于红外接收头来说,它接收到了载波频率后,解析输出就为低电平,而低电平对于UART接收时序来说,正好代表起始位;这样的电平状态正好符合了UART收发送时序,通过或门电平与UART结合实现了UART的红外通讯功能。

细心的小伙伴可能会有疑问,为什么串口波特率设置为4800,而不是其它更快速率呢?这是因为在我下面的硬件电路设计中,选择的红外接收头有这样一个特性Min burst length的值为8 cycles;而我们发送的38kHz载波的时间是由UART的每一比特位传输时间来决定的,所以如果UART的波特率越大,那每一比特位的传输时间就会越短,这个位传输时间就直接影响了38kHz输出的周期数量。38kHz波形的一个周期时间约为26.3微秒,那最小8个周期就是210微秒左右,而UART波特率设置为9600时对应的位时间只有104微秒左右,38kHz载波周期满足不了红外接收头的器件要求,在接收数据时就会出现误码的情况;而当UART波特率设置为4800时对应的位时间大概在208.3微秒,刚刚达到红外接收后的要求,如下图所示;所以为了保证UART红外通讯的稳定,建议将波特率设置成2400或者1200UART的传输速率并不是因为UART性能的问题,而是因为38kHz的载波频率和红外接收头的特性所决定的,为了提升UART红外通讯速率,可以选择性能更好的红外接收头。

771646222c43f239fb.png

原理图设计:

硬件上选用MM32F0133C6P作为主控芯片,搭载最小系统电路及CH340作为打印调试接口,使用TC7SZ32FU或门芯片结合IR383-A实现红外发送电路,使用IRM-3638MF56红外接收头实现红外接收电路,通过引出8GPIO引脚,作为后面扩展4*4按键矩阵接口,来实现遥控器的功能。

277436222c4574f547.png
693656222c45f1ea6d.png

PCB设计:

198706222c46e4f46b.png

焊接成品:

685676222c4811510f.png

UART红外通讯代码实现:配置TIM输出38kHz载波频率:

  1. /*******************************************************************************
  2. * [url=home.php?mod=space&uid=247401]@brief[/url]      
  3. * @param      
  4. * @retval      
  5. * [url=home.php?mod=space&uid=93590]@Attention[/url]   
  6. *******************************************************************************/
  7. void IRM_InitTIM1(void)
  8. {
  9.     GPIO_InitTypeDef        GPIO_InitStructure;
  10.     TIM_OCInitTypeDef       TIM_OCInitStructure;
  11.     TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  12.     RCC_ClocksTypeDef  RCC_Clocks;
  13.     RCC_GetClocksFreq(&RCC_Clocks);

  14.     RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);

  15.     TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  16.     TIM_TimeBaseStructure.TIM_Prescaler         = (RCC_Clocks.PCLK2_Frequency / 380000 - 1);
  17.     TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
  18.     TIM_TimeBaseStructure.TIM_Period            = (10 - 1);
  19.     TIM_TimeBaseStructure.TIM_ClockDivision     = TIM_CKD_DIV1;
  20.     TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  21.     TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

  22.     TIM_OCStructInit(&TIM_OCInitStructure);
  23.     TIM_OCInitStructure.TIM_OCMode      = TIM_OCMode_PWM1;
  24.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  25.     TIM_OCInitStructure.TIM_Pulse       = 5;
  26.     TIM_OCInitStructure.TIM_OCPolarity  = TIM_OCPolarity_High;
  27.     TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
  28.     TIM_OC2Init(TIM1, &TIM_OCInitStructure);

  29.     TIM_Cmd(TIM1, ENABLE);

  30.     RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

  31.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_3);

  32.     GPIO_StructInit(&GPIO_InitStructure);
  33.     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;
  34.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  35.     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
  36.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  37.     TIM_CtrlPWMOutputs(TIM1, ENABLE);
  38. }

UART红外通讯代码实现:配置UART工作在收发模式,参数为4800/N/8/1:

  1. /*******************************************************************************
  2. * [url=home.php?mod=space&uid=247401]@brief[/url]      
  3. * @param      
  4. * @retval      
  5. * [url=home.php?mod=space&uid=93590]@Attention[/url]   
  6. *******************************************************************************/
  7. void IRM_InitUART(void)
  8. {
  9.     GPIO_InitTypeDef GPIO_InitStructure;
  10.     NVIC_InitTypeDef NVIC_InitStructure;
  11.     UART_InitTypeDef UART_InitStructure;

  12.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART2, ENABLE);

  13.     NVIC_InitStructure.NVIC_IRQChannel = UART2_IRQn;
  14.     NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
  15.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  16.     NVIC_Init(&NVIC_InitStructure);

  17.     UART_StructInit(&UART_InitStructure);
  18.     UART_InitStructure.UART_BaudRate            = 4800;
  19.     UART_InitStructure.UART_WordLength          = UART_WordLength_8b;
  20.     UART_InitStructure.UART_StopBits            = UART_StopBits_1;
  21.     UART_InitStructure.UART_Parity              = UART_Parity_No;
  22.     UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
  23.     UART_InitStructure.UART_Mode                = UART_Mode_Rx | UART_Mode_Tx;
  24.     UART_Init(UART2, &UART_InitStructure);

  25.     UART_ITConfig(UART2, UART_IT_RXIEN, ENABLE);
  26.     UART_Cmd(UART2, ENABLE);

  27.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  28.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);
  29.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1);

  30.     GPIO_StructInit(&GPIO_InitStructure);
  31.     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
  32.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  33.     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
  34.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  35.     GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_3;
  36.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  37.     GPIO_Init(GPIOA, &GPIO_InitStructure);
  38. }


  39. /*******************************************************************************
  40. * @brief      
  41. * @param      
  42. * @retval      
  43. * @attention   
  44. *******************************************************************************/
  45. void UART2_IRQHandler(void)
  46. {
  47.     uint8_t Data = 0;

  48.     if(UART_GetITStatus(UART2, UART_IT_RXIEN) != RESET)
  49.     {
  50.         Data = UART_ReceiveData(UART2);
  51.         UART_ClearITPendingBit(UART2, UART_IT_RXIEN);

  52.         printf("\r\nRX : 0x%02x", Data);
  53.     }
  54. }


  55. /*******************************************************************************
  56. * @brief      
  57. * @param      
  58. * @retval      
  59. * @attention   
  60. *******************************************************************************/
  61. void IRM_UART_SendData(uint8_t Data)
  62. {
  63.     UART_SendData(UART2, Data);
  64.     while(UART_GetFlagStatus(UART2, UART_IT_TXIEN) == RESET);
  65. }

调试演示结果:

如下图是红外发送0x55数据时的UART调制波形:

485856222c4f058d09.png

如下图是红外发送一串数据后,红外接收到数据后,产生UART接收中断后将数据打印出来的运行截图:

747226222c4fc2d945.png


2、基于MM32红外遥控器NEC解码实现

NEC协议是红外遥控器协议中的一种,它由引导码、地址码、地址反码、命令码和命令反码组成;逻辑12.25ms,其中脉冲时间为560us;逻辑01.12ms,其中脉冲时间为560us;如下图所示:

982316222c55032a4f.png

重复码为9ms的脉冲电平和2.25ms的低电平组成,如下图所示:

535306222c55ad9ec2.png

NEC协议格式:

182556222c56e6dd3b.png

正常发送:首先发送的是引导码,它是由9ms的脉冲电平和4.5ms的低电平组成,后面跟着的是8位数据的地址码、地址反码、命令码和命令反码,以LSB的发送方式进行位传输 。

995926222c57b5c373.png

重复发送:如果你想重复发送一个命令码,在完成正常发送后,就不需要重复的发送命令码了,只需要间隔发送重复码就可以了。

对于红外遥控器NEC解码,对于红外硬件接收头来说就不能使用UART功能,NEC协议本身与UART位发送时序是完全不一样的,我们就需要通过MCU的其它外设功能来实现;这里我们将UART_RX这个GPIO引脚当作EXTI外部中断线触发引脚,结合TIM定时器来实现对NEC的解码功能;通过TIM计数EXTI触发的时间间隔来解析NEC传输的数据,支持重复命令码的识别,具体的配置代码如下:

  1. uint8_t  IRM_RX_Buffer[32];
  2. uint8_t  IRM_RX_Index  = 0;
  3. uint8_t  IRM_RX_Repeat = 0;

  4. uint8_t  IRM_RX_Complete = 0;
  5. uint32_t IRM_RX_Overtime = 0;

  6. uint8_t  IRM_RX_Step = 0;
  7. uint8_t  IRM_RX_Flag = 0;
  8. uint32_t IRM_RX_Tick = 0;


  9. /*******************************************************************************
  10. * @brief      
  11. * @param      
  12. * @retval      
  13. * @attention   
  14. *******************************************************************************/
  15. void IRM_InitEXTI(void)
  16. {
  17.     GPIO_InitTypeDef GPIO_InitStructure;
  18.     EXTI_InitTypeDef EXTI_InitStructure;
  19.     NVIC_InitTypeDef NVIC_InitStructure;

  20.     RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

  21.     GPIO_StructInit(&GPIO_InitStructure);
  22.     GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_3;
  23.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  24.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  25.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

  26.     SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource3);

  27.     EXTI_StructInit(&EXTI_InitStructure);
  28.     EXTI_InitStructure.EXTI_Line    = EXTI_Line3;
  29.     EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
  30.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
  31.     EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  32.     EXTI_Init(&EXTI_InitStructure);

  33.     NVIC_InitStructure.NVIC_IRQChannel = EXTI2_3_IRQn;
  34.     NVIC_InitStructure.NVIC_IRQChannelPriority = 0x03;
  35.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  36.     NVIC_Init(&NVIC_InitStructure);
  37. }


  38. /*******************************************************************************
  39. * @brief      
  40. * @param      
  41. * @retval      
  42. * @attention   
  43. *******************************************************************************/
  44. void EXTI2_3_IRQHandler(void)
  45. {
  46.     uint32_t t = 0;

  47.     t = IRM_RX_Tick;
  48.     IRM_RX_Tick = 0;

  49.     IRM_RX_Overtime = 0;

  50.     switch(IRM_RX_Step)
  51.     {
  52.         case 0:
  53.             if(     GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)    && (t > 850) && (t < 950))
  54.             {
  55.                 IRM_RX_Step = 1;
  56.             }
  57.             break;

  58.         case 1:
  59.             if(     !GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t > 400) && (t < 500))
  60.             {
  61.                 IRM_RX_Step = 2;    IRM_RX_Index = 0;   IRM_RX_Flag = 0;
  62.             }
  63.             else if(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t > 175) && (t < 275))
  64.             {
  65.                 IRM_RX_Step = 4;
  66.             }
  67.             break;

  68.         case 2:
  69.             if(     GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)    && (t >  46) && (t <  66))
  70.             {
  71.                 IRM_RX_Step = 3;
  72.             }
  73.             break;

  74.         case 3:
  75.             if(     !GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t >  46) && (t <  66))
  76.             {
  77.                 IRM_RX_Buffer[IRM_RX_Index++] = 0;
  78.             }
  79.             else if(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t > 119) && (t < 219))
  80.             {
  81.                 IRM_RX_Buffer[IRM_RX_Index++] = 1;
  82.             }

  83.             if(IRM_RX_Index == 32)
  84.             {
  85.                 IRM_RX_Step = 0;    IRM_RX_Repeat = 0;  IRM_RX_Flag = 1;
  86.             }
  87.             else
  88.             {
  89.                 IRM_RX_Step = 2;
  90.             }
  91.             break;

  92.         case 4:
  93.             if(     GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)    && (t >  46) && (t <  66))
  94.             {
  95.                 IRM_RX_Step = 0;    IRM_RX_Repeat++;
  96.             }
  97.             break;

  98.         default:
  99.             break;
  100.     }

  101.     EXTI_ClearITPendingBit(EXTI_Line3);
  102. }


  103. /*******************************************************************************
  104. * @brief      
  105. * @param      
  106. * @retval      
  107. * @attention   
  108. *******************************************************************************/
  109. void IRM_InitTIM3(void)
  110. {
  111.     NVIC_InitTypeDef        NVIC_InitStructure;
  112.     TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  113.     RCC_ClocksTypeDef  RCC_Clocks;
  114.     RCC_GetClocksFreq(&RCC_Clocks);

  115.     RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM3, ENABLE);

  116.     TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  117.     TIM_TimeBaseStructure.TIM_Prescaler         = (RCC_Clocks.PCLK1_Frequency * 2 / 1000000 - 1);
  118.     TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
  119.     TIM_TimeBaseStructure.TIM_Period            = (10 - 1);
  120.     TIM_TimeBaseStructure.TIM_ClockDivision     = TIM_CKD_DIV1;
  121.     TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  122.     TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

  123.     TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
  124.     TIM_ITConfig(TIM3, TIM_IT_Update,  ENABLE);

  125.     TIM_Cmd(TIM3, ENABLE);

  126.     NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
  127.     NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
  128.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  129.     NVIC_Init(&NVIC_InitStructure);
  130. }


  131. /*******************************************************************************
  132. * @brief      
  133. * @param      
  134. * @retval      
  135. * @attention   
  136. *******************************************************************************/
  137. void TIM3_IRQHandler(void)
  138. {
  139.     IRM_RX_Tick++;

  140.     if(IRM_RX_Complete == 0)
  141.     {
  142.         if(IRM_RX_Overtime++ > 2000)
  143.         {
  144.             IRM_RX_Complete = 1;    IRM_RX_Step = 0;
  145.         }
  146.     }

  147.     TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
  148. }

通过NEC编码制式的控制器,对着开发板按键,这时在红外接收头解析到数据后,进行输出打印,实际运行效果如下所示:

690996222c59fa863c.png


3、基于MM32红外遥控器NEC编码实现

对于红外遥控器NEC编码,我们硬件电路也是共用的,还是需要一个TIM定时器来固定输出38kHz频率的载波;然后不能使用UART_TX功能了,它同样不符合NEC协议,此时我们将UART_TX这个GPIO引脚当作普通GPIO引脚使用,通过控制这个GPIO引脚输出高低电平的时序来实现NEC编码;此时需要另外一个TIM定时器来实现精确控制,配合GPIO来实现NEC编码的发送,具体的配置代码如下:

  1. /*******************************************************************************
  2. * @brief      
  3. * @param      
  4. * @retval      
  5. * @attention   
  6. *******************************************************************************/
  7. void IRM_InitGPIO(void)
  8. {
  9.     GPIO_InitTypeDef GPIO_InitStructure;

  10.     RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

  11.     GPIO_StructInit(&GPIO_InitStructure);
  12.     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
  13.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  14.     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
  15.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  16.     GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_SET);
  17. }


  18. /*******************************************************************************
  19. * @brief      
  20. * @param      
  21. * @retval      
  22. * @attention   
  23. *******************************************************************************/
  24. void IRM_InitTIM1(void)
  25. {
  26.     GPIO_InitTypeDef        GPIO_InitStructure;
  27.     TIM_OCInitTypeDef       TIM_OCInitStructure;
  28.     TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  29.     RCC_ClocksTypeDef  RCC_Clocks;
  30.     RCC_GetClocksFreq(&RCC_Clocks);

  31.     RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);

  32.     TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  33.     TIM_TimeBaseStructure.TIM_Prescaler         = (RCC_Clocks.PCLK2_Frequency / 380000 - 1);
  34.     TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
  35.     TIM_TimeBaseStructure.TIM_Period            = (10 - 1);
  36.     TIM_TimeBaseStructure.TIM_ClockDivision     = TIM_CKD_DIV1;
  37.     TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  38.     TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

  39.     TIM_OCStructInit(&TIM_OCInitStructure);
  40.     TIM_OCInitStructure.TIM_OCMode      = TIM_OCMode_PWM1;
  41.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  42.     TIM_OCInitStructure.TIM_Pulse       = 5;
  43.     TIM_OCInitStructure.TIM_OCPolarity  = TIM_OCPolarity_High;
  44.     TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
  45.     TIM_OC2Init(TIM1, &TIM_OCInitStructure);

  46.     TIM_Cmd(TIM1, ENABLE);

  47.     RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

  48.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_3);

  49.     GPIO_StructInit(&GPIO_InitStructure);
  50.     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;
  51.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  52.     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
  53.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  54.     TIM_CtrlPWMOutputs(TIM1, ENABLE);
  55. }


  56. /*******************************************************************************
  57. * @brief      
  58. * @param      
  59. * @retval      
  60. * @attention   
  61. *******************************************************************************/
  62. void IRM_InitTIM2(void)
  63. {
  64.     TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  65.     NVIC_InitTypeDef        NVIC_InitStructure;

  66.     RCC_ClocksTypeDef  RCC_Clocks;
  67.     RCC_GetClocksFreq(&RCC_Clocks);

  68.     RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);

  69.     TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  70.     TIM_TimeBaseStructure.TIM_Prescaler         = (RCC_Clocks.PCLK1_Frequency * 2 / 1000000 - 1);
  71.     TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
  72.     TIM_TimeBaseStructure.TIM_Period            = (10 - 1);
  73.     TIM_TimeBaseStructure.TIM_ClockDivision     = TIM_CKD_DIV1;
  74.     TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  75.     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

  76.     TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  77.     TIM_ITConfig(TIM2, TIM_IT_Update,  ENABLE);

  78.     TIM_Cmd(TIM2, ENABLE);

  79.     NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
  80.     NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
  81.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  82.     NVIC_Init(&NVIC_InitStructure);
  83. }


  84. uint16_t IRM_TX_Buffer[70] =
  85. {
  86.     900,        /* 9.0ms */
  87.     450,        /* 4.5ms */
  88.     56, 56,     /* Bit01 */
  89.     56, 56,     /* Bit02 */
  90.     56, 56,     /* Bit03 */
  91.     56, 56,     /* Bit04 */
  92.     56, 56,     /* Bit05 */
  93.     56, 56,     /* Bit06 */
  94.     56, 56,     /* Bit07 */
  95.     56, 56,     /* Bit08 */
  96.     56, 56,     /* Bit09 */
  97.     56, 56,     /* Bit10 */
  98.     56, 56,     /* Bit11 */
  99.     56, 56,     /* Bit12 */
  100.     56, 56,     /* Bit13 */
  101.     56, 56,     /* Bit14 */
  102.     56, 56,     /* Bit15 */
  103.     56, 56,     /* Bit16 */
  104.     56, 56,     /* Bit17 */
  105.     56, 56,     /* Bit18 */
  106.     56, 56,     /* Bit19 */
  107.     56, 56,     /* Bit20 */
  108.     56, 56,     /* Bit21 */
  109.     56, 56,     /* Bit22 */
  110.     56, 56,     /* Bit23 */
  111.     56, 56,     /* Bit24 */
  112.     56, 56,     /* Bit25 */
  113.     56, 56,     /* Bit26 */
  114.     56, 56,     /* Bit27 */
  115.     56, 56,     /* Bit28 */
  116.     56, 56,     /* Bit29 */
  117.     56, 56,     /* Bit30 */
  118.     56, 56,     /* Bit31 */
  119.     56, 56,     /* Bit32 */
  120.     56, 3000,   /* STOP  */
  121.     0,  0,
  122. };

  123. uint8_t  IRM_TX_Index  = 0;

  124. uint8_t  IRM_TX_Finish = 0;
  125. uint8_t  IRM_TX_Enable = 0;

  126. uint32_t IRM_TX_Tick   = 0;

  127. /*******************************************************************************
  128. * @brief      
  129. * @param      
  130. * @retval      
  131. * @attention   
  132. *******************************************************************************/
  133. void TIM2_IRQHandler(void)
  134. {
  135.     if(IRM_TX_Enable == 1)
  136.     {
  137.         if(IRM_TX_Tick++ >= IRM_TX_Buffer[IRM_TX_Index])
  138.         {
  139.             IRM_TX_Tick = 0;

  140.             if(++IRM_TX_Index >= 68)
  141.             {
  142.                 IRM_TX_Enable = 0;
  143.                 IRM_TX_Finish = 1;
  144.             }
  145.             else
  146.             {
  147.                 if(!GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2))
  148.                 {
  149.                     GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_SET);
  150.                 }
  151.                 else
  152.                 {
  153.                     GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_RESET);
  154.                 }
  155.             }
  156.         }
  157.     }

  158.     TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  159. }


  160. /*******************************************************************************
  161. * @brief      
  162. * @param      
  163. * @retval      
  164. * @attention   
  165. *******************************************************************************/
  166. void IRM_SendData(uint8_t *Data)
  167. {
  168.     for(uint8_t i = 0; i < 4; i++)
  169.     {
  170.         for(uint8_t j = 0; j < 8; j++)
  171.         {
  172.             if(Data[i] & (0x80 >> j))
  173.             {
  174.                 IRM_TX_Buffer[i * 16 + j * 2 + 3] = 56 * 3;
  175.             }
  176.             else
  177.             {
  178.                 IRM_TX_Buffer[i * 16 + j * 2 + 3] = 56;
  179.             }
  180.         }
  181.     }

  182.     GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_RESET);

  183.     IRM_TX_Index  = 0;
  184.     IRM_TX_Finish = 0;
  185.     IRM_TX_Enable = 1;

  186.     while(IRM_TX_Finish == 0);
  187. }

通过调用NEC编码发送函数,在NEC编码发出的同时,NEC也同样接收到了编码数据,对其进行解析和打印输出,实际运行效果如下所示:

758396222c60bab227.png


4、基于MM32带自学功能的红外遥控器

通过外接4*4矩阵按键,如下图所示:

32906222c62b6fdee.png

实现功能:系统启动后,从MCU内部FLASH存储空间读取已经存储的按键对应的红外编码数据,在按下相对应的按键后,通过红外发射头发送NEC数据;按下板子上的KEY按键后,可以在按键发码和学习其它遥控器编码这两个工作模式之间切换,在学习编码的过程中,先按下一对应的按键,确认是哪个按键要学习,然后再用其它的遥控器对着开发板按键发送NEC编码,如果连接上USB调试接口,还会有对应的操作提示和过程打印信息,在学习完成后,再通过板载的KEY按键切换到正常发码模式;在进入学习模式的时候,板载的LED灯处于闪烁的状态,用于提示当前的工作状态,在退出学习模式时,LED将会熄灭,同时会将刚刚学到的编码保存到MCU的内部FLASH存储空间,这样就可以实现掉电保存了;

具体的关键实现代码如下所示:

基于MCU内部FLASH加载和存储红外遥控器NEC编码数据:
  1. /* Private define ------------------------------------------------------------*/
  2. #define IRMC_FLASH_ADDRESS  (0x08000000 + 60 * 1024)


  3. /* Private macro -------------------------------------------------------------*/


  4. /* Private variables ---------------------------------------------------------*/
  5. uint8_t IRMC_StudyState = 0;
  6. uint8_t IRMC_StudyIndex = 0;


  7. /* Private variables ---------------------------------------------------------*/
  8. uint8_t IRMC_CodeTable[16][4] =
  9. {
  10.     {0x12, 0x34, 0x45, 0x78},
  11.     {0x00, 0x00, 0x00, 0x00},
  12.     {0x00, 0x00, 0x00, 0x00},
  13.     {0x00, 0x00, 0x00, 0x00},
  14.     {0x00, 0x00, 0x00, 0x00},
  15.     {0x00, 0x00, 0x00, 0x00},
  16.     {0x00, 0x00, 0x00, 0x00},
  17.     {0x00, 0x00, 0x00, 0x00},
  18.     {0x00, 0x00, 0x00, 0x00},
  19.     {0x00, 0x00, 0x00, 0x00},
  20.     {0x00, 0x00, 0x00, 0x00},
  21.     {0x00, 0x00, 0x00, 0x00},
  22.     {0x00, 0x00, 0x00, 0x00},
  23.     {0x00, 0x00, 0x00, 0x00},
  24.     {0x00, 0x00, 0x00, 0x00},
  25.     {0x00, 0x00, 0x00, 0x00},
  26. };


  27. /* Private function prototypes -----------------------------------------------*/
  28. /* Private functions ---------------------------------------------------------*/


  29. /* Exported variables --------------------------------------------------------*/
  30. /* Exported function prototypes ----------------------------------------------*/


  31. /*******************************************************************************
  32. * @brief      
  33. * @param      
  34. * @retval      
  35. * @attention   
  36. *******************************************************************************/
  37. void IRMC_Load(void)
  38. {
  39.     printf("\r\n%s", __FUNCTION__);

  40.     for(uint8_t i = 0; i < 16; i++)
  41.     {
  42.         for(uint8_t j = 0; j < 4; j++)
  43.         {
  44.             IRMC_CodeTable[i][j] = *(volatile uint8_t *)(IRMC_FLASH_ADDRESS + i * 4 + j);
  45.         }
  46.     }

  47.     for(uint8_t i = 0; i < 16; i++)
  48.     {
  49.         printf("\r\nNEC[%02d] : 0x%02x, 0x%02x, 0x%02x, 0x%02x",
  50.                 i,
  51.                 IRMC_CodeTable[i][0], IRMC_CodeTable[i][1],
  52.                 IRMC_CodeTable[i][2], IRMC_CodeTable[i][3]);
  53.     }

  54.     printf("\r\n");
  55. }


  56. /*******************************************************************************
  57. * @brief      
  58. * @param      
  59. * @retval      
  60. * @attention   
  61. *******************************************************************************/
  62. void IRMC_Save(void)
  63. {
  64.     uint32_t     Data;
  65.     FLASH_Status Status;

  66.     FLASH_Unlock();
  67.     FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);

  68.     Status = FLASH_ErasePage(IRMC_FLASH_ADDRESS);
  69.     FLASH_ClearFlag(FLASH_FLAG_EOP);

  70.     if(Status == FLASH_COMPLETE)
  71.     {
  72.         for(uint8_t i = 0; i < 16; i++)
  73.         {
  74.             Data <<= 8; Data  = IRMC_CodeTable[i][3];
  75.             Data <<= 8; Data |= IRMC_CodeTable[i][2];
  76.             Data <<= 8; Data |= IRMC_CodeTable[i][1];
  77.             Data <<= 8; Data |= IRMC_CodeTable[i][0];

  78.             Status = FLASH_ProgramWord(IRMC_FLASH_ADDRESS + i * 4, Data);
  79.             FLASH_ClearFlag(FLASH_FLAG_EOP);

  80.             if(Data != *(uint32_t *)(IRMC_FLASH_ADDRESS + i * 4))
  81.             {
  82.                 printf("\r\nIRMC Save Error!!!");
  83.             }
  84.         }        
  85.     }

  86.     FLASH_Lock();
  87. }

按键和矩阵按键识别及应用处理

  1. 由于字数限制,详见附件程序

红外接收处理函数实现部分:
  1. 由于字数限制,详见附件程序

实际操作演示过程:

上电读取NEC按键编码:

655776222c65b251ae.png

进入学习模式,KEY1学习按键编码,然后退出学习模式:

469866222c669996f2.png

系统重新上电后,加载按键红外编码,刚刚学习到的已经被保存下来了:

690406222c67c793e4.png

正常模式下,按键发送红外编码数据:

335916222c68b67917.png


附件:
相关数据手册: IR383-A.PDF (192.91 KB, 下载次数: 14) IRM-3638MF56.PDF (716.2 KB, 下载次数: 13)

硬件原理图: Schematic_MM32F0133C6P-IRM_2022-03-01.pdf (122.17 KB, 下载次数: 34)

工程源代码: IRM_Controller.zip (706.12 KB, 下载次数: 38)

打赏榜单

21小跑堂 打赏了 150.00 元 2022-03-09
理由:恭喜通过原创奖文章审核!请多多加油哦!

guijial511 发表于 2022-3-6 12:45 来自手机 | 显示全部楼层
感谢分享,学习了。
xxdcq 发表于 2022-3-11 15:09 | 显示全部楼层
仅限NEC的编码毫无用处
 楼主| xld0932 发表于 2022-3-11 17:04 | 显示全部楼层
本帖最后由 xld0932 于 2022-3-11 17:12 编辑
xxdcq 发表于 2022-3-11 15:09
仅限NEC的编码毫无用处

MCU都是通用的,有没有用看你怎么设计了
xiaoqi976633690 发表于 2022-3-16 10:55 | 显示全部楼层
谢谢分享。收藏学习了
probedog 发表于 2022-3-16 11:23 | 显示全部楼层
这贴非常有借鉴价值啊,必须收藏
carpsnow 发表于 2022-3-30 15:23 | 显示全部楼层
nec只是编码吧?
tpgf 发表于 2022-4-1 16:29 | 显示全部楼层
第一次知道红外抄表
wowu 发表于 2022-4-1 16:43 | 显示全部楼层
这种数据稳定吗
xiaoqizi 发表于 2022-4-1 16:48 | 显示全部楼层
对应用环境有要求吗
木木guainv 发表于 2022-4-1 16:54 | 显示全部楼层
一般这种怎么供电呢
磨砂 发表于 2022-4-1 17:00 | 显示全部楼层
对楼主的源代码很感兴趣 谢谢啊
晓伍 发表于 2022-4-1 17:06 | 显示全部楼层
算是全自动抄表吗
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:King.Xu

77

主题

3023

帖子

38

粉丝
快速回复 在线客服 返回列表 返回顶部
个人签名:King.Xu

77

主题

3023

帖子

38

粉丝
快速回复 在线客服 返回列表 返回顶部