51单片机——红外遥控(二)

目录

[3. 硬件设计](#3. 硬件设计)

[4. 软件设计](#4. 软件设计)

[4.1 smg.c 与 smg.h](#4.1 smg.c 与 smg.h)

[4.2 ired.c 与 ired.h](#4.2 ired.c 与 ired.h)

[4.2.1 NEC 红外协议基础](#4.2.1 NEC 红外协议基础)

[4.2.2 全局变量](#4.2.2 全局变量)

[4.2.3 初始化函数](#4.2.3 初始化函数)

[4.2.4 中断服务函数](#4.2.4 中断服务函数)

[4.3 main.c](#4.3 main.c)

[4.4 public.c 与 public.h](#4.4 public.c 与 public.h)


3. 硬件设计

① 动态数码管

② 红外接收头和遥控器

红外接收头和遥控器是一体的,内部结构不用管,下面是开发板上红外接收模块电路

红外接收头的输出管脚接至 P3.2 管脚,为了保证红外接收头输出管脚默认为高电平,

需外接一个 10K 上拉电阻

4. 软件设计

实验目的:数码管上显示红外解码遥控器键值

4.1 smg.c 与 smg.h

smg.c

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

//共阴极数码管显示 0-F 的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
				0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
/*******************************************************************************
* 函 数 名       : smg_display
* 函数功能		 : 动态数码管显示
* 输    入       : dat:要显示的数据
				   pos:从左开始第几个位置开始显示,范围1-8
* 输    出    	 : 无
*******************************************************************************/
void smg_display(u8 dat[],u8 pos)
{
	u8 i=0;
	u8 pos_temp=pos-1;//转换为数组对应索引

	for(i=pos_temp;i<8;i++)
	{
	   	switch(i)//位选
		{
			case 0: LSC=1;LSB=1;LSA=1;break;
			case 1: LSC=1;LSB=1;LSA=0;break;
			case 2: LSC=1;LSB=0;LSA=1;break;
			case 3: LSC=1;LSB=0;LSA=0;break;
			case 4: LSC=0;LSB=1;LSA=1;break;
			case 5: LSC=0;LSB=1;LSA=0;break;
			case 6: LSC=0;LSB=0;LSA=1;break;
			case 7: LSC=0;LSB=0;LSA=0;break;
		}
		SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
		delay_10us(100);//延时一段时间,等待显示稳定
		SMG_A_DP_PORT=0x00;			  //消影
	}
}

smg.h

cpp 复制代码
#ifndef _smg_H
#define _smg_H

#include "public.h"


#define SMG_A_DP_PORT	P0	//使用宏定义数码管段码口

//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

extern u8 gsmg_code[17];

void smg_display(u8 dat[],u8 pos);

#endif

代码理解如下:

pos: 指定显示的数码管的起始位置

dat :显示的内容

① 数码管从第 i+1 个数码管开始显示,一直到第 8个,为什么结果只有几个灯亮?

cpp 复制代码
for(i=pos_temp;i<8;i++)

② 如何进行该循环?

cpp 复制代码
for(i=pos_temp;i<8;i++)
	{
	   	switch(i)//位选
		{
			case 0: LSC=1;LSB=1;LSA=1;break;
			case 1: LSC=1;LSB=1;LSA=0;break;
			case 2: LSC=1;LSB=0;LSA=1;break;
			case 3: LSC=1;LSB=0;LSA=0;break;
			case 4: LSC=0;LSB=1;LSA=1;break;
			case 5: LSC=0;LSB=1;LSA=0;break;
			case 6: LSC=0;LSB=0;LSA=1;break;
			case 7: LSC=0;LSB=0;LSA=0;break;
		}
		SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
		delay_10us(100);//延时一段时间,等待显示稳定
		SMG_A_DP_PORT=0x00;			  //消影
	}
4.2 ired.c 与 ired.h

ired.c

核心逻辑:

中断触发→检测引导码→逐位解析 4 字节数据(按 NEC 时序区分 0/1、处理位顺序)→校验控制码→缓存有效数据

本质是把红外接收头的模拟电平信号,按 NEC 协议规则转换为可识别的 4 字节数字码,供主程序判断 "按下了哪个按键"(比如读取 gired_data[2] 的值,判断是电源键 / 音量键等)

这是 ired.c 文件

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

u8 gired_data[4];//存储4个字节接收码(地址码+地址反码+控制码+控制反码)

/*******************************************************************************
* 函 数 名         : ired_init
* 函数功能		   : 红外端口初始化函数,外部中断0配置 
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void ired_init(void)
{
	IT0=1;	//下降沿触发
	EX0=1;	//打开中断0允许
	EA=1;	//打开总中断
	IRED=1;	//初始化端口
}

void ired() interrupt 0	//外部中断0服务函数
{
	u8 ired_high_time=0;
	u16 time_cnt=0;
	u8 i=0,j=0;

	if(IRED==0)
	{
		time_cnt=1000;
		while((!IRED)&&(time_cnt))//等待引导信号9ms低电平结束,若超过10ms强制退出
		{
			delay_10us(1);//延时约10us
			time_cnt--;
			if(time_cnt==0)return;		
		}
		if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
		{
			time_cnt=500;
			while(IRED&&time_cnt)//等待引导信号4.5ms高电平结束,若超过5ms强制退出
			{
				delay_10us(1);
				time_cnt--;
				if(time_cnt==0)return;	
			}
			for(i=0;i<4;i++)//循环4次,读取4个字节数据
			{
				for(j=0;j<8;j++)//循环8次读取每位数据即一个字节
				{
					time_cnt=600;
					while((IRED==0)&&time_cnt)//等待数据1或0前面的0.56ms结束,若超过6ms强制退出
					{
						delay_10us(1);
						time_cnt--;
						if(time_cnt==0)return;	
					}
					time_cnt=20;
					while(IRED)//等待数据1或0后面的高电平结束,若超过2ms强制退出
					{
						delay_10us(10);//约0.1ms
						ired_high_time++;
						if(ired_high_time>20)return;	
					}
					gired_data[i]>>=1;//先读取的为低位,然后是高位
					if(ired_high_time>=8)//如果高电平时间大于0.8ms,数据则为1,否则为0
						gired_data[i]|=0x80;
					ired_high_time=0;//重新清零,等待下一次计算时间
				}
			}
		}
		if(gired_data[2]!=~gired_data[3])//校验控制码与反码,错误则返回
		{
			for(i=0;i<4;i++)
				gired_data[i]=0;
			return;	
		}
	}		
}

ired.h

cpp 复制代码
#ifndef _ired_H
#define _ired_H

#include "public.h"

//管脚定义
sbit IRED=P3^2;

//声明变量
extern u8 gired_data[4];


//函数声明
void ired_init(void);

#endif

代码理解如下:

4.2.1 NEC 红外协议基础

红外通信是用红外线(不可见光,波长约 940nm)传输数据,就像两个人用暗号说话:

「说话的人」:遥控器里的红外发射管(通电后发射 38kHz 的红外光);

「听话的人」:电视/空调里的红外接收头(只认 38kHz 的红外光,过滤阳光 / 灯光干扰);

「暗号规则」:就是 NEC 协议(规定 "怎么说""怎么认")。

4.2.2 全局变量

缓存红外接收的一帧完整数据

cpp 复制代码
u8 gired_data[4];//存储4个字节接收码(地址码+地址反码+控制码+控制反码)
  • gired_data[0]:红外遥控的地址码(比如电视 / 空调的设备地址);
  • gired_data[1]:地址码的反码(用于校验地址是否正确,代码中未校验,仅校验了控制码);
  • gired_data[2]控制码(比如 "音量 +""电源" 等按键对应的码值);
  • gired_data[3]:控制码的反码(核心校验位)。
4.2.3 初始化函数

ired_init():红外接收头(如 HS0038)无信号时输出高电平,收到红外信号(引导码低电平)时会产生下降沿,触发外部中断 0,进入解析流程。

cpp 复制代码
void ired_init(void)
{
	IT0=1;	//下降沿触发:外部中断0由引脚电平"高→低"触发(红外引导码开始是低电平,符合触发条件)
	EX0=1;	//打开外部中断0的允许开关
	EA=1;	//打开总中断(51单片机所有中断的总开关,必须开启)
	IRED=1;	//初始化红外接收引脚为高电平(红外接收头无信号时输出高电平)
}
4.2.4 中断服务函数

ired()(核心解析逻辑)

这份代码是51 单片机基于外部中断的 NEC 红外协议解码函数,整体思路可总结为:

检测引导码(9ms+4.5ms)→ 过滤干扰;

逐位解析 4 字节数据(地址码、地址反码、数据码、数据反码);

反码校验(地址码和地址反码匹配、数据码和数据反码匹配);

校验通过后,把数据码存到gired_data[2]里;

这个思路是嵌入式 "硬件时序解析" 的典型范式:先过滤无效信号,再解析有效数据,最后校验兜底,全程防卡死

1. 「引导码」

接收端(描述 2):先检查是不是「9ms+4.5ms」的引导码("双层过滤筛除干扰")。

✅ 对应逻辑:发射端发引导码是为了 "标记有效信号开头",接收端先校验引导码,是为了过滤掉不是 NEC 协议的干扰信号(比如阳光、灯光的杂波)------ 本质是同一回事的 "发" 和 "验"。

2. 「4 字节数据」

接收端(描述 2):"按 NEC 数据位时序逐位解析 4 字节数据"------ 解析的就是发射端发的这 4 字节(8 位 = 1 字节,4×8=32 位)。

3. 「反码校验」的对应

接收端(描述 2):"通过反码校验保证数据正确"------ 就是核对 "地址码和地址反码是否互补、数据码和数据反码是否互补"。

4. 「超时保护」的补充(为什么发射端没提)

"全程用超时保护避免程序卡死",是接收端专属的逻辑(发射端不需要):

  • 比如电视在解析信号时,如果解析到一半,信号突然断了(比如遥控器被挡住),总不能一直等着解析,会导致电视的程序卡死;
  • 所以接收端会设置 "超时时间"(比如超过 10ms 没收到下一位信号,就判定信号无效,直接放弃解析);
  • 发射端只需要按固定时序发信号就行,不存在 "等信号" 的情况,所以这是接收端的 "额外保护逻辑"。

5. 中断

NEC 协议解析的核心是 "检测电平跳变(比如发射→不发射)+ 计算跳变之间的时长",对应 51 单片机的两类中断:

中断函数是 "特殊函数",格式固定,单片机检测到中断信号会自动跳转到这里执行:

  • interrupt 中断号:指定是哪类中断(51 的固定规则):
    • 外部中断 0 → 中断号 0;
    • 定时器 0 中断 → 中断号 1;
    • 外部中断 1 → 中断号 2;
    • 定时器 1 中断 → 中断号 3;
  • 不需要返回值(void),也不能传参;
  • using 寄存器组:可选,一般省略。

6. 外部中断 0 函数(ired_int)

红外接收头的电平每跳变一次(比如从低→高、高→低),就会触发该中断函数

cpp 复制代码
void ired() interrupt 0	//外部中断 0 服务函数,中断号为 0
{
	// 局部变量定义
	u8 ired_high_time=0;	// 统计数据位高电平的时长(判断是0/1)
	u16 time_cnt=0;			// 超时计数器(防止死等)
	u8 i=0,j=0;

	// 第一步:检测引导码的起始低电平(必须先有低电平才是有效信号)
	if(IRED==0)
	{
		time_cnt=1000;
		
		while((!IRED)&&(time_cnt))//低电平结束 或 超时,则退出循环
		{
			delay_10us(1);//每次延时约10us
			time_cnt--;	  //待 time_cnt 执行完用时大约 10ms
			if(time_cnt==0) return;		
		}
		
		if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
		{
			time_cnt=500;
			while(IRED&&time_cnt)//等待引导信号4.5ms高电平结束,若超过5ms强制退出
			{
				delay_10us(1);
				time_cnt--;
				if(time_cnt==0) return;	
			}
			
			for(i=0;i<4;i++)//循环4次,读取4个字节数据
			{
				for(j=0;j<8;j++)//循环8次读取每位数据即一个字节
				{
					time_cnt=600;
					while((IRED==0)&&time_cnt)//等待数据1或0前面的0.56ms结束,若超过6ms强制退出
					{
						delay_10us(1);
						time_cnt--;
						if(time_cnt==0)return;	
					}
					
					time_cnt = 20;
					while(IRED)//等待数据1/0后面的高电平结束,若超过 2ms 强制退出
					{
						delay_10us(10);		// 约0.1ms
						ired_high_time++;   // 统计高电平持续的"0.1ms个数"
						if(ired_high_time>20)	return;	
					}
					
					gired_data[i] >>= 1;//先读取的为低位,然后是高位
					if(ired_high_time >= 8)//若高电平时间大于0.8ms,数据则为1,则把1置最高位
						gired_data[i] |= 0x80;//把其最高位置1,其余位保持不变(0和任何位或都不变,1和任何位或都为1)
					
					ired_high_time=0;//重新清零,等待下一次计算时间
				}
			}
		}
		
		if(gired_data[2]!=~gired_data[3])//校验控制码与反码,错误则返回
		{
			for(i=0;i<4;i++)
				gired_data[i]=0;
			return;	
		}
	}		
}

以 10μs 为时间粒度,等待红外接收头的 9ms( NEC 协议要求引导码低电平是 9ms**) 低电平结束;若等待时间超过 10ms(判定为无效信号),则直接退出中断函数,避免程序卡死。**

这是红外解码中 "过滤无效信号、保证时序准确性" 的关键环节,核心是「时序检测 + 超时保护」的组合设计 ------ 这也是嵌入式代码中处理硬件时序的典型思路。

红外接收头可能收到干扰信号(比如灯光),只有 "9ms 低 + 4.5ms 高" 的引导码才是 NEC 协议的有效帧开头,过滤无效干扰。

① 嵌入式代码中,while 循环的条件往往是 "硬件状态 + 超时保护 " 的组合,硬件状态(这里的!IRED)是核心,超时保护(time_cnt)是兜底;

任何嵌入式时序检测的代码,都不能脱离 "硬件电平 与 状态判断"

② 正常情况下:9ms 低电平后变为高电平便退出循环,不会低电平持续到 10 ms

超时保护是核心 :若没有 time_cnt 超时机制,一旦红外引脚因短路 / 强干扰一直为低电平,程序会永远卡在 while 循环中,单片机无法执行其他任务 ------ 这是嵌入式代码的 "鲁棒性设计"(避免死循环)。

时间余量合理 :超时设为 10ms(比 9ms 多 1ms),而非刚好 9ms:因为实际硬件中,延时函数可能有 ±10% 的误差(比如delay_10us(1)实际延时 11μs),留余量能避免 "标准 9ms 低电平却被误判为超时"。

delay_10us(1)红外时序的最小单位是 0.56ms(560μs),用 10μs 的粒度检测,既能保证精度,又不会因粒度太细导致循环次数过多(比如用 1μs 粒度的话,9ms 需要 9000 次循环,效率低)

cpp 复制代码
time_cnt=1000;
		
		while((!IRED)&&(time_cnt))//低电平结束 或 超时,则退出循环
		{
			delay_10us(1);//每次延时约10us
			time_cnt--;	  //待 time_cnt 执行完用时大约 10ms
			if(time_cnt==0) return;		
		}

这是 NEC 红外协议引导码第二阶段 ------4.5ms 高电平 的检测逻辑,同时加入 5ms 超时保护

等待引导码的 4.5ms 高电平自然结束(红外接收引脚 IRED 从高→低);

cpp 复制代码
if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
		{
			time_cnt=500;
			while(IRED&&time_cnt)//等待引导信号4.5ms高电平结束,若超过5ms强制退出
			{
				delay_10us(1);
				time_cnt--;
				if(time_cnt==0) return;	
			}

该代码与前序" 9ms 低电平检测 "完全遵循同一套嵌入式时序模板,抓住 "电平条件对应协议时序,计数器对应超时保护" 这两个核心,这也是所有嵌入式硬件时序解析的通用思路。,仅电平判断相反,可总结通用范式:

cpp 复制代码
// 通用:检测"目标电平时长",加"略大的超时保护"
time_cnt = 超时阈值; // 目标时长×10μs × 1.1(余量)
while(目标电平条件 && time_cnt) 
{
    delay_10us(1); // 固定时间粒度
    time_cnt--;
    if(time_cnt==0) return; // 超时退出
}

下面是 for 循环代码:

cpp 复制代码
for(i=0;i<4;i++)//循环4次,读取4个字节数据
			{
				for(j=0;j<8;j++)//循环8次读取每位数据即一个字节
				{
					time_cnt=600;
					while((IRED==0)&&time_cnt)//等待数据1或0前面的0.56ms结束,若超过6ms强制退出
					{
						delay_10us(1);
						time_cnt--;
						if(time_cnt==0)return;	
					}
					time_cnt=20;
					while(IRED)//等待数据1或0后面的高电平结束,若超过2ms强制退出
					{
						delay_10us(10);//约0.1ms
						ired_high_time++;
						if(ired_high_time>20)return;	
					}
					gired_data[i]>>=1;//先读取的为低位,然后是高位
					if(ired_high_time>=8)//如果高电平时间大于0.8ms,数据则为1,否则为0
						gired_data[i]|=0x80;
					ired_high_time=0;//重新清零,等待下一次计算时间
				}
			}

① NEC 协议数据位的时序规则

核心逻辑:低电平时长固定,通过高电平时长区分 0 和 1

cpp 复制代码
                    time_cnt=20;
					while(IRED)//等待数据1/0后面的高电平结束,若超过 2ms 强制退出
					{
						delay_10us(10);		// 约0.1ms
						ired_high_time++;   // 统计高电平持续的"0.1ms个数"
						if(ired_high_time>20)	return;	
					}

这段代码的核心目标不是 "单纯等待高电平结束",而是 **"统计高电平的持续时长" ** ------ 通用模板的 "time_cnt--" 是 "倒计时"(关注剩余时间),而这里需要 "正计时"(关注已持续时间),因此调整了写法

通用模板(如引导码检测)的目标是 "等电平结束,不关心具体时长",只需 "超时就退出",因此用time_cnt 倒计时即可;

这段代码(数据位检测)的目标是 "统计高电平到底持续了多久"(用来区分 0/1),因此需要ired_high_time 正计时(从 0 开始累加),直接获取时长数值

cpp 复制代码
gired_data[i]>>=1;//先读取的为低位,然后是高位

if(ired_high_time>=8)//如果高电平时间大于0.8ms,数据则为1,否则为0
    gired_data[i]|=0x80;

ired_high_time=0;//重新清零,等待下一次计算时间

|= 0x80 的位运算含义

0x80的二进制是1000 0000(仅最高位为 1,其余位为 0);

|=是 "按位或赋值运算符":只把gired_data[i]最高位置 1,其余位保持不变(因为 0 和任何位或都不变,1 和任何位或都为 1)。

② 为什么要 "置最高位"?

因为我们先做了 gired_data[i] >>= 1(右移),后续每解析一位都会右移一次 ------先把 1 放到最高位,后续右移会逐步把这个 1 "推" 到低位,刚好适配 "先传低位" 的规则。

比如:

  • 第 1 次解析出 "1"(最低位):置最高位→1000 0000
  • 第 2 次解析出 "0":右移→0100 0000,不置 1→保持0100 0000
  • 第 3 次解析出 "0":右移→0010 0000,不置 1→保持0010 0000
  • ......
  • 第 8 次解析完成:最终 0000 0001(刚好是先传的低位 1 落到最低位)。
cpp 复制代码
if(gired_data[2]!=~gired_data[3])//校验控制码与反码,错误则返回
		{
			for(i=0;i<4;i++)
				gired_data[i]=0;
			return;	
		}

① 核心背景:NEC 协议的反码校验规则

NEC 红外协议的一帧数据包含 4 字节:

gired_data[0]:地址码(设备唯一标识,比如电视 / 空调的地址)

gired_data[1]:地址反码(地址码的按位取反)

gired_data[2]:控制码(按键功能,比如 "电源""音量 +")

gired_data[3]:控制反码(控制码的按位取反)

协议设计反码的核心目的:校验数据传输是否出错------ 红外信号易受灯光、距离干扰,传输过程中可能出现位翻转(比如 1 变 0),通过 "原码≠~ 反码" 可快速判定数据无效。

② 校验失败的处理:for(i=0;i<4;i++) gired_data[i]=0

作用:清空整个红外数据数组(地址码、地址反码、控制码、控制反码全部置 0);

必要性:若数据出错,保留错误值会导致主程序误判(比如误识别成其他按键),清空后主程序可通过 "gired_data[2]==0" 判定 "无有效按键"。

③ 强制退出:return

作用:直接退出整个红外中断服务函数,不再执行后续任何逻辑;

逻辑:既然控制码校验失败,说明整帧数据都不可信,无需继续处理,直接退出以节省 CPU 资源。

③ 为什么只校验控制码,不校验地址码?

代码中仅校验了gired_data[2](控制码)和gired_data[3](控制反码),未校验gired_data[0](地址码)和gired_data[1](地址反码),原因:

  • 简化逻辑:地址码用于区分设备(比如只接收 "自家遥控器" 的信号),若仅做 "按键功能识别",可省略地址码校验;
  • 资源节省:红外中断函数需快速执行,减少校验步骤可降低 CPU 占用;
  • 可优化:工业级代码会补充地址码校验(if(gired_data[0]!=~gired_data[1])),进一步保证数据有效性。

④ 清空数组的必要性

若校验失败但不清空数组,gired_data中会残留错误数据:

  • 比如控制码错成0x13,主程序可能误判为 "音量 +"(本该是 "电源");
  • 清空为 0 后,主程序可通过 "gired_data[2]==0" 过滤无效按键,避免误操作。
4.3 main.c
cpp 复制代码
/**************************************************************************************
实验名称:红外遥控实验
接线说明:	
实验现象:下载程序后,数码管上显示数码管上显示红外解码遥控器键值
注意事项:红外接收头凸起处要与PCB板接口凸起丝印处对应																				  
***************************************************************************************/
#include "public.h"
#include "smg.h"
#include "ired.h"

/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/

void main()
{	
	u8 ired_buf[3];//定义数组:存3个数码管段码(高4位、低4位、字母H)

	ired_init();//红外初始化
	/*初始化做了啥?→ 配置红外接收头的IO口为输入、开启定时器/中断,
    让单片机能检测NEC协议的时序(9ms引导码、0/1的时长等),为解码做准备*/

	while(1)
	{				
		//将控制码高 4 位转换为数码管段码
		ired_buf[0]=gsmg_code[gired_data[2]/16];
		
		//将控制码低 4 位转换为数码管段码
		ired_buf[1]=gsmg_code[gired_data[2]%16];
		
		//显示 H 的段码:0X76 是共阴极数码管显示 H 的固定编码
		ired_buf[2]=0X76;
		
		//把3个段码显示到数码管上(6:第六个数码管开始显示)
		smg_display(ired_buf,6);	
	}		
}

数据码拆分为高低 4 位的原因

cpp 复制代码
//将控制码高 4 位转换为数码管段码
ired_buf[0]=gsmg_code[gired_data[2]/16];
		
//将控制码低 4 位转换为数码管段码
ired_buf[1]=gsmg_code[gired_data[2]%16];

比如数据是 0x12(0001 0010),0x12/16=1(高四位:0001),gsmg_code1 就是"数字1"对应的数码管段码(0x06)

0x12%16=2(低四位:0010),gsmg_code2 就是"数字2"对应的数码管段码(0x5B)

数码管一般是 "一位显示一个数字 / 字母",而 NEC 数据码是 8 位(1 字节,比如 0x12),拆成高 4 位(1)和低 4 位(2),就能用两位数码管分别显示,再加一位显示 "H"(表示十六进制),最终显示 "12H"。

实验现象的底层逻辑

按遥控器电源键→遥控器发 NEC 信号(引导码 + 地址码 + 数据码 0x01 + 反码);

单片机接收并解码→gired_data[2] = 0x01

代码计算:0x01/16=0(高 4 位)、0x01%16=1(低 4 位);

数码管显示:0(第一位)、1(第二位)、H(第三位)→ 最终看到 "01H"。

③ 中断函数和主函数的关联(核心!)

cpp 复制代码
ired_buf[0]=gsmg_code[gired_data[2]/16];  // 取数据码高4位
ired_buf[1]=gsmg_code[gired_data[2]%16];  // 取数据码低4位
  • gired_data[2]的值是中断函数解析后写入的
  • 主函数只需要在死循环里读取gired_data[2],不需要关心解析过程(中断在后台自动处理
  • 中断函数是 "后台工作者",主函数是 "前台展示者"------ 中断解析出按键值,主函数把值显示到数码管。
4.4 public.c 与 public.h
cpp 复制代码
#ifndef _public_H
#define _public_H

#include "reg52.h"

typedef unsigned int u16;	//对系统默认数据类型进行重定义
typedef unsigned char u8;
typedef unsigned long u32;

void delay_10us(u16 ten_us);
void delay_ms(u16 ms);

#endif
cpp 复制代码
#include "public.h"

/*******************************************************************************
* 函 数 名       : delay_10us
* 函数功能		 : 延时函数,ten_us=1时,大约延时10us
* 输    入       : ten_us
* 输    出    	 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
	while(ten_us--);	
}

/*******************************************************************************
* 函 数 名       : delay_ms
* 函数功能		 : ms延时函数,ms=1时,大约延时1ms
* 输    入       : ms:ms延时时间
* 输    出    	 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
	u16 i,j;
	for(i=ms;i>0;i--)
		for(j=110;j>0;j--);
}
相关推荐
蓝斯49711 小时前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
熙芯XiChip11 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
伟大的前程11 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工12 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip13 小时前
CAN与CANopen的本质区别
单片机
lilihewo13 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
神罚天下ET14 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院15 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰16 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥16 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5