STM32-红外遥控控制原理与示例(源码)

📅 2026/7/18 10:37:38 👁️ 阅读次数 📝 编程学习
STM32-红外遥控控制原理与示例(源码)

一、简介:

由于在B站上很多人私信我要红外遥控源码,因此本期出一期讲解,不会涉及很深入的理论讲解请放心享用,如果需要源码评论区留下邮箱,笔者看到第一时间会发送到指定邮箱。本章讲解红外收发原理以及实验,如有不对请大家指出改正。

二、传感器:

市面上有很多红外收发模块,以及遥控器,遥控器里面也是内嵌一个红外发射模块哈,我们可以购买如下常见的传感器作为本次实验器件。

三、红外收发原理:

3.1、 原理:

首先红外收发是基于NEC协议的,要知道它发送时是以38Khz的载波将数据发送出去的,为什么使用38Khz?因为38Khz不容易被干扰!,因此只需要将定时器调制成38Khz,然后按照NEC协议即可发送数据;那么接收呢?很简单接收的时候与发送是反相的电平,比如发送9ms高电平,那么接收的时候是9ms低电平;为什么是反相?这就跟NEC协议有关了,我们继续往下看。

3.2、NEC协议:

3.2.1 简介:

NEC协议红外遥控通信中广泛使用的一种协议,通常使用38kHz的载波频率进行调制,它采用了反相逻辑,在传输过程中,逻辑‘1’可能对应于红外LED的关闭状态(即没有光发射),而逻辑‘0’则对应于开启状态(即有光发射),因此接收时反过来接收判断即可。

3.2.2 NEC协议帧:

协议帧内由几个重要的码制,如下图,后面的高低电平时间的组合就得到前面的码制,引导码就可以理解为帧头,重复码就是长按的时候命令的重复发送,而不重新发送完整的地址和命令数据。

① 引导码:9ms的低电平+4.5ms的高电平

② 重复码:9ms的低电平+2.25ms的高电平

③ 数据1: 560us的低电平+1680us的高电平

④ 数据0: 560us的低电平+560us的高电平

3.2.3 NEC数据格式:

通过3.2.2学习我们知道了各种码制,可见下图,实际上结束码不存在图里面显化,这是因为结束码实际就是一个时间间隔,标志着一次完整消息的结束。这个高电平的时间长度可以更长,取决于控制器何时开始下一次传输。一帧完整的数据帧格式应由如下组成:

五、代码实现:

5.1、STM32红外发送逻辑:

extern uint16_t cmt; /************************************************************************** ** -------------------------------------------------------------------- ** ** @name : ADVANCE_TIM_Mode_Config ** @brief : None ** @param : None ** @retval : None ** @author data : 轩哥 2023-05-15 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ static void ADVANCE_TIM_Mode_Config(void) { TIM_OCInitTypeDef TIM_OCInitStructure; //输出比较定时器结构体声明 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定时器时基结构体声明 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); // 开启定时器时钟,即内部时钟CK_INT=72M /*****************************时基结构体初始化**********************************/ TIM_Cmd(TIM3, DISABLE); //失能TIM3 TIM_ITConfig(TIM3, TIM_IT_Update | TIM_IT_CC4, DISABLE); //失能IT中断 TIM_TimeBaseStructure.TIM_Period=25; // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断 TIM_TimeBaseStructure.TIM_Prescaler=35; // 分频系数 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频因子 ,配置死区时间时需要用到 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,设置为向上计数 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 初始化定时器 /*****************************输出比较结构体初始化**********************************/ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 配置为PWM模式1 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能 TIM_OCInitStructure.TIM_Pulse = 0; // 设置占空比大小 (这里不设置 外面再设置) TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出通道电平极性配置 TIM_OC1Init(TIM3, &TIM_OCInitStructure); //输出比较结构体初始化 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能预加ARR载寄存器 (因为ARR需要清零重新向上计数) TIM_Cmd(TIM3, ENABLE); // 使能计数器 } /*GPIO初始化*/ void HW_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; //定义一个GPIO_InitTypeDef类型的结构体 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE); //开启LED相关的GPIO外设时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //选择要控制的GPIO引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置引脚模式为通用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置翻转速度为50 GPIO_Init(GPIOA, &GPIO_InitStructure); //调用库函数,初始化GPIO ADVANCE_TIM_Mode_Config(); } /*引导码*/ void send_qd(void) { TIM_SetCompare1(TIM3, 9); Sys_delay_ms(9); TIM_SetCompare1(TIM3, 0); Sys_delay_us(4500); } /*重复码*/ void send_cf(void) { TIM_SetCompare1(TIM3, 9); Sys_delay_ms(1); TIM_SetCompare1(TIM3, 0); Sys_delay_us(2500); } /*数据1*/ void send_1(void) { TIM_SetCompare1(TIM3, 9); Sys_delay_us(560); TIM_SetCompare1(TIM3, 0); Sys_delay_us(1680); } /*数据0*/ void send_0(void) { TIM_SetCompare1(TIM3, 9); Sys_delay_us(560); TIM_SetCompare1(TIM3, 0); Sys_delay_us(560); } /*结束码*/ void send_end(void) { TIM_SetCompare1(TIM3, 9); Sys_delay_us(200); TIM_SetCompare1(TIM3, 0); Sys_delay_us(300); } //发送一字节数据 void Hw_Send_data(uint8_t data) { u8 i; for (i = 0; i < 8; i++) { if (data<<i & 0x80) { send_1(); } else { send_0(); } } } /*发送一帧数据*/ void nec_send(uint8_t data) { if(cmt<5) //如果小于5那认为是单击 { send_qd(); //引导码 Hw_Send_data(addr1); //地址码 Hw_Send_data(~addr1); //地址反码 Hw_Send_data(data); //数据码 Hw_Send_data(~data); //数据反码 }else //如果大于5认为长按了 { if(cmt==5) //到5认为长按了先发送一次引导码 { send_qd(); //引导码 } send_cf(); //发送重复码 } send_end(); //发送结束码 }

