单片机数码管LED灯独立按键中断定时器综合实战

1. 引言

在单片机入门与进阶过程中,数码管显示、LED灯控制、独立按键检测、外部中断和定时器是五个最基础也最常用的功能模块。本文以51单片机(STC89C52)为例,把这五个模块整合到一个综合实战项目中,帮助读者理解它们之间的协作关系,并掌握中断与定时器在真实场景中的典型用法。

2. 硬件基础与原理

在动手写代码之前,先梳理一下各模块的硬件连接和基本原理。

2.1 数码管显示原理

数码管按段数分为七段和八段(含小数点),按结构分为共阴极和共阳极两种。共阴极数码管公共端接地,某段引脚给高电平时对应段点亮;共阳极则相反,公共端接电源,某段引脚给低电平时点亮。

多位数码管通常采用动态扫描方式驱动:同一时刻只点亮一位,通过快速轮流点亮各位,利用人眼视觉暂留效应形成"同时显示"的效果。扫描频率一般不低于50Hz,否则会出现明显闪烁。

2.2 LED灯控制

LED灯通过单片机I/O口驱动,通常串联一个限流电阻(约220Ω到1kΩ)防止电流过大损坏器件。输出高电平点亮还是低电平点亮,取决于LED的接法(灌电流或拉电流)。

2.3 独立按键与消抖

独立按键一端接I/O口,另一端接电源或地。按键按下和释放时,由于机械触点的弹性,会产生约5ms到10ms的电平抖动,需要软件消抖(延时检测)或硬件消抖(RC滤波)来避免误触发。

2.4 外部中断

51单片机提供两个外部中断INT0(P3.2)和INT1(P3.3),可配置为下降沿触发或低电平触发。外部中断适合处理需要即时响应的外部事件,例如紧急停止、计数脉冲等。

2.5 定时器

51单片机通常内置两个16位定时器/计数器T0和T1。定时器工作在定时模式时,对机器周期计数,可用于精确延时、定时扫描、产生PWM等。定时器溢出会触发中断,在中断服务函数中处理周期性任务。

3. 系统整体设计

本实战项目实现以下功能:

  • 数码管动态显示一个从0到99循环计数的数值。
  • LED灯以1Hz频率闪烁,指示系统运行状态。
  • 独立按键K1用于暂停/恢复计数,K2用于清零。
  • 外部中断INT0用于紧急暂停,INT1用于切换LED闪烁模式。
  • 定时器T0产生1ms基准中断,用于数码管扫描和计时。

整体架构采用"定时器中断驱动 + 主循环查询"的方式:定时器中断负责精确计时和数码管扫描,主循环负责按键检测和逻辑处理。

4. 硬件连接

模块 引脚 说明
数码管段选 P0 共阴极数码管段码输出
数码管位选 P2.0-P2.3 四位数码管位选控制
LED灯 P1.0 状态指示灯
按键K1 P3.0 暂停/恢复计数
按键K2 P3.1 计数清零
外部中断INT0 P3.2 紧急暂停
外部中断INT1 P3.3 切换LED闪烁模式

5. 软件设计

5.1 定时器初始化

定时器T0工作在方式1(16位定时),定时1ms。晶振12MHz时,机器周期为1us,计数1000次即1ms,初值为65536-1000=64536,即0xFC18。

c 复制代码
void Timer0_Init(void)
{
    TMOD &= 0xF0;   // 清除T0模式位
    TMOD |= 0x01;   // T0方式1,16位定时
    TH0 = 0xFC;     // 初值高8位
    TL0 = 0x18;     // 初值低8位
    ET0 = 1;        // 开启T0中断
    EA = 1;         // 开启总中断
    TR0 = 1;        // 启动定时器
}

5.2 外部中断初始化

INT0和INT1均配置为下降沿触发,优先级设为最高,确保紧急操作能及时响应。

c 复制代码
void External_Interrupt_Init(void)
{
    IT0 = 1;        // INT0下降沿触发
    IT1 = 1;        // INT1下降沿触发
    EX0 = 1;        // 开启INT0中断
    EX1 = 1;        // 开启INT1中断
    PX0 = 1;        // INT0高优先级
    PX1 = 1;        // INT1高优先级
    EA = 1;         // 开启总中断
}

