C2000 SCI多处理器通信:空闲线与地址位模式深度解析与实践

1. 项目概述与核心价值

在嵌入式系统开发,尤其是工业控制、电机驱动和能源管理领域,德州仪器(TI)的C2000系列微控制器因其强大的实时控制能力而备受青睐。在这些复杂的系统中,微控制器往往不是孤岛,它需要与上位机、其他处理器模块、显示终端或传感器进行可靠的数据交换。这时,串行通信接口(SCI)就成为了连接这些节点的关键“血管”。

SCI,本质上就是大家熟知的UART(通用异步收发器)。它采用简单的两根线(TX和RX)实现全双工或半双工通信,协议简单,抗干扰能力在短距离内表现尚可,是嵌入式领域最基础、最广泛的通信方式之一。然而,当你需要构建一个由多个C2000芯片,或者C2000与其他处理器协同工作的系统时,简单的点对点UART通信就会显得力不从心。如何让一个“主讲者”(Talker)高效地将数据块发送给指定的“聆听者”(Listener),而其他设备又能保持静默、不被打扰?这就是多处理器通信模式要解决的核心问题。

C2000的SCI模块提供了两种优雅的解决方案:空闲线模式(Idle-Line)和地址位模式(Address-Bit)。这两种模式远不止是手册里的几行描述,它们直接决定了你系统通信的效率和可靠性。理解它们的工作原理、适用场景以及那些手册里可能一笔带过的“坑”,对于设计稳定可靠的分布式控制系统至关重要。本文将从一个实际开发者的角度,深入解析C2000 SCI模块,从最基础的UART帧格式讲起,逐步深入到多处理器通信的两种模式,并结合寄存器配置、DriverLib函数库的使用以及中断和FIFO操作,为你呈现一套可直接落地的实践指南。

2. SCI模块架构与基础原理拆解

在深入多处理器通信之前,我们必须先夯实基础,理解C2000 SCI模块的“五脏六腑”。很多人觉得UART简单,但往往是在配置波特率、数据位时出错,或者遇到数据丢失才回头补课。让我们先抛开复杂的多机协议,看看一个数据帧是如何被准确发送和接收的。

2.1 异步通信与NRZ帧格式

SCI通信是异步的,这意味着通信双方没有共享的时钟线。接收方必须从数据流中自己“提取”出时钟信息,实现同步。这全靠数据帧的特定格式。C2000 SCI采用NRZ(非归零)格式,一个完整的帧包含以下部分(通过SCICCR寄存器配置):

  • 起始位(Start Bit):一个逻辑低电平,标志着帧的开始。这是接收方同步的基准点。
  • 数据位(Data Bits):有效载荷,长度可配置为1到8位(SCICCR.SCICHAR)。通常使用8位(一个字节)。
  • 奇偶校验位(Parity Bit,可选):用于简单的错误检测。由SCICCR.PARITYENA使能,SCICCR.PARITY决定奇校验还是偶校验。在工业噪声环境中,开启校验是个好习惯。
  • 地址/数据位(Address/Data Bit,仅地址位模式):这是多处理器通信的关键,后面会详细讲。
  • 停止位(Stop Bit(s)):1个或2个逻辑高电平(SCICCR.STOP BITS),标志着帧的结束,并为接收方提供准备下一帧的时间。

接收方如何识别起始位呢?它持续采样RX引脚。当检测到连续4个SCICLK周期(SCI内部波特率时钟)的低电平时,才确认为有效的起始位,否则重新开始寻找。对于起始位之后的每个数据位,接收方会在该比特位的中间时刻(第4、5、6个SCICLK周期)采样三次,采取“多数表决”原则(三取二)来确定该位的值是0还是1。这种机制提供了良好的抗噪能力。

注意:这里的SCICLK是模块内部时钟,由LSPCLK(低速外设时钟)通过波特率发生器分频得到。确保LSPCLK时钟源正确且稳定,是通信成功的基石。一个常见的错误是系统时钟配置后,忘记使能或正确配置LSPCLK的分频(通过SysCtl_setClock相关函数),导致实际波特率与预期不符。

