蓝桥杯单片机数码管与定时器

单片机数码管显示与定时器应用笔记
一、SEG数码管原理图解析

  1. 数码管基本工作原理
    1.1 双信号控制系统
    数码管显示依赖于两个独立控制的信号:

段选(SEG_DLE):控制7段LED(a~g)+ 小数点的亮灭状态,决定显示何种字符

位选(SEG_WLE):控制6位数码管中哪一位被激活,决定字符的显示位置

电平逻辑:本系统中采用低电平有效控制方式

段选信号:0→亮,1→灭

位选信号:0→选中该位数码管,1→未选中

1.2 段码映射规则
段码二进制位(共8位)对应关系(从低位到高位):

text
位序: 0 1 2 3 4 5 6 7
对应: dp g f e d c b a
记忆口诀:从小数点(dp)开始,逆时针依次对应各段

dp → g段 → f段 → e段 → d段 → c段 → b段 → a段

1.3 硬件接口定义
text

信号线 控制引脚 数据端口
段选(Seg_DLE) P2.6 P0口(输出段码)
位选(Seg_WLE) P2.7 P0口(输出位码)
  1. 基础驱动代码实现
    2.1 必要的头文件与延时函数
    c
    #include <REGX52.H> // 8051系列单片机寄存器定义
    #include <intrins.h> // 包含_nop_()等内联函数

/**

  • @brief 毫秒级延时函数
  • @param xms 延时毫秒数
  • @note 基于12MHz晶振计算
    /
    void Delay(unsigned int xms)
    {
    unsigned char i, j;
    while(xms–)
    {
    i = 2;
    j = 239;
    do
    {
    while (–j);
    } while (–i);
    }
    }
    2.2 预定义码表
    c
    /
    共阴极数码管段码表(0-9 + 全灭)*/
    unsigned char Seg_Dula[11] = {
    0x3F, // 0: 0011 1111
    0x06, // 1: 0000 0110
    0x5B, // 2: 0101 1011
    0x4F, // 3: 0100 1111
    0x66, // 4: 0110 0110
    0x6D, // 5: 0110 1101
    0x7D, // 6: 0111 1101
    0x07, // 7: 0000 0111
    0x7F, // 8: 0111 1111
    0x6F, // 9: 0110 1111
    0x00 // 空: 0000 0000
    };

/* 6位数码管位选码表 /
unsigned char Seg_Wela[6] = {
0xFE, // 1111 1110 - 第1位
0xFD, // 1111 1101 - 第2位
0xFB, // 1111 1011 - 第3位
0xF7, // 1111 0111 - 第4位
0xEF, // 1110 1111 - 第5位
0xDF // 1101 1111 - 第6位
};
2.3 按键矩阵扫描函数
c
/
*

  • @brief 4×4矩阵键盘扫描函数

  • @return 1-16: 对应按键编号, 0: 无按键按下
    */
    unsigned char Key_Read()
    {
    unsigned char key_val = 0;

    /* 扫描第一行(P3.0=0) */
    P3 = 0xFE; // 1111 1110
    if(P3_4 == 0) key_val = 1;
    if(P3_5 == 0) key_val = 2;
    if(P3_6 == 0) key_val = 3;
    if(P3_7 == 0) key_val = 4;

    /* 扫描第二行(P3.1=0) */
    P3 = 0xFD; // 1111 1101
    if(P3_4 == 0) key_val = 5;
    if(P3_5 == 0) key_val = 6;
    if(P3_6 == 0) key_val = 7;
    if(P3_7 == 0) key_val = 8;

    /* 扫描第三行(P3.2=0) */
    P3 = 0xFB; // 1111 1011
    if(P3_4 == 0) key_val = 9;
    if(P3_5 == 0) key_val = 10;
    if(P3_6 == 0) key_val = 11;
    if(P3_7 == 0) key_val = 12;

    /* 扫描第四行(P3.3=0) */
    P3 = 0xF7; // 1111 0111
    if(P3_4 == 0) key_val = 13;
    if(P3_5 == 0) key_val = 14;
    if(P3_6 == 0) key_val = 15;
    if(P3_7 == 0) key_val = 16;

    return key_val;
    }
    2.4 数码管显示核心函数
    c
    /**

  • @brief 单个数码管显示函数

  • @param wela 位选索引(0-5对应第1-6位)

  • @param dula 段选索引(0-9对应数字,10对应全灭)
    /
    void Seg_Disp(unsigned char wela, unsigned char dula)
    {
    /
    第一步:消隐处理(避免显示重影) */
    P0 = 0x00; // 段码数据置零(全灭)
    P2_6 = 1; // 打开段选锁存器
    P2_6 = 0; // 关闭锁存器,数据被锁存

    /* 第二步:选择显示位置 */
    P0 = Seg_Wela[wela]; // 输出位选码
    P2_7 = 1; // 打开位选锁存器
    P2_7 = 0; // 关闭锁存器,位选数据被锁存

    /* 第三步:显示具体字符 /
    P0 = Seg_Dula[dula]; // 输出段码
    P2_6 = 1; // 打开段选锁存器
    P2_6 = 0; // 关闭锁存器,显示数据被锁存
    }
    2.5 按键状态检测机制
    c
    /
    全局按键状态变量 */
    unsigned char Key_Val = 0; // 当前按键值
    unsigned char Key_Down = 0; // 按键按下事件
    unsigned char Key_Up = 0; // 按键释放事件
    unsigned char Key_Old = 0; // 上一次按键值

