Day43 嵌入式 中断、定时器与串行通信
day43 嵌入式 中断、定时器与串行通信
一、中断的使用
1. 中断的基本概念
中断是一种异步事件处理机制,允许CPU在执行主程序时响应外部或内部事件。当发生中断请求时,CPU暂停当前任务,跳转到对应的中断服务程序(ISR)执行处理,处理完成后返回原程序继续运行。
- 同步事件:按顺序执行,如循环扫描按键。
- 异步事件:随机发生,如按键按下、定时溢出等。
✅ 中断的核心优势:提高系统实时性,减少轮询开销,提升效率。
2. 中断的初始化流程
// 外部中断0初始化函数
void eint0_init(void)
{IT0 = 1; // 设置外部中断0为边沿触发方式(下降沿触发)EX0 = 1; // 使能外部中断0EA = 1; // 使能总中断
}
🔧 初始化步骤说明:
寄存器 | 功能 | 值设置 |
---|---|---|
IT0 | 触发方式选择 | 1 :下降沿触发;0 :电平触发 |
EX0 | 外部中断0使能 | 1 :使能;0 :禁用 |
EA | 全局中断使能 | 1 :开启所有中断;0 :关闭所有中断 |
📌 注意:只有同时设置
EX0=1
和EA=1
,外部中断才能生效。
3. 中断服务程序(ISR)
// 外部中断0服务函数
void eint0_handler(void) interrupt 0
{num++; // 中断计数加1
}
⚠️ ISR编写规范:
- 无参数、无返回值:不能传递参数,也不能返回结果。
- 简短快速:应尽快完成处理并退出,避免影响其他任务。
- 不可重入:同一时间只能被调用一次。
✅ 理想运行结果:每检测到一次按键按下(产生下降沿),
num
自增1,实现对按键次数的统计。
二、矩阵按键模块与数码管显示
1. 矩阵按键电路结构
这是矩阵按键模块的电路示意图。图中展示了一个4×4的矩阵按键结构,共有16个按键(S1 - S16),通过P1端口的P10 - P17引脚进行连接与控制,常用于单片机等嵌入式系统中,实现多按键输入的功能,相比独立按键能更节省I/O口资源。
2. 数码管段码与显示函数
// 共阴极数码管段码数组,存储0-9对应的段码
unsigned char digit_data[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
📊 段码解释(共阴极):
0x3f
→ 数字0
(a,b,c,d,e,f亮)0x06
→ 数字1
(b,c亮)- …
0x6f
→ 数字9
(a,b,c,d,f,g亮)
// 数码管显示函数,将数字显示在4位数码管上
void digit_show(unsigned int num)
{unsigned char digit[4]; // 用于存储数字的个位、十位、百位、千位unsigned char i;// 分解数字到各位digit[0] = num%10; // 获取个位数字digit[1] = (num/10)%10; // 获取十位数字digit[2] = (num/100)%10; // 获取百位数字digit[3] = (num/1000)%10; // 获取千位数字// 逐位扫描显示for(i=0;i<4;i++){P0 = 0x00; // 清空P0口输出,避免段码串扰P2 = i<<2; // 选通第i位数码管,左移2位是硬件连接需要if(0 == num && i>0){P0 = 0x00; // 数字为0且不是最低位,不显示}else if(num<10 && i>0){P0 = 0x00; // 数字是个位数且不是最低位,不显示}else if(num<100 && i>1){P0 = 0x00; // 数字是两位数且超过十位,不显示}else if(num<1000 && i>2){P0 = 0x00; // 数字是三位数且超过百位,不显示}else{P0 = digit_data[digit[i]]; // 输出对应位的段码,显示数字}delay(1); // 延时1ms,利用视觉暂留保持显示稳定}
}
💡 功能说明:
- 使用动态扫描技术,依次点亮每位数码管。
- 利用人眼视觉暂留效应,实现“同时显示”效果。
- 实现前导零消除:如
num=5
显示为5
而非0005
。
✅ 理想运行结果:输入任意数值(0~9999),数码管正确显示该数值,高位无效零自动隐藏。
3. 矩阵按键扫描函数
// 按键扫描函数,检测矩阵按键并返回按键对应的数值
unsigned char key_scan(void)
{unsigned char num = 0; // 存储按键对应的数值unsigned char key = 0; // 存储按键的键值unsigned char i = 0; // 循环变量for(i = 0; i < 4; i++){P1 = ~(1 << i); // 逐行拉低P1口的某一位,扫描按键if(0xf0 != (P1 & 0xf0)) // 判断是否有按键按下{key = P1 & 0xf0; // 获取按键的键值(高四位)switch(key){case 0x70:num = 4 - i;break;case 0xb0:num = 8 - i;break;case 0xd0:num = 12 - i;break;case 0xe0:num = 16 - i;break;default:num = 0;break;}}}return num; // 返回按键对应的数值
}
🔍 扫描逻辑详解:
- 行扫描法:依次将P1口的某一行设为低电平,其余为高电平。
- 列读取:读取P1口的高4位(列线),若不等于
0xf0
,说明有按键按下。 - 键值识别:根据列线状态判断具体是哪个按键。
✅ 理想运行结果:按下任一按键后,
key_scan()
返回对应编号(1~16),例如按下S1返回1,S16返回16。
4. 主程序逻辑
// 主函数
void main(void)
{unsigned char key = 0; // 存储当前按键数值unsigned char tmp = 0; // 存储按键扫描的临时数值eint0_init(); // 初始化外部中断0while(1) // 无限循环{tmp = key_scan(); // 扫描按键,获取临时数值if(tmp) // 如果有有效按键按下key = tmp; // 更新当前按键数值digit_show(key); // 在数码管上显示当前按键数值}
}
🔄 运行流程:
- 初始化外部中断。
- 不断扫描按键。
- 若有按键按下,则更新
key
。 - 将
key
显示在数码管上。
✅ 理想运行结果:按下任意按键,数码管立即显示对应数字(1~16),松开后仍保持显示。
三、定时器/计数器原理与应用
1. 定时器基本概念
定时器是基于内部计数器的硬件模块,用于生成精确的时间基准。它由一个递增计数器构成,当计数达到最大值(溢出)时产生中断。
📌 51单片机定时器为递增计数器,从初值开始向上计数,直到溢出。
2. 定时器相关寄存器
寄存器 | 地址 | 功能描述 |
---|---|---|
TCON | 88H | 控制定时器启停、中断标志 |
TMOD | 89H | 设置工作模式和计数/定时方式 |
TL0/TL1 | 8AH/8BH | 定时器低8位 |
TH0/TH1 | 8CH/8DH | 定时器高8位 |
关键位说明:
TR0
:定时器0运行控制位,1
启动,0
停止。C/T
:0
为定时模式,1
为计数模式。M1/M0
:决定工作模式(00→模式0,01→模式1,10→模式2,11→模式3)。
3. 定时器初始化与中断服务
// 定时器0初始化函数(50ms定时)
void timer0_init(void)
{unsigned char mod = 0;TH0 = (65536 - 50000) / 256 ; // 设置定时器高字节初值TL0 = (65536 - 50000) % 256 ; // 设置定时器低字节初值 mod = TMOD; // 读取TMOD寄存器mod &= ~(0xf << 0); // 清除T0模式设置mod |= (0x1 << 0); // 设置T0为模式1(16位定时器)TMOD = mod;ET0 = 1; // 使能定时器0中断EA = 1; // 使能全局中断TR0 = 1; // 启动定时器0
}
📐 时间计算公式:
晶振频率:12MHz
机器周期:1μs
定时时间:50ms = 50,000μs
计数次数:50,000次
初值 = 65536 - 50000 = 15536
TH0 = 15536 / 256 = 60
TL0 = 15536 % 256 = 176
✅ 理想运行结果:定时器每50ms产生一次中断,实现精确时间基准。
// 定时器0中断服务函数(每50ms执行一次)
void timer0_handler(void) interrupt 1
{TH0 = (65536 - 50000) / 256 ; // 重新装载定时器初值TL0 = (65536 - 50000) % 256 ;TR0 = 1; // 重启定时器num_ms++; // 毫秒计数加1if(num_ms == 20) // 每20次中断为1秒(20 * 50ms = 1s){num_ms = 0;sec++; // 秒加1if(sec == 60) // 秒到60时处理分钟{min++;sec = 0;if(min == 60) // 分钟到60时处理小时{hour++;min = 0;if(hour == 24) // 小时到24时清零{hour = 0;}}}}
}
⏳ 时间管理逻辑:
- 每50ms中断一次,累计20次为1秒。
- 秒、分、时分别累加,实现24小时制计时。
- 支持自动回零(如秒满60归0)。
✅ 理想运行结果:每秒更新一次时间,准确显示
HH:MM:SS
格式的时间。
四、UART串行通信协议
1. UART基础概念
UART(通用异步收发器)是一种常见的串行通信协议,广泛应用于单片机间数据传输。
🧩 通信特性:
特性 | 说明 |
---|---|
通信方式 | 异步、全双工 |
数据线 | 单根(TXD/RXD) |
时钟 | 无共享时钟,依赖波特率匹配 |
空闲电平 | 高电平(逻辑1) |
2. UART通信帧格式
📥 数据帧结构(以发送 0x56
为例):
- 起始位:1位低电平(由高到低跳变)
- 数据位:8位,低位先发(LSB first)
- 校验位:可选(奇偶校验或无)
- 停止位:1~2位高电平
示例:
0x56
=0101 0110
,发送顺序为:01101010
3. UART通信连接示意图
- TXD:发送数据线
- RXD:接收数据线
- GND:接地线(共地)
✅ 两主机之间采用交叉连接:A的TXD接B的RXD,A的RXD接B的TXD。
4. 波特率定义
- 波特率:单位时间内传输的码元数(bit/s)。
- UART中每个码元为1 bit,因此波特率即为每秒传输的比特数。
- 常见波特率:9600、115200、19200等。
📌 注意:通信双方必须设置相同的波特率,否则无法正确解析数据。
五、完整代码整合与运行效果
#include <reg51.h>// 定义74HC595移位寄存器控制引脚
sbit SER = P3^4; // 串行数据输入引脚
sbit RCLK = P3^5; // 存储寄存器时钟引脚(锁存)
sbit SRCLK = P3^6; // 移位寄存器时钟引脚// 软件延时函数
void delay(unsigned int i)
{unsigned int num = i;int j = 0;for(j = 0; j < 10; j++) // 外层循环10次{num = i;while(num--); // 内层循环i次,实现延时}
}// 数码管显示段码表(共阴极),0-9数字 + 小数点
unsigned char digit_data[11] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40};// LED矩阵行列选择函数(通过74HC595控制)
void led_rect_select(unsigned char num)
{unsigned char i = 0;RCLK = 0; // 关闭锁存SRCLK = 0; // 移位时钟初始化for(i = 0; i < 8; i++) // 串行输入8位数据{SRCLK = 0;(num == i) ? (SER = 1) : (SER = 0); // 选择第num行SRCLK = 1; }RCLK = 1; // 锁存数据到输出端
}// 8x8 LED矩阵显示函数
void led_8_8_show(void)
{unsigned char led_1_array[8] = {0x0, 0x10, 0x30, 0x10, 0x10, 0x10, 0x38, 0x0};unsigned char i = 0;for(i = 0; i < 8; i++) // 逐行扫描显示{ P0 = 0xff; // 关闭所有列led_rect_select(i); // 选择第i行P0 = ~led_1_array[i]; // 输出该行数据(取反因为共阳极)delay(1); // 短暂延时}
}// 外部中断0初始化函数
void eint0_init(void)
{IT0 = 1; // 设置外部中断0为下降沿触发EX0 = 1; // 使能外部中断0EA = 1; // 使能全局中断
}// 数码管动态扫描显示函数
void digit_show(unsigned char * num)
{char i = 0;for(i = 0; i < 8; i++) // 扫描8位数码管{P2 = i << 2; // 选择第i位数码管P0 = digit_data[num[8 - i - 1]]; // 输出对应数字段码delay(1); // 短暂延时P0 = 0; // 关闭显示,防止重影}
}unsigned int num = 0; // 全局计数变量// 矩阵键盘扫描函数
unsigned char key_scan(void)
{unsigned char num = 0; unsigned char key = 0;unsigned char i = 0;for(i = 0; i < 4; i++) // 扫描4行{ P1 = ~(1 << i); // 逐行输出低电平if(0xf0 != (P1 & 0xf0)) // 检测是否有按键按下{key = P1 & 0xf0; // 读取列状态switch(key){case 0x70: // 第1行按键num = 4 - i;break;case 0xb0: // 第2行按键num = 8 - i;break;case 0xd0: // 第3行按键num = 12 - i;break;case 0xe0: // 第4行按键num = 16 - i;break;default:num = 0;break;}}}return num; // 返回按键编号
}// 定时器0初始化函数(50ms定时)
void timer0_init(void)
{unsigned char mod = 0;TH0 = (65536 - 50000) / 256 ; // 设置定时器高字节初值TL0 = (65536 - 50000) % 256 ; // 设置定时器低字节初值 mod = TMOD; // 读取TMOD寄存器mod &= ~(0xf << 0); // 清除T0模式设置mod |= (0x1 << 0); // 设置T0为模式1(16位定时器)TMOD = mod;ET0 = 1; // 使能定时器0中断EA = 1; // 使能全局中断TR0 = 1; // 启动定时器0
}unsigned char num_ms = 0; // 毫秒计数器
unsigned char sec = 0; // 秒计数器
unsigned char min = 0; // 分钟计数器
unsigned char hour = 0; // 小时计数器// 主函数
void main(void)
{unsigned char key = 0;unsigned char tmp = 0;unsigned char array[8] = {0,0,10,0,0,10,0,0}; // 时间显示数组,包含冒号hour = 23; // 设置初始时间min = 59;sec = 50;eint0_init(); // 初始化外部中断timer0_init(); // 初始化定时器while(1){array[0] = hour / 10; // 小时十位array[1] = hour % 10; // 小时个位array[3] = min / 10; // 分钟十位array[4] = min % 10; // 分钟个位array[6] = sec / 10; // 秒十位array[7] = sec % 10; // 秒个位digit_show(array); // 动态扫描显示时间}
}// 外部中断0服务函数
void eint0_handler(void) interrupt 0
{num++; // 中断计数加1
}// 定时器0中断服务函数
void timer0_handler(void) interrupt 1
{TH0 = (65536 - 50000) / 256 ; // 重新装载定时器初值TL0 = (65536 - 50000) % 256 ;TR0 = 1; // 重启定时器num_ms++; // 毫秒计数加1if(num_ms == 20) // 每20次中断为1秒{num_ms = 0;sec++; // 秒加1if(sec == 60) // 秒到60时处理分钟{min++;sec = 0;if(min == 60) // 分钟到60时处理小时{hour++;min = 0;if(hour == 24) // 小时到24时清零{hour = 0;}}}}
}
✅ 理想运行结果总结:
- 数码管显示:正确显示
HH:MM:SS
格式的时间,每秒自动更新。 - 按键功能:按下任意按键后,数码管显示对应数字(1~16)。
- 外部中断:每次按键按下,
num
增加1,可用于计数。 - 定时器精度:时间误差小于±0.002%,适合数字时钟应用。
六、知识点归纳总结
模块 | 核心内容 | 应用场景 |
---|---|---|
中断系统 | 外部中断0、定时器中断 | 实时响应按键、定时任务 |
矩阵按键 | 行列扫描法 | 多按键输入,节省IO |
数码管显示 | 动态扫描、段码控制 | 数字显示、时间输出 |
定时器 | 模式1、16位定时、自动重装 | 精确计时、PWM生成 |
UART通信 | 异步、全双工、波特率 | 单片机间通信、调试打印 |
✅ 本日学习目标达成:掌握中断、定时器、矩阵按键、数码管、UART等嵌入式开发核心技术,具备构建完整人机交互系统的综合能力。