2.2 核心寄存器与双缓冲机制

理解SCI的“双缓冲”机制是避免数据覆盖或丢失的关键。从图25-2的模块框图中,我们可以看到两个核心的“数据站”:

  • 发送端SCITXBUF(发送数据缓冲寄存器) ->TXSHF(发送移位寄存器)。CPU将待发送数据写入SCITXBUF。当TXSHF空闲时,SCITXBUF中的数据会自动加载到TXSHF中,然后由硬件逐位移出到SCITXD引脚。SCITXBUF为空后,TXRDY标志位会置1,表示可以写入下一个数据。
  • 接收端RXSHF(接收移位寄存器) ->SCIRXBUF(接收数据缓冲寄存器)。从SCIRXD引脚移入的完整帧数据,会从RXSHF自动转存到SCIRXBUF中,并置位RXRDY标志。CPU从SCIRXBUF读取数据后,RXRDY自动清零。

双缓冲的意义在于:当TXSHF正在移出前一个字节时,CPU可以提前将下一个字节写入SCITXBUF排队,从而提高了连续发送的效率。接收端亦然,SCIRXBUF保存了已接收的字节,即使CPU暂时没来读取,RXSHF也可以开始接收下一个字节,直到SCIRXBUF被填满(如果使能了FIFO,则是FIFO满)。

2.3 波特率计算:一个必须算对的数字

波特率配置错误是通信失败的最常见原因。C2000 SCI的波特率由16位的波特率选择寄存器SCIHBAUDSCILBAUD共同决定。计算公式如下:波特率 = LSPCLK / [(BRR + 1) * 8]其中,BRRSCIHBAUD:SCILBAUD组成的16位整数值。

实操要点

  1. 先算LSPCLKLSPCLK通常由系统时钟SYSCLK分频得到。例如,SYSCLK=200MHz,低速外设时钟预分频器设置为LSPCLK = SYSCLK / 4,则LSPCLK = 50MHz
  2. 反求BRR:根据目标波特率计算BRR。例如,目标波特率为115200LSPCLK=50MHz,则:BRR = LSPCLK / (波特率 * 8) - 1 = 50,000,000 / (115200 * 8) - 1 ≈ 54.25 - 1 = 53.25取整后BRR = 53
  3. 计算实际波特率与误差:将BRR=53代回公式,实际波特率 =50,000,000 / [(53+1)*8] ≈ 115,740.7。误差 =(115740.7 - 115200) / 115200 ≈ 0.47%。通常误差在2%以内,通信是可靠的。
  4. 使用DriverLib:TI提供的DriverLib库函数SCI_setBaudRate()会自动完成上述计算和寄存器配置,但了解原理对于调试至关重要。当通信不稳定时,首先应验算波特率误差。

3. 多处理器通信模式深度解析

现在进入核心主题。当总线上挂载了多个设备(处理器)时,我们如何实现定向通信?广播数据让所有设备都接收并中断CPU,显然效率低下且增加各CPU的负担。C2000 SCI的多处理器模式巧妙地解决了这个问题,其核心思想是:让非目标设备在数据块传输期间“休眠”,忽略数据帧,只有目标设备被“唤醒”并处理整个数据块。

3.1 空闲线模式(Idle-Line Mode)

这种模式通过帧之间的空闲时间长短来区分数据块。它规定:如果一个帧结束后,总线保持高电平(空闲)的时间达到或超过10个比特位的时间,那么下一个帧就被认为是新数据块的地址帧

