第三周单片机

51单片机数码管时钟项目笔记

项目概述

基于51单片机(STC89C52)的数码管显示时钟,具备时间显示、时钟设置、闹钟设置等功能。

头文件声明

#include <REGX52.H>
#include "Key.h"
#include "Seg.h"

全局变量定义

时间相关变量

unsigned char Clock_Disp[3] = {23,59,55};     // 时钟显示数据 [时,分,秒]
unsigned char Clock_Set[3];                   // 时钟设置缓存
unsigned char Clock_Set_Index;                // 时钟设置索引 0-时,1-分,2-秒
unsigned int Timer_1000ms;                    // 1000ms计时器

闹钟相关变量

unsigned char Alarm[] = {0,0,0};              // 闹钟时间 [时,分,秒]
unsigned char Alarm_Set[3];                   // 闹钟设置缓存
bit Alarm_Flag = 1;                          // 闹钟开关标志(1-开,0-关)
bit Alarm_Enable_Flag;                       // 闹钟使能标志

显示相关变量

unsigned char Seg_Buf[6] = {10,10,10,10,10,10};  // 数码管显示缓存
unsigned char Seg_Point[6] = {0,1,0,1,0,1};     // 小数点控制
unsigned char Seg_Disp_Mode;                     // 显示模式 0-时钟,1-时钟设置,2-闹钟
unsigned char Seg_Pos;                          // 数码管扫描位置
bit Seg_Flag;                                  // 闪烁标志
unsigned char Led;                             // LED显示数据

按键相关变量

unsigned char Key_Slow_Down;                   // 按键减速(10ms)
unsigned int Seg_Slow_Down;                    // 数码管减速(500ms)
unsigned char Key_Val, Key_Down, Key_Old;      // 按键扫描变量

核心功能函数

1. 按键处理函数

void Key_Proc()
{
    if(Key_Slow_Down) return;        // 10ms减速控制
    Key_Slow_Down = 1;
    
    // 读取按键值并检测按键按下
    Key_Val = Key_read();
    Key_Down = Key_Val & (Key_Val ^ Key_Old);
    Key_Old = Key_Val;
    
    if(Key_Down != 0) {
        Alarm_Enable_Flag = 0;       // 任何按键按下都关闭闹钟
    }
    
    switch(Key_Down) {
        case 1:  // 进入时钟设置模式
            Clock_Set_Index = 0;
            Clock_Set[0] = Clock_Disp[0];
            Clock_Set[1] = Clock_Disp[1];
            Clock_Set[2] = Clock_Disp[2];
            Seg_Disp_Mode = 1;
            break;
            
        case 2:  // 进入闹钟设置模式
            Clock_Set_Index = 0;
            Alarm_Set[0] = Alarm[0];
            Alarm_Set[1] = Alarm[1];
            Alarm_Set[2] = Alarm[2];
            Seg_Disp_Mode = 2;
            break;
            
        case 3:  // 切换设置项(时→分→秒)
            Clock_Set_Index++;
            if(Clock_Set_Index == 3) Clock_Set_Index = 0;
            break;
            
        case 4:  // 闹钟开关切换
            Alarm_Flag ^= 1;
            break;
            
        case 5:  // 加操作
            if(Seg_Disp_Mode == 1) {  // 时钟设置
                Clock_Set[Clock_Set_Index]++;
                if(Clock_Set[Clock_Set_Index] == (Clock_Set_Index==0?24:60))
                    Clock_Set[Clock_Set_Index] = 0;
            }
            if(Seg_Disp_Mode == 2) {  // 闹钟设置
                Alarm_Set[Clock_Set_Index]++;
                if(Alarm_Set[Clock_Set_Index] == (Clock_Set_Index==0?24:60))
                    Alarm_Set[Clock_Set_Index] = 0;
            }
            break;
            
        case 6:  // 减操作
            if(Seg_Disp_Mode == 1) {
                Clock_Set[Clock_Set_Index]--;
                if(Clock_Set[Clock_Set_Index] == 255)
                    Clock_Set[Clock_Set_Index] = Clock_Set_Index==0?23:59;
            }
            if(Seg_Disp_Mode == 2) {
                Alarm_Set[Clock_Set_Index]--;
                if(Alarm_Set[Clock_Set_Index] == 255)
                    Alarm_Set[Clock_Set_Index] = Clock_Set_Index==0?23:59;
            }
            break;
            
        case 7:  // 保存并返回
            if(Seg_Disp_Mode == 1) {
                Clock_Disp[0] = Clock_Set[0];
                Clock_Disp[1] = Clock_Set[1];
                Clock_Disp[2] = Clock_Set[2];
            }
            if(Seg_Disp_Mode == 2) {
                Alarm[0] = Alarm_Set[0];
                Alarm[1] = Alarm_Set[1];
                Alarm[2] = Alarm_Set[2];
            }
            Seg_Disp_Mode = 0;
            break;
            
        case 8:  // 取消操作
            Seg_Disp_Mode = 0;
            break;
    }
}

