STM32HAL库实现红外接收的NEC解码(整体思路分析的细致讲解)

视频展示效果

红外解码视频演示

一、环境介绍

硬件设备:STM32F103C8T6、红外模块

软件环境:STM32CubeIDE

二、红外接收波形监测分析(重点)

我这里使用逻辑分析仪分析接收到的红外波形,截取了几种关键波形作分析

下图是我长按红外遥控按键“1”数秒后松手产生的波形

下图是起始信号波形(9ms的低电平,4ms的高电平,总共13ms)

下图是数据帧中的“0”信号(640us的低电平,500us的高电平,总共1.14ms)

下图是数据帧中的“1”信号(640us的低电平,1.6ms的高电平,总共2.25ms)

下图是结束信号(640us的脉冲)

下图是重复信号(9ms的低电平,2ms的高电平,总共11ms)

三、硬件资源分析

1.硬件资源与IO使用情况

接收红外信号:PB11,外部中断脚

串口2发送数据:PA2,PA3,串口收发脚

定时器:使用基础计时功能,任意一个即可(我这里使用TIM1)

2.实物连接

3.图形化配置初始化

  • 时钟引脚配置(HSE和LSE均配置为无源晶振,LSE不配也行,这里没用到)
  • 教大家一个方法判断板子上的晶振是有源晶振还是无源晶振,直接看他的原理图,如果晶振两端有接起振电容就是无源晶振,没有接就是有源晶振

  • IO配置(LED参数默认,外部中断脚上拉,下降沿触发)

  • 串口配置(异步通讯,其他参数默认)

  • 定时器配置(时钟源选择内部时钟,预分频系数为72-1,因为时钟会配置为72M,这样计数值加1就是1us,默认向上计数,重载值65535)

  • 时钟树配置(系统时钟72M)

四、基础功能调试

1.LED点灯,看看工程能不能跑,板子是否正常工作

可在用户代码区2给LED写入低电平(我这块板子低电平点亮)。

2.EXIT外部中断验证

代码可以像我这样写,验证可以用杜邦线一端接上PB11,另一端去碰GND,不灵敏是正常的,有反应即可。

3.串口发送验证

串口可以直接移植我封装好的模块,也可以自己写,在while(1)里能输出信息即可。

STM32CubeIDE实现和printf函数一样的串口发送函数(附完整文件)-CSDN博客文章浏览阅读216次,点赞10次,收藏2次。对C库的sprintf和32HAL库的HAL_UART_Transmit进行封装,实现更方便的带参字符串发送。https://blog.csdn.net/xdedmbb/article/details/137653818?spm=1001.2014.3001.5501

4.TIM计时是否准确

我这里使用官方的HAL_Delay()函数进行粗测。

提醒HAL_Delay()函数是ms级的,不适合做us的延时,而且这个函数延时会多1ms,传参要减一。

代码验证可以参考我的代码,串口发送看下来挺准的。

五、解码程序编写(重点)

先贴代码

  • tim.c
/*
 * tim.c
 *
 *  Created on: 2024年4月11日
 *      Author: 废话文学创始人
 */
#include <tim.h>
//设置计数值
void set_tim1_cnt(uint32_t cnt)
{
	htim1.Instance->CNT = cnt;
}
//清空计数值
void clear_tim1_cnt()
{
	set_tim1_cnt(0);
}
//获得计数值
uint32_t get_tim1_cnt()
{
	return htim1.Instance->CNT;
}
//定时器1开始计时
void count_time_start()
{
	clear_tim1_cnt();
	HAL_TIM_Base_Start(&htim1);
}
//定时器1停止计时
void count_time_stop()
{
	HAL_TIM_Base_Stop(&htim1);
	clear_tim1_cnt();
}
  • tim.h
/*
 * tim.h
 *
 *  Created on: 2024年4月11日
 *      Author: 废话文学创始人
 */