工作流程

  1. 所有从设备初始化时,都将其SCI的SLEEP位(SCICTL1.2)置1,进入“休眠”状态。在此状态下,接收器虽然仍在工作,但只有检测到地址帧时,才会置位RXRDY或产生接收中断。
  2. 主设备要发送数据块给某个从设备。它首先发送一个地址帧,并在该帧之前,确保总线有至少10个比特位的空闲时间。
  3. 总线上所有从设备都会检测到这个长空闲后的地址帧,并产生中断。
  4. 在每个从设备的中断服务程序(ISR)中,软件读取SCIRXBUF中的地址字节,并与自身预设的地址进行比较。
  5. 地址匹配的从设备:清除自身的SLEEP位(置0),从而“唤醒”自己的SCI接收器。随后,主设备发送的数据帧都会被该从设备正常接收并产生中断。
  6. 地址不匹配的从设备:保持SLEEP位为1,继续“休眠”。对于后续的数据帧,其SCI接收器不会置位RXRDY,也不会产生中断,CPU完全不受干扰,直到下一个长空闲(新数据块开始)出现。

如何产生“长空闲”?主设备有两种方法:

  • 软件延时:在发送完上一个数据块的最后一帧后,延迟一段时间(超过10个比特位时间),再发送地址帧。这种方法简单,但会浪费总线时间。
  • 利用TXWAKE:这是更高效的方法。主设备在写入地址帧到SCITXBUF之前,先将TXWAKE位(SCICTL1.3)置1,然后先向SCITXBUF写入一个任意值(dummy data)。当这个任意值帧被发送时,硬件会自动在其前面插入一个恰好11个比特位的空闲周期。发送完成后,TXWAKE自动清零。接着,主设备再写入真正的地址帧。这种方式产生的空闲时间精确且不浪费额外时间。

关键避坑点:在空闲线模式下,从设备的RX中断服务程序(ISR)必须足够快。手册中有一个非常重要的警告:如果CPU读取SCIRXBUF(或FIFO)中所有数据的时间超过了10个比特位的时间,SCI模块可能会错过检测下一个数据块开始的“长空闲”信号。这是因为RXWAKE逻辑只在首次识别到10比特空闲时置位一次,即使总线之后继续保持空闲,读取SCIRXBUF清空条件后也不会再次置位。解决方案

  1. 优化ISR:RX ISR只做最必要的工作——将数据从SCIRXBUF或FIFO快速搬运到内存中的软件缓冲区。所有解析、处理逻辑应放在主循环或低优先级任务中。
  2. 软件复位接收器:在ISR末尾,读取完所有数据后,执行一次SCI_softResetReceiver()(通过设置SCICTL1.SWRESET位)来复位接收状态机,确保能检测下一个起始信号。
  3. 检查RXWAKE:在ISR中读取数据前,先检查SCIRXST.RXWAKE位。如果该位被置位(表示检测到块开始),则在ISR结束时不要SLEEP位置1(即保持唤醒状态),以准备接收紧随其后的地址帧。

3.2 地址位模式(Address-Bit Mode)

这种模式在每一个数据帧中都增加了一个额外的位——地址位(Address Bit),紧跟在最后一个数据位之后。通过这个位的值来区分帧的类型:

  • 地址位 = 1:该帧是地址帧。
  • 地址位 = 0:该帧是数据帧。

工作流程

  1. 同样,所有从设备初始化时将SLEEP位置1。
  2. 主设备发送数据块。它首先发送地址帧,并通过硬件自动将其地址位置1。
  3. 所有从设备收到地址帧(因为地址位为1)都会产生中断。
  4. 从设备在ISR中比较地址。匹配者清除SLEEP位,准备接收后续数据帧;不匹配者保持SLEEP位为1。
  5. 主设备发送后续数据帧,硬件自动将它们的地址位置0。
  6. 处于“休眠”(SLEEP=1)的从设备,其SCI对地址位为0的帧完全无视,不会产生任何状态标志或中断。只有被唤醒的从设备才会处理这些数据帧。

如何设置地址位?主设备通过TXWAKE位控制。当TXWAKE=1时,写入SCITXBUF的数据帧,其地址位会被硬件设置为1。发送完成后,TXWAKE自动清零。因此,发送地址帧的代码顺序是:置TXWAKE=1->写地址值到SCITXBUF。发送数据帧时,TXWAKE保持为0即可。