按键功能说明:

  • 键1:进入时钟设置
  • 键2:进入闹钟设置
  • 键3:切换设置项
  • 键4:闹钟开关
  • 键5:数值加
  • 键6:数值减
  • 键7:保存返回
  • 键8:取消返回

2. 数码管信息处理

void Seg_Proc()
{
    unsigned char i;
    if(Seg_Slow_Down) return;        // 500ms减速控制
    Seg_Slow_Down = 1;
    
    switch(Seg_Disp_Mode) {
        case 0:  // 正常时钟显示
            for(i=0; i<3; i++) {
                Seg_Buf[0+i*2] = Clock_Disp[i] / 10 % 10;
                Seg_Buf[1+i*2] = Clock_Disp[i] % 10;
            }
            break;
            
        case 1:  // 时钟设置界面(带闪烁效果)
            Seg_Buf[0] = Clock_Set[0] / 10 % 10;
            Seg_Buf[1] = Clock_Set[0] % 10;
            Seg_Buf[2] = Clock_Set[1] / 10 % 10;
            Seg_Buf[3] = Clock_Set[1] % 10;
            Seg_Buf[4] = Clock_Set[2] / 10 % 10;
            Seg_Buf[5] = Clock_Set[2] % 10;
            // 当前设置项闪烁显示
            Seg_Buf[0+Clock_Set_Index*2] = Seg_Flag ? Clock_Set[Clock_Set_Index] / 10 % 10 : 10;
            Seg_Buf[1+Clock_Set_Index*2] = Seg_Flag ? Clock_Set[Clock_Set_Index] % 10 : 10;
            break;
            
        case 2:  // 闹钟设置界面
            Seg_Buf[0] = Alarm_Set[0] / 10 % 10;
            Seg_Buf[1] = Alarm_Set[0] % 10;
            Seg_Buf[2] = Alarm_Set[1] / 10 % 10;
            Seg_Buf[3] = Alarm_Set[1] % 10;
            Seg_Buf[4] = Alarm_Set[2] / 10 % 10;
            Seg_Buf[5] = Alarm_Set[2] % 10;
            Seg_Buf[0+Clock_Set_Index*2] = Seg_Flag ? Alarm_Set[Clock_Set_Index] / 10 % 10 : 10;
            Seg_Buf[1+Clock_Set_Index*2] = Seg_Flag ? Alarm_Set[Clock_Set_Index] % 10 : 10;
            break;
    }
}

显示模式说明:

  • 模式0:正常显示时间,格式为 HH:MM:SS
  • 模式1:时钟设置,当前设置项会闪烁
  • 模式2:闹钟设置,当前设置项会闪烁

3. LED显示处理

void Led_Proc()
{
    if(Alarm_Flag == 1) {  // 闹钟功能开启
        // 检测是否到达闹钟时间
        if(Clock_Disp[0] == Alarm[0] && Clock_Disp[1] == Alarm[1] && Clock_Disp[2] == Alarm[2])
            Alarm_Enable_Flag = 1;
            
        if(Alarm_Enable_Flag == 1) {
            P2_3 = 0;      // 使能LED显示
            P1 = Led;      // 输出LED数据
        } else {
            P2_3 = 1;      // 关闭LED显示
            P1 = 0xff;
        }
    } else {
        P2_3 = 1;          // 闹钟关闭时关闭LED
        P1 = 0xff;
    }
}

4. 定时器初始化

void Timer0_Init(void)        // 1毫秒@12.000MHz
{
    TMOD &= 0xF0;            // 设置定时器模式
    TMOD |= 0x01;            // 定时器0工作在模式1
    TL0 = 0x18;              // 设置定时初始值
    TH0 = 0xFC;              // 设置定时初始值
    TF0 = 0;                 // 清除TF0标志
    TR0 = 1;                 // 定时器0开始计时
    ET0 = 1;                 // 开启定时器0中断
    EA = 1;                  // 开启总中断
}

