【stm32协议外设篇】- HX1838 红外接收头

一、适用场景

适用场景:红外遥控器解码(家电/小车/灯控)、菜单翻页与参数设置、低成本无线控制、学习红外通信协议(NEC / RC5 等)、嵌入式外部中断与定时器实战教学等。

二、器材清单

HX1838 红外接收头模块 ×1

红外遥控器(NEC 协议常见)×1

stm32f103开发板 ×1

若干杜邦线 ×1组

三、工作原理(要点)

HX1838 是一款 38kHz 调制红外信号接收头 输出的是 已解调的数字信号,MCU 无需再处理 38kHz 载波

NEC 协议是最常见的红外遥控协议之一,其时序特点:

引导码:

9ms 低电平 + 4.5ms 高电平

数据位(LSB 优先):

逻辑 0:560µs 低 + 560µs 高

逻辑 1:560µs 低 + 1.69ms 高

一帧数据共 32bit:

8bit 地址

8bit 地址反码

8bit 数据

8bit 数据反码

四、接线示意

GND → GND

VCC→ 5V/3.3V电源

标准库

S→ PB9

HAL库

S→ PA8

五、示例代码

标准库

cpp 复制代码
#include "rmt.h"

//遥控器接收状态
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留
//[4]:标记上升沿是否已经被捕获
//[3:0]:溢出计时器
u8 	RmtSta=0;




u16 Dval;		//下降沿时计数器的值
u32 RmtRec=0;	//红外接收到的数据
u8  RmtCnt=0;	//按键按下的次数
int mask=0,num_flag=0,cnt=0,cout=0;
u8 temp=0;

void Remote_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_ICInitTypeDef  TIM_ICInitStructure;
 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);	//TIM4 时钟使能
 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;				 //PB9 输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; 		//上拉输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
//    GPIO_SetBits(GPIOB,GPIO_Pin_9);	//初始化GPIOB.9
 
 
    TIM_TimeBaseStructure.TIM_Period = 10000; //设定计数器自动重装值 最大10ms溢出
    TIM_TimeBaseStructure.TIM_Prescaler =(72-1); 	//预分频器,1M的计数频率,1us加1.
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
 
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx
 
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;  // 选择输入端 IC4映射到TI4上
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//上升沿捕获
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 //配置输入分频,不分频
    TIM_ICInitStructure.TIM_ICFilter = 0x03;//IC4F=0011 配置输入滤波器 8个定时器时钟周期滤波
    TIM_ICInit(TIM4, &TIM_ICInitStructure);//初始化定时器输入捕获通道
 
    TIM_Cmd(TIM4,ENABLE); 	//使能定时器4
 
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;  //TIM3中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级0级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
 
    TIM_ITConfig( TIM4,TIM_IT_Update|TIM_IT_CC4,ENABLE);//允许更新中断 ,允许CC4IE捕获中断
}

u8 Remote_Scan(void)//获取键值
{
    u8 sta=0;
    u8 t1,t2;
    if(RmtSta&(1<<6))//得到一个按键的所有信息了
    {
        t1=RmtRec>>24;			//得到地址码
        t2=(RmtRec>>16)&0xff;	//得到地址反码
			  if(t1==((u8)~t2))//检验遥控地址
        {
            t1=RmtRec>>8;
            t2=RmtRec;
            if(t1==(u8)~t2)sta=t1;//键值正确
        }
        if((sta==0)||((RmtSta&0X80)==0))//遥控没有按下了/按键数据错误(没有引导码)
        {
            RmtSta&=~(1<<6);//清除接收到有效按键标识
//            RmtCnt=0;		//清除按键次数计数器,是为了重复码
        }
    }
    return sta;
}



void init_ALL(void)
{
	USART_Config();
	SysTick_Init();        //初始化滴答计时器
	Remote_Init();            //红外按键初始化
}

