1. 串行通信基础为什么我们需要SCI和SPI在嵌入式系统开发中无论是让单片机读取一个温湿度传感器的数据还是驱动一块TFT屏幕显示图像设备之间的“对话”都离不开串行通信。想象一下如果每个设备之间都用8根、16根甚至32根线并行连接来传输数据你的电路板会瞬间变成一团乱麻成本、功耗和物理空间都会成为大问题。串行通信的核心思想就是“化繁为简”把数据排成一队通过一根或少数几根数据线一位一位地按顺序发送出去。这就像把一队士兵从宽阔的大马路并行总线转移到一条独木桥串行总线上过河虽然单个士兵通过的速度慢了但架桥的成本和复杂度大大降低在大多数场景下整体的效率和可靠性反而更高。串行通信主要分为两大阵营异步通信和同步通信。它们的核心区别在于“如何同步”。异步通信比如我们常说的UART通用异步收发器或本文要讲的SCI串行通信接口它没有统一的时钟线。发送方和接收方事先约定好一个速度波特率然后数据包自带“起跑信号”起始位和“终点线”停止位。接收方就靠着这些标志和约定的速度努力对齐并解读数据。这种方式硬件简单只需要两根线TX和RX就能实现全双工非常适合单片机与电脑串口调试、GPS模块、蓝牙模块等设备通信。但它的缺点也很明显对时钟精度要求高如果双方时钟偏差累积就容易读错数据。而同步通信以SPI串行外设接口为代表则提供了一条“节拍器”线——时钟线SCK。主设备控制着时钟节拍从设备严格跟着这个节拍来发送或接收每一位数据。这就好比乐队指挥主设备打着拍子乐手从设备们跟着拍子演奏节奏完全同步几乎不会出错。SPI通常需要四根线SCK, MOSI, MISO, SS能实现高速的全双工通信广泛用于连接Flash存储器、SD卡、触摸屏、各类传感器等。它的缺点是线多且一个主设备通常需要为每个从设备单独提供一根片选线SS当从设备很多时会占用大量IO口。所以选择SCI还是SPI不是一个谁好谁坏的问题而是一个“合适”的问题。如果你需要远距离、抗干扰、设备简单的通信SCIUART是经典选择。如果你追求板内设备间的高速、可靠数据交换SPI则是更优解。接下来我们就深入这两种接口的“五脏六腑”看看它们是如何工作的以及在配置和使用时有哪些必须注意的“坑”。2. SCI接口深度解析从帧结构到容错机制SCI在Freescale现NXP的微控制器中通常指的就是其UART模块的实现。理解SCI核心在于理解它的异步帧格式和内在的同步与容错机制。2.1 帧结构数据是如何被打包的一个标准的SCI数据帧绝不是简单地把数据位扔到线上。为了保证接收方能正确识别它被精心包装成一个“数据包”空闲状态数据线TXD/RXD通常保持在高电平逻辑1。起始位一个比特时间的低电平逻辑0。这是接收方的“起床铃”告诉它“注意一帧数据要开始了”接收器检测到这个下降沿就会启动内部计时准备采样后续数据。数据位紧接着起始位之后就是实际要传输的数据可以是5、6、7、8或9位具体由配置决定。通常我们传输一个字节8位LSB最低有效位先行。奇偶校验位可选用于简单的错误检测。发送方根据数据位中“1”的个数计算并附加一个校验位使整个帧包括校验位中“1”的个数为奇数奇校验或偶数偶校验。接收方重新计算并比对不一致则报告校验错误。停止位1位、1.5位或2位的高电平。它标志着一帧的结束并确保线路恢复到空闲状态高电平为检测下一个起始位的下降沿做好准备。注意起始位和停止位带来了额外的开销。比如传输一个8位数据无校验1位停止位那么一帧总共是10位。如果你以9600波特率每秒9600比特通信实际有效数据速率只有 9600 * (8/10) 7680 bps。这是计算实际吞吐量时必须考虑的。2.2 波特率容错时钟不匹配时通信如何不“跑偏”这是SCI设计中最精妙的部分之一。理想情况下发送器和接收器使用完全相同的波特率时钟。但现实中晶振有误差温度会漂移双方的时钟总有微小差异。如果接收方采样点随着时间累积慢慢滑到了数据位的边缘甚至外部就会采样到错误的值导致通信失败。SCI硬件通过两个机制来对抗这种“漂移”帧内重同步接收器并非只在每个比特开始时采样一次。以常见的16倍过采样为例它将一个比特时间划分为16个RT接收器定时器时钟周期。它会在每个比特时间的中间段如第8、9、10个RT周期进行三次采样以“少数服从多数”的原则确定该比特的值例如两次高一次低则判为高。更重要的是接收器会在帧内的每个有效下降沿如起始位、数据位从1到0的跳变进行重同步重新对齐自己的RT时钟。这就像跑步时你不仅看着终点还会在途中不断瞄着领跑者调整自己的步伐。波特率容错计算即使有重同步如果收发双方时钟差异太大累积误差仍可能导致在停止位采样时“踩空”。文档中给出的“慢速数据容忍度”和“快速数据容忍度”计算定量地告诉了我们这个安全范围有多大。以传输8位数据无校验1位停止位为例接收器需要9个比特时间 * 16 RT周期 7个RT周期 151个RT周期来开始对停止位进行采样从起始位下降沿算起到RT8时刻。而发送器发送完这8位数据和一个停止位需要9个比特时间 * 16 RT周期 144个RT周期。慢速容忍度如果接收器时钟比发送器慢那么当发送器发完帧时144 RTt接收器才数到151 RTr。允许的最大误差是(151 - 144) / 151 ≈ 4.63%。这意味着接收器时钟即使比发送器慢4.63%仍能正确采样到停止位。快速容忍度如果接收器时钟比发送器快计算方式类似容忍度约为4.375%。实操心得这个4.6%左右的容限是理论极限在实际工程中必须留足余量。通常我们会确保收发双方的波特率误差控制在2%以内尤其是通信距离较长或环境干扰较大时。选择晶振时要关注其精度如±50ppm即±0.005%。很多通信失败排查到最后就是晶振精度不够或波特率计算寄存器配置有误。2.3 错误处理与唤醒机制SCI硬件能自动检测几种常见错误噪声错误对停止位的三次采样值RT8, RT9, RT10不一致。帧错误在停止位应出现的位置采样到的主要是逻辑0即没有检测到有效的停止位高电平。这通常意味着波特率严重不匹配或线路受到严重干扰。溢出错误前一帧数据还未被CPU读取新一帧数据已经接收完毕并要覆盖数据寄存器导致旧数据丢失。在多接收器系统中如一主多从SCI支持唤醒功能。通过设置RWU位可以让接收器进入“待机”状态忽略总线上的普通数据。唤醒方式有两种空闲线唤醒当总线出现连续的高电平空闲状态时唤醒所有接收器。适用于消息间用空闲字符分隔的协议。地址标记唤醒当检测到一个数据帧的最高位MSB为1时将该帧识别为地址帧并唤醒接收器。接收器解析地址如果是发给自己的则继续接收后续数据帧否则再次进入待机。这种方式允许消息中包含空闲字符效率更高。3. SPI接口深度解析主从时钟同步的艺术如果说SCI是两位约定好步调的跑者那SPI就是一位指挥和一群乐手。SPI的同步特性使其速度远超UART轻松达到几十Mbps但其配置也更为复杂核心就在于理解时钟极性CPOL和时钟相位CPHA。3.1 SPI总线信号与主从架构一个典型的SPI总线包含四根线SCK串行时钟由主设备产生。MOSI主设备输出从设备输入。MISO主设备输入从设备输出。SS/CS从设备选择低电平有效。主设备通过拉低对应从设备的SS线来选中它。SPI是全双工的。数据在SCK的边沿驱动并在相对的边沿被采样。主设备和被选中的从设备同时通过MOSI和MIO线交换数据。这意味着主设备在发送一个字节的同时也会收到从设备返回的一个字节。即使你只想发送命令也会读回一个字节可能是从设备的状态或无用数据反之亦然。3.2 时钟模式CPOL与CPHASPI配置的“万恶之源”这是SPI最让人困惑也最关键的地方。CPOL和CPHA两个位的组合定义了四种SPI模式Mode 0-3。它们决定了CPOL时钟极性SCK在空闲时的电平。CPOL0空闲时SCK为低电平。CPOL1空闲时SCK为高电平。CPHA时钟相位数据在SCK的哪个边沿被采样。CPHA0数据在第一个SCK边沿即SCK从空闲状态跳变到活动状态的边沿被采样。CPHA1数据在第二个SCK边沿被采样。为了直观理解我们以CPOL0 CPHA0即Mode 0为例空闲时SCK为低CPOL0SS线被拉低选中从设备。主设备在SCK的第一个边沿即上升沿因为从低变高之前就将数据位放到MOSI线上。在SCK的上升沿第一个边沿从设备采样MOSI线上的数据CPHA0。同时主设备也在这个上升沿采样MISO线上的数据。主设备在SCK的下降沿第二个边沿之前准备好下一位数据。在SCK的下降沿主从设备为下一位数据的传输做准备对于CPHA0下降沿是数据变化的时刻。关键在于主设备和从设备的CPOL、CPHA模式必须完全一致否则数据采样会完全错位。大多数SPI器件的数据手册都会明确说明其支持的SPI模式。避坑指南在初始化SPI时务必先查阅所有连接设备的 datasheet确认其支持的SPI模式。一个常见的错误是单片机默认配置为Mode 0而传感器要求Mode 3导致通信失败。调试时用逻辑分析仪抓取SCK、MOSI、MISO波形对照模式图分析是排查此类问题最快的方法。3.3 传输宽度与双缓冲机制SPI支持8位或16位传输宽度通过XFRW位配置。这不仅仅是传输数据长度的区别更影响了数据寄存器的访问方式。8位模式只使用SPIDRL寄存器。读写操作都在这个寄存器上进行。16位模式SPIDRH和SPIDRL组成一个16位寄存器。访问顺序有严格要求错误的访问顺序会导致标志位无法正确清除或数据丢失。SPI采用了双缓冲结构。这意味着它有一个发送数据寄存器和一个发送移位寄存器以及一个接收数据寄存器和一个接收移位寄存器。当CPU向发送数据寄存器写入数据后该数据会在下一个传输开始时自动加载到发送移位寄存器中并逐位发出。同时CPU可以立即写入下一个要发送的数据到发送数据寄存器实现“背靠背”连续发送提高效率。接收端同理正在接收的数据暂存于接收移位寄存器接收完成后自动转移到接收数据寄存器供CPU读取移位寄存器则可以立刻开始接收下一帧。3.4 波特率计算与模式故障SPI的波特率由主设备的系统总线时钟分频得到。计算公式为波特率分频系数 (SPPR 1) * 2^(SPR 1)最终波特率 总线时钟 / 波特率分频系数。SPPR和SPR是SPIBR寄存器中的两个3位字段提供了丰富的分频选择。例如在25MHz总线时钟下通过查表或计算可以获得从12.5Mbps到12.21kbps等多种速率。模式故障MODF是SPI主模式下的一个保护机制。当SPI配置为主模式且MODFEN位使能时如果其SS引脚被外部拉低意味着有另一个设备试图成为主机MODF标志位会被置位SPI会自动切换为从模式并停止传输以防止总线冲突。这在多主SPI系统中是必要的。4. 实战配置以S12ZVHY微控制器为例理论说得再多不如一行代码。我们以Freescale S12ZVHY的SCI和SPI模块为例看看如何配置它们。这里假设使用C语言和常见的嵌入式开发环境。4.1 SCI模块初始化与数据收发假设我们需要配置SCI0为9600波特率8位数据无校验1位停止位使能接收中断。// 假设总线时钟为25MHz #define BUS_CLOCK 25000000UL #define BAUD_RATE 9600UL void SCI0_Init(void) { // 1. 计算波特率寄存器值 SBR // SCI波特率 Bus Clock / (16 * SBR) // 因此 SBR Bus Clock / (16 * Desired Baud Rate) uint16_t sbr (uint16_t)((BUS_CLOCK) / (16 * BAUD_RATE)); // 2. 禁用SCI收发器以便配置 SCI0CR2 ~(SCI0CR2_TE_MASK | SCI0CR2_RE_MASK); // 3. 配置波特率寄存器 (SBR12-SBR0) SCI0BDH (uint8_t)((sbr 8) 0x1F); // 高5位 SCI0BDL (uint8_t)(sbr 0xFF); // 低8位 // 4. 配置控制寄存器1: 8位数据无奇偶校验 SCI0CR1 0x00; // M0 (8位), PE0 (无校验) // 5. 配置控制寄存器2: 使能接收器、发送器使能接收中断 SCI0CR2 SCI0CR2_RE_MASK | SCI0CR2_TE_MASK | SCI0CR2_RIE_MASK; // 6. 使能SCI中断在中断控制器中 // EnableInterrupts; 或具体的中断使能指令 } // SCI0接收中断服务例程 interrupt void SCI0_Rx_ISR(void) { uint8_t status SCI0SR1; uint8_t data; // 检查接收数据寄存器满标志 if (status SCI0SR1_RDRF_MASK) { data SCI0DRL; // 读取数据会自动清除RDRF标志 // 处理接收到的数据例如放入环形缓冲区 // ... } // 检查其他错误标志帧错误、噪声错误、溢出错误 if (status (SCI0SR1_FE_MASK | SCI0SR1_NF_MASK | SCI0SR1_OR_MASK)) { // 错误处理例如记录错误日志复位接收状态 // 读取数据寄存器可以清除一些错误标志但通常需要更复杂的恢复 volatile uint8_t clear SCI0DRL; // 读数据寄存器以清除标志 // 可能需要重新初始化SCI或采取其他措施 } } // 发送一个字节轮询方式 void SCI0_SendByte(uint8_t data) { while(!(SCI0SR1 SCI0SR1_TDRE_MASK)) { // 等待发送数据寄存器空 } SCI0DRL data; // 写入数据启动发送 }注意事项波特率计算时SBR必须是整数否则会产生误差。上述计算中sbr可能不是整数需要四舍五入或取整。实际误差应控制在容限范围内。另外在配置波特率等关键参数前务必先禁用收发器TE0 RE0否则可能导致不可预知的传输。4.2 SPI模块初始化主模式假设配置SPI0为主模式模式0CPOL0 CPHA0波特率约1MHz8位数据。#define SPI_BUS_CLOCK 25000000UL // 25MHz #define SPI_BAUD_RATE 1000000UL // 1MHz void SPI0_Master_Init(void) { // 1. 禁用SPI (SPE0)以便安全配置 SPI0CR1 ~SPI0CR1_SPE_MASK; // 2. 配置SPI控制寄存器2 // 本例使用正常模式非双向等待模式下SPI停止8位传输 SPI0CR2 0x00; // 默认值即可XFRW0 (8位), MODFEN0 (禁用模式故障SS用作GPIO), SPISWAI0 // 3. 计算并配置波特率寄存器 // 波特率 Bus Clock / ((SPPR1) * 2^(SPR1)) // 目标分频系数 25MHz / 1MHz 25 // 查表或计算SPPR0b001 (SPPR1), SPR0b011 (SPR3) // 分频系数 (11) * 2^(31) 2 * 16 32 - 波特率25MHz/32781.25kHz (最接近1MHz的配置) // 或者选择 SPPR0b000, SPR0b100 (分频系数32) 结果相同 // 我们选择 SPPR0, SPR4 (即SPPR[2:0]000, SPR[2:0]100) SPI0BR (0 6) | (0 3) | 0x04; // SPPR0, SPR4 // 4. 配置SPI控制寄存器1 // SPE1(使能), SPIE0(先禁用中断), MSTR1(主模式), CPOL0, CPHA0, SSOE0, LSBFE0(MSB先发) SPI0CR1 SPI0CR1_SPE_MASK | SPI0CR1_MSTR_MASK; // CPOL和CPHA在复位后默认就是0所以可以不写 // 5. 配置SS引脚为通用输出高电平软件控制片选 // 假设SS引脚对应PORTB0 DDRB | 0x01; // 设置为输出 PORTB | 0x01; // 输出高电平不选中 } // SPI全双工交换一个字节 uint8_t SPI0_ExchangeByte(uint8_t txData) { // 等待发送缓冲区空 while(!(SPI0SR SPI0SR_SPTEF_MASK)); // 写入数据启动传输 SPI0DRL txData; // 等待接收完成 while(!(SPI0SR SPI0SR_SPIF_MASK)); // 读取接收到的数据 return SPI0DRL; } // 使用片选发送数据块 void SPI0_WriteBlock(uint8_t slaveIndex, uint8_t* pData, uint16_t size) { // 根据slaveIndex拉低对应的SS引脚例如PORTB0 PORTB ~0x01; for(uint16_t i0; isize; i) { SPI0_ExchangeByte(pData[i]); // 发送并忽略接收 } // 传输完成释放片选 PORTB | 0x01; }关键点解析片选SS控制在SPI主模式下如果MODFEN0则SS引脚不会被SPI模块自动控制需要软件将其配置为GPIO输出并手动控制其电平来选通从设备。拉低选中拉高释放。标志位清除顺序SPI的状态标志清除有严格顺序。对于SPIF接收完成必须先读SPISR状态寄存器再读SPIDRL数据寄存器。对于SPTEF发送空必须先读SPISR再写SPIDRL。代码中的while循环和读写操作隐含地遵循了这个顺序。波特率选择计算出的分频系数可能无法精确得到目标波特率需要选择最接近的配置。上例中目标1MHz实际得到781.25kHz需确认从设备是否能接受此速率。5. 常见问题排查与调试技巧在实际项目中通信接口调不通是家常便饭。下面是一些常见问题的排查思路和调试技巧。5.1 SCI通信失败排查清单现象可能原因排查步骤完全无数据1. 硬件连接错误TX/RX接反2. 波特率配置错误3. 收发器未使能TE/RE位4. 引脚复用功能未开启1. 用万用表或示波器检查TX/RX线连接。2. 双机互发测试用逻辑分析仪抓取波形测量比特时间反推实际波特率。3. 检查SCI控制寄存器2SCICR2的TE和RE位是否置1。4. 检查芯片的引脚功能复用控制寄存器确保引脚被配置为SCI功能而非GPIO。收到乱码1. 波特率不匹配最常见2. 数据格式不一致数据位、停止位、校验位3. 电气电平不匹配如3.3V与5V器件直连4. 地线未共地1.首要检查用逻辑分析仪对比发送和接收波形看比特宽度是否一致。计算双方波特率生成寄存器的值。2. 确认双方的数据位长度8/9、停止位1/2、奇偶校验设置是否完全相同。3. 检查双方器件供电电压若不同需使用电平转换芯片。4. 确保发送端和接收端有可靠的地线连接。偶尔丢数据1. 接收缓冲区溢出CPU处理太慢2. 中断优先级冲突导致接收中断被延迟3. 线路干扰1. 检查接收中断服务程序ISR是否执行时间过长。使用环形缓冲区ISR中只做最简单的数据搬运。2. 提高接收中断的优先级避免被其他长时间中断阻塞。3. 检查硬件长距离通信建议使用RS-232/RS-485等差分标准并做好屏蔽。只能发不能收或反之1. 单向测试时未将自发自收环回断开2. 对方设备故障或未上电3. 自身接收/发送中断或DMA未正确配置1. 如果做了硬件环回测试记得恢复正常连接。2. 确认对方设备正常工作。3. 检查中断使能位RIE, TIE和中断向量表配置。调试利器——逻辑分析仪对于串行通信调试一个支持协议分析的逻辑分析仪如Saleae是必备的。它能直观显示波形、自动解析UART/SPI协议、测量时序、发现毛刺效率远超示波器手动测量。5.2 SPI通信失败排查清单现象可能原因排查步骤从设备无响应1. 片选SS信号问题未拉低、时序不对2. 时钟模式CPOL/CPHA不匹配3. 从设备供电或复位问题4. 波特率过高1.首要检查用逻辑分析仪同时抓取SCK、MOSI、MISO、SS四根线。确认SS信号在数据传输前被正确拉低并在结束后拉高。2.第二检查项对照从设备数据手册确认主设备SPI的CPOL和CPHA设置与从设备要求完全一致。抓取波形看数据采样边沿是否正确。3. 测量从设备的电源和复位引脚。4. 尝试降低SPI波特率特别是布线较长时。主设备收不到数据MISO一直为高阻或固定电平1. 从设备输出使能问题2. MISO引脚配置错误应为主输入3. 从设备未被正确激活或处于错误状态1. 确认从设备在SS有效且收到时钟时是否被正确驱动MISO线。有些设备需要先发送特定命令字才会输出数据。2. 检查主设备MISO引脚是否配置为输入或复用功能。3. 阅读从设备手册确认其初始化序列和读写时序。数据错位如0x55收成0xAA1. 数据传输位序LSB/MSB First不匹配2. 时钟极性或相位有1位偏差1. 检查主从设备的LSBFE或类似配置确保位序一致。通常MSB先行。2. 仔细核对CPOL和CPHA有时错一个模式会导致数据看起来是反码或移位。高速传输时数据错误1. 信号完整性问题过冲、振铃2. 时序裕量不足3. 主从设备时钟抖动1. 观察SCK和MOSI/MISO波形看是否有严重的振铃或边沿过缓。可能需要串联小电阻如22-100欧姆进行阻抗匹配。2. 降低时钟频率看问题是否消失。计算建立时间和保持时间是否满足从设备要求。3. 在极端温度或电压下测试。SPI模式记忆口诀记不住四种模式可以这样记Mode (CPOL 1) | CPHA。即CPOL为高位CPHA为低位。Mode 0就是00Mode 3就是11。大多数SPI Flash器件支持Mode 0和Mode 3。6. 进阶应用与设计考量掌握了基本配置和调试后我们来看看一些更深入的应用场景和设计选择。6.1 SCI的软件流控与硬件流控当发送端速度高于接收端处理能力时会发生数据溢出。除了提高接收端处理能力或使用更大的缓冲区还可以使用流控。软件流控XON/XOFF接收端在缓冲区快满时向发送端发送一个特殊字符XOFF如0x13让其暂停当缓冲区有空闲时再发送另一个字符XON如0x11让其继续。实现简单但会占用数据带宽且不能用于二进制数据可能和XON/XOFF字符冲突。硬件流控RTS/CTS使用额外的两根线。接收端的RTS请求发送信号告诉发送端“我是否可以接收”发送端在发送前检查对方的CTS清除发送信号是否为有效电平。硬件流控实时、可靠不占用数据带宽但需要额外的引脚和连接。6.2 SPI的多从机连接与菊花链独立片选标准方式每个从设备独占一根SS线。主设备通过拉低不同的SS线来选择通信对象。优点是逻辑简单从设备间互不影响缺点是占用IO口多。菊花链Daisy-Chain所有从设备的MISO和MOSI首尾相连形成一个环。主设备只连接第一个从设备的MOSI和最后一个从设备的MISO。数据像接力棒一样从一个设备传到下一个。主设备发送一个很长的数据帧经过所有从设备后再读回一个同样长的帧其中包含了链上所有设备的响应。这种方式节省IO但协议复杂所有设备必须支持菊花链模式且通信效率低数据要穿过所有设备。6.3 低功耗模式下的行为在嵌入式系统中低功耗至关重要。S12ZVHY的SCI和SPI模块在等待模式和停止模式下的行为需要关注SCI通过SCISWAI位控制。若该位置1进入等待模式后SCI时钟停止以省电但任何正在进行的传输会暂停并在退出等待模式后恢复。在停止模式下SCI完全关闭但接收引脚上的有效边沿可以唤醒CPU。SPI通过SPISWAI位控制行为类似。在从模式下即使主设备设置了SPISWAI如果外部SPI主设备仍在提供时钟从设备为了保持同步可能无法完全停止时钟功耗节省有限。设计低功耗系统时需要综合考虑通信模块的状态。6.4 使用DMA解放CPU无论是SCI还是SPI在大量数据传输时频繁的中断会消耗大量CPU资源。此时直接内存访问DMA控制器是绝佳帮手。你可以配置DMA通道在SCI接收数据寄存器满RDRF或SPI接收完成SPIF时自动将数据搬运到指定的内存缓冲区或者在发送寄存器空TDRE/SPTEF时自动从内存缓冲区加载新数据。CPU只需在缓冲区满或空时进行批量处理极大提高了系统效率。在配置DMA时需要特别注意数据宽度、地址递增模式以及和中断的配合。最后无论是SCI还是SPI稳定的通信都建立在扎实的硬件基础上。良好的PCB布局缩短走线、避免平行长线、正确的电源去耦每个芯片电源引脚附近放置0.1uF电容、可靠的地平面都是保证高速数据可靠传输的隐形基石。在软件上则要牢记添加超时机制、校验和如CRC以及错误恢复流程让你的嵌入式系统在面对复杂环境时依然坚如磐石。
嵌入式串行通信:SCI与SPI接口原理、配置与实战调试指南
1. 串行通信基础为什么我们需要SCI和SPI在嵌入式系统开发中无论是让单片机读取一个温湿度传感器的数据还是驱动一块TFT屏幕显示图像设备之间的“对话”都离不开串行通信。想象一下如果每个设备之间都用8根、16根甚至32根线并行连接来传输数据你的电路板会瞬间变成一团乱麻成本、功耗和物理空间都会成为大问题。串行通信的核心思想就是“化繁为简”把数据排成一队通过一根或少数几根数据线一位一位地按顺序发送出去。这就像把一队士兵从宽阔的大马路并行总线转移到一条独木桥串行总线上过河虽然单个士兵通过的速度慢了但架桥的成本和复杂度大大降低在大多数场景下整体的效率和可靠性反而更高。串行通信主要分为两大阵营异步通信和同步通信。它们的核心区别在于“如何同步”。异步通信比如我们常说的UART通用异步收发器或本文要讲的SCI串行通信接口它没有统一的时钟线。发送方和接收方事先约定好一个速度波特率然后数据包自带“起跑信号”起始位和“终点线”停止位。接收方就靠着这些标志和约定的速度努力对齐并解读数据。这种方式硬件简单只需要两根线TX和RX就能实现全双工非常适合单片机与电脑串口调试、GPS模块、蓝牙模块等设备通信。但它的缺点也很明显对时钟精度要求高如果双方时钟偏差累积就容易读错数据。而同步通信以SPI串行外设接口为代表则提供了一条“节拍器”线——时钟线SCK。主设备控制着时钟节拍从设备严格跟着这个节拍来发送或接收每一位数据。这就好比乐队指挥主设备打着拍子乐手从设备们跟着拍子演奏节奏完全同步几乎不会出错。SPI通常需要四根线SCK, MOSI, MISO, SS能实现高速的全双工通信广泛用于连接Flash存储器、SD卡、触摸屏、各类传感器等。它的缺点是线多且一个主设备通常需要为每个从设备单独提供一根片选线SS当从设备很多时会占用大量IO口。所以选择SCI还是SPI不是一个谁好谁坏的问题而是一个“合适”的问题。如果你需要远距离、抗干扰、设备简单的通信SCIUART是经典选择。如果你追求板内设备间的高速、可靠数据交换SPI则是更优解。接下来我们就深入这两种接口的“五脏六腑”看看它们是如何工作的以及在配置和使用时有哪些必须注意的“坑”。2. SCI接口深度解析从帧结构到容错机制SCI在Freescale现NXP的微控制器中通常指的就是其UART模块的实现。理解SCI核心在于理解它的异步帧格式和内在的同步与容错机制。2.1 帧结构数据是如何被打包的一个标准的SCI数据帧绝不是简单地把数据位扔到线上。为了保证接收方能正确识别它被精心包装成一个“数据包”空闲状态数据线TXD/RXD通常保持在高电平逻辑1。起始位一个比特时间的低电平逻辑0。这是接收方的“起床铃”告诉它“注意一帧数据要开始了”接收器检测到这个下降沿就会启动内部计时准备采样后续数据。数据位紧接着起始位之后就是实际要传输的数据可以是5、6、7、8或9位具体由配置决定。通常我们传输一个字节8位LSB最低有效位先行。奇偶校验位可选用于简单的错误检测。发送方根据数据位中“1”的个数计算并附加一个校验位使整个帧包括校验位中“1”的个数为奇数奇校验或偶数偶校验。接收方重新计算并比对不一致则报告校验错误。停止位1位、1.5位或2位的高电平。它标志着一帧的结束并确保线路恢复到空闲状态高电平为检测下一个起始位的下降沿做好准备。注意起始位和停止位带来了额外的开销。比如传输一个8位数据无校验1位停止位那么一帧总共是10位。如果你以9600波特率每秒9600比特通信实际有效数据速率只有 9600 * (8/10) 7680 bps。这是计算实际吞吐量时必须考虑的。2.2 波特率容错时钟不匹配时通信如何不“跑偏”这是SCI设计中最精妙的部分之一。理想情况下发送器和接收器使用完全相同的波特率时钟。但现实中晶振有误差温度会漂移双方的时钟总有微小差异。如果接收方采样点随着时间累积慢慢滑到了数据位的边缘甚至外部就会采样到错误的值导致通信失败。SCI硬件通过两个机制来对抗这种“漂移”帧内重同步接收器并非只在每个比特开始时采样一次。以常见的16倍过采样为例它将一个比特时间划分为16个RT接收器定时器时钟周期。它会在每个比特时间的中间段如第8、9、10个RT周期进行三次采样以“少数服从多数”的原则确定该比特的值例如两次高一次低则判为高。更重要的是接收器会在帧内的每个有效下降沿如起始位、数据位从1到0的跳变进行重同步重新对齐自己的RT时钟。这就像跑步时你不仅看着终点还会在途中不断瞄着领跑者调整自己的步伐。波特率容错计算即使有重同步如果收发双方时钟差异太大累积误差仍可能导致在停止位采样时“踩空”。文档中给出的“慢速数据容忍度”和“快速数据容忍度”计算定量地告诉了我们这个安全范围有多大。以传输8位数据无校验1位停止位为例接收器需要9个比特时间 * 16 RT周期 7个RT周期 151个RT周期来开始对停止位进行采样从起始位下降沿算起到RT8时刻。而发送器发送完这8位数据和一个停止位需要9个比特时间 * 16 RT周期 144个RT周期。慢速容忍度如果接收器时钟比发送器慢那么当发送器发完帧时144 RTt接收器才数到151 RTr。允许的最大误差是(151 - 144) / 151 ≈ 4.63%。这意味着接收器时钟即使比发送器慢4.63%仍能正确采样到停止位。快速容忍度如果接收器时钟比发送器快计算方式类似容忍度约为4.375%。实操心得这个4.6%左右的容限是理论极限在实际工程中必须留足余量。通常我们会确保收发双方的波特率误差控制在2%以内尤其是通信距离较长或环境干扰较大时。选择晶振时要关注其精度如±50ppm即±0.005%。很多通信失败排查到最后就是晶振精度不够或波特率计算寄存器配置有误。2.3 错误处理与唤醒机制SCI硬件能自动检测几种常见错误噪声错误对停止位的三次采样值RT8, RT9, RT10不一致。帧错误在停止位应出现的位置采样到的主要是逻辑0即没有检测到有效的停止位高电平。这通常意味着波特率严重不匹配或线路受到严重干扰。溢出错误前一帧数据还未被CPU读取新一帧数据已经接收完毕并要覆盖数据寄存器导致旧数据丢失。在多接收器系统中如一主多从SCI支持唤醒功能。通过设置RWU位可以让接收器进入“待机”状态忽略总线上的普通数据。唤醒方式有两种空闲线唤醒当总线出现连续的高电平空闲状态时唤醒所有接收器。适用于消息间用空闲字符分隔的协议。地址标记唤醒当检测到一个数据帧的最高位MSB为1时将该帧识别为地址帧并唤醒接收器。接收器解析地址如果是发给自己的则继续接收后续数据帧否则再次进入待机。这种方式允许消息中包含空闲字符效率更高。3. SPI接口深度解析主从时钟同步的艺术如果说SCI是两位约定好步调的跑者那SPI就是一位指挥和一群乐手。SPI的同步特性使其速度远超UART轻松达到几十Mbps但其配置也更为复杂核心就在于理解时钟极性CPOL和时钟相位CPHA。3.1 SPI总线信号与主从架构一个典型的SPI总线包含四根线SCK串行时钟由主设备产生。MOSI主设备输出从设备输入。MISO主设备输入从设备输出。SS/CS从设备选择低电平有效。主设备通过拉低对应从设备的SS线来选中它。SPI是全双工的。数据在SCK的边沿驱动并在相对的边沿被采样。主设备和被选中的从设备同时通过MOSI和MIO线交换数据。这意味着主设备在发送一个字节的同时也会收到从设备返回的一个字节。即使你只想发送命令也会读回一个字节可能是从设备的状态或无用数据反之亦然。3.2 时钟模式CPOL与CPHASPI配置的“万恶之源”这是SPI最让人困惑也最关键的地方。CPOL和CPHA两个位的组合定义了四种SPI模式Mode 0-3。它们决定了CPOL时钟极性SCK在空闲时的电平。CPOL0空闲时SCK为低电平。CPOL1空闲时SCK为高电平。CPHA时钟相位数据在SCK的哪个边沿被采样。CPHA0数据在第一个SCK边沿即SCK从空闲状态跳变到活动状态的边沿被采样。CPHA1数据在第二个SCK边沿被采样。为了直观理解我们以CPOL0 CPHA0即Mode 0为例空闲时SCK为低CPOL0SS线被拉低选中从设备。主设备在SCK的第一个边沿即上升沿因为从低变高之前就将数据位放到MOSI线上。在SCK的上升沿第一个边沿从设备采样MOSI线上的数据CPHA0。同时主设备也在这个上升沿采样MISO线上的数据。主设备在SCK的下降沿第二个边沿之前准备好下一位数据。在SCK的下降沿主从设备为下一位数据的传输做准备对于CPHA0下降沿是数据变化的时刻。关键在于主设备和从设备的CPOL、CPHA模式必须完全一致否则数据采样会完全错位。大多数SPI器件的数据手册都会明确说明其支持的SPI模式。避坑指南在初始化SPI时务必先查阅所有连接设备的 datasheet确认其支持的SPI模式。一个常见的错误是单片机默认配置为Mode 0而传感器要求Mode 3导致通信失败。调试时用逻辑分析仪抓取SCK、MOSI、MISO波形对照模式图分析是排查此类问题最快的方法。3.3 传输宽度与双缓冲机制SPI支持8位或16位传输宽度通过XFRW位配置。这不仅仅是传输数据长度的区别更影响了数据寄存器的访问方式。8位模式只使用SPIDRL寄存器。读写操作都在这个寄存器上进行。16位模式SPIDRH和SPIDRL组成一个16位寄存器。访问顺序有严格要求错误的访问顺序会导致标志位无法正确清除或数据丢失。SPI采用了双缓冲结构。这意味着它有一个发送数据寄存器和一个发送移位寄存器以及一个接收数据寄存器和一个接收移位寄存器。当CPU向发送数据寄存器写入数据后该数据会在下一个传输开始时自动加载到发送移位寄存器中并逐位发出。同时CPU可以立即写入下一个要发送的数据到发送数据寄存器实现“背靠背”连续发送提高效率。接收端同理正在接收的数据暂存于接收移位寄存器接收完成后自动转移到接收数据寄存器供CPU读取移位寄存器则可以立刻开始接收下一帧。3.4 波特率计算与模式故障SPI的波特率由主设备的系统总线时钟分频得到。计算公式为波特率分频系数 (SPPR 1) * 2^(SPR 1)最终波特率 总线时钟 / 波特率分频系数。SPPR和SPR是SPIBR寄存器中的两个3位字段提供了丰富的分频选择。例如在25MHz总线时钟下通过查表或计算可以获得从12.5Mbps到12.21kbps等多种速率。模式故障MODF是SPI主模式下的一个保护机制。当SPI配置为主模式且MODFEN位使能时如果其SS引脚被外部拉低意味着有另一个设备试图成为主机MODF标志位会被置位SPI会自动切换为从模式并停止传输以防止总线冲突。这在多主SPI系统中是必要的。4. 实战配置以S12ZVHY微控制器为例理论说得再多不如一行代码。我们以Freescale S12ZVHY的SCI和SPI模块为例看看如何配置它们。这里假设使用C语言和常见的嵌入式开发环境。4.1 SCI模块初始化与数据收发假设我们需要配置SCI0为9600波特率8位数据无校验1位停止位使能接收中断。// 假设总线时钟为25MHz #define BUS_CLOCK 25000000UL #define BAUD_RATE 9600UL void SCI0_Init(void) { // 1. 计算波特率寄存器值 SBR // SCI波特率 Bus Clock / (16 * SBR) // 因此 SBR Bus Clock / (16 * Desired Baud Rate) uint16_t sbr (uint16_t)((BUS_CLOCK) / (16 * BAUD_RATE)); // 2. 禁用SCI收发器以便配置 SCI0CR2 ~(SCI0CR2_TE_MASK | SCI0CR2_RE_MASK); // 3. 配置波特率寄存器 (SBR12-SBR0) SCI0BDH (uint8_t)((sbr 8) 0x1F); // 高5位 SCI0BDL (uint8_t)(sbr 0xFF); // 低8位 // 4. 配置控制寄存器1: 8位数据无奇偶校验 SCI0CR1 0x00; // M0 (8位), PE0 (无校验) // 5. 配置控制寄存器2: 使能接收器、发送器使能接收中断 SCI0CR2 SCI0CR2_RE_MASK | SCI0CR2_TE_MASK | SCI0CR2_RIE_MASK; // 6. 使能SCI中断在中断控制器中 // EnableInterrupts; 或具体的中断使能指令 } // SCI0接收中断服务例程 interrupt void SCI0_Rx_ISR(void) { uint8_t status SCI0SR1; uint8_t data; // 检查接收数据寄存器满标志 if (status SCI0SR1_RDRF_MASK) { data SCI0DRL; // 读取数据会自动清除RDRF标志 // 处理接收到的数据例如放入环形缓冲区 // ... } // 检查其他错误标志帧错误、噪声错误、溢出错误 if (status (SCI0SR1_FE_MASK | SCI0SR1_NF_MASK | SCI0SR1_OR_MASK)) { // 错误处理例如记录错误日志复位接收状态 // 读取数据寄存器可以清除一些错误标志但通常需要更复杂的恢复 volatile uint8_t clear SCI0DRL; // 读数据寄存器以清除标志 // 可能需要重新初始化SCI或采取其他措施 } } // 发送一个字节轮询方式 void SCI0_SendByte(uint8_t data) { while(!(SCI0SR1 SCI0SR1_TDRE_MASK)) { // 等待发送数据寄存器空 } SCI0DRL data; // 写入数据启动发送 }注意事项波特率计算时SBR必须是整数否则会产生误差。上述计算中sbr可能不是整数需要四舍五入或取整。实际误差应控制在容限范围内。另外在配置波特率等关键参数前务必先禁用收发器TE0 RE0否则可能导致不可预知的传输。4.2 SPI模块初始化主模式假设配置SPI0为主模式模式0CPOL0 CPHA0波特率约1MHz8位数据。#define SPI_BUS_CLOCK 25000000UL // 25MHz #define SPI_BAUD_RATE 1000000UL // 1MHz void SPI0_Master_Init(void) { // 1. 禁用SPI (SPE0)以便安全配置 SPI0CR1 ~SPI0CR1_SPE_MASK; // 2. 配置SPI控制寄存器2 // 本例使用正常模式非双向等待模式下SPI停止8位传输 SPI0CR2 0x00; // 默认值即可XFRW0 (8位), MODFEN0 (禁用模式故障SS用作GPIO), SPISWAI0 // 3. 计算并配置波特率寄存器 // 波特率 Bus Clock / ((SPPR1) * 2^(SPR1)) // 目标分频系数 25MHz / 1MHz 25 // 查表或计算SPPR0b001 (SPPR1), SPR0b011 (SPR3) // 分频系数 (11) * 2^(31) 2 * 16 32 - 波特率25MHz/32781.25kHz (最接近1MHz的配置) // 或者选择 SPPR0b000, SPR0b100 (分频系数32) 结果相同 // 我们选择 SPPR0, SPR4 (即SPPR[2:0]000, SPR[2:0]100) SPI0BR (0 6) | (0 3) | 0x04; // SPPR0, SPR4 // 4. 配置SPI控制寄存器1 // SPE1(使能), SPIE0(先禁用中断), MSTR1(主模式), CPOL0, CPHA0, SSOE0, LSBFE0(MSB先发) SPI0CR1 SPI0CR1_SPE_MASK | SPI0CR1_MSTR_MASK; // CPOL和CPHA在复位后默认就是0所以可以不写 // 5. 配置SS引脚为通用输出高电平软件控制片选 // 假设SS引脚对应PORTB0 DDRB | 0x01; // 设置为输出 PORTB | 0x01; // 输出高电平不选中 } // SPI全双工交换一个字节 uint8_t SPI0_ExchangeByte(uint8_t txData) { // 等待发送缓冲区空 while(!(SPI0SR SPI0SR_SPTEF_MASK)); // 写入数据启动传输 SPI0DRL txData; // 等待接收完成 while(!(SPI0SR SPI0SR_SPIF_MASK)); // 读取接收到的数据 return SPI0DRL; } // 使用片选发送数据块 void SPI0_WriteBlock(uint8_t slaveIndex, uint8_t* pData, uint16_t size) { // 根据slaveIndex拉低对应的SS引脚例如PORTB0 PORTB ~0x01; for(uint16_t i0; isize; i) { SPI0_ExchangeByte(pData[i]); // 发送并忽略接收 } // 传输完成释放片选 PORTB | 0x01; }关键点解析片选SS控制在SPI主模式下如果MODFEN0则SS引脚不会被SPI模块自动控制需要软件将其配置为GPIO输出并手动控制其电平来选通从设备。拉低选中拉高释放。标志位清除顺序SPI的状态标志清除有严格顺序。对于SPIF接收完成必须先读SPISR状态寄存器再读SPIDRL数据寄存器。对于SPTEF发送空必须先读SPISR再写SPIDRL。代码中的while循环和读写操作隐含地遵循了这个顺序。波特率选择计算出的分频系数可能无法精确得到目标波特率需要选择最接近的配置。上例中目标1MHz实际得到781.25kHz需确认从设备是否能接受此速率。5. 常见问题排查与调试技巧在实际项目中通信接口调不通是家常便饭。下面是一些常见问题的排查思路和调试技巧。5.1 SCI通信失败排查清单现象可能原因排查步骤完全无数据1. 硬件连接错误TX/RX接反2. 波特率配置错误3. 收发器未使能TE/RE位4. 引脚复用功能未开启1. 用万用表或示波器检查TX/RX线连接。2. 双机互发测试用逻辑分析仪抓取波形测量比特时间反推实际波特率。3. 检查SCI控制寄存器2SCICR2的TE和RE位是否置1。4. 检查芯片的引脚功能复用控制寄存器确保引脚被配置为SCI功能而非GPIO。收到乱码1. 波特率不匹配最常见2. 数据格式不一致数据位、停止位、校验位3. 电气电平不匹配如3.3V与5V器件直连4. 地线未共地1.首要检查用逻辑分析仪对比发送和接收波形看比特宽度是否一致。计算双方波特率生成寄存器的值。2. 确认双方的数据位长度8/9、停止位1/2、奇偶校验设置是否完全相同。3. 检查双方器件供电电压若不同需使用电平转换芯片。4. 确保发送端和接收端有可靠的地线连接。偶尔丢数据1. 接收缓冲区溢出CPU处理太慢2. 中断优先级冲突导致接收中断被延迟3. 线路干扰1. 检查接收中断服务程序ISR是否执行时间过长。使用环形缓冲区ISR中只做最简单的数据搬运。2. 提高接收中断的优先级避免被其他长时间中断阻塞。3. 检查硬件长距离通信建议使用RS-232/RS-485等差分标准并做好屏蔽。只能发不能收或反之1. 单向测试时未将自发自收环回断开2. 对方设备故障或未上电3. 自身接收/发送中断或DMA未正确配置1. 如果做了硬件环回测试记得恢复正常连接。2. 确认对方设备正常工作。3. 检查中断使能位RIE, TIE和中断向量表配置。调试利器——逻辑分析仪对于串行通信调试一个支持协议分析的逻辑分析仪如Saleae是必备的。它能直观显示波形、自动解析UART/SPI协议、测量时序、发现毛刺效率远超示波器手动测量。5.2 SPI通信失败排查清单现象可能原因排查步骤从设备无响应1. 片选SS信号问题未拉低、时序不对2. 时钟模式CPOL/CPHA不匹配3. 从设备供电或复位问题4. 波特率过高1.首要检查用逻辑分析仪同时抓取SCK、MOSI、MISO、SS四根线。确认SS信号在数据传输前被正确拉低并在结束后拉高。2.第二检查项对照从设备数据手册确认主设备SPI的CPOL和CPHA设置与从设备要求完全一致。抓取波形看数据采样边沿是否正确。3. 测量从设备的电源和复位引脚。4. 尝试降低SPI波特率特别是布线较长时。主设备收不到数据MISO一直为高阻或固定电平1. 从设备输出使能问题2. MISO引脚配置错误应为主输入3. 从设备未被正确激活或处于错误状态1. 确认从设备在SS有效且收到时钟时是否被正确驱动MISO线。有些设备需要先发送特定命令字才会输出数据。2. 检查主设备MISO引脚是否配置为输入或复用功能。3. 阅读从设备手册确认其初始化序列和读写时序。数据错位如0x55收成0xAA1. 数据传输位序LSB/MSB First不匹配2. 时钟极性或相位有1位偏差1. 检查主从设备的LSBFE或类似配置确保位序一致。通常MSB先行。2. 仔细核对CPOL和CPHA有时错一个模式会导致数据看起来是反码或移位。高速传输时数据错误1. 信号完整性问题过冲、振铃2. 时序裕量不足3. 主从设备时钟抖动1. 观察SCK和MOSI/MISO波形看是否有严重的振铃或边沿过缓。可能需要串联小电阻如22-100欧姆进行阻抗匹配。2. 降低时钟频率看问题是否消失。计算建立时间和保持时间是否满足从设备要求。3. 在极端温度或电压下测试。SPI模式记忆口诀记不住四种模式可以这样记Mode (CPOL 1) | CPHA。即CPOL为高位CPHA为低位。Mode 0就是00Mode 3就是11。大多数SPI Flash器件支持Mode 0和Mode 3。6. 进阶应用与设计考量掌握了基本配置和调试后我们来看看一些更深入的应用场景和设计选择。6.1 SCI的软件流控与硬件流控当发送端速度高于接收端处理能力时会发生数据溢出。除了提高接收端处理能力或使用更大的缓冲区还可以使用流控。软件流控XON/XOFF接收端在缓冲区快满时向发送端发送一个特殊字符XOFF如0x13让其暂停当缓冲区有空闲时再发送另一个字符XON如0x11让其继续。实现简单但会占用数据带宽且不能用于二进制数据可能和XON/XOFF字符冲突。硬件流控RTS/CTS使用额外的两根线。接收端的RTS请求发送信号告诉发送端“我是否可以接收”发送端在发送前检查对方的CTS清除发送信号是否为有效电平。硬件流控实时、可靠不占用数据带宽但需要额外的引脚和连接。6.2 SPI的多从机连接与菊花链独立片选标准方式每个从设备独占一根SS线。主设备通过拉低不同的SS线来选择通信对象。优点是逻辑简单从设备间互不影响缺点是占用IO口多。菊花链Daisy-Chain所有从设备的MISO和MOSI首尾相连形成一个环。主设备只连接第一个从设备的MOSI和最后一个从设备的MISO。数据像接力棒一样从一个设备传到下一个。主设备发送一个很长的数据帧经过所有从设备后再读回一个同样长的帧其中包含了链上所有设备的响应。这种方式节省IO但协议复杂所有设备必须支持菊花链模式且通信效率低数据要穿过所有设备。6.3 低功耗模式下的行为在嵌入式系统中低功耗至关重要。S12ZVHY的SCI和SPI模块在等待模式和停止模式下的行为需要关注SCI通过SCISWAI位控制。若该位置1进入等待模式后SCI时钟停止以省电但任何正在进行的传输会暂停并在退出等待模式后恢复。在停止模式下SCI完全关闭但接收引脚上的有效边沿可以唤醒CPU。SPI通过SPISWAI位控制行为类似。在从模式下即使主设备设置了SPISWAI如果外部SPI主设备仍在提供时钟从设备为了保持同步可能无法完全停止时钟功耗节省有限。设计低功耗系统时需要综合考虑通信模块的状态。6.4 使用DMA解放CPU无论是SCI还是SPI在大量数据传输时频繁的中断会消耗大量CPU资源。此时直接内存访问DMA控制器是绝佳帮手。你可以配置DMA通道在SCI接收数据寄存器满RDRF或SPI接收完成SPIF时自动将数据搬运到指定的内存缓冲区或者在发送寄存器空TDRE/SPTEF时自动从内存缓冲区加载新数据。CPU只需在缓冲区满或空时进行批量处理极大提高了系统效率。在配置DMA时需要特别注意数据宽度、地址递增模式以及和中断的配合。最后无论是SCI还是SPI稳定的通信都建立在扎实的硬件基础上。良好的PCB布局缩短走线、避免平行长线、正确的电源去耦每个芯片电源引脚附近放置0.1uF电容、可靠的地平面都是保证高速数据可靠传输的隐形基石。在软件上则要牢记添加超时机制、校验和如CRC以及错误恢复流程让你的嵌入式系统在面对复杂环境时依然坚如磐石。