5.3 数码管段码表

共阴极数码管0到9的段码定义如下,按a、b、c、d、e、f、g、dp顺序排列。

c 复制代码
unsigned char code seg_code[10] = {
    0x3F, // 0
    0x06, // 1
    0x5B, // 2
    0x4F, // 3
    0x66, // 4
    0x6D, // 5
    0x7D, // 6
    0x07, // 7
    0x7F, // 8
    0x6F  // 9
};

5.4 数码管动态扫描

在定时器中断中,每1ms切换一位数码管,实现四位数码管的动态扫描。

c 复制代码
void Display_Scan(void)
{
    static unsigned char pos = 0;
    P2 = 0x00;      // 关闭所有位选,消隐
    switch(pos)
    {
        case 0: P0 = seg_code[count / 10]; P2 = 0x01; break;
        case 1: P0 = seg_code[count % 10]; P2 = 0x02; break;
        case 2: P0 = seg_code[led_mode];  P2 = 0x04; break;
        case 3: P0 = seg_code[state];     P2 = 0x08; break;
    }
    pos++;
    if(pos >= 4) pos = 0;
}

5.5 定时器中断服务函数

定时器中断每1ms进入一次,负责数码管扫描和计时累加。每1000次(即1秒)更新一次计数。

c 复制代码
void Timer0_ISR(void) interrupt 1
{
    static unsigned int ms_count = 0;
    TH0 = 0xFC;     // 重装初值
    TL0 = 0x18;
    Display_Scan(); // 数码管扫描
    ms_count++;
    if(ms_count >= 1000)
    {
        ms_count = 0;
        if(running)
        {
            count++;
            if(count > 99) count = 0;
        }
        led_toggle = !led_toggle;
        P1_0 = led_toggle;
    }
}

5.6 外部中断服务函数

INT0用于紧急暂停,INT1用于切换LED闪烁模式。

c 复制代码
void INT0_ISR(void) interrupt 0
{
    running = 0;    // 紧急暂停
    state = 0;
}

void INT1_ISR(void) interrupt 2
{
    led_mode = !led_mode;   // 切换LED模式
    if(led_mode == 0)
        P1_0 = 0;   // 模式0:LED熄灭
    else
        P1_0 = 1;   // 模式1:LED常亮
}

5.7 按键检测与消抖

主循环中轮询按键,采用延时消抖方式,避免机械抖动造成误触发。

c 复制代码
void Key_Scan(void)
{
    if(K1 == 0)
    {
        delay_ms(10);
        if(K1 == 0)
        {
            running = !running;
            state = running ? 1 : 0;
            while(K1 == 0); // 等待释放
        }
    }
    if(K2 == 0)
    {
        delay_ms(10);
        if(K2 == 0)
        {
            count = 0;
            while(K2 == 0); // 等待释放
        }
    }
}

5.8 主函数

主函数完成初始化后进入主循环,主循环只负责按键检测,其余任务由中断驱动。

c 复制代码
void main(void)
{
    Timer0_Init();
    External_Interrupt_Init();
    running = 1;
    state = 1;
    led_mode = 1;
    while(1)
    {
        Key_Scan();
    }
}

6. 完整代码

将上述模块整合为完整工程,可直接在Keil中编译下载运行。

c 复制代码
#include <reg52.h>

sbit K1 = P3^0;
sbit K2 = P3^1;
sbit P1_0 = P1^0;

unsigned char code seg_code[10] = {
    0x3F, 0x06, 0x5B, 0x4F, 0x66,
    0x6D, 0x7D, 0x07, 0x7F, 0x6F
};

unsigned char count = 0;
unsigned char running = 1;
unsigned char state = 1;
unsigned char led_mode = 1;
bit led_toggle = 0;

void delay_ms(unsigned int ms)
{
    unsigned int i, j;
    for(i = 0; i < ms; i++)
        for(j = 0; j < 120; j++);
}

void Timer0_Init(void)
{
    TMOD &= 0xF0;
    TMOD |= 0x01;
    TH0 = 0xFC;
    TL0 = 0x18;
    ET0 = 1;
    EA = 1;
    TR0 = 1;
}

void External_Interrupt_Init(void)
{
    IT0 = 1;
    IT1 = 1;
    EX0 = 1;
    EX1 = 1;
    PX0 = 1;
    PX1 = 1;
    EA = 1;
}

