【STM32】NEC协议红外连续收发硬件电路设计与实现基于STM32平台
基于STM32的NEC协议红外连续接收和发送的硬件电路及其实现
博主使用的芯片是STM32L431,但本文内容适用于STM32全系列芯片。本帖中的代码原理在任何MCU上都是通用的。
本帖介绍的IR红外通信主要用于超近距离通信,例如外设设备的出厂设置。不过,该代码经过适当调整后,也可用于任何距离的红外通信场景。
NEC协议

NEC协议发送数据的数据帧格式:引导码+地址+地址取反+数据+数据取反,其中取反用于验证数据是否正确

除了发出一帧的数据以外,还可能会有按着发送键不放的情况,这种情况就要使用重复码,重复码即重复信号,如下图
重复信号的周期是110ms,由9ms高电平 + 2.25ms低电平 + 560us高电平组成。

硬件电路图
PA8 需要可以开启定时器的设备,本文使用定时器1的通道1
PA0 需要可以开启定时器PWM的设备,本文使用定时器2的通道1
值得注意的是,在本文的设计过程中,采用了两个定时器来完成收发功能,但是原理上是可以使用一个定时器来完成的。
只不过,该项目在设计过程中资源不紧张,故开启了两个定时器。
如下图实物图:
中间的两个排针一个是红外发送一个是红外接收
实现原理
配置如下图
本文硬件结构是当PWM输出低电平的时候,红外发射管发射红外信号,而输出高电平的时候,红外发射管是熄灭状态。
配置如下图
上图中,
代码部分
接收部分
/* 使用HAL库 如果使用标准库,请自行更改 */
#include "main.h"
#include <stdint.h>
#include <stdbool.h>
#include "tim.h"
// IR接收状态结构体
typedef struct {
uint16_t valueUp; // 上升沿对应的计数器值
uint16_t valueDown; // 下降沿对应的计数器值
bool isUpCapt; // 是否为上升沿捕获
int width; // 下降沿与上升沿之间的差值(us单位)
uint16_t buffer[128]; // 存储数据
uint16_t bufferId; // 存储数据数组下标
bool rcvFlag; // 接收标志位
uint8_t hexbuf[4]; // 存储转换的16进制数据
} IR_HandleTypeDef;
IR_HandleTypeDef hIR;
/*
* 微秒级延时函数
* 该函数使用systick定时,在使用该函数前应该初始化systick
* systick初始化函数详见本贴附录
*/
static void delay_us(uint32_t us) {
SysTick->LOAD = (SystemCoreClock / 1000000) * us - 1;
SysTick->VAL = 0;
while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
}
/**
* @biref 初始化红外
* @note 初始化函数中对结构体进行赋初值,并开启TIM1的中断
*/
void IR_Init(void) {
hIR.valueUp = 0;
hIR.valueDown = 0;
hIR.isUpCapt = true;
hIR.width = 0;
hIR.bufferId = 0;
hIR.rcvFlag = false;
/*开启定时器1中断*/
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
}
/**
* @biref 红外接收函数
* @note 接收红外信号并解析
*/
uint8_t *IR_Receive(void) {
uint8_t rcvData[32] = {0};
hIR.rcvFlag = false;
// 数据解码,低电平大于1000us的为逻辑1,小于1000us的为逻辑0
// 定时器10us捕获一次,故为100
for (int i = 0; i < 32; i++) {
rcvData[i] = hIR.buffer[hIR.bufferId + 1 + i] > 100 ? 1 : 0;
}
// 变换成4位16进制,并直接进行位序颠倒
for (int i = 8; i <= 32; i += 8) {
uint8_t temp = 0;
// 将二进制数据转换为字节
for (int j = 0; j < 8; j++) {
temp |= (rcvData[i - 8 + j] << j);
}
hIR.hexbuf[i / 8 - 1] = temp;
}
//===== 打印解析后的数据 DEBUG ====//
printf("IR:");
for (int i = 0; i < 4; i++) {
printf("%02X ", hIR.hexbuf[i]);
}
printf("\r\n");
return hIR.hexbuf;
}
/** 输入捕获中断回调函数
* @note 该函数用于在触发输入捕获中断后对数据进行解析
* @note 使用该函数前,请确保定时器的中断捕获中断配置完成
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (hIR.isUpCapt) {
hIR.isUpCapt = false;
/*上升沿捕获时间点*/
hIR.valueUp = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
} else {
hIR.isUpCapt = true;
/*下降沿捕获时间点*/
hIR.valueDown = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);
/* 低电平时长 */
hIR.width = hIR.valueDown - hIR.valueUp;
/* 阻塞中断等待高电平发送完成 */
delay_us(577);
/*根据NEC协议,引导区为9ms高电平 + 4.5ms低电平,判断4.5ms低电平即可*/
if (hIR.width > 440 && hIR.width < 460) {
hIR.bufferId = 0;
hIR.buffer[hIR.bufferId++] = hIR.width;
} else if (hIR.bufferId && hIR.bufferId <= 32) {
//保存32位数据
hIR.buffer[hIR.bufferId++] = hIR.width;
}else if(hIR.width > 9000)
{ // 重复接收,根据NEC协议计算,重复码末尾的低电平约为98.19ms,而前面的高电平为560us
// 所以可以判断是否接收到90ms以上的低电平来实现重复接收
hIR.rcvFlag = true;
hIR.bufferId = 33;
}
// 数据接收完成
if (hIR.bufferId == 33) {
hIR.rcvFlag = true;
hIR.bufferId = 0;
}
}
}
// 使能红外接收
void IR_Enable(void) {
if (hIR.rcvFlag) {
uint8_t *buf = IR_Receive();
uint8_t data = 0;
if (buf != NULL && IR_CheckData(buf, &data) == HAL_OK) {
IR_TableCMD(data);
}
}
}
发送部分
/*提醒: 如IR_HandleTypeDef结构体、延时函数等基本代码已在接收部分完成,故不在发送部分复写*/
#define IR_SendBitHigh HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
#define IR_SendBitLow HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
/*
* 红外发送
* 引导码:9ms的高电平 + 4.5ms的低电平。
* 用户码:8位数据,通常为0x00。
* 数据码:8位数据,表示要发送的命令。
* 反码:数据码的反码,用于校验。
* 结束码:560µs的高电平。
*/
// 发送NEC引导码
void IR_SendLeader(void) {
IR_SendBitHigh; // 产生9ms的高电平
delay_us(9000);
IR_SendBitLow; // 产生4.5ms的低电平
delay_us(4500);
}
// 发送NEC数据位0/1
void IR_SendBit(uint8_t bit) {
if (bit) { //数据位1
IR_SendBitHigh; // 产生560us的高电平
delay_us(560);
IR_SendBitLow; // 产生1680us的低电平
delay_us(1680);
} else { //数据位0
IR_SendBitHigh; // 产生560us的高电平
delay_us(560);
IR_SendBitLow; // 产生560us的低电平
delay_us(560);
}
}
// NEC数据发送一个字节,从低位开始
void IR_WriteByte(uint8_t byte) {
for (uint8_t i = 0; i < 8; i++) {
// 提取当前位的值
uint8_t bit = (byte >> i) & 0x01;
// 写入这一位
IR_SendBit(bit);
}
}
// 发送NEC数据包
void IR_Send(uint8_t data) {
/*打开PWM发送*/
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
IR_SendLeader(); // 发送引导码
IR_WriteByte(0x00); // 发送地址码
IR_WriteByte(0xFF); // 发送地址反码
IR_WriteByte(data); // 发送命令码
IR_WriteByte(~data); // 发送命令反码
IR_SendBitHigh; // 发送结束码
delay_us(560);
IR_SendBitLow;
/*关闭PWM发送*/
HAL_TIM_PWM_Stop(&htim2,TIM_CHANNEL_1);
}
函数使用
void mian()
{
IR_Init(); /*初始化*/
/*发送红外数据 0x44*/
IR_Send(0x44);
while(1)
{
IR_Enable(); /*开启红外连续接收功能*/
}
}
现象
接收现象
随意找一个可以发送红外信号的遥控器或者装置,对准红外接收头(由于本文做的是近距离的红外通信,所以需要很久,但如果你的红外接收头能够接收较远距离的红外信号,可以适当拉远距离)
串口助手获取信息
这里的数据是十六进制的数据,其中,前两位是地址位(0x00)和地址反码(0xFF),后两位是数据位(0x44)和数据反码(0xBB)
注意,此处的反码与数电中的原码反码不是一个意思,只是代表按位取反的意思
发送现象
拿一个红外转串口的模块来接收发送的红外信号
串口调试助手现象
根据该红外转串口模块商家描述,当该模块接收到信息后,只会通过串口输出 地址位+地址反码+数据位的信息帧。
不过,具体的反馈信息看具体的模块商家给的模块说明。
以上信息显示是正确的。
以上便是红外发送和接收的硬件电路和实现方法,如有缺漏,请指出。
参考文献
NEC格式详解
基于STM32f103c8t6的红外接收发送
附录
systick初始化
/**
* @brief 初始化SysTick定时器
* @param 无
* @retval 无
*/
void SysTick_Init(void)
{
// 配置SysTick重装载值为系统时钟频率的1/1000000(1us)
SysTick->LOAD = (SystemCoreClock / 1000000) - 1;
// 清空当前计数器值
SysTick->VAL = 0;
// 配置SysTick时钟源为系统时钟,并启动计数器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
}
作者:Asialing