1. SPI通信协议核心原理与C672x DSP适配性解析
SPI,全称Serial Peripheral Interface,几乎是我在嵌入式开发生涯中打交道最多的通信协议之一。它的设计哲学非常直接——用最少的线实现全双工、高速的同步数据交换。一个主设备(Master)带着一个或多个从设备(Slave),通过四根基础信号线(SCLK时钟、MOSI主出从入、MISO主入从出、CS片选)就能构建起一个高效的微型数据网络。这种简洁性让它成为了连接Flash、EEPROM、ADC、DAC、各类传感器和显示屏的“万能胶”。
但简洁不等于简单。SPI协议本身没有严格的国际标准,这既是其灵活性的来源,也是工程师“踩坑”的起点。时钟极性(CPOL)和时钟相位(CPHA)的组合定义了四种工作模式(Mode 0-3),主从设备必须严格匹配。Mode 0(CPOL=0, CPHA=0)是最常见的,时钟空闲为低电平,数据在第一个时钟边沿(上升沿)采样。而Mode 3(CPOL=1, CPHA=1)则完全相反。我见过太多通信失败,最后追查下来就是主从模式没对齐,数据采样窗口整个错位。
为什么C672x DSP的SPI模块值得我们深入剖析?因为它将这种灵活性做到了寄存器级别,提供了工业级应用所需的精细控制能力。与许多简单的8位或32位通用MCU内置的SPI控制器不同,C672x的SPI模块不仅仅是一个“位搬运工”。它内置了强大的时序引擎,允许你以系统时钟(SYSCLK2)的周期为单位,精确配置片选建立时间、保持时间、传输间隔,甚至支持基于硬件握手信号(SPIx_ENA)的超时与同步错误检测。这意味着你可以用它去驱动那些时序要求极为苛刻的“龟毛”外设,比如某些高速ADC或带有复杂就绪/忙状态指示的定制ASIC。
在深入寄存器之前,我们必须建立一个核心认知:SPI通信的稳定性,一半取决于协议本身,另一半则取决于时序的精准控制。主设备在拉低片选后,需要给从设备足够的准备时间(C2TDELAY)才能发出第一个时钟;在最后一个数据位传输完成后,也需要保持片选一段时间(T2CDELAY),确保从设备有足够的时间锁存数据。C672x的SPIDELAY寄存器就是专门干这个的。而SPIFMTx寄存器则定义了通信的“语言规则”:数据位宽(CHARLEN)、时钟速率(PRESCALE)、移位方向(SHIFTDIR)以及前面提到的极性和相位。把这些寄存器玩明白了,你就能让DSP的SPI模块说任何外设能听懂的“方言”。
2. C672x DSP SPI寄存器组深度剖析与配置逻辑
C672x DSP的SPI外设是一套相当完整的控制器,其寄存器映射是理解和操控它的钥匙。整个SPI模块的寄存器空间是连续映射到内存的,SPI0和SPI1有各自独立的基地址(例如0x4700_0000和0x4800_0000)。编程时,我们通常定义一个指向该基地址的指针,然后通过寄存器偏移量进行访问。这种内存映射I/O(MMIO)的方式在DSP和高端MCU中非常普遍。
2.1 全局控制与引脚配置寄存器
任何外设的初始化,第一步都是将其从复位状态中释放出来。SPIGCR0寄存器的RESET位(位0)就是干这个的。上电或软复位后,该位为0,模块处于复位状态,所有配置无效。必须将其写1,模块才能进入可配置状态。这里有个细节:在修改大部分其他配置寄存器(如SPIFMTx, SPIDELAY)前,确保模块处于复位状态或ENABLE位为0是一个好习惯,可以避免配置过程中产生不可预料的毛刺或传输。
SPIGCR1寄存器是另一个核心。它的CLKMOD位决定时钟源(内部或外部),而MASTER位则设置主从模式。对于大多数应用,我们配置为内部时钟源的主模式。这个寄存器里还有一个关键的ENABLE位(位24),它像是SPI模块的总开关。必须在所有参数(格式、延时、中断等)配置妥当之后,最后才将其置1,模块才会开始响应传输请求。
引脚功能复用是嵌入式系统设计的常态。C672x的SPI引脚可能与其他外设(如GPIO、UART)共享。SPIPC0寄存器就是用来指定哪些引脚被配置为SPI功能。例如,要使用完整的5线模式(SCLK, SIMO, SOMI, SCS, ENA),就需要将对应的位域设置为功能引脚而非通用IO。配置错误会导致信号无法输出到引脚上,这是新手常犯的“无声”错误——逻辑分析仪上看不到任何波形,不是因为程序没跑,而是信号根本没送到引脚。
2.2 数据格式寄存器SPIFMTx:定义通信“语言”
这是SPI配置的灵魂所在,C672x最多支持4组数据格式(SPIFMT0-3),可以通过SPIDAT1.DFSEL动态选择,这为与多个不同规格的从设备通信提供了极大便利,无需频繁重配寄存器。
时钟生成与波特率:通信速率由PRESCALE位域(位15-8)控制。计算公式为:SPI Clock = SYSCLK2 / (PRESCALE + 1)。当PRESCALE设为0时,时钟为SYSCLK2的一半,这是最高速率。这里SYSCLK2是DSP的系统时钟2分频,你需要根据你的核心时钟来推算。例如,若SYSCLK2 = 100 MHz,PRESCALE设为9,则SPI时钟为10 MHz。务必确保该速率在从设备支持的范围内。
数据帧结构:CHARLEN位域(位4-0)定义了一次传输的数据位宽,合法值是2到16位。注意,它定义的是“字符长度”,对于8位数据,应设置为0x08(十进制8)。许多新手会误设为0x07,因为从0开始计数,但这里寄存器值直接对应位数。
时钟模式:POLARITY(位17)和PHASE(位16)共同决定了前述的四种SPI模式。
- POLARITY=0:时钟空闲为低。
- POLARITY=1:时钟空闲为高。
- PHASE=0:数据在第一个时钟边沿采样(对于POLARITY=0是上升沿,对于POLARITY=1是下降沿)。
- PHASE=1:数据在第二个时钟边沿采样,第一个边沿用于数据建立。
移位方向:SHIFTDIR位(位20)决定了先发送最高位(MSB First,通常为0)还是最低位(LSB First,通常为1)。这必须与从设备的数据表严格一致,否则你读到的数据高低位全是反的。
等待使能与握手:WAITENA位(位21)在主机模式下尤为重要。如果置1,主机在发起传输前,会等待从设备的SPIx_ENA引脚变为有效(低电平)。这实现了硬件流控,适用于那些处理速度较慢、需要“准备好”信号的从设备。如果从设备不支持此引脚,则必须将该位清零。
注意:C672x的SPI模块不支持奇偶校验,因此PARITYENA和PARPOL位必须保持为0,写入1会导致未定义行为。
2.3 延时寄存器SPIDELAY:精密时序的操控器
如果说SPIFMTx定义了“说什么”和“说多快”,那么SPIDELAY寄存器就是控制“什么时候说”和“说多久”的指挥家。它对通信可靠性,尤其是连接不同制造商的器件时,至关重要。
C2TDELAY(片选有效到传输开始延时):位28-24。这是主设备拉低片选(SCS)后,到发出第一个SCLK边沿之间的延时。单位为SYSCLK2周期,可配置范围是2到33个周期。它给了从设备一个“准备时间”,让其有足够的时间从片选有效边沿中唤醒,并准备好数据线。对于反应较慢的从设备,这个值需要设大一些。计算公式:tC2TDELAY = (C2TDELAY + 2) * SYSCLK2周期。
T2CDELAY(传输结束到片选无效延时):位20-16。在最后一个数据位(或校验位)传输完成后,片选信号保持有效的时长。单位为SYSCLK2周期,范围1到32。这是数据的“保持时间”,确保从设备有足够的时间锁存最后一位数据。设得太短可能导致从设备采样失败。
T2EDELAY与C2EDELAY(ENA超时控制):这两个延时专用于4线ENA模式或5线模式,用于管理SPIx_ENA握手信号。
- C2EDELAY(位7-0):片选有效后,主设备等待从设备拉低SPIx_ENA信号的最大时间。如果超时,会触发TIMEOUT错误。这防止了主设备因等待一个故障或无响应的从设备而永远挂起。若WAITENA=1且C2EDELAY=0,则主机会无限等待。
- T2EDELAY(位15-8):传输结束后,主设备等待从设备释放SPIx_ENA信号(拉高)的最大时间。如果从设备因丢失时钟等原因“卡住”,未能及时释放ENA,主设备会触发DESYNC(失步)错误。这两个超时机制是构建健壮主从系统的关键。
这些延时值的计算依赖于SYSCLK2频率和SPI时钟分频器(PRESCALE)。手册中的公式提供了计算方法,但务必注意手册末尾的警告:这些公式未计入芯片I/O引脚本身的时序特性(如翻转速率)。因此,理论计算值应作为起点,最终值需要在示波器上根据实际信号波形进行微调。
2.4 数据与缓冲寄存器
SPIDAT0和SPIDAT1是数据发送寄存器。写入SPIDAT0会启动一次立即传输。而SPIDAT1除了低16位是发送数据,其高16位还包含了控制信息,最重要的是DFSEL位域(位19-16),用于选择本次传输使用哪一组SPIFMTx格式(0-3)。这实现了动态格式切换。
SPIBUF是数据接收缓冲器。读取它会返回最新接收到的数据,但同时会自动清除接收中断标志(RXINTFLAG)和溢出中断标志(OVRNINTFLG)。这是一个关键行为!如果你在中断服务程序中需要根据接收到的数据做判断,务必先读取数据到临时变量,再进行判断,否则读取操作本身会清除标志位,可能影响后续逻辑。
SPIEMU,仿真寄存器,它是SPIBUF的一个“别名”。读取它也能得到接收数据,但不会清除RXINTFLAG和OVRNINTFLG标志。这个寄存器主要用于仿真器和调试场景,让你可以不断读取数据而不影响中断状态位。在产品代码中,除非有特殊调试需求,否则应使用SPIBUF进行读取。
2.5 中断与状态管理
高效的系统离不开中断。C672x SPI的中断系统较为清晰。
- SPIFLG(标志寄存器):所有状态标志位都在这。包括接收完成(RXINTFLAG)、发送缓冲空(TXINTFLAG)、溢出(OVRNINTFLG)、位错误(BITERRFLG)、超时(TIMEOUTFLG)、失步(DESYNCFLG)、片选错误(DESELERRFLG)等。任何错误发生,对应的标志位会被置1。
- SPIINT0(中断使能寄存器):你可以选择让哪些事件触发中断。例如,你可以只使能接收完成中断和超时错误中断。
- SPILVL(中断级别寄存器):可以将不同事件分配到不同的中断优先级(Level 0或Level 1)。但手册中有一个非常重要的警告:在C672x上,通常建议只使用Level 0。因为SPI模块内部的两个中断级别在模块外部又会合并成一个CPU中断请求线。同时使用两个级别只会增加中断服务程序的复杂度,而不会带来真正的优先级提升。
- TGINTVECT0/1(中断向量寄存器):当使用中断向量模式时,读取这个寄存器可以快速获知当前最高优先级的中断源是什么,并且读取操作会自动清除对应的标志位。这提供了一种高效的中断服务程序入口判断机制。
3. 从零构建SPI驱动:主从模式配置与数据传输实战
理解了寄存器,我们来动手写代码。我将以最常见的场景为例:C672x作为SPI主机,以轮询方式与一个从设备通信,然后升级到使用dMAX(直接内存访问控制器)进行自动数据传输。
3.1 基础配置:SPI主机轮询模式
轮询模式简单直接,适合低速或初始化阶段的通信。以下是配置SPI0为主机、5线模式(带ENA握手)的步骤详解。
#include <stdio.h> #include <c6x.h> // SPI0 寄存器基地址偏移量定义 #define SPIGCR0 0x00 #define SPIGCR1 0x04 #define SPIINT0 0x08 #define SPILVL 0x0C #define SPIFLG 0x10 #define SPIPC0 0x14 #define SPIDAT0 0x38 #define SPIDAT1 0x3C #define SPIBUF 0x40 #define SPIDELAY 0x48 #define SPIFMT0 0x50 // ... 其他寄存器偏移量 void SPI_Master_Init_Polling(void) { volatile unsigned int *spi0 = (unsigned int *)0x47000000; // SPI0 基地址 // 1. 将SPI模块置于复位状态(确保配置前状态已知) spi0[SPIGCR0] = 0x00000000; // 2. 配置为主机模式,使用内部时钟 // SPIGCR1: CLKMOD=1 (内部时钟), MASTER=1 (主机) spi0[SPIGCR1] = 0x00000003; // 3. 配置引脚功能为5线模式 (SCLK, SIMO, SOMI, SCS, ENA) // SPIPC0: 对应引脚功能选择位需置位,具体位域参考数据手册 // 假设位[11:8]分别控制SIMO, SOMI, CLK, ENA,位[0]控制SCS spi0[SPIPC0] = 0x00000F01; // 使能所有5个SPI功能引脚 // 4. 选择使用SPIFMT0作为本次传输的数据格式 spi0[SPIDAT1] = 0x00000000; // DFSEL=0, 使用SPIFMT0 // 5. 配置SPIFMT0数据格式 // 目标:16位数据,MSB先发,时钟极性高空闲(CPOL=1),相位为0(CPHA=0) // 使能等待ENA信号(WAITENA=1),波特率预分频设为1 (PRESCALE=1) // 计算:CHARLEN=16 (0x10), POLARITY=1, PHASE=0, WAITENA=1, SHIFTDIR=0, PRESCALE=1 // 寄存器位:PRESCALE[15:8]=1, CHARLEN[4:0]=0x10 // POLARITY[17]=1, PHASE[16]=0, WAITENA[21]=1, SHIFTDIR[20]=0 // 组合成32位值:0x00221810 spi0[SPIFMT0] = 0x00221810; // 6. 配置主模式延时参数 // C2TDELAY = 2个SYSCLK2周期(约80ns @ 25MHz) // T2CDELAY = 2个周期 // T2EDELAY = 4个SPI时钟周期(超时检测) // C2EDELAY = 8个SPI时钟周期(等待ENA超时) // SPIDELAY寄存器布局:[31-29][28-24 C2TDELAY][23-21][20-16 T2CDELAY][15-8 T2EDELAY][7-0 C2EDELAY] spi0[SPIDELAY] = (2 << 24) | (2 << 16) | (4 << 8) | 8; // 即 0x02020408 // 7. 配置中断(轮询模式,先禁用所有中断) spi0[SPIINT0] = 0x00000000; spi0[SPILVL] = 0x00000000; // 8. 将SPI模块退出复位,并全局使能 spi0[SPIGCR0] = 0x00000001; // 退出复位 // 注意:先退出复位,再使能通信 spi0[SPIGCR1] |= 0x01000000; // 设置ENABLE位 }配置完成后,就可以进行轮询式数据收发了:
unsigned int SPI_Master_Transfer_Polling(unsigned int txData) { volatile unsigned int *spi0 = (unsigned int *)0x47000000; unsigned int timeout = 0xFFFF; unsigned int status; // 1. 等待发送缓冲区为空(TXINTFLAG = 1) while (!(spi0[SPIFLG] & 0x200)) { if (--timeout == 0) { // 处理超时错误 return 0xFFFFFFFF; } } // 2. 写入要发送的数据到SPIDAT0,启动传输 spi0[SPIDAT0] = txData & 0xFFFF; // 只取低16位,因为我们配置了16位字长 // 3. 等待接收完成(RXINTFLAG = 1)或任何错误 timeout = 0xFFFF; while (1) { status = spi0[SPIFLG]; if (status & 0x100) { // RXINTFLAG break; // 接收完成 } if (status & 0x10) { // BITERRFLG // 处理位错误 return 0xFFFFFFFF; } if (status & 0x02) { // TIMEOUTFLG // 处理从设备响应超时 return 0xFFFFFFFF; } if (--timeout == 0) { // 总体超时 return 0xFFFFFFFF; } } // 4. 读取接收到的数据(读取SPIBUF会自动清除RXINTFLAG) return spi0[SPIBUF] & 0xFFFF; }3.2 进阶配置:使用dMAX实现自动数据传输
轮询方式会大量占用CPU。在实际产品中,尤其是高速或连续传输场景,必须使用DMA(在C672x中称为dMAX)。dMAX可以自动将数据从内存搬运到SPIDAT0,并在接收完成后将数据从SPIBUF搬回内存,全程无需CPU干预,仅需在传输完成或出错时触发中断。
首先,需要配置dMAX通道。这里假设使用dMAX通道0服务SPI0发送请求,通道1服务接收请求。
// 简化的dMAX配置示例(具体参数需根据dMAX控制器手册设置) void dMAX_SPI_Config(void) { // dMAX全局控制使能等初始化步骤... // 配置dMAX通道0:内存 -> SPI0发送寄存器 // 源地址:发送数据数组地址 // 目标地址:SPI0的SPIDAT0寄存器地址 (0x47000038) // 传输计数:数据个数 // 触发源:SPI0的发送缓冲区空中断请求 // 配置dMAX通道1:SPI0接收缓冲区 -> 内存 // 源地址:SPI0的SPIBUF寄存器地址 (0x47000040) // 目标地址:接收数据数组地址 // 传输计数:数据个数 // 触发源:SPI0的接收完成中断请求 }然后,修改SPI初始化代码,使能dMAX请求和相应中断:
void SPI_Master_Init_DMA(void) { volatile unsigned int *spi0 = (unsigned int *)0x47000000; // 步骤1-6与轮询初始化相同 SPI_Master_Init_Polling(); // 复用基础配置,但注意下面会覆盖中断设置 // 7. 配置中断:使能接收完成中断和可能的错误中断,并允许dMAX请求 // SPIINT0: 使能接收中断(RXINTEN=1), 使能dMAX请求(DMAREQEN=1) spi0[SPIINT0] = 0x00010040; // 位16: DMAREQEN, 位6: RXINTEN // 8. 配置中断级别(通常只使用Level 0) spi0[SPILVL] = 0x00000000; // 所有使能的中断事件都映射到Level 0 // 9. 使能SPI模块(已在Init_Polling中完成) // 10. 配置并启动dMAX通道 dMAX_SPI_Config(); }此时,你只需要在内存中准备好要发送的数据数组,并设置好dMAX的传输计数,然后启动dMAX通道。SPI模块会在发送缓冲区空时自动触发dMAX搬移数据,在接收完成时自动触发dMAX将数据保存。全部完成后,会产生一个传输完成中断通知CPU。
3.3 从设备配置要点
从设备的配置相对简单,但有几个关键区别:
- 模式设置:SPIGCR1.MASTER位必须设为0(从模式)。
- 时钟配置:从设备无需配置波特率(PRESCALE),时钟由主机提供。但SPIFMT0中的CHARLEN、SHIFTDIR、POLARITY、PHASE必须与主机严格一致。
- 延时寄存器:从模式下,SPIDELAY寄存器不生效,通常写0即可。
- 中断处理:从设备通常使能接收完成中断(RXINTEN)和溢出错误中断(OVRNINTEN)。因为从设备是被动接收,需要及时读取数据避免被新数据覆盖(溢出)。
void SPI_Slave_Init(void) { volatile unsigned int *spi1 = (unsigned int *)0x48000000; // 假设使用SPI1作为从机 spi1[SPIGCR0] = 0x00; // 复位 spi1[SPIGCR1] = 0x00; // 从模式,内部时钟模式(具体看手册,从机可能也需要时钟源) spi1[SPIPC0] = 0x00000F01; // 5线模式引脚配置 spi1[SPIDAT1] = 0x00000000; // 使用SPIFMT0 spi1[SPIFMT0] = 0x00020010; // 格式与主机匹配,但WAITENA=0(从机忽略此位) spi1[SPIDELAY] = 0x00000000; // 从机延时无效 spi1[SPIINT0] = 0x00000054; // 使能接收中断(RXINTEN)和溢出中断(OVRNINTEN) spi1[SPILVL] = 0x00; spi1[SPIGCR0] = 0x01; // 退出复位 spi1[SPIGCR1] |= 0x01000000; // 使能 }4. 时序调试与常见问题深度排查指南
即使寄存器配置完全正确,SPI通信仍可能失败。此时,逻辑分析仪或示波器是必不可少的工具。你需要抓取SCLK、MOSI、MISO、CS(和ENA)的波形,对照数据手册和以下要点进行分析。
4.1 典型故障波形与原因分析
完全没有时钟或数据信号:
- 检查:SPIGCR0.RESET是否为1?SPIGCR1.ENABLE是否为1?SPIPC0引脚复用配置是否正确?对应的GPIO是否被其他功能占用?
- 实操技巧:在初始化后,尝试向SPIDAT0写一个值,然后用调试器读取SPIFLG寄存器。如果TXINTFLAG一直不为1,说明传输根本没启动,问题出在使能或时钟源上。
数据错误(位错位或字节错位):
- 首要怀疑:SPIFMTx中的SHIFTDIR、POLARITY、PHASE与从设备不匹配。这是最高频的原因。
- 检查:CHARLEN设置是否正确?如果你认为传输的是8位数据,但设成了16位,那么你会看到16个时钟脉冲,从设备可能只响应前8位,导致数据错乱。
- 使用逻辑分析仪:解码SPI信号时,务必在软件中设置与程序中完全一致的CPOL、CPHA和位序。一个设置不对,解码出的数据就是乱的。
从设备无响应或响应错误数据:
- 检查片选时序:这是最容易忽略的一点。用示波器测量C2TDELAY和T2CDELAY的实际时间。
- 现象:片选拉低后,几乎立即出现时钟。可能C2TDELAY设置过小,从设备来不及准备。
- 现象:最后一个时钟边沿与片选拉高几乎同时发生。可能T2CDELAY设置过小,从设备在片选失效前未能锁存最后一位数据。
- 检查握手信号(如果使用):在5线模式下,测量SPIx_ENA信号。
- 现象:主机一直不发时钟,且SPIFLG.TIMEOUTFLG被置位。说明从设备未能在C2EDELAY时间内拉低ENA。检查从设备是否支持此引脚,电平是否匹配(低有效)。
- 现象:传输完成后,主机很快开始下一次传输,但从设备似乎“卡住”。检查T2EDELAY,可能从设备释放ENA太慢,触发了DESYNC错误,但你的代码没有处理该错误,导致状态异常。
- 检查片选时序:这是最容易忽略的一点。用示波器测量C2TDELAY和T2CDELAY的实际时间。
高速传输时数据丢失或错误:
- 检查波特率:计算出的SPI时钟是否超过了从设备支持的最大速率?是否超过了PCB走线在特定长度下能可靠传输的速率?适当降低PRESCALE值。
- 检查电气特性:上拉电阻是否必要?MOSI/MISO线是否过长,存在振铃或边沿退化?在高速下(如>10MHz),可能需要端接电阻。
- 检查dMAX或中断服务程序:如果使用DMA,是否发生了缓冲区溢出?dMAX的传输节奏是否跟得上SPI的发送/接收速度?中断服务程序处理是否太慢,导致溢出(OVRNINTFLG)?
4.2 关键寄存器配置检查清单
在调试时,可以依次核对以下寄存器值(以主机5线模式为例):
| 寄存器 | 关注位域 | 预期值/含义 | 检查目的 |
|---|---|---|---|
| SPIGCR0 | RESET | 0x1 | 模块已退出复位 |
| SPIGCR1 | MASTER, CLKMOD, ENABLE | 0x01000003 | 主机、内部时钟、已使能 |
| SPIPC0 | 相关引脚位 | 如 0xF01 | 引脚功能已正确复用到SPI |
| SPIFMTx | CHARLEN | 0x2-0x10 | 字长匹配从设备 |
| SHIFTDIR | 0或1 | MSB/LSB优先匹配 | |
| POLARITY, PHASE | 0/1组合 | SPI模式(0-3)匹配 | |
| PRESCALE | 计算值 | 波特率在从设备允许范围内 | |
| WAITENA | 1(如果使用ENA) | 已使能等待握手信号 | |
| SPIDELAY | C2TDELAY | 根据从设备tCSS设置 | 满足从设备片选建立时间 |
| T2CDELAY | 根据从设备tCSH设置 | 满足从设备片选保持时间 | |
| C2EDELAY | 根据从设备tENA响应设置 | ENA响应超时合理 | |
| T2EDELAY | 根据从设备释放ENA时间设置 | ENA释放超时合理 | |
| SPIINT0 | RXINTEN, TXINTEN, DMAREQEN | 根据需求 | 中断/DMA使能正确 |
4.3 软件层面的健壮性设计
错误处理必须完备:每次传输后,不要只检查RXINTFLAG,一定要检查SPIFLG中的错误标志位(BITERRFLG, TIMEOUTFLG, DESYNCFLG, OVRNINTFLG)。在中断服务程序中,应读取并清除所有已发生的错误标志,并记录到错误日志中。
超时机制:即使是中断或DMA模式,也要在应用层为关键操作(如等待DMA完成标志)添加软件超时。防止因硬件故障导致程序死锁。
缓冲区管理:使用DMA时,确保源和目标缓冲区在传输期间内存地址有效(不被释放)。对于双向连续传输,考虑使用乒乓缓冲区。
初始化顺序强化:一个稳健的初始化流程应该是:复位模块 -> 配置所有参数(格式、延时、引脚)-> 退出复位 -> 使能中断/DMA -> 最后使能模块(ENABLE=1)。关闭时顺序大致相反。
利用仿真寄存器调试:在复杂问题排查时,可以在中断服务程序中读取SPIEMU来查看数据,而不会影响中断标志位,方便单步调试。
最后,分享一个我踩过的坑:在一次项目中,SPI通信在低温下不稳定。最终发现是T2CDELAY设置得太极限了。在低温下,从设备的内部逻辑延迟略有增加,导致在片选失效前,数据保持时间不足。将T2CDELAY从2个周期增加到4个周期后问题消失。教训:时序参数不要卡着数据手册的最小值来设置,尤其是在环境条件变化大的应用中,务必留足余量。SPIDELAY寄存器提供的这种可编程精细延时,正是为了应对这种实际工程挑战。