定时器配置:

  • 工作模式:模式1(16位定时器)
  • 定时时长:1ms
  • 晶振频率:12MHz

5. 定时器中断服务函数

void Timer0Sever() interrupt 1
{
    TL0 = 0x18;              // 重装定时器初值
    TH0 = 0xFC;
    
    // 减速控制
    if(++Key_Slow_Down == 10) Key_Slow_Down = 0;    // 10ms
    if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;   // 500ms
    
    // 数码管扫描显示
    if(++Seg_Pos == 6) Seg_Pos = 0;
    Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], Seg_Point[Seg_Pos]);
    
    // 时钟计时
    Timer_1000ms++;
    if(Timer_1000ms == 1000) {
        Timer_1000ms = 0;
        Clock_Disp[2]++;     // 秒加1
        
        // 时间进位处理
        if(Clock_Disp[2] == 60) {
            Clock_Disp[2] = 0;
            Clock_Disp[1]++;
            if(Clock_Disp[1] == 60) {
                Clock_Disp[1] = 0;
                Clock_Disp[0]++;
                if(Clock_Disp[0] == 24)
                    Clock_Disp[0] = 0;
            }
        }
    }
    
    // 闪烁控制和LED效果
    Timer_500ms++;
    if(Timer_500ms == 500) {
        Timer_500ms = 0;
        Seg_Flag ^= 1;       // 切换闪烁状态
        
        // LED显示效果(上午/下午不同效果)
        if(Clock_Disp[0] >= 12) {
            Led ^= 0xf0;     // 下午:高4位闪烁
        } else {
            Led ^= 0x0f;     // 上午:低4位闪烁
        }
    }
}

中断功能:

  • 按键扫描减速(10ms)
  • 数码管刷新减速(500ms)
  • 数码管动态扫描
  • 时钟计时逻辑
  • 设置项闪烁控制
  • LED显示效果控制

6. 主函数

void main()
{
    Timer0_Init();           // 初始化定时器
    while(1) {
        Key_Proc();          // 按键处理
        Seg_Proc();          // 数码管数据处理
        Led_Proc();          // LED显示处理
    }
}

外设驱动代码

数码管显示驱动 (Seg.h)

// 数码管段码表(0-9,灭)
unsigned char code Seg_Dula[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00};
// 数码管位选表(6位数码管)
unsigned char code Seg_Wela[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf};

void Seg_Disp(unsigned char wela, dula, point)
{
    P0 = 0x00;              // 消影处理
    P2_6 = 1; P2_6 = 0;     // 锁存器控制
    
    P0 = Seg_Wela[wela];    // 位选
    P2_7 = 1; P2_7 = 0;
    
    if(point == 1)
        P0 = Seg_Dula[dula] | 0x80;  // 带小数点
    else
        P0 = Seg_Dula[dula];         // 无小数点
        
    P2_6 = 1; P2_6 = 0;     // 输出显示
}

数码管控制引脚:

  • P2_6:段码锁存
  • P2_7:位选锁存
  • P0:数据端口

按键扫描驱动 (Key.h)

unsigned char Key_read()
{
    unsigned char temp = 0;
    
    // 矩阵键盘扫描
    P3_0 = 0; P3_1 = 1; P3_2 = 1; P3_3 = 1;
    if(P3_4 == 0) temp = 1; if(P3_5 == 0) temp = 2;
    if(P3_6 == 0) temp = 3; if(P3_7 == 0) temp = 4;
    
    P3_0 = 1; P3_1 = 0; P3_2 = 1; P3_3 = 1;
    if(P3_4 == 0) temp = 5; if(P3_5 == 0) temp = 6;
    if(P3_6 == 0) temp = 7; if(P3_7 == 0) temp = 8;
    
    P3_0 = 1; P3_1 = 1; P3_2 = 0; P3_3 = 1;
    if(P3_4 == 0) temp = 9; if(P3_5 == 0) temp = 10;
    if(P3_6 == 0) temp = 11; if(P3_7 == 0) temp = 12;
    
    P3_0 = 1; P3_1 = 1; P3_2 = 1; P3_3 = 0;
    if(P3_4 == 0) temp = 13; if(P3_5 == 0) temp = 14;
    if(P3_6 == 0) temp = 15; if(P3_7 == 0) temp = 16;
    
    return temp;
}

按键矩阵:

  • 4×4矩阵键盘,使用P3端口
  • 返回键值1-16对应不同功能
1 个赞