xld0932 发表于 2022-3-5 10:31

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

本帖最后由 xld0932 于 2022-3-5 10:31 编辑

#申请原创#   @21小跑堂

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

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


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微秒。


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


所以结合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或者1200,UART的传输速率并不是因为UART性能的问题,而是因为38kHz的载波频率和红外接收头的特性所决定的,为了提升UART红外通讯速率,可以选择性能更好的红外接收头。


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



PCB设计:


焊接成品:


UART红外通讯代码实现:配置TIM输出38kHz载波频率:
/*******************************************************************************
* @brief      
* @param      
* @retval      
* @Attention   
*******************************************************************************/
void IRM_InitTIM1(void)
{
    GPIO_InitTypeDef      GPIO_InitStructure;
    TIM_OCInitTypeDef       TIM_OCInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_ClocksTypeDefRCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Prescaler         = (RCC_Clocks.PCLK2_Frequency / 380000 - 1);
    TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period            = (10 - 1);
    TIM_TimeBaseStructure.TIM_ClockDivision   = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode      = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse       = 5;
    TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);

    TIM_Cmd(TIM1, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_3);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
UART红外通讯代码实现:配置UART工作在收发模式,参数为4800/N/8/1:
/*******************************************************************************
* @brief      
* @param      
* @retval      
* @Attention   
*******************************************************************************/
void IRM_InitUART(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    UART_InitTypeDef UART_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART2, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = UART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.UART_BaudRate            = 4800;
    UART_InitStructure.UART_WordLength          = UART_WordLength_8b;
    UART_InitStructure.UART_StopBits            = UART_StopBits_1;
    UART_InitStructure.UART_Parity            = UART_Parity_No;
    UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
    UART_InitStructure.UART_Mode                = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(UART2, &UART_InitStructure);

    UART_ITConfig(UART2, UART_IT_RXIEN, ENABLE);
    UART_Cmd(UART2, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void UART2_IRQHandler(void)
{
    uint8_t Data = 0;

    if(UART_GetITStatus(UART2, UART_IT_RXIEN) != RESET)
    {
      Data = UART_ReceiveData(UART2);
      UART_ClearITPendingBit(UART2, UART_IT_RXIEN);

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


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRM_UART_SendData(uint8_t Data)
{
    UART_SendData(UART2, Data);
    while(UART_GetFlagStatus(UART2, UART_IT_TXIEN) == RESET);
}
调试演示结果:
如下图是红外发送0x55数据时的UART调制波形:


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



2、基于MM32红外遥控器NEC解码实现
NEC协议是红外遥控器协议中的一种,它由引导码、地址码、地址反码、命令码和命令反码组成;逻辑1为2.25ms,其中脉冲时间为560us;逻辑0为1.12ms,其中脉冲时间为560us;如下图所示:


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


NEC协议格式:


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


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

uint8_tIRM_RX_Complete = 0;
uint32_t IRM_RX_Overtime = 0;

uint8_tIRM_RX_Step = 0;
uint8_tIRM_RX_Flag = 0;
uint32_t IRM_RX_Tick = 0;


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRM_InitEXTI(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource3);

    EXTI_StructInit(&EXTI_InitStructure);
    EXTI_InitStructure.EXTI_Line    = EXTI_Line3;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI2_3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0x03;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EXTI2_3_IRQHandler(void)
{
    uint32_t t = 0;

    t = IRM_RX_Tick;
    IRM_RX_Tick = 0;

    IRM_RX_Overtime = 0;

    switch(IRM_RX_Step)
    {
      case 0:
            if(   GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)    && (t > 850) && (t < 950))
            {
                IRM_RX_Step = 1;
            }
            break;

      case 1:
            if(   !GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t > 400) && (t < 500))
            {
                IRM_RX_Step = 2;    IRM_RX_Index = 0;   IRM_RX_Flag = 0;
            }
            else if(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t > 175) && (t < 275))
            {
                IRM_RX_Step = 4;
            }
            break;

      case 2:
            if(   GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)    && (t >46) && (t <66))
            {
                IRM_RX_Step = 3;
            }
            break;

      case 3:
            if(   !GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t >46) && (t <66))
            {
                IRM_RX_Buffer = 0;
            }
            else if(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t > 119) && (t < 219))
            {
                IRM_RX_Buffer = 1;
            }

            if(IRM_RX_Index == 32)
            {
                IRM_RX_Step = 0;    IRM_RX_Repeat = 0;IRM_RX_Flag = 1;
            }
            else
            {
                IRM_RX_Step = 2;
            }
            break;

      case 4:
            if(   GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)    && (t >46) && (t <66))
            {
                IRM_RX_Step = 0;    IRM_RX_Repeat++;
            }
            break;

      default:
            break;
    }

    EXTI_ClearITPendingBit(EXTI_Line3);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRM_InitTIM3(void)
{
    NVIC_InitTypeDef      NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_ClocksTypeDefRCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);

    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM3, ENABLE);

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

    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    TIM_ITConfig(TIM3, TIM_IT_Update,ENABLE);

    TIM_Cmd(TIM3, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void TIM3_IRQHandler(void)
{
    IRM_RX_Tick++;

    if(IRM_RX_Complete == 0)
    {
      if(IRM_RX_Overtime++ > 2000)
      {
            IRM_RX_Complete = 1;    IRM_RX_Step = 0;
      }
    }

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



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

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_SET);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRM_InitTIM1(void)
{
    GPIO_InitTypeDef      GPIO_InitStructure;
    TIM_OCInitTypeDef       TIM_OCInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_ClocksTypeDefRCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Prescaler         = (RCC_Clocks.PCLK2_Frequency / 380000 - 1);
    TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period            = (10 - 1);
    TIM_TimeBaseStructure.TIM_ClockDivision   = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode      = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse       = 5;
    TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);

    TIM_Cmd(TIM1, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_3);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_CtrlPWMOutputs(TIM1, ENABLE);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRM_InitTIM2(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef      NVIC_InitStructure;

    RCC_ClocksTypeDefRCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);

    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);

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

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);

    TIM_Cmd(TIM2, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


uint16_t IRM_TX_Buffer =
{
    900,      /* 9.0ms */
    450,      /* 4.5ms */
    56, 56,   /* Bit01 */
    56, 56,   /* Bit02 */
    56, 56,   /* Bit03 */
    56, 56,   /* Bit04 */
    56, 56,   /* Bit05 */
    56, 56,   /* Bit06 */
    56, 56,   /* Bit07 */
    56, 56,   /* Bit08 */
    56, 56,   /* Bit09 */
    56, 56,   /* Bit10 */
    56, 56,   /* Bit11 */
    56, 56,   /* Bit12 */
    56, 56,   /* Bit13 */
    56, 56,   /* Bit14 */
    56, 56,   /* Bit15 */
    56, 56,   /* Bit16 */
    56, 56,   /* Bit17 */
    56, 56,   /* Bit18 */
    56, 56,   /* Bit19 */
    56, 56,   /* Bit20 */
    56, 56,   /* Bit21 */
    56, 56,   /* Bit22 */
    56, 56,   /* Bit23 */
    56, 56,   /* Bit24 */
    56, 56,   /* Bit25 */
    56, 56,   /* Bit26 */
    56, 56,   /* Bit27 */
    56, 56,   /* Bit28 */
    56, 56,   /* Bit29 */
    56, 56,   /* Bit30 */
    56, 56,   /* Bit31 */
    56, 56,   /* Bit32 */
    56, 3000,   /* STOP*/
    0,0,
};

uint8_tIRM_TX_Index= 0;

uint8_tIRM_TX_Finish = 0;
uint8_tIRM_TX_Enable = 0;

uint32_t IRM_TX_Tick   = 0;

/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void TIM2_IRQHandler(void)
{
    if(IRM_TX_Enable == 1)
    {
      if(IRM_TX_Tick++ >= IRM_TX_Buffer)
      {
            IRM_TX_Tick = 0;

            if(++IRM_TX_Index >= 68)
            {
                IRM_TX_Enable = 0;
                IRM_TX_Finish = 1;
            }
            else
            {
                if(!GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2))
                {
                  GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_SET);
                }
                else
                {
                  GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_RESET);
                }
            }
      }
    }

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRM_SendData(uint8_t *Data)
{
    for(uint8_t i = 0; i < 4; i++)
    {
      for(uint8_t j = 0; j < 8; j++)
      {
            if(Data & (0x80 >> j))
            {
                IRM_TX_Buffer = 56 * 3;
            }
            else
            {
                IRM_TX_Buffer = 56;
            }
      }
    }

    GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_RESET);

    IRM_TX_Index= 0;
    IRM_TX_Finish = 0;
    IRM_TX_Enable = 1;

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



