我想做一台「看得见摸得着」的计算机:不是跑在模拟器里,而是能接上电源、点亮一排绿莹莹的 VFD 荧光管、按键盘能算数、屏幕上数字稳稳地跳。用的是一块 8051 内核的开发板(CH6800 / HC6800 系列),显示部分是 VFD 动态扫描。
这活儿看着简单,真做起来全是细节。下面是完整的记录。
这台 8 位计算机是什么
核心是一颗 8051 兼容内核,跑在 12MHz 晶振上,机器周期是经典的 1µs(12 时钟/机器周期)。它的任务很朴素:
- 读矩阵键盘输入;
- 做加减乘除运算;
- 把结果通过动态扫描推到 VFD 显示;
- 响应定时器中断,维持刷新节拍。
之所以叫「8 位计算机」而不是「单片机小实验」,是因为它有一套完整的输入 → 运算 → 显示闭环,能当一个独立设备用,而不是插在电脑上跑例程。
VFD 荧光管:好看但不好伺候
VFD(Vacuum Fluorescent Display,真空荧光显示)原理是灯丝加热发射电子、栅极和阳极加速轰击荧光粉发光。它比 LED 数码管漂亮太多,但驱动上有几个坑:
- 需要灯丝电压和高压阳极,供电设计不对会亮度不均甚至不亮;
- 不能用普通 GPIO 直驱,一般要经过驱动芯片(如 74HC595 + 高压驱动);
- 必须动态扫描,静态点亮会烧阳极、功耗也爆炸。
新手最容易犯的错:把 VFD 当 LED 数码管直接灌电流。VFD 的阳极是「吸引电子」的,不是「供电」的,驱动逻辑完全不同。
动态扫描与 100Hz 刷新基线
动态扫描的核心思想:同一时刻只点亮一位,靠人眼视觉暂留把多位「拼」成同时亮。刷新率必须够高,否则会看到明显的位间闪烁。经验阈值是整屏刷新 ≥ 100Hz。
我的做法是用定时器 T0 产生固定时隙中断,每进一次中断切换一位:
| 参数 | 取值 | 说明 |
|---|---|---|
| 单时隙 T0_COUNT | 830 µs | 每位点亮的时间 |
| 位数(时隙数) | 12 | 12 位数码位 |
| 帧周期 | 830µs × 12 ≈ 9.96 ms | 扫完一整屏 |
| 刷新率 | 1 / 9.96ms ≈ 100.4 Hz | 稳定不闪 |
这套参数是我实测下来最稳的基线:830µs 的单时隙既能保证亮度,又给中断服务程序留出足够余量去做按键扫描和运算,不至于因为 ISR 太长把节拍拉垮。
// 8051 定时器 0 中断:每 830µs 切一位
void T0_ISR(void) interrupt 1 {
TH0 = T0_RELOAD_H; // 830us 重装值
TL0 = T0_RELOAD_L;
P1 = 0x00; // 先关段,防止鬼影
digit = (digit + 1) % 12;
out_segment(seg_buf[digit]); // 送段码
out_digit(digit); // 开对应位
}
关键细节:切换位之前先把段码清零(
P1 = 0x00),否则上一位的残影会串到这一位,形成「鬼影」。
显示闪烁的排查思路
如果屏幕还是闪,按这个顺序查,基本能定位:
- 中断里干了太多活——ISR 里只做「切位 + 送段码」,运算和按键扫描全部放主循环,用标志位传递;
- 段码表放在慢速存储——把段码表放
code段(ROM),别放 RAM 里反复算; - 时隙太长——如果 830µs 下亮度够但偶尔闪,先量一下 ISR 实际执行时间,超过时隙的 30% 就要优化;
- 电源纹波——灯丝和逻辑共用电源时,大电流切换会把逻辑电压拉低,加去耦电容。
Keil 限制版代码超限的真实解法
这是最现实的一关。Keil 的评估版 / 限制版对代码大小有硬上限:0800H(2KB)。我的工程一路加到 0A76H(约 2678 字节),直接超限 130%,编译报错、烧不进去。
没有完整 PK51 授权的前提下,我是这么挤出空间的:
- 开优化等级:从
-O0提到-O2/-O3,仅这一步通常能省 10~25%; - 去掉浮点:8 位机上用整数 / 定点运算,浮点库是体积杀手;
- 精简 printf / sprintf:这些格式化函数会拖进巨大的库代码,改成自己写的小函数;
- 段码表压缩:能算出来的段码不要写成大数组,用位运算生成;
- 函数复用:把重复的显示 / 读取逻辑抽成一个函数,别为每位写一套。
如果优化完还是超限,换用 SDCC(开源 8051 编译器)是彻底解法——它没有 2KB 限制,代价是要改一些 Keil 专有语法(如
interrupt关键字写法、code/xdata存储区声明)。
这一关我后来单独写了一篇完整的:Keil C51 评估版 2KB 代码限制怎么破,里面有 0800H 这条报错到底管什么、怎么看 .M51 内存映射找出体积黑洞、以及 Keil 和 SDCC 的语法对照表。
我目前的稳定基线是:T0_COUNT=830µs、12 时隙、9.96ms 帧周期、100Hz。在这套节拍上,显示终于稳了,剩下的就是继续抠代码体积。
硬件项目最迷人的地方在于:软件的错误你能 rerun,硬件的错误你要拿烙铁去改。每一根飞线、每一个去耦电容,都是真金白银的经验。
制作过程的视频我发在 B站 / 抖音,搜 codevtool 就能看到这台机器从散件到点亮的过程。