基于MC68HC05的PS/2键盘接口温度计:嵌入式协议与位操作实战
1. 项目概述与核心价值在嵌入式开发的早期阶段资源受限的8位微控制器MCU是绝对的主流。那时候没有现成的USB库、没有丰富的通信协议栈工程师需要直接操作硬件寄存器用最基础的“位操作”来与外部世界对话。MC68HC05系列正是那个时代的经典代表以其极致的性价比和可靠性广泛应用于从家电控制到工业仪表的各种场景。今天要聊的这个“键盘接口温度计”项目就是一个非常典型的案例它不依赖任何复杂的通信总线而是巧妙地“劫持”了PC上最普及的接口之一——PS/2键盘接口来实现数据采集和上报。这个项目的核心价值远不止于测量温度。它本质上是一堂生动的“嵌入式系统通信协议”实战课。你将会看到如何让一颗简单的MCU理解并模拟复杂的PS/2双向通信协议如何精确地操作I/O引脚来满足严苛的时序要求以及如何将传感器数据“翻译”成主机能够识别的键盘扫描码。整个过程充满了底层硬件的乐趣与挑战对于理解计算机系统如何与外围设备“交谈”有着莫大的帮助。无论你是想重温经典架构还是学习底层协议的精髓这个基于MC68HC05J1A和DS1820的设计都能提供丰富的养分。2. 系统架构与设计思路拆解2.1 整体方案选型为什么是PS/2键盘接口在为一个简单的温度测量功能选择与PC通信的接口时我们面临多种选择串口RS-232、并口、USB等。然而在资源极其有限的MC68HC05J1A上有限的I/O、内存和时钟速度这些方案都有其局限性。串口需要额外的电平转换芯片如MAX232并占用定时器资源实现UART并口需要较多引脚USB协议则过于复杂。PS/2键盘接口脱颖而出原因有四供电与通信二合一PS/2接口直接提供5V电源和地线省去了为系统单独设计电源的麻烦。协议相对简单它是一种同步、双向、半双工的串行协议只需两根信号线时钟CLK和数据DATA非常适合用MCU的通用I/O口通过“位操作”来模拟。主机兼容性高任何带有PS/2键盘口的PC主要是早期的AT架构和PS/2架构PC都能直接连接无需安装额外驱动。主机将接收到的扫描码视为键盘按键兼容性极好。资源占用极低实现该协议主要依赖对I/O引脚状态的循环检测和精确的软件延时对MCU的定时器、中断等资源消耗很小非常适合MC68HC05这类简单MCU。因此选择PS/2接口作为通信桥梁是一个在成本、复杂度与功能之间取得完美平衡的经典嵌入式设计思路。2.2 核心模块划分与协作流程根据应用笔记的描述整个固件被清晰地划分为三个核心模块它们像流水线一样协同工作激活信号采集模块这是系统的“监听器”和“握手者”。它持续监控PS/2接口的时钟和数据线等待来自PC主机的特定“激活序列”。只有正确接收到这个序列温度计才被“唤醒”并开始工作否则处于低功耗的监听状态。这避免了设备误触发也体现了主机对从设备的控制权。温度采集与转换模块这是系统的“传感器”。一旦被激活该模块负责与DS1820单总线数字温度传感器通信发起温度转换命令读取原始的9位温度数据并进行校验和格式转换为后续的“翻译”工作准备好原料。键盘接口模块这是系统的“翻译官”和“发言人”。它接收来自温度模块的格式化数据将其转换成一串标准的键盘扫描码例如温度“25.5”会被转换成‘2‘, ‘5‘, ‘.‘, ‘5‘, 回车等按键的扫描码。然后它严格按照PS/2设备到主机的通信协议通过“位操作”控制I/O引脚将这串扫描码发送给PC。PC会将其识别为键盘输入从而在光标所在位置如命令行窗口显示出温度值。这三个模块构成了一个完整的“感知-处理-上报”闭环逻辑清晰职责分明。3. 核心细节解析与实操要点3.1 PS/2协议的精髓与固件实现策略PS/2协议是一种基于时钟同步的串行协议。理解其细节是成功实现固件的关键。通信方向与帧结构主机到设备主机通过拉低时钟线至少100µs来发起通信然后交替控制时钟和数据线发送数据帧。一帧数据包括1个起始位总是0、8个数据位LSB先发、1个奇校验位、1个停止位总是1。发送完毕后设备需要在最后一个时钟周期内将数据线拉低作为应答。设备到主机设备通过检测时钟线为高电平时将数据线拉低来请求发送然后主机通过产生时钟信号来读取数据。数据帧格式相同起始位、8数据位、奇校验位、停止位但主机不需要应答。固件实现的核心挑战与策略精确的时序控制协议对时钟频率10-16.7 kHz、数据建立/保持时间5-25 µs有严格要求。在MC68HC05上我们没有硬件SPI或UART必须用软件循环NOP指令和循环递减来实现微秒级的精确延时。代码中的HALF_CLOCK,FULL_CLOCK,DELAY_80µS等延时子程序就是为此而生。计算这些延时循环的次数需要根据MCU的核心时钟频率如4MHz和指令周期数来精确计算。注意软件延时易受中断干扰。在本应用中所有关键通信期间必须禁止中断以确保时序的绝对准确。可靠的边沿检测与超时处理无论是接收还是发送代码都需要在等待时钟或数据线的上升沿/下降沿。一个设计良好的固件绝不能无限期等待。原文中特别强调了“software timeout loop”的重要性。例如在READ_COMMAND函数中每次等待边沿时都有一个计数器递减循环如果超时如等待70µs后边沿仍未到来则置错误标志并退出。这是防止程序“死锁”的必备安全措施。双向通信的引脚管理PS/2的数据线是双向开集电极的。这意味着多个设备可以“线与”。在固件中当MCU作为设备发送数据时需要将对应的I/O引脚配置为输出模式并主动拉低或释放输出高电平依靠上拉电阻变高数据线。当MCU监听主机发送时需要将引脚配置为输入模式以读取电平。代码中通过设置数据方向寄存器DDRA,DDRB和操作数据寄存器PORTA,PORTB来实现这一切换。3.2 DS1820单总线通信的实现要点DS1820是Dallas现Maxim的经典单总线数字温度传感器仅需一根数据线和地线即可完成供电和通信。通信序列复位脉冲主机MCU拉低总线至少480µs然后释放。DS1820会在随后拉低总线60-240µs作为存在脉冲。固件中的RESET_1820函数实现了此过程并检测存在脉冲以确认传感器在线。ROM命令如SKIP ROM(0xCC)用于跳过寻址当总线上只有一个器件时使用。功能命令如开始温度转换CONVERT T(0x44)读取暂存器READ SCRATCHPAD(0xBE)。数据读写读写操作以“时隙”为单位。写“0”时隙主机拉低总线60-120µs写“1”时隙主机拉低总线1-15µs后迅速释放。读时隙主机拉低总线至少1µs然后在时隙早期采样总线电平。固件实现关键严格的时序DS1820对时隙宽度有严格要求。代码中的WRITE_1820和READ_1820函数通过精心编排的NOP和DELAY_80µS等延时来满足这些要求。例如写“1”时隙中拉低总线后仅执行几个NOP就释放然后保持高电平直至时隙结束。电源模式本项目采用寄生供电模式传感器从数据线偷电。在温度转换期间总线必须被强上拉以提供足够电流。虽然原理图中未明确显示强上拉电路但在代码执行CONVERT T命令后需要保持总线为高一段时间转换时间可达750ms。固件通过READ_LOOP循环读取直到DS1820返回非0xFF值表示转换完成。3.3 键盘扫描码的转换与发送逻辑这是将物理量温度转化为PC可理解信息的关键一步。转换流程(FORMAT_TEMP函数)数据处理从DS1820读取的9位温度数据低4位为小数部分0或0.5高5位为整数部分补码形式。函数首先判断正负负数则存储“-”的扫描码并将数值转换为正数。数值分解将整数部分分解为百位、十位、个位。由于DS1820量程通常为-55°C到125°C百位只可能是‘1‘或没有。代码通过比较和减法来处理百位然后通过除以10的循环得到十位和个位的商与余数。查表映射通过一个预定义的SCAN_TABLE数组将数字0-9映射到对应的通码扫描码例如0x16对应‘1‘0x1E对应‘2‘。这是PS/2扫描码集的标准部分。小数处理检查最低有效位判断是否为0.5°C。如果是则在数字后追加“.”和“5”的扫描码否则追加“.”和“0”。构建缓冲区将上述扫描码依次存入TX_BUFFER最后添加一个结束符如0x5A可能是回车键的扫描码和一个停止符0xFF。发送逻辑(SEND_TEMP函数) 函数遍历TX_BUFFER对每个扫描码调用SEND_BYTE。SEND_BYTE是协议实现的核心它暂时断开键盘通过控制模拟开关4066独占总线。调用底层的SEND函数进行“位操作”发送。检查发送后PC的响应。如果PC在接收过程中检测到奇偶校验错误它会发送一个RESEND命令 (0xFE)。SEND_BYTE能接收此命令并重发数据实现了简单的错误恢复机制。每个字符发送后有约1ms的延时 (DELAY_500µS循环两次)模拟人手按键的间隔避免数据过快被主机丢失或合并。4. 实操过程与核心环节实现4.1 硬件环境搭建与引脚分配要复现或理解此项目首先需要厘清硬件连接。根据附录A的原理图我们可以梳理出关键连接MCU (MC68HC705J1A):PA0 (DATA_OUT): 输出连接到7407缓冲器控制PS/2数据线的输出状态。PA1 (DATA_IN): 输入通过4066模拟开关读取PS/2数据线的状态。PA2 (CLOCK_OUT): 输出连接到7407控制PS/2时钟线的输出状态。PA3 (CLOCK_IN): 输入通过4066读取PS/2时钟线的状态。PA6 (DQ): 复用为DS1820的单总线数据线。同时通过DQ_CTRL(可能是某个寄存器位控制) 来控制该引脚是推挽输出驱动总线还是高阻输入读取总线。PA4 (BUSY), PA5 (CONTROL): 用于控制4066模拟开关实现键盘与MCU对PS/2总线的切换。电平转换与总线管理:7407 (开集电极缓冲器): 用于将MCU的5V CMOS输出电平转换为PS/2接口兼容的开集电极输出并提供驱动能力。4066 (模拟开关): 是关键的多路复用器。在MCU需要监听主机-键盘通信时将键盘的CLK和DATA线连接到MCU的输入引脚在MCU需要模拟键盘发送数据时将MCU的输出连接到主机的CLK和DATA线同时断开键盘。DS1820: 其DQ引脚连接到MCU的PA6VDD接电源GND接地。通常需要在DQ线上加一个4.7kΩ的上拉电阻至VCC原理图中的R6 (10kΩ) 即为此用途。实操心得在面包板或万用板上搭建此电路时务必注意PS/2接口的引脚顺序5V DATA NC GND CLK NC。连接错误可能损坏MCU或PC端口。建议先使用逻辑分析仪或示波器在真正的PS/2键盘线上抓取通信波形直观理解协议时序再着手编写或调试固件。4.2 固件初始化与主循环剖析固件的入口是START标签处的代码这是一个典型的事件驱动型超级循环架构。START BSR INITIALIZE ; 初始化MCU I/O端口。 WAIT_4_COMMAND JSR CONTACT ; 等待PC联系设备。 JSR ACQUIRE_TEMP ; 如果与PC建立联系则从DS1820获取温度读数 TST FLAG ; 将其转换为一系列PC键盘扫描码 BNE WAIT_4_COMMAND ; 并发送给PC。 JSR FORMAT_TEMP JSR SEND_TEMP BRA WAIT_4_COMMANDINITIALIZE: 设置端口A和B的数据方向寄存器。根据原理图需要将CLOCK_OUT,DATA_OUT,BUSY,CONTROL,DQ_CTRL等引脚设置为输出将CLOCK_IN,DATA_IN,DQ(读取时) 设置为输入。初始状态应将所有输出引脚置为高电平对于开集电极控制线高电平意味着释放/不驱动。CONTACT: 这是系统的休眠与唤醒机制。它循环调用READ_COMMAND试图从主机-键盘的通信流中捕捉特定的“激活序列”。根据描述这个序列是两个连续的ECHO命令-响应对主机发送0xEE键盘回复0xEE。只有当连续两次完整捕获到这个序列才认为激活成功跳出循环。这确保了只有运行了配套PC程序THERMO.EXE的主机才能启动温度计防止误触发。ACQUIRE_TEMP: 激活后执行完整的DS1820通信序列复位-发送跳过ROM命令-发送开始转换命令-等待转换完成-再次复位-发送跳过ROM命令-发送读暂存器命令-读取温度低字节和高字节。每一步都有错误检查FLAG任何一步失败都会导致流程终止返回等待激活状态。FORMAT_TEMP与SEND_TEMP: 如前所述进行数据转换和发送。这种结构清晰、健壮是嵌入式裸机编程的典范。4.3 关键通信子程序代码解读以READ_COMMAND(读取主机到键盘命令) 函数为例看其如何实现协议解析READ_COMMAND CLR FLAG ; 清除返回标志 ... (保存寄存器) LDX #$9 ; 等待数据线下降沿起始位开始 WAIT4COMMAND BRSET DATA_IN,PORTA,WAIT4COMMAND ; 等待数据线变低 BRSET CLOCK_IN,PORTA,READ_CMD_ERROR ; 如果时钟线为高继续否则错误 LDA #$48 WAIT4CLOCKHI BRSET CLOCK_IN,PORTA,STARTBITCLOCK ; 等待时钟线上升超时计数 DECA BEQ READ_CMD_ERROR BRA WAIT4CLOCKHI STARTBITCLOCK LDA #$D7 ; 等待时钟线下降以采样起始位 WAIT4STARTING BRCLR CLOCK_IN,PORTA,RISINGCLOCK DECA BEQ READ_CMD_ERROR BRA WAIT4STARTING RISINGCLOCK LDA #$0A ; 等待时钟线再次上升 WAIT4RISING BRSET CLOCK_IN,PORTA,GET_BIT DECA BEQ READ_CMD_ERROR BRA WAIT4RISING GET_BIT LDA #$3 ; 延时约10µs后在时钟高电平中点采样数据位 GET_BIT_DELAY DECA BNE GET_BIT_DELAY BRCLR DATA_IN,PORTA,GET_LOW_BIT ; 判断数据位是0还是1 ... (计算奇偶校验) ROR DATA ; 将数据位移入DATA变量LSB first DECX BNE FALLINGCLOCK ; 循环8次读取8个数据位 ... (后续处理奇偶校验位、停止位、键盘应答位)这段代码完美诠释了“位操作”通信它不依赖任何硬件外设完全通过循环检测I/O引脚电平的变化并在精确的时刻采样数据。每个等待边沿的循环都带有超时判断保证了程序的鲁棒性。$48,$D7,$0A,$3这些立即数都是根据4MHz系统时钟计算出的延时循环次数以满足协议规定的几十微秒的时序窗口。5. 常见问题与排查技巧实录在开发和调试此类底层硬件交互项目时会遇到一些典型问题。以下是我在实际操作中总结的排查清单问题现象可能原因排查思路与解决方法PC程序无法激活温度计1. 激活序列不匹配。2. PS/2总线竞争或冲突。3. 时序不准确导致帧错误。1.逻辑分析仪是首选同时抓取CLK和DATA线查看PC发送的ECHO命令 (0xEE) 和键盘的回复波形。确认MCU是否正确识别了这两个连续帧。2.检查模拟开关控制确保在监听阶段 (CONTACT)BUSY和CONTROL信号使键盘连接到主机MCU仅作为监听者。在发送阶段MCU应断开键盘并接管总线。3.校准软件延时用示波器测量MCU产生的时钟脉冲宽度。根据4MHz时钟周期0.25µs调整HALF_CLOCK、FULL_CLOCK等子程序中的循环计数使时钟频率落在10-16.7kHz范围内并确保数据在时钟下降沿附近稳定。温度读数全为0或固定值1. DS1820通信失败。2. 电源或上拉电阻问题。3. 时序不符合DS1820要求。1.检查复位存在脉冲在RESET_1820函数后设置断点或通过LED指示确认MCU能检测到DS1820的拉低应答。如果没有检查DQ线连接、上拉电阻R6是否接好。2.测量供电电压在DS1820进行温度转换时执行CONVERT T后用示波器查看DQ线电压。在寄生供电下此时应有强上拉接近VCC否则转换可能失败。可以考虑在代码中在转换期间将PA6引脚改为强推挽输出高电平。3.核对读写时隙用逻辑分析仪抓取MCU与DS1820的通信波形对照DS1820数据手册检查复位脉冲、写“1”、写“0”、读时隙的宽度是否在允许范围内。PC接收到乱码或部分字符丢失1. 扫描码转换表错误。2. 发送时序过快主机处理不及。3. 奇偶校验错误导致重发失败。1.验证扫描码确保SCAN_TABLE中的值与所用键盘的扫描码集通常是XT或AT集一致。可以先用一个简单的测试程序让MCU固定发送字符‘A‘的扫描码 (0x1C)看PC是否能正确接收。2.增加字符间延时在SEND_TEMP函数中尝试增加TX_DELAY的循环次数模拟更慢的按键速度。PS/2主机对快速键击的处理能力有限。3.检查奇偶校验计算在SEND函数中仔细跟踪TEMP变量用于计算奇偶校验位的更新逻辑。确保对于每个发送的字节奇偶校验位计算正确。可以在发送前将待发数据和计算出的校验位通过调试端口输出进行验证。系统运行不稳定偶尔死机1. 软件延时被中断打断。2. 堆栈溢出。3. 电源噪声或毛刺。1.关键段禁用中断在READ_COMMAND、SEND、RESET_1820等对时序要求苛刻的函数入口处使用SEI指令关闭全局中断退出时再用CLI开启。确保定时器中断等不会干扰微秒级延时。2.检查子程序调用深度MC68HC05的堆栈深度有限。避免过深的嵌套调用。确保中断服务程序本身尽量简短。3.加强电源去耦在MCU和DS1820的VCC与GND之间尽可能靠近芯片引脚放置0.1µF的瓷片电容原理图中的C1-C4。对于DS1820如果布线较长可以考虑在靠近其电源引脚处增加一个10µF的电解电容。最后的个人体会这个项目虽然基于一个较老的MCU平台但它所蕴含的“理解协议、精确控制、资源优化”的思想在今天开发基于ARM Cortex-M0甚至RISC-V的嵌入式系统时依然完全适用。当你用惯了HAL库和Arduino封装好的digitalWrite、Wire库之后回头来实现一遍这种纯“寄存器位操作”的驱动会对“计算机究竟是如何工作的”有更深刻的认识。调试过程中逻辑分析仪是你的最佳伙伴它能将抽象的时序协议变成可视化的波形让一切问题无所遁形。每一次成功让PC屏幕上显示出正确的温度值都是对底层硬件世界的一次完美对话。