1. 项目概述与核心价值
在嵌入式开发的早期岁月里,MC68HC705C8这类8位微控制器是许多经典项目的核心。它们没有如今ARM Cortex-M系列那样丰富的外设和强大的库函数支持,每一个字节的ROM和每一个引脚的配置都需要开发者精打细算。正是在这种资源受限的环境下,理解并驾驭芯片内置的串行通信接口(SCI)和串行外设接口(SPI),成为了区分“能写代码”和“懂硬件”工程师的关键。SCI,本质上是一个全双工的UART,是那个时代连接终端、PC或者实现多机网络通信的“标准电话线”;而SPI,则是高速、同步的“内部总线”,用于高效地驱动显示屏、读取传感器或者扩展IO。很多人可能觉得这些老古董的技术文档枯燥乏味,但恰恰是这些文档里蕴含的硬件设计思想和软件控制逻辑,是构建稳定、可靠嵌入式系统的基石。今天,我们就抛开那些泛泛而谈的概念,直接钻进MC68HC705C8的数据手册,结合我过去调试类似老芯片的实际经验,把SCI和SPI从寄存器位到波形时序,从初始化代码到避坑指南,彻底讲透。无论你是正在维护一个遗留系统,还是想深入理解串行通信的底层原理,这篇文章都能给你提供一份可以直接“抄作业”的实战指南。
2. SCI串行通信接口深度解析
SCI是MC68HC705C8上用于异步串行通信的模块,遵循标准的UART协议。它的设计非常经典,理解了它,几乎就理解了所有8位微控制器UART的工作原理。
2.1 核心架构与双缓冲机制
SCI模块的核心是一个全双工、独立运行的收发系统。所谓“全双工”,意味着它可以同时进行数据的发送和接收,这依赖于两套独立的硬件单元:发送器和接收器。而“独立运行”则是指它们拥有各自的状态标志和控制逻辑,软件可以分别使能或禁用。
注意:很多新手会混淆“全双工”和“同时收发”。全双工指物理通道支持双向数据流,但具体能否“同时”进行,还取决于软件设计。如果采用简单的查询方式,程序在等待发送完成时无法处理接收,逻辑上就不是并发的。这就需要用到中断或者更高级的调度机制。
文档中提到的“双缓冲”(Double Buffering)是保证通信效率的关键设计,务必理解透彻。如图3-29所示:
- 发送端:存在一个发送数据寄存器(TDR)缓冲区和一个发送移位寄存器。当软件向
SCDAT寄存器写入数据时,数据首先进入TDR缓冲区。只要发送移位寄存器空闲,TDR中的数据就会立即被加载到移位寄存器中,并开始逐位串行发出。同时,TDRE标志置位,表明TDR已空,软件可以写入下一个待发送字节。这意味着,在理想情况下,软件可以在当前字节正在发送时,就提前准备好下一个字节,从而实现近乎连续的发送流,避免因软件延迟而产生的字符间不必要的空闲时间。 - 接收端:同样存在一个接收数据寄存器(RDR)缓冲区和一个接收移位寄存器。当接收移位寄存器收齐一个完整的字符(包括起始位、数据位、停止位)后,数据会被自动转移到RDR缓冲区,并置位
RDRF标志。此时,移位寄存器立即释放,可以开始接收下一个字符,而软件可以从容地读取RDR缓冲区中的数据。这有效防止了因软件响应不及时而导致的后续数据覆盖(即“溢出”)问题。
这种双缓冲结构对于任何实时性要求稍高的通信场景都至关重要。在编写驱动时,我们的核心任务之一就是高效地服务这两个缓冲区:及时写入数据以防发送断流,及时读取数据以防接收溢出。
2.2 关键寄存器详解与配置实战
配置SCI就是配置它的五个寄存器:BAUD、SCCR1、SCCR2、SCSR和SCDAT。我们逐一来拆解。
2.2.1 波特率寄存器(BAUD)配置计算
波特率配置是通信的基石,配置错误会导致完全无法通信或数据错乱。MC68HC705C8的波特率发生器由两级分频器构成(见图3-24):
- 预分频器(Prescaler):由
SCP1和SCP0位控制,对内部处理器时钟进行N分频(N=1, 3, 4, 13)。 - SCI速率选择器(SCI Rate Select):由
SCR2、SCR1、SCR0位控制,对预分频器的输出进行M分频(M=1, 2, 4, 8, ..., 128)。
最终,发送波特率时钟 = (内部处理器时钟频率) / (N * M * 16)。这里除以16是因为接收器采用16倍过采样技术以提高抗噪性和起始位检测精度,但发送端使用的就是1倍波特率时钟。
实战配置示例:假设我们使用一颗4MHz的晶体,内部总线时钟为2MHz(通常CPU时钟是晶振频率的二分频)。我们需要配置波特率为9600bps。
- 查找预分频值(N):查看表3-10。在2MHz处理器时钟下,找到预分频输出频率最接近9600Hz * 16 = 153.6kHz的一行。
SCP1:SCP0 = 1:1(除13)时,输出为19.20kHz。153.6kHz / 19.20kHz = 8, 这是一个2的整数次幂(2^3),符合下一级分频器的设定。 - 查找速率选择值(M):查看表3-11。在“Representative Highest Prescaler Baud Rate Output”列中找到19.20kHz。然后在该列向下查找,直到找到9600Hz。对应的
SCR2:SCR1:SCR0为0:1:1,即M=8。 - 验证计算:发送波特率 = 2,000,000 Hz / (13 * 8 * 16) = 2,000,000 / 1664 ≈ 1201.92 Hz。嗯?这和9600相差甚远。这里出现了文档中一个容易让人困惑的点。表3-10和表3-11中的频率值是“预分频器输出频率”和“发送波特率”,而不是分频因子。
让我们重新计算:目标是得到9600Hz的发送时钟。 公式:发送时钟 = 内部时钟 / (N * M * 16) 即:2,000,000 / (N * M * 16) = 9600 => N * M = 2,000,000 / (9600 * 16) ≈ 13.02
我们需要找到一对N和M(N来自{1,3,4,13}, M来自{1,2,4,8,16,32,64,128}),使得它们的乘积最接近13.02。
- 如果选 N=13, 则 M = 13.02 / 13 ≈ 1.00。 M=1最接近。代入验证:2,000,000 / (13 * 1 * 16) = 9615.38 Hz。误差率 (9615.38-9600)/9600 ≈ 0.16%, 在异步串口允许的误差范围内(通常<2%)。
- 查看表3-11,在“19.20 kHz”这一行(对应N=13时预分频器输出频率),
SCR=000(M=1)对应的发送波特率正是9600Hz。这与我们的计算吻合。
因此,对于2MHz总线时钟、9600波特率,配置应为:SCP1:SCP0 = 1:1(N=13),SCR2:SCR1:SCR0 = 0:0:0(M=1)。BAUD寄存器应写入0b11xxx000,其中xxx根据SCR位决定,这里就是11000000,即$C0。
实操心得:老式微控制器的波特率配置表往往基于特定晶振频率。如果你的晶振频率不在表中,必须手动计算。核心公式就是:
目标波特率 = Fbus / (16 * N * M)。先根据可选的N值(1,3,4,13)计算出一个接近的M值,然后选择误差最小的一组。误差最好控制在2%以内,对于更高的波特率(如115200),要求更苛刻。
2.2.2 控制与状态寄存器精讲
SCCR1(控制寄存器1):主要用于高级功能。
M位:字长选择。0代表8位数据,1代表9位数据。9位模式常用于多机通信,其中第9位作为地址/数据标识位。WAKE位:唤醒方法选择。当接收器处于休眠模式(RWU=1)时,0表示通过检测空闲线(Idle Line)唤醒,1表示通过检测地址位(第9位为1)唤醒。这在多节点网络中用于节能和寻址。T8/R8:当M=1时,分别作为发送和接收数据的第9位。- 常见配置:对于普通的8位数据、无地址模式的点对点通信,通常将
SCCR1初始化为$00。
SCCR2(控制寄存器2):这是SCI的“总开关”。
TE/RE:发送/接收使能。必须置1才能启用相应功能。TIE/TCIE/RIE/ILIE:各类中断使能。如果使用查询方式,这些位应清零;如果使用中断方式,根据需要置位。例如,使能接收中断RIE,这样一旦收到数据(RDRF=1)就会触发中断。RWU:接收器唤醒控制。置1使接收器进入休眠(不置位RDRF),等待唤醒条件。通常清零。SBK:发送中止符。置1后,发送器会持续输出低电平(Break信号),直到软件将其清零。用于协议中的帧分隔或复位从设备。- 常见配置:使能收发、禁用中断时,写入
$0C(0000 1100, 即TE=1,RE=1)。
SCSR(状态寄存器):用于查询通信状态。
TDRE:发送数据寄存器空。当TDR缓冲区可写入新数据时置1。这是发送数据前必须检查的标志。RDRF:接收数据寄存器满。当RDR缓冲区有数据可读时置1。这是读取数据前必须检查的标志。TC:发送完成。当发送移位寄存器也空闲(即最后一个停止位也已发出)时置1。用于在关闭发送器或切换线路方向(如RS-485)前确保数据已完全发出。IDLE:检测到空闲线(接收数据线持续为高电平超过一个完整字符时间)。可用于检测通信中断。OR:溢出错误。软件尚未读取RDR中的数据,新数据又已接收完毕,导致旧数据被覆盖。NF:噪声标志。在3个采样点中(第7、8、9个),如果起始位或数据位的电平不一致,此位置1。FE:帧错误。当停止位被检测为低电平时置1。通常由波特率不匹配、线路干扰或对方发送Break引起。- 关键点:
TDRE和RDRF是查询法驱动中最常用的两个标志。OR、NF、FE是错误标志,在可靠性要求高的场合,每次接收后都应检查并处理。
2.2.3 数据寄存器(SCDAT)
这是一个特殊的寄存器,读写操作指向不同的物理实体。
- 写入
SCDAT:数据被写入发送数据寄存器(TDR)缓冲区。 - 读取
SCDAT:数据从接收数据寄存器(RDR)缓冲区读出。 这种共享地址的设计简化了编程模型,但务必清楚其背后的双缓冲机制。
2.3 数据格式与波形
SCI采用标准的NRZ(非归零)格式,如图3-30所示:
- 空闲状态:线路为高电平(逻辑1)。
- 起始位:一个比特时间的低电平(逻辑0),标志一个字符帧的开始。
- 数据位:8位或9位数据,低位(LSB)先行。这是最容易出错的地方,务必与对方设备设置一致。
- 停止位:至少一个比特时间的高电平(逻辑1)。MC68HC705C8固定为1位停止位。
- Break信号:持续至少10或11个比特时间的低电平。由
SBK位控制产生。
注意:“LSB先行”是绝大多数UART/USART的标准。但在与某些非标准设备(如某些老式调制解调器或自定义协议设备)通信时,需要确认字节序。如果发现接收到的数据字节顺序颠倒,很可能就是位序问题。
2.4 软件驱动实现:查询法与中断法
文档图3-31和3-32给出了最基础的查询法流程图。我们来将其转化为更健壮的C语言风格伪代码,并补充中断法思路。
2.4.1 查询法发送与接收
// 初始化函数 void SCI_Init(uint8_t baud_rate) { BAUD = baud_rate; // 例如 0xC0, 对应9600@2MHz SCCR1 = 0x00; // 8位数据, 无唤醒 SCCR2 = 0x0C; // 使能发送和接收, 禁用所有中断 } // 查询法发送一个字节 void SCI_SendByte(uint8_t data) { while ((SCSR & 0x80) == 0) { // 等待 TDRE 标志置位 (SCSR.7 == 1) // 在实际项目中,这里应该加入超时机制,防止死等 } SCDAT = data; // 写入数据,启动发送 } // 查询法接收一个字节(阻塞式) uint8_t SCI_ReceiveByte(void) { while ((SCSR & 0x20) == 0) { // 等待 RDRF 标志置位 (SCSR.5 == 1) // 同样需要超时机制 } // 可选:检查错误标志 OR, NF, FE uint8_t status = SCSR; if (status & 0x0E) { // 检查 OR(0x08), NF(0x04), FE(0x02) // 错误处理:清标志(通过读SCSR,再读SCDAT)、记录日志、重发请求等 // 清错误标志的方法是先读SCSR,再读SCDAT uint8_t dummy = SCSR; dummy = SCDAT; } return SCDAT; // 读取数据 }2.4.2 中断法驱动设计
中断法能极大解放CPU,提高系统响应效率。以接收中断为例:
- 初始化:在
SCI_Init中,除了设置波特率,还需将SCCR2的RIE位置1(例如设为0x2C),并配置MCU的全局中断使能。 - 中断服务程序(ISR):
// 假设的SCI中断向量入口 __interrupt void SCI_ISR(void) { uint8_t status = SCSR; uint8_t data; if (status & 0x20) { // RDRF 接收中断 data = SCDAT; // 读取数据,这会自动清除RDRF标志 // 将数据放入环形缓冲区(Ring Buffer)供主程序处理 ring_buffer_write(&rx_buf, data); // 检查错误(错误标志通常在RDRF置位时一同有效) if (status & 0x0E) { // 错误处理:记录错误类型,可能需要清错误标志 // 读SCDAT已清除了RDRF,但OR/NF/FE需要读SCSR再读SCDAT来清除 uint8_t err_stat = SCSR; // 再次读取以获取错误状态 // ... 错误处理逻辑 ... } } // 可以同时处理发送中断(TDRE)和发送完成中断(TC) if ((status & 0x80) && (SCCR2 & 0x80)) { // TDRE & TIE // 如果发送缓冲区有数据,则取出并写入SCDAT if (!ring_buffer_empty(&tx_buf)) { SCDAT = ring_buffer_read(&tx_buf); } else { // 发送缓冲区空,可禁用发送中断(TIE清0)以避免空中断 SCCR2 &= ~0x80; } } } - 主程序发送:主程序只需将待发送数据放入发送环形缓冲区,然后使能发送中断(
TIE置1)。如果发送缓冲区之前是空的,则需要手动触发一次发送(向SCDAT写第一个字节)。
实操心得:环形缓冲区是中断驱动串口程序的灵魂。它解耦了产生/消费数据的速度差异。缓冲区大小需要根据最大数据包长度和系统处理能力来设计,通常为2的幂次方以便于使用位操作进行索引循环。避免在ISR中进行复杂计算或调用不可重入函数。
2.5 应用实例剖析与扩展
文档图3-33提供了一个将接收到的ASCII字符转换为其十六进制值并回传的例子。这个例子虽然简单,但体现了典型的“接收-处理-发送”流程。我们可以从中提炼出更通用的框架:
- 命令解析框架:许多嵌入式系统通过串口接收文本命令。可以扩展此例,实现一个简单的命令行接口(CLI)。例如,定义命令如
SET LED ON、READ TEMP, 在接收中断中组装字符串,在主循环中解析并执行。 - 二进制协议:���于效率要求高的场合,应采用二进制协议。定义帧头、长度、命令字、数据、校验和(如CRC8/16)的固定格式。在接收中断中按状态机解析,确保帧的完整性。
- 流量控制:在高速或大数据量传输时,需要硬件流控(RTS/CTS)或软件流控(XON/XOFF)。MC68HC705C8的SCI本身不支持硬件流控引脚,但可以用两个通用IO口模拟。软件流控则需要在协议中实现。
3. SPI同步串行外设接口实战指南
SPI是一种高速、全双工、同步的串行总线。相比SCI的异步方式,SPI需要额外的时钟线(SCK)来同步数据,因此速率可以很高(MC68HC705C8最高可达1.05 MHz主模式)。
3.1 主从架构与四线制
SPI总线通常包含四根线:
- SCK (Serial Clock):时钟信号,由主设备产生。
- MOSI (Master Out Slave In):主设备数据输出,从设备数据输入。
- MISO (Master In Slave Out):主设备数据输入,从设备数据输出。
- SS (Slave Select):从设备片选,低电平有效。每个从设备都需要独立的SS线。
SPI的关键特性是主从(Master-Slave)架构。整个通信的节奏完全由主设备掌控。主设备通过产生SCK时钟来驱动数据的移入和移出。从设备只有在被主设备通过其SS线选中时,才会响应时钟并驱动MISO线。
3.2 移位寄存器原理与数据交换
SPI数据传输的核心是一个分布式的16位移位寄存器(见图3-35)。主设备和从设备内部各有一个8位移位寄存器,通过MOSI和MISO线首尾相连。当主设备发起一次传输(通常通过向SPI数据寄存器写入一个字节)时,两个移位寄存器在SCK的驱动下同时循环移位8次。结果是:主设备移位寄存器中的8位数据移到了从设备中,而从设备移位寄存器中的8位数据移到了主设备中。一次传输完成了主从设备之间一个字节的双向交换。
这个特性非常重要:
- 如果主设备想读取从设备的数据,它必须向从设备发送一个字节(可以是任意值,如0xFF或命令字),同时从设备的数据会通过MISO线移入。
- 如果主设备想写入数据到从设备,它发送有效数据,同时也会从MISO线收到一个字节(可能是从设备的旧数据或状态),这个数据通常被忽略,但必须被读取以清空接收缓冲区。
3.3 SPI寄存器配置与模式选择
MC68HC705C8的SPI模块主要通过两个寄存器控制:SPCR(控制寄存器)和SPSR(状态寄存器)。文档中图3-34的框图清晰地展示了其内部结构。
3.3.1 控制寄存器(SPCR)关键位
SPE:SPI使能位。必须置1才能使用SPI功能。MSTR:主/从模式选择。1为主模式,0为从模式。在硬件连接确定后,此位必须正确设置,否则总线冲突。CPOL:时钟极性。0表示SCK空闲时为低电平,1表示空闲时为高电平。CPHA:时钟相位。决定数据在SCK的哪个边沿采样。CPHA=0:数据在SCK的第一个边沿(如果CPOL=0则是上升沿,CPOL=1则是下降沿)被采样。CPHA=1:数据在SCK的第二个边沿被采样。CPOL和CPHA共同定义了四种SPI模式(Mode 0-3),这是SPI通信中最容易出错的地方之一,必须与从设备严格匹配。
| 模式 | CPOL | CPHA | SCK空闲状态 | 数据采样边沿 | 数据变化边沿 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 | 下降沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 | 上升沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 | 上升沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 | 下降沿 |
SPR1,SPR0:SPI时钟速率选择。在主模式下,这两位决定SCK相对于内部总线时钟的分频比(2, 4, 16, 32)。在从模式下,这两位无意义,SCK由外部主设备提供。
3.3.2 状态寄存器(SPSR)关键位
SPIF:SPI传输完成标志。当一次8位数据传输完成时,硬件置1。这是判断一次SPI操作是否完成的主要标志。读取SPSR(访问SPIF位)然后读取SPDR,可以清除此标志。WCOL:写冲突标志。如果在一次SPI传输尚未完成(SPIF=0)时,软件试图向SPDR写入数据,此位置1。发生写冲突时,本次写入无效,必须通过先读SPSR再读SPDR来清除WCOL标志。MODF:模式错误标志。在主模式下,如果SS引脚被意外拉低(可能表示有另一个主设备在争夺总线),此位置1。发生模式错误后,SPI系统可能被禁用,需要软件处理错误并重新初始化。
3.4 SPI驱动实现与典型外设操作
3.4.1 基础查询式字节传输函数
// SPI初始化为主机,模式0,低速(分频32) void SPI_Master_Init(void) { // 配置PD2(MISO)为输入,PD3(MOSI), PD4(SCK), PD5(SS)为输出 DDRD |= (1 << PD5) | (1 << PD4) | (1 << PD3); DDRD &= ~(1 << PD2); // 拉高SS线,不选中任何从设备 PORTD |= (1 << PD5); // 配置SPCR: 使能SPI,主机模式,模式0, 时钟频率Fosc/32 // SPE=1, MSTR=1, CPOL=0, CPHA=0, SPR1=1, SPR0=1 SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); } // SPI发送并接收一个字节(查询方式) uint8_t SPI_TransferByte(uint8_t data) { SPDR = data; // 启动传输 while (!(SPSR & (1 << SPIF))) { // 等待传输完成 } return SPDR; // 读取接收到的数据 }3.4.2 操作典型SPI从设备示例:93C46 EEPROM
以Microchip 93C46(1Kbit SPI串行EEPROM)为例,演示如何通过SPI进行读写。
写使能指令:在写入前,必须先发送写使能指令。
void EEPROM_WriteEnable(void) { PORTD &= ~(1 << PD5); // 拉低SS,选中EEPROM SPI_TransferByte(0x06); // 发送写使能指令码 PORTD |= (1 << PD5); // 拉高SS,结束指令 // 需要短暂延时(几个us) }写入数据:93C46需要先发送写指令(
0x05)、地址(9位,分两次发送),再发送数据。void EEPROM_WriteByte(uint16_t addr, uint8_t data) { EEPROM_WriteEnable(); // 先使能写操作 PORTD &= ~(1 << PD5); // 发送写指令 (0x05) 和地址高7位 SPI_TransferByte(0x05 | ((addr >> 6) & 0x0E)); // 发送地址低6位和数据高2位(合并为一个字节) SPI_TransferByte(((addr & 0x3F) << 2) | ((data >> 6) & 0x03)); // 发送数据低6位 SPI_TransferByte((data & 0x3F) << 2); PORTD |= (1 << PD5); // 等待写入完成(轮询READY/BUSY状态或简单延时5ms) _delay_ms(5); }读取数据:
uint8_t EEPROM_ReadByte(uint16_t addr) { uint8_t high_byte, low_byte; PORTD &= ~(1 << PD5); // 发送读指令 (0x03) 和地址高7位 SPI_TransferByte(0x03 | ((addr >> 6) & 0x0E)); // 发送地址低6位和一个空字节以启动读取 SPI_TransferByte((addr & 0x3F) << 2); // 接收两个数据字节(93C46输出16位数据) high_byte = SPI_TransferByte(0xFF); low_byte = SPI_TransferByte(0xFF); PORTD |= (1 << PD5); // 从两个字节中提取8位数据(具体格式取决于93C46的模式) return ((high_byte & 0x0F) << 4) | ((low_byte >> 2) & 0x0F); }
避坑指南:SPI通信的时序极其严格。必须仔细阅读从设备的数据手册,确认:
- SPI模式(CPOL, CPHA):93C46通常工作在模式0或3。
- 数据位顺序:是MSB先行还是LSB先行?绝大多数SPI设备是MSB先行,MC68HC705C8的SPI也是MSB先行。
- 片选(SS)时序:指令、地址、数据是否需要在同一个SS低电平周期内完成?SS拉高后是否需要保持一段时间(
t_CSH)?- 时钟速率:从设备支持的最大SCK频率是多少?MC68HC705C8主模式最高1.05MHz,需确保不超过从设备极限。
- 写周期时间:像EEPROM这类存储器,写入后需要等待数毫秒才能进行下一次操作,期间读取会返回无效数据。
3.5 多从设备连接与软件片选
当一个主设备需要连接多个SPI从设备时,硬件连接上,所有设备的SCK、MOSI、MISO分别并联,但每个从设备需要独立的SS线(见图3-34中多个从设备的示意)。在软件上,操作某一从设备前,先将其对应的SS引脚拉低,操作完成后拉高。其他未被选中的从设备,其SS线保持高电平,它们的MISO输出应处于高阻态,不会干扰总线。
#define SS_DEVICE1_PIN PD5 #define SS_DEVICE2_PIN PD6 // 假设用另一个IO口控制第二个设备 void SPI_SelectDevice1(void) { PORTD &= ~(1 << SS_DEVICE1_PIN); PORTD |= (1 << SS_DEVICE2_PIN); // 确保其他设备取消选中 } void SPI_SelectDevice2(void) { PORTD &= ~(1 << SS_DEVICE2_PIN); PORTD |= (1 << SS_DEVICE1_PIN); } void SPI_DeselectAll(void) { PORTD |= (1 << SS_DEVICE1_PIN) | (1 << SS_DEVICE2_PIN); }4. 常见问题排查与调试技巧
调试串行通信,尤其是这种没有现成调试工具的底层接口,是嵌入式开发的基本功。以下是我多年积累的一些实战技巧。
4.1 SCI通信失败排查清单
完全无通信,电平无变化:
- 检查硬件连接:TX接RX, RX接TX, 地线共地。这是最常犯的错误。
- 检查电平转换:MC68HC705C8是0-5V TTL电平,如果连接PC串口(RS-232, ±12V),必须使用MAX232之类的电平转换芯片。直接用导线连接会损坏芯片或无法工作。
- 确认引脚复用:确保
TE/RE位已正确使能,SCI功能已覆盖通用IO功能。 - 测量波特率:用示波器测量TDO引脚。发送一个固定的字节(如
0x55, 二进制01010101),测量一个位的时间。0x55的波形是方波,易于测量。时间 = 1 / 波特率。例如9600bps下,一个位应为104us。如果偏差太大,检查波特率寄存器计算和晶振频率。
能收到数据,但全是乱码:
- 波特率不匹配:这是最常见原因。用上述方法测量实际波特率,与对方设备设置对比。计算误差是否在允许范围内(通常<2%)。
- 数据格式不匹配:检查数据位(8/9)、停止位(MC68HC705C8固定1位)、奇偶校验(MC68HC705C8的SCI不支持硬件奇偶校验,需软件实现)。最常见的是LSB/MSB顺序错误。
- 电气噪声:长距离通信时,线路可能引入噪声导致误码。检查接地,考虑使用差分通信(如RS-485)或增加终端电阻。
只能发送,不能接收(或反之):
- 检查控制寄存器:确认
SCCR2中的TE和RE位都已使能。 - 检查中断/查询逻辑:如果使用中断,是否正确配置了中断向量和全局中断使能?如果使用查询,程序是否卡在等待某个标志上?
- 检查
SCSR错误标志:FE(帧错误)和OR(溢出错误)会阻止后续数据的正确接收。确保在错误发生后按正确顺序(先读SCSR,再读SCDAT)清除标志。
- 检查控制寄存器:确认
4.2 SPI通信失败排查清单
主设备无法驱动时钟或数据线:
- 确认主从模式:主设备的
MSTR位必须为1,且SS引脚必须配置为输出并置高(或设置为通用IO并拉高),防止其被拉低导致意外进入从模式(触发MODF错误)。 - 检查引脚方向:主设备的MOSI和SCK必须设置为输出,MISO设置为输入。
- 检查
SPE位:SPI总使能位必须置1。
- 确认主从模式:主设备的
通信数据错误:
- SPI模式不匹配:这是SPI调试的头号杀手。用示波器同时观察SCK和MOSI/MISO。根据
CPOL和CPHA,确定数据是在SCK的哪个边沿采样和变化的。与从设备数据手册的时序图严格比对。 - 时钟极性反相:如果
CPOL设反,数据可能完全错位。尝试另一种模式。 - 片选时序问题:确保在发送指令/数据前,SS已稳定拉低一段时间(满足
t_CSS);在传输完成后,SS拉高并保持足够时间(满足t_CSH)。有些设备要求SS在字节之间保持低电平,有些则要求每个字节都重新拉低。 - 时钟频率过高:降低
SPR1/SPR0的分频比,使用更低的SCK频率测试。
- SPI模式不匹配:这是SPI调试的头号杀手。用示波器同时观察SCK和MOSI/MISO。根据
多从设备干扰:
- 未选中设备未释放MISO:确保所有从设备的MISO引脚在不被选中时处于高阻态。如果某个从设备MISO一直驱动总线,会导致总线冲突。检查从设备的
SS引脚连接和内部上拉。 - 软件片选逻辑错误:在切换操作对象时,确保前一设备的SS已拉高,并插入短暂延时(通常几百纳秒即可),再拉低新设备的SS。
- 未选中设备未释放MISO:确保所有从设备的MISO引脚在不被选中时处于高阻态。如果某个从设备MISO一直驱动总线,会导致总线冲突。检查从设备的
4.3 示波器/逻辑分析仪调试技巧
一台示波器或逻辑分析仪是调试串行通信的“眼睛”。
- SCI调试:触发设置在起始位的下降沿。观察一个完整的字符帧(10-11位),检查起始位、数据位(LSB先行)、停止位的宽度和电平是否正确。测量位宽计算实际波特率。
- SPI调试:使用四通道同时捕获SCK、MOSI、MISO和SS。设置解码功能为SPI,并正确配置模式(CPOL, CPHA)、位序(MSB/LSB)。这样可以直接看到十六进制的数据字节,极大提高调试效率。重点关注SS有效期间的时序,以及数据相对于SCK边沿的建立和保持时间是否满足从设备要求。
4.4 软件层面的鲁棒性设计
超时机制:所有等待标志的循环(如
while (!(SCSR & 0x80));)必须加入超时计数器。防止因硬件故障、对方设备掉线等原因导致程序死锁。#define SCI_TX_TIMEOUT 1000 // 超时计数 uint16_t timeout = 0; while (((SCSR & 0x80) == 0) && (timeout < SCI_TX_TIMEOUT)) { timeout++; // 可插入短延时或执行其他轻量级任务 } if (timeout >= SCI_TX_TIMEOUT) { // 超时处理:记录错误、重置SCI模块、尝试恢复等 SCI_ErrorHandler(); }错误恢复:当检测到
OR,FE等错误时,不应只是简单地清除标志。应该有一个错误恢复流程,例如:清空接收缓冲区、重新同步协议(如发送一个特定的同步字)、甚至重置SCI模块(先禁用再重新初始化)。数据缓冲与流控:对于高速或突发数据,必须使用环形缓冲区。并评估是否需要实现软件流控(XON/XOFF)来防止缓冲区溢出。对于MC68HC705C8这种资源有限的芯片,缓冲区大小需要仔细权衡。
深入理解MC68HC705C8的SCI和SPI模块,不仅仅是学会配置几个寄存器。更重要的是掌握异步和同步串行通信的核心思想、硬件与软件的协同、以及调试复杂时序问题的系统方法。这些经验,在你面对更现代的ARM Cortex-M系列芯片的USART、SPI、I2C, 甚至是高速SerDes接口时,依然具有极高的参考价值。硬件在变,协议在演进,但解决问题的底层逻辑和调试的基本功,是相通的。最后一个小建议,在项目初期,不妨用这些老芯片搭建一个最简单的收发测试环境,用最原始的示波器和点灯调试法,亲手“看见”每一个比特的流动,这种直观的感受是阅读任何文档都无法替代的。