/* 主循环中的按键检测逻辑 */
while(1)
{
Key_Val = Key_Read(); // 1. 获取当前按键状态
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 2. 检测下降沿(按下瞬间)
Key_Up = (~Key_Val) & (Key_Val ^ Key_Old); // 3. 检测上升沿(释放瞬间)
Key_Old = Key_Val; // 4. 更新历史状态

// 说明:这四行代码必须按顺序执行,且需在循环中不断调用

}
二、定时器中断应用详解

  1. 定时器的必要性
    问题分析
    静态显示问题:直接调用显示函数只能固定显示单一内容

动态显示需求:多位数码管需要显示不同数字

视觉暂留原理:人眼有约0.1秒的视觉暂留效应

解决方案
使用定时器中断实现动态扫描:

每隔1-5ms切换显示一位

6位数码管扫描周期为6-30ms(> 50Hz无闪烁)

利用人眼视觉暂留,实现"同时显示"效果

  1. 定时器配置流程(使用STC-ISP工具)
    步骤 操作 参数设置
    1 打开STC-ISP软件 选择对应单片机型号
    2 进入定时器计算器 工具 → 定时器计算器
    3 设置系统频率 12.000MHz
    4 设置定时周期 1.000毫秒
    5 选择定时器模式 16位自动重装
    6 生成代码 复制到工程中
  2. 定时器初始化代码
    c
    /**
  • @brief 定时器0初始化(1ms@12MHz)
    /
    void Timer0_Init(void)
    {
    /
    模式设置:16位定时器模式 */
    TMOD &= 0xF0; // 清空T0控制位
    TMOD |= 0x01; // 设置为模式1(16位定时器)

    /* 定时初值计算:1ms定时 */
    // 定时计数值 = 65536 - (12000000/12)*0.001
    // = 65536 - 1000 = 64536 = 0xFC18
    TL0 = 0x18; // 低8位:0x18
    TH0 = 0xFC; // 高8位:0xFC

    /* 中断与启动设置 */
    TF0 = 0; // 清除溢出标志
    TR0 = 1; // 启动定时器0
    ET0 = 1; // 使能定时器0中断
    EA = 1; // 开启总中断
    }

  1. 中断服务函数实现
    c
    /* 全局扫描变量 */
    unsigned char Seg_Pos = 0; // 当前扫描位置(0-5)
    unsigned char Seg_Buf[6] = {0}; // 显示缓冲区

/**

  • @brief 定时器0中断服务函数

  • @note interrupt 1对应定时器0中断号
    /
    void Timer0_ISR() interrupt 1
    {
    /
    重新装载定时初值(模式1需手动重装) */
    TL0 = 0x18;
    TH0 = 0xFC;

    /* 数码管动态扫描 */
    Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos]); // 显示当前位

    /* 更新扫描位置 */
    Seg_Pos++;
    if(Seg_Pos >= 6)
    {
    Seg_Pos = 0; // 循环扫描0-5位
    }
    }

  1. 完整应用示例:可调计时器
    c
    /* 全局变量定义 */
    unsigned char Seg_Buf[6] = {0}; // 数码管显示缓冲区
    unsigned int time_counter = 0; // 计时变量(单位:ms)
    unsigned int display_time = 500; // 显示时间(单位:ms)
    bit system_enable = 0; // 系统使能标志

/**

  • @brief 主函数
    /
    void main()
    {
    /
    初始化系统 */
    Timer0_Init(); // 初始化定时器
    system_enable = 0; // 初始为暂停状态

    /* 主循环 /
    while(1)
    {
    /
    按键检测与处理 */
    Key_Val = Key_Read();
    Key_Down = Key_Val & (Key_Val ^ Key_Old);
    Key_Old = Key_Val;

     /* 按键功能分配 */
     switch(Key_Down)
     {
         case 1:  // 按键1:启动计时
             system_enable = 1;
             break;
             
         case 2:  // 按键2:暂停计时
             system_enable = 0;
             break;
             
         case 3:  // 按键3:时间+100ms
             if(display_time < 9900)  // 限制最大9999
                 display_time += 100;
             break;
             
         case 4:  // 按键4:时间-100ms
             if(display_time > 100)   // 限制最小0
                 display_time -= 100;
             break;
             
         default:
             break;
     }
     
     /* 计时逻辑 */
     if(system_enable && time_counter < display_time)
     {
         time_counter++;  // 每毫秒增加
     }
     else if(time_counter >= display_time)
     {
         system_enable = 0;  // 计时结束自动停止
     }
     
     /* 更新显示缓冲区 */
     // 显示格式:HHHH.HH(实际只显示前3位)
     Seg_Buf[0] = display_time / 1000;          // 千位
     Seg_Buf[1] = (display_time % 1000) / 100; // 百位
     Seg_Buf[2] = (display_time % 100) / 10;   // 十位
     Seg_Buf[3] = 10;  // 空(第4位)
     Seg_Buf[4] = 10;  // 空(第5位)
     Seg_Buf[5] = 10;  // 空(第6位)
    

    }
    }
    三、核心概念总结与技巧

  1. 数码管显示三大要点
    要点 说明 关键操作
    消隐 消除切换时的残影 显示前先送全灭段码
    位选优先 先选位置再送数据 位选→段选的顺序
    动态扫描 多位数码管分时显示 1-5ms切换一位
  2. 定时器工作原理类比

中断机制类比:

ET0=1:设置闹钟(允许定时器中断)

EA=1:打开闹钟开关(总中断使能)

interrupt 1:闹钟响时执行的函数

中断返回:执行完后回到原位置继续