使用PY003基于外部中断+定时器的方式实现NEC红外解码

写在前边

最近项目用到一款遥控器是38K红外载波,NEC协议的,找了很多帖子有看到用外部中断下降沿判断(但可惜判定数据的方式是while在外部中断里面死等的),有看到用100us定时器定时刷来判断,感觉都不太适合用在我这个工程里,最后没办法自己写了一个,使用没问题,不确定有没有bug。暂且记录着。

配置定时器: 10ms定时中断一次

void TIM3_Init(void)
{
    /* TIM3 配置*/
    Tim3Handle.Instance = TIM3;                                           /* 选择TIM3 */
    Tim3Handle.Init.Period            = 10000 - 1;                        /* 自动重装载值 */
    Tim3Handle.Init.Prescaler         = 24 - 1;                           /* 预分频为1000-1 */
    Tim3Handle.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;           /* 时钟不分频 */
    Tim3Handle.Init.CounterMode       = TIM_COUNTERMODE_UP;               /* 向上计数 */
    Tim3Handle.Init.RepetitionCounter = 1 - 1;                            /* 不重复计数 */
    Tim3Handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;   /* 自动重装载寄存器没有缓冲 */
    if (HAL_TIM_Base_Init(&Tim3Handle) != HAL_OK)                         /* TIM3初始化 */
    {
        APP_ErrorHandler();
    }

    if (HAL_TIM_Base_Start_IT(&Tim3Handle) != HAL_OK)                     /* TIM3使能启动,并使能中断 */
    {
        APP_ErrorHandler();
    }
}

配置GPIO:GPIO_InitStruct.Mode这里将IO的外部中断配置成上升沿+下降沿中断

void GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();                           /* GPIOA时钟使能 */

	//PA5 红外解码输入
	GPIO_InitStruct.Pin   = GPIO_PIN_5 ;                    /* PIN脚 */
	GPIO_InitStruct.Mode  = GPIO_MODE_IT_RISING_FALLING ;	/* 工作模式 */
	GPIO_InitStruct.Pull  = GPIO_PULLUP ;	                /* 拉电阻 */
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH ;     /* IO时钟速度 */
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    

}

主函数中开启定时器和外部中断:(因为是PA5 所以选外部中断5 外部中断4-15都是一个入口地址)

	TIM3_Init();
    HAL_TIM_Base_Stop_IT( &Tim3Handle );    //先关闭TIM3,等收到一个下降沿之后再打开
    TIM3->CNT = 0;                          //清零定时器,为下一次接受红外做准备
    
    /* 使能NVIC GPIO外部中断 */
    HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
    HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0);

中断服务函数:
定时器的:

/**
  * @brief This function handles TIM3 Interrupt .
  */
void TIM3_IRQHandler(void)
{
    __HAL_TIM_CLEAR_IT(&Tim3Handle, TIM_IT_UPDATE);         //最长10ms溢出中断,如果是超过10ms的溢出中断,说明此前为空闲,清零所有变量

    HAL_TIM_Base_Stop( &Tim3Handle );						//如果进入了定时器3的中断服务函数,说明是10ms溢出了,直接停止TIM3定时器
    TIM3->CNT = 0;
    IR.IR_decode_bit = 0; //电平计数超过了码元长度 清除位数计数
    IR.IR_code = 0;
    IR.IR_event = 0;

    IR.IR_code_get = 0;
    IR.low_8bit = 0;
    IR.hig_8bit = 0;
    IR.rece_ok = 0;
}

外部中断5的:

/**
  * @brief This function handles EXIT Interrupt .
  */