模式选择建议

  • 地址位模式:适用于数据块较小(通常建议小于11字节)的场景。因为每个帧都额外增加了一个地址位,对于大数据块来说开销比例较大。但其优点是数据块之间无需等待空闲时间,流式传输效率高。
  • 空闲线模式:适用于数据块较大(通常大于12字节)的场景。它没有每帧的额外比特开销,但对于块间的间隔有严格要求。更适合于突发性的大数据包传输。
特性对比空闲线模式 (Idle-Line)地址位模式 (Address-Bit)
区分方式帧间空闲时间(≥10 bits)帧内地址位(1=地址,0=数据)
额外开销块间有固定空闲时间开销每帧增加1比特开销
适用场景大数据块(>12字节)传输小数据块(≤11字节)传输
软件复杂度需注意ISR速度,防错过起始相对简单,时序要求宽松
总线利用率块间有闲置,适合突发传输连续传输,流式效率高

4. 基于DriverLib的配置与通信实践

理解了原理,我们来看如何用代码实现。TI的C2000 DriverLib库封装了寄存器操作,让开发更便捷。但知其然更要知其所以然,我们结合寄存器来看函数背后的逻辑。

4.1 SCI模块初始化配置

一个完整的SCI初始化,通常包括引脚复用、模块使能、波特率设置、帧格式配置、中断和FIFO设置。以下是一个配置SCI-A为115200波特率、8位数据、无校验、1位停止位、使能FIFO并准备用于空闲线多处理器通信的示例:

#include "driverlib.h” #include “device.h” void initSCI_A(void) { // 1. 使能SCI-A模块时钟 (在PCLKCR寄存器中) SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_SCIA); // 2. 配置GPIO引脚为SCI功能 // 注意:为避免引脚电平毛刺,先配置GPyGMUX,再配置GPyMUX // 假设使用GPIO28(SCIRXDA)和GPIO29(SCITXDA) GPIO_setPinConfig(GPIO_28_SCIRXDA); GPIO_setPinConfig(GPIO_29_SCITXDA); // 对于输入引脚(RX),建议将输入限定设置为异步模式,以忽略同步时钟 GPIO_setQualificationMode(28, GPIO_QUAL_ASYNC); // GPIO28 输入异步 // 3. 初始化SCI模块配置结构体 SCI_Config sciConfig; sciConfig.baudrate = 115200; // 目标波特率 sciConfig.wordLength = SCI_WORDLENGTH_8BITS; // 8位数据 sciConfig.stopBits = SCI_STOPBITS_ONE; // 1位停止位 sciConfig.parity = SCI_PARITY_NONE; // 无校验 sciConfig.mode = SCI_MODE_IDLE_LINE; // 空闲线多处理器模式 // 也可选择 SCI_MODE_ADDR_BIT 或 SCI_MODE_SOFTWARE(普通异步模式) sciConfig.enableLoopback = false; // 禁用回环测试 sciConfig.enableTx = true; // 使能发送 sciConfig.enableRx = true; // 使能接收 // 4. 应用配置 SCI_initModule(SCIA_BASE, &sciConfig); // 5. 配置FIFO(强烈推荐使用,减轻CPU中断负担) SCI_enableFIFO(SCIA_BASE); // 使能FIFO SCI_resetTxFIFO(SCIA_BASE); // 复位发送FIFO SCI_resetRxFIFO(SCIA_BASE); // 复位接收FIFO // 设置接收FIFO中断触发级别:当RX FIFO中有4个或更多数据时产生中断 SCI_setFIFOInterruptLevel(SCIA_BASE, SCI_FIFO_RX_LEVEL_4, SCI_FIFO_TX_LEVEL_0); // 使能基于FIFO的接收中断(替代传统的RXRDY中断) SCI_enableInterrupt(SCIA_BASE, SCI_INT_RXFF); // 6. 使能SCI模块(此操作会置位SWRESET,完成软复位后模块开始工作) SCI_enableModule(SCIA_BASE); // 7. 配置PIE(外设中断扩展)向量表,将SCIA的RX中断服务程序关联起来 // 假设使用SCIA的RXFF中断(对应PIE组9,中断1) Interrupt_register(INT_SCIA_RX, &sciaRxFifoIsr); Interrupt_enable(INT_SCIA_RX); SCI_enableInterrupt(SCIA_BASE, SCI_INT_RXFF); // 确保模块级中断使能 // 8. 如果作为从设备,进入多处理器“休眠”状态 // SCI_setSleepMode(SCIA_BASE, true); // 设置SLEEP位为1 }

