单片机数码管显示与定时器应用笔记
一、SEG数码管原理图解析
- 数码管基本工作原理
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口(输出位码) |
- 基础驱动代码实现
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. 更新历史状态
// 说明:这四行代码必须按顺序执行,且需在循环中不断调用
}
二、定时器中断应用详解
- 定时器的必要性
问题分析
静态显示问题:直接调用显示函数只能固定显示单一内容
动态显示需求:多位数码管需要显示不同数字
视觉暂留原理:人眼有约0.1秒的视觉暂留效应
解决方案
使用定时器中断实现动态扫描:
每隔1-5ms切换显示一位
6位数码管扫描周期为6-30ms(> 50Hz无闪烁)
利用人眼视觉暂留,实现"同时显示"效果
- 定时器配置流程(使用STC-ISP工具)
步骤 操作 参数设置
1 打开STC-ISP软件 选择对应单片机型号
2 进入定时器计算器 工具 → 定时器计算器
3 设置系统频率 12.000MHz
4 设置定时周期 1.000毫秒
5 选择定时器模式 16位自动重装
6 生成代码 复制到工程中 - 定时器初始化代码
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; // 开启总中断
}
- 中断服务函数实现
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位
}
}
- 完整应用示例:可调计时器
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-5ms切换一位 - 定时器工作原理类比
中断机制类比:
ET0=1:设置闹钟(允许定时器中断)
EA=1:打开闹钟开关(总中断使能)
interrupt 1:闹钟响时执行的函数
中断返回:执行完后回到原位置继续