SPI — SPI 主从全双工轮询
固件库: GD32F30x_Firmware_Library V3.0.3
芯片: GD32F30x(以 GD32F307 评估板为例)
源文件:GD32F30x_Firmware_Library_V3.0.3/Examples/SPI/SPI_master_slave_fullduplex_polling/main.c
配套例程:SPI_master_slave_fullduplex_polling
功能简介
本示例把同一块板子上的两个 SPI 外设连在一起,一个当"主机"(SPI0),一个当"从机"(SPI2),用杜邦线把它们的信号线交叉对接,让两者互相发送一数组数据,再校验收到的内容是否和对方发出去的一致。
- SPI0 作为主机:主动产生时钟,发送
spi0_send_array[](10 字节:0xA1~0xAA),并接收从机回的数据。 - SPI2 作为从机:跟着主机的时钟走,发送
spi2_send_array[](10 字节:0xB1~0xBA),并接收主机回的数据。 - 因为是全双工,主机发 1 字节的同时也从从机收 1 字节;收完后用
memory_compare()比对:- 从机 SPI2 收到的 == 主机发出的 → LED2 亮
- 主机 SPI0 收到的 == 从机发出的 → LED3 亮
为什么要"自己和自己通信"?真实项目里 SPI 主机是 MCU、从机是外部芯片。但评估板上没有现成从芯片时,用另一路 SPI 当从机是验证 SPI 通信逻辑最省事的办法——你完全掌控双方时序,出问题好定位。
本示例默认 SPI_CRC_ENABLE = 0(关闭 CRC 校验),走最简单的 10 字节纯收发逻辑,最适合入门。
硬件准备
1. 开发板
- GD32F30x 评估板一块(例程按 GD32F307C EVAL 板编写,LED2/LED3 由
gd_eval_led_init初始化)。
2. 杜邦线(关键!)
把板上的 SPI0 和 SPI2 用线交叉对接。SPI2 例程里做了引脚重映射(GPIO_SPI2_REMAP),所以 SPI2 用的是 PC 口。具体接法:
| 主机 SPI0 | 从机 SPI2(重映射后) | 说明 |
|---|---|---|
| PA3 | PA4 | NSS(片选):主机用 PA3 当软件片选,去选从机的 NSS(PA4) |
| PA5 (SCK) | PC10 (SCK) | 时钟 |
| PA6 (MISO) | PC11 (MOSI) | 主机收 = 从机发 |
| PA7 (MOSI) | PC12 (MISO) | 主机发 = 从机收 |
注意 MISO / MOSI 的接法:主机 MOSI(PA7) 接从机"数据输入脚"——在重映射后从机 SPI2 的数据输入脚是 PC12(例程把它配成了
AF_PP输出)。主机的 MISO(PA6) 接从机的数据输出脚 PC11(配成IN_FLOATING输入)。一句话:把"主发"对"从收",“主收"对"从发”。
3. 串口 / 调试器(可选)
本例用 LED 指示结果,不需要串口。想看具体收发数组可用调试器看 spi0_receive_array[] / spi2_receive_array[]。
完整代码(带中文注释)
|
|
代码分段讲解
1. 宏与全局数组
|
|
SPI_CRC_ENABLE控制是否启用 CRC 校验,教学版设为 0,走简单分支。SET_SPI0_NSS_*把"片选拉高/拉低"封装成宏。SPI 通信必须先拉低 NSS 选中从机,结束后拉高。- 两个发送数组:主机发
0xA1~0xAA,从机发0xB1~0xBA。它们内容不同,正好用来验证"收到的确实是对方发的"。
2. 时钟与 GPIO 配置
SPI 的引脚是复用功能(AF),不是普通 GPIO,所以:
- SPI0 的 SCK/MOSI 配成
GPIO_MODE_AF_PP(复用推挽);MISO 配IN_FLOATING(浮空输入,由对方驱动)。 gpio_pin_remap_config(GPIO_SPI2_REMAP, ENABLE)把 SPI2 从默认引脚挪到 PC10/11/12,重映射必须开RCU_AF时钟,这一点常被忘记。- 主机 NSS(PA3) 只是个普通输出脚,由软件拉高拉低,所以配
OUT_PP。 - 从机 NSS(PA4) 配
IN_FLOATING,因为它被配成了SPI_NSS_HARD——由 SPI 硬件自动检测这个引脚是否被拉低。
为什么主机的 MOSI(PA7) 接的是从机的 PC12(配成 AF_PP),而主机的 MISO(PA6) 接的是从机的 PC11(配成 IN_FLOATING)? 因为对"从机 SPI2"来说,PC12 是它的数据输入(DI)、PC11 是它的数据输出(DO)。主机 MOSI(主发)对接从机 DI,主机 MISO(主收)对接从机 DO。名字叫 MOSI/MISO 是站在主机视角;接到从机时要看从机那端是 DI 还是 DO。
3. SPI 参数配置
|
|
- 主从必须使用相同的 CPOL/CPHA(模式),否则采样错位、数据全错。本例两者都用同一结构体初始化,天然一致。
SPI_PSC_256把 SPI 时钟分得极低(APB 时钟 / 256),仅为方便观察;真实项目里在从机允许范围内尽量取高分频值以提高速率。- 主机
SPI_NSS_SOFT,从机SPI_NSS_HARD:这是 SPI 主从对接的常见搭配——主机用任意 GPIO 当片选,从机用硬件 NSS 引脚感知是否被选中。
4. 主循环:收发成对出现
|
|
这是 SPI 全双工的本质体现:
- 等两个 SPI 的 TBE(发送缓冲空),分别把各自要发的数据写进发送寄存器。主机写数据的同时会产生 8 个 SCK 时钟,从机跟着这个时钟把它的数据也移位出来。
- 等两个 SPI 的 RBNE(接收缓冲非空),把各自收到的数据读走。
- 因为一帧 8 位里主机发 1 字节、从机也回 1 字节,所以"发"和"收"永远成对,循环里先 TBE 后 RBNE 各两行。
为什么从机也要等 TBE 才发? 从机没有自己的时钟,它的发送也是靠主机提供的 SCK 来移位。主机每发一个字节就产生 8 个时钟,从机借这 8 个时钟把自己的发送寄存器移出去、把主机的移进来。所以只要主机在发,从机就能收发,二者节奏完全由主机时钟同步。
5. 结束与校验
|
|
拉高 NSS 告诉从机"别再通信了"。然后用逐字节比较判断互收是否成功。两个 LED 都亮代表主从收发完全正确。
实验现象
- 下载程序并复位后,接线正确的前提下:
- LED2 亮:表示 SPI2 从机收到的数据 == 主机 SPI0 发出的
0xA1~0xAA。 - LED3 亮:表示 SPI0 主机收到的数据 == 从机 SPI2 发出的
0xB1~0xBA。
- LED2 亮:表示 SPI2 从机收到的数据 == 主机 SPI0 发出的
- 两个 LED 都亮 = 全双工收发成功。
- 若用调试器暂停,可观察
spi2_receive_array[]应为{0xA1..0xAA},spi0_receive_array[]应为{0xB1..0xBA}。
因为是"同一块板子内部两个 SPI 互连",只要接线对、配置对,几乎必然成功,非常适合拿来建立 SPI 的直观认识。
常见问题
Q1:两个 LED 都不亮 / 只有一个亮,收的数据对不上?
最常见原因是接线接反了。重点检查:主机 MOSI(PA7) 必须接从机的数据输入脚(重映射后是 PC12),主机 MISO(PA6) 必须接从机的数据输出脚(PC11)。把 MOSI/MISO 当成"同名直连"是最常见的错接。
Q2:主从的 CPOL/CPHA 要怎么配?
必须完全一致。本例主从都用 SPI_CK_PL_LOW_PH_1EDGE(模式 0)。若你后续接外部芯片(如 W25Q Flash),要先查手册它的 SPI 模式是多少,把 MCU 配成同一个。
Q3:为什么从机用 SPI_NSS_HARD,主机用 SPI_NSS_SOFT?
这是 SPI 主从的常规搭配。主机随便挑一个 GPIO(本例 PA3)用软件拉高拉低当片选;从机的 NSS 引脚(PA4)被硬件监控——只有它被拉低,从机才认为"被选中"并开始通信。若从机也用软件 NSS,则它不会去检测外部 PA4 电平。
Q4:分频 SPI_PSC_256 这么慢,能调快吗?
可以。256 只是为了教学便于观察。真实项目中在从机允许的最高速率内尽量取小分频以提高吞吐。注意 SPI2 在 APB1 上、SPI0 在 APB2 上,二者挂载总线不同,但本例主从分频一致即可。
Q5:为什么我接了线还是不通,但仿真看发送数组没问题?
检查三件事:① RCU_AF 时钟是否打开(用到重映射必须开);② gpio_pin_remap_config(GPIO_SPI2_REMAP, ENABLE) 是否调用;③ NSS 是否先在 spi_enable 之前/之后被拉低。SPI 必须在 NSS 为低期间通信。
Q6:开启 CRC(SPI_CRC_ENABLE = 1)后逻辑有什么不同?
开启后,循环里会先发前 9 个字节,最后再各发 1 个字节并追加发送 CRC 值,然后双方各收 1 个数据字节 + 1 个 CRC 字节,最后读 SPI_FLAG_CRCERR 标志判断 CRC 是否出错。CRC 用于在噪声环境下检测数据对错,教学阶段建议先关掉理解基础收发。
小结
本示例完整展示了 SPI 主从全双工轮询的核心套路:
- 开时钟:
RCU_GPIOx+RCU_SPIx+ 用到重映射时的RCU_AF。 - 配 GPIO:SCK/MOSI 复用推挽,MISO 浮空输入;NSS 主机用普通 GPIO。
- 配 SPI 参数:模式(全双工/主从)、帧长、CPOL/CPHA、分频、NSS 软/硬、MSB/LSB——主从模式必须一致。
- 拉低 NSS → 轮询 TBE 发、轮询 RBNE 收(成对)→ 拉高 NSS。
- 校验:比较收/发数组,用 LED 等直观指示结果。
记住 SPI 的"三件套"——四线、全双工、主从同模式,你就掌握了它的灵魂。
拓展应用
掌握这个基础例程后,你可以往这些方向走:
- 驱动 SPI Flash(如 W25Q64 / W25Q128):这是 SPI 最经典的应用。把例程里的从机换成 Flash 芯片,按芯片手册的指令集(写使能→擦除→页编程→读)就能做出"掉电不丢数据"的存储模块。
- 驱动 LCD / OLED 屏(如 ST7735、SSD1306 用 SPI 接口):用 SPI 高速刷屏,比用 GPIO 模拟快得多。
- 驱动 ADC / DAC(如 ADS1256、MCP4822):用 SPI 读取高精度采样值或输出模拟电压。
- 接 SD 卡 / TF 卡:在 SPI 模式下读写文件系统(配合 FatFs)。
- 把轮询升级为 DMA:当数据量大(如连续采 1024 点波形、刷整屏图像)时,改用
SPI_master_slave_fullduplex_dma例程,让数据自动搬运、CPU 去干别的事。 - 一主多从:保留一个主机 SPI,用多根 NSS 分别选通多个从机(Flash、LCD、传感器),实现单总线扩展多个外设。
想进一步提速或减轻 CPU 负担,建议直接阅读同目录的
SPI_master_slave_fullduplex_dma例程。