void EXTI4_15_IRQHandler(void)
{
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_5);

    IR.timer_count = TIM3->CNT;     //每次进外部中断,先保存当前的计数值 24M主频,24分频,CNT加1的时间为1us
    TIM3->CNT = 0;                  //清零寄存器
    if( GPIOA->IDR & 0x00000020 )   //检查PA5电平 上升沿    IR.timer_count的值为上一个下降沿到这次的上升沿,中间的间隔时间(即低电平时间)
    {
        if(IR.IR_event == 0 && IR.f_head == 0)          //既没有开始接受01数据,也没有开始接受头码,那么进入接收头码判断
        {   
            if( IR.timer_count > 8900 && IR.timer_count < 9100)     //头码为9ms的低电平,允许误差100us
            {
                IR.f_head = 1;                                      //如果符合,开始判定头码
            }
            else if(IR.timer_count > 9100)                          //如果头码已经大于9.1ms 认为是错误
            {
                IR.f_head = 0;
                IR.IR_decode_bit = 0;                               //电平计数超过了码元长度 清除位数计数
                IR.IR_code = 0;
                IR.IR_event = 0;

                IR.IR_code_get = 0;
                IR.low_8bit = 0;
                IR.hig_8bit = 0;
                IR.rece_ok = 0;
            }
        }
        else if(IR.IR_event == 1)                                   //如果头码判定正确,开始接受01数据
        {
            if(IR.timer_count > 500 && IR.timer_count < 650)        //所有的01数据,开头都是0.56ms的低电平
            {
                IR.get_bit = 1;                                     //如果符合,那么开始根据后面的高电平时间来判定此次为0还是1
            }
            else                                                    //否则清除所有数据
            {
                IR.f_head = 0;
                IR.IR_decode_bit = 0; //电平计数超过了码元长度 清除位数计数
                IR.IR_code = 0;
                IR.IR_event = 0;
                
                IR.IR_code_get = 0;
                IR.low_8bit = 0;
                IR.hig_8bit = 0;
                IR.rece_ok = 0;
            }
        }
    }
    else                                                            //上一个上升沿到这次的下降沿,之间的时间间隔(即高电平时间)
    {
        if(IR.f_head==1)                                            //如果9ms的低电平成立了,开始判断是否有4.5ms的高电平时间
        {
            if( IR.timer_count > 4400 && IR.timer_count < 4600)
            {
                IR.IR_event = 1;                                    //4.5ms成立,认为此次的头码成立,开始接收01数据
            }
            else
            {
                IR.IR_decode_bit = 0;                               //电平计数超过了码元长度 清除位数计数
                IR.IR_code = 0;
                IR.IR_event = 0;

                IR.IR_code_get = 0;
                IR.low_8bit = 0;
                IR.hig_8bit = 0;
                IR.rece_ok = 0;
            }
            IR.f_head = 0;                                          //清除接受头码标志
        }

        if(IR.IR_event == 1)                                        //接收数据内容成立
        {
            if(IR.get_bit == 1)                                     //如果0.56ms的低电平已经成立,那么开始判断这次的高电平时间
            {
                if(IR.timer_count > 500 && IR.timer_count < 650)    //0.56ms低+0.56ms高,说明传输的数据为 0 
                {
					IR.IR_code <<= 1;
                    IR.IR_decode_bit ++;
                }
                else if(IR.timer_count > 1500 && IR.timer_count < 1800)     //0.56ms低+1.68s高,说明传输的数据为 1
                {
					IR.IR_code <<= 1;   
					IR.IR_code ++;                                  //数据位置1
                    IR.IR_decode_bit ++;
                }
                else                                                //如果0.56ms的低电平后接着的是不符合要求的高电平时间,那么清零所有数据
                {
                    IR.f_head = 0;
                    IR.IR_decode_bit = 0; //电平计数超过了码元长度 清除位数计数
                    IR.IR_event = 0;
                    IR.IR_code = 0;

                    IR.IR_code_get = 0;
                    IR.low_8bit = 0;
                    IR.hig_8bit = 0;
                    IR.rece_ok = 0;
                }

                if( IR.IR_decode_bit == 32 )                        //接收了32位数据之后,开始判断内容
                {
                    IR.IR_code_get = IR.IR_code;
                    
                    IR.low_8bit = IR.IR_code_get & 0x000000FF;      //对于本项目而言之后低16位数据有用,其中15-8为数据,7-0为数据反码
                    IR.hig_8bit = (IR.IR_code_get & 0x0000FF00)>>8;
                    IR.low_8bit ^= 0xFF;                            //拿反码异或1 得到取反数据

                    if( IR.low_8bit == IR.hig_8bit )                //判定数据跟数据反码是否相符,如果相符则为有用数据,否则舍弃所有数据
                    {
                        IR.rece_ok = 1;
                    } 
                    else
                    {
                        IR.rece_ok = 0;
                        IR.low_8bit = 0;
                        IR.hig_8bit = 0;
                        IR.IR_code_get = 0;
                    }

                    IR.IR_decode_bit = 0;
                    IR.IR_code = 0;
                    IR.f_head = 0;
                    IR.IR_event = 0;
                }

                IR.get_bit = 0;                                     //每次处理完一位数据之后,清楚标志位,待下次0.56ms低电平成立之后再次判断
            }
        }
    }

    HAL_TIM_Base_Start_IT( &Tim3Handle );                           //每次进入外部中断都要启动计数器
}

IR结构体声明在main.h文件中:

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "py32f0xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* Private defines -----------------------------------------------------------*/

#define IR_CODE_GET_POWER (uint8_t)(0xE2)
#define IR_CODE_GET_SPEED (uint8_t)(0xA2)
#define IR_CODE_GET_TIMER (uint8_t)(0xA8)
#define IR_CODE_GET_SHAKE (uint8_t)(0x18)



/* Exported variables prototypes ---------------------------------------------*/
extern UART_HandleTypeDef UartHandle;
extern __IO ITStatus UartReady;
extern TIM_HandleTypeDef TimHandle,Tim3Handle,Tim1Handle, Tim17Handle;
extern EXTI_HandleTypeDef exti_handle;
extern GPIO_InitTypeDef GPIO_InitStruct;