void TIM4_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)//计时器更新中断
    {
			cnt++;
			if(cnt > 100)//超时就去获取键码值并打开显示标志位
			{
				cnt=0;				
				temp=Remote_Scan();
				if(temp!=0)
					num_flag=1;
			}
			
        if((RmtSta&0x40) != 0 )								//引导码不为空,表示上次有数据被接收到了,在空闲的情况下就需要将上次的数据位清除,如果有数据也是先执行完上面的显示函数才会到下面清除标记位,所以不用担心会其冲突
        {
            RmtSta&=~0X10;							//取消上升沿已经被捕获标记
						RmtSta&=~(1<<7);					//清空引导标识
//            RmtSta&=0XF0;						//清空计数器,是为了重复码
        }
    }
    if(TIM_GetITStatus(TIM4,TIM_IT_CC4)!=RESET)//捕获中断
    {
        if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==SET)//上升沿捕获
        {
            TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling);						//CC4P=1	设置为下降沿捕获
            TIM_SetCounter(TIM4,0);							//清空定时器值
            RmtSta|=0x10;							//标记上升沿已经被捕获
        } 
				else //下降沿捕获
        {
            Dval=TIM_GetCapture4(TIM4);					//读取CCR4也可以清CC4IF标志位,获取高电平的时间
            TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising);				//CC4P=0	设置为上升沿捕获
            if(RmtSta&0X10)							//如果已经完成一次高电平捕获
            {
                if(RmtSta&0X80)//接收到了引导码
                {
                    if(Dval>300&&Dval<800)			//560为标准值,560us
                    {
                        RmtRec<<=1;					//左移一位.
                        RmtRec|=0;					//接收到0
                    } 
										else if(Dval>1400&&Dval<1800)	//1680为标准值,1680us
                    {
                        RmtRec<<=1;					//左移一位.
                        RmtRec|=1;					//接收到1
                    } 
//										else if(Dval>2200&&Dval<2600)	//得到按键键值增加的信息 2500为标准值2.5ms
//                    {
//                        RmtCnt++; 					//按键次数增加1次
//                        RmtSta&=0XF0;				//清空计时器
//                    }
										cout++;//统计目前获取多少位数据
										if(cout>32)//获取到32位数据
										{
											cout=0;
											RmtSta|=0x40;
											Usart_SendString(USART1,"ok\n");
										}
                }
								else if(Dval>4200&&Dval<4700)		//4500为标准值4.5ms
                {
                    RmtSta|=1<<7;					//标记成功接收到了引导码
//                    RmtCnt=0;						//清除按键次数计数器,是为了重复码
                }
            }
            RmtSta&=~(1<<4);//取消上升沿已经被捕获标记
        }
    }
    TIM_ClearITPendingBit(TIM4,TIM_IT_Update|TIM_IT_CC4);
}
#include "stm32f10x.h"
#include "stdio.h"
#include "rmt.h"
#include "bsp_SysTick.h"
#include "bsp_usart.h"

extern u8 RmtSta,temp;
extern int num_flag;
char show[20];

int main(void)
{	
	init_ALL();
	while(1)
	{ 
		if(num_flag==1)//1s后显示数据
		{
			num_flag=0;
			sprintf(show,"编码=%d,按键=%x\r\n",temp,temp);
			Usart_SendString(USART1,show);
		}
		
	}
}

HAL库

cpp 复制代码
#include "HX1838.h"
#include "main.h"
#include "stm32f1xx_hal.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h> 
#include <math.h>
extern TIM_HandleTypeDef htim1;
#define  RX_DBG_EN   0

#define  RX_SEQ_NUM  33

#if RX_DBG_EN
#define RX_DBG(format, ...) printf(format, ##__VA_ARGS__)
#else
#define RX_DBG(format, ...) ;
#endif
static uint8_t  tim_udt_cnt      = 0;
static uint8_t  cap_pol          = 0; 
static uint8_t  cap_pulse_cnt    = 0;
static uint8_t  sta_idle         = 1;
static uint8_t  cap_frame        = 0;
static uint16_t rx_frame[RX_SEQ_NUM*2] = {0}; 

struct {
    uint16_t  src_data[RX_SEQ_NUM*2];
    uint16_t  repet_cnt;
    union{
        uint32_t rev;
        struct
        {
            uint32_t key_val_n:8;
            uint32_t key_val  :8;
            uint32_t addr_n   :8;
            uint32_t addr     :8;
        }_rev;
    }data;
}rx;
extern UART_HandleTypeDef huart2;
char show[65]={0};



uint8_t appro(int num1,int num2)
{
    return (abs(num1-num2) < 300);
}

void rx_rcv_init(void)
{
    cap_frame     = 0;                                       //未捕获到新数据
    sta_idle      = 0;                                       //非空闲状态
    tim_udt_cnt   = 0;                                       //定时器溢出清0
    cap_pulse_cnt = 0;                                       //捕获到的计数清0
    
    memset(rx_frame,0x00,sizeof(rx_frame));
}
void hx1838_cap_start(void)
{
    HAL_TIM_Base_Start_IT(&htim1);
    HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_1);
}

