深入解析MC68HC908 SCI模块:异步串行通信原理与实战配置
1. 项目概述与核心价值
在嵌入式系统开发,尤其是工业控制、汽车电子和智能设备领域,微控制器(MCU)与外部世界的数据交换是基础且核心的需求。无论是将传感器数据上传至上位机,还是接收来自PC的配置指令,亦或是多个MCU节点之间组成简单的网络,串行通信都是最常用、最可靠的桥梁之一。而串行通信接口(Serial Communications Interface, SCI),作为实现异步串行通信的硬件外设,其稳定性和易用性直接决定了整个系统的通信效率和可靠性。
今天,我们就以Freescale(现为NXP)经典的8位微控制器MC68HC908GR8A/GR4A为例,深入“扒开”其SCI模块的内部结构和工作原理。这份数据手册的章节内容,就像一张精密的电路图纸,但图纸本身不会告诉你布线时哪里容易短路,哪个参数配置错了会导致数据乱码。我将结合自己多年在8位/16位MCU上“摸爬滚打”的经验,不仅解读手册上的“是什么”,更重点分享“为什么”这么设计,以及在实际项目中“怎么用”才能避开那些手册里没写的“坑”。
对于嵌入式工程师而言,理解一个SCI模块,绝不仅仅是知道如何调用几个API函数。真正的价值在于:当通信出现偶发性错误时,你能通过状态寄存器快速定位是噪声干扰、波特率失配还是缓冲区溢出;当系统功耗敏感时,你知道如何利用唤醒机制让MCU在多数时间休眠,仅在收到特定地址帧时才被激活;当需要更高的通信可靠性时,你懂得如何启用并正确解读奇偶校验和帧错误标志。MC68HC908GR8A/GR4A的SCI模块虽然诞生于一个经典的架构,但其设计思想——全双工、可编程波特率、丰富的中断与错误检测机制——至今仍是许多现代MCU串行外设的蓝本。掌握它,就等于掌握了一类通信外设的通用“心法”。
2. SCI模块架构与核心功能拆解
2.1 模块定位与核心特性
MC68HC908GR8A/GR4A的SCI模块是一个完全独立于CPU的硬件外设,它通过两个引脚(PTE0/TxD和PTE1/RxD)与外界进行串行数据交换。其核心设计目标是在最小化CPU干预的前提下,提供可靠、灵活的异步串行通信能力。所谓“异步”,是指通信双方没有统一的时钟信号线,依靠预先约定好的波特率(Baud Rate)来同步每一位数据的采样时刻。这种方式的优点是连线简单(仅需两根线用于双向通信),缺点是波特率必须高度匹配,否则会产生累积误差导致通信失败。
该SCI模块的几个核心特性,直接决定了它的应用能力和可靠性等级:
- 全双工操作:这是最基础也是最重要的特性。发送器(Transmitter)和接收器(Receiver)拥有完全独立的移位寄存器和缓冲区(SCDR),可以同时进行数据的发送和接收,互不干扰。这就像一条双向车道,上行和下行的车辆可以同时通行,极大地提高了通信效率。
- 标准NRZ格式:采用非归零(Non-Return-to-Zero)编码,即逻辑1和0分别用固定的高、低电平表示,在位周期内电平保持不变。这是最通用、最简单的串行编码格式,被绝大多数串行协议(如RS-232)所采用。
- 32种可编程波特率:波特率发生器非常灵活,其时钟源(SCICLK)可以选择内部总线时钟(BUS CLK)或外部时钟(CGMXCLK),再通过一个可编程的预分频器(÷4或÷16)和一个13位的波特率分频器(SCBR寄存器控制)共同生成最终波特率。手册中给出的公式是计算基础,但实际配置时,我们更关心如何根据系统时钟和期望波特率反推出SCBR寄存器的值。
- 可编程字符长度(8位或9位):这提供了额外的灵活性。第9位(Bit 8)可以作为一个额外的数据位、一个简单的地址/数据标志位(用于多机通信唤醒),或者当启用奇偶校验时,作为校验位。这个设计巧妙地将多种功能复用到一个位上。
- 独立的中断系统:模块提供了多达8个中断标志(发送器空、发送完成、接收器满、接收线路空闲、接收溢出、噪声错误、帧错误、奇偶错误),并且每个标志都可以单独使能中断。这种精细的中断控制允许开发者根据应用需求,采用最高效的编程模型——是轮询状态位,还是让CPU在数据就绪或错误发生时立即被中断响应。
- 硬件错误检测:这是保证通信可靠性的关键。模块硬件层面自动检测帧错误(停止位不是预期的‘1’)、奇偶校验错误(如果使能了校验)、噪声错误(在采样点检测到电平不一致)和接收溢出错误(新数据覆盖了未读的旧数据)。这些标志位为调试和容错处理提供了直接依据。
- 两种接收器唤醒方法:这是低功耗和多机通信系统的福音。当接收器处于“休眠”(RWU=1)状态时,可以通过检测到空闲线路(RxD引脚上出现连续10/11个‘1’)或地址标志(接收到的字符最高位MSB=1)来唤醒。这允许总线上只有一个主设备广播消息,而只有地址匹配的从设备才会被唤醒并处理后续数据,其他从设备继续休眠以节省功耗。
2.2 引脚复用与配置要点
SCI模块的收发引脚PTE0/TxD和PTE1/RxD是与通用I/O口Port E复用的。这是一个非常典型的设计。这里有一个极易被忽略但至关重要的细节:一旦通过设置ENSCI位使能了SCI模块,这两个引脚的方向控制将完全由SCI模块接管,与数据方向寄存器DDRE的相应位无关。
实操心得:这意味着,在初始化SCI之前,你完全不需要(也不应该)去配置
DDRE寄存器中对应PTE0和PTE1的方向。相反,如果你在使能SCI后,又试图通过DDRE去改变这两个引脚的方向,可能会导致不可预测的行为。正确的做法是,先通过DDRE将这两个引脚设置为需要的上拉/下拉输入状态(如果需要的话),然后再使能SCI。SCI使能后,TxD自动变为输出,RxD自动变为输入。
2.3 数据格式与帧结构解析
SCI使用标准的异步串行帧格式,如下图所示(基于手册图13-2):
[起始位 (0)] [数据位 (LSB ... MSB)] [奇偶校验位 (可选)] [停止位 (1)]- 起始位:总是逻辑‘0’,标志着一次数据传输的开始。接收端依靠检测到这个下降沿来启动一次接收过程。
- 数据位:可以是8位或9位(由SCC1寄存器的
M位决定),传输顺序是低位(LSB)在前。这是串行通信中需要特别注意的一点,与我们的书写习惯(高位在前)相反。 - 奇偶校验位:由SCC1寄存器的
PEN和PTY位控制是否启用及奇偶类型。它是一个简单的错误检测位,用于检查数据位中‘1’的个数是奇数还是偶数。注意:当启用奇偶校验时,对于8位数据模式(M=0),实际传输的是7个数据位+1个校验位;对于9位数据模式(M=1),则是8个数据位+1个校验位。校验位占据字符的最高位(第8或第9位)。 - 停止位:总是逻辑‘1’,标志着一次数据传输的结束。它也为接收端提供了重新同步和检测帧错误的机会。
为什么需要起始位和停止位?因为通信是异步的,接收方需要有一个明确的信号来界定一帧数据的开始和结束。起始位的下降沿是同步的触发点,停止位的高电平则确保在下一帧起始位到来之前,线路有一个最小的空闲时间(至少1位),同时也作为帧结构的“锚点”,如果这里检测到‘0’,就说明帧结构被破坏(帧错误)。
3. 发送器(Transmitter)深度剖析与配置实战
3.1 发送器工作流程与寄存器操作
发送器的核心是一个发送移位寄存器和一个作为缓冲区的SCI数据寄存器(SCDR)。SCDR对CPU是只写的。发送流程是一个典型的中断或轮询驱动过程:
- 初始化:设置好波特率寄存器
SCBR、控制寄存器SCC1(字符长度、奇偶校验等),然后置位ENSCI使能整个SCI模块,再置位SCC2中的TE位使能发送器。一旦TE被置位,发送器会立即在TxD引脚上输出一个前导码(Preamble),即连续10个(8位模式)或11个(9位模式)的‘1’(空闲电平)。这个前导码的作用是让接收方的时钟同步机制稳定下来。 - 启动发送:向
SCDR写入要发送的数据。这个动作会自动清除“发送器空(SCTE)”状态位。数据会从SCDR被加载到发送移位寄存器中。 - 移位发送:发送移位寄存器开始工作,按照设定的波特率,依次将起始位(0)、数据位(从LSB到MSB)、奇偶校验位(如果启用)、停止位(1)移位到TxD引脚上。
- 缓冲区空标志:当数据从
SCDR转移到发送移位寄存器的瞬间,SCTE状态位会被自动置1。这表示SCDR缓冲区已经空了,可以接受下一个要发送的字节。如果此时SCTIE(发送中断使能)位也为1,则会向CPU产生一个发送中断请求。 - 发送完成标志:当发送移位寄存器中的最后一个停止位也移出后,如果此时
SCDR也是空的(即没有新的数据等待发送),并且没有Break或Idle字符正在生成,那么“发送完成(TC)”状态位会被置1。如果TCIE位使能,也会产生中断。
注意事项:这里有一个关键顺序。手册中“初始化”步骤的第三步提到“Clear the SCI transmitter empty bit by first reading SCI status register 1 (SCS1) and then writing to the SCDR”。这描述的是一个清除特定状态标志的标准操作,但容易引起误解。实际上,
SCTE位是只读的状态位,我们无法直接“清除”它。正确的理解是:在首次发送前,为了确保发送逻辑处于正确的初始状态,一个常见的做法是先读取SCS1寄存器(这个读操作会清除某些可写清除的标志,但对SCTE无影响),然后再向SCDR写入数据。向SCDR写数据这个动作本身,就会导致SCTE位被硬件自动清零(表示缓冲区满),然后当数据被移入移位寄存器后,SCTE又会被置1(表示缓冲区空)。所以,更实用的流程是:判断SCTE是否为1(缓冲区空),如果是,则向SCDR写入数据。
3.2 发送特殊字符:Break与Idle
除了普通数据,发送器还能产生两种特殊的字符序列,它们在协议控制中非常有用。
- Break字符:通过置位
SCC2中的SBK位来发送。Break字符是一串连续的‘0’,没有起始位、停止位和校验位。其长度取决于M位(8位或9位数据模式)。只要SBK保持为1,发送器就会持续发送Break字符。当SBK被清零后,发送器会在发送完当前的Break字符后,自动在TxD上输出至少一个‘1’(空闲位)。这个自动的‘1’至关重要,它确保了接收方能够正确识别下一帧数据的起始位(下降沿)。- 应用场景:在诸如Modbus RTU等协议中,Break字符常用于表示一帧报文的开始,或用于复位/同步通信链路。
- Idle字符:Idle字符是一串连续的‘1’,同样没有起始、停止和校验位。有两种方式产生Idle字符:1) 每次传输开始时的前导码就是Idle字符;2) 在发送过程中,先清零再置位
TE位,可以“排队”一个Idle字符在当前字符发送完毕后发出。- 重要警告:手册特别强调,如果要排队发送Idle字符,必须在当前字符的停止位出现在TxD引脚之前将
TE位重新置1。如果操作晚了,之前写入SCDR的数据可能会丢失。一个安全的做法是:等待SCTE位变为1(表示SCDR已空,当前字符已全部移入移位寄存器),在写入下一个字节到SCDR之前,快速地“翻转”一下TE位(0->1)。
- 重要警告:手册特别强调,如果要排队发送Idle字符,必须在当前字符的停止位出现在TxD引脚之前将
3.3 发送器中断策略选择
发送器提供了两个中断源:SCTE(发送缓冲区空)和TC(发送完成)。如何选择取决于你的数据流控制方式。
- 使用
SCTE中断(更常用):这是一种“缓冲区空”中断。一旦SCDR的数据被转移到移位寄存器,SCTE置1,产生中断。在中断服务程序(ISR)中,你可以立即写入下一个要发送的字节。这种方式可以实现连续流式发送,只要ISR响应足够快,就能几乎无缝地填满发送缓冲区,最大化利用带宽。适用于需要连续发送大量数据的场景。 - 使用
TC中断:TC置1表示移位寄存器也空了,且没有新数据在SCDR中等待,整个发送链路完全空闲。这个中断更适合用于知道一包数据已完全发出的时刻,例如在发送完一个命令帧后,切换接收模式或进行超时判断。
在实际项目中,我通常只使能SCTE中断。在ISR中,从一个发送队列(软件FIFO)里取出下一个字节写入SCDR。如果队列为空,则关闭SCTE中断,避免空中断。当有新的数据需要发送时,先放入队列,如果发送器空闲(可以通过判断SCTE和队列状态),则手动写入第一个字节并打开SCTE中断,后续字节由中断自动处理。这种“队列+中断”的模式非常高效且易于管理。
4. 接收器(Receiver)核心机制与抗干扰设计
4.1 接收器工作流程与数据采样
接收器的核心是一个接收移位寄存器和一个只读的SCI数据寄存器(SCDR)。其工作流程与发送器对称但更复杂,因为它需要从异步的信号中可靠地恢复出数据。
- 初始化与使能:配置好波特率、帧格式后,置位
SCC2中的RE位使能接收器。接收器开始持续监测RxD引脚。 - 起始位检测与同步:接收器以16倍于波特率的频率(RT时钟)对RxD引脚进行采样。它不断地寻找一个下降沿(从‘1’到‘0’的跳变),这可能是起始位的开始。一旦检测到下降沿,RT时钟计数器从1开始计数。
- 起始位验证:在RT3、RT5、RT7这三个时刻(位于起始位的第3、5、7个采样点),接收器再次采样。如果这三个采样点中至少有两个是‘0’,则认为这是一个有效的起始位,否则认为是噪声干扰,重新开始搜索。这个“三取二”的多数表决机制,是抗噪声的第一道防线。
- 数据位与停止位采样:对于后续的每个数据位和停止位,接收器在RT8、RT9、RT10这三个中心位置进行采样。同样采用“三取二”的原则来决定该位的值是‘0’还是‘1’。如果三个采样值不一致,则置位噪声标志(NF),但数据位仍以多数表决结果为准。
- 数据就绪:当一个完整字符的所有位(包括停止位)都接收完毕后,数据位部分被并行加载到
SCDR中,同时接收器满(SCRF)状态位置1。如果SCRIE位使能,则产生接收中断。CPU应在下一个字符覆盖它之前读取SCDR。
4.2 波特率容错与同步机制
异步通信的可靠性严重依赖于收发双方的波特率匹配。MC68HC908的SCI接收器设计了一套聪明的容错和同步机制。
- 重同步(Resynchronization):接收器并非只在起始位进行同步。在接收一个字符的任何数据位期间,只要检测到从‘1’到‘0’的有效下降沿,它都会重置RT时钟计数器。这意味着,如果因为波特率微小偏差导致采样点逐渐漂移,那么数据位中的跳变可以将其“拉回”正轨。这个特性显著提升了波特率不匹配时的容忍度。
- 容错计算:手册中给出了慢速数据和快速数据的容错计算公式。简单来说,对于8位数据格式(无校验),接收器对停止位的采样需要154个RT时钟周期。如果发送方稍慢,只要在接收方计数到154时,发送方计数不小于147,就不会出错(容错约+4.54%)。如果发送方稍快,只要在接收方计数到154时,发送方计数不大于160,也不会出错(容错约-3.90%)。因此,最大的累积波特率误差应控制在约±4%以内,这是异步通信的一个经验值。在实际设计时,应通过精确计算分频值,将误差控制在1%以内,留足余量。
4.3 错误检测机制详解
接收器内置了四种错误检测标志,是调试通信问题的利器:
- 帧错误(FE, Framing Error):当接收器在停止位的预期位置采样到‘0’时,此位置1。Break字符也会导致FE置位,因为Break没有停止位。FE和SCRF同时置起。
- 噪声错误(NF, Noise Flag):在起始位、数据位或停止位的采样过程中,如果RT8、RT9、RT10三个采样点不一致(非全0或全1),则NF置位。这表明该位受到了噪声干扰,但接收器仍以多数表决的结果作为该位的值。
- 奇偶校验错误(PE, Parity Error):如果使能了奇偶校验(
PEN=1),接收器会计算接收到的数据位中‘1’的个数,并与接收到的校验位进行比较。如果不匹配(奇校验时‘1’的个数应为奇数,偶校验则为偶数),则PE置位。 - 溢出错误(OR, Overrun Error):这是最需要警惕的软件错误。当一个新的字符已经完全移入接收移位寄存器,但CPU还没有读取上一个存储在
SCDR中的字符时,就会发生溢出。此时,新的字符会丢失,SCDR中保留的还是旧字符,同时OR标志置位。这意味着你丢了一帧数据。
避坑指南:处理接收中断时,必须首先读取SCS1状态寄存器,然后再读取SCDR数据寄存器。因为读取SCS1会锁定当前的状态(对于OR、NF、FE、PE等标志),而读取SCDR会清除SCRF标志。如果你先读数据,再读状态,可能在两次读取之间又收到了新数据,导致状态标志被更新,你读到的就是新数据的错误状态,而丢失了旧数据的错误信息。标准的ISR写法是:
void SCI_RX_ISR(void) { uint8_t status = SCS1; // 首先读取状态,锁定当前错误标志 uint8_t data = SCDR; // 然后读取数据,清除SCRF // 接下来根据`status`变量中的标志位处理错误和数据 if (status & SCI_OR_MASK) { /* 处理溢出 */ } if (status & SCI_FE_MASK) { /* 处理帧错误 */ } // ... 处理有效数据 `data` }
4.4 接收器唤醒与多机通信
在多机通信(一主多从)网络中,为了降低从机的功耗,MC68HC908的SCI提供了硬件唤醒功能。
- 进入休眠:通过置位
SCC2中的RWU位,可以使接收器进入休眠状态。在此状态下,接收器仍能接收数据,但不会置位SCRF标志,也不会产生接收中断。 - 唤醒条件:唤醒由
SCC1中的WAKE位决定。- 空闲线唤醒(WAKE=0):当RxD引脚上出现连续10个(或11个,取决于
M位和ILTY位)‘1’(即一个完整的空闲字符)时,硬件自动清除RWU位,唤醒接收器。唤醒本身不会置位IDLE或SCRF标志。 - 地址标志唤醒(WAKE=1):当接收到的字符**最高位(MSB)为‘1’**时,硬件自动清除
RWU位,唤醒接收器,同时会置位SCRF标志。这通常用于“地址帧”唤醒。
- 空闲线唤醒(WAKE=0):当RxD引脚上出现连续10个(或11个,取决于
ILTY位的作用:此位仅用于空闲线唤醒模式。它决定空闲字符的计数器是从起始位之后开始计数(ILTY=0),还是从停止位之后开始计数(ILTY=1)。设置为ILTY=1可以避免前一帧数据末尾的一串‘1’被误判为空闲字符,但要求通信是严格同步的、帧与帧之间没有间隙。通常为了可靠,在异步通信中建议使用ILTY=0。
多机通信典型流程:
- 所有从机初始化SCI,设置
WAKE=1(地址标志唤醒),并置位RWU进入休眠。 - 主机发送一个“地址帧”,该帧数据的最高位(第8或第9位)设置为‘1’,数据部分包含目标从机的地址。
- 总线上所有从机都会被这个地址帧唤醒(因为MSB=1),并产生接收中断(SCRF置位)。
- 在每个从机的中断服务程序中,读取接收到的地址,与自身地址比较。
- 地址匹配的从机,清除
RWU位,保持唤醒状态,准备接收后续的“数据帧”(数据帧的MSB=0)。 - 地址不匹配的从机,重新置位
RWU,再次进入休眠,忽略后续数据帧。
这种机制极大地减少了非目标从机的功耗和CPU中断开销。
5. 寄存器详解与初始化配置实战
理解每个寄存器的每一位是进行精准控制的基础。下面我们结合实战,看看如何配置一个典型的SCI通信。
5.1 核心寄存器功能总览
MC68HC908GR8A/GR4A的SCI模块共有7个寄存器,地址从$0013到$0019:
- SCC1 ($0013):控制寄存器1,配置基础工作模式(循环、使能、极性、字符长度、唤醒方式、空闲检测、奇偶校验)。
- SCC2 ($0014):控制寄存器2,配置发送/接收使能、唤醒控制、Break发送以及各类中断的使能(发送空、发送完成、接收满、线路空闲)。
- SCC3 ($0015):控制寄存器3,包含第9数据位(R8/T8)和各类错误中断的使能(溢出、噪声、帧错误、奇偶错误)。
- SCS1 ($0016):状态寄存器1,只读,反映了发送器、接收器及错误标志的实时状态(发送空、发送完成、接收满、线路空闲、溢出、噪声、帧错误、奇偶错误)。
- SCS2 ($0017):状态寄存器2,只读,包含Break标志和接收进行中标志。
- SCDR ($0018):数据寄存器,读写分离。写操作写入发送缓冲区,读操作读取接收缓冲区。
- SCBR ($0019):波特率寄存器,配置预分频器和波特率分频值。
5.2 波特率计算与SCBR配置
波特率生成是初始化的第一步,也是最容易出错的一步。时钟树如下:
时钟源 (SCICLK) -> [预分频器 (/4 或 /16)] -> [波特率分频器 (13-bit)] -> 发送时钟 (TxC) / 接收时钟 (RxC = 16 * 波特率)- SCICLK来源:由配置寄存器
CONFIG2中的SCIBDSRC位选择,可以是内部总线时钟(BUSCLK)或外部时钟(CGMXCLK)。我们通常使用内部总线时钟。 - 预分频器:由
SCBR寄存器的SCP[1:0]位控制。SCP1:SCP0 = 00表示÷1,01表示÷3,10表示÷4,11表示÷6。注意:手册图13-3和图13-5中标注为“÷4”预分频器,但寄存器描述和公式表明有4种分频选择。这里需要以寄存器描述为准。 - 波特率分频器:由
SCBR寄存器的SCR[12:0](实际是SCR2, SCR1, SCR0组成的13位值)控制。它是一个分频系数。
波特率计算公式(根据手册推导):
波特率 = SCICLK / (预分频因子 * (32 * SBR))其中,SBR是13位分频器SCR[12:0]的值(1到8191)。SCICLK是所选时钟源的频率。
实战配置示例:假设系统总线时钟BUSCLK = 8 MHz,我们选择SCICLK = BUSCLK,目标波特率Baud = 9600。
- 选择预分频因子。为了得到较大的SBR值以提高精度,先尝试选择÷1(
SCP=00)。 - 计算SBR:
SBR = SCICLK / (预分频因子 * 32 * Baud) = 8,000,000 / (1 * 32 * 9600) ≈ 26.04 - SBR必须为整数,取整后
SBR = 26。 - 计算实际波特率:
实际波特率 = 8,000,000 / (1 * 32 * 26) ≈ 9615.38。 - 误差 =
(9615.38 - 9600) / 9600 ≈ 0.16%,远小于±4%,完全可接受。 - 因此,配置
SCP1:SCP0 = 00,SCR[12:0] = 26。
如果计算出的SBR小于1,则需要增大预分频因子(选择÷3、÷4或÷6)重新计算。SBR的值应尽可能大,这样对时钟源的抖动更不敏感。
5.3 一个完整的初始化代码示例
假设我们需要配置一个8位数据、无奇偶校验、1位停止位、波特率9600、使能发送和接收、使用接收中断和错误中断的SCI。
// 假设 BUSCLK = 8MHz #define BUSCLK_HZ 8000000UL #define BAUD_RATE 9600UL void SCI_Init(void) { // 1. 配置波特率寄存器 SCBR // 选择预分频 ÷1,计算SBR = 8M / (1 * 32 * 9600) ≈ 26 uint16_t sbr = (uint16_t)((BUSCLK_HZ) / (32UL * BAUD_RATE)); // 检查sbr是否在有效范围(1-8191)内,这里sbr=26,有效。 SCBR = 0x00; // 先清零 SCBR |= (0 << 6) | (0 << 5); // SCP1=0, SCP0=0, 预分频 ÷1 SCBR |= (sbr & 0x1F); // SCR[4:0] 放在低5位 // 注意:SCR[12:5] 在SCBR中不存在,MC68HC908GR8A的SCBR只有低6位有效(SCR[4:0]和SCP[1:0])。 // 查阅数据手册确认:SCBR只有8位,高2位是SCP[1:0],低6位是SCR[5:0]?这里需要根据实际手册修正。 // 根据提供的寄存器图($0019),Bit7,6是SCP1,SCP0,Bit5是保留,Bit4-0是SCR4-SCR0。 // 这意味着SCR只有5位(0-31)!这与之前13位的假设矛盾。 // 重新审视手册:图13-3和图13-5中的“BAUD DIVIDER”可能是一个简化的表示。 // 必须根据数据手册中SCBR寄存器的确切描述和波特率计算公式来配置。 // 这是一个关键点:不同型号MCU的波特率发生器结构可能不同。 // 假设经过查阅完整手册,确认公式为:Baud = SCICLK / (64 * (SBR+1)),其中SBR为5位。 // 则计算:SBR = (SCICLK / (64 * Baud)) - 1 = (8M/(64*9600)) -1 ≈ 12.02 -> 12 // 实际波特率 = 8M / (64 * 13) ≈ 9615.38,误差0.16%。 // 因此配置:SCP1:SCP0=00, SCR[4:0]=12 (0x0C) SCBR = (0x00) | 0x0C; // SCP=00, SCR=12 // 2. 配置控制寄存器 SCC1 // LOOPS=0: 正常模式;ENSCI=0: 先不使能(最后打开);TXINV=0: 不反转; // M=0: 8位数据;WAKE=0: 空闲线唤醒(本例不用唤醒);ILTY=0: 起始位后计数; // PEN=0: 禁用奇偶校验;PTY=0: 偶校验(未用) SCC1 = 0x00; // 3. 配置控制寄存器 SCC2 // 使能发送器(TE)和接收器(RE) // 使能接收中断(SCRIE),可选使能空闲中断(ILIE) // 不使能发送中断(SCTIE),我们采用轮询发送 // 不清醒唤醒(RWU=0),不发送Break(SBK=0) SCC2 = 0x0C; // 二进制 0000 1100,即 RE=1, TE=1 // 4. 配置控制寄存器 SCC3 // 使能错误中断:溢出(ORIE)、噪声(NEIE)、帧错误(FEIE)、奇偶错误(PEIE) SCC3 = 0x0F; // 二进制 0000 1111 // 5. 最后,使能SCI模块 SCC1 |= 0x40; // 设置ENSCI位 (bit 6) // 6. (可选)清除任何可能存在的初始状态标志 uint8_t dummy = SCS1; // 读SCS1以清除某些标志 dummy = SCDR; // 读SCDR以清除SCRF(如果存在) }重要提醒:以上波特率配置部分是一个示例,实际值必须根据具体型号的数据手册中提供的精确公式计算。MC68HC908系列不同型号的波特率发生器可能有差异。务必查阅你手中芯片的官方数据手册第13.8.7节(SCI Baud Rate Register)获取准确公式。
5.4 中断服务程序框架
一个健壮的接收中断服务程序需要处理数据接收和错误处理。
// 假设已定义好寄存器地址和位掩码 #define SCI_STATUS_RDRF_MASK 0x20 // 假设SCRF是SCS1的bit5 #define SCI_STATUS_OR_MASK 0x10 // 溢出错误 #define SCI_STATUS_FE_MASK 0x08 // 帧错误 #define SCI_STATUS_PE_MASK 0x04 // 奇偶错误 volatile uint8_t sci_rx_buffer[256]; volatile uint16_t sci_rx_head = 0; volatile uint16_t sci_rx_tail = 0; void __interrupt VectorNumber_SCI_Receive sci_rx_isr(void) { uint8_t status = SCS1; // 关键:先读状态! uint8_t data = SCDR; // 然后读数据,清除SCRF // 1. 检查错误 if (status & SCI_STATUS_OR_MASK) { // 溢出错误:数据丢失,需要软件处理,如重置缓冲区或标志错误 // 通常需要清空接收队列,因为当前数据可能是旧的 sci_rx_head = sci_rx_tail = 0; // 可以设置一个错误标志供主程序查询 } if (status & (SCI_STATUS_FE_MASK | SCI_STATUS_PE_MASK)) { // 帧错误或奇偶错误:当前接收的`data`不可信,应丢弃 // 可以记录错误计数 return; // 丢弃本帧数据 } // 噪声错误(NF)通常可忽略,因为数据位已通过多数表决纠正 // 2. 处理有效数据 if (!(status & SCI_STATUS_OR_MASK)) { // 如果没有溢出,存储数据 uint16_t next_head = (sci_rx_head + 1) % 256; if (next_head != sci_rx_tail) { // 缓冲区未满 sci_rx_buffer[sci_rx_head] = data; sci_rx_head = next_head; } else { // 缓冲区满,处理策略:丢弃最旧数据或新数据,或设置错误标志 } } }这个ISR框架实现了:1) 正确的状态/数据读取顺序;2) 错误检测与处理;3) 将有效数据存入环形缓冲区,供主程序非阻塞读取。
6. 低功耗模式与调试技巧
6.1 在WAIT和STOP模式下的行为
- WAIT模式:执行
WAIT指令后,CPU停止,但外设时钟通常仍在运行(取决于具体MCU的低功耗设计)。SCI模块如果被使能,将继续工作。任何已使能的SCI中断都可以将MCU从WAIT模式唤醒。如果不需要SCI在WAIT模式下工作,应在进入WAIT前将其禁用(ENSCI=0)以节省功耗。 - STOP模式:执行
STOP指令后,内部主时钟停止,几乎所有模块都掉电。SCI模块完全停止工作,当前的发送或接收会被中止并导致数据错误。在进入STOP模式前,务必确保没有正在进行的SCI通信。从STOP模式唤醒后,需要重新初始化SCI模块。
6.2 调试常见问题与排查技巧
收不到数据(发送方正常):
- 检查物理连接:TxD与RxD是否交叉连接?地线是否共地?
- 检查波特率:计算出的波特率分频值是否正确?双方波特率是否一致?用示波器测量TxD引脚波形,计算位宽是否与预期波特率相符。
- 检查帧格式:数据位、停止位、奇偶校验设置是否与对方一致?特别是LSB/MSB顺序(MCU通常是LSB first)。
- 检查接收器使能:
RE位是否置1?ENSCI位是否置1? - 检查中断/轮询:如果使用中断,中断向量是否正确?全局中断是否开启?如果使用轮询,是否在持续检查
SCRF位? - 检查引脚配置:PTE1/RxD引脚是否被其他功能(如通用I/O)占用?SCI使能后,方向自动设置为输入。
发送数据对方收不到(接收方正常):
- 检查发送器使能:
TE位是否置1?ENSCI位是否置1? - 检查发送缓冲区状态:是否在
SCTE=1(或TC=1)时才写入SCDR?写入后SCTE是否清零? - 用示波器看波形:这是最直接的方法。看TxD引脚是否有波形输出?波形是否符合预期的帧格式(起始位低,停止位高)?电平幅度是否正常?
- 检查发送器使能:
通信数据错乱:
- 首先检查错误标志:在接收中断中,第一时间读取
SCS1,检查OR,FE,PE,NF标志。溢出错误通常意味着CPU处理速度跟不上接收速度,需要优化代码或使用更大的缓冲区。帧错误和奇偶错误表明线路噪声或波特率严重不匹配。 - 检查波特率精度:计算实际波特率与理论值的误差。误差应尽可能控制在1%以内,特别是长距离通信时。
- 检查地线噪声:良好的共地是数字通信的基础。在电机控制等噪声大的环境中,考虑使用光耦或RS-485等差分信号进行隔离和传输。
- 首先检查错误标志:在接收中断中,第一时间读取
多机通信唤醒失败:
- 确认唤醒模式:
WAKE位设置是否正确(地址唤醒=1,空闲唤醒=0)? - 确认地址帧格式:发送的地址帧最高位(第9位或第8位,取决于
M)是否为1?从机是否在地址帧后正确清除了RWU? - 检查
ILTY位:如果使用空闲唤醒,ILTY位的设置是否与通信协议匹配?帧间是否有足够长的空闲时间(大于10/11个位时间)?
- 确认唤醒模式:
最后的小技巧:在项目初期,可以编写一个简单的“回环测试(Loopback)”程序。将SCC1的LOOPS位置1,同时使能发送器和接收器(TE=1,RE=1)。这样,发送的数据会直接内部环回到接收端。通过自发自收,可以快速验证SCI模块的软件配置是否正确,隔离硬件连接问题。测试通过后,再将LOOPS位清零,接入外部电路。
