嵌入式SPI与UART寄存器实战:从芯片手册到稳定通信配置
1. 项目概述与核心价值
在嵌入式系统开发中,设备间的“对话”能力是项目成败的关键。无论是传感器数据采集、显示屏驱动,还是与上位机进行调试通信,都离不开稳定可靠的串行通信接口。从业十多年,我调试过无数基于SPI和UART(SCI)的电路和代码,深知一个道理:看芯片手册的寄存器描述,就像看一份加密电报,每个比特位都藏着玄机,理解不透彻,通信就时灵时不灵,调试起来能让人抓狂。
这次,我们不谈空洞的理论,直接切入工程师最关心的实战核心:寄存器。你手头的芯片手册(比如TI的DSP或MCU手册)里,关于SPI和SCI的章节往往有几十页,表格密密麻麻。很多人看了就头大,只知道照着例程抄几个配置值,一旦通信出问题,根本不知道从何查起。这篇文章的目的,就是帮你把这份“加密电报”彻底破译。我们将以一份典型的寄存器手册内容为蓝本,深度拆解SPI的SPICTL、SPISTS、SPIBRR等关键寄存器,以及SCI(UART)的配置逻辑,不仅告诉你每个位是干什么的,更重点剖析它们在实际电路和代码中是如何联动工作的,配置不当会导致什么诡异现象,以及如何根据你的具体需求(全双工、半双工、波特率、中断方式)来组合这些寄存器位,形成一套稳定可靠的通信方案。
无论你是正在学习嵌入式的新手,还是遇到通信难题寻求突破的老手,这篇文章都将提供可直接“抄作业”的配置思路和避坑指南。我们将从通信协议的本质差异讲起,过渡到寄存器位的精确控制,最后落到代码实现和调试技巧上。你会发现,理解了寄存器,就相当于握住了与硬件直接对话的钥匙。
2. SPI与SCI/UART的本质差异与选型思考
在深入寄存器之前,我们必须先厘清SPI和SCI(通常指UART)的根本不同。这不是为了考试,而是为了你在设计电路或编写驱动时,能做出最合理的选择。很多初学者容易混淆,用错了场景,导致事倍功半。
SPI(Serial Peripheral Interface):你可以把它想象成一场组织严密、节奏明确的“同步对话”。这场对话必须有一个“主持人”(主设备),由他来发起并控制整个对话的节奏(提供时钟SCLK)。主设备通过MOSI(主出从入)线发送指令,同时通过MISO(主入从出)线接收从设备的回应,两者同时进行,这就是全双工。SPI是同步通信,意味着数据位的采样和传输严格跟随时钟边沿,因此速度可以很高(轻松达到几十MHz),但没有额外的起始位、停止位等开销,效率极高。它通常用于板内高速、短距离的芯片间通信,比如连接Flash存储器、ADC芯片、显示屏驱动IC等。它的缺点是至少需要4根线(SCLK, MOSI, MISO, CS),每个从设备还需要一根独立的片选线(CS),在连接多个设备时,引脚资源消耗较大。
SCI/UART(Universal Asynchronous Receiver/Transmitter):这更像两个独立运行的“异步信箱”。通信双方没有统一的时钟线,各自依靠内部约定好的速率(波特率)来发送和解析数据。每发送一个字节的数据,都需要在前后加上起始位和停止位作为“信封”,因此有固定的帧格式开销。它是异步通信,常见的是全双工(有独立的TXD和RXD线)或半双工。由于不需要时钟线,只需要两根数据线(加上地线共三根)就能实现通信,结构简单,抗干扰能力相对较强(通过起始位同步),非常适合较长距离的设备间通信,比如连接电脑串口、GPS模块、蓝牙模块等。但其速度受限于波特率精度和线路质量,通常远低于SPI。
选型决策清单:
- 需要高速(>1Mbps)、板内通信:优先选择SPI。
- 连接多个同型号传感器/芯片:考虑SPI(共用三根线,用不同CS控制),或I2C(但速度慢于SPI)。
- 需要连接电脑调试、与其他独立设备通信、距离较远(几米):必须使用UART(SCI)。
- 系统引脚资源非常紧张:UART(2线)优于SPI(4线+n*CS)。
- 对时序要求极其严格,不能有字节间的不确定延迟:SPI的同步特性更可靠。
理解了这个根本区别,我们再看寄存器配置,就会明白为什么SPI寄存器里充满了对时钟相位(CLK_PHASE)、极性(CLOCK POLARITY)的控制,而SCI寄存器则重点关注波特率分频、帧格式(数据位、停止位、校验位)和错误检测(帧错误、溢出错误)。接下来,我们就钻进SPI的寄存器世界。
3. SPI核心寄存器深度解析与实战配置
芯片手册里SPI寄存器部分通常有十几个,但最核心、决定通信骨架的也就那么几个。我们以提供的资料中的SPICTL、SPISTS、SPIBRR等为例,进行实战化解读。
3.1 SPICTL寄存器:通信的控制中枢
SPICTL寄存器是SPI模块的“大脑”,它决定了通信的基本模式和行为。我们逐位分析其实战意义:
位4 - OVERRUNINTENA(溢出中断使能):
- 是什么:当接收溢出标志(SPISTS.7)被硬件置位时,是否产生中断。
- 为什么重要:接收溢出是SPI通信中最常见的错误之一。当CPU还没从接收缓冲区(SPIRXBUF)读取上一个数据,下一个数据已经接收完毕并准备覆盖缓冲区时,溢出就发生了。如果不处理,数据会丢失。
- 怎么配:在需要高可靠性的中断驱动程序中,务必使能此位(设为1)。这样一旦溢出,能立刻进入中断服务程序进行错误处理(如清空缓冲区、重发数据)。在简单的查询式程序中,可以禁用,但必须定期查询SPISTS.7位。
- 实战坑点:SPI接收溢出中断和SPI传输完成中断(INT_FLAG)共享同一个中断向量。这意味着在你的中断服务程序里,第一件事就是检查是哪个标志位触发的中断,否则无法正确处理。通常的写法是先检查OVERRUN_FLAG,再检查INT_FLAG。
位3 - CLK_PHASE(时钟相位选择):
- 是什么:控制数据采样相对于时钟边沿的位置。它与另一个寄存器SPICCR中的CLOCK POLARITY(时钟极性)位共同决定了SPI的四种工作模式(Mode 0, 1, 2, 3)。
- 为什么重要:主从设备必须工作在相同的时钟模式下,否则数据采样会错位,读到的全是乱码。这是SPI通信配置的第一要务。
- 四种模式详解:
- 模式0 (CPOL=0, CPHA=0):时钟空闲时为低电平,数据在时钟的第一个边沿(上升沿)采样。这是最常用的模式。
- 模式1 (CPOL=0, CPHA=1):时钟空闲时为低电平,数据在时钟的第二个边沿(下降沿)采样。
- 模式2 (CPOL=1, CPHA=0):时钟空闲时为高电平,数据在时钟的第一个边沿(下降沿)采样。
- 模式3 (CPOL=1, CPHA=1):时钟空闲时为高电平,数据在时钟的第二个边沿(上升沿)采样。
- 怎么配:完全取决于你的从设备芯片手册!找到从设备SPI接口的时序图,看它的数据是在时钟的哪个边沿稳定(输出)的,就在那个边沿由主设备进行采样。一般来说,Flash、ADC常用模式0或模式3。
位2 - MASTER_SLAVE(主从模式控制):
- 是什么:决定本设备是作为主设备(发起时钟和数据)还是从设备(响应时钟)。
- 怎么配:这通常由硬件电路设计决定。一个系统中一般只有一个主设备。配置错误会导致总线冲突。特别注意:手册提到复位后SPI自动配置为从模式。如果你的设计是主设���,初始化流程中一定要尽早将此位置1。
位1 - TALK(发送使能):
- 是什么:使能或禁用数据发送输出引脚(主设备为SPISIMO,从设备为SPISOMI)的输出驱动器。
- 为什么重要:在多个从设备共享SPI总线(除了CS线)的系统中,未被选中的从设备必须将其数据输出引脚置为高阻态(Hi-Z),以避免总线冲突。这就是通过将TALK位清零来实现的。
- 怎么配:主设备通常一直使能TALK。从设备的TALK位应受其片选(CS)引脚控制:当CS有效(被拉低)时,使能TALK;当CS无效时,立即禁用TALK。这是一个关键的安全配置,忽略它会导致多从机系统无法工作。
位0 - SPIINTENA(SPI中断使能):
- 是什么:控制SPI传输完成(发送/接收完一个完整数据帧)时是否产生中断。
- 怎么配:如果你采用中断方式处理数据(高效,不阻塞CPU),则使能它。如果采用查询方式,则禁用,但需要轮询SPISTS.6(INT_FLAG)位。
3.2 SPISTS寄存器:通信的状态仪表盘
这个寄存器是只读(或写1清除)的,它告诉你SPI模块当前正在发生什么。调试时,查看这个寄存器的值是最直接的诊断手段。
位7 - OVERRUN_FLAG(接收溢出标志):
- 触发条件:接收缓冲区(SPIRXBUF)里的旧数据还没被读取,新数据已经接收完毕并准备覆盖它。
- 清除方式:写1清零(W1C)。这是一个关键操作!很多新手读数据后发现标志位还在,就是因为用了读操作而不是写1操作来清除。在中断服务程序中,处理完溢出错误后,必须执行
SPISTS |= (1 << 7);这样的操作来清除它。 - 关联中断:受SPICTL.4(OVERRUNINTENA)控制。
位6 - INT_FLAG(中断标志):
- 触发条件:SPI完成发送或接收最后一个比特位。这是最常用的标志位。
- 清除方式:读取SPIRXBUF寄存器、软件复位或系统复位。这意味着,在中断服务程序中,必须通过读取接收数据来清除此标志,否则会反复进入中断。这也是一个常见坑点。
- 注意:手册明确提到,如果使能了FIFO模式,不应使用此位,而应使用FIFO的状态位或中断位。
位5 - BUFFULL_FLAG(发送缓冲区满标志):
- 是什么:当向发送缓冲区(SPITXBUF)写入一个字符后,此位被硬件置1。当该字符被自动加载到移位寄存器(SPIDAT)开始发送后,此位被清零。
- 有什么用:在查询式发送中,你可以轮询此位,当它为0时(缓冲区空),才能写入下一个要发送的数据,避免覆盖未发送的数据。这是实现阻塞式发送函数的基础。
3.3 SPIBRR寄存器:速度的调节器
SPI的通信速率(波特率)由主设备决定,通过SPIBRR寄存器配置。计算公式是手册里的核心:
- 当
SPIBRR = 3 到 127时:SPI波特率 = LSPCLK / (SPIBRR + 1) - 当
SPIBRR = 0, 1, 或 2时:SPI波特率 = LSPCLK / 4
LSPCLK是什么?它是芯片低速外设时钟,通常由系统时钟(SYSCLK)分频而来。你必须在系统初始化时明确知道LSPCLK的频率。
实战计算示例: 假设你的系统LSPCLK = 50 MHz,你希望SPI波特率为 5 Mbps。
- 根据公式反推:
SPIBRR = LSPCLK / 波特率 - 1 = 50e6 / 5e6 - 1 = 10 - 1 = 9。 - 将十进制9转换为十六进制0x09,写入SPIBRR寄存器即可。
重要限制:在从模式下,SPIBRR配置无效,时钟由外部主设备提供。但手册给出了一个关键约束:从设备输入的SPICLK频率不得超过 LSPCLK / 4。例如,若LSPCLK为50MHz,则外部主设备提供的SCLK最高不能超过12.5MHz。如果超过,从设备可能无法正确采样数据,这是硬件设计时必须考虑的。
3.4 数据流核心寄存器:SPIDAT, SPITXBUF, SPIRXBUF
理解了控制与状态,再看数据流就清晰了:
- SPITXBUF(发送数据缓冲区):这是CPU写入待发送数据的地方。写入后,BUFFULL_FLAG置位。
- SPIDAT(发送/接收移位寄存器):这是真正的“搬运工”。当发送空闲时,SPITXBUF的数据会自动加载到SPIDAT,然后数据在SCLK驱动下,一位一位地从MOSI线移出。同时,MISO线上的数据也被一位一位地移入SPIDAT的另一端。
- SPIRXBUF(接收数据缓冲区):当SPIDAT接收完一个完整数据帧(比如16位)后,其中的数据会自动并行加载到SPIRXBUF,供CPU读取。读取SPIRXBUF的操作会清除INT_FLAG位。
- SPIRXEMU(仿真接收缓冲区):这是一个“镜像”寄存器,读取它不会清除INT_FLAG。主要用于仿真器调试时,在不干扰中断标志的前提下观察接收到的数据。普通编程一般用不到,直接操作SPIRXBUF即可。
数据对齐注意:手册强调,对于短于16位的数据帧,数据必须左对齐写入,右对齐读取。例如,配置为8位数据帧时,向SPITXBUF写入数据0xAA,应实际写入0xAA00(左移8位);从SPIRXBUF读取到的0x00AA,其有效数据0xAA在低8位(右对齐)。
3.5 FIFO增强功能寄存器:SPIFFTX, SPIFFRX, SPIFFCT
对于需要高速、连续传输大量数据的场景,芯片提供了FIFO(先入先出缓冲区)功能,可以显著减少CPU中断频率,提升效率。
SPIFFTX(发送FIFO控制):
SPIFFENA(位14):总开关,必须置1才能使用FIFO功能。TXFIFO Reset(位13):写0复位发送FIFO指针,通常在初始化时操作一次,然后置1释放。TXFFST(位12-8):只读,指示当前发送FIFO中有多少个待发送字(0-16)。TXFFIL(位4-0):发送FIFO中断触发水平。例如,设置为2,则当TXFFST <= 2(即FIFO中只剩2个或更少数据)时,触发发送中断,提醒CPU及时补充数据,避免FIFO变空导致发送停顿。
SPIFFRX(接收FIFO控制):
RXFFST(位12-8):指示当前接收FIFO中有多少个已接收字。RXFFIL(位4-0):接收FIFO中断触发水平。例如,设置为8,则当RXFFST >= 8(即FIFO中数据达到或超过8个)时,触发接收中断,CPU可以一次读取多个数据,效率远高于每收到1字节就中断一次。RXFFOVF(位15):接收FIFO溢出标志。当FIFO已满(16字)且还有新数据到来时,此位置1,最早的数据会丢失。必须使能接收中断并及时读取数据来避免溢出。
SPIFFCT(FIFO传输延迟控制):
TXDLY(位7-0):定义每个数据从FIFO传输到发送移位寄存器之间的延迟(以SPI串行时钟周期数为单位)。这个功能用于在连续的字节流之间插入可控的间隔,对于某些需要特定字节间延迟的旧式从设备(如某些LCD控制器)非常有用。对于大多数现代器件,设置为0即可。
FIFO使用心得:
- 初始化顺序:先使能FIFO (
SPIFFENA=1),再复位FIFO (TXFIFO=0, RXFIFO=0),最后释放复位 (TXFIFO=1, RXFIFO=1)。 - 中断配置:使用FIFO后,应禁用标准的SPIINTENA,转而使用FIFO中断(
TXFFIENA,RXFFIENA),并根据你的数据处理能力设置合理的TXFFIL和RXFFIL水平。 - 数据搬运:发送时,可以一次性向FIFO写入多个数据(检查TXFFST确保不满);接收中断到来时,应一次性从FIFO读取
RXFFST指示的所有数据。
4. SCI (UART) 核心配置与��存器逻辑
SCI,即我们常说的UART,其寄存器配置逻辑与SPI有相通之处,但关注点完全不同,因为它面对的是异步、带帧格式的通信。
4.1 SCI通信格式配置
SCI的数据格式通过SCICCR(通信控制寄存器)配置,这是异步通信的“��法规则”:
- 数据位长度:可选1至8位,通常选8位。
- 停止位:1位或2位。2位能提供更好的帧间隔识别,但会降低有效数据吞吐率。通常1位足够。
- 奇偶校验位:可选奇校验、偶校验或无校验。用于简单的错误检测。在干扰不大的环境中通常禁用(无校验)。
- 地址/数据模式:用于多机通信,会在数据帧中插入一个地址标识位。
波特率配置:这是异步通信的“节奏共识”。通过两个8位寄存器SCIHBAUD和SCILBAUD(合起来是一个16位值BRR)来设置。计算公式为:SCI波特率 = LSPCLK / [(BRR + 1) * 8]假设LSPCLK = 50MHz,目标波特率115200,则:BRR = 50,000,000 / (115200 * 8) - 1 ≈ 53.25 - 1 = 52.25取整后BRR = 52,实际波特率会有微小误差。计算误差率是必要的,一般要求误差小于2%。BRR=52时,实际波特率约为50e6/(53*8) ≈ 117,924 bps,误差约2.3%,在可接受边缘。对于115200,更精确的BRR值可能需要调整系统时钟分频。
4.2 SCI核心控制与状态寄存器
- SCICTL1/2(控制寄存器):控制发送/接收使能 (
TXENA,RXENA)、中断使能、睡眠模式等。特别注意:要使能发送或接收,必须将对应位置1,并且确保引脚复用已正确配置为SCI功能。 - SCIRXST(接收状态寄存器):这是UART调试的“生命线”。它包含几个关键错误标志:
FE(帧错误):未在预期位置检测到有效的停止位。通常由波特率不匹配、线路干扰或发送方过早停止发送引起。OE(溢出错误):接收缓冲区(SCIRXBUF)中的数据尚未被读取,新数据已覆盖它。与SPI溢出类似,意味着CPU处理速度跟不上接收速度。PE(奇偶校验错误):如果使能了奇偶校验,此位表示接收数据的奇偶性与预期不符。BRKDT(间断检测):检测到接收线持续为低电平(起始位+数据位+停止位的时间)。通常表示对方发送了“break”信号。
- SCIRXBUF(接收数据缓冲区):读取此寄存器获取数据,同时会清除
RXRDY标志。 - SCITXBUF(发送数据缓冲区):写入待发送数据。只有在上一个数据已从缓冲区移入发送移位寄存器后,才能写入新数据,否则会覆盖。需要通过查询
TXRDY标志或使用发送中断来管理。
4.3 SCI的FIFO与自动波特率检测
现代SCI模块也普遍支持FIFO(通过SCIFFTX,SCIFFRX等寄存器控制),其原理和配置思路与SPI FIFO高度相似,用于降低中断频率,提高大数据量传输效率。
自动波特率检测是一个高级且实用的功能。它允许SCI模块通过检测接收到的特定字符(通常是字符‘A’或‘a’,其ASCII码位模式为0x41或0x61,拥有对称的位跳变)来自动计算并匹配对方的波特率。这在设备需要与未知波特率的上位机建立初始通信时非常有用。使能该功能后,模块会测量起始位到第一个下降沿(或特定跳变)的时间,从而反推出波特率并自动配置BRR寄存器。
5. 实战配置流程与代码示例
理论说再多,不如一行代码。下面我以TI C2000系列DSP的SPI主设备初始化为例,展示一个典型的配置流程和注意事项。
// 假设 LSPCLK = 50MHz, 目标 SPI 波特率 = 5Mbps, 模式0 (CPOL=0, CPHA=0), 16位数据帧, 使能主模式、发送、标准中断 void SPI_Master_Init(void) { // 1. 首先,确保SPI模块时钟使能(在PCLKCR寄存器中) // 这一步取决于具体芯片,通常为:SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1; // 2. 复位SPI模块(可选,但推荐) SpiaRegs.SPICCR.bit.SPISWRESET = 0; // 进入复位状态 DELAY_US(10); // 短暂延时 // 3. 配置SPICCR (Clock Control Register) SpiaRegs.SPICCR.all = 0x000F; // 具体值需按位设置,更清晰的写法如下: SpiaRegs.SPICCR.bit.SPICHAR = 0xF; // 16位数据字符长度 (0xF表示15+1=16) SpiaRegs.SPICCR.bit.SPILBK = 0; // 禁用回环模式 SpiaRegs.SPICCR.bit.CLK_POLARITY = 0; // 时钟极性CPOL=0 (空闲低电平) // 注意:SPISWRESET位在初始化最后才置1 // 4. 配置SPICTL (Control Register) SpiaRegs.SPICTL.all = 0x0006; // 更清晰的位操作: SpiaRegs.SPICTL.bit.SPIINTENA = 0; // 初始化阶段先禁用中断,配置完再打开 SpiaRegs.SPICTL.bit.TALK = 1; // 主模式,使能发送 SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1; // 配置为主模式 SpiaRegs.SPICTL.bit.CLK_PHASE = 0; // 时钟相位CPHA=0, 模式0 SpiaRegs.SPICTL.bit.OVERRUNINTENA = 1; // 使能溢出中断,提高鲁棒性 // 5. 配置波特率寄存器 SPIBRR // BRR = LSPCLK / 波特率 - 1 = 50e6 / 5e6 - 1 = 9 SpiaRegs.SPIBRR = 9; // 6. 如果使用FIFO,在此配置SPIFFTX/SPIFFRX/SPIFFCT // SpiaRegs.SPIFFTX.bit.SPIFFENA = 1; // 使能FIFO增强功能 // SpiaRegs.SPIFFTX.bit.TXFIFO = 0; // 复位发送FIFO // SpiaRegs.SPIFFRX.bit.RXFIFO = 0; // 复位接收FIFO // SpiaRegs.SPIFFTX.bit.TXFIFO = 1; // 使能发送FIFO // SpiaRegs.SPIFFRX.bit.RXFIFO = 1; // 使能接收FIFO // SpiaRegs.SPIFFTX.bit.TXFFIL = 4; // 发送FIFO中断水平设为4 // SpiaRegs.SPIFFRX.bit.RXFFIL = 8; // 接收FIFO中断水平设为8 // SpiaRegs.SPIFFTX.bit.TXFFIENA = 1; // 使能发送FIFO中断 // SpiaRegs.SPIFFRX.bit.RXFFIENA = 1; // 使能接收FIFO中断 // 7. 最后,释放SPI软件复位,模块开始工作 SpiaRegs.SPICCR.bit.SPISWRESET = 1; // 8. 使能中断(如果使用) // SpiaRegs.SPICTL.bit.SPIINTENA = 1; // 同时需要配置PIE向量表,使能CPU级中断等... } // 一个简单的查询式发送函数(非FIFO模式) void SPI_WriteWord(uint16_t data) { // 等待发送缓冲区为空 while(SpiaRegs.SPISTS.bit.BUFFULL_FLAG == 1) { ; // 等待 } // 写入数据,启动传输 SpiaRegs.SPITXBUF = data; // 数据会自动左对齐处理 } // SPI中断服务程序示例(非FIFO模式) __interrupt void SPIA_RX_ISR(void) { uint16_t receivedData; // 1. 首先检查并处理溢出错误(最高优先级错误) if(SpiaRegs.SPISTS.bit.OVERRUN_FLAG == 1) { // 发生溢出,数据已丢失 // 进行错误处理,如记录日志、重置通信序列等 SpiaRegs.SPISTS.bit.OVERRUN_FLAG = 1; // 写1清除溢出标志 // 注意:清除后,可能还需要读取一次SPIRXBUF来丢弃无效数据 receivedData = SpiaRegs.SPIRXBUF; // 读取以清空缓冲区 } // 2. 处理正常接收完成中断 if(SpiaRegs.SPISTS.bit.INT_FLAG == 1) // 实际上,进入此ISR通常就意味着INT_FLAG=1 { // 读取接收到的数据(此操作会清除INT_FLAG) receivedData = SpiaRegs.SPIRXBUF; // 处理数据,例如放入环形缓冲区 // ... } // 3. 清除PIE中断标志,必不可少! PieCtrlRegs.PIEACK.all = PIEACK_GROUP6; // 假设SPIA在PIE组6 }6. 调试技巧与常见问题排查实录
即使寄存器配置完全正确,通信仍可能失败。以下是我多年调试中总结的“排查清单”,按优先级排序:
问题一:完全没有通信,示波器上看不到任何时钟或数据信号。
- 检查1:电源和时钟。确认芯片供电正常,系统时钟和LSPCLK是否已正确配置并运行?用示波器测一下相关时钟引脚。
- 检查2:引脚复用。这是最容易被忽略的一步!MCU的引脚通常有多种功能(GPIO、SPI、UART等)。你必须查阅芯片的“PinMux”表格,将对应的SPI或SCI引脚配置为外设功能,而不是默认的GPIO。例如,配置相应的
GPxMUX和GPxGMUX寄存器。 - 检查3:模块使能。外设模块的时钟门控是否打开?查找��似
PCLKCR0这样的寄存器,确保SPI或SCI模块的时钟使能位被置1。 - 检查4:主从模式与片选。对于SPI从设备,检查其片选(CS)引脚是否被主设备正确拉低。对于SPI主设备,检查
MASTER_SLAVE位是否设为1。对于UART,检查发送(TXD)和接收(RXD)是否交叉连接(本机的TXD接对端的RXD)。
问题二:有时钟信号,但数据全为0或全是乱码。
- 检查1:时钟模式(SPI专属)。这是SPI通信的头号杀手!用示波器同时测量主设备的SCLK、MOSI和从设备的MISO。对照从设备数据手册的时序图,检查:
- 时钟空闲电平(CPOL)是否正确?
- 数据是在哪个时钟边沿采样(CPHA)?主从设备的CPHA设置必须完全一致。
- 一个常见的技巧:如果数据看起来是错位的(比如发送0xAA
10101010,收到0x5501010101),很可能就是CPHA配反了。
- 检查2:数据帧长度。主从设备配置的数据位宽(如8位、16位)是否相同?如果主设备发16位,从设备按8位解析,必然乱码。
- 检查3:波特率(UART专属)。这是UART通信的头号问题。双方波特率必须一致,误差最好在2%以内。检查两边的波特率计算和寄存器配置。可以用示波器测量一个字节的传输时间(从起始位下降沿到停止位结束),计算实际波特率进行验证。
- 检查4:发送使能TALK位(SPI从设备)。从设备的
TALK位是否在其片选有效时才被使能?如果一直使能,在多从机系统中会导致总线冲突。
问题三:通信不稳定,偶尔丢数据或产生错误。
- 检查1:中断处理。你是否及时清除了中断标志?在SPI标准中断中,读取
SPIRXBUF会清除INT_FLAG;对于溢出标志OVERRUN_FLAG,需要写1清除。在UART中,读取SCIRXBUF清除RXRDY。标志未及时清除会导致无法触发下一次中断或反复进入中断。 - 检查2:缓冲区管理。你是否在发送缓冲区满(
BUFFULL_FLAG=1)时强行写入?是否在接收数据就绪前就去读取?使用查询方式时,务必在写之前检查BUFFULL_FLAG,在读之前检查INT_FLAG或RXRDY。 - 检查3:信号完整性。对于高速SPI(>10MHz)或长距离UART,需要考虑信号质量问题。检查PCB走线,是否过细、过长,是否有靠近干扰源?必要时串联小电阻(如22欧姆)进行阻抗匹配,或并联电容滤波。
- 检查4:电源噪声。用示波器查看通信期间的电源电压,是否有明显的毛刺?数字电路高速切换会引起电源噪声,可能干扰模拟的通信电平。确保电源去耦电容(0.1uF和10uF)靠近芯片电源引脚放置。
- 检查5:FIFO配置。如果使用了FIFO,中断触发水平
TXFFIL/RXFFIL设置是否合理?设置过小会导致中断过于频繁,消耗CPU;设置过大可能导致缓冲区溢出或下溢。同时,确保在程序初始化时正确复位了FIFO指针。
问题四:UART能收到数据,但都是错误的,且伴有帧错误(FE)。
- 检查1:波特率误差。这是最常见原因。重新计算波特率分频值,确保误差最小。考虑使用芯片提供的自动波特率检测功能(如果支持)。
- 检查2:数据格式。双方的数据位、停止位、校验位设置是否一致?例如,一方8位数据无校验,另一方7位数据偶校验,必然出错。
- 检查3:电气电平。如果是RS-232电平,需要经过电平转换芯片(如MAX3232)。直接连接MCU的3.3V TTL电平到PC的串口(±12V)会损坏芯片且无法通信。
一个高级调试技巧:使用IO模拟辅助调试。当硬件SPI/UART调不通时,可以暂时用GPIO模拟相应的时序(bit-banging)。写一个简单的模拟发送函数,如果能通,证明从设备、电路和基本时序理解是正确的,问题很可能出在寄存器配置或硬件模块本身。如果模拟也不通,那就集中检查硬件电路和从设备。