void HX1838_demo(void)
{
    hx1838_cap_start();//定时器1通道1,输入捕获启动
    while(1)
    {
        if(cap_frame)//标记捕获到新的数据
        {   
            hx1838_proc(hx1838_data_decode());//解析数据
            cap_frame = 0;
        }
    }
    
}
//根据键码值显示
void hx1838_proc(uint8_t res)
{
    if(res == 0)
    {    
        return;
    }
    
    if(res == 2)
    {   
        return;
    }      
    switch(rx.data._rev.key_val)
    {
        case 162:
					HAL_UART_Transmit(&huart2, "1\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 98:
           HAL_UART_Transmit(&huart2, "2\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 226:
            HAL_UART_Transmit(&huart2, "3\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 34:
            HAL_UART_Transmit(&huart2, "4\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 2:
           HAL_UART_Transmit(&huart2, "5\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 194:
            HAL_UART_Transmit(&huart2, "6\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 224:
           HAL_UART_Transmit(&huart2, "7\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 168:
            HAL_UART_Transmit(&huart2, "8\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 144:
            HAL_UART_Transmit(&huart2, "9\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 152:
            HAL_UART_Transmit(&huart2, "0\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 104:
					HAL_UART_Transmit(&huart2, "*\r\n", 3, HAL_MAX_DELAY);
            break;
        
        case 176:
					HAL_UART_Transmit(&huart2, "#\r\n", 3, HAL_MAX_DELAY);
            break;
                
        case 24:
        HAL_UART_Transmit(&huart2, "^\r\n", 3, HAL_MAX_DELAY); 
            break;
                
        case 16:
					HAL_UART_Transmit(&huart2, "<\r\n", 3, HAL_MAX_DELAY); 
            break;
        
        case 74:
    HAL_UART_Transmit(&huart2, "v\r\n", 3, HAL_MAX_DELAY); 	
            break;
        
        case 90:
					HAL_UART_Transmit(&huart2, ">\r\n", 3, HAL_MAX_DELAY); 		
            break;
        
        case 56:
					HAL_UART_Transmit(&huart2, "ok\r\n", 4, HAL_MAX_DELAY); 		
            break;
        
        default:
            
            break;
        
    }
}

/* 溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{   
	if(TIM1 == htim->Instance)
	{
        if(sta_idle)                                          //空闲状态,不作任何处理
        {
            return;
        }
        
        tim_udt_cnt++;                                         //溢出一次
        if(tim_udt_cnt == 3)                                   //溢出3次
        {
            tim_udt_cnt = 0;                                   //溢出次数清零
            sta_idle    = 1;                                   //这是为空闲状态
            cap_frame   = 1;                                   //标记捕获到新的数据
        }
	} 
}


/* 电平捕获中断回调 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t tmp_cnt_l,tmp_cnt_h;
	if(TIM1 == htim->Instance)
	{
        switch(cap_pol)                                                                                         //根据极性标志位判断捕获是低电平还是高电平
        {   
            /* 捕获到下降沿 */
            case 0:
                tmp_cnt_l = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1);                                    //记录当前时刻
                TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1);                                               //复位极性配置
                TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);                //改变极性
                
                cap_pol = 1;                                                                                    //极性标志位改为上升沿
                
                if(sta_idle)                                                                                    //如果当前为空闲状态,空闲捕获到的时序,为第一个下降沿
                {
                    rx_rcv_init();
                    break;                                                                                      //返回
                }
                rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_l - tmp_cnt_h;                          //与上次捕获的计时作差,记录值
                tim_udt_cnt = 0; 
//								sprintf(show,"(%2d)%4d us:H\r\n",cap_pulse_cnt,rx_frame[cap_pulse_cnt]);
//								HAL_UART_Transmit(&huart2, show, strlen(show), HAL_MAX_DELAY); 	
								//溢出次数清0
								//DBG:打印捕获到的电平及其时长
                cap_pulse_cnt++;                                                                                //计数++
                break;
            
            /* 捕获到上升沿 */
            case 1:
                tmp_cnt_h = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1);
                TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1);               
                TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
                
                cap_pol = 0;   
                if(sta_idle)
                {
                    rx_rcv_init();
                    break;
                }
                rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_h - tmp_cnt_l;
                tim_udt_cnt = 0;
//								sprintf(show,"(%2d)%4d us:L\r\n",cap_pulse_cnt,rx_frame[cap_pulse_cnt]);
//								HAL_UART_Transmit(&huart2, show, strlen(show), HAL_MAX_DELAY); 	
                cap_pulse_cnt++;
                break;
            
            default:
                break;
        }
    }
}
//数据解码
uint8_t hx1838_data_decode(void)
{
    memcpy(rx.src_data,rx_frame,RX_SEQ_NUM*4);
    memset(rx_frame,0x00,RX_SEQ_NUM*4);   
//		HAL_UART_Transmit(&huart2, "========= rx.src[] =================\r\n", strlen("========= rx.src[] =================\r\n"), HAL_MAX_DELAY); 	
//    for(uint8_t i = 0;i<=(RX_SEQ_NUM*2);i++)
//    {
//			sprintf(show,"[%d]%d\r\n",i,rx.src_data[i]);
//								HAL_UART_Transmit(&huart2, show, strlen(show), HAL_MAX_DELAY); 	
//    }
		HAL_UART_Transmit(&huart2, "========= rx.rec =================\r\n", strlen("========= rx.rec =================\r\n"), HAL_MAX_DELAY); 
    if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],4500)) //#1. 检测前导码,9ms,4.5ms
    {
        uint8_t tmp_idx = 0;
        rx.repet_cnt  = 0;                                       //按键重复个数清0
        for(uint8_t i = 2;i<(RX_SEQ_NUM*2);i++)                 //#2. 检测数据
        {
            if(!appro(rx.src_data[i],560))
            {
							sprintf(show,"%d,err:%d != 560\r\n",i,rx.src_data[i]);
								HAL_UART_Transmit(&huart2, show, strlen(show), HAL_MAX_DELAY); 	
                return 0;
            }
            i++;
            if(appro(rx.src_data[i],1680))
            {
                rx.data.rev |= (0x80000000 >> tmp_idx);                          //第 tmp_idx 为置1
                tmp_idx++;
            }
            else if(appro(rx.src_data[i],560))
            {
                rx.data.rev &= ~(0x80000000 >> tmp_idx);                         //第 tmp_idx 位清0
                tmp_idx++;
            }
            else
            {
							sprintf(show,"%d,err:%d != 560||1680\r\n",i,rx.src_data[i+1]);
								HAL_UART_Transmit(&huart2, show, strlen(show), HAL_MAX_DELAY); 
                return 0;
            }
        }
    }
    else if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],2250) && appro(rx.src_data[2],560))
    {
        rx.repet_cnt++;
        return 2;
    }
    else
    {
			HAL_UART_Transmit(&huart2, "前导码检测错误\r\n", strlen("前导码检测错误\r\n"), HAL_MAX_DELAY); 
        return 0;
    }
    return 1;
}
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
	HX1838_demo();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		HX1838_demo();//调用红外接收并显示
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

