4. SPI 协议实现:GD32V 主从模式、时钟相位与 DMA 传输

SPI 这个接口,说实话,在嵌入式系统里太常用了。从 SD 卡、Flash 到各种传感器,几乎天天跟它打交道。GD32V 的 SPI 模块功能挺全的,但配置起来有几个关键点,稍不注意就容易翻车。这一章,我就带你把这些坑都填上。

4.1 SPI 主从模式配置

GD32V 的 SPI 可以工作在主机模式,也可以工作在从机模式。区别其实很简单:谁提供时钟,谁就是主机。

模式 时钟源 片选控制 典型应用
主机模式 内部时钟(SCK 输出) 软件或硬件 NSS 读写 Flash、SD 卡
从机模式 外部时钟(SCK 输入) 外部 NSS 控制 与主控 MCU 通信

配置主机模式时,我习惯先把 GPIO 的复用功能开好。SCK、MOSI、MISO 这三根线,必须配到 SPI 对应的引脚上。GD32V 的引脚复用表挺长的,建议直接查数据手册的「Alternate function mapping」章节。

核心配置步骤:

  1. 使能 SPI 时钟和 GPIO 时钟
  2. 配置 GPIO 为复用推挽输出(SCK、MOSI)和浮空输入(MISO)
  3. 设置 SPI 控制寄存器:主机模式、数据帧格式、波特率
  4. 使能 SPI

从机模式呢?我建议你把 NSS 引脚配成硬件片选模式。这样主机的片选信号一拉低,从机就自动准备接收数据了。省心。

4.2 时钟极性(CPOL)和相位(CPHA)设置

这个知识点,面试常考,项目里也常坑人。CPOL 决定时钟空闲时的电平,CPHA 决定数据在哪个边沿采样。

模式 CPOL CPHA 采样边沿 空闲时钟
模式 0 0 0 上升沿 低电平
模式 1 0 1 下降沿 低电平
模式 2 1 0 上升沿 高电平
模式 3 1 1 下降沿 高电平

我曾经在一个项目里,用 GD32V 跟一个温湿度传感器通信。传感器手册上写的是「模式 0」,我心想这简单,直接配了 CPOL=0、CPHA=0。结果读出来的数据全是 0xFF。折腾了半天,用逻辑分析仪一看,才发现传感器实际用的是模式 3。嗯,手册有时候也会写错,或者你理解错了。所以我的建议是:先看波形,再写代码。

避坑指南: 我曾经遇到过 SPI 通信偶尔出错的情况,排查了很久。最后发现是 CPHA 配置错了半个边沿。主机在上升沿发送数据,从机也在上升沿采样,结果数据刚好在跳变的时候被采到。这种问题,用示波器看 MISO 和 SCK 的时序关系,一眼就能发现。

4.3 DMA 传输

SPI 做轮询传输,CPU 得一直等着。数据量一大,比如刷屏或者读写大容量 Flash,CPU 就被占死了。这时候就该 DMA 上场了。

GD32V 的 DMA 控制器有多个通道,每个通道可以绑定不同的外设请求。SPI 的发送和接收,分别对应 DMA 的请求线。

DMA 传输配置流程:

  1. 配置 DMA 通道:源地址、目的地址、传输长度
  2. 设置传输方向:外设到内存(接收)或内存到外设(发送)
  3. 使能 SPI 的 DMA 发送或接收请求
  4. 启动 DMA 传输

这里有个细节:SPI 的接收和发送是独立的 DMA 请求。如果你只做发送,可以不使能接收 DMA。但如果你要同时收发,就得配两个 DMA 通道。我一般会配一个通道专门发,一个通道专门收。

// SPI DMA 发送配置示例
dma_single_data_parameter_struct dma_init_struct;
dma_deinit(DMA0, DMA_CH0);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_init_struct.memory_addr = (uint32_t)tx_buffer;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.periph_addr = (uint32_t)&SPI0->DATA;
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.number = 256;
dma_single_data_mode_init(DMA0, DMA_CH0, &dma_init_struct);
dma_circulation_disable(DMA0, DMA_CH0);
dma_channel_enable(DMA0, DMA_CH0);

// 使能 SPI 的 DMA 发送
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);

DMA 传输完成后,会触发中断。你可以在中断里设置一个标志位,告诉主循环「数据发完了」。这样 CPU 就可以去干别的事,比如处理按键或者刷新显示。

个人经验: 用 DMA 做 SPI 传输时,记得检查 DMA 的传输完成标志。我曾经遇到过 DMA 配置了循环模式,结果数据一直在发,根本停不下来。后来我习惯在每次传输前,先清一下标志位,再启动 DMA。

4.4 知识结构图

下面这张图,把 SPI 配置的核心逻辑串起来了。从模式选择到时钟配置,再到 DMA 传输,每一步都有对应的寄存器操作。

SPI 配置核心流程 开始配置 SPI 选择主/从模式 配置 CPOL / CPHA / 波特率 是否使用 DMA? 是 配置 DMA 通道 使能 SPI DMA 请求 否 轮询发送/接收 SPI 配置完成 提示:DMA 传输完成后,记得检查传输完成标志,避免数据未就绪就开始下一轮传输

4.5 调试与故障定位

SPI 出问题,最常见的现象就是数据对不上。我总结了几种典型场景:

  • 全是 0xFF: 大概率是 MISO 没接好,或者从机没被片选。用万用表量一下 NSS 引脚电平。
  • 数据错位: 时钟极性或相位不匹配。用逻辑分析仪抓 SCK 和 MOSI,看看数据是在上升沿还是下降沿变化的。
  • DMA 传输卡死: 检查 DMA 通道是否被其他外设占用。GD32V 的 DMA 通道有优先级,高优先级的会抢占低优先级的。

调试小技巧: 我习惯在 SPI 初始化完成后,先发一个 0x55 或 0xAA 的测试数据。这两个数的二进制是 01010101 和 10101010,在示波器上能很清楚地看到每一位的变化。如果波形不对,说明配置有问题。