5.2、STM32红外接收逻辑:

void TIM4_IRQHandler(void) { if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET) //更新中断触发 { if(TIM_Values.DataFlag==1) //是否接收到了引导码 { if(TIM_Values.Period>3) //如果4次溢出没有收到数据要么准备接收重复码,要么完成了 { if(RmtCnt==0||TIM_Values.Period>9) //重复按键值为0或者溢出大于90ms没有接收到数据 TIM_Values.DataFlag=0; //清空引导码标志 判断收完数据,清0退出 } TIM_Values.Period++; //更新中断次数递增 } } if(TIM_GetITStatus(TIM4,TIM_IT_CC4)!=RESET) //捕获到上下边沿 { if(TIM_Values.StartFlag==0) //第一次进来必定是上升沿 { TIM_SetCounter(TIM4,0); //清空计数寄存器值 TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling);//配置为下降沿捕获、 TIM_Values.CrrValue=0; //捕获值清0 TIM_Values.StartFlag=1; //开始下降沿捕获 TIM_Values.Period=0; //自动重装载寄存器清零 } else { TIM_Values.CrrValue=TIM_GetCapture4(TIM4); //获取通道4 捕获寄存器的值 TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising);//配置为上升沿捕获 TIM_Values.StartFlag=0; //开始标志复0,重新判断捕获上升沿 //printf("%d\r\n",TIM_Values.CrrValue); //观察捕捉到的占空比时长 if(TIM_Values.DataFlag==1) //是否接收到了引导码 { if(TIM_Values.CrrValue>300&&TIM_Values.CrrValue < 800)//560US为标准值 { TIM_Values.Data<<=1; //内容左移一位 TIM_Values.Data|=0; //接收到0 IRdatas++; //接收到的bit数据加1 } else if(TIM_Values.CrrValue > 1400 && TIM_Values.CrrValue < 1800) //1680为标准值 { TIM_Values.Data<<=1; //内容左移一位 TIM_Values.Data|=1; //接收到1 IRdatas++; //接收到的bit数据加1 /*这是 NEC 码规定的110ms连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平 +97.94ms 高电平组成),如果在一帧数据发送完毕之后,按键仍然没有放开,则发射重复码, 即连发码,可以通过统计连发码的次数来标记按键按下的长短/次数。结束码 :0.56ms 高电平*/ } else if(TIM_Values.CrrValue > 2100 && TIM_Values.CrrValue < 2600) { if(RmtCnt>3) flagcf=1; IRdatas++; //接收到的bit数加1 RmtCnt++; //重复按键次数增加1次 } } else if(TIM_Values.CrrValue > 4100 && TIM_Values.CrrValue < 5300) //4500为标准值4.5ms { TIM_Values.DataFlag=1; //成功接收到引导码,标志位为1 IRdatas=0; //清空bit RmtCnt=0; //清除按键次数计数器 flagcf=0; //清空重复码标志位 TIM_Values.KeyLongFlag=0; //清空长按标志位 LED1_TOGGLE; } if(flagcf==1) //为了兼容红外发射重复码 { Remote =IRval; //重复赋值 TIM_Values.KeyLongFlag=1; //长按标志位 } else switch(IRdatas) { // case 8: //接收完特征码(特定设备地址ID) // if(TIM_Values.Data!=IR_ID) //如果接收到的ID与遥控器ID不同则数据全部清零从来 // { // IRdatas=0; //接收数据bit数清零。 // TIM_Values.DataFlag=0; //开始接收数据标志复位为0 // } // TIM_Values.Data = 0; //接收到的ID数据清零 // break; case 16://接收完特征反码(地址ID) //if ( (u8)~TIM_Values.Data != IR_ID ) //特定设备地址ID if((u8)(TIM_Values.Data>>8) != (u8)~(TIM_Values.Data&0xff)) //符合协议的设备即可 { IRdatas=0; //接收数据次数清零 TIM_Values.DataFlag=0; //开始接收数据标志复位为0 } TIM_Values.Data = 0; //接收到的ID数据清零 break; case 24://接收完数据正码 IRval=TIM_Values.Data; //把数据存到变量 TIM_Values.Data=0; //接收到的数据清零。 // printf("数据正码%x\r\n",IRval); break; case 32://接收完数据反码 //printf("数据正码%x\r\n",~TIM_Values.Data);//反码的反码 if(IRval!=(u8)~TIM_Values.Data) //如果正码和反码不相等的话 { IRdatas=0; //接收bit数据清零 IRval=0; //数据byte清零 } else { // printf("数据反码也正确"); } TIM_Values.Data = 0; //接收到的数据清0,准备接收下一次中断数据 Remote = IRval; //把收到的正确按键值赋值给全局变量 Remote IRdatas=33; //赋值接收bit位为33,防止等下结束码进入中断又响应32导致数据被清零 break; // case 34://重复码这里比32多两次认为多一次重复按键 // Remote =IRval; //重复赋值 // TIM_Values.KeyLongFlag=1; // IRdatas=33; //重新赋值回33,防止重复按键次数太多,导致数值溢出,且不需要写后面的switch选择 // break; } } } TIM_ClearITPendingBit(TIM4,TIM_IT_Update|TIM_IT_CC4); }

