☰
STM32教程:HX1838红外模块的使用
2026/10/17 6:52:02 网站建设 项目流程

本文章将介绍HX1838模块基本信息、NEC红外协议简介和基本实现方法、STM32控制该模块的简易代码,使用的MCU是STM32F103C8T6。后续代码升级请持续关注博主更新。

目录

模块简介

NEC协议简介

硬件原理概述

引导码

逻辑0和逻辑1的表示

地址码、数据码及其反码

停止位

低位优先

重发码

代码实现思路

STM32CubeMX配置

硬件连线

示例代码

代码问题分析


模块简介

HX1838红外接收头模块是一款遵循NEC红外协议的模块,通常配合商家赠送的红外遥控器使用,除此之外,也可以使用红外灯珠设计红外遥控器进行控制(商家可能会赠送)。

HX1838接收头+遥控器

接收头只负责接收遥控器或红外线发射二极管发送的信息,信息的解析由硬件电路和单片机进行处理。

遥控器发送的数据包遵循NEC红外协议,如果采用自己设计的红外遥控器进行控制,发送数据的格式和协议可以自定义,以下是对NEC协议的介绍。

NEC协议简介

硬件原理概述

下图为遥控器发送的信号,黄色部分并不是高电平,而是38kHz的连续脉冲信号。

遥控器实际发送的信号
图片来自up主 爱上半导体
一帧红外遥控信号,竟如此复杂,超乎你的想象!红外遥控的工作原理!_哔哩哔哩_bilibili
接收头内置芯片(仅三个引脚的接收头才内置芯片)

经由红外接收管内置的芯片处理,芯片收到高频脉冲就会输出低电平,收不到则输出高电平,故单片机引脚接收到的信号如下图:

图片根据CSDN博主光子物联单片机的文章​​ 零基础STM32单片机编程入门(十八) HX1838红外遥控模块实战含源码-CSDN博客
中的图片进行修改

一帧数据包含:

引导码,地址码,地址反码,数据码,数据反码,停止位(按照发送顺序排列)

引导码

由上文知,信号线空闲的时候,红外模块内置的芯片输出的是高电平,当一帧信号传输到单片机引脚,单片机最先收到的就是引导码,产生9ms的低电平,接下来再产生4.5ms的高电平。

引导码作为帧头,负责引出后续的有效信息,单片机通过检测引导码来确认是否开始接收一帧数据。

逻辑0和逻辑1的表示

涉及到通信协议,就有必要思考,如何表示数据位的某一位是0还是1?由于NEC是单线通信协议,我们通过检测高电平时长和低电平时长确定。NEC使用这种编码方式,我们不需要设置接收位的接收周期,也可以检测数据中的多个逻辑0和1,较为方便和节省资源。

地址码、数据码及其反码

单片机通过检测地址码,来识别遥控器,不同的遥控器可以设置不同的地址码,地址共8位(bit),通过上图中的“逻辑1”和“逻辑0”来表示每1位。HX1838红外模块赠送的遥控器,地址码一般是0x00,不同产品可能存在差异。

数据码就是需要传输的有效数据,比如红外遥控器的键值,共8位。下表是HX1838模块赠送的遥控器的键值表,不同遥控器键值表可能不同,建议到手后检查核对。

键值

实际数据

数据反码

1

0x45

0xba

2

0x46

0xb9

3

0x47

0xb8

4

0x44

0xbb

5

0x40

0xbf

6

0x43

0xbc

7

0x07

0xf8

8

0x15

0xea

9

0x09

0xf6

*

0x16

0xe9

0

0x19

0xe6

#

0x0d

0xf2

上箭头

0x18

0xe7

左箭头

0x08

0xf7

右箭头

0x5a

0xa5

下箭头

0x52

0xad

OK

0x1c

0xe3

地址反码和数据反码就是对前面的地址码、数据码进行按位取反,是为了对数据进行校验而存在的。在收到的一帧数据中,如果~(xx反码)≠ xx码,那么这帧数据就是无效的,需要丢弃。

停止位

至于停止位,往往是持续0.56ms的低电平,一般在程序中无需判断停止位,收到引导码后,接收四个字节(地址码、数据码及其反码)即可选择结束本轮接收。

重发码

在发送完一帧数据后,如果红外遥控器的按键依旧按下,就会每110ms发送一次重发码。用于检测遥控器按键是否持续按下。如果我们不设计持续按下对应的相关功能,通常需要识别出重发码即可,重点在于区分重发码和引导码。

低位优先

在数据位接收顺序方面,NEC协议遵循低位优先原则,发送地址码、数据码及其反码时,先发较低位再发较高位。

代码实现思路

单片机没有NEC通信接口,我们需要自己编写代码去识别每一个码元。如何识别?通过测量波形来识别,单片机与测量波形相关的功能有输入捕获、PWM输入模式,考虑到复杂度与资源占用,本项目采用较简单轻便的输入捕获模式。

输入捕获(reset从模式)的基本原理
图片摘自江协科技STM32教程

上图展示了输入捕获的基本原理,以上升沿触发为例,当外输入信号产生一个上升沿,TIM定时器外设就会产生一个捕获事件,单片机内部会产生以下变化:

1.TIMx会将CNT当前的值写入CCR1寄存器;

2.TIMx产生一个捕获/比较中断(如果我们使能了该中断);

3.如果我们配置了reset从模式,CNT寄存器的值在被写入CCR1寄存器后,会被清零;

在reset从模式下,我们直接读取CCR1寄存器,就可以获知两个上升沿之间的时长,不必再作加减运算。

确定了使用的外设以后,我们来对NEC协议进行简单分析和拆解:

图片根据CSDN博主光子物联单片机的文章​​ 零基础STM32单片机编程入门(十八) HX1838红外遥控模块实战含源码-CSDN博客
中的图片进行修改

由上述分析,引导码、逻辑0、逻辑1、重复码(地址码、数据码及其反码由若干逻辑0和逻辑1组合而成),这些码元都是以下降沿开始,下降沿结束的。我们可以将TIMx触发捕获事件的边沿设置为下降沿。下降沿意味着当前码元的结束,恰好此时捕获中断产生,我们可以在中断函数内处理该码元;同时也意味着下一个码元的开始。

这些码元的长度也是各不相同的,我们可以通过输入捕获测量它们的总时长,根据时长来区别不同的码元。当然,判断条件应适当留有余量,具体可参照下文的代码。

STM32CubeMX配置

以STM32F103C8T6为例进行配置。如果你使用的是标准库,可以依照下图来使用库函数配置必要项。

配置时钟树:

配置定时器:

硬件连线

红外接收模块的原理图

如图,S为信号线,连接到我们刚刚配置的TIM1 CH1即可,与单片机共VCC共地即可。本项目中使用的MCU是STM32,VCC接+3.3V即可。S引脚和负极的引脚应该会在模块上标出。

示例代码

hx1838.c

#include "hx1838.h" volatile uint8_t IR_buf[4]; volatile uint8_t IR_Update_flag; /** * @brief 对红外模块初始化,打开中断 * @param None * @retval None */ void IR_Init(void) { HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1); } /** * @brief 将NEC协议的数据位映射到对应的业务上 * @param None * @retval None */ void IR_DataBitTranslate(void) { //判断并更新键值 if ((IR_buf[0] == 0x00) && ((IR_buf[0] ^ IR_buf[1]) == 0xFF) && ((IR_buf[2] ^ IR_buf[3]) == 0xFF)) { // 校验通过,处理这帧数据 switch(IR_buf[2]) { case IR_KEY_LEFT: IR_AniMode = (IR_AniMode_t)((IR_AniMode + 1) % MODE_TOTAL); IR_AnimationInit(); break; case IR_KEY_RIGHT: IR_AniMode = (IR_AniMode_t)((IR_AniMode + 3 - 1) % MODE_TOTAL); IR_AnimationInit(); break; default:; } } } /** * @brief 将接收到的字节收入环形缓冲区 * @param byte 填入收到的字节 * @retval None */ void IR_InQuene(uint8_t byte, uint8_t indexx) { IR_buf[indexx] = byte; } /** * @brief 检测到对应边沿后,IRQ函数会调取该函数进行必要的解码操作 * @note 此工程中,检测到下降沿,然后测量电平持续时间 * @param htim TIM的句柄 * @retval None */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { static volatile uint8_t OneByte = 0; static volatile uint8_t byte_sum = 0; static volatile uint8_t poi = 0; static volatile uint8_t LeaderCopy_Flag = 0; static volatile uint32_t period = 0; if(htim == &htim1) { period = HAL_TIM_ReadCapturedValue(&htim1, TIM_CHANNEL_1); //接收1 Byte地址/数据,建立在引导码发送的前提 if(LeaderCopy_Flag == 1) { if(period >= 1800 && period <= 2900) { //逻辑1 OneByte = OneByte | (0x01 << poi); poi++; } else if(period >= 800 && period <= 1400) { //逻辑0 OneByte = OneByte & ~(0x01 << poi); poi++; } else { //信号异常,直接重启 poi = 0; OneByte = 0; byte_sum = 0; LeaderCopy_Flag = 0; return; } //接收完OneByte if(poi == 8) { IR_InQuene(OneByte, byte_sum); //后续会改成置标志位,中断外入队 byte_sum++; poi = 0; OneByte = 0; if(byte_sum == 4) { byte_sum = 0; LeaderCopy_Flag = 0; IR_Update_flag = 1; } } } else { if(period >= 10000 && period <= 11500) //单位是us { //重复码 return; } else if(period > 11500 && period <= 15000) { //引导码 LeaderCopy_Flag = 1; } } } }

hx1838.h

#ifndef __HX1838_H__ #define __HX1838_H__ #include "main.h" #include "stm32f1xx_hal.h" #include "tim.h" #include "ws2812.h" void IR_Init(void); void IR_DataBitTranslate(void); #endif

代码问题分析

1.灵敏度有时较低,有时按下按键后可能无法执行相关业务,具体问题有待排查;

2.回调函数较臃肿,只需留下必要的接收语句,即period = ...;解码操作可在主函数执行;

3.仅采用裸一维数组当缓冲区,数据极容易被覆盖,后续会使用环形缓冲区;

4.未设计重发码相关程序。

后续会针对这些问题优化更新

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询