我想做一台「看得见摸得着」的计算机:不是跑在模拟器里,而是能接上电源、点亮一排绿莹莹的 VFD 荧光管、按键盘能算数、屏幕上数字稳稳地跳。用的是一块 8051 内核的开发板(CH6800 / HC6800 系列),显示部分是 VFD 动态扫描。

这活儿看着简单,真做起来全是细节。下面是完整的记录。

这台 8 位计算机是什么

核心是一颗 8051 兼容内核,跑在 12MHz 晶振上,机器周期是经典的 1µs(12 时钟/机器周期)。它的任务很朴素:

之所以叫「8 位计算机」而不是「单片机小实验」,是因为它有一套完整的输入 → 运算 → 显示闭环,能当一个独立设备用,而不是插在电脑上跑例程。

VFD 荧光管:好看但不好伺候

VFD(Vacuum Fluorescent Display,真空荧光显示)原理是灯丝加热发射电子、栅极和阳极加速轰击荧光粉发光。它比 LED 数码管漂亮太多,但驱动上有几个坑:

新手最容易犯的错:把 VFD 当 LED 数码管直接灌电流。VFD 的阳极是「吸引电子」的,不是「供电」的,驱动逻辑完全不同。

动态扫描与 100Hz 刷新基线

动态扫描的核心思想:同一时刻只点亮一位,靠人眼视觉暂留把多位「拼」成同时亮。刷新率必须够高,否则会看到明显的位间闪烁。经验阈值是整屏刷新 ≥ 100Hz。

我的做法是用定时器 T0 产生固定时隙中断,每进一次中断切换一位:

参数取值说明
单时隙 T0_COUNT830 µs每位点亮的时间
位数(时隙数)1212 位数码位
帧周期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),否则上一位的残影会串到这一位,形成「鬼影」。

显示闪烁的排查思路

如果屏幕还是闪,按这个顺序查,基本能定位:

  1. 中断里干了太多活——ISR 里只做「切位 + 送段码」,运算和按键扫描全部放主循环,用标志位传递;
  2. 段码表放在慢速存储——把段码表放 code 段(ROM),别放 RAM 里反复算;
  3. 时隙太长——如果 830µs 下亮度够但偶尔闪,先量一下 ISR 实际执行时间,超过时隙的 30% 就要优化;
  4. 电源纹波——灯丝和逻辑共用电源时,大电流切换会把逻辑电压拉低,加去耦电容。

Keil 限制版代码超限的真实解法

这是最现实的一关。Keil 的评估版 / 限制版对代码大小有硬上限:0800H(2KB)。我的工程一路加到 0A76H(约 2678 字节),直接超限 130%,编译报错、烧不进去。

没有完整 PK51 授权的前提下,我是这么挤出空间的:

如果优化完还是超限,换用 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 就能看到这台机器从散件到点亮的过程。