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

目录

[17.1 红外遥控简介](#17.1 红外遥控简介)

[17.2 红外遥控硬件电路](#17.2 红外遥控硬件电路)

[17.3 NEC编码](#17.3 NEC编码)

[17.4 51单片机+红外遥控的硬件连接](#17.4 51单片机+红外遥控的硬件连接)

[17.5 状态机+中断+定时器的红外解码](#17.5 状态机+中断+定时器的红外解码)

[17.5.1 中断模块](#17.5.1 中断模块)

⭐Int0.c

⭐Int0.h

[17.5.2 定时器模块](#17.5.2 定时器模块)

⭐Timer0.c

⭐Timer0.h

[17.5.3 状态机红外解码模块](#17.5.3 状态机红外解码模块)

⭐IR.c

⭐IR.h

[17.6 项目示例1:红外遥控](#17.6 项目示例1:红外遥控)

✅项目功能:

✅项目架构:

[✅main.c 主函数:](#✅main.c 主函数:)

[17.7 项目示例2:红外遥控电机调速](#17.7 项目示例2:红外遥控电机调速)

✅项目功能:

✅项目架构:

⭐Timer1.c

⭐Timer1.h

⭐Motor.c

⭐Motor.h

[✅main.c 主函数:](#✅main.c 主函数:)


17.1 红外遥控简介

红外遥控是一种以 红外线(波长约 940nm,不可见光) 为传输介质的短距离无线通信方式,核心由两部分组成:

  • 发射端:红外遥控器(内置红外发射管),按下按键时会发送编码后的红外信号;
  • 接收端:红外接收头(如 HS0038),接在 51 单片机上,负责接收并解调红外信号,输出单片机可识别的电信号。

17.2 红外遥控硬件电路



红外信号会调制在 38kHz 载波上(抗干扰),接收头会自动解调,输出 "去载波" 后的高低电平信号,单片机只需解析电平的时间长短即可。

17.3 NEC编码


地址码:区分不同遥控器(比如电视 / 空调遥控器,避免串扰)
命令码:对应按键的功能(比如 "1" 键、"+" 键,每个按键对应唯一的 8 位数据);
地址反码与命令反码均是用于校验作用,确保数据接收无误

17.4 51单片机+红外遥控的硬件连接

红外接收头(HS0038)接线极简单,仅需 3 根线,推荐接外部中断引脚(避免轮询占用 CPU):

为什么选外部中断口?因为红外信号的电平变化是 "瞬间的",用中断可以及时捕捉引导码的下降沿,避免数据丢失。

红外信号会调制在 38kHz 载波上(抗干扰),接收头会自动解调,输出 "去载波" 后的高低电平信号,单片机只需解析电平的时间长短即可。

17.5 状态机+中断+定时器的红外解码

17.5.1 中断模块

⭐Int0.c

cpp 复制代码
#include <REGX52.H>  // 52系列单片机寄存器定义头文件,包含IT0、IE0、EX0等中断相关寄存器的定义

/**
  * @brief  外部中断0(INT0)初始化函数
  * @param  无
  * @retval 无
  * @note   INT0对应单片机P3.2引脚,此函数配置该中断为下降沿触发、开启中断并设置高优先级
  */
void Int0_Init(void)
{
	// 1. 配置外部中断0的触发方式
	IT0=1;  // IT0是TCON寄存器的bit0位:
	        // IT0=1 → 外部中断0由**下降沿**触发(红外遥控解码常用此方式,捕捉电平突变)
	        // IT0=0 → 外部中断0由**低电平**触发(适合持续低电平触发的场景)
	
	// 2. 清除外部中断0的中断请求标志
	IE0=0;  // IE0是TCON寄存器的bit1位:
	        // IE0=1表示有外部中断0的中断请求,初始化时手动清零,避免误触发
	
	// 3. 开启外部中断0的中断允许
	EX0=1;  // EX0是IE寄存器的bit0位:
	        // EX0=1 → 允许外部中断0请求,EX0=0 → 关闭外部中断0
	
	// 4. 开启总中断允许(CPU级)
	EA=1;   // EA是IE寄存器的bit7位:
	        // EA=1 → 开启单片机总中断(所有中断的总开关,必须开启否则任何中断都无效)
	        // EA=0 → 关闭所有中断,即使单个中断允许位开启也无效
	
	// 5. 设置外部中断0的优先级为高优先级
	PX0=1;  // PX0是IP寄存器的bit0位:
	        // PX0=1 → 外部中断0为高优先级,PX0=0 → 为低优先级
	        // 高优先级中断可打断低优先级中断,适合红外解码这种需要及时响应的场景
}

/* 外部触发中断模板
void Int0_Routine(void) interrupt 0
{
	// 中断服务函数主体:中断触发后执行的代码(如红外解码逻辑、按键处理等)
	// interrupt 0 是固定写法,表示这是外部中断0的中断服务函数(中断号0对应INT0)
}
*/

⭐Int0.h

cpp 复制代码
#ifndef _INT0_H_
#define _INT0_H_

void Int0_Init(void);
	
#endif

17.5.2 定时器模块

⭐Timer0.c

cpp 复制代码
#include <REGX52.H>  // 52系列单片机寄存器定义头文件,包含TMOD、TL0、TH0等定时器相关寄存器

/**
  * @brief  定时器0初始化函数
  * @param  无
  * @retval 无
  * @note   配置定时器0为16位定时器模式(模式1),晶振为12.000MHz,初始不启动计时
  *         12MHz晶振下,定时器0每计数1次耗时1us(机器周期=12/晶振频率=1us)
  */
void Timer0_Init(void)		//1毫秒@12.000MHz(注:此处注释仅标注晶振,实际初值为0,需手动设置)
{
	// 配置定时器0的工作模式(TMOD寄存器:高4位控制定时器1,低4位控制定时器0)
	TMOD &= 0xF0;		// 清零定时器0的模式位(bit0~bit3),保留定时器1的配置
	TMOD |= 0x01;		// 设置定时器0为模式1(16位定时/计数模式,无自动重装)
	
	// 设置定时器初始计数值(此处初始化为0,后续通过Timer0_SetCounter手动设置)
	TL0 = 0x00;		// 低8位初值设为0
	TH0 = 0x00;		// 高8位初值设为0
	
	TF0 = 0;		// 清除定时器0的溢出标志(TF0=1表示定时器溢出,初始化时清零避免误触发)
	TR0 = 0;		// 定时器0运行控制位置0 → 初始化后定时器不启动计时
}

/**
  * @brief  设置定时器0的计数值
  * @param  Value:要设置的16位计数值(范围0~65535)
  * @retval 无
  * @note   将16位数值拆分为高8位(TH0)和低8位(TL0)分别写入定时器寄存器
  */
void Timer0_SetCounter(unsigned char Value)  // 注:参数建议改为unsigned int更合理,此处保留原代码
{
	TH0 = Value / 256;  // 取Value的高8位(除以256等价于右移8位),写入TH0寄存器
	TL0 = Value % 256;  // 取Value的低8位(对256取余),写入TL0寄存器
}

/**
  * @brief  获取定时器0当前的计数值
  * @param  无
  * @retval 当前16位计数值(TH0<<8 | TL0),范围0~65535
  * @note   将高8位(TH0)和低8位(TL0)合并为一个16位整数返回
  */
unsigned int Timer0_GetCounter(void)
{
	// TH0左移8位(补全高8位),再与TL0按位或,合并为完整的16位计数值
	return (TH0 << 8) | TL0;
}

/**
  * @brief  控制定时器0的启停
  * @param  Flag:启停标志(1=启动定时器,0=停止定时器)
  * @retval 无
  * @note   通过TR0寄存器控制,TR0=1时定时器开始计数,TR0=0时停止
  */
void Timer_Run(unsigned char Flag)
{
	TR0 = Flag;  // TR0是定时器0的运行控制位,1启动,0停止
}

⭐Timer0.h

cpp 复制代码
#ifndef _TIMER0_H_
#define _TIMER0_H_

void Timer0_Init(void);
void Timer0_SetCounter(unsigned char Value);
unsigned int Timer0_GetCounter(void);
void Timer_Run(unsigned char Flag);

#endif

17.5.3 状态机红外解码模块

⭐IR.c

cpp 复制代码
#include <REGX52.H>      // 52系列单片机寄存器定义头文件
#include "Timer0.h"     // 定时器0驱动头文件(包含初始化、计数值设置/获取、启停等函数)
#include "Int0.h"       // 外部中断0驱动头文件(包含中断初始化等函数)

// 全局变量定义 - 红外解码核心变量
unsigned int IR_Time;    // 存储定时器0的计数值,用于计算红外信号电平持续时间
unsigned char IR_State;  // 红外解码状态机标志:0-初始态 1-检测引导码 2-接收数据位

unsigned char IR_Data[4];// 存储红外接收的4字节数据:[0]地址码 [1]地址反码 [2]命令码 [3]命令反码
unsigned char IR_pData;  // 数据位计数指针(0-31),记录当前接收的是第几位数据

// 功能标志位
unsigned char IR_DataFlag;   // 数据接收完成标志:1-接收到有效数据
unsigned char IR_RepeatFlag; // 重复码标志:1-检测到长按重复码
unsigned char IR_Address;    // 解析后的有效地址码
unsigned char IR_Command;    // 解析后的有效命令码(按键对应的数值)

/**
  * @brief  红外遥控初始化函数
  * @param  无
  * @retval 无
  * @note   初始化定时器0(用于计时)和外部中断0(用于捕捉红外信号电平变化)
  */
void IR_Init(void)
{
	Timer0_Init();  // 初始化定时器0(需在Timer0.h中实现:定时10us/1us等基础配置)
	Int0_Init();    // 初始化外部中断0(需在Int0.h中实现:下降沿触发)
}

/**
  * @brief  获取红外数据接收完成标志
  * @param  无
  * @retval 1-接收到有效数据,0-未接收到
  * @note   调用后会清零标志位,避免重复处理同一帧数据
  */
unsigned char IR_GetDataFlag(void)
{
	if(IR_DataFlag)  // 检测到有效数据
	{
		IR_DataFlag=0; // 清零标志位
		return 1;      // 返回1表示有新数据
	}else
	{
		return 0;      // 无新数据返回0
	}
}

/**
  * @brief  获取红外重复码标志(长按按键)
  * @param  无
  * @retval 1-检测到重复码,0-未检测到
  * @note   调用后会清零标志位,避免重复响应长按
  */
unsigned char IR_GetRepeatFlag(void)
{
	if(IR_RepeatFlag) // 检测到重复码
	{
		IR_RepeatFlag=0;// 清零标志位
		return 1;       // 返回1表示有长按重复
	}else
	{
		return 0;       // 无重复码返回0
	}
}

/**
  * @brief  获取解析后的红外地址码
  * @param  无
  * @retval 红外地址码(8位)
  * @note   需在IR_DataFlag置1后调用,否则数据无效
  */
unsigned char IR_GetAddress(void)
{
	return IR_Address; // 返回存储的有效地址码
}

/**
  * @brief  获取解析后的红外命令码(按键值)
  * @param  无
  * @retval 红外命令码(8位)
  * @note   需在IR_DataFlag置1后调用,否则数据无效
  */
unsigned char IR_GetCommand(void)
{
	return IR_Command; // 返回存储的有效命令码
}
	
/**
  * @brief  外部中断0服务函数(红外信号解码核心)
  * @param  无
  * @retval 无
  * @note   红外接收头输出接P3.2(INT0),下降沿触发此中断,用于捕捉电平变化并解码
  */
void Int0_Routine(void) interrupt 0  // interrupt 0 表示外部中断0中断服务函数
{
	// 状态机0:初始态(等待检测引导码)
	if(IR_State==0)
	{
		Timer0_SetCounter(0); // 定时器计数值清零
		Timer_Run(1);         // 启动定时器0(开始计时)
		IR_State=1;           // 进入状态1:检测引导码阶段
	}
	// 状态机1:检测引导码/重复码阶段(判断电平持续时间)
	else if(IR_State==1)
	{
		IR_Time=Timer0_GetCounter(); // 获取定时器计数值(记录上一段电平的持续时间)
		Timer0_SetCounter(0);        // 定时器计数值清零,准备下一次计时
		
		// 判断是否是NEC协议引导码(9ms高+4.5ms低 = 13500us,允许±500us误差)
		if(IR_Time>13500-500 && IR_Time<13500+500)
		{
			IR_State=2; // 引导码检测成功,进入状态2:接收数据位阶段
		}
		// 判断是否是NEC协议重复码(9ms高+2.25ms低 = 11250us,允许±500us误差)
		else if(IR_Time>11250-500 && IR_Time<11250+500)
		{
			IR_RepeatFlag=1; // 置位重复码标志(表示长按按键)
			Timer_Run(0);    // 关闭定时器
			IR_State=0;      // 回到初始态,等待下一次信号
		}
		// 既不是引导码也不是重复码,视为无效信号,重新检测
		else
		{
			IR_State=1; // 保持状态1,继续检测
		}		
	}
	// 状态机2:接收数据位阶段(解析32位数据:地址码+地址反码+命令码+命令反码)
	else if(IR_State==2)
	{
		IR_Time=Timer0_GetCounter(); // 获取当前电平持续时间
		Timer0_SetCounter(0);        // 定时器清零
		
		// 判断是否是数据位0(低电平560us+高电平560us ≈ 1120us,允许±500us误差)
		if(IR_Time>1120-500 && IR_Time<1120+500)
		{
			// 对应位清零:IR_pData/8确定字节索引,IR_pData%8确定位索引
			IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));
			IR_pData++; // 数据位指针自增,接收下一位
		}
		// 判断是否是数据位1(低电平560us+高电平1690us ≈ 2250us,允许±500us误差)
		else if(IR_Time>2250-500 && IR_Time<2250+500)
		{
			// 对应位置1:IR_pData/8确定字节索引,IR_pData%8确定位索引
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
			IR_pData++; // 数据位指针自增,接收下一位
		}
		// 电平时间异常,视为无效数据,重置解码状态
		else 
		{
			IR_State=1;  // 回到状态1,重新检测引导码
			IR_pData=0;  // 数据位指针清零
		}
		
		// 判断32位数据是否接收完成(0-31位共32位)
		if(IR_pData>=32)
		{
			IR_pData=0; // 数据位指针清零
			// 数据校验:地址码=地址反码取反 且 命令码=命令反码取反(确保接收数据无误)
			if(IR_Data[0]==~IR_Data[1] && IR_Data[2]==~IR_Data[3])
			{
				IR_Address=IR_Data[0];  // 存储有效地址码
				IR_Command=IR_Data[2];  // 存储有效命令码(按键值)
				IR_DataFlag=1;          // 置位数据接收完成标志
			}
			Timer_Run(0); // 关闭定时器
			IR_State=0;   // 回到初始态,等待下一次红外信号
		}
	}
}

⭐IR.h

cpp 复制代码
#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
#define IR_VOL_ADD		0x09
#define IR_0			0x16
#define IR_RPT			0x19
#define IR_USD			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_GetDataFlag(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetCommand(void);

#endif

17.6 项目示例1:红外遥控

✅项目功能:

实现 51 单片机接收红外遥控信号,在 LCD1602 上实时显示红外地址码、命令码,并响应 "音量加 / 减" 按键修改计数变量并显示

✅项目架构:

✅main.c 主函数:

cpp 复制代码
#include <REGX52.H>        // 52系列单片机寄存器定义头文件
#include "Delay.h"         // 延时函数驱动头文件(LCD初始化/显示需延时)
#include "LCD1602.h"       // LCD1602液晶显示驱动头文件
#include "IR.h"            // 红外遥控解码驱动头文件(包含之前编写的IR_Init、IR_GetDataFlag等函数)

// 全局变量定义
unsigned char Address;     // 存储红外遥控解析后的地址码
unsigned char Command;     // 存储红外遥控解析后的命令码(按键对应的数值)
unsigned char Num;         // 用于演示的计数变量(受红外音量±按键控制)

/**
  * @brief  主函数(程序入口)
  * @param  无
  * @retval 无
  * @note   初始化LCD和红外后,循环检测红外按键,显示地址码/命令码,并响应音量±按键修改计数变量
  */
void main()
{
	// 初始化外设
	LCD_Init();           // 初始化LCD1602液晶(配置端口、显示模式等)
	IR_Init();            // 初始化红外遥控解码(定时器0+外部中断0)
	
	// LCD显示初始化:固定表头(第一行)
	LCD_ShowString(1,1,"ADDR  CMD  NUM");  // 第1行第1列显示"ADDR  CMD  NUM",分别对应地址码、命令码、计数变量
	LCD_ShowString(2,1,"00    00   00");   // 第2行第1列显示初始值,占位符
	
	// 主循环(程序持续运行)
	while(1)
	{
		// 检测是否接收到红外有效数据(IR_GetDataFlag=1)或长按重复码(IR_GetRepeatFlag=1)
		if(IR_GetDataFlag()||IR_GetRepeatFlag())
		{
			Address=IR_GetAddress();  // 获取解析后的红外地址码
			Command=IR_GetCommand();  // 获取解析后的红外命令码(按键值)
			
			// 将地址码/命令码以16进制显示到LCD
			LCD_ShowHexNum(2,1,Address,2);  // 第2行第1列显示地址码,2位16进制(不足补0)
			LCD_ShowHexNum(2,7,Command,2);  // 第2行第7列显示命令码,2位16进制(不足补0)
		}
		
		// 根据红外命令码执行对应操作(响应音量减/加按键)
		if(Command==IR_VOL_MINUS)  // IR_VOL_MINUS是红外遥控器"音量减"按键对应的命令码(宏定义在IR.h中)
		{
			Num--;  // 计数变量减1
		}else if(Command==IR_VOL_ADD)  // IR_VOL_ADD是"音量加"按键对应的命令码
		{
			Num++;  // 计数变量加1
		}
		
		// 将计数变量以16进制显示到LCD(第2行第12列,3位,不足补0)
		LCD_ShowHexNum(2,12,Num,3);
	}
}

17.7 项目示例2:红外遥控电机调速

✅项目功能:

实现 51 单片机通过 按键红外遥控两种方式控制电机转速,同时在数码管上实时显示当前速度档位(0-3 档)

✅项目架构:

⭐Timer1.c

cpp 复制代码
#include <REGX52.H>  // 52系列单片机寄存器定义头文件,包含TMOD、TL1、TH1等定时器1相关寄存器

/**
  * @brief  定时器1初始化函数
  * @param  无
  * @retval 无
  * @note   配置定时器1为16位定时器模式(模式1),12.000MHz晶振下定时100微秒,开启中断并启动计时
  *         12MHz晶振下,机器周期=1us,定时器每计数1次耗时1us
  */
void Timer1_Init(void)		//100微秒@12.000MHz(定时周期为100微秒)
{
	// 1. 配置定时器1的工作模式(TMOD寄存器:低4位控制定时器0,高4位控制定时器1)
	TMOD &= 0x0F;		// 清零定时器1的模式位(bit4~bit7),保留定时器0的配置
	TMOD |= 0x10;		// 设置定时器1为模式1(16位定时/计数模式,无自动重装)
	
	// 2. 设置定时器1定时初值(实现100us定时,12MHz晶振下计数1次=1us)
	// 计算过程:16位计数器最大值=65536,需要计数100次(100us),因此初值=65536-100=65436
	// 65436转换为16进制:65436 = 0xFF9C → 高8位TH1=0xFF,低8位TL1=0x9C
	TL1 = 0x9C;		// 定时器1低8位初值设为0x9C
	TH1 = 0xFF;		// 定时器1高8位初值设为0xFF
	
	// 3. 清除定时器1的溢出标志
	TF1 = 0;		// TF1是定时器1的溢出标志(TF1=1表示定时器溢出),初始化时清零避免误触发
	
	// 4. 启动定时器1计数
	TR1 = 1;		// TR1是定时器1的运行控制位,TR1=1 → 启动定时器1开始计数
	
	// 5. 配置定时器1中断
	ET1 = 1;		// ET1是定时器1的中断允许位,ET1=1 → 允许定时器1溢出中断
	EA = 1;			// EA是总中断允许位,EA=1 → 开启单片机所有中断的总开关(必须开启)
	PT1 = 0;		// PT1是定时器1的优先级控制位,PT1=0 → 低优先级;PT1=1 → 高优先级
}

/*定时器1中断服务函数模板
void Timer1Rountine() interrupt 3
{
	// 定义静态变量:函数执行完后值不丢失,用于累计中断次数
	static unsigned int T1Count;
	
	// 重装定时初值(模式1无自动重装,溢出后需手动恢复初值,否则下次定时不准)
	TL1 = 0x9C;		// 恢复低8位初值
	TH1 = 0xFF;		// 恢复高8位初值
	
	T1Count++;      // 每次中断(100us)计数+1
	// 累计1000次中断 → 100us×1000=100ms(0.1秒)
	if(T1Count>=1000)
	{
		T1Count=0;  // 计数清零,重新累计
		// 此处可添加需要每隔100ms执行的代码(如LED闪烁、电机PWM调速等)
	}
}
/*

⭐Timer1.h

cpp 复制代码
#ifndef _TIMER1_H_
#define _TIMER1_H_

void Timer1_Init(void);

#endif

⭐Motor.c

cpp 复制代码
#include <REGX52.H>        // 52系列单片机寄存器定义头文件
#include "Timer1.h"        // 定时器1驱动头文件(包含Timer1_Init初始化函数)

// 电机控制引脚定义:P1^0引脚连接电机驱动模块(如三极管/继电器/电机驱动芯片)
sbit Motor = P1^0;        

// 全局变量定义(PWM调速核心)
unsigned char Count;       // 定时器中断累计计数器(用于PWM周期计数,范围0-99)
unsigned char Compare;     // PWM占空比比较阈值(0-100),决定电机转速(0=停止,100=全速)

/**
  * @brief  电机初始化函数
  * @param  无
  * @retval 无
  * @note   本质是初始化定时器1(100us中断),为PWM波生成提供定时基础
  */
void Motor_Init(void)
{
	Timer1_Init();  // 调用定时器1初始化函数(12MHz晶振下100us触发一次中断)
}

/**
  * @brief  设置电机转速
  * @param  Speed:转速百分比(0-100),0=停止,100=最大转速
  * @retval 无
  * @note   将转速值赋值给Compare,作为PWM占空比的比较阈值
  */
void Motor_SetSpeed(unsigned char Speed)
{
	Compare = Speed;  // Speed范围0-100,对应PWM占空比0%-100%
}

/**
  * @brief  定时器1中断服务函数(100us触发一次)
  * @param  无
  * @retval 无
  * @note   生成10ms周期的PWM波,通过Count与Compare的比较控制Motor引脚高低电平,实现占空比调节
  *         100us×100次=10ms(PWM周期),Count<Compare时Motor=1,否则=0
  */
void Timer1_Rountine() interrupt 3  // interrupt 3 表示定时器1中断服务函数(中断号3)
{
	// 重装定时器1初值(模式1无自动重装,必须手动恢复100us定时)
	TL1 = 0x9C;		// 低8位初值(对应100us定时)
	TH1 = 0xFF;		// 高8位初值(对应100us定时)
	
	Count++;        // 每次中断(100us)计数器+1
	Count %= 100;   // 计数器取模100,限制范围0-99(累计100次中断=10ms,形成PWM周期)
	
	// PWM占空比控制核心逻辑
	if(Count < Compare)  // 计数器小于比较阈值 → 电机引脚置1
	{
		Motor = 1;       // Motor引脚高电平,电机驱动电路工作(转速由高电平时长决定)
	}else                // 计数器大于等于比较阈值 → 电机引脚置0
	{
		Motor = 0;       // Motor引脚低电平,电机驱动电路停止
	}
}

⭐Motor.h

cpp 复制代码
#ifndef _MOTOR_H_
#define _MOTOR_H_

void Motor_Init(void);
void Motor_SetSpeed(unsigned char Speed);
	
#endif

✅main.c 主函数:

cpp 复制代码
#include <REGX52.H>        // 52系列单片机寄存器定义头文件
#include "Key.h"           // 按键驱动头文件(包含Key()函数,用于读取按键值)
#include "NiXie.h"         // 数码管驱动头文件(NiXie为"数码"拼音,包含NiXie()显示函数)
#include "IR.h"            // 红外遥控解码驱动头文件(红外命令码解析)
#include "Motor.h"         // 电机驱动头文件(包含Motor_Init/Motor_SetSpeed函数)

// 全局变量定义
unsigned char KeyNum;   // 存储读取到的按键值(由Key()函数返回)
unsigned char Speed;    // 电机速度档位(0-3档,对应不同转速)
unsigned char Command;  // 存储红外遥控解析后的命令码(按键对应的数值)

/**
  * @brief  主函数(程序入口)
  * @param  无
  * @retval 无
  * @note   初始化红外和电机后,循环检测按键/红外信号,控制电机速度并在数码管显示档位
  */
void main()
{
	IR_Init();        // 初始化红外遥控解码模块(定时器0+外部中断0)
	Motor_Init();     // 初始化电机驱动模块(如PWM输出、电机端口配置等)
	
	// 主循环(程序持续运行)
	while(1)
	{	
		KeyNum = Key();  // 读取按键值(Key()函数会扫描按键,返回按下的按键编号,无按键返回0)
		
		// 按键1控制:速度档位循环递增(0→1→2→3→0)
		if(KeyNum == 1)
		{
			Speed++;          // 档位自增
			Speed %= 4;       // 取模4,限制档位范围为0-3(避免超出范围)
			
			// 根据档位设置电机具体转速(0=停止,1=50%转速,2=75%转速,3=100%转速)
			if(Speed == 0){Motor_SetSpeed(0);}    // 0档:电机停止
			if(Speed == 1){Motor_SetSpeed(50);}   // 1档:电机50%转速
			if(Speed == 2){Motor_SetSpeed(75);}   // 2档:电机75%转速
			if(Speed == 3){Motor_SetSpeed(100);}  // 3档:电机100%转速
		}
		
		// 红外遥控控制:检测是否接收到有效红外数据
		if(IR_GetDataFlag())
		{
			Command = IR_GetCommand();  // 获取红外遥控的命令码(如IR_0对应遥控器"0"键)
			
			// 根据红外按键(0/1/2/3)直接设置速度档位
			if(Command == IR_0) {Speed = 0;}  // 红外"0"键:设置为0档
			if(Command == IR_1) {Speed = 1;}  // 红外"1"键:设置为1档
			if(Command == IR_2) {Speed = 2;}  // 红外"2"键:设置为2档
			if(Command == IR_3) {Speed = 3;}  // 红外"3"键:设置为3档
			
			// 根据新档位更新电机转速(与按键逻辑一致)
			if(Speed == 0){Motor_SetSpeed(0);}
			if(Speed == 1){Motor_SetSpeed(50);}
			if(Speed == 2){Motor_SetSpeed(75);}
			if(Speed == 3){Motor_SetSpeed(100);}
		}
		
		NiXie(1, Speed);  // 数码管显示:第1位显示当前速度档位(0-3)
	}
}
相关推荐
麦托团子8 小时前
51单片机学习笔记13-AT24C02(I2C)
51单片机
麦托团子1 天前
51单片机学习笔记14-DS18B20(单总线)
51单片机
麦托团子1 天前
51单片机学习笔记12-蜂鸣器
51单片机
麦托团子1 天前
51单片机学习笔记15-PWM脉冲编码调制
51单片机
麦托团子2 天前
51单片机学习笔记10-点阵屏
51单片机
恶魔泡泡糖2 天前
51单片机外部中断
c语言·单片机·嵌入式硬件·51单片机
项目題供诗2 天前
51单片机入门(五)
单片机·嵌入式硬件·51单片机
A-code2 天前
嵌入式UI刷新:观察者模式实战
stm32·单片机·mcu·物联网·51单片机
项目題供诗3 天前
51单片机入门(四)
单片机·嵌入式硬件·51单片机