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的PA6,VDD接电源,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屏幕上显示出正确的温度值,都是对底层硬件世界的一次完美对话。