1. SPI接口原理与核心概念解析
SPI,全称Serial Peripheral Interface,即串行外设接口,是嵌入式系统领域应用最广泛的同步串行通信协议之一。它不像UART那样需要复杂的波特率协商,也不像I2C那样需要地址寻址,其设计哲学就是“简单、直接、高速”。在我十多年的嵌入式开发经历里,从简单的8位MCU到复杂的多核处理器,SPI几乎无处不在——驱动Flash存储器、读取传感器数据、控制TFT显示屏,甚至作为FPGA的配置接口。它的核心价值在于,用最少的硬件引脚(通常4根线)实现了全双工、高速的数据交换,这对于资源受限且对实时性要求高的嵌入式场景来说,是难以替代的。
SPI通信建立在主从架构之上。想象一下乐队指挥和乐手的关系:指挥(主设备)掌控着节拍(时钟),决定何时开始演奏(发起通信),并点名哪位乐手(从设备)参与。在SPI中,这个“点名”就是通过片选信号(SPISEL,或称SS、CS)实现的。一个主设备可以连接多个从设备,但同一时刻只能与一个从设备通信,这是通过独立的片选线来管理的。通信一旦开始,数据就在主设备发出的时钟(SPICLK)节拍下,通过两根数据线同步传输:主设备发送数据给从设备用MOSI(Master Out Slave In),从设备返回数据给主设备用MISO(Master In Slave Out)。这种全双工特性意味着主设备在发送指令的同时,就能收到从设备的响应数据,效率非常高。
SPI协议的精髓,或者说最容易让新手困惑的地方,在于时钟极性和相位的配置,也就是常说的SPI Mode。这决定了数据在时钟信号的哪个边沿被采样,是通信双方能够正确解读数据的前提。时钟极性(CPOL或CI)定义了时钟信号在空闲时的电平状态:是低电平(CPOL=0)还是高电平(CPOL=1)。时钟相位(CPHA或CP)则定义了数据采样的时刻:是在时钟的第一个边沿(CPHA=0)还是第二个边沿(CPHA=1)。这两者的组合构成了四种SPI模式(Mode 0, 1, 2, 3)。绝大多数SPI从设备的数据手册都会明确指定其支持的SPI模式,主设备的配置必须与之严格匹配,否则读回来的将是一堆乱码。这是调试SPI驱动时第一个要检查的地方。
1.1 SPI工作模式与信号时序深度剖析
要真正玩转SPI,不能只停留在概念上,必须深入到信号波形层面去理解。我们以MPC8309的SPI控制器为例,其模式寄存器SPMODE中的CI(Clock Invert)和CP(Clock Phase)位,就分别对应着时钟极性和相位。
当SPMODE[CI] = 0时,SPICLK的空闲状态为低电平;为1时则为高电平。而SPMODE[CP]则决定了数据锁存的边沿。参考手册中的图19-5和图19-6非常关键,它们直观展示了四种组合下的时序。
- 模式0 (CP=0, CI=0):这是最常见的一种模式。时钟空闲为低(CI=0),数据在时钟的上升沿被采样(CP=0意味着时钟在数据周期中间开始跳变,第一个边沿是上升沿)。数据必须在时钟上升沿到来之前就保持稳定(建立时间),并在上升沿之后继续保持一段时间(保持时间)。
- 模式1 (CP=0, CI=1):时钟空闲为高(CI=1),数据在时钟的下降沿被采样(第一个边沿是下降沿)。
- 模式2 (CP=1, CI=0):时钟空闲为低(CI=0),数据在时钟的下降沿被采样(CP=1意味着时钟在数据周期开始时跳变,第二个边沿是下降沿)。
- 模式3 (CP=1, CI=1):时钟空闲为高(CI=1),数据在时钟的上升沿被采样(第二个边沿是上升沿)。
这里有一个非常实用的记忆技巧:关注数据采样边沿。对于CP=0,采样发生在时钟的第一个跳变沿(从空闲状态跳变到相反状态的那个边沿);对于CP=1,采样发生在时钟的第二个跳变沿(跳变回空闲状态的那个边沿)。在配置主设备时,务必根据从设备手册的要求,正确设置CI和CP位。
注意:SPI通信的发起和结束完全由主设备控制。对于从设备而言,只有当其片选信号SPISEL被主设备拉低(有效)时,它才会“监听”SPICLK并参与通信。在SPISEL无效期间,从设备的MISO引脚必须处于高阻态,以避免总线冲突。在多主设备系统中,这一点尤为重要,通常需要将SPI信号线配置为开漏(Open-Drain)模式,并通过外部上拉电阻实现“线与”逻辑。
1.2 多主环境与开漏配置
标准的SPI是单主多从的,但在一些复杂的系统中,可能存在多个主设备(例如两个MPC8309之间需要直接通信)。MPC8309的SPI模块支持这种多主环境。其关键在于SPMODE[OD](Open Drain)位和SPIE[MME](Multiple-Master Error)状态位。
当SPMODE[OD] = 1时,SPI的输出引脚(SPIMOSI, SPICLK)被配置为开漏模式。这意味着控制器只能将信号拉低,而不能主动驱动为高电平。高电平状态需要依靠连接在总线上的外部上拉电阻来维持。这样,当多个主设备连接到同一组SPI总线时,如果一个设备驱动低电平,整个总线就是低电平,实现了“线与”功能,避免了多个设备同时驱动高电平造成的短路风险。
在多主模式下,SPISEL信号的角色发生了变化。对于主设备,SPISEL是一个输入信号。当主设备试图发起通信时,它会先检测SPISEL引脚的电平。如果发现SPISEL已经被另一个主设备拉低(表明总线正被占用),那么当前主设备就会触发一个多主错误(MME),并设置SPIE[MME]位,同时可能产生中断。这是一种简单的硬件仲裁机制,防止了总线冲突。因此,在多主配置中,每个主设备都需要一个额外的GPIO来作为输出,用于拉低SPISEL以选择目标从设备(或另一个作为从设备的主设备),同时其自身的SPISEL引脚需要被配置为输入,用于检测总线忙状态。
2. MPC8309 SPI模块寄存器详解与配置逻辑
MPC8309的SPI控制器是一个高度可编程的模块,其行为完全由一组内存映射寄存器控制。理解每个寄存器的每一位,是编写稳定可靠驱动的基础。这些寄存器位于由IMMRBAR(Internal Memory Map Register Base Address Register)定义的基地址偏移处,SPI模块的基地址偏移是0x0_7000。
2.1 SPI模式寄存器(SPMODE):通信的基石
SPMODE寄存器(偏移0x020)是SPI配置的核心,它定义了通信的基本框架。
| 位域 | 名称 | 描述与配置要点 |
|---|---|---|
| 1 | LOOP | 回环模式。置1时,发送端输出直接内部连接到接收端输入,用于测试SPI控制器本身是否工作正常,无需外部连接。调试驱动时,首先启用回环模式自测,可以快速隔离是软件配置问题还是硬件连接问题。 |
| 2 | CI | 时钟极性。0: SPICLK空闲为低;1: SPICLK空闲为高。必须与从设备严格匹配。 |
| 3 | CP | 时钟相位。0: SPICLK在数据周期中间开始跳变;1: SPICLK在数据周期开始时跳变。必须与从设备严格匹配。 |
| 4 | DIV16 | 16分频选择(仅主模式有效)。0: SPI波特率发生器(BRG)的输入时钟为系统输入时钟;1: BRG输入时钟为系统输入时钟/16。用于降低时钟频率,扩展通信距离或适配低速从设备。 |
| 5 | REV | 数据位序反转。0: 先发送/接收最低有效位(LSB First);1: 先发送/接收最高有效位(MSB First)。这是另一个常见的坑点,很多设备(如某些ADC)要求MSB First,而默认往往是LSB First。 |
| 6 | M/S | 主/从模式选择。0: 从模式;1: 主模式。 |
| 7 | EN | SPI使能。1使能SPI模块。手册特别强调:在清除EN位(禁用SPI)后,至少需要等待10个输入时钟周期,才能重新置位EN。这是为了确保内部状态机完全复位。 |
| 8-11 | LEN | 字符长度。定义每次传输的数据位宽,从4位到16位,或32位。例如,0011表示4位,1111表示16位,0000表示32位。需要注意的是,无论LEN设置为多少,发送(SPITD)和接收(SPIRD)寄存器都是32位。当LEN<=16时,有效数据位于寄存器的低16位中。具体位置需要根据REV位计算。 |
| 12-15 | PM | 预分频模数。与DIV16位共同决定SPI波特率。公式为:SPICLK频率 = 输入时钟频率 / ( (DIV16?16:1) * 4 * (PM+1) )。PM取值范围0-15,因此分频系数范围为4到64。 |
| 19 | OD | 开漏模式。1: 所有SPI输出引脚(SPIMOSI, SPICLK)配置为开漏,用于多主系统。 |
配置心得:在初始化SPMODE时,一个稳健的做法是,先配置好除EN位之外的所有参数,最后再一次性写入EN=1。因为手册明确指出,在EN=1时,不应更改SPMODE的其他位。波特率的计算需要结合处理器的系统时钟。例如,假设系统给SPI的输入时钟是66.67MHz,我们希望得到约1MHz的SPICLK。可以设置DIV16=1(先除以16得~4.167MHz),再设置PM=5(分频系数为4*(5+1)=24),最终SPICLK = 66.67MHz / (16 * 24) ≈ 0.174MHz。如果觉得太快,可以调整PM值。
2.2 数据与命令寄存器:控制数据流
数据收发通过三个关键寄存器协同完成:SPITD(发送)、SPIRD(接收)和SPCOM(命令)。
SPI发送数据保持寄存器(SPITD,偏移0x030):这是一个只写寄存器。当状态寄存器SPIE[NF](Not Full)为1时,表明发送缓冲区为空,可以向SPITD写入下一个要发送的字符。写入操作会自动清除NF位,直到该字符被移出移位寄存器,NF会再次被置1。这里有一个关键细节:即使你配置的字符长度(LEN)只有8位,你写入SPITD的32位数据中,也只有对应的有效位会被发送。你需要根据REV和LEN的设置,将数据放到正确的位置。参考手册图19-12至19-15给出了清晰的示例。
SPI接收数据保持寄存器(SPIRD,偏移0x034):这是一个只读寄存器。当SPIE[NE](Not Empty)为1时,表明接收缓冲区有数据,可以从SPIRD读取接收到的字符。读取操作会清除NE位(如果后续没有待读数据)。读取的数据同样需要根据REV和LEN的设置,从32位寄存器中提取出有效位。
SPI命令寄存器(SPCOM,偏移0x02C):这个寄存器只有一个有效位LST(Last)。在写入一帧数据的最后一个字符到SPITD之前,需要先向SPCOM寄存器写入LST=1。这样,当这个最后一个字符传输完毕时,SPI控制器会设置SPIE[LT](Last Transmitted)事件位,这通常用于触发中断,通知CPU本帧数据传输结束。这是一个非常容易忽略的步骤,如果忘记设置LST,你将无法通过LT事件得知传输何时真正结束。
2.3 事件与中断管理:高效处理通信状态
SPI的事件和中断由三个寄存器管理:SPIE(事件)、SPIM(中断掩码)和相关的GPIO中断控制寄存器(如果使用GPIO模拟片选)。
SPI事件寄存器(SPIE,偏移0x024):这是一个混合访问寄存器(可读,写1清除)。它实时反映了SPI控制器的各种状态。关键位包括:
NE(Not Empty): 接收寄存器有数据。NF(Not Full): 发送寄存器可写入新数据。OV(Overrun): 从机/主机溢出。当接收端尚未读取旧数据,新数据已经到来时发生。UN(Underrun): 从机欠载。在从模式下,主机时钟到来时,从机发送寄存器没有准备好数据。MME(Multiple-Master Error): 多主错误。在主模式下,如果SPISEL引脚被外部拉低,则置位。LT(Last Transmitted): 最后一字符发送完成(当SPCOM[LST]=1时)。
SPI中断掩码寄存器(SPIM,偏移0x028):该寄存器的位与SPIE一一对应。将某位置1,则使能对应事件的中断;清0则屏蔽。例如,如果你希望每当接收寄存器有数据(NE)时就产生中断,那么就将SPIM[NE]置1。
中断处理流程:典型的查询或中断驱动流程如下:
- 初始化时,向
SPIE写入0xFFFF_FFFF以清除所有可能的历史事件位。 - 根据需求配置
SPIM,使能所需中断(如NE, LT)。 - 在中断服务程序(ISR)中,读取
SPIE判断事件来源。 - 处理事件(如读取SPIRD,或写入下一个数据到SPITD)。
- 向SPIE的相应位写1以清除事件标志(这是关键!否则会持续触发中断)。
- 如果使能了
LT中断,在最后一笔数据写入SPITD前,设置SPCOM[LST]=1。
实操陷阱:
OV(溢出)和UN(欠载)错误往往意味着你的程序处理速度跟不上SPI的通信速率。在高速通信或大数据量传输时,必须使用DMA或确保中断服务例程足够快。对于从机欠载,在从机模式下,必须在SPISEL有效前,就将要发送的数据预写入SPITD。
3. MPC8309 SPI驱动配置实操指南
理论说得再多,不如动手调一遍。下面我将结合一个具体的场景:配置MPC8309的SPI为主模式,以模式0(CP=0, CI=0),1MHz时钟,8位数据长度,MSB First,与一个SPI Flash芯片通信,来演示完整的配置和驱动编写流程。我们假设使用GPIO1的Pin0作为Flash的片选信号。
3.1 硬件连接与初始化序列
首先,确认硬件连接:
- MPC8309 SPIMOSI -> Flash SI (数据输入)
- MPC8309 SPIMISO -> Flash SO (数据输出)
- MPC8309 SPICLK -> Flash SCK (时钟)
- MPC8309 GPIO1_0 -> Flash CS# (片选,低有效)
初始化序列必须严格按照手册19.5.1节的步骤,并补充细节:
// 假设 SPI 模块基地址 #define SPI_BASE (IMMRBAR + 0x7000) #define SPI_MODE (*(volatile uint32_t *)(SPI_BASE + 0x020)) #define SPI_EVENT (*(volatile uint32_t *)(SPI_BASE + 0x024)) #define SPI_MASK (*(volatile uint32_t *)(SPI_BASE + 0x028)) #define SPI_COM (*(volatile uint32_t *)(SPI_BASE + 0x02C)) #define SPI_TXDATA (*(volatile uint32_t *)(SPI_BASE + 0x030)) #define SPI_RXDATA (*(volatile uint32_t *)(SPI_BASE + 0x034)) // GPIO1 基地址 #define GPIO1_BASE (IMMRBAR + 0x0C00) #define GPIO1_DIR (*(volatile uint32_t *)(GPIO1_BASE + 0x00)) #define GPIO1_DAT (*(volatile uint32_t *)(GPIO1_BASE + 0x08)) void spi_master_init(void) { // 1. 配置GPIO1_0为输出,作为片选,初始化为高电平(不选中) GPIO1_DIR |= (1 << 0); // 设置为输出模式 GPIO1_DAT |= (1 << 0); // 输出高电平,取消片选 // 2. 清除所有SPI历史事件标志 SPI_EVENT = 0xFFFFFFFF; // 3. 配置SPI中断掩码(本例使用查询方式,故全部屏蔽) SPI_MASK = 0x00000000; // 4. 配置SPI模式寄存器 (SPMODE) // 假设系统输入时钟为66.67MHz,目标SPICLK=~1MHz // DIV16=1, PM=5: 分频系数 = 16 * 4*(5+1) = 16*24=384 // 66.67MHz / 384 ≈ 0.174MHz。如果需要更接近1MHz,可调整PM。 // 这里我们选择 PM=2,分频系数=16*4*(2+1)=192, 66.67/192≈0.347MHz // 模式0: CI=0, CP=0; 主模式: M/S=1; 使能: EN=1; 字符长度8位: LEN=0x7 (二进制0111) // MSB First: REV=1; 正常推挽输出: OD=0; LOOP=0。 uint32_t spmode_val = 0; spmode_val |= (0 << 1); // LOOP = 0, 正常模式 spmode_val |= (0 << 2); // CI = 0 spmode_val |= (0 << 3); // CP = 0 spmode_val |= (1 << 4); // DIV16 = 1 spmode_val |= (1 << 5); // REV = 1, MSB first spmode_val |= (1 << 6); // M/S = 1, 主模式 spmode_val |= (1 << 7); // EN = 1, 使能SPI (注意:先配置其他位) spmode_val |= (7 << 8); // LEN = 7 (对应8位字符) spmode_val |= (2 << 12); // PM = 2 spmode_val |= (0 << 19); // OD = 0 SPI_MODE = spmode_val; // 5. 可选:写入第一个数据(如果知道的话),或等待后续传输 }3.2 阻塞式单字节收发函数实现
对于简单的控制,阻塞式(查询式)收发足矣。其核心是轮询NF和NE状态位。
uint8_t spi_transfer_byte(uint8_t tx_data) { uint8_t rx_data = 0; uint32_t timeout = 100000; // 超时计数器,防止死等 // 1. 等待发送缓冲区为空(NF=1) while (!(SPI_EVENT & (1 << 23))) { // 检查NF位(第23位) if (--timeout == 0) { // 超时处理,可能是硬件故障或配置错误 return 0xFF; } } // 2. 将要发送的数据写入SPITD // 注意:根据REV=1和LEN=8,数据应放在bit[23:16](参见手册图19-13) SPI_TXDATA = ((uint32_t)tx_data << 16); // 3. 等待接收缓冲区非空(NE=1) timeout = 100000; while (!(SPI_EVENT & (1 << 22))) { // 检查NE位(第22位) if (--timeout == 0) { return 0xFF; } } // 4. 读取接收到的数据 rx_data = (SPI_RXDATA >> 16) & 0xFF; // 从bit[23:16]提取 // 5. 清除事件标志(可选,查询模式下非必须,但良好习惯) // SPI_EVENT = (1 << 22) | (1 << 23); // 写1清除NE和NF标志 return rx_data; }3.3 多字节帧传输与片选控制
实际应用中,SPI通信往往以“帧”为单位,例如向Flash发送一个读命令(0x03) followed by 一个24位的地址。这就需要我们控制片选信号,并处理多字节序列。
void spi_cs_low(void) { GPIO1_DAT &= ~(1 << 0); // GPIO1_0输出低电平,选中设备 // 微小延时,确保片选稳定。某些设备对片选建立时间有要求。 for(volatile int i=0; i<10; i++); } void spi_cs_high(void) { // 传输结束后,先拉高片选 GPIO1_DAT |= (1 << 0); // GPIO1_0输出高电平,取消选中 // 微小延时,确保片选保持时间 for(volatile int i=0; i<10; i++); } // 发送并接收多个字节(阻塞式) void spi_transfer_frame(uint8_t *tx_buf, uint8_t *rx_buf, uint32_t len, uint8_t is_last_frame) { spi_cs_low(); // 开始传输,拉低片选 // 如果不是最后一帧,不要设置LST if (is_last_frame) { // 在写入最后一字节前,设置LST位 // 注意:SPCOM是只写寄存器,写入即生效。我们只需设置LST位。 SPI_COM = (1 << 9); // 设置LST位为1 } for (uint32_t i = 0; i < len; i++) { uint8_t tx_byte = (tx_buf != NULL) ? tx_buf[i] : 0xFF; // 如果只读,发送0xFF uint8_t rx_byte = spi_transfer_byte(tx_byte); if (rx_buf != NULL) { rx_buf[i] = rx_byte; } } // 如果是最后一帧,等待LT标志置位,确保所有数据(包括最后一字节)都已移出 if (is_last_frame) { uint32_t timeout = 100000; while (!(SPI_EVENT & (1 << 17))) { // 等待LT位(第17位)置位 if (--timeout == 0) break; } // 清除LT标志 SPI_EVENT = (1 << 17); // 传输结束,可以拉高片选(如果后续没有连续传输) // 但更常见的做法是在函数外部统一控制片选,以支持连续命令。 } // 注意:对于非最后一帧,这里不能拉高片选,因为帧间片选需要保持有效。 } // 示例:读取SPI Flash的ID(命令0x9F) uint32_t spi_flash_read_id(void) { uint8_t tx_cmd = 0x9F; // READ ID命令 uint8_t rx_buf[3] = {0}; // 通常返回3字节ID uint32_t id = 0; spi_cs_low(); spi_transfer_byte(tx_cmd); // 发送命令 for(int i=0; i<3; i++) { rx_buf[i] = spi_transfer_byte(0xFF); // 发送dummy数据,读取ID字节 } spi_cs_high(); // 读取完成,拉高片选 id = (rx_buf[0] << 16) | (rx_buf[1] << 8) | rx_buf[2]; return id; }4. 常见问题排查与调试经验实录
调试SPI驱动,逻辑分析仪或者示波器是必不可少的。光看代码是否正确,不如直接抓取SPICLK, MOSI, MISO, CS四条线上的波形来得直观。下面是我在项目中遇到过的典型问题及排查思路。
4.1 问题一:收不到数据或数据全为0xFF/0x00
这是最常见的问题。请按以下清单逐项检查:
- 硬件连接:用万用表检查四根线是否连通,有无虚焊。特别注意MISO和MOSI是否接反。
- 片选信号:确认片选信号(CS)是否在通信期间被正确拉低。用示波器查看,片选拉低的时间是否覆盖了整个数据传输过程。一个低级错误是:在每发送一个字节后就拉高了片选,而不是在一帧命令全部发完后再拉高。
- 时钟极性与相位(Mode):这是导致数据错位的头号杀手。用示波器同时抓取SPICLK和MOSI。对照从设备的数据手册,看数据是在时钟的上升沿还是下降沿稳定,以及时钟空闲状态。调整主设备的CI和CP位,直到波形匹配。一个技巧:先尝试Mode 0和Mode 3,因为大多数设备支持这两种之一。
- 数据位序(MSB/LSB):检查
SPMODE[REV]位。用逻辑分析仪解码SPI数据,如果发现发送的命令字节位序反了(例如发送0x03(0000 0011)却解码出0xC0(1100 0000)),那就是REV位设反了。 - 波特率过高:如果线缆较长或有干扰,过高的SPICLK会导致数据采样错误。尝试降低波特率(增大PM值或启用DIV16),看问题是否消失。
- 从设备是否上电/就绪:有些设备(如Flash)上电后需要一定时间初始化。查阅手册,确认是否需要发送特定的唤醒命令(如Release Power-Down)。
4.2 问题二:只能发送,不能接收(MISO线一直是高阻或固定电平)
- MISO引脚配置:在MPC8309作为主设备时,MISO是输入引脚,通常无需特别配置。但请确认该引脚没有被复用作其他功能(如GPIO输出)。检查设备树(Device Tree)或引脚复用寄存器,确保SPI功能被正确映射到物理引脚上。
- 从设备驱动能力:如果总线上挂载了多个从设备,且未使用的从设备MISO引脚未处于高阻态,可能会造成总线冲突。确保未选中的从设备其MISO为高阻。
- 回环测试:将
SPMODE[LOOP]置1,进行回环测试。主设备发送一个特定数据,然后读取。如果回环模式下能正确收到自己发送的数据,说明MPC8309的SPI控制器本身和软件配置基本正确,问题出在外部硬件或从设备上。如果回环都失败,那就要重点检查软件配置和寄存器读写是否正确。
4.3 问题三:通信不稳定,偶尔出错
- 电源与地噪声:SPI对电源质量敏感,尤其是高速情况下。确保电源滤波电容充足,地线回路良好。可以用示波器查看SPICLK和电源上的噪声。
- 时序裕量不足:在接近SPI控制器或从设备极限频率下工作,容易因时钟抖动、建立保持时间不足而出错。务必留出足够的时序裕量。计算出的理论最高频率,在实际应用中建议只用到70%-80%。
- 中断与DMA竞争:如果使用了中断或DMA,在高速连续传输时,要确保数据处理(如填充发送缓冲区、读取接收缓冲区)的速度快于SPI传输速度,否则会发生OV(溢出)或UN(欠载)错误。检查
SPIE寄存器中的错误标志位。 - 多主冲突:在多主系统中,如果
SPIE[MME]位被置位,说明发生了总线仲裁冲突。需要检查总线仲裁逻辑,确保同一时刻只有一个主设备在驱动总线。
4.4 寄存器配置检查表
当你觉得配置无误但SPI就是不工作时,可以对照此表,��过调试器直接读取寄存器值进行核对:
| 寄存器 | 偏移地址 | 关键位检查点 | 预期值(示例:主模式,8位,Mode0,MSB First) |
|---|---|---|---|
| SPMODE | 0x020 | EN=1, M/S=1, REV=1, CP=0, CI=0, LEN=7, PM/DIV16 | 例如:0x0000_0E87 (需根据时钟计算PM) |
| SPIE | 0x024 | 初始化后应清除所有位(写0xFFFF_FFFF)。传输前NF应为1。 | 0x0080_0000 (仅NF=1) |
| SPIM | 0x028 | 如果不用中断,应为0。如果用中断,使能对应位。 | 0x0000_0000 |
| SPCOM | 0x02C | 仅在发送最后一字节前写入LST=1。 | 0x0000_0200 (LST=1时) |
最后,分享一个我踩过的深坑:在一次调试中,SPI通信时好时坏。最终发现是PCB布局问题,SPI时钟线(SCK)与一条高速数据线平行且距离过近,导致了严重的串扰。用示波器在SCK上看到了明显的毛刺。重新布线后问题彻底解决。所以,当软件和基础硬件检查都无效时,不妨怀疑一下PCB设计,特别是对于几十MHz以上的SPI时钟。