void Display_Scan(void)
{
    static unsigned char pos = 0;
    P2 = 0x00;
    switch(pos)
    {
        case 0: P0 = seg_code[count / 10]; P2 = 0x01; break;
        case 1: P0 = seg_code[count % 10]; P2 = 0x02; break;
        case 2: P0 = seg_code[led_mode];  P2 = 0x04; break;
        case 3: P0 = seg_code[state];     P2 = 0x08; break;
    }
    pos++;
    if(pos >= 4) pos = 0;
}

void Timer0_ISR(void) interrupt 1
{
    static unsigned int ms_count = 0;
    TH0 = 0xFC;
    TL0 = 0x18;
    Display_Scan();
    ms_count++;
    if(ms_count >= 1000)
    {
        ms_count = 0;
        if(running)
        {
            count++;
            if(count > 99) count = 0;
        }
        led_toggle = !led_toggle;
        P1_0 = led_toggle;
    }
}

void INT0_ISR(void) interrupt 0
{
    running = 0;
    state = 0;
}

void INT1_ISR(void) interrupt 2
{
    led_mode = !led_mode;
    if(led_mode == 0)
        P1_0 = 0;
    else
        P1_0 = 1;
}

void Key_Scan(void)
{
    if(K1 == 0)
    {
        delay_ms(10);
        if(K1 == 0)
        {
            running = !running;
            state = running ? 1 : 0;
            while(K1 == 0);
        }
    }
    if(K2 == 0)
    {
        delay_ms(10);
        if(K2 == 0)
        {
            count = 0;
            while(K2 == 0);
        }
    }
}

void main(void)
{
    Timer0_Init();
    External_Interrupt_Init();
    running = 1;
    state = 1;
    led_mode = 1;
    while(1)
    {
        Key_Scan();
    }
}

7. 调试要点与常见问题

  • 数码管闪烁:检查扫描频率是否足够,位选切换前务必先消隐,避免拖影。
  • 按键误触发:确认消抖延时是否足够,建议10ms到20ms。
  • 中断不响应:检查EA总中断是否开启,外部中断触发方式配置是否正确。
  • 定时不准:确认晶振频率与初值计算是否匹配,中断服务函数中要及时重装初值。
  • 优先级冲突:外部中断和定时器中断同时触发时,注意优先级设置,避免关键操作被延迟。

8. 总结

本文通过一个综合实战项目,把数码管动态扫描、LED控制、独立按键消抖、外部中断和定时器中断整合在一起。核心思路是:定时器中断提供稳定的时间基准,负责周期性任务;外部中断处理紧急事件;主循环处理按键等非实时任务。理解这种"中断驱动 + 主循环查询"的架构,对后续学习更复杂的单片机系统非常有帮助。

相关推荐
自小吃多2 小时前
PCB Editor软件复制、改变、删除操作笔记
笔记·嵌入式硬件
数字新视界3 小时前
数据中心基础设施管理系统发布新版本,助力企业云化转型
嵌入式硬件·物联网·数据中心·数据中心基础设施管理·dcim管理系统
SKH.3 小时前
单片机(1)
单片机·嵌入式硬件
llilian_164 小时前
小切口解决大问题:时序校准场景下的标准时间间隔脉冲信号发生器应用方案
功能测试·单片机·嵌入式硬件·51单片机
三块石头1014 小时前
车载ADC避坑:为什么ADC采集值偏低?
经验分享·stm32·单片机·嵌入式硬件·mcu·硬件架构·硬件工程
jianqiang.xue4 小时前
ESP-IDF保姆级入门39|固件安全与安全启动全解:签名校验/Flash加密/防回滚/OTP密钥/知识产权保护,掌握工业级固件安全防护体系
单片机·物联网·esp32
llilian_164 小时前
时间统一系统 高精度时统设备选购避坑指南 授时系统
大数据·网络·人工智能·功能测试·单片机·嵌入式硬件·51单片机
en.en..4 小时前
sizeof与strlen的区别(附UDP网络编程实战坑点)
单片机·嵌入式硬件
AOI小白新手上路5 小时前
韦东山IMX6ULL快速入门路线
嵌入式硬件