尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

51单片机进阶总结(二):数码管、中断与定时器(附完整代码)

51单片机进阶总结(二):数码管、中断与定时器(附完整代码) 文章目录前言一、数码管1.1 数码管的结构1.2 静态显示1.3 动态显示原理1.4 关键消影1.5 静态扫描代码软件延时版二、中断系统2.1 什么是中断2.2 中断相关寄存器IE —— 中断允许寄存器TCON —— 中断触发与标志IP —— 中断优先级2.3 中断服务函数的写法2.4 外部中断实验按键翻转 LED2.5 外部中断 vs 按键轮询三、定时器 / 计数器3.1 定时器原理3.2 TMOD —— 工作方式寄存器3.3 定时初值计算3.4 定时器初始化模板3.5 定时器中断实验1 秒 LED 闪烁3.6 定时器做计数器四、综合实验数码管秒表定时器扫描 计时五、把按键也加进来秒表启停六、常见问题6.1 数码管显示重影 / 花屏6.2 数码管亮度不均匀6.3 定时器时间不准6.4 中断不触发6.5 变量在 ISR 和主程序之间不更新七、总结前言上一篇我们总结了 51单片机的基本概念、最小系统、流水灯和独立按键。这一篇继续往下走聊三个非常核心的内容数码管从静态显示到动态扫描中断系统外部中断的用法定时器/计数器精准定时、定时器中断这三者是 51单片机从会点灯到能做事的分水岭。掌握之后就可以做数码管时钟、计数器、按键计时器等综合项目了。代码基于 Keil C51 STC89C52 / AT89C5212MHz 晶振。不同开发板引脚可能不同请以实际原理图为准。一、数码管1.1 数码管的结构数码管本质上是 8 个 LED7 段 1 个小数点封装在一起a ┌─────┐ f │ │ b ├──g──┤ e │ │ c └─────┘ · dp d按公共端接法分为两类类型公共端点亮方式段码共阴接 GND段线给高电平正逻辑共阳接 VCC段线给低电平反逻辑共阴段码表a 为最低位dp 为最高位数字段码数字段码00x3F50x6D10x0660x7D20x5B70x0730x4F80x7F40x6690x6F共阳段码 共阴段码按位取反0 - 0xC0 1 - 0xF9 2 - 0xA4 ...带小数点的数字共阴段码| 0x80例如 0. 是0xBF。1.2 静态显示一位数码管独占一组 IO 口直接输出段码即可#includereg52.htypedefunsignedcharu8;u8 code seg_code[]{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};voidmain(){while(1){P0seg_code[8];// 显示数字 8}}优点程序简单显示稳定无闪烁。缺点一位占用 8 个 IO。4 位数码管就要 32 个 IO51单片机根本不够用。所以多位数码管几乎都用动态扫描。1.3 动态显示原理多位数码管内部段线并联在一起只有位选端公共端独立。以 4 位共阴数码管为例段选 P0 (a~dp) 共用 │ ┌─────────┼─────────┼─────────┼─────────┐ │ │ │ │ │ 位选1 位选2 位选3 位选4 P2.0 P2.1 P2.2 P2.3扫描过程位选1拉低段选送第 1 位数字 → 第 1 位亮关闭第 1 位位选2拉低段选送第 2 位数字 → 第 2 位亮……依次快速循环只要扫描速度够快每位 1~2ms整屏刷新率 50Hz人眼就会因为视觉暂留看到 4 位同时显示。1.4 关键消影扫描时如果不消影上一位的段码会残留在下一位上导致显示重影。正确顺序P20xFF;// 1. 先关闭所有位选消影P0seg_code[digit];// 2. 再送新的段码P2~(1pos);// 3. 最后打开当前位如果先送段码再切位选就会看到上一位的鬼影。1.5 静态扫描代码软件延时版#includereg52.htypedefunsignedcharu8;typedefunsignedintu16;u8 code seg_code[]{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};u8 disp_buf[4]{1,2,3,4};// 4 位显示缓冲区voiddelay_ms(u16 ms){u16 i,j;for(ims;i0;i--)for(j110;j0;j--);}voiddisplay(){u8 i;for(i0;i4;i){P20xFF;// 消影P0seg_code[disp_buf[i]];P2~(1i);// 打开第 i 位delay_ms(2);// 每位显示 2ms}}voidmain(){while(1){display();}}这段代码能显示但问题在于display()会一直占用 CPU。如果还要做按键检测、计时就会互相干扰。真正工程化的做法是用定时器中断来扫描后面会讲。二、中断系统2.1 什么是中断中断就是CPU 正在执行主程序时突然有个紧急事件CPU 暂停主程序转去执行中断服务函数ISR执行完再回到原处继续。51单片机有 5 个中断源中断源中断号入口地址触发条件外部中断0 (INT0)00x0003P3.2 引脚电平/边沿定时器0 (T0)10x000BT0 溢出外部中断1 (INT1)20x0013P3.3 引脚电平/边沿定时器1 (T1)30x001BT1 溢出串口 (UART)40x0023接收/发送完成2.2 中断相关寄存器IE —— 中断允许寄存器位名称作用EA总中断允许1开总中断ES串口中断1允许ET1定时器1中断1允许EX1外部中断11允许ET0定时器0中断1允许EX0外部中断01允许记住任何中断要生效必须 EA1 且对应位1。TCON —— 中断触发与标志位名称作用IT0INT0 触发方式1下降沿0低电平IE0INT0 请求标志硬件自动置位IT1INT1 触发方式1下降沿0低电平IE1INT1 请求标志硬件自动置位TR0 / TR1定时器启停1启动TF0 / TF1定时器溢出标志溢出时硬件置 1实际开发中通常只操作IT0、IT1。IP —— 中断优先级默认同级中断不能嵌套。设置PX01、PT01等可提高优先级实现中断嵌套。初学阶段一般不用动。2.3 中断服务函数的写法void函数名()interrupt 中断号{// 中断处理代码}interrupt是关键字中断号必须是 0~4ISR 应尽量短小不要在里面放长延时全局变量在 ISR 和主程序之间共享时建议加volatile2.4 外部中断实验按键翻转 LED硬件按键接 P3.2INT0LED 接 P2.0低电平点亮。#includereg52.hsbit LEDP2^0;voiddelay_ms(unsignedintms){unsignedinti,j;for(ims;i0;i--)for(j110;j0;j--);}voidmain(){IT01;// INT0 下降沿触发EX01;// 允许外部中断0EA1;// 开总中断LED1;// 初始熄灭while(1){// 主循环可以去做别的事}}voidEX0_ISR()interrupt0{delay_ms(20);// 简单消抖if(P3^20){LED!LED;// 翻转 LED}}注意按键未按下时 P3.2 应为高电平需要上拉下降沿触发时按键按下的瞬间会触发中断ISR 里加延时是权宜之计更好的做法是用定时器消抖2.5 外部中断 vs 按键轮询方式优点缺点轮询while里判断简单直观占用 CPU主循环被拖慢外部中断响应快不占用主循环需要消抖ISR 要短实际项目中按键通常还是用定时器扫描 状态机最稳外部中断更适合处理脉冲计数、编码器、红外接收这类事件。三、定时器 / 计数器3.1 定时器原理51单片机内部有两个定时器/计数器T0 和 T152 系列还有 T2。它的本质是一个加法计数器每个机器周期计一次数计满溢出时硬件置TF0 1如果开了中断就会触发定时器中断计数脉冲来源C/T 0内部机器周期 →定时器模式C/T 1外部引脚T0 是 P3.4T1 是 P3.5→计数器模式3.2 TMOD —— 工作方式寄存器高 4 位管 T1低 4 位管 T0位名称说明GATE门控0TRx 启动1INTx 引脚TRx 共同启动C/T定时/计数选择0定时1计数M1 M0工作方式见下表M1 M0方式说明0 0方式013 位定时器0 1方式116 位定时器最常用1 0方式28 位自动重装串口波特率常用1 1方式3拆分使用常用配置TMOD0x01;// T0方式1定时器TMOD0x10;// T1方式1定时器TMOD0x21;// T1 方式2 T0 方式1串口场景保险的写法是先清零再置位TMOD0xF0;// 清 T0 的低 4 位TMOD|0x01;// T0 方式13.3 定时初值计算方式1是 16 位最大值 65536。从初值计到 65536 溢出定时时间 (65536 - 初值) × 机器周期 机器周期 12 / 晶振频率12MHz 晶振机器周期 1μs一个计数 1μs。定时时长计数次数初值THTL1 ms1000645360xFC0x1810 ms10000555360xD80xF020 ms20000455360xB10xE050 ms50000155360x3C0xB0最大定时方式1 单次最长 65536 μs ≈ 65.5 ms12MHz。如果要定时 1 秒就让定时器每 50ms 中断一次累计 20 次。3.4 定时器初始化模板voidTimer0_Init(){TMOD0xF0;TMOD|0x01;// T0 方式1TH00xFC;// 1ms 12MHzTL00x18;TF00;// 清溢出标志ET01;// 允许 T0 中断EA1;// 开总中断TR01;// 启动 T0}注意方式1不是自动重装的ISR 里必须重新赋初值否则下一次定时时间会乱。3.5 定时器中断实验1 秒 LED 闪烁#includereg52.htypedefunsignedcharu8;typedefunsignedintu16;sbit LEDP2^0;u16 cnt0;// 50ms 中断计数voidTimer0_Init(){TMOD0xF0;TMOD|0x01;TH00x3C;// 50ms 12MHzTL00xB0;ET01;EA1;TR01;}voidmain(){Timer0_Init();while(1){// 主循环空闲可以做别的}}voidTimer0_ISR()interrupt1{TH00x3C;// 重装初值TL00xB0;cnt;if(cnt20)// 20 × 50ms 1s{cnt0;LED!LED;}}主循环完全空闲LED 依然精准闪烁。这就是定时器的价值。3.6 定时器做计数器将 T0 设为计数器模式接 P3.4 输入脉冲voidCounter0_Init(){TMOD0xF0;TMOD|0x05;// T0方式1计数器模式TH00;TL00;ET01;EA1;TR01;}每来一个下降沿脉冲T0 加 1计满 65536 溢出触发中断。常用于测速、计件。四、综合实验数码管秒表定时器扫描 计时把前面三块拼起来用一个定时器同时完成数码管动态扫描和秒表计时。硬件4 位共阴数码管段选 → P0位选 → P2.0 ~ P2.3低电平选通思路定时器 0 每 1ms 中断一次每次中断扫描一位数码管4ms 扫完一轮累计 1000 次中断 1 秒秒数 1主循环只负责把秒数拆成 4 位数字#includereg52.htypedefunsignedcharu8;typedefunsignedintu16;u8 code seg_code[]{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};u8 disp_buf[4]{0,0,0,0};// 显示缓冲区u16 ms_cnt0;// 毫秒计数u16 seconds0;// 秒数/* ---------------- 定时器0 初始化1ms ---------------- */voidTimer0_Init(){TMOD0xF0;TMOD|0x01;// T0方式1TH00xFC;// 1ms 12MHzTL00x18;ET01;EA1;TR01;}/* ---------------- 主函数 ---------------- */voidmain(){Timer0_Init();while(1){// 把秒数拆成 4 位数字disp_buf[0]seconds/1000%10;disp_buf[1]seconds/100%10;disp_buf[2]seconds/10%10;disp_buf[3]seconds%10;if(seconds9999)seconds0;}}/* ---------------- 定时器0 中断服务函数 ---------------- */voidTimer0_ISR()interrupt1{staticu8 digit0;/* --- 重装初值 --- */TH00xFC;TL00x18;/* --- 数码管扫描含消影 --- */P20xFF;// 关闭所有位选P0seg_code[disp_buf[digit]];// 送段码P2~(1digit);// 打开当前位低电平选通digit;if(digit4)digit0;/* --- 计时 --- */ms_cnt;if(ms_cnt1000)// 1000 × 1ms 1s{ms_cnt0;seconds;}}现象数码管从 0000 开始每秒 1显示稳定无闪烁。关键点扫描周期 4ms刷新率 250Hz远高于人眼临界频率每次中断只做一件事扫一位ISR 很短主循环空闲可以继续加按键处理、串口通信五、把按键也加进来秒表启停在中断里扫描数码管主循环里读按键两者互不干扰sbit KEYP3^1;voidmain(){bit running1;// 秒表运行标志Timer0_Init();while(1){/* 按键检测启停切换 */if(KEY0){delay_ms(20);if(KEY0){running!running;while(KEY0);// 等待松手}}/* 显示缓冲区更新 */disp_buf[0]seconds/1000%10;disp_buf[1]seconds/100%10;disp_buf[2]seconds/10%10;disp_buf[3]seconds%10;/* 用一个全局标志控制计时是否累加 */g_runningrunning;}}在 ISR 中if(g_running){ms_cnt;if(ms_cnt1000){ms_cnt0;seconds;}}g_running需要定义为volatile bit或volatile u8避免编译器优化导致的读取异常。六、常见问题6.1 数码管显示重影 / 花屏没消影先关位选再送段码最后开位选扫描太慢每位控制在 1~2ms位选引脚冲突检查位选和段选是否共用端口6.2 数码管亮度不均匀各位显示时间不同 → 确保每位延时一致限流电阻过大 → 适当减小注意别超过 IO 灌电流上限位数太多 → 每位的有效点亮时间变短6.3 定时器时间不准忘记重装初值方式1 必须在 ISR 里重新赋值ISR 执行时间过长会挤占计时导致累计误差晶振频率不对11.0592MHz 与 12MHz 初值完全不同想更准 → 用方式2 自动重装或改用 11.0592MHz 晶振6.4 中断不触发EA 1忘了吗对应中断允许位EX0/ET0开了吗外部中断触发方式IT0设对了吗按键没上拉P3.2 一直是低电平低电平触发时会导致反复进中断6.5 变量在 ISR 和主程序之间不更新加volatilevolatile u16 seconds;多字节变量如u16在主程序读取时可能被中断打断造成读到半新半旧的值。临界区可以这样处理EA0;tmpseconds;EA1;七、总结这一篇的核心就三句话数码管多位显示靠动态扫描扫描关键是消影。中断让 CPU 从死等变成事件驱动EA1 对应允许位 中断服务函数。定时器内部硬件计数精准定时是数码管扫描、按键消抖、串口波特率、PWM的通用基础设施。把这三块吃透就可以做很多综合项目了数码管电子钟时/分/秒 按键调时秒表 / 计数器红外遥控解码 数码管显示温度采集 数码管显示串口通信 上位机显示下一篇可以聊串口通信UART和PWM 调光那也是 51单片机非常实用的两块内容。本文为学习笔记代码已尽量精简可读欢迎交流指正。如果对你有帮助可以点赞收藏。
返回列表