1. 项目概述:用现代微控制器复活经典操作系统
最近在复古计算和嵌入式开发的圈子里,有个项目挺有意思的,它把两个看似不搭界的东西给捏合到了一起:一个是上世纪七八十年代统治微型计算机的CP/M操作系统,另一个是当下最火的微控制器之一——树莓派RP2040。这个项目叫“Tiny RunCPM”,而它的硬件载体,则是一块小巧的Adafruit RP2040 Adalogger开发板。简单来说,这就是在一颗售价几美元、性能强悍的现代MCU上,完整地模拟出了一台能运行老式CP/M软件的“古董电脑”。
这听起来可能有点“杀鸡用牛刀”,毕竟RP2040双核240MHz的主频,去跑一个几十年前为8位Z80或8080 CPU设计的系统,性能绰绰有余。但它的魅力恰恰在于这种“反差感”和实现的精巧。它不是为了解决什么实际的现代计算问题,而是为爱好者、教育者和怀旧者提供了一个绝佳的实验平台。你可以用它来学习计算机架构的演变历史,亲手体验命令行时代的人机交互,或者仅仅是为了享受那种在巴掌大的硬件上,让古老的WordStar、dBase II或Turbo Pascal重新“活”过来的乐趣。
这个项目的核心是“RunCPM”软件,它是一个用C语言编写的、高度可移植的CP/M 2.2模拟器。而Adafruit RP2040 Adalogger板子,则因其内置的MicroSD卡槽和简洁的设计,成为了运行它的理想硬件。你不需要复杂的飞线或外设,只需要这块板子、一张存储了CP/M系统文件和应用程序的SD卡,以及一个USB转串口工具(或者利用板载的USB CDC功能),就能立刻拥有一台可编程的CP/M计算机。对于玩腻了Arduino基础项目,想深入探索系统软件、模拟器原理,或者对计算机历史感兴趣的朋友来说,这绝对是一个值得投入时间把玩的项目。
2. 核心组件与工具链解析
2.1 硬件基石:Adafruit RP2040 Adalogger深度剖析
选择这块板子作为载体,绝非偶然。Adafruit RP2040 Adalogger(产品ID:P4692)在设计上就充分考虑了对“数据记录”和“独立运行”的支持,这些特性完美契合了运行一个完整操作系统的需求。
首先,RP2040微控制器是项目的“大脑”。它的双核Cortex-M0+架构,主频最高可达133MHz(通常超频到250MHz运行也稳定),拥有264KB的SRAM。对于RunCPM模拟器来说,双核并非必需,但充裕的RAM至关重要。原始的CP/M系统通常运行在64KB内存的机器上,RunCPM需要在RP2040的内存中模拟出这片地址空间,并留出足够的余量给模拟器自身和FatFS文件系统(用于读写SD卡)。RP2040的264KB SRAM完全能满足这个需求,甚至能模拟多个“虚拟磁盘驱动器”。
其次,板载的MicroSD卡槽是这个项目的“灵魂”。CP/M时代,软件和数据存储在软盘上。在Tiny RunCPM中,SD卡就扮演了虚拟软盘驱动器的角色。你可以在电脑上,将CP/M的磁盘映像文件(通常是.DSK格式)直接拷贝到SD卡中,RunCPM在启动时就能识别并挂载它们,就像在真正的CP/M电脑上插入了一张软盘。Adalogger板通过RP2040的SPI接口与SD卡通信,其驱动程序已经集成在RunCPM的工程中。
再者,简洁的外设布局降低了入门门槛。板子除了基本的电源、调试接口和用户按键,没有多余的东西。这意味着你需要的外设非常明确:一个5V电源(USB即可),一个用于输入输出的串口。输出显示通常通过串口终端软件(如PuTTY、CoolTerm)实现,这是一种最原始也最通用的“显示器”。当然,如果你想让体验更复古,完全可以外接一个字符LCD屏,或者像网络热词提到的,用一块Adafruit ST7789驱动的SPI TFT屏来显示80x25的字符终端,这就更像一台真正的老式电脑了。
注意:Adafruit RP2040 Adalogger的3.3V逻辑电平与大多数SD卡兼容。请确保使用质量可靠的MicroSD卡,并格式化为FAT32文件系统。一些大容量或高速卡可能存在兼容性问题,从经验来看,使用16GB或32GB的Class 10品牌卡成功率最高。
2.2 软件核心:RunCPM模拟器的工作原理
RunCPM不是一个完整的硬件模拟器(比如QEMU),而是一个“系统级模拟器”。它并不逐周期地模拟Z80 CPU的每一个电子信号,而是在C语言层面,实现了Z80的指令集和CP/M操作系统的API(BDOS调用)。你可以把它理解为一个“翻译层”或“兼容层”。
它的工作流程大致如下:
- 启动加载:RP2040上电后,运行一个引导程序,然后加载RunCPM固件。
- 环境初始化:固件初始化硬件(时钟、GPIO、SPI、UART),挂载SD卡上的FAT文件系统,并寻找默认的磁盘映像文件。
- CPU模拟循环:模拟器进入一个主循环。在这个循环中,它从模拟的64KB内存中读取“下一条指令”(实际上是从一个代表内存的C语言数组中读取数据)。
- 指令解码与执行:根据读到的数据(模拟的Z80机器码),调用对应的C函数来模拟该指令的执行效果,例如寄存器加减、内存读写、跳转等。
- BDOS调用拦截:当模拟器“执行”到特定的CALL指令(通常是调用5号地址,这是CP/M的BDOS入口)时,RunCPM会跳出模拟循环。它检查寄存器中的参数,判断CP/M程序想要做什么(例如:在控制台输出一个字符、从磁盘读取一个扇区、打开文件等)。
- 主机服务实现:RunCPM不再去模拟古老的BIOS磁盘操作,而是将这些BDOS请求“翻译”成对主机(RP2040)资源的调用。比如,“读磁盘扇区”的请求会被翻译成使用FatFS库从SD卡上的
.DSK映像文件中读取相应数据块;“输出字符”请求则通过RP2040的UART发送到串口终端。 - 状态恢复与继续:完成主机服务后,模拟器设置好Z80模拟寄存器的返回值,然后跳回第3步,继续执行下一条CP/M指令。
这种设计非常高效。它避免了底层硬件模拟的巨大开销,直接在现代硬件上实现了老操作系统的核心功能。项目开源代码的结构清晰,z80.c/z80.h负责CPU指令模拟,bdos.c处理所有的CP/M系统调用,而hardware.c则包含了所有针对RP2040平台的硬件驱动代码。
2.3 开发环境与工具链搭建
要为RP2040编译RunCPM,你需要搭建ARM Cortex-M0+的开发环境。目前最主流、最便捷的选择是Raspberry Pi Pico SDK配合CMake和GCC ARM工具链。
具体搭建步骤(以Windows为例,Linux/macOS类似):
安装工具链:
- 下载并安装
arm-none-eabi-gcc工具链。可以从ARM官网或MSYS2等包管理器获取。 - 安装
CMake和Ninja(或Make)构建工具。 - 安装
Git用于克隆代码。
- 下载并安装
获取Pico SDK和项目代码:
# 克隆Pico SDK(建议放到一个干净的目录,如 c:/pico/) git clone -b master https://github.com/raspberrypi/pico-sdk.git cd pico-sdk git submodule update --init # 设置环境变量 PICO_SDK_PATH 指向此路径 # 克隆Tiny RunCPM for Adalogger的代码库 git clone https://github.com/MockbaTheBorg/RunCPM.git通常,针对特定板子的移植代码会在
RunCPM/ports目录下。你需要找到为RP2040-AdaLogger或类似命名的端口。配置与编译:
cd RunCPM/ports/RP2040-AdaLogger mkdir build cd build cmake -G "Ninja" -DPICO_BOARD=adafruit_rp2040_adalogger .. ninja执行成功后,会在
build目录下生成RunCPM.uf2文件。这就是可以刷写到RP2040上的固件。刷写固件:
- 按住Adalogger板上的
BOOT(或BOOTSEL)按钮,同时将USB线连接到电脑。 - 电脑上会出现一个名为
RPI-RP2的可移动磁盘。 - 将编译好的
RunCPM.uf2文件拖拽进去。板子会自动复位并运行新固件。
- 按住Adalogger板上的
实操心得:在Windows上,更推荐使用
Visual Studio Code配合CMake Tools和Cortex-Debug插件进行开发。这样可以直接在IDE里完成配置、编译、调试(通过SWD接口)的全流程,效率高很多。初次搭建环境时,确保PICO_SDK_PATH环境变量设置正确是成功编译的关键,90%的编译错误都源于此。
3. 系统构建与磁盘映像制备
3.1 准备CP/M系统磁盘映像
光有模拟器硬件还不够,你需要CP/M的“灵魂”——系统磁盘映像。一个标准的CP/M系统盘需要包含以下几个部分:
- 引导扇区:虽然RunCPM不从磁盘引导,但映像结构需要符合规范。
- CP/M BIOS:基本输入输出系统,在RunCPM中其功能已被
hardware.c中的代码替代。 - CP/M BDOS:基本磁盘操作系统,是RunCPM模拟的核心。
- CP/M CCP:控制台命令处理器,也就是你看到的命令行解释器(例如经典的“A>”提示符)。
- 系统工具:像
DIR(列目录)、ERA(删除文件)、TYPE(显示文件内容)、PIP(文件复制)等命令。
对于初学者,最方便的方式是直接使用现成的磁盘映像文件。互联网上有许多归档网站提供了经典的CP/M软件合集,例如cbios、22nice等打包好的.DSK文件。你可以直接下载这些.DSK文件,并将其复制到SD卡的根目录下。
更进阶的做法是自己使用工具创建和编辑磁盘映像。在PC上,你可以使用cpmtools这套工具。首先,你需要为你的“虚拟CP/M机器”定义一个磁盘格式描述文件(diskdef)。例如,定义一个8英寸单面单密度软盘格式(经典的SSSD格式):
diskdef sssd seclen 128 tracks 77 sectrk 26 blocksize 1024 maxdir 64 skew 1 boottrk 0 os 2.2 end然后,你可以用mkfs.cpm创建一个空的磁盘映像,并用cpm命令(一个交互式工具)向里面添加文件:
# 创建一个空的sssd格式磁盘映像,命名为MYDISK.DSK mkfs.cpm -f sssd MYDISK.DSK # 启动交互式编辑器,将PC上的文件复制到映像中 cpm -f sssd MYDISK.DSK cpm> put myfile.txt cpm> dir cpm> exit将编辑好的MYDISK.DSK放到SD卡中,RunCPM启动后就可以识别并访问里面的文件了。
3.2 配置RunCPM与虚拟驱动器映射
RunCPM在启动时,会读取SD卡根目录下的一个名为CPM.INI(或类似名称)的配置文件。这个文件用于定义虚拟驱动器(A:, B:, C: ...)与SD卡上具体.DSK文件的映射关系。
一个典型的CPM.INI配置内容如下:
A=MASTER.DSK B=GAMES.DSK C=WORK.DSK这表示将SD卡上的MASTER.DSK文件映射为CP/M系统中的A驱动器,GAMES.DSK映射为B驱动器,依此类推。A驱动器通常是系统盘,包含了CCP和常用工具。
你还可以在配置文件中设置其他参数,比如:
- 串口的波特率(
BAUDRATE=115200)。 - 默认的驱动器(
USER=0,表示A驱动器)。 - 模拟的Z80 CPU频率(
CPU=Z80或CPU=8080)。
配置文件的名称和位置可能因RunCPM的不同移植版本而异,需要查阅具体端口的文档。正确配置这个文件是让系统按照你期望的方式启动的关键一步。
3.3 上电启动与串口终端连接
硬件和软件准备就绪后,就可以开始第一次启动了。
- 硬件连接:将准备好的SD卡插入Adalogger板。使用USB线为板子供电。如果你需要通过串口交互(这是最常用的方式),则需要连接串口引脚。Adalogger RP2040的UART通常映射在GPIO0(TX)和GPIO1(RX)。你需要一个USB转TTL串口模块,将其RX接板子的TX(GPIO0),TX接板子的RX(GPIO1),GND对接。
- 启动顺序:上电后,板子上的LED可能会闪烁几下,表示正在初始化SD卡和加载配置。如果一切正常,它就会开始运行CP/M模拟器。
- 终端软件配置:在电脑上打开串口终端软件(如PuTTY、Tera Term、CoolTerm或VS Code的串口监视器)。选择正确的串口号(在Windows设备管理器中查看),设置波特率(与
CPM.INI中配置的一致,默认为115200),数据位8,停止位1,无奇偶校验,无流控。 - 进入系统:连接串口后,按几次回车键,你应该会看到经典的CP/M提示符,例如
A>。至此,你的Tiny RunCPM计算机就启动成功了!输入DIR命令,可以看到A驱动器下的文件列表。
注意事项:第一次启动如果只看到乱码或者没有任何输出,请按以下步骤排查:1) 确认串口波特率设置是否正确;2) 确认串口线的TX/RX是否接反;3) 检查SD卡中是否有正确的磁盘映像文件和配置文件;4) 尝试降低波特率到9600或19200,看是否是时钟精度问题。
4. 应用探索与高级玩法
4.1 运行经典CP/M软件
成功进入系统后,最大的乐趣就是运行那些尘封的经典软件了。你可以从互联网档案馆(archive.org)或专门的复古计算站点找到大量的CP/M软件包,通常是打包好的.DSK映像。
- 文字处理:WordStar, 这款在DOS时代之前统治办公领域的文字处理器,其操作方式(如Ctrl+S保存, Ctrl+Q退出)影响深远。在CP/M上运行它,能让你直观感受到早期软件的设计哲学。
- 电子表格:VisiCalc, 世界上第一款电子表格软件,是推动个人电脑普及的重要功臣。在Tiny RunCPM上体验它,别有一番历史厚重感。
- 数据库:dBase II, 关系型数据库的早期王者,学习它的命令式操作,是理解数据库发展的活教材。
- 编程语言:Microsoft BASIC-80、Turbo Pascal 3.0、C Compiler (BDS C)。在这些古老的开发环境中编写一个“Hello, World!”程序,然后编译、链接、运行,整个过程能让你对现代IDE的便利性有全新的感激,也能深刻理解编译器、链接器这些工具链的原始形态。
- 游戏:像《Zork》(文字冒险游戏鼻祖)、《Microsoft Adventure》等,它们不依赖图形,全靠文字描述来构建一个奇幻世界,是另一种沉浸式体验。
运行方法很简单:将包含这些软件的.DSK文件拷贝到SD卡,并在CPM.INI中将其映射为一个驱动器(例如D=GAMES.DSK)。启动后,在A>提示符下输入D:切换到D驱动器,再用DIR查看,运行其中的.COM可执行文件即可。
4.2 外接显示设备:从串口终端到SPI屏幕
虽然串口终端是最简单通用的方式,但总少了一点“电脑”的实感。这时,网络热词中提到的Adafruit ST7789驱动的SPI TFT屏就派上用场了。这种屏幕分辨率通常为240x240或240x320,足够显示一个80列x25行的字符终端(每个字符8x16像素的话,需要640x400,但可以通过缩放或显示部分区域实现)。
要实现这个功能,你需要:
- 硬件连接:将ST7789屏幕的SPI接口(SCK, MOSI)连接到RP2040的SPI引脚(例如SPI0),并连接DC、RST、CS等控制引脚。还需要背光控制。具体引脚定义需参考屏幕和Adalogger的引脚图。
- 修改固件:RunCPM的默认输出是UART。你需要修改
hardware.c中的控制台输出函数(通常是conout)。将原本向UART发送字符的代码,改为调用Adafruit ST7789图形库(如Adafruit_ST7789)的函数,在屏幕上绘制相应的ASCII字符。 - 实现字符帧缓冲区:在内存中开辟一块区域作为字符屏幕的映射(80x25的数组)。
conout函数不仅要在物理屏上画点,还要更新这个缓冲区。同时需要实现光标闪烁、滚屏等基本终端功能。 - 输入处理:你可能还需要连接一个PS/2键盘或通过矩阵键盘来输入,替换掉串口输入。这需要修改
conin函数。
这个过程相当于为RunCPM“移植”了一个新的显示驱动。虽然有一定工作量,但完成后,你就得到了一台自带屏幕的、真正“一体化”的微型CP/M电脑,体验感直接拉满。网上已有一些爱好者分享了他们为RP2040和ST7789移植的图形终端代码,可以作为很好的起点。
4.3 扩展存储与网络幻想
RP2040 Adalogger只有一个SD卡槽,但在CP/M系统中,你可以通过修改配置,将一个大的SD卡分区或单个大文件,虚拟成多个独立的磁盘驱动器(A:, B:, C: ...)。RunCPM本身支持这种映射。
更进一步的幻想是网络功能。原始的CP/M有网络概念吗?几乎没有。但我们可以“开脑洞”。例如:
- 虚拟穿孔纸带/磁带:将串口模拟成一个低速网络通道,在两台RunCPM设备之间用
PIP命令通过串口发送文件。这模拟了早期计算机通过纸带阅读器或磁带机共享数据的场景。 - TCP/IP桥接:在RP2040上运行一个轻量级TCP/IP栈(如lwIP),并编写一个特殊的CP/M“设备驱动”。这个驱动可以将BDOS的读写请求,转发到TCP/IP套接字上,连接到网络上的一个文件服务器。这样,在CP/M的
DIR命令下,你看到的可能是远程服务器上的文件。这实现起来非常复杂,但理论上可行,是将古董系统带入现代网络环境的一种终极黑客玩法。
5. 常见问题与调试心得实录
在搭建和把玩Tiny RunCPM的过程中,你肯定会遇到各种各样的问题。下面记录了一些典型问题和我踩过的坑。
5.1 编译与刷写问题
问题1:CMake配置失败,找不到PICO_SDK_PATH。
- 现象:执行
cmake时提示错误,无法找到SDK或工具链。 - 排查:这是环境变量问题。在Windows命令提示符下,用
echo %PICO_SDK_PATH%检查是否设置。在Linux/macOS下用echo $PICO_SDK_PATH。 - 解决:永久设置环境变量,或者在执行cmake前临时设置:
然后重新执行cmake命令。# Windows (cmd) set PICO_SDK_PATH=C:\pico\pico-sdk # Linux/macOS export PICO_SDK_PATH=/path/to/pico-sdk
问题2:刷写UF2后,串口无任何输出。
- 现象:板子LED可能正常亮,但串口终端一片空白。
- 排查:
- 波特率:首先确认终端软件的波特率是否与固件内设置的一致。尝试常见的波特率:115200, 9600, 19200。
- 接线:确认USB转TTL模块的RX/TX是否与板子交叉连接(模块RX接板子TX)。
- 电源:确保板子供电充足。如果只通过USB转TTL模块的3.3V供电,可能功率不足,建议同时连接USB供电。
- 固件:确认编译的固件是否针对
adafruit_rp2040_adalogger板型。刷错了板型的固件可能无法初始化硬件。
- 解决:最稳妥的方法是使用一个已知能输出调试信息的简单程序(如Pico SDK的
hello_uart例程)测试串口硬件和连接是否正确。
5.2 运行时与文件系统问题
问题3:启动后提示“No System Disk”或直接进入BASIC(如果固件包含)。
- 现象:RunCPM启动了,但无法加载CP/M系统。
- 排查:
- SD卡:SD卡是否格式化为FAT32?是否插好?尝试重新格式化(快速格式化即可)。
- 磁盘映像文件:SD卡根目录下是否有
CPM.INI配置文件?配置文件中指定的.DSK映像文件(如A=MASTER.DSK)是否存在且文件名拼写正确? - 映像文件本身:尝试换一个已知可用的
.DSK文件测试。
- 解决:确保SD卡文件系统结构简洁,将必要的文件放在根目录。避免使用过长的文件名或中文名。
问题4:在CP/M下执行某些程序时死机或行为异常。
- 现象:运行某个
.COM文件后,系统失去响应。 - 排查:这通常是程序与RunCPM模拟器或硬件配置不兼容。
- 程序类型:确认该程序是为Z80还是8080 CPU编译的?RunCPM可以配置模拟的CPU类型。
- 系统调用:一些程序可能使用了非标准的BDOS调用,或者直接操作硬件。RunCPM可能没有完全模拟这些行为。
- 内存冲突:一些程序可能试图访问它认为存在的特定内存区域(如图形显存),而这在模拟环境中不存在。
- 解决:尝试在
CPM.INI中切换CPU=8080或CPU=Z80。如果不行,大概率是该程序无法在此模拟环境下运行。CP/M软件浩如烟海,兼容性并非100%。
问题5:文件操作(复制、删除)失败。
- 现象:使用
PIP复制文件或ERA删除文件时出错。 - 排查:
- 磁盘映像只读:有些从网上下载的
.DSK映像是被设置为“只读”属性的,或者其文件系统已满。 - RunCPM的磁盘模拟模式:RunCPM可能以“只读”模式挂载了某个磁盘映像。
- 文件属性:在CP/M下,用
STAT命令查看文件属性。系统文件可能具有“只读”或“系统”属性,无法直接删除。
- 磁盘映像只读:有些从网上下载的
- 解决:对于需要写入的磁盘,确保你使用的是自己创建的可写映像。在CP/M下,使用
STAT 文件名 $RWS可以将文件属性改为读/写/系统。
5.3 性能优化与自定义
心得1:超频以获得更流畅的体验。RP2040默认运行在125MHz,但很多板子可以稳定超频到250MHz甚至更高。在CMakeLists.txt或pico_sdk_import.cmake所在的配置中,可以设置超频参数。对于RunCPM来说,更高的主频意味着控制台响应更快,特别是使用串口终端时,滚屏和字符回显会更跟手。修改CMakeLists.txt,添加:
pico_set_binary_defaults(runcpm_target NO_FLASH NO_EEPROM) pico_add_extra_outputs(runcpm_target) # 设置超频到250MHz pico_set_sysclock_250mhz(runcpm_target)重新编译并刷写即可。超频有风险,需确保电源质量良好,并注意芯片散热。
心得2:自定义键盘映射。如果你外接了PS/2键盘,可能会发现某些键位不对应。这是因为RunCPM将收到的扫描码映射到CP/M期望的ASCII码。这个映射表通常在hardware.c或一个独立的头文件中定义。例如,你可能需要修改scancode_to_ascii[]这个数组,来将你键盘上的“\”键映射为CP/M下常用的“←”(退格)字符。修改后需要重新编译固件。
心得3:利用第二个核心。RP2040是双核的,而RunCPM主模拟循环是单线程的。一个有趣的实验是,能否将模拟器的CPU循环放在一个核心,而将文件I/O、终端显示等任务放在另一个核心,通过队列进行通信。这可以进一步提升响应速度,尤其是在处理磁盘访问时,模拟的“CPU”可以继续运行而不被阻塞。这需要对RunCPM源码和FreeRTOS(或Pico SDK的多核API)有较深的理解,是一个极具挑战性但也很有成就感的修改方向。
折腾Tiny RunCPM的过程,更像是一次穿越时空的计算机考古与再创造。它没有直接的生产力价值,但其中涉及的软硬件协同、系统模拟、跨时代技术对接的思考,其价值远超项目本身。当你看到绿色的光标在古老的“A>”提示符后闪烁时,连接的不是电路,而是跨越四十年的计算之魂。