关键寄存器与DriverLib函数映射解析: 从你提供的Table 24-19可以看到DriverLib如何封装寄存器操作。例如:

  • SCI_setConfig():这个函数是“集大成者”,它内部会根据传入的sciConfig结构体,配置SCICCR(数据格式)、SCIHBAUD/SCILBAUD(波特率)、SCICTL1/2(控制位)等多个寄存器。理解这个函数在做什么,比单独操作每个寄存器更高效。
  • FIFO相关函数:SCI_enableFIFO()对应配置SCIFFTXSCIFFRX寄存器中的SCIFFENA位。SCI_setFIFOInterruptLevel()则配置SCIFFTXSCIFFRX中的TXFFILRXFFIL位。使用16级深度的FIFO可以大幅减少中断频率,提升系统实时性。
  • SCI_enableTriWire():这个函数对应SPIPRI寄存器的TRIWIRE位,但请注意,这是SPI模块的三线模式使能,并非SCI功能。你提供的资料开头部分是关于SPI的,这是一个需要区分的关键点。SCI是两线(RX/TX)异步通信,没有三线模式。

4.2 数据收发与中断处理实践

配置完成后,数据的收发通常通过中断驱动。以下是基于FIFO的接收中断服务程序示例,它演示了在多处理器模式下如何解析地址帧:

// 假设的从设备地址 #define MY_SLAVE_ADDR 0x55 volatile bool gSciaRxReady = false; uint16_t gSciaRxBuffer[256]; uint16_t gSciaRxIndex = 0; __interrupt void sciaRxFifoIsr(void) { uint32_t status = SCI_getInterruptStatus(SCIA_BASE); SCI_clearInterruptStatus(SCIA_BASE, status); // 检查是否是RX FIFO中断(达到预设触发级别) if (status & SCI_INT_RXFF) { // 1. 读取RX FIFO状态,获取当前有多少数据 uint16_t rxLevel = SCI_getRxFIFOStatus(SCIA_BASE) & SCI_FIFO_RX_LEVEL_MASK; // 2. 循环读取所有可用的数据 for(int i = 0; i < rxLevel; i++) { uint16_t rxData = SCI_readDataNonBlocking(SCIA_BASE); // 非阻塞读取 // 3. **关键:检查当前是否处于“唤醒”状态** // 通过检查SCIRXST.RXWAKE位(在地址位模式下),或通过软件状态机(在空闲线模式下) // 这里以地址位模式为例,假设我们之前设置了SCI_MODE_ADDR_BIT uint16_t rxStatus = SCI_getRxStatus(SCIA_BASE); bool isAddressFrame = (rxStatus & SCI_RXSTATUS_RXWAKE) ? true : false; if(isAddressFrame) { // 收到地址帧 if(rxData == MY_SLAVE_ADDR) { // 地址匹配,唤醒本机SCI,准备接收后续数据 SCI_setSleepMode(SCIA_BASE, false); // 清除SLEEP位 gSciaRxIndex = 0; // 重置接收缓冲区索引 gSciaRxBuffer[gSciaRxIndex++] = rxData; // 可选:存储地址字节 } else { // 地址不匹配,确保进入休眠,忽略后续数据帧 SCI_setSleepMode(SCIA_BASE, true); // 设置SLEEP位 } } else { // 收到数据帧 // 只有SLEEP位为0(已唤醒)的设备才会执行到这里 if(SCI_getSleepMode(SCIA_BASE) == false) { // 存储数据到缓冲区 if(gSciaRxIndex < 256) { gSciaRxBuffer[gSciaRxIndex++] = rxData; } // 这里可以添加判断数据块结束的逻辑,例如收到特定结束符或达到预定长度 // 然后置位全局标志,让主循环处理数据 } // 如果SLEEP位为1,则此数据帧被硬件自动忽略,不会进入此分支 } } // 4. 数据处理完成后,可以重新使能FIFO中断(如果之前关闭了) // 并设置全局标志通知主循环 gSciaRxReady = true; } // 必须确认中断已处理 Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP9); }