4、基于MM32带自学功能的红外遥控器
通过外接4*4矩阵按键,如下图所示:


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


/* Private macro -------------------------------------------------------------*/


/* Private variables ---------------------------------------------------------*/
uint8_t IRMC_StudyState = 0;
uint8_t IRMC_StudyIndex = 0;


/* Private variables ---------------------------------------------------------*/
uint8_t IRMC_CodeTable =
{
    {0x12, 0x34, 0x45, 0x78},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
    {0x00, 0x00, 0x00, 0x00},
};


/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/


/* Exported variables --------------------------------------------------------*/
/* Exported function prototypes ----------------------------------------------*/


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRMC_Load(void)
{
    printf("\r\n%s", __FUNCTION__);

    for(uint8_t i = 0; i < 16; i++)
    {
      for(uint8_t j = 0; j < 4; j++)
      {
            IRMC_CodeTable = *(volatile uint8_t *)(IRMC_FLASH_ADDRESS + i * 4 + j);
      }
    }

    for(uint8_t i = 0; i < 16; i++)
    {
      printf("\r\nNEC[%02d] : 0x%02x, 0x%02x, 0x%02x, 0x%02x",
                i,
                IRMC_CodeTable, IRMC_CodeTable,
                IRMC_CodeTable, IRMC_CodeTable);
    }

    printf("\r\n");
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void IRMC_Save(void)
{
    uint32_t   Data;
    FLASH_Status Status;

    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);

    Status = FLASH_ErasePage(IRMC_FLASH_ADDRESS);
    FLASH_ClearFlag(FLASH_FLAG_EOP);

    if(Status == FLASH_COMPLETE)
    {
      for(uint8_t i = 0; i < 16; i++)
      {
            Data <<= 8; Data= IRMC_CodeTable;
            Data <<= 8; Data |= IRMC_CodeTable;
            Data <<= 8; Data |= IRMC_CodeTable;
            Data <<= 8; Data |= IRMC_CodeTable;

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

            if(Data != *(uint32_t *)(IRMC_FLASH_ADDRESS + i * 4))
            {
                printf("\r\nIRMC Save Error!!!");
            }
      }      
    }

    FLASH_Lock();
}
按键和矩阵按键识别及应用处理:
由于字数限制,详见附件程序
红外接收处理函数实现部分:由于字数限制,详见附件程序
实际操作演示过程:
上电读取NEC按键编码:


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


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


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



附件:相关数据手册:
硬件原理图:
工程源代码:

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

算是全自动抄表吗
页: [1]
查看完整版本: 基于MM32在红外抄表及红外遥控器中的应用