写在前边
最近项目用到一款遥控器是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红外线协议解码(超低成本无线传输方案)