发送数据块(主设备)示例

void sendDataBlockToSlave(uint16_t slaveAddr, uint16_t *data, uint16_t length) { // 1. 确保使用正确的多处理器模式(例如空闲线模式) // 2. 发送块起始信号(以空闲线模式为例,使用TXWAKE) SCI_setWakeMode(SCIA_BASE, true); // 设置TXWAKE位 SCI_writeDataBlockingNonFIFO(SCIA_BASE, 0x00); // 发送任意数据,触发产生11位空闲 // 注意:writeDataBlockingNonFIFO会等待TXRDY,在FIFO模式下可使用FIFO相关函数 // 3. 发送地址帧 SCI_setWakeMode(SCIA_BASE, false); // 发送地址帧前,TXWAKE应已自动清零,这里显式确保 // 在地址位模式下,发送地址帧前需要设置TXWAKE=1,硬件会自动将地址位置1 // SCI_setWakeMode(SCIA_BASE, true); // 地址位模式需此步 SCI_writeDataBlockingNonFIFO(SCIA_BASE, slaveAddr); // 4. 发送数据帧 for(uint16_t i = 0; i < length; i++) { // 在地址位模式下,发送数据帧时TXWAKE应为0,硬件自动将地址位置0 // SCI_setWakeMode(SCIA_BASE, false); // 地址位模式需此步(通常保持为0即可) SCI_writeDataBlockingNonFIFO(SCIA_BASE, data[i]); // 在实际应用中,建议使用非阻塞或FIFO方式发送,并检查发送完成状态或使用中断 // 以避免长时间阻塞。例如: // while(SCI_isBusy(SCIA_BASE)) {}; // 等待发送完成(简单轮询) } }

5. 常见问题排查与实战经验

在实际项目中,SCI通信问题层出不穷。以下是我在多年调试中总结的典型问题及解决方法。

5.1 通信完全无反应(收不到也发不出)

  • 检查清单
    1. 时钟与电源:确认SYSCLKLSPCLK已正确配置并使能。使用调试器查看相关时钟控制寄存器的值。
    2. 引脚复用:这是最高频的错误。确认GPIOxMUXGPIOxGMUX寄存器已正确配置为SCI功能。务必遵循手册顺序:先配GMUX,再配MUX
    3. 模块使能:确认PCLKCR寄存器中对应SCI模块的时钟使能位已置位。SCI_enableModule()函数内部会操作SCICTL1.SWRESET,确保模块结束复位状态。
    4. 硬件连接:检查TX和RX是否交叉连接(本机TX接对端RX)。测量引脚是否有波形。如果使用RS-232或RS-485电平转换芯片,检查其供电和使能端。

5.2 能发送但不能接收,或接收数据乱码

  • 首要怀疑对象:波特率。计算LSPCLKBRR值,确保发送和接收双方配置完全一致。误差最好控制在2%以内。使用示波器测量一个字节的时长,反算实际波特率进行验证。
  • 帧格式不匹配:检查双方的数据位长度、停止位数、校验位设置是否一致。一个8位无校验1停止位的设备,无法正确解析一个7位偶校验2停止位的设备发来的数据。
  • 电气电平问题:如果通信距离较远(超过1米),考虑使用RS-485差分信号。单端TTL/CMOS电平抗干扰能力差。检查地线是否连接良好,共地是基础。
  • FIFO或中断配置错误:如果使能了FIFO,但中断触发级别RXFFIL设置得过高(比如15),而对方只发送了少量数据,则无法触发中断。建议初始调试时先禁用FIFO,使用基本的RXRDY中断,或者将RXFFIL设为1。

5.3 多处理器模式下,从设备无法被正确唤醒

  • 模式设置错误:主从设备的SCICCR.3 (ADDR/IDLE MODE)位必须设置为相同的模式(同为0-空闲线,或同为1-地址位)。
  • 地址比对失败:在从设备的ISR中,检查读取的地址字节是否正确。注意字节序(MSB/LSB)问题。确保从设备预设的地址与主设备发送的地址完全一致。
  • SLEEP位管理不当
    • 从设备初始化后必须置SLEEP=1
    • 地址匹配后,必须在ISR中手动清除SLEEP=0
    • 一个数据块接收完毕后,从设备必须重新置SLEEP=1,以准备监听下一个地址帧。这个“接收完毕”的判断逻辑需要你在软件中实现,例如基于特定结束符、固定包长或超时机制。
  • 空闲线模式的“10比特空闲”条件不满足:主设备产生的空闲时间不足10个比特位。如果使用TXWAKE方式,确保在写入地址帧之前,先写入了一个dummy数据并等待其发送完成。用示波器观察总线,确认两个帧之间的高电平时间是否足够。

5.4 中断响应不及时导致数据丢失或错误

这是实时系统中的经典问题,尤其是手册中强调的“SCI模块中断反应时间”问题。

  • 症状:偶尔出现帧错误(FE)、奇偶校验错误(PE)或断点检测错误(BRKDT),特别是在高波特率下。
  • 根因:如手册所述,RX中断在停止位被检测到约7/8比特时间后才触发。留给ISR的执行时间窗口非常短(约1/8比特时间)。如果ISR复杂、耗时,或者被更高优先级中断嵌套,导致在下一个字节的起始位到来前未能完成,SCI就会错误地对齐起始位,引发连续错误。
  • 解决方案
    1. 精简RX ISR:ISR只做“搬运工”,将数据从SCIRXBUF或FIFO快速复制到全局数组(环形缓冲区)中。所有协议解析、业务处理都放到后台主循环中。
    2. 提升波特率容错:如果条件允许,与通信对方协商,使用2个停止位。这为你赢得了额外的1个比特位时间来处理ISR。
    3. 降低波特率:在满足业务需求的前提下,降低波特率可以等比例增加每个比特位的时间,从而放宽对ISR执行时间的苛刻要求。
    4. 发送方增加延迟:如果可以控制发送设备(如另一个MCU或PC软件),在发送每个字节后增加一个小的软件延时,人为拉大数据帧间隔。

5.5 FIFO使用中的陷阱

  • 发送FIFO空中断TXFFIL设置为0时,表示发送FIFO完全空时才产生中断。这对于需要持续流式发送的场景很有效。但在发送最后一个数据包后,如果你等待发送完成中断,要注意这可能意味着FIFO和移位寄存器TXSHF都已空。
  • 接收FIFO溢出:如果RX FIFO已满(收到16个数据),而CPU未及时读取,后续接收的数据会导致溢出错误(RXFFOVF标志置位),并且新数据会丢失。必须在ISR中及时读取数据,或者提高RXFFIL中断触发级别,让ISR更早被调用。
  • FIFO与非FIFO模式中断标志:使能FIFO后,应使用SCI_INT_RXFFSCI_INT_TXFF中断,而不是传统的SCI_INT_RXRDYSCI_INT_TXRDYRXRDYTXRDY标志的行为在FIFO模式下会发生变化。

最后,调试SCI这类外设,示波器或逻辑分析仪是最得力的助手。直接抓取TX/RX引脚上的波形,可以直观地看到起始位、数据位、停止位、空闲时间、地址位(如果使能)是否完全符合预期。它能帮你快速定位是软件配置问题,还是硬件连接问题,抑或是复杂的时序问题。将理论、代码和实际信号三者结合,是解决一切嵌入式通信难题的不二法门。