5.3、main.c按键触发红外发送任务逻辑:

void task2(void *pvParameters) { uint8_t flag=0; EventBits_t EventBits; while (1) { /*等待标志函数*/ EventBits = xEventGroupWaitBits(evebtgroup_handle, //事件标志组的句柄 EVENTBIT_1, //需要等待的时间标志位 pdFAIL, //成功等待到标志位就清除标志位 pdTRUE, //标志组等待的位全部为1 0); //等待阻塞的时间——死等 if (EventBits == 0x02) { if(flag==0) { flag=1; OLED_CLS3(); WriteCmd(0xC8); WriteCmd(0xA1); Oled_ShowString(40, 3, (uint8_t *)"Begin"); vTaskDelay(500); OLED_CLS3(); TIM_Cmd(TIM4,ENABLE); TIM_ITConfig(TIM4,TIM_IT_Update|TIM_IT_CC4,ENABLE); //使能中断 } if(Remote!=0) //如果Remote不为0证明有数据 { Remote_Scan(Remote); //接收处理 } } /*等待标志函数*/ EventBits = xEventGroupWaitBits(evebtgroup_handle, //事件标志组的句柄 EVENTBIT_0, //需要等待的时间标志位 pdFAIL, //成功等待到标志位就清除标志位 pdTRUE, //标志组等待的位全部为1 0); //等待阻塞的时间——死等 if (EventBits == 0X03 || EventBits == 0X01) //按键触发 { /*发送红外信号*/ nec_send(0x62); xEventGroupClearBits(evebtgroup_handle, EVENTBIT_0); } vTaskDelay(10); //延时500ms任务切换及测距间隔 } }

六、实验示例:

通过遥控器短按/长按可见OLED已经显示正确接收到,通过按键进行短按/长按,红外模块也是正常收发。

七、总结:

通过以上学习我们知道只要掌握了NEC协议的数据帧格式就可以尽情使用红外控制功能了,如果需要实验示例源码评论区留下邮箱。