红外遥控(外部中断)(51单片机学习笔记)

📅 2026/8/24 3:06:54
红外遥控(外部中断)(51单片机学习笔记)
在上一篇文章《从直流电机调速PWM到AD/DA 模数/数模转换》中我们完成了51单片机学习的最后一个基础模块。作为51单片机系列学习的收尾本文将深入探讨红外遥控技术——这是嵌入式系统中一种经典且实用的无线通信方式。通过本文你将掌握红外遥控的工作原理、NEC编码协议、51单片机的外部中断与定时器配置并最终实现两个完整的实战项目红外遥控计数显示和红外遥控电机调速。这些项目将为你后续制作更复杂的综合练手项目打下坚实基础。如果觉得有收获可以为我的文章点点小赞这将激励我创作更好的内容谢谢一、红外遥控1.1 红外遥控简介- 红外遥控是利用红外光进行通信的设备由红外LED将调制后的信号发出由专用的红外接收头进行解调输出- 通信方式单工异步- 红外LED波长940nm- 通信协议标准NEC标准1.2 硬件电路1.3 基本发送与接收- 空闲状态红外LED不亮接收头输出高电平- 发送低电平红外LED以38KHz频率闪烁发光接收头输出低电平- 发送高电平红外LED不亮接收头输出高电平1.4 NEC编码1.5 遥控器键码1.6 51单片机的外部中断1.7 外部中断寄存器用来配置外部中断的Int0代码模块Int0.c文件↓----------------------------#include REGX52.H /** brief 外部中断0初始化 param 无 retval 无 */ void Int0_Init(void) { IT01; //1为下降沿出发0为低电平触发 IE00; EX01; EA1; PX01; } /*外部中断0中断函数模板 void Int0_Routine(void) interrupt 0 { } */----------------------------1.8 配置定时器0用以红外遥控通过给Timer0增加扩展升级为Timer0_IR增加1.开始计时2.计时写入3.计时读出三个和红外遥控配套的子函数使定时器模块为之后红外通信的时间计算做基础Timer0_IR.c文件↓----------------------------#include REGX52.H /** brief 定时器0初始化1毫秒11.0592Mhz param 无 retval 无 */ void Timer0_Init() { TMOD0xF0; //把TMOD的低四位清零高四位保持不变 TMOD|0x01; //把TMOD的最低位置1高四位保持不变 TL00; //设置定时低八位初值 TH00; //设置定时高八位初值 TF00; //清除TF0标志 TR00; //定时器0不计数 } void Timer0_SetCounter(unsigned int Value)//准备计时将输入值赋给TL0TH0 { TL0Value%256; TH0Value/256; } unsigned int Timer0_GetCounter(void)//获取计时返回TH0TL0共16位 { unsigned int Result0; Result(TH08)|TL0; return Result; } void Timer0_Run(unsigned char Flag) //开始计时给1开始计时 { TR0Flag; } /*定时器0函数中断模板1s void Timer0_Routine() interrupt 1 //定时器0中断函数 { static unsigned int T0Count0; TL00x66; //设置定时初值 TH00xFC; //设置定时初值 T0Count; if(T0Count1000) { T0Count0; } } */----------------------------1.9 红外遥控代码模块(1)先写是否获取数据的Pool和是否获取Repeat指令的Pool的标志的子函数#include REGX52.H #include Timer0_IR.h #include Int0.h unsigned int IR_Time; unsigned char IR_State; unsigned char IR_Date[4]; unsigned char IR_pDate0; unsigned char IR_DateFlag; unsigned char IR_RepeatFlag0; unsigned char IR_Address; unsigned char IR_Command; void IR_Init(void)//红外遥控初始化 { Timer0_Init();//初始化红外定时器 Int0_Init();//打开红外定时器 } unsigned char IR_GetDateFlag(void)//返回是否获取数据的Pool { unsigned char PoolResult10; if(IR_DateFlag) { IR_DateFlag0; PoolResult11; } return PoolResult1; } unsigned char IR_GetRepeatFlag(void)//返回是否获取Repeat指令的Pool { unsigned char PoolResult20; if(IR_RepeatFlag) { IR_RepeatFlag0; PoolResult21; } return PoolResult2; }(2)将获取数据和指令的功能封装成子函数unsigned char IR_GetAddress(void)//最终获取的地址值 { return IR_Address; } unsigned char IR_GetCommand(void)//最终获取的指令值 { return IR_Command; }(3)写外部中断函数——有接收到红外信号就进中断利用红外NEC协议编码记住红外接收器出现电平下降沿时进入0号外部中断函数这是在定时器寄存器那里配置的以下是完整的中断函数里面的内容有点复杂但我几乎每一句都有加了注释可以停下来用心慢慢看//这个红外遥控外部中断被设置后会在红外接收器出现电平下降沿时进入0号中断函数 void Int0_Routine(void) interrupt 0 { if(IR_State0) //第一次出现下降沿IR_State0红外遥控开始读取指令标志取消 空闲 { Timer0_SetCounter(0); //清理定时器计数准备计时 Timer0_Run(1); //开始计时 IR_State1; //打开接收指令函数 } //此时IR_State1接收指令函数被打开如果再出现下降沿就会进入以下判断 else if(IR_State1) //再一次出现下降沿进行指令接收并分析是什么指令 { IR_TimeTimer0_GetCounter(); //获取两次下降沿的间隔时间 Timer0_SetCounter(0); //将定时器计数清零重新计时方便下次提取时间 if(IR_Timegt;12442-500 amp;amp; IR_Timelt;12442500)//接收到了开始指令 { IR_State2; //接收到了开始指令进行下一部分的接收数据函数 } else if(IR_Timegt;10369-500 amp;amp; IR_Timelt;10369500)//接收到了Repeat指令 { IR_RepeatFlag1; //接收到Repeat信号IR_RepeatFlag标志置一 Timer0_Run(0); //停止计时 IR_State0; //返回状态0等待下一个下降沿 } else //其他干扰情况 { IR_State1; //保持待接收 } } //此时如果接收到开始指令则IR_State2下面进行接收数据函数 else if(IR_State2) //再次出现下降沿进行接收数据函数并验证数据将接收完成标志置1 { IR_TimeTimer0_GetCounter(); //获取两次下降沿的间隔时间 Timer0_SetCounter(0); //将定时器计数清零重新计时方便下次提取时间 if(IR_Timegt;1032-500 amp;amp; IR_Timelt;1032500)//接收到一位0 { IR_Date[IR_pDate/8]amp;~(0x01lt;lt;(IR_pDate%8)); //将0写入由小到大与写1互斥由4个char类型组成的共32bit 的数组 IR_pDate; //计数加1做到与写1互斥 } else if(IR_Timegt;2074-500 amp;amp; IR_Timelt;2074500) //接收到一位1 { IR_Date[IR_pDate/8]|(0x01lt;lt;(IR_pDate%8)); //将1写入由小到大与写0互斥由4个char类型组成的共32bit 的数组 IR_pDate; //计数加1做到与写0互斥 } else //接收到错误数据 { IR_pDate0; //清零计数 IR_State1; //保持待接收舍弃当前所有数据待接收下一个指令 } if(IR_pDategt;32) //判断接收到32位bit接收完成 { IR_pDate0; //清零计数 if((IR_Date[0]~IR_Date[1]) amp;amp; (IR_Date[2]~IR_Date[3]))//判断接收数据是否满足对应相反关系完成数据校验 { IR_AddressIR_Date[0]; //转存数据 IR_CommandIR_Date[2]; //转存指令 IR_DateFlag1; //接收完毕将接收完成标志置1IR_DateFlag标志置一 } Timer0_Run(0); //接收完数据和指令停止外部中断定时器 IR_State0; //返回至初始状态 } } }综上完整的IR代码模块为IR.h头文件↓----------------------------#ifndef __IR_H__ #define __IR_H__ //红外遥控器宏定义 #define IR_POWER 0x45 //开关 #define IR_MODE 0x46 #define IR_MUTE 0x47 //静音 #define IR_START_STOP 0x44 //蓝色开始、停止按键 #define IR_PREVIOUS 0x40 //向左快进 #define IR_NEXT 0x43 //向右快进 #define IR_EQ 0x07 #define IR_VOL_MINUS 0x15 //VOL- #define IR_VOL_ADD 0x09 //VOL #define IR_0 0x16 #define IR_RPT 0x19 #define IR_U_SD 0x0D #define IR_1 0x0C #define IR_2 0x18 #define IR_3 0x5E #define IR_4 0x08 #define IR_5 0x1C #define IR_6 0x5A #define IR_7 0x42 #define IR_8 0x52 #define IR_9 0x4A void IR_Init(void);//红外遥控初始化 unsigned char IR_GetDateFlag(void);//返回是否获取数据的Pool unsigned char IR_GetRepeatFlag(void);//返回是否获取指令的Pool unsigned char IR_GetAddress(void);//最终获取的地址值 unsigned char IR_GetCommand(void);//最终获取的指令值 #endif----------------------------IR.c文件↓----------------------------#include REGX52.H #include Timer0_IR.h #include Int0.h unsigned int IR_Time; unsigned char IR_State; unsigned char IR_Date[4]; unsigned char IR_pDate0; unsigned char IR_DateFlag; unsigned char IR_RepeatFlag0; unsigned char IR_Address; unsigned char IR_Command; void IR_Init(void)//红外遥控初始化 { Timer0_Init();//初始化红外定时器 Int0_Init();//打开外部中断 } unsigned char IR_GetDateFlag(void)//返回是否获取数据的Pool { unsigned char PoolResult10; if(IR_DateFlag) { IR_DateFlag0; PoolResult11; } return PoolResult1; } unsigned char IR_GetRepeatFlag(void)//返回是否获取Repeat指令的Pool { unsigned char PoolResult20; if(IR_RepeatFlag) { IR_RepeatFlag0; PoolResult21; } return PoolResult2; } unsigned char IR_GetAddress(void) //最终获取的地址值 { return IR_Address; } unsigned char IR_GetCommand(void) //最终获取的指令值 { return IR_Command; } //这个红外遥控外部中断被设置后会在红外接收器出现电平下降沿时进入0号中断函数 void Int0_Routine(void) interrupt 0 { if(IR_State0) //第一次出现下降沿IR_State0红外遥控开始读取指令标志取消空闲 { Timer0_SetCounter(0); //清理定时器计数准备计时 Timer0_Run(1); //开始计时 IR_State1; //打开接收指令函数 } //此时IR_State1接收指令函数被打开如果再出现下降沿就会进入以下判断 else if(IR_State1) //再一次出现下降沿进行指令接收并分析是什么指令 { IR_TimeTimer0_GetCounter(); //获取两次下降沿的间隔时间 Timer0_SetCounter(0); //将定时器计数清零重新计时方便下次提取时间 if(IR_Timegt;12442-500 amp;amp; IR_Timelt;12442500)//接收到了开始指令 { IR_State2; //接收到了开始指令进行下一部分的接收数据函数 } else if(IR_Timegt;10369-500 amp;amp; IR_Timelt;10369500)//接收到了Repeat指令 { IR_RepeatFlag1; //接收到Repeat信号IR_RepeatFlag标志置一 Timer0_Run(0); //停止计时 IR_State0; //返回状态0等待下一个下降沿 } else //其他干扰情况 { IR_State1; //保持待接收 } } //此时如果接收到开始指令则IR_State2下面进行接收数据函数 else if(IR_State2) //再次出现下降沿进行接收数据函数并验证数据将接收完成标志置1 { IR_TimeTimer0_GetCounter(); //获取两次下降沿的间隔时间 Timer0_SetCounter(0); //将定时器计数清零重新计时方便下次提取时间 if(IR_Timegt;1032-500 amp;amp; IR_Timelt;1032500)//接收到一位0 { IR_Date[IR_pDate/8]amp;~(0x01lt;lt;(IR_pDate%8)); //将0写入由小到大与写1互斥由4个char类型组成的共32bit 的数组 IR_pDate; //计数加1做到与写1互斥 } else if(IR_Timegt;2074-500 amp;amp; IR_Timelt;2074500) //接收到一位1 { IR_Date[IR_pDate/8]|(0x01lt;lt;(IR_pDate%8)); //将1写入由小到大与写0互斥由4个char类型组成的共32bit 的数组 IR_pDate; //计数加1做到与写0互斥 } else //接收到错误数据 { IR_pDate0; //清零计数 IR_State1; //保持待接收舍弃当前所有数据待接收下一个指令 } if(IR_pDategt;32) //判断接收到32位bit接收完成 { IR_pDate0; //清零计数 if((IR_Date[0]~IR_Date[1]) amp;amp; (IR_Date[2]~IR_Date[3]))//判断接收数据是否满足对应相反关系完成数据校验 { IR_AddressIR_Date[0]; //转存数据 IR_CommandIR_Date[2];//转存指令 IR_DateFlag1; //接收完毕将接收完成标志置1IR_DateFlag标志置一 } Timer0_Run(0); //接收完数据和指令停止外部中断定时器 IR_State0; //返回至初始状态 } } }----------------------------1.10 实例一17.1.1红外遥控main.c文件↓----------------------------#include REGX52.H #include Delay.h #include LCD1602.h #include IR.h unsigned char Num; unsigned char Address,Command; void main() { LCD_Init(); LCD_ShowString(1,1,ADDR CMD NUM); LCD_ShowString(2,1,00 00 000); IR_Init(); while(1) { if(IR_GetDateFlag() || IR_GetRepeatFlag())//判断接收标志为1或是重发标志为1重发为1则会取得过去离重发标志 最近的一帧数据 { AddressIR_GetAddress(); //获取红外遥控地址码 CommandIR_GetCommand(); //获取指令码 LCD_ShowHexNum(2,1,Address,2); LCD_ShowHexNum(2,7,Command,2); if(CommandIR_VOL_MINUS) //给遥控的VOL-添加让计数减一的功能 { Num--; } if(CommandIR_VOL_ADD) //给遥控的VOL-添加让计数加一的功能 { Num; } LCD_ShowNum(2,12,Num,3); } } }----------------------------1.11 实例二17.2.1红外遥控电机调速电机代码模块Motor.c文件↓----------------------------#include REGX52.H #include Timer1.h //引脚定义 sbit MotorP1^0; unsigned char Counter0,Compare0; /** brief 电机初始化 param 无 retval 无 */ void Motor_Init(void)//电机初始化 { Timer1_Init(); } /** brief 电机设置速度 param Speed 要设置的速度范围0~100100是全转 retval 无 */ void Motor_SetSpeed(unsigned char Speed)//电机速度设置 //这个speed是速度比较值 { CompareSpeed; } void Timer1_Routine() interrupt 3 //电机——定时器1中断函数 { Counter; // Counter%100; //0到99的线性计数计数值变化范围限制在0~99 if(Counterlt;Compare)//计数值小于比较值 { Motor1; //1取反后给0给低电平电机启动 } else { Motor0; //0取反后给1给高电平电机启动 } TL10xA4; //设置定时初值 TH10xFF; //设置定时初值100us }----------------------------main.c文件↓----------------------------#include REGX52.H #include Delay.h #include Key.h #include Nixie.h #include IR.h #include Motor.h unsigned char Command; unsigned char Motor_Speed_Gear0; //这个Motor_Speed_Gear是速度快慢挡的第几挡 void main() { Motor_Init(); //电机初始化 IR_Init(); //红外遥控初始化 while(1) { if(IR_GetDateFlag()1) //如果收到数据帧仅单发无连发功能 { CommandIR_GetCommand(); //获取遥控器命令码 if(CommandIR_0){Motor_Speed_Gear0;}// if(CommandIR_1){Motor_Speed_Gear1;}// if(CommandIR_2){Motor_Speed_Gear2;}// if(CommandIR_3){Motor_Speed_Gear3;}//根据遥控器命令码设置速度 if(Motor_Speed_Gear0){Motor_SetSpeed(0);} if(Motor_Speed_Gear1){Motor_SetSpeed(40);} if(Motor_Speed_Gear2){Motor_SetSpeed(60);} if(Motor_Speed_Gear3){Motor_SetSpeed(80);} } Nixie(1,Motor_Speed_Gear); //数码管显示速度 } }----------------------------二、总结本文系统性地介绍了基于51单片机的红外遥控技术实现从基础原理到完整代码实现涵盖了以下核心内容红外遥控基础介绍了红外遥控的工作原理、通信方式单工、异步、硬件电路以及NEC编码协议标准。51单片机硬件支持详细讲解了外部中断INT0和定时器Timer0的配置方法这是红外信号接收和时序解析的关键硬件基础。代码模块化设计将红外遥控功能封装为独立的IR模块包含初始化、数据获取、指令解析等函数提高了代码的可复用性和可维护性。完整实现流程通过状态机的方式实现了NEC协议的解码过程包括引导码识别、数据位接收、校验和验证等关键步骤。实践应用案例提供了两个完整的应用实例红外遥控计数显示和红外遥控电机调速展示了红外遥控在实际项目中的应用方法。技术要点总结硬件配置正确配置外部中断触发方式下降沿触发和定时器工作模式是红外接收的基础。时序精度NEC协议对时序要求严格需要精确测量脉冲宽度来区分逻辑0和逻辑1。状态机设计采用状态机IR_State管理解码过程使代码逻辑清晰易于调试和维护。数据校验通过地址码和命令码的反码校验机制提高了数据传输的可靠性。模块化编程将红外遥控功能封装为独立模块便于在不同项目中复用和移植。学习建议建议读者先理解NEC协议的时间参数再结合代码中的状态转换逻辑进行学习。调试时可以使用示波器观察红外接收头的输出波形验证解码逻辑的正确性。在实际应用中可以根据需要调整时序容差代码中的±500微秒以适应不同遥控器的特性。本文提供的代码框架可以扩展支持其他红外协议只需修改状态机中的时间判断条件即可。都看到这了点点赞吧