#ifndef INC_TIM_H_
#define INC_TIM_H_

#include <stm32f1xx_hal.h>
#include <stm32f1xx_hal_tim.h>

extern TIM_HandleTypeDef htim1;

void set_tim1_cnt(uint32_t cnt);
void clear_tim1_cnt();
uint32_t get_tim1_cnt();
void count_time_start();
void count_time_stop();


#endif /* INC_TIM_H_ */
  • it.c中一些相关变量和宏的定义
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define wait  			 0
#define start 			 1
#define data_rx     	 2
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t state = 0;
uint8_t signal_repeat = 0;
uint8_t signal_data_rx_over = 0;
uint32_t count = 0;
uint8_t pdata = 0;
uint8_t ir_receive[4];
/* USER CODE END PV */
  • 外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	//判断外部中断源是否为GPIO_PIN_11
	if(GPIO_Pin == GPIO_PIN_11){
		switch(state){
		case wait://空闲状态
			state = start;
			//检测到下降沿开始计时
			count_time_start();
			break;
		case start://起始信号和重复信息阶段
			//读取间隔时间
			count = get_tim1_cnt();
			//停止计数
			count_time_stop();
			//判断是起始信号还是重复信号
			if(count >= 13500 - 500 && count <= 13500 + 500){
				//进入数据接收阶段
				state = data_rx;
				//为数据阶段计时
				count_time_start();
			}
			else if(count >= 11250 - 500 && count <= 11250 + 500){
				//反馈重复信号,重回空闲状态
				signal_repeat = 1;
				state = wait;
			}
			else state = wait;
			break;
		case data_rx://数据处理阶段
			//读取间隔时间
			count = get_tim1_cnt();
			//停止计数
			count_time_stop();
			//数据处理
			if((count >= 1140 - 500 && count <= 1140 + 500)){
				//向pdata位写入0
				ir_receive[pdata/8] &= ~(1 <<(pdata%8));
			}
			else if((count >= 2250 - 500 && count <= 2250 + 500)){
				//向pdata位写入1
				ir_receive[pdata/8] |= (1 <<(pdata%8));
			}
			//pdata位移向下一位
			pdata++;
			//继续接收下一位byte数据
			state = data_rx;
			//数据帧处理结束
			if(pdata == 32){
				//重置pdata为0
				pdata = 0;
				//利用数据协议的反码数据校验数据准确性
				if((ir_receive[0]=~ir_receive[2])&&(ir_receive[1]=~ir_receive[3])){
					//反馈数据处理完成信号
					signal_data_rx_over = 1;
				}
				//重回空闲状态
				state = wait;
			}
			//数据帧未结束,为下次下降沿计时
			else count_time_start();
			break;
		}
	}
}
  • main函数
  while (1)
  {
	  //对数据接收完成信号进行处理
  	  if(signal_data_rx_over == 1){
  		  //发送地址码和命令码
  		  Uart_Send_String(" address:%2x, command:%2x\n",ir_receive[0],ir_receive[2]);
  		  //发送地址码和命令码的反码
  		  Uart_Send_String("~address:%2x,~command:%2x\n",ir_receive[1],ir_receive[3]);
  		  //标志清零
  		  signal_data_rx_over = 0;
  	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

思路讲解:

TIM的基本计时功能进行函数封装,可让代码思路更清晰。(前提是你函数命名没有偷懒)

解码部分参考了别人的状态机解码思路,将长时间的高电平时间段设为空闲阶段,将起始信号和重复信号时间段设为开始阶段,将数据帧信号时间端设为数据接收处理阶段。通过下降沿触发中断实现3种状态之间的迁移。

各个信号的下降沿间隔时间有明显区别,利用此特性,计时两次下降沿之间的时间,去判断是何种信号。

数据接收完进行数据校验,具体校验原理是因为他发的数据格式就是地址码-地址码反码-命令码-命令码反码。

  • 31
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

废话文学创始人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值