发新帖本帖赏金 150.00元(功能说明)我要提问
返回列表
打印
[MM32生态]

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

[复制链接]
1484|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 xld0932 于 2022-3-5 10:31 编辑

#申请原创#   @21小跑堂

在之前的分享中有介绍基于MM32IrDA红外通讯功能,IrDA其本身不具体载波通讯的功能,在干扰较大或者远距离通讯时,会有明显的不足;所以本文分享了红外通讯的另外一种实现方式:红外载波通讯。常用的红外载波频率有36kHz38kHz40kHz等等,对于红外接收头来说,当接收到载波信号时会解析成低电平,当没有载波信号时会解析成高电平,通过高低电平的组合切换,实现数据位传输,从而实现数据通讯。本文包含如下几个小节的内容:
  • 基于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或者1200UART的传输速率并不是因为UART性能的问题,而是因为38kHz的载波频率和红外接收头的特性所决定的,为了提升UART红外通讯速率,可以选择性能更好的红外接收头。



原理图设计:

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




PCB设计:



焊接成品:



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

/*******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]      
* @param      
* @retval      
* [url=home.php?mod=space&uid=93590]@Attention[/url]   
*******************************************************************************/
void IRM_InitTIM1(void)
{
    GPIO_InitTypeDef        GPIO_InitStructure;
    TIM_OCInitTypeDef       TIM_OCInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_ClocksTypeDef  RCC_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:

/*******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]      
* @param      
* @retval      
* [url=home.php?mod=space&uid=93590]@Attention[/url]   
*******************************************************************************/
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协议是红外遥控器协议中的一种,它由引导码、地址码、地址反码、命令码和命令反码组成;逻辑12.25ms,其中脉冲时间为560us;逻辑01.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_t  IRM_RX_Buffer[32];
uint8_t  IRM_RX_Index  = 0;
uint8_t  IRM_RX_Repeat = 0;

uint8_t  IRM_RX_Complete = 0;
uint32_t IRM_RX_Overtime = 0;

uint8_t  IRM_RX_Step = 0;
uint8_t  IRM_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[IRM_RX_Index++] = 0;
            }
            else if(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)   && (t > 119) && (t < 219))
            {
                IRM_RX_Buffer[IRM_RX_Index++] = 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_ClocksTypeDef  RCC_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_ClocksTypeDef  RCC_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_ClocksTypeDef  RCC_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[70] =
{
    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_t  IRM_TX_Index  = 0;

uint8_t  IRM_TX_Finish = 0;
uint8_t  IRM_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_Index])
        {
            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[i] & (0x80 >> j))
            {
                IRM_TX_Buffer[i * 16 + j * 2 + 3] = 56 * 3;
            }
            else
            {
                IRM_TX_Buffer[i * 16 + j * 2 + 3] = 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[16][4] =
{
    {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[i][j] = *(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[i][0], IRMC_CodeTable[i][1],
                IRMC_CodeTable[i][2], IRMC_CodeTable[i][3]);
    }

    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[i][3];
            Data <<= 8; Data |= IRMC_CodeTable[i][2];
            Data <<= 8; Data |= IRMC_CodeTable[i][1];
            Data <<= 8; Data |= IRMC_CodeTable[i][0];

            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学习按键编码,然后退出学习模式:



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



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




附件:
相关数据手册: IR383-A.PDF (192.91 KB) IRM-3638MF56.PDF (716.2 KB)

硬件原理图: Schematic_MM32F0133C6P-IRM_2022-03-01.pdf (122.17 KB)

工程源代码: IRM_Controller.zip (706.12 KB)

使用特权

评论回复

打赏榜单

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都是通用的,有没有用看你怎么设计了

使用特权

评论回复
5
xiaoqi976633690| | 2022-3-16 10:55 | 只看该作者
谢谢分享。收藏学习了

使用特权

评论回复
6
probedog| | 2022-3-16 11:23 | 只看该作者
这贴非常有借鉴价值啊,必须收藏

使用特权

评论回复
7
carpsnow| | 2022-3-30 15:23 | 只看该作者
nec只是编码吧?

使用特权

评论回复
8
tpgf| | 2022-4-1 16:29 | 只看该作者
第一次知道红外抄表

使用特权

评论回复
9
wowu| | 2022-4-1 16:43 | 只看该作者
这种数据稳定吗

使用特权

评论回复
10
xiaoqizi| | 2022-4-1 16:48 | 只看该作者
对应用环境有要求吗

使用特权

评论回复
11
木木guainv| | 2022-4-1 16:54 | 只看该作者
一般这种怎么供电呢

使用特权

评论回复
12
磨砂| | 2022-4-1 17:00 | 只看该作者
对楼主的源代码很感兴趣 谢谢啊

使用特权

评论回复
13
晓伍| | 2022-4-1 17:06 | 只看该作者
算是全自动抄表吗

使用特权

评论回复
发新帖 本帖赏金 150.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

67

主题

2993

帖子

29

粉丝