六、讲解视频

https://www.bilibili.com/video/BV1EYvvBnE1t/?spm_id_from=333.1387.search.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719

https://www.bilibili.com/video/BV1uQvvBMEUU/?spm_id_from=333.1387.search.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719

https://www.bilibili.com/video/BV1G1vvBGEvh/?spm_id_from=333.1387.search.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719

相关推荐
小刘爱玩单片机1 小时前
【stm32协议外设篇】- DS18B20 单总线数字温度检测模块
c语言·stm32·单片机·嵌入式硬件
羽获飞2 小时前
从零开始学嵌入式之STM32——12.使用STM32普通引脚通过软件模拟实现I2C通讯协议
stm32·单片机·嵌入式硬件
LCG元2 小时前
嵌入式GUI设计:STM32F429+LVGL,智能仪表盘界面开发指南
驱动开发·stm32·嵌入式硬件
姜太公钓鲸2332 小时前
RAM就是运行内存,实际的存储介质是SRAM。上述文字中的运行内存、存储介质是什么意思?
stm32
意法半导体STM322 小时前
【官方原创】使用GPDMA进行SPI LCD整屏传输 LAT1435
网络·stm32·单片机·嵌入式硬件·mcu·网络协议·stm32开发
螺丝钉的扭矩一瞬间产生高能蛋白2 小时前
深入剖析FreeRTOS优先级继承机制:vTaskPriorityInherit与xTaskPriorityDisinherit源码解析
stm32·freertos·嵌入式软件·优先级反转
姜太公钓鲸2333 小时前
时钟周期是什么?
单片机·嵌入式硬件
bepeater12343 小时前
Laravel9.X核心特性全面解析
c语言·c++·c#·php
bing_feilong3 小时前
Jetson Orin Nano(3): Button Header
嵌入式硬件