typedef struct IR_decode
{
    uint32_t  timer_count; //定时器数据
    uint32_t  IR_code;     //读取到的红外码 
    uint32_t  IR_code_get;
    uint32_t  IR_decode_bit;//解码接收位数
    uint32_t  IR_event;    //红外输出事件
    uint8_t   data_recving_flag;//红外接收中的标志
    uint8_t   f_head;
    uint8_t   get_bit;
    uint8_t   low_8bit;
    uint8_t   hig_8bit;
    uint8_t   rece_ok;

    // int16_t   *send_data;//红外发送码数据的首地址
    // uint8_t   send_code_bits;//红外发送位
    // uint8_t   send_rep_times;//红外码重复发送次数
    // uint8_t   send_times;//切换档位所需要次数
    // uint8_t   send_delay;//多包间隔时间
    // uint8_t   data_send_flag;//红外发送标志 

    uint8_t   printf_datalen;

}IR_struct;

extern IR_struct IR;


/* Exported functions prototypes ---------------------------------------------*/
void APP_ErrorHandler(void);

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

/************************ (C) COPYRIGHT Puya *****END OF FILE******************/

结构体初始化在main.c中:

	IR_struct IR;           //红外部分需要用到的结构体变量声明

    IR.IR_code = 0;
    IR.IR_decode_bit = 0;
    IR.IR_event = 0;
    IR.timer_count = 0;
    IR.f_head = 0;
    IR.get_bit = 0;
    IR.low_8bit = 0;
    IR.hig_8bit = 0;
    IR.rece_ok = 0;

C文件内容是:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "string.h"
#include "setup.h"

/* Private define ------------------------------------------------------------*/


/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef UartHandle;
__IO ITStatus UartReady = RESET;
TIM_HandleTypeDef TimHandle,Tim3Handle,Tim1Handle, Tim17Handle;
TIM_OC_InitTypeDef Tim17OCHandle,Tim16OCHandle;
I2C_HandleTypeDef I2cHandle;
TIM_IC_InitTypeDef sConfig;
TIM_SlaveConfigTypeDef   sSlaveConfig;
EXTI_HandleTypeDef exti_handle;
GPIO_InitTypeDef GPIO_InitStruct;



IR_struct IR;           //红外部分需要用到的结构体变量声明


/* Private user code ---------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/


/**
  * @brief  应用程序入口函数.
  * @retval int
  */
int main(void)
{   
    /* 初始化所有外设,Flash接口,SysTick */
    HAL_Init();
    /* 系统时钟配置 */
    APP_SystemClockConfig();
    GPIO_Init();

    TIM3_Init();
    HAL_TIM_Base_Stop_IT( &Tim3Handle );    //先关闭TIM3,等收到一个下降沿之后再打开
    TIM3->CNT = 0;                          //清零定时器,为下一次接受红外做准备
    
    /* 使能NVIC GPIO外部中断 */
    HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
    HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0);


    IR.IR_code = 0;
    IR.IR_decode_bit = 0;
    IR.IR_event = 0;
    IR.timer_count = 0;
    IR.f_head = 0;
    IR.get_bit = 0;
    IR.low_8bit = 0;
    IR.hig_8bit = 0;
    IR.rece_ok = 0;
    
    while (1)
    {
	 	if( IR.rece_ok )
        {
            switch (IR.hig_8bit)
            {
                case IR_CODE_GET_POWER :
                    
                break;
                case IR_CODE_GET_SHAKE :
                    
                break;
                case IR_CODE_GET_SPEED :
                    
                break;
                case IR_CODE_GET_TIMER :
                    
                break;  
                default:
                break;
            }
        
            IR.IR_code_get = 0;
            IR.low_8bit = 0;
            IR.hig_8bit = 0;

            boot_cheak_link_over = 0;

            IR.rece_ok = 0;
        }
    }
}


/**
  * @brief  错误执行函数
  * @param  无
  * @retval 无
  */
void APP_ErrorHandler(void)
{
    /* 无限循环 */
    while (1)
    {
    }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  输出产生断言错误的源文件名及行号
  * @param  file:源文件名指针
  * @param  line:发生断言错误的行号
  * @retval 无
  */
void assert_failed(uint8_t *file, uint32_t line)
{
    /* 用户可以根据需要添加自己的打印信息,
        例如: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    /* 无限循环 */
    while (1)
    {
    }
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT Puya *****END OF FILE******************/

贴一下抓到的接收头波形:
在这里插入图片描述
数据内容发完之后如果一直按住不松手,那么发过来的其实就是重复码。

代码内容比较乱,其实核心就是开启一个上升沿+下降沿的中断,判断进入外部中断时是什么电平,如果进入外部中断时是高电平,说明TIM3的CNT存放的就是这次上升沿与上一次的下降沿之间低电平的持续时间;如果进入外部中断时是低电平,说明TIM3的CNT存放的就是这次下降沿与上一次的上升沿之间高电平的持续时间。通过不停进外部中断判断TIM3的CNT数值来判断高低电平的持续时间,完成解码。这种方式不会堵塞MCU,比进入外部中断之后判定电平死等的那种方式更加合理。

更加详细的内容可以看这篇博客,这个大佬写得比较详细,不过他用的是进中断后while的方式来判定时间:
链接: STM32入门开发: NEC红外线协议解码(超低成本无线传输方案)

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值