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

📅 发布时间:2026/7/18 10:29:28
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_INT72M /*****************************时基结构体初始化**********************************/ TIM_Cmd(TIM3, DISABLE); //失能TIM3 TIM_ITConfig(TIM3, TIM_IT_Update | TIM_IT_CC4, DISABLE); //失能IT中断 TIM_TimeBaseStructure.TIM_Period25; // 自动重装载寄存器的值累计TIM_Period1个频率后产生一个更新或者中断 TIM_TimeBaseStructure.TIM_Prescaler35; // 分频系数 TIM_TimeBaseStructure.TIM_ClockDivisionTIM_CKD_DIV1; // 时钟分频因子 配置死区时间时需要用到 TIM_TimeBaseStructure.TIM_CounterModeTIM_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 (datai 0x80) { send_1(); } else { send_0(); } } } /*发送一帧数据*/ void nec_send(uint8_t data) { if(cmt5) //如果小于5那认为是单击 { send_qd(); //引导码 Hw_Send_data(addr1); //地址码 Hw_Send_data(~addr1); //地址反码 Hw_Send_data(data); //数据码 Hw_Send_data(~data); //数据反码 }else //如果大于5认为长按了 { if(cmt5) //到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.DataFlag1) //是否接收到了引导码 { if(TIM_Values.Period3) //如果4次溢出没有收到数据要么准备接收重复码要么完成了 { if(RmtCnt0||TIM_Values.Period9) //重复按键值为0或者溢出大于90ms没有接收到数据 TIM_Values.DataFlag0; //清空引导码标志 判断收完数据清0退出 } TIM_Values.Period; //更新中断次数递增 } } if(TIM_GetITStatus(TIM4,TIM_IT_CC4)!RESET) //捕获到上下边沿 { if(TIM_Values.StartFlag0) //第一次进来必定是上升沿 { TIM_SetCounter(TIM4,0); //清空计数寄存器值 TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling);//配置为下降沿捕获、 TIM_Values.CrrValue0; //捕获值清0 TIM_Values.StartFlag1; //开始下降沿捕获 TIM_Values.Period0; //自动重装载寄存器清零 } else { TIM_Values.CrrValueTIM_GetCapture4(TIM4); //获取通道4 捕获寄存器的值 TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising);//配置为上升沿捕获 TIM_Values.StartFlag0; //开始标志复0重新判断捕获上升沿 //printf(%d\r\n,TIM_Values.CrrValue); //观察捕捉到的占空比时长 if(TIM_Values.DataFlag1) //是否接收到了引导码 { if(TIM_Values.CrrValue300TIM_Values.CrrValue 800)//560US为标准值 { TIM_Values.Data1; //内容左移一位 TIM_Values.Data|0; //接收到0 IRdatas; //接收到的bit数据加1 } else if(TIM_Values.CrrValue 1400 TIM_Values.CrrValue 1800) //1680为标准值 { TIM_Values.Data1; //内容左移一位 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(RmtCnt3) flagcf1; IRdatas; //接收到的bit数加1 RmtCnt; //重复按键次数增加1次 } } else if(TIM_Values.CrrValue 4100 TIM_Values.CrrValue 5300) //4500为标准值4.5ms { TIM_Values.DataFlag1; //成功接收到引导码标志位为1 IRdatas0; //清空bit RmtCnt0; //清除按键次数计数器 flagcf0; //清空重复码标志位 TIM_Values.KeyLongFlag0; //清空长按标志位 LED1_TOGGLE; } if(flagcf1) //为了兼容红外发射重复码 { Remote IRval; //重复赋值 TIM_Values.KeyLongFlag1; //长按标志位 } else switch(IRdatas) { // case 8: //接收完特征码特定设备地址ID // if(TIM_Values.Data!IR_ID) //如果接收到的ID与遥控器ID不同则数据全部清零从来 // { // IRdatas0; //接收数据bit数清零。 // TIM_Values.DataFlag0; //开始接收数据标志复位为0 // } // TIM_Values.Data 0; //接收到的ID数据清零 // break; case 16://接收完特征反码地址ID //if ( (u8)~TIM_Values.Data ! IR_ID ) //特定设备地址ID if((u8)(TIM_Values.Data8) ! (u8)~(TIM_Values.Data0xff)) //符合协议的设备即可 { IRdatas0; //接收数据次数清零 TIM_Values.DataFlag0; //开始接收数据标志复位为0 } TIM_Values.Data 0; //接收到的ID数据清零 break; case 24://接收完数据正码 IRvalTIM_Values.Data; //把数据存到变量 TIM_Values.Data0; //接收到的数据清零。 // printf(数据正码%x\r\n,IRval); break; case 32://接收完数据反码 //printf(数据正码%x\r\n,~TIM_Values.Data);//反码的反码 if(IRval!(u8)~TIM_Values.Data) //如果正码和反码不相等的话 { IRdatas0; //接收bit数据清零 IRval0; //数据byte清零 } else { // printf(数据反码也正确); } TIM_Values.Data 0; //接收到的数据清0准备接收下一次中断数据 Remote IRval; //把收到的正确按键值赋值给全局变量 Remote IRdatas33; //赋值接收bit位为33防止等下结束码进入中断又响应32导致数据被清零 break; // case 34://重复码这里比32多两次认为多一次重复按键 // Remote IRval; //重复赋值 // TIM_Values.KeyLongFlag1; // IRdatas33; //重新赋值回33防止重复按键次数太多导致数值溢出且不需要写后面的switch选择 // break; } } } TIM_ClearITPendingBit(TIM4,TIM_IT_Update|TIM_IT_CC4); }5.3、main.c按键触发红外发送任务逻辑void task2(void *pvParameters) { uint8_t flag0; EventBits_t EventBits; while (1) { /*等待标志函数*/ EventBits xEventGroupWaitBits(evebtgroup_handle, //事件标志组的句柄 EVENTBIT_1, //需要等待的时间标志位 pdFAIL, //成功等待到标志位就清除标志位 pdTRUE, //标志组等待的位全部为1 0); //等待阻塞的时间——死等 if (EventBits 0x02) { if(flag0) { flag1; 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协议的数据帧格式就可以尽情使用红外控制功能了如果需要实验示例源码评论区留下邮箱。