1. 项目概述与核心价值
在嵌入式系统开发的早期,资源受限是常态。微控制器(MCU)的I/O口、电源、通信接口都极为宝贵。今天要分享的这个项目,就是一个在资源极度受限条件下“螺蛳壳里做道场”的经典案例:基于MC68HC05的键盘接口温度计。它的核心思路非常巧妙——利用PC的PS/2键盘接口,同时解决嵌入式设备的供电和通信两大难题。
想象一下,你有一个MC68HC05微控制器和一个DS1820温度传感器,想做一个能向PC报告温度的小设备。常规思路可能需要额外的USB转串口芯片、电平转换电路,还得找个5V电源适配器。但这个项目告诉你,完全不需要。一根标准的PS/2键盘延长线,一头插电脑,另一头接上你的设备,再把自己的键盘插在设备上,电有了,数据通道也有了。电脑上跑个简单的DOS程序(THERMO.EXE),就能像接收键盘输入一样,收到设备发来的温度数据。这种设计对于需要与PC进行简单、低成本数据交互的嵌入式应用(如环境监测探头、简易数据采集器)具有很高的参考价值。
整个固件的核心,就是让MC68HC05这颗简单的8位MCU,同时扮演好三个角色:一个PS/2协议分析器,一个单总线主机,以及一个键盘模拟器。下面,我们就来深入拆解这套固件的设计思路、实现细节,以及我在复现和调试过程中积累的一些实战经验。
2. 系统架构与核心模块设计思路
这个键盘温度计固件,从功能上清晰地划分为三个模块,但它们在逻辑和时序上紧密耦合。理解这个架构,是读懂后续所有代码和操作细节的基础。
2.1 模块交互与总体工作流
系统上电后,MCU完成初始化,然后便进入一个主循环等待状态。此时,键盘与PC之间的通信是直通的,设备处于“监听”模式。整个工作流由PC主机主动触发:
- 等待激活:PC端的
THERMO.EXE程序启动后,会通过键盘接口向键盘发送两次ECHO命令(命令码0xEE)。固件的激活信号采集模块持续监听PS/2数据线,专门捕捉这个特定的“两次ECHO”序列。只有正确识别到这个“暗号”,设备才认为主机发出了数据请求,从而启动温度测量流程。 - 采集温度:一旦激活,温度采集与转换模块开始工作。它通过严格的单总线时序,向DS1820传感器发送复位、跳过ROM、启动温度转换、读取暂存器等命令,获取原始的9位温度数据。
- 格式化与发送:获取的原始温度数据(例如,代表25.5°C的二进制值)需要转换成人类可读的ASCII字符,并进一步转换成PC键盘能理解的扫描码(Scan Code)。键盘接口模块负责将格式化后的扫描码数组,按照PS/2设备到主机的通信协议,一位一位地“敲”给PC。PC接收到这些扫描码后,会如同用户真的在键盘上输入了“25.5”一样,将其传递给
THERMO.EXE程序显示出来。
这个流程的精妙之处在于非侵入性。在设备不活动时,键盘与PC通信无阻;在设备活动时,它通过电子开关(原理图中的4066模拟开关)暂时接管数据线,发送完数据后立即释放,键盘功能不受影响。
2.2 硬件接口与资源分配解析
看原理图(Appendix A)是理解硬件约束的关键。MC68HC705J1A是核心,它的I/O口分配如下:
- PS/2接口侧:
CLOCK_IN,DATA_IN:配置为输入,用于监听主机到键盘的通信(READ_COMMAND)以及键盘到主机的响应(READ_RESPONSE)。CLOCK_OUT,DATA_OUT:配置为输出,用于在设备发送数据时,模拟键盘产生时钟和数据信号(SEND函数)。BUSY,CONTROL:用于控制模拟开关(4066),在设备需要发送数据时,断开键盘与主机的连接,将设备自己的CLOCK_OUT和DATA_OUT接入总线。
- DS1820传感器侧:
DQ:双向数据线,用于单总线通信。需要软件严格模拟复位、读写时序。
- 其他:
- 系统使用4MHz外部振荡器。
- 7407是开集电极缓冲器,用于增强PS/2接口的驱动能力,并实现电平的兼容。
注意:PS/2协议是双向、开集电极(Open-Collector)的。这意味着任何设备(主机或键盘)只能将线拉低(逻辑0),释放时由上拉电阻拉高(逻辑1)。因此,在代码中,无论是监听还是驱动,都需要注意“线与”逻辑。驱动时,输出0是主动拉低,输出1实际上是释放总线(高阻态),由上拉电阻拉高。
3. 核心模块一:激活信号采集模块深度解析
这个模块是设备与主机建立联系的“握手协议”实现者。它不能干扰键盘正常通信,又要能精准捕捉到主机发来的特定指令序列。
3.1 协议基础与激活序列设计
标准PS/2通信中,主机向键盘发送一个命令(如0xEEECHO),键盘必须回复一个应答(对于ECHO命令,回复也是0xEE)。THERMO.EXE程序连续发送两个ECHO命令,并期待收到两个ECHO响应。
固件中的CONTACT函数就是为此而生。它循环调用READ_COMMAND和READ_RESPONSE,寻找连续的“命令0xEE-> 响应0xEE-> 命令0xEE-> 响应0xEE”序列。为什么是两次?主要是为了抗干扰。键盘在正常使用中也可能产生数据,单次匹配到0xEE有可能是巧合。连续两次完美匹配的概率极低,这大大提高了激活的可靠性。
3.2 READ_COMMAND函数:主机到设备通信的监听器
READ_COMMAND函数的任务是解析一次完整的主机到键盘的数据帧。PS/2协议的一帧数据包含:
- 起始位(总是0)
- 8个数据位(LSB first)
- 奇校验位
- 停止位(总是1)
- 键盘的应答位(ACK,低电平)
函数实现采用了典型的状态机和超时保护思想,这是嵌入式裸机编程的精华。
关键步骤与代码剖析:
等待起始条件:主机发送数据前,会先把时钟线拉低至少100µs,然后把数据线拉低。
READ_COMMAND首先等待DATA_IN变低,并检查此时CLOCK_IN是否为高(主机控制时钟线为低是发送起始信号,如果时钟线已经是低,可能是异常)。WAIT4COMMAND BRSET DATA_IN,PORTA,WAIT4COMMAND ; 等待数据线变低 BRSET CLOCK_IN,PORTA,READ_CMD_ERROR ; 如果此时时钟线是高,错误位采样与超时:之后,函数等待时钟线变高(上升沿),在上升沿后延迟约10µs(代码中的
GET_BIT_DELAY循环),再读取数据线的状态。这里的关键是每一个等待时钟沿的循环,都必须加入超时判断。例如,等待时钟变高的循环:LDA #$48 ; 超时计数器 WAIT4CLOCKHI BRSET CLOCK_IN,PORTA,STARTBITCLOCK ; 时钟变高则跳走 DECA BEQ READ_CMD_ERROR ; 计数到0则超时错误 BRA WAIT4CLOCKHI超时值(如
#$48)需要根据MCU时钟周期精确计算,确保覆盖协议允许的最大间隔,又不会让MCU死等。这是保证系统鲁棒性的生命线。校验与帧校验:8位数据接收完毕后,代码会计算收到的奇校验位是否正确,并检查停止位是否为1,以及键盘是否回送了ACK(低电平)。任何一步出错,都会设置错误标志(
INC FLAG)并退出。
3.3 READ_RESPONSE函数:设备到主机通信的监听器
READ_RESPONSE与READ_COMMAND类似,但监听的是键盘到主机的通信。区别在于帧结构:键盘到主机的数据帧没有ACK位。函数流程变为:等待起始位->读取8位数据+奇校验位->检查停止位。
实操心得:在调试这个模块时,最头疼的就是时序问题。逻辑分析仪是必备工具。你需要同时抓取主机时钟、主机数据、键盘数据(如果可能)以及MCU的某个GPIO(用于打点标记代码执行到何处)。通过对比抓到的波形和代码中预期的时序(比如10µs的采样点),才能定位是超时设置太短,还是中断干扰了延时循环。在没有逻辑分析仪的情况下,可以尝试用GPIO翻转来“模拟”一个简单的波形,用示波器观察,辅助判断代码执行到哪个阶段卡住了。
4. 核心模块二:温度采集与转换模块实战要点
这个模块负责与DS1820单总线温度传感器通信。单总线协议以其节省IO口著称,但代价是极其严格的时序要求,所有时序都由MCU软件模拟。
4.1 单总线协议与DS1820操作序列
DS1820的典型操作序列是:初始化(复位->存在脉冲)->发送ROM命令(如SKIP ROM [0xCC])->发送功能命令(如CONVERT T [0x44]或READ SCRATCHPAD [0xBE])->读写数据。
固件中的ACQUIRE_TEMP函数清晰地体现了这个流程:
RESET_1820: 发送480-960µs的低电平复位脉冲,然后释放总线并等待60-240µs,检测DS1820回应的60-240µs低电平存在脉冲。- 发送
SKIP ROM [0xCC]命令(因为总线上只有一个传感器,可以跳过寻址)。 - 发送
CONVERT T [0x44]命令启动温度转换。对于DS1820,转换时间最多可达750ms。代码采用轮询方式,不断发送读时隙,直到读到非0xFF值,表示转换完成。这是一种简单有效的等待方式。 - 再次复位,发送
SKIP ROM和READ SCRATCHPAD [0xBE]命令。 - 连续读取两个字节(
READ_1820),得到9位的温度值(低字节LSB,高字节MSB的bit0是小数部分)。
4.2 底层读写时序的软件模拟
WRITE_1820和READ_1820函数是时序模拟的核心。
写时隙:MCU将总线拉低至少1µs,然后根据要写的是“1”还是“0”,在15µs内释放总线(写1)或继续保持低电平60µs(写0)。整个时隙持续60-120µs。
WRITE_ZERO BCLR DQ,PORTA ; 拉低总线开始写时隙 JSR DELAY_80µS ; 保持低电平约80µs(写0) BSET DQ,PORTA ; 释放总线 BRA DEC_WRITE WRITE_ONE BCLR DQ,PORTA ; 拉低总线至少1µs NOP ; 几个NOP指令实现短暂延时 NOP NOP BSET DQ,PORTA ; 很快释放总线(写1) JSR DELAY_80µS ; 等待时隙结束这里的
DELAY_80µS延时子程序需要根据4MHz主频精确计算指令周期来编写。读时隙:MCU发起读时隙(拉低总线至少1µs后释放),然后在15µs内采样总线状态。DS1820会在MCU拉低总线后,在15-60µs的时间窗口内将总线拉低(表示0)或保持高(表示1)。
READ_BIT BSET DQ,PORTA BSET DQ_CTRL,DDRA ; 确保DQ为输出模式 BCLR DQ,PORTA ; MCU拉低总线至少1µs NOP ; 短暂延时 NOP NOP NOP NOP BCLR DQ_CTRL,DDRA ; 切换DQ为输入模式,释放总线 BRSET DQ,PORTA,READ_ONE ; 延时后采样总线 CLC ; 读到0 BRA READ_SHIFT READ_ONE SEC ; 读到1
避坑指南:
- 延时精度:单总线对时序宽容度很小。所有
DELAY_xxx函数必须用汇编语言精确实现,考虑所有指令周期。使用C语言循环通常难以满足要求。 - 总线恢复:每次读写操作后,必须保证MCU将总线控制权释放(设置为输入或输出高电平),并由上拉电阻拉高,为下一次操作做好准备。
- 中断干扰:如果系统开启了中断,在操作单总线的关键时序段(如
READ_1820,WRITE_1820)必须禁用中断,否则一个中断服务程序的执行可能彻底破坏时序,导致通信失败。本固件作为简单示例可能未考虑,但在复杂系统中是必须处理的。
5. 核心模块三:键盘接口模块与数据发送
这是设备“说话”的模块,负责将温度值“敲”进电脑。核心是将温度数值转换为PS/2扫描码,并模拟键盘发送。
5.1 温度数据到扫描码的转换
FORMAT_TEMP函数完成了从原始9位温度数据到扫描码数组的转换。DS1820的温度数据格式是:低字节的bit0为0.5°C,高字节为符号位和整数部分。
例如,0x0191(二进制 0000 0001 1001 0001)表示 +25.0625°C?这里需要仔细核对DS1820数据手册。实际上,DS1820的默认分辨率是9位,格式为:符号位S(高字节bit7),高字节低7位和低字节bit7组成8位整数部分,低字节bit0是0.5°C,bit1-3是0.125, 0.0625等。但本固件似乎只处理了0.5°C精度(检查ODD_MULTIPLE标志位),将温度转换为带一位小数的字符串。
转换步骤:
- 判断正负,负数则在发送缓冲区存入减号“-”的扫描码(
MINUS=$4E),并对数据取补码。 - 分离整数部分和小数部分(0或0.5)。
- 将整数部分除以10,分离十位和个位。
- 通过查表
SCAN_TABLE,将十位、个位数字转换为对应的通码(Make Code)。例如,数字‘2’的通码是$1E。 - 根据小数部分,存入小数点“.”的扫描码(
POINT=$49)和“5”(FIVE=$2E)或“0”(ZERO=$45)的扫描码。 - 在缓冲区末尾存入结束符
END($5A,即回车键的扫描码?这里$5A是数字小键盘回车?通常主回车是$5A,但需要确认)和停止符$FF。
5.2 模拟键盘发送:SEND与SEND_BYTE函数
SEND_BYTE是高层发送函数,它处理了PS/2协议中的错误重传机制。流程如下:
- 通过控制
BUSY和CONTROL信号,断开键盘,连接设备到总线。 - 调用底层
SEND函数发送一个字节。 - 如果发送成功(PC未拉低时钟线表示错误),则结束。
- 如果发送失败,设备会等待并调用
RECEIVE函数,尝试接收主机发来的RESEND命令(0xFE)。 - 如果收到
RESEND,则重新发送原数据;否则报错。
SEND函数是真正的“比特爆破(Bit Banging)”实现者,它严格按照PS/2设备到主机的时序,用软件控制CLOCK_OUT和DATA_OUT引脚:
- 设备在发送前,需要检查时钟线是否为高(主机允许发送)。
- 设备先将数据线拉低,然后产生一个时钟脉冲(低-高-低),作为起始位。
- 接着,从LSB开始,依次发送8个数据位和1个奇校验位。每个位都是在时钟线为低时准备好数据,然后产生一个时钟脉冲。
- 发送停止位(高)。
- 释放总线,等待主机拉低时钟线作为应答(ACK),然后主机再释放时钟线。代码中通过
PC_BUSY和STILL_BUSY循环等待主机处理。
关键时序参数(来自PS/2协议):
- 时钟频率:10-16.7 kHz
- 每个时钟周期:60-100 µs
- 数据在时钟下降沿变化,在时钟上升沿被采样。
- 设备在发送前,必须检查时钟线在高电平状态至少50µs。
代码中的HALF_CLOCK和FULL_CLOCK延时循环,就是用来产生符合标准的时钟脉冲宽度。
5.3 主机端程序THERMO.EXE的角色
固件设计是与主机程序THERMO.EXE紧密配合的。该程序大概做了以下几件事:
- 向键盘接口发送特定的激活序列(两次ECHO)。
- 持续监听键盘输入,将接收到的扫描码转换为ASCII字符。
- 将转换后的字符串(如“25.5”)在DOS界面显示出来。
- 提供简单的用户交互(如等待用户按Q退出)。
它的流程图(Appendix B)清晰地展示了这个交互过程:发送激活序列->等待/接收温度字符串->显示->等待用户输入->循环。
6. 固件源代码的关键例程与调试技巧
翻阅附录D的完整源码,除了上述主要函数,还有一些支撑性的例程和全局设计值得关注。
6.1 全局变量与内存规划
在RAM的起始部分,定义了一系列变量:
DATA,FLAG: 通用数据寄存器和状态标志寄存器。TX_BUFFER: 发送缓冲区,存放格式化后的扫描码序列。TEMP_HI,TEMP_LO: 存放从DS1820读取的原始温度数据。- 各种临时变量和掩码常量。
在资源紧张的MC68HC705J1A上(RAM可能只有64或128字节),这种清晰的内存规划至关重要。FLAG变量的各个位被复用为不同函数的错误标志,节省了空间。
6.2 延时子程序的艺术
所有时序都依赖于精确的软件延时。源码最后一部分是延时子程序集,如DELAY_80µS,DELAY_500µS,FULL_CLOCK,HALF_CLOCK等。它们都是用汇编指令(NOP,DECA,BNE)构成的紧密循环。
计算延时:以4MHz时钟为例,一个机器周期是0.25µs(假设内部2分频)。一个NOP是2个机器周期(0.5µs)。DELAY_80µS的循环体是NOP; NOP; NOP; DECA; BNE,需要计算循环次数A的初始值,使得总时间约为80µs。这需要根据指令集手册精确计算,是嵌入式汇编编程的基本功。
6.3 调试方法与问题排查实录
开发此类底层驱动,调试是最大的挑战。以下是我总结的排查思路:
问题:设备完全无反应,PC程序收不到数据。
- 排查电源:首先用万用表测量PS/2接口的+5V和GND是否正确接入MCU和传感器。PS/2接口的供电能力有限(约100-200mA),确保整个电路功耗在其范围内。
- 排查激活:在
CONTACT函数入口设置一个LED闪烁或GPIO翻转。观察设备是否成功进入了激活序列检测。如果没有,问题可能在READ_COMMAND对主机信号的监听上。用逻辑分析仪抓取主机发送的两个ECHO命令序列,与代码逻辑对比。 - 排查硬件连接:检查原理图中的模拟开关(4066)和控制信号(
BUSY,CONTROL)逻辑是否正确。设备在监听时,开关应连接键盘到主机;在发送时,应切换为设备到主机。
问题:能激活,但温度读数全是0或错误。
- 排查DS1820通信:在
ACQUIRE_TEMP函数的RESET_1820、WRITE_1820、READ_1820等关键点设置调试信号。用示波器观察DQ线上的波形,与DS1820数据手册的时序图对比。最常见的问题是延时不准或中断干扰。 - 检查传感器连接:DS1820的数据线需要接一个4.7kΩ的上拉电阻到VCC。确保连接可靠。
- 排查DS1820通信:在
问题:能激活,温度读取似乎正确,但PC端收到乱码或部分字符。
- 排查扫描码转换:在
FORMAT_TEMP函数结束后,检查TX_BUFFER数组里的内容。对照PS/2扫描码集,看转换是否正确。例如,数字‘1’的扫描码是否是$16。 - 排查发送时序:用逻辑分析仪同时抓取设备发送时的
CLOCK_OUT和DATA_OUT信号。测量时钟周期、数据建立和保持时间是否符合PS/2规范。特别注意设备发送前,是否等待了足够长的时间(>50µs)确保时钟线为高。 - 排查PC端程序:确认
THERMO.EXE程序是否与固件预期的扫描码集匹配(通常是XT/AT扫描码集)。也可以用一个简单的键盘监听程序,看看设备发出的原始扫描码是什么。
- 排查扫描码转换:在
问题:发送数据导致键盘锁死或PC异常。
- 检查总线竞争:确保在设备发送数据前,
BUSY和CONTROL信号已有效断开了键盘。发送完成后,必须及时恢复键盘连接。逻辑分析仪可以清晰显示切换过程。 - 检查错误处理:
SEND_BYTE函数中的错误重发逻辑是否健全?如果PC一直不发RESEND命令,设备是否能在超时后安全退出,恢复键盘连接?
- 检查总线竞争:确保在设备发送数据前,
一个实用的调试技巧:利用未使用的I/O口。例如,可以在每个主要函数入口、出口,或关键判断分支处,给某个I/O口一个不同的脉冲(如短脉冲、长脉冲、双脉冲)。用示波器观察这个“调试通道”,就能像单步执行一样,了解代码的执行流卡在了哪里。这比点灯更高效。
7. 项目总结与扩展思考
回顾这个基于MC68HC05的键盘温度计项目,其核心价值在于展示了一种极简主义的系统集成思想。它不增加任何额外的接口芯片,仅凭MCU的GPIO和软件智慧,就巧妙地“寄生”在成熟的PS/2生态系统上,实现了数据与电源的获取。
从今天的视角看,MC68HC05早已不是主流,PS/2接口也日渐稀少。但这个项目所蕴含的协议分析、时序模拟、资源管理、软硬件协同调试等技能,依然是嵌入式开发的基石。你可以将这套思路迁移到其他场景:
- 协议替换:将PS/2接口换成USB。虽然USB协议复杂得多,但可以使用像V-USB这样的软件USB实现,或者更简单的,用一个USB转串口芯片(如CH340),MCU通过UART发送数据,主机端用虚拟串口接收。这更符合现代硬件环境。
- 传感器扩展:DS1820可以替换为DHT11(温湿度)、DS18B20(更高精度温度)或其他I2C、SPI传感器。只需修改对应的驱动函数。
- 功能扩展:设备可以不止上报温度。可以做成一个多功能键盘侧边小工具,上报传感器数据、自定义宏按键状态等。
- 低功耗优化:原设计MCU可能一直处于运行状态。可以修改为:平时MCU进入低功耗休眠模式,通过PS/2时钟线或数据线的边沿变化(需配置为外部中断)来唤醒,然后执行一轮测量和发送,再继续休眠,大大降低整体功耗。
最后,想强调一点:阅读和理解这类“古老”的应用笔记和源码,最大的收获不是学会如何操作一个特定的过时芯片,而是学习前辈工程师在严苛限制下解决问题的架构思维和实现技巧。这些思维和技巧,在你面对任何资源受限的嵌入式场景时,都会成为你工具箱里最宝贵的资产。动手复现它,哪怕是在模拟器上,你会对“位操作”、“状态机”、“时序”这些概念有刻骨铭心的理解。