1. 项目概述:为什么我们需要定制CP/M BIOS?
如果你玩过基于Z80或8080兼容处理器(比如V20-MBC上那颗NEC V20)的复古计算机或嵌入式系统,那你对CP/M操作系统一定不陌生。作为个人计算机的早期王者,CP/M的精髓之一就在于其模块化设计,尤其是它的BIOS(基本输入/输出系统)。与今天PC上那庞大、封闭的UEFI BIOS不同,CP/M的BIOS是一段相对小巧、完全开源的汇编代码,它直接定义了操作系统如何与你的特定硬件“对话”。
我最初动手修改V20-MBC的CP/M-2.2 BIOS,动机很简单:每次程序退出或按Ctrl+C时,屏幕上都会跳出一个“WARM BOOT”的提示信息,这在频繁调试时显得有点啰嗦。我想把它关掉。但这个小需求背后,触及的是一个更大的可能性:通过定制BIOS,你可以让CP/M系统支持原本不存在的硬件,比如通过GPIO引脚连接一个并口打印机(PRN设备),或者为特殊的存储设备编写驱动。这就像是拿到了系统的“底层钥匙”,你可以重新定义硬件与软件的交互规则。
然而,在Windows环境下为这样一个古董级的系统编译BIOS,听起来就像是用现代车床去加工一个蒸汽机零件——工具和环境都隔了好几代。网上资料零散,工具古老,步骤琐碎。我花了些时间,把整个流程从头到尾跑通并记录下来,目的就是给后来者铺一条清晰的路。无论你是想移除一个烦人的提示,还是雄心勃勃地想添加全新的硬件支持,第一步都是要搭建一个能成功编译出原始BIOS二进制文件(.bin)的环境。这篇文章,就是关于如何跨过这第一步,并为你后续的修改打下坚实基础的完整指南。
2. 环境准备:在Windows上重建一个80年代的“车间”
为CP/M编译BIOS,核心工具是一个Z80汇编编译器。在当年,流行的是Microsoft的M80或Digital Research的ASM。但时过境迁,这些工具在现代Windows上配置异常麻烦。经过一番搜寻和测试,TASM (Turbo Assembler) 3.2的Z80/8085版本被证明是当前最可靠的选择。它体积小巧,命令行操作简洁,并且能很好地处理V20-MBC项目中的源码。
2.1 获取必要的“原材料”
你需要准备三样东西:
- 一台Windows机器:物理机或虚拟机均可。我本人日常用Linux,所以是在Windows 10虚拟机里完成的所有工作,用完后直接挂起,不占用主机资源,非常干净。确保系统有基本的命令行操作权限。
- TASM 3.2 编译器:这是一个有点年头的软件了,但依然有效。你可以通过搜索“TASM 3.2 z80”找到它。通常,它存在于一些复古计算或编译器存档网站上。下载后你会得到一个名为
tasm.zip的压缩包。 - V20-MBC的SD卡映像文件:这是项目的核心资源包。你需要从V20-MBC的项目主页(例如Hackaday.io的项目页面)下载最新的SD卡映像ZIP文件。这个包里不仅包含可启动的CP/M系统,更重要的是,它包含了完整的CP/M-2.2源代码,其中就有我们要修改的
CPM22-8080.ASM和BIOS CPM22 - S140520.asm文件。
注意:务必从项目官方渠道获取SD卡映像,不同版本的硬件(如V20-MBC2)对应的源码可能有细微差别,用错版本可能导致编译出的BIOS无法启动。
2.2 规划你的工作目录
在Windows上,一个清晰、无空格、路径短的工作目录能省去无数麻烦。我强烈建议采用以下结构:
C:\ ├── tasm\ (存放TASM编译器所有文件) └── v20\ ├── SD\ (解压SD卡映像ZIP文件到此) └── src\ (后续用于存放源码,但SD卡包内通常已包含)具体操作如下:
- 在C盘根目录创建
v20文件夹。 - 在
v20内创建SD文件夹。 - 在C盘根目录创建
tasm文件夹。 - 将下载的
tasm.zip文件全部内容解压到C:\tasm。你应该能在里面看到TASM.EXE、TASM.HLP等文件。 - 将下载的SD卡映像ZIP文件(例如
SD-xxxxxx.zip)解压到C:\v20\SD。解压后,你通常会看到一堆.DSK磁盘映像文件和CPM22.BIN等。
2.3 配置系统环境变量
这是让TASM在任意位置都能被调用的关键步骤。Windows的环境变量相当于给系统设置了一个全局的“地址簿”。
- 右键点击“此电脑”(或“我的电脑”),选择“属性”。
- 点击“高级系统设置”。
- 在弹出的“系统属性”窗口中,点击右下角的“环境变量”按钮。
- 新建用户变量:
- 在“用户变量”区域(上半部分),点击“新建”。
- 变量名输入:
TASMTABS - 变量值输入:
C:\tasm - 点击“确定”。这个变量告诉TASM去哪里寻找它的指令集表格文件。
- 修改系统Path变量:
- 在“系统变量”区域(下半部分),滚动找到并选中“Path”变量,点击“编辑”。
- 在打开的编辑窗口中,点击“新建”,然后输入:
C:\tasm - 点击“确定”关闭所有窗口。
实操心得:完成设置后,务必重启你的Windows系统或至少注销重新登录。这是为了让新的环境变量生效。很多初学者卡在这一步,就是因为修改后没有重启,导致命令行依然找不到
tasm命令。
3. 编译流程详解:从源码到可启动的BIN文件
环境搭好了,我们直接进入实战。目标是成功编译出与原始SD卡中功能完全一致的cpm22.bin文件,验证整个工具链是畅通的。
3.1 定位源码与初次编译测试
打开命令提示符(CMD)。按
Win + R,输入cmd,回车。切换到源码目录。根据SD卡包的解压结构,源码通常位于
SD目录下的某个子文件夹中。常见路径是C:\v20\SD\src\cpm22_8080。使用命令:cd C:\v20\SD\src\cpm22_8080使用
dir命令列出文件,你应该能看到两个关键的.ASM文件。关键一步:修改编译模式。直接编译
CPM22-8080.ASM会默认生成用于模拟器或特定加载器的格式,而不是V20-MBC所需的纯二进制映像。我们需要修改源码中的一个配置开关。- 用任何文本编辑器(如记事本、Notepad++、VS Code)打开
CPM22-8080.ASM。 - 滚动到大约第70行,或者直接搜索
iLoadMode。你会看到如下代码块:;----------------------------------------------------------------------- iLoadMode .equ 0 ; set to 0 for track 0 image generation, ; Set to 1 for iLoad-80 mode (for testing), ; set to 2 for cpm22.bin binary file generation ;----------------------------------------------------------------------- - 将
iLoadMode的值从0改为2。正如注释所说,模式2专门用于生成cpm22.bin二进制文件。这是整个编译过程中最容易出错的一步,改错或忘记改都会导致生成的文件无法使用。 - 保存并关闭文件。
- 用任何文本编辑器(如记事本、Notepad++、VS Code)打开
执行编译命令。在CMD中,输入以下命令:
tasm -85 -b CPM22-8080.ASM -o cpm22.bin让我们拆解这个命令:
tasm: 调用编译器。-85: 指定使用8085指令集表格。虽然V20是8080兼容的,但8085指令集是8080的超集,完全兼容且是TASM的常用选项,用这个更稳妥。-b: 指示生成二进制输出文件。CPM22-8080.ASM: 输入的源文件。-o cpm22.bin: 指定输出文件名为cpm22.bin。
如果一切顺利,你将看到类似下面的输出,显示“Number of errors = 0”:
TASM 8085 Assembler. Version 3.2 September, 2001. Copyright (C) 2001 Squak Valley Software tasm: pass 1 complete. tasm: pass 2 complete. tasm: Number of errors = 0同时,目录下会生成cpm22.bin和CPM22-8080.lst(列表文件,可用于调试)等新文件。
3.2 验证与部署编译成果
编译成功只是第一步,我们必须验证这个.bin文件是有效的。
- 备份原始文件:在进行任何替换之前,这是铁律。进入你的SD卡根目录(
C:\v20\SD),找到原有的cpm22.bin文件,将其重命名为cpm22.bin.backup。 - 复制新文件:将刚刚在
src目录下生成的新cpm22.bin文件,复制到SD卡的根目录,覆盖(因为已备份)或替换原来的位置。 - 上机测试:将SD卡插入V20-MBC,启动系统。如果系统能正常引导进入CP/M,并且功能与之前完全一致(除了你还没做任何修改),那么恭喜你,你的编译环境已经完全就绪了!
注意事项:第一次编译时,建议完全按照上述流程,先不要做任何代码修改,只改动
iLoadMode。这样能确保工具链本身没问题。如果此时启动失败,问题很可能出在源码路径、环境变量或SD卡映像版本不匹配上,而不是你的操作步骤。
4. BIOS源码结构解析与定制入门
现在,我们有了可靠的编译流水线,可以开始真正的“定制”了。但在动刀之前,有必要了解一下CP/M BIOS源码的基本结构,这样你才知道在哪里下刀,以及下刀后会产生什么影响。
4.1 CP/M BIOS的模块化构成
打开CPM22-8080.ASM,你会发现它其实是一个“骨架”,它包含了对系统尺寸的定义、跳转表,并通过INCLUDE语句引入了另一个核心文件——通常是类似BIOS CPM22 - S140520.asm这样的文件。真正的硬件相关代码,都在这个被包含的BIOS文件中。
一个标准的CP/M BIOS需要实现一系列入口点,CP/M内核(BDOS)会调用这些入口点来与硬件交互。主要可以分为以下几大块:
- 冷启动入口(COLD BOOT):系统上电或硬复位时执行,负责最基础的硬件初始化,如设置串口波特率、初始化内存、建立磁盘参数块(DPB)等。
- 热启动入口(WARM BOOT):当程序退出或用户按下Ctrl+C时调用。通常用于重新初始化部分硬件(但不包括内存清零),并打印提示信息(比如那个我们想删掉的“WARM BOOT”)。
- 控制台输入/输出(CONIN, CONOUT, CONST):
CONIN: 从控制台(通常是串口终端)读取一个字符,并回显。CONOUT: 向控制台输出一个字符。CONST: 检查控制台是否有字符输入(状态检查)。
- 磁盘I/O(SELDSK, SETTRK, SETSEC, SETDMA, READ, WRITE):这是最复杂的一部分,负责与SD卡(在V20-MBC上通过SPI模拟磁盘)进行数据交换。它实现了CP/M的磁盘抽象层。
- 其他设备入口:如
LIST(列表设备输出,通常指打印机)、PUNCH、READER等,在简单系统上可能只是空操作或指向控制台。
4.2 实战:移除“WARM BOOT”提示信息
以我最初的需求为例,我们来看看如何定位并移除这个提示。
定位代码:在
BIOS CPM22 - S140520.asm文件中,搜索“WARM BOOT”字符串。你很快会找到类似下面的代码段:WBOOT: ; Warm boot entry point ... LD HL, MSG_WARM_BOOT ; 将提示信息的地址加载到HL寄存器 CALL PRINT_STRING ; 调用打印字符串子程序 ... JP GOCPM ; 跳转到CP/M内核或者,信息可能直接内联在附近:
LD HL, WARM_BOOT_MSG ... WARM_BOOT_MSG: .DB 'WARM BOOT', 13, 10, 0分析并修改:我们的目标是不打印这条信息。最简单直接的方法就是注释掉或删除加载字符串和调用打印函数的那两条指令。例如:
WBOOT: ; Warm boot entry point ... ; LD HL, MSG_WARM_BOOT ; 注释掉这行 ; CALL PRINT_STRING ; 注释掉这行 ... JP GOCPM或者,如果你确定
PRINT_STRING调用后没有其他关键依赖,也可以直接删除这两行。重新编译与测试:保存BIOS源文件。回到CMD,在源码目录再次执行编译命令:
tasm -85 -b CPM22-8080.ASM -o cpm22.bin将新的
cpm22.bin复制到SD卡并启动V20-MBC。现在,当程序退出或你按下Ctrl+C时,应该直接返回到CP/M命令行(如A>),而不再显示“WARM BOOT”了。
避坑技巧:在修改汇编代码时,务必注意指令的字节长度。如果你删除的指令不是恰好3字节(
LD HL, nn)或3字节(CALL nn),可能会导致后续代码地址错乱,引发不可预知的崩溃。最安全的方法是使用NOP(空操作,0x00)指令填充被删除指令的空间,或者直接注释掉而非物理删除。在不确定的情况下,注释掉是最稳妥的。
4.3 进阶思路:添加一个简单的GPIO设备驱动
假设你想通过V20-MBC的某个GPIO引脚控制一个LED,并在CP/M下通过一个简单命令访问。这需要修改BIOS,增加一个新的I/O函数入口。
- 规划功能:例如,我们定义通过调用一个自定义的BIOS功能号来设置GPIO状态。CP/M的BDOS调用
CALL 5通常用于标准功能,但我们可以利用一个未使用的功能号,或者更常见的是,在BIOS中预留一个自定义跳转点,然后写一个小的汇编测试程序去调用它。 - 在BIOS中增加代码:
- 在BIOS源码的末尾(通常在所有标准入口点定义之后),找一个空闲区域,添加你的GPIO控制子程序。例如:
; Custom function to set GPIO pin ; Input: Register C = pin state (e.g., 0 for low, 1 for high) SET_GPIO: ; 这里添加具体的硬件操作代码 ; 例如,将C寄存器的值写入某个I/O端口地址 OUT (GPIO_PORT), A ; 假设GPIO端口地址已定义 RET - 在BIOS的跳转表(通常位于文件开头,名为
JMPTBL或类似)中,增加一个指向这个新子程序的入口。你需要确保跳转表的总长度和入口顺序符合CP/M的预期。
- 在BIOS源码的末尾(通常在所有标准入口点定义之后),找一个空闲区域,添加你的GPIO控制子程序。例如:
- 编写测试程序:在CP/M下,用汇编语言写一个小程序(
.ASM文件),编译后(使用CP/M下的ASM或MAC编译器),通过CALL指令直接调用你添加的BIOS例程的绝对地址。 - 集成挑战:这种方法需要对V20-MBC的硬件地址映射(GPIO端口号)和CP/M的BIOS内存布局有深入了解。更系统的方法是扩展CP/M的BDOS功能,但这涉及更复杂的内核修改,超出了基础BIOS定制的范畴。对于初学者,建议从修改现有功能(如控制台行为、磁盘参数)开始。
5. 常见问题排查与深度优化指南
即使按照步骤操作,你也可能会遇到一些“坑”。这里汇总了我遇到过的一些典型问题及其解决方案。
5.1 编译过程报错
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
‘tasm’ 不是内部或外部命令... | 环境变量Path未生效或设置错误。 | 1. 检查C:\tasm下是否有TASM.EXE。2. 重启CMD或整个系统。 3. 在CMD中手动设置临时路径: set PATH=C:\tasm;%PATH%,再运行tasm。 |
Error: Can't open include file ... | 源码中INCLUDE语句指向的文件路径不对。 | 确保被包含的BIOS文件(如BIOS CPM22 - S140520.asm)与主汇编文件在同一目录,或者修改INCLUDE语句为正确相对/绝对路径。 |
| 汇编语法错误(如未定义符号) | 源码版本与TASM不兼容,或源码本身有误。 | 1. 确认使用的是V20-MBC官方提供的源码包。 2. 检查是否有标点符号使用全角字符,汇编必须使用英文半角。 3. 尝试在TASM命令中增加 -l选项生成列表文件(.lst),查看具体错误位置。 |
5.2 生成的BIN文件无法启动
| 故障现象 | 排查思路 | 解决方案 |
|---|---|---|
| 系统黑屏或卡住 | 编译出的BIOS二进制文件损坏或格式不对。 | 1.首要检查:确认iLoadMode已设置为2。2. 比较新旧 cpm22.bin文件大小。新文件大小应与原版非常接近(例如,都是约17KB)。差异过大通常意味着编译模式错误。3. 使用二进制比较工具(如 fc /b命令)对比你编译的bin和备份的原版bin,在修改前它们应该完全一致。 |
| 启动后字符乱码或串口无输出 | BIOS中串口初始化代码(波特率、数据位等)被意外修改。 | 1. 回顾你的修改,是否触及了COLD BOOT部分的硬件初始化代码?2. 仔细检查你编辑的源文件,确保没有误删或误改其他行。 3.最有效的调试方法:使用“二分法”。如果你做了多处修改,先全部注释掉,确保能编译出可启动的版本。然后逐一启用修改,每改一处就测试一次,从而定位问题点。 |
| 磁盘无法访问(如出现BDOS错误) | 磁盘I/O相关代码(DPB、扇区转换逻辑)被破坏。 | 1. 这通常是危险信号,意味着修改可能影响了磁盘参数块或读写例程。 2. 立即换回备份的原始BIOS文件,确认硬件和SD卡本身正常。 3. 检查你的修改是否在磁盘相关函数( SELDSK,READ,WRITE等)附近,即使看似无关的代码删除也可能改变内存布局,影响这些函数的调用。 |
5.3 性能与稳定性优化建议
- 版本控制:在开始任何修改前,务必使用Git或其他版本管理工具初始化你的源码目录。每次编译测试前提交一次,这样你可以随时回退到任何一个可工作的状态。对于汇编项目,这比手动备份文件要可靠得多。
- 交叉编译考量:如果你像我一样主要工作在Linux/macOS下,可以考虑在Windows虚拟机中只保留编译和测试环境,而在宿主机上用更现代的编辑器(如VS Code with ASM插件)进行代码编辑,通过共享文件夹同步源码。这能极大提升编辑体验。
- 利用列表文件:TASM生成的
.lst文件是极佳的调试助手。它包含了源码、生成的机器码和对应的内存地址。当你需要计算跳转偏移量或查看指令编译结果时,这个文件不可或缺。 - 模拟器先行:在将BIOS刷入实体机前,如果存在Z80/8080模拟器(如
z80pack、simh)能运行你的CP/M映像,可以现在模拟器上测试BIOS的基本功能。这能避免频繁插拔SD卡,提高调试效率。不过,V20-MBC的硬件特性(如GPIO)在模拟器中无法测试。
6. 从修改到创造:拓展你的复古计算项目
成功编译并修改了第一个BIOS功能后,你的旅程才刚刚开始。V20-MBC和CP/M-2.2构成了一个极具可玩性的平台。
- 深入硬件驱动:研究V20-MBC的电路图,了解其IO端口映射。你可以尝试为PS/2键盘、VGA显示模块(如果有)或额外的存储设备编写更完整的BIOS驱动,将这些现代或经典的硬件融入CP/M世界。
- 探索CP/M内核:BIOS之上是BDOS(基本磁盘操作系统)和CCP(控制台命令处理器)。这些部分也有源码(通常在
CPM22.SRC等文件中)。理论上,你可以修改命令行解释器、增加内部命令,甚至调整磁盘文件系统的参数。 - 集成高级语言:CP/M下有丰富的语言,如Microsoft BASIC、Turbo Pascal、C(如BDS C)。定制BIOS可以确保这些语言环境能与你的特殊硬件完美配合。例如,为Pascal的
Write函数重定向到你的自定义打印端口。 - 参与社区:Hackaday.io、RC2014论坛等复古计算社区非常活跃。分享你的修改,提出你遇到的问题。你遇到的难题,很可能别人已经解决过;你的独特创意,也可能激发其他人的新项目。
定制CP/M BIOS,本质上是一场与计算机历史的深度对话。你不仅是在配置一个开发环境,更是在学习一种早已融入计算机设计哲学的理念——系统的每一层都应对开发者透明、可控。当你在命令行中输入DIR,而屏幕上的列表流畅显示时,背后是你亲手编写或修改过的代码在驱动硬件。这种从底层掌控系统的成就感,是当今许多抽象层次极高的开发环境所无法给予的。拿起你的编辑器,开始这场穿越时空的编程之旅吧,下一个被优化的系统特性,也许就来自你的灵感。