V20-MBC复古单板机CP/M-2.2下8080汇编控制LED与GPIO实战
1. 项目概述与硬件平台解析如果你对复古计算或者自己动手捣鼓单板机感兴趣那么V20-MBC这个名字你或许不陌生。它是一款基于NEC V20处理器兼容Intel 8088/8086但也能运行8080代码设计的开源单板计算机。我最近在折腾这台小机器尤其是在它运行经典的CP/M-2.2操作系统时用最底层的8080汇编语言去直接“对话”硬件比如点亮一个LED或者读取一个开关状态这种体验非常纯粹能让你真切地感受到计算机是如何从最基础的电子信号开始工作的。这不仅仅是怀旧对于理解现代嵌入式系统的底层原理比如GPIO、内存映射I/O甚至是操作系统的引导过程都大有裨益。本文要分享的就是我在V20-MBC上运行CP/M-2.2时编写的两个8080汇编程序实例。它们的目标很简单一个程序控制板载的用户LED另一个程序则通过GPIO接口控制一个外接的LED。代码本身不长但麻雀虽小五脏俱全完整涵盖了从源代码编写、汇编、链接到最终在CP/M下运行的全过程。无论你是想重温80年代的程序开发氛围还是希望为学习更复杂的嵌入式系统打下坚实的硬件控制基础这两个例子都是一个绝佳的起点。接下来我会详细拆解代码的每一行解释其背后的硬件原理并分享在实操过程中可能遇到的“坑”以及如何避开它们。2. V20-MBC硬件架构与CP/M-2.2环境搭建2.1 V20-MBC核心硬件剖析要写汇编程序控制硬件第一件事就是得搞清楚你的“战场”地图。V20-MBC虽然结构紧凑但该有的都有。其核心是NEC V20 CPU这是一颗非常有趣的芯片它硬件兼容8088但通过一个内置的“8080仿真模式”能够直接执行8080的机器码。我们的程序正是利用了这一特性在CP/M-2.2这个为8080/Z80设计的操作系统上运行。对于硬件控制而言最关键的是它的I/O映射。V20-MBC通过一块Zilog Z85C50 SCC串行通信控制器芯片提供串口这是我们与它交互的窗口。而我们要控制的用户LEDUSER LED和用户按键USER SWITCH以及扩展的**GPIOGeneral Purpose Input/Output**端口通常是通过其他I/O芯片如8255 PPI或者直接由CP/M的BIOS基本输入输出系统提供了软件接口。在CP/M下我们一般不直接操作物理内存地址而是通过BDOS基本磁盘操作系统或BIOS的系统调用来实现功能但为了极致性能和直接控制有时也会知道特定的I/O端口地址。注意在复古系统上硬件信息往往散落在原理图、BIOS源代码或社区文档中。对于V20-MBC最关键的文件是项目主页的A250220-sch.pdf原理图以及CP/M-2.2 BIOS的源代码。你需要从中查找USER LED、USER SWITCH和GPIO对应原理图中的J7接头所对应的CP/M BIOS功能调用号或具体的I/O端口地址。这是所有后续编程的基础。2.2 CP/M-2.2开发环境配置CP/M-2.2是一个磁盘操作系统我们的开发工作就在其命令行环境下进行。你需要准备以下环境硬件连接通过USB转TTL串口适配器将你的现代电脑与V20-MBC的串口连接起来。使用如PuTTY、Tera Term、minicom等终端软件设置正确的串口号、波特率通常是9600 8N1进行连接。文件传输这是开发的第一步。你有几种选择手动输入最复古的方式就像80年代从杂志上抄代码一样适用于极短的程序。但对于稍长的代码极易出错。XMODEM传输CP/M系统通常自带或可以安装XMODEM协议传输工具。在终端软件中启动发送文件功能选择XMODEM协议然后在V20-MBC的CP/M命令行下运行相应的接收命令如XD。这是最常用的方式之一。通过SD卡V20-MBC从SD卡启动并模拟磁盘驱动器。你可以使用现代电脑配合cpmtools工具包直接将文件写入SD卡镜像的特定“磁盘”中然后将SD卡插回V20-MBC即可访问。汇编器CP/M-2.2下常用的8080汇编器是ASM.COMDigital Research的宏汇编器或M80.COMMicrosoft的宏汇编器。你需要确认你的V20-MBC CP/M镜像中已经安装了其中之一。通常它们位于A驱动器系统盘。我个人的经验是优先建立XMODEM传输渠道。它虽然速度不快但最直接能让你快速进入“编辑-传输-测试”的循环。准备好一个简单的文本编辑器如ED.COM或TE.COM来编写你的汇编源代码.ASM文件。3. 实例一控制板载用户LED与读取按键这个例子展示了如何与板载的最简单硬件交互一个LED和一个按键。它不涉及外部接线是测试环境是否就绪的完美第一课。3.1 代码逐行解析与原理假设我们通过研究V20-MBC的BIOS源码得知控制用户LED和读取用户按键的功能可以通过CP/M的BDOS系统调用来实现。BDOS调用通过将功能号放入C寄存器然后执行CALL 5指令来发起。; ; LED.ASM - V20-MBC User LED Switch Test ; 目标循环闪烁用户LED当用户按键按下时退出 ; BDOS EQU 5 ; CP/M BDOS入口地址 CONIO EQU 6 ; 直接控制台I/O BDOS功能号 ORG 0100H ; CP/M .COM程序标准起始地址 START: MVI C, CONIO ; 设置BDOS功能号直接控制台I/O MVI E, 0FFH ; 向控制台输出字符0FFH表示读状态 ; 但这里我们可能用特定参数控制LED CALL BDOS ; 注意以上是假设的调用方式。实际需要查阅BIOS文档。 ; 假设功能号CONIO的一个特定子功能或参数能控制LED。 ; 下面我们写一个更通用的“伪代码”逻辑流程。 MAIN_LOOP: ; 步骤1点亮LED MVI C, LED_ON_FUNC ; 假设的“LED开”功能号 CALL BDOS ; 步骤2延时一段时间 CALL DELAY ; 步骤3熄灭LED MVI C, LED_OFF_FUNC ; 假设的“LED关”功能号 CALL BDOS ; 步骤4再次延时 CALL DELAY ; 步骤5检查用户按键是否被按下 MVI C, KEY_CHECK_FUNC ; 假设的“检查按键”功能号 CALL BDOS ORA A ; 检查A寄存器返回值非零表示按下 JZ MAIN_LOOP ; 如果为零未按下继续循环 ; 步骤6按键被按下程序退出返回CP/M RET ; 简单的软件延时子程序 DELAY: PUSH B PUSH D LXI B, 0FFFFH ; 设置外层循环计数器 DELAY_LOOP1: LXI D, 0100H ; 设置内层循环计数器 DELAY_LOOP2: DCX D MOV A, D ORA E JNZ DELAY_LOOP2 DCX B MOV A, B ORA C JNZ DELAY_LOOP1 POP D POP B RET END START核心原理剖析ORG 0100H这是CP/M下可执行.COM文件的强制要求。程序会被加载到内存地址0100H开始处前面的256字节是CP/M的程序段前缀PSP。BDOS调用CALL 5是CP/M程序与操作系统交互的标准方式。具体做什么由调用前C寄存器中的功能号决定。我们的任务就是找到控制LED和读取按键的正确功能号。这些功能通常由V20-MBC的定制BIOS提供并非标准CP/M BDOS的一部分。软件延时在缺乏硬件定时器的简单控制中我们使用双重循环消耗CPU时间来制造延时。LXI B, 0FFFFH和LXI D, 0100H的值决定了延时长短需要根据实际CPU频率调整。V20-MBC的V20 CPU频率可能在4-8MHz左右但运行在8080仿真模式速度会有所不同需要实测调整。循环与退出程序主体是一个无限循环交替开/关LED并检查按键。一旦检测到按键动作通过BDOS调用返回非零值便用RET指令结束程序控制权交回CP/M命令行。实操心得在查找正确的BIOS功能号时最可靠的方法是阅读V20-MBC的CP/M BIOS源代码。如果找不到可以尝试在社区论坛搜索或者用“笨办法”写一个小测试程序遍历一些可能的功能号或I/O端口观察LED的反应。务必记录下找到的正确地址或功能号这是你项目的宝贵资产。3.2 汇编、链接与运行流程假设我们已经找到了正确的功能号并将源代码保存为LED.ASM并且通过XMODEM将其传到了V20-MBC的H驱动器。汇编使用CP/M下的汇编器将源代码转换为目标文件。以ASM.COM为例。Ha:asm led.asm如果汇编成功将生成LED.HEXIntel Hex格式和LED.PRN列表文件用于调试。生成COM文件CP/M直接运行的是.COM文件。我们需要将HEX文件转换为COM文件。通常使用LOAD.COM或HEXCOM工具。Ha:load led.hex或者如果汇编器直接生成COM文件有些汇编器如M80配合L80链接器可以则此步省略。执行后生成LED.COM。运行Hled此时你应该看到板载的用户LED开始闪烁。按下板载的用户按键程序应停止并返回到H提示符。常见问题与排查汇编错误仔细检查错误信息。8080汇编语法严格标点符号逗号、空格、标号定义和使用必须一致。列表文件.PRN能帮你定位错误行。程序运行无反应首先确认程序是否真的在运行CPU是否在跑。可以尝试在代码最开始加一个向串口输出字符的功能例如使用BDOS功能2输出一个*来验证程序是否被执行。如果LED不闪九成是控制LED的功能号或I/O端口地址不对。LED常亮或常灭检查你的LED控制序列。可能是“开”和“关”的指令反了或者是延时太短/太长导致肉眼无法分辨闪烁。调整DELAY子程序中的循环计数值。按键无法退出同理检查读取按键的功能号或端口地址以及判断返回值是否正确。有些硬件设计是低电平有效按下为0你的判断逻辑JZ还是JNZ就需要相应调整。4. 实例二通过GPIO控制外部LED这个例子更进一步我们通过V20-MBC的GPIO接头通常是J7来控制一个外接的LED。这涉及到基本的电子知识限流电阻。4.1 硬件连接与电路原理根据原理图J7 GPIO接口的某个引脚例如Pin 8被配置为输出。我们需要将一个LED和限流电阻串联后接在这个引脚和地GND例如Pin 20之间。接线示意图V20-MBC GPIO (J7) Pin 8 (输出) --- LED阳极长脚 --- 680Ω电阻 --- GND (Pin 20)重要警告必须使用限流电阻直接连接LED到电源和地之间会瞬间烧毁LED。电阻值取决于GPIO引脚的高电平电压很可能是5V和LED的工作电流通常5-20mA。以5V、期望电流10mA计算电阻R (5V - LED压降约2V) / 0.01A ≈ 300Ω。使用680Ω是更保守、安全的选择电流约4.4mALED亮度足够且寿命更长。4.2 汇编代码实现深度解析控制GPIO通常有两种方式1) 通过CP/M BIOS提供的更高级的GPIO控制功能2) 直接读写GPIO芯片的I/O端口。后者更底层性能也更高。我们假设通过查阅资料找到了GPIO数据端口的地址例如PORT_GPIO_DATA EQU 0F2H。; ; GPIO.ASM - V20-MBC GPIO LED Blink Test ; 目标通过GPIO引脚循环闪烁外接LED ; 假设GPIO数据端口地址为 0F2H Pin8对应数据位 D0 ; GPIO_DATA EQU 0F2H ; GPIO数据端口地址需根据实际硬件修改 DELAY_COUNT EQU 0FFFFH ; 延时外循环次数 ORG 0100H START: ; 初始化设置GPIO引脚为输出模式如果需要 ; 有些GPIO芯片需要先配置方向寄存器这里假设已默认配置为输出 ; MVI A, 01H ; 假设方向寄存器地址是GPIO_DATA1写入01h使D0为输出 ; OUT GPIO_DATA1 ; 实际情况请查阅硬件手册 MAIN_LOOP: ; 将GPIO的D0位置1输出高电平点亮LED MVI A, 01H OUT GPIO_DATA CALL DELAY ; 将GPIO的D0位置0输出低电平熄灭LED MVI A, 00H OUT GPIO_DATA CALL DELAY ; 检查控制台是否有键按下BDOS功能60FFH为读状态 MVI C, 6 MVI E, 0FFH CALL 5 ORA A ; 检查A寄存器非0表示有键 JZ MAIN_LOOP ; 无键按下继续循环 ; 有键按下退出前确保LED熄灭可选良好习惯 MVI A, 00H OUT GPIO_DATA RET ; 延时子程序与LED.ASM类似可调整 DELAY: PUSH B PUSH D LXI B, DELAY_COUNT DELAY_LOOP1: LXI D, 0080H ; 内循环次数调整闪烁频率 DELAY_LOOP2: DCX D MOV A, D ORA E JNZ DELAY_LOOP2 DCX B MOV A, B ORA C JNZ DELAY_LOOP1 POP D POP B RET END START代码关键点解读OUT指令这是8080汇编中向I/O端口输出数据的关键指令。OUT GPIO_DATA会将累加器A中的值01H或00H发送到地址为GPIO_DATA的端口。硬件电路会据此改变对应引脚的电平。位操作代码中我们只操作了A寄存器的最低位D0。如果GPIO端口是8位的那么A寄存器的每一位D0-D7可能对应一个物理引脚。通过改变A的值如01H-00H我们只改变D0不影响其他位。键盘监听退出这里使用了标准的CP/M BDOS功能6直接控制台I/O来非阻塞地检查是否有按键。MVI E, 0FFH是输入状态检查的参数。如果有键按下A寄存器返回非零字符码程序退出。硬件依赖GPIO_DATA的端口地址0F2H是示例假设必须替换为V20-MBC实际使用的地址。这个信息同样需要从原理图或BIOS源码中挖掘。4.3 完整的开发、调试与优化流程获取与修改源码从GitHub下载gpio.asm示例。第一件事就是打开文件找到GPIO_DATA的定义行将其修改为从V20-MBC资料中查到的正确端口地址。如果找不到确切地址可以尝试在0F0H到0FFH范围内进行测试很多老式系统将I/O端口设在这个范围但务必小心。汇编与运行过程与LED示例完全相同。Ha:asm gpio.asm Ha:load gpio.hex Hgpio如果连接正确且端口地址正确外接的LED应该开始闪烁。终端会显示提示信息如果代码中有按任意键退出。调试技巧万用表是你的好朋友如果LED不亮先用万用表测量GPIO引脚在程序运行时的电压是否在0V和~5V之间跳变。如果没有变化说明软件控制未生效端口地址错或芯片未初始化。使用逻辑分析仪或示波器可以精确观察引脚电平变化的时间和波形确认延时是否准确输出序列是否正确。软件仿真在开始硬件实操前可以使用如SIMH或z80pack等模拟器运行CP/M和你的程序验证逻辑正确性。虽然无法模拟真实的GPIO硬件但能排除语法和流程错误。加入调试输出在关键代码段前后插入向串口输出特定字符如A、B的BDOS调用。通过终端观察程序执行流这是复古调试的经典方法。性能优化考虑延时精度软件延时受CPU频率影响大不精确。对于需要精确定时的应用应查询V20-MBC是否有可用的硬件定时器/计数器如8253/8254芯片并通过中断或查询方式使用它。代码精简循环中的OUT指令每次都要重新加载A寄存器。可以优化为只改变特定位。例如如果想翻转D0位可以使用XOR指令; 在循环开始前初始化 MVI A, 01H ... LOOP: OUT GPIO_DATA CALL DELAY XRI 01H ; 翻转D0位 (0-1) JMP LOOP复用代码将DELAY子程序做得更通用可以通过寄存器传递延时参数方便不同地方调用不同长度的延时。5. 从示例到项目扩展思路与进阶挑战掌握了这两个基本示例后你已经拿到了打开V20-MBC硬件控制大门的钥匙。但这仅仅是开始你可以沿着多个方向进行扩展打造更有趣的项目多LED控制与流水灯利用GPIO端口的8个数据位连接8个LED编写程序实现流水灯、呼吸灯通过PWM模拟、二进制计数器显示等效果。这需要你熟练掌握8080的位操作指令ANI,ORI,XRI,RLC,RRC等。读取数字输入将GPIO引脚配置为输入连接拨码开关或按钮。编写程序读取开关状态并根据状态改变LED行为。这涉及到IN指令读取端口以及去抖动处理软件延时或硬件电路。驱动字符型LCD显示器这是经典的嵌入式项目。通过GPIO模拟或使用并口连接一个1602 LCD实现字符显示。你需要编写底层驱动程序发送命令和数据并处理繁忙标志检查。这会极大地锻炼你的时序控制能力和协议理解。制作一个简单的音乐播放器利用一个GPIO引脚连接一个小喇叭或蜂鸣器通过程序控制输出不同频率的方波就能产生简单的音乐。你需要计算音符频率对应的延时周期并组织乐谱数据。与CP/M系统深度集成将你的硬件控制功能封装成一个CP/M的“设备驱动”或TSR终止并驻留程序。例如写一个程序让用户按键成为系统级的“热键”或者让LED指示磁盘活动状态。进阶挑战与资源理解中断V20-MBC的硬件可能支持中断。学习如何编写中断服务程序ISR来处理定时器事件、键盘输入等这能使你的程序更高效、更实时。混合编程在CP/M下你可以用更高层的语言如C、Pascal调用汇编子程序或者反过来。这允许你用C处理复杂逻辑用汇编处理对性能或硬件访问要求极高的部分。社区与文档复古计算社区非常活跃。遇到问题时在像hackaday.io、retrocomputing forum等地方搜索或提问往往能得到原作者或资深爱好者的帮助。仔细阅读项目主页的文档、原理图和BIOS源码是解决问题的根本。折腾V20-MBC和CP/M下的8080汇编是一个充满乐趣的学习过程。它强迫你从最底层思考理解每一行代码如何直接转化为电路的动作。这种深刻的理解是使用现代高级语言和集成开发环境难以获得的。从点亮一个LED开始每一步都踏实地前进你会发现这台小小的复古计算机能带给你的知识和成就感远超想象。