矩阵按键 + 数码管倒计时系统 — 使用手册
芯片: STC8A8K64D4 @ 24MHz | 函数库: ESD_STC8 V3.0
一、全局变量
| 变量名 | 类型 | 说明 |
|---|---|---|
gear |
uchar | 挡位,范围 1~9,默认 1 |
time_set |
uchar | 倒计时预设秒数,范围 1~99,默认 30 |
time_disp |
uchar | 数码管当前显示的值(设置态 = time_set,运行态 = time_left) |
running |
uchar | 系统状态:0=设置模式,1=倒计时运行中 |
time_left |
uchar | 倒计时剩余秒数,运行态时逐秒减 1 |
ms_cnt |
uint | 毫秒累加器,每 1ms 加 1,满 1000 进 1 秒 |
变量关系图:
设置态 (running=0) 运行态 (running=1)
┌─────────────────┐ ┌─────────────────┐
│ time_set = 30 │──K5启动→ │ time_left = 30 │
│ │ │ ↓ 每秒减1 │
│ time_disp = 30 │ │ time_left = 29 │
│ │ │ ↓ │
│ 按键可调 │ │ time_disp = 29 │
│ gear 1~9 │ │ ↓ │
│ time_set 1~99 │ │ ...到0 │
│ │←─到0自动回─│ running变回0 │
└─────────────────┘ └─────────────────┘
二、自定义函数
函数1:Scan_Keys()
原型: uchar Scan_Keys(void)
参数: 无
返回: 0 = 无按键
1 = K1 按下(挡位+)
2 = K2 按下(挡位-)
3 = K3 按下(时间+)
4 = K4 按下(时间-)
5 = K5 按下(启动)
原理:矩阵扫描法,2 行 × 3 列,分两轮检测
- 第①轮:P16=0(选中行1)→ 读 P14 查 K1,读 P15 查 K2
- 第②轮:P17=0(选中行2)→ 读 P14 查 K3,读 P15 查 K4,读 P35 查 K5
每次只激活一行,被激活行的"强 0"才能把列线拉低;另一行输出"强 1",列线不受影响。
坐标系理解:
- Y 轴(行扫描):P16 / P17 轮流输出强 0
- X 轴(列检测):P14 / P15 / P35 默认弱 1,被拉低 = 按键按下
第1步:Y=P16=0 → 查 X 轴 → 找到第1行的按键(K1 或 K2)
第2步:Y=P17=0 → 查 X 轴 → 找到第2行的按键(K3 或 K4 或 K5)
注意:如果同时按下多个键,函数只返回后检测到的那一个。
函数2:TM0_isr()
原型: void TM0_isr(void)
调用者: isr.c 中的 TM0_I() interrupt 1(每 1ms 自动调用)
参数: 无
返回: 无
功能 A:数码管动态扫描(333Hz 无闪烁刷新)
流程:
- 消隐:关闭所有位选(P36=P37=P33=0),关闭所有段选(P2.0~P2.7 全写 1 → 全灭)
- 轮流亮一位:
pos=0:送 gear 的段码到 P2 → 开 P36(第 1 位亮)pos=1:送 time_disp 十位的段码 → 开 P37(第 2 位亮)pos=2:送 time_disp 个位的段码 → 开 P33(第 3 位亮)
- pos 循环:0 → 1 → 2 → 0 ...
时间轴:
t=0ms 消隐 → 亮第1位(挡位) pos=1
t=1ms 消隐 → 亮第2位(秒十位) pos=2
t=2ms 消隐 → 亮第3位(秒个位) pos=0
t=3ms 循环 ...
每位亮 1ms,3 位一轮 = 3ms,刷新率 333Hz
功能 B:倒计时(仅 running==1 时执行)
ms_cnt每 1ms 加 1ms_cnt满 1000 → 经过了 1 秒time_left减 1time_left到 0 → running 置 0,LED 灭(Out_IO(10,1))
三、用到的库函数(ESD_STC8 V3.0)
| 库函数 | 说明 |
|---|---|
GPIO_init_allpin(mode) |
初始化所有 IO,mode: 0=准双向 1=推挽 2=高阻 3=开漏 |
GPIO_init_8pin(port, mode) |
初始化整组 IO,port: 0=P0 2=P2... |
GPIO_init_pin(pin, mode) |
初始化单个 IO,pin: 36=P3.6 14=P1.4... |
Out_IO(pin, val) |
设置 IO 输出,val: 0=低电平 1=高电平 |
Get_IO(pin) |
读取 IO 输入,返回 bit(0 或 1) |
PIT_init_ms(n, t) |
启动定时器中断,n: 0 |
Delay_X_mS(x) |
毫秒延时,x: 1~1000 |
Delay_X_uS(x) |
微秒延时,x: 1~1000 |
引脚编号规则:两位数字,十位 = 端口号,个位 = 引脚号
14= P1^4 /16= P1^6 /20= P2^0 /27= P2^733= P3^3 /35= P3^5 /36= P3^6 /37= P3^7
四、段码表
P2 口位:.7=DP .6=G .5=F .4=E .3=D .2=C .1=B .0=A
共阳规则:0 = 亮,1 = 灭
| 数字 | 十六进制 | 亮的段 |
|---|---|---|
| 0 | 0xC0 |
A B C D E F |
| 1 | 0xF9 |
B C |
| 2 | 0xA4 |
A B D E G |
| 3 | 0xB0 |
A B C D G |
| 4 | 0x99 |
B C F G |
| 5 | 0x92 |
A C D F G |
| 6 | 0x82 |
A C D E F G |
| 7 | 0xF8 |
A B C |
| 8 | 0x80 |
A B C D E F G(全部) |
| 9 | 0x90 |
A B C D F G |
五、硬件接线速查表
按键矩阵(2 行 × 3 列)
| P14(列1) | P15(列2) | P35(列3) | |
|---|---|---|---|
| P16(行1) | K1 挡位+ | K2 挡位- | (空) |
| P17(行2) | K3 时间+ | K4 时间- | K5 启动 |
省线技巧:K1、K2 朝向 P16 那端焊在一起,共用一根飞线到 P16;K3、K4、K5 朝向 P17 那端焊在一起,共用一根飞线到 P17。
电阻:不需要外部电阻(代码用内部上拉)。
数码管 5641BS(共阳极,12 脚)
段码(各串 100Ω 电阻):
| P2 脚 | 数码管脚 | 段名 |
|---|---|---|
| P2.0 | 脚11 | A |
| P2.1 | 脚7 | B |
| P2.2 | 脚4 | C |
| P2.3 | 脚2 | D |
| P2.4 | 脚1 | E |
| P2.5 | 脚10 | F |
| P2.6 | 脚5 | G |
| P2.7 | 脚3 | DP |
位选:
| 单片机脚 | 数码管脚 | 说明 |
|---|---|---|
| P3.6 | 脚12 | 第1位 = 挡位显示 |
| P3.7 | 脚9 | 第2位 = 秒十位 |
| P3.3 | 脚8 | 第3位 = 秒个位 |
| — | 脚6 | 悬空(第4位不用) |
LED 指示灯
VCC(5V) → [1KΩ电阻] → LED正极(长脚) → LED负极(短脚) → P1.0
- P1.0 = 高电平(5V):LED 两端无压差 → 灭
- P1.0 = 低电平(0V):电流从 VCC 流向 P1.0 → 亮
六、程序执行流程
主循环(后台)
main()
│
├→ GPIO 配置:全部准双向 → P2/位选/行扫/LED 改推挽
├→ LED 初始灭:Out_IO(10,1)
├→ 定时器启动:PIT_init_ms(0,1) 每 1ms 进中断
│
└→ while(1):
│
├→ ① time_disp = running ? time_left : time_set
│ 更新数码管数据源
│
├→ ② key = Scan_Keys() 扫描按键
│ if (有键) {
│ Delay_15ms 防抖
│ 再次确认
│ if (!locked) {
│ locked = 1
│ if (!running) { // 设置态才处理
│ key=1 → gear++
│ key=2 → gear--
│ key=3 → time_set++
│ key=4 → time_set--
│ key=5 → 启动倒计时
│ }
│ }
│ } else {
│ locked = 0 // 松手解锁
│ }
│
└──→ 循环
定时器中断(前台,每 1ms)
TM0_isr()
│
├→ 消隐:关位选 + 关段选
│
├→ 轮流亮位:
│ pos=0: Seg[gear] → P2口 → P36=1
│ pos=1: Seg[time_disp十位] → P2口 → P37=1
│ pos=2: Seg[time_disp个位] → P2口 → P33=1
│
└→ 倒计时(running==1 时):
ms_cnt++
ms_cnt 到 1000 → time_left--
time_left 到 0 → running=0, LED灭
主循环和中断通过全局变量
gear/time_disp/running/time_left通信,彼此独立运行。
七、按键防抖机制
问题:机械按键按下/松开的瞬间,金属弹片反复弹跳,产生一串极短的通→断→通→断脉冲(持续约 5~15ms)。
时间轴:
按下瞬间 ├── 抖动区(~15ms)──┤ 稳定按住
电平: 1 │ 1 0 1 0 1 0 1 0 1 │ 0 0 0 0 0
↑ ↑
第1次读数 第2次读数
(可能是1) (稳定是0)
两次一致 → 确认有效 ✓
防长按(locked 标志位)
| 循环 | locked | 动作 |
|---|---|---|
| 第1次 | 0 | 执行功能 → locked=1 |
| 第2次 | 1 | 跳过(按住不放) |
| 第N次 | 1 | 跳过 |
| 松手 | — | key=0 → locked=0 |
| 再按 | 0 | 可以再次执行 |
效果:按一次只触发一次,按住不放不会连续触发。
八、常见问题排查
Q:数码管不亮?
- 检查共阳脚是否接对(脚 12、9、8)
- 段码电阻是否每根都接了
- 万用表测 P36/P37/P33 有无轮流出现高电平
- 确认 5641BS 是共阳型(不是共阴)
Q:数码管显示乱码?
段码脚位和代码不匹配。用 5V → 100Ω 逐个碰段脚,确认 A/B/C/D/E/F/G/DP 对应哪个脚,调整 P2 接线。
Q:按一下数字跳了好几次?
按键接触不良或抖动没消干净。
- 杜邦线插紧
Delay_X_mS(15)可以改大到 20~30
Q:按了按键没反应?
- 检查按键矩阵接线是否正确(特别注意共用的行线)
- 万用表测按下去是否导通
- 检查
running是否为 0(运行态按键被屏蔽)
Q:倒计时不准?
确认 config.h 中的 MAIN_Fosc 和 ISP 下载软件中设置的主频一致。
Q:编译有 MULTIPLE CALL 警告?
正常,不影响运行。Out_IO 在主循环和 ISR 中都被调用,Keil 提醒可能重入,但 Out_IO 内部无静态变量,实际不会出错。
Q:LED 不亮?
- 检查正负极是否接反(长脚 → 电阻 → VCC,短脚 → P1.0)
- 检查 running=1 时 P1.0 是否为低电平