SPI本身是标准协议,但需要知道在这个芯片上怎么做。重点包括:
-
硬件层面:必须强调引脚映射,比如PAxx引脚既要当SPI又要当GPIO时如何配置;电气特性;时钟源选择。
-
软件层面:GD32F470的SDK很可能提供HAL库或寄存器操作示例,需要核心流程框架。要指出关键寄存器(控制寄存器、数据寄存器、时钟分频),并关注芯片特有的点(比如DMA支持、中断方式)。
-
验证流程:这是最迫切需要的------如何证明SPI通了。不能只讲理论,要给出可操作的步骤:从硬件回环测试(避免外部设备故障干扰)到实际设备读写,再到示波器测量波形。特别是回环测试,对排查硬件连接错误极有帮助。
我们来对 SPI 进行一个全面且面向实战的介绍,并结合一个具体的读写验证流程。
第一部分:SPI 基本介绍
1.1.什么是SPI?
SPI 是一种全双工、同步、串行 的通信总线协议。它以其简单、高速 的特点,被广泛应用于微控制器与各种外设(如 Flash、SD卡、传感器、显示屏、ADC/DAC等)之间的短距离通信,工作原理其实很直接:主机产生时钟信号,从机跟着这个时钟同步收发数据。允许双向传输,可以在同一时钟周期内进行主从设备的数据交换。


1.核心特点:
-
全双工: 数据可以同时在主设备 和从设备之间发送和接收。
-
同步: 通信由主设备提供的时钟信号 SCLK 驱动,确保发送和接收的同步。
-
串行: 数据一位一位地传输。
-
主从架构: 一个总线上只能有一个主设备,但可以有多个从设备。
-
高速: 速度远高于 I2C 和 UART,通常可达几十 MHz。
2.硬件接口(4 线制标准):
SPI 至少需要 4 根信号线:
| 引脚简称 | 全称 | 方向(主设备侧) | 描述 |
|---|---|---|---|
| SCLK | Serial Clock | 输出 | 时钟信号,由主设备产生,用于同步数据位。 |
| MOSI | Master Out Slave In | 输出 | 主设备输出,从设备输入,主设备发送数据的通道。 |
| MISO | Master In Slave Out | 输入 | 主设备输入,从设备输出,主设备接收数据的通道。 |
| CS / SS | Chip Select / Slave Select | 输出 | 片选信号,低电平有效。主设备通过拉低对应从设备的 CS 引脚来选中它进行通信。每个从设备需要一个独立的 CS 线。 |

主机和从机之间的SPI连接
那怎么判断哪个是主机呢?咱们产生时钟信号的器件称为主机。主机和从机之间传输的数据与主机产生的时钟同步。另外SPI接口只能有一个主机,但可以有一个或多个从机。
来自主机的片选信号用于选择从机。这通常是一个低电平有效信号,拉高时从机与SPI总线断开连接。当使用多个从机时,主机需要为每个从机提供单独的片选信号。
对于 GD32F470芯片:
在数据手册的 "引脚配置及功能 " 章节(可以看到每个引脚(如 PA10, PB05 等)都支持多种复用功能,其中 SPIn_SCLK, SPIn_MOSI, SPIn_MISO, SPIn_CS_x 就是 SPI 功能。您需要根据硬件设计,通过配置寄存器将特定引脚设置为对应的 SPI 功能。
3.工作速度:
SPI速度的典型范围:1 MHz至100 MHz(4 Mbps至400 Mbps),具体速率取决于器件等级:
消费级IC:1-10 MHz(4-40 Mbps)
工业级器件:10-25 MHz(40-100 Mbps)
但咱们在实际应用中,会受限于外设支持(如Flash芯片可达80 MHz,传感器通常8 MHz)
**这里再插一句,对比I2C来说,**I2C速度就低太多了:标准模式100Kbps,高速模式5Mbps(理论值),但是I2C会少占用两个IO口,所以I2C速度胜在省引脚,SPI赢在速度快。
4.数据传输
-
数据长度: 通常是 8 位,但也可以是 4 到 16 位或更多(取决于控制器支持)。
-
传输顺序: MSB(最高位)先行 是最常见的,但也有 LSB 先行的设备,需要根据数据手册配置。
-
CS 控制: 在一次完整的数据帧传输期间,CS 应保持有效(低电平)。可以在连续传输多字节时一直保持有效,也可以在每字节间重新置高再拉低。
SPI的数据传输,整个过程是这样的:
首先,作为主控的设备要产生时钟信号(SCLK),这个信号就像通信的节拍器。同时,主机要通过拉低片选信号(CS)来选中要通信的从设备,这个CS信号通常是低电平有效,所以主机要给它一个逻辑0。
SPI最厉害的地方在于它是全双工的,这意味着主机可以通过MOSI线往外发送数据的同时,从设备也能通过MISO线往主机发送数据,两边是同时进行的。具体来说,主机把数据一位一位地放到MOSI线上移出去,同时又在MISO线上一位一位地收数据。
所有这些数据的发送和接收都是严格跟着时钟信号的边沿走的,你可以选择在时钟的上升沿或者下降沿来采样数据。至于每次通信要传多少位数据,这个得看具体器件的规格书,不同芯片的要求可能不一样。咱们来看两张动图


这个过程中要注意三点:
- 是时钟必须由主机产生;
- CS信号要提前拉低选中从机;
- 数据的收发是同步进行的,不是先发后收或者先收后发。这种设计让SPI既简单又高效,特别适合需要快速传输数据的场合。
1.2.SPI的传输模式
SPI有4种工作模式,主要通过CPOL和CPHA两个参数来配置:


如下是SPI模式0的时序图:
传输的开始和结束用绿色虚线表示;采样边沿用橙色虚线表示;移位边沿用蓝色虚线表示。当然,这些图形仅供参考。要成功进行SPI通信,必须参阅器件的数据手册并确保满足器件的时序规格。

如下是SPI模式1的时序图。此时:时钟极性为0,表示时钟信号的空闲状态为低电平;时钟相位为1,表示数据在下降沿采样(由橙色虚线显示),并且数据在时钟信号的上升沿移出(由蓝色虚线显示)。

如下是SPI模式2的时序图。此时:时钟极性为1,表示时钟信号的空闲状态为高电平。时钟相位为1,表示数据在下降沿采样(由橙色虚线显示),并且数据在时钟信号的上升沿移出(由蓝色虚线显示)。

如下是SPI模式3的时序图。此时:时钟极性为1,表示时钟信号的空闲状态为高电平。时钟相位为0,表示数据在上升沿采样(由橙色虚线显示),并且数据在时钟信号的下降沿移出(由蓝色虚线显示)。

- 如何选择? 由从设备的数据手册决定。比如,一个 Flash 芯片会明确规定它工作在模式 0 或模式 3。
1.3.多从机配置
咱们的多个从机可与单个SPI主机一起使用。从机可以采用常规模式连接,或采用菊花链模式连接。常规SPI模式:

在咱们SPI常规连接模式下,咱们主机必须为每个从设备单独配备一个片选信号线(CS)。当主机将某个从机的CS信号拉低时,这个从机就被选中,此时MOSI和MISO线上的时钟和数据信号就可以和这个特定的从机进行通信。关注公众号:硬件笔记本
这里有个重要限制:同一时间只能有一个从机的CS信号被拉低,如果同时拉低多个CS信号,会导致多个从机同时在MISO线上返回数据,造成数据冲突,主机就无法分辨这些数据到底来自哪个从机。
随着系统连接的从机数量增多,主机需要提供的片选线数量也会相应增加。比如连接8个从机就需要8根独立的CS线,这会快速占用主机的IO口资源,从而限制了系统能够支持的从机数量。为了解决这个问题,可以采用一些扩展技术,比如使用多路复用器(MUX)来产生片选信号。不过这种方案会增加一些硬件复杂度,需要额外添加多路复用器芯片。
菊花链模式:

在SPI菊花链连接方式下,所有从设备共享同一个片选信号(CS),从一个从机传播到下一个从机。 具体工作过程是:主机发出时钟信号(SCLK)后,数据首先传输到第一个从机,第一个从机将数据提供给第二个从机,这样依次传递下去。所有从机都使用同一个时钟信号进行同步。
这种连接方式的特点是数据传输需要更多的时钟周期。如下图,比如在一个8位数据宽度的系统中,如果要让第三个从机收到数据,就需要24个时钟脉冲(3个从机×8位),而在常规SPI模式下只需要8个时钟脉冲。这是因为数据需要依次通过每个从机,每个从机都会对数据进行处理和转发。

菊花链配置:数据传播
需要注意的是,不是所有SPI设备都支持菊花链模式。在使用这种连接方式前,必须仔细查看所用芯片的技术手册,确认其是否支持菊花链功能。有些芯片可能只支持常规的独立片选模式。此外,菊花链模式虽然节省了片选信号线,但会降低通信效率,因此要根据实际应用需求权衡选择。
第二部分:软件与协议细节
2.1.硬件SPI的介绍

我们需要操纵的只有时钟生成器和控制寄存器。
时钟生成器:控制什么时候开启时钟,什么时候关闭时钟以及时钟的速率等。
控制寄存器:控制硬件SPI发送的数据位数,以及全双工等参数。
中断:数据发送完成和接收完成采用中断的方式,不要去等待发送完成或者接收完成,只需要开启发送完成或者接收完成中断标志位,这样就不需要等待,可以执行其他任务。
DMA:主要实现数据的高效传输,使用DMA后数据由硬件方式将数据搬运到发送缓存区或者将接收缓存区的数据通过硬件搬运到MCU的内存里,释放了MCU的时间,而无需重复发送和接收的动作。
硬件缓冲区:如果不使用硬件缓冲区,数据的发送和接收都是通过MCU去直接发送和获取的,如果使用了硬件缓冲区,数据接收的时候,会临时存在缓冲区里,这样就不需要一位位的接收,而是一个字节的整个接收。
2.2.W25Q64存储器的介绍

**闪存:**闪说明读写操作速度很快,可以在很短的时间内完成数据的存储和读取;存说明闪存是一种用于存储数据的存储器件,可以持久的保存数据,并且断电后仍然能够保持数据的完整性。
容量:64Mbit(64兆位=8兆字节)
交叉连接:主机的MOSI要接从机的MISO(DI),主机的MISO要接从机的MOSI(DO);(与串口类似)
上升沿接收、下降沿发送: 
W25Q64以模式0(相位和极性都为0)作为案例,极性0:空闲为低电平,相位0:上升沿采样,下降沿移出。DI是接收所以上升沿采样,DO是发送所以下降沿移出数据。
2.3.配置SPI
1.管脚配置




其中CLK,MOSI,MISO采用硬件SPI的方式,复用引脚。CS采样软件的方式,配置成普通的推挽输出,CS使用软件配置,方便控制多个从机;采样硬件SPI配置,一个SPI只能接一个从机。
2.其他参数配置

采用全双工主机模式,数据帧为8位,采用SPI模式3,SPI时钟预分频为2:根据从设备的时钟频率来确定,只要不超过其最高的SPI频率就可以;通过手册查看SPI的时钟来源。
我们使用的是SPI4,所以时钟来源为PCLK2;
查看手册的时钟树,它是由APB2最高120MHZ得来的;在查看W25Q64的数据手册,
单线SPI速率可达133MHZ,而APB2最高才120MHZ,所以选择最快的2分频,大小端模式:选择高位在前(大端模式MSB)
3.发送与接收函数

SPI_FLAG_TBE:发送缓冲区为空标志位(等待发送缓冲区为空才能发送数据,否则会导致数据重叠错乱的问题)。
SPI_FLAG_RBNE:接收缓冲区不为空标志位(接收缓冲区不为空就代表收到数据了,既然收到数据就赶紧把数据读取出来,返回出去)
4.读取设备ID

先将CS段拉低,通过发送与读写函数去发送一个字节,这样就自动启动了时钟,发送0X90是手册中查询厂商期间8位ID的命令,后面再跟着发送24个二进制0,也就是三个0X00,发送完数据后就开始准备接收数据,由于从设备的时钟是由主机控制的,所以就需要主机再去发送一个无关紧要的数据(这里是0XFF)去触发这个时钟的生成。这样我们才能够从机发送给我们的数据,其中接收的数据又分为高八位制造商ID,低八位设备ID,所以触发时钟后将读取到的数据左移八位或到十六位的数据里,在发送一段0XFF触发这个时钟,读取到设备ID再或到十六位数据的低八位里,这样就得到了高位和低位整合的16位数据了(EF16),在拉高CS片选线,就完成了一次读取设备ID的操作。



5.写入数据流程

首先,每次写入之前都要确保写入地址中的数据为1,换成八位就是0XFF,否则会导致写入失败,所以第一步就是先将数据擦除,确保数据能够写入成功,下一步就是开启写使能,要对W25Q64进行更改操作的话每一次都要进行写使能的操作,才可以控制W25Q64成功,下一步是判断是否忙,在写入数据或者擦除数据时要确保W25Q64处于空闲的状态,只有空闲状态才能对它进行操作,
判断忙:

通过查看手册可以发现通过读取状态寄存器1的S0位可以查看BUSY,为1时表示正在忙,为0不忙,通过时序读取,发送数据之前要将CS端拉低,读取状态寄存器1通过SPI总线发送0X05,之后就能读取数据了,但是时钟是由主机控制,所以在发送OXFF(可任意)产生时钟给从机,从机才能发送数据给主机,将数据保存到变量里,读取完后再将CS端拉高恢复状态,在进行while判断,将读取到的值&上0X01,相与的结果如果等于1,说明while判断条件为真,就会陷入while循环,由于这里是do,while的形式,所以这里如果条件成立的话,就会跳回do,重新读取状态寄存器的值,直到while条件为0为止,不符合条件就结束while循环。
6.发送写使能

通过拉低CS端,在发送0X06出去,在拉高CS,就完成了一个写使能的操作。
7.擦除写入地址内的数据

这里是擦除一个扇区,W25Q64将内存分为了0-127共128块,每个块有64KB,然后又将块分成了0-15共16个扇区,每个扇区是4KB,块0的首地址是0H,结束地址是FFFFh,转换到扇区首地址就是0000h,结束地址就是FFFFh,然后扇区内的第一个扇区结束地址就是0FFFh,第二个扇区结束地址就是1FFFh,一直到最后一个扇区FFFFh,这里实现的就是擦除一个扇区的操作,一个扇区的4K换算成16进制就是0FFFh,转换成十进制就是4096,4K扇区的意思是0X00-0XFF 内存为4096。

先将CS端拉低,发送数据命令20H,再发24位的擦除地址,addr是传入的擦除地址,在将地址给划分出来,首先要先发送数据的高位,所以要将高八位右移16位去到低位,然后再转换成8位的数据,发送完高8位数据后再发送中8位(将整个数据右移8位)在转换成8位的数据(转换成U8类型是为了将高位数据清除,这样才能将中位数据真正发送出去),在将低8位数据转成U8发送出去,才完成24位地址的发送,再将CS端拉高恢复CS线,每次要更改W25Q64里的内容都要先开启写使能,在判断芯片是否忙是为了判断写使能是否结束,如果芯片正在忙说明还在执行写使能操作,写能结束后就能够擦除数据了,擦除数据完之后又要判断是否忙,即等待擦完为止,参数addr是擦除的扇区号,范围=0---15,4096转换成16进制就是1000,所以*4096传入1就是扇区1,传入2就是扇区2。
8.综合写入数据

首先擦除写入地址内的数据,开启写使能,判断是否忙,再进行写入数据,写入数据之前先将CS端拉低,拉低后发送02H命令写入数据,接着将24位的数据发送出去,紧接着就要发送要写入的数据,根据参数numbyte写入的长度,发送到SPI总线上,就实现了不断的发送,发送后再将CS端拉高,最后在确定数据发送完毕,加个忙检查判断。
下面addr/4096是为了写入地址通过除于4096的方式计算出扇区,进行擦除这一个扇区。扇区擦除是根据扇区号来进行计算的,比如我传入的地址是0XFE21写入数据,打开计算器程序员模式(除法没有小数点)换算成10就是65057,处于4096就等于15,说明是在15扇区进行保存的 。

代码实现:




9.读数据代码实现

将数据读取出来测试写入的数据是否准确,读数据首先发送03H这个命令,在发送读取的地址,然后从机就会发送数据给我们,代码方面首先拉低CS,发送03H命令告诉从机我要读取数据,然后发送24位的读取地址,然后根据我们传入的读取长度去读取出数据,0XFF是假数据(是为了产生CLK),将读取的数据不断保存到传入的参数buffer里(参数会不断的自增),就实现了连续的读,读完后再将CS端拉高,就结束读取数据的操作。


在主函数里调用写数据函数,填入写进数据的内容,写入数据的地址,写入数据的长度,写入"你好"这个数据长度是5个字节,一个汉字是2个字节,结尾还有一个\0结束符,写入后在读取出来,读取函数中第一个参数是要保存的地址,读取的地址,读取的长度,最后在通过串口输出打印出来。

10. 通信流程总结(以主设备向从设备读写一个字节为例)
-
初始化: 配置 MCU 的 SPI 控制器(时钟频率、模式、数据位宽、帧格式),并将相关引脚初始化为 SPI 功能。
-
选择从设备: 主设备拉低目标从设备的 CS 引脚。
-
开始传输: 主设备启动时钟 SCLK。
-
发送与接收(同时进行):
-
主设备将待发送数据字节的每一位,在
数据变化时刻放到 MOSI 线上。 -
从设备在
数据采样时刻读取 MOSI 线上的数据位。 -
与此同时 ,从设备将其要返回的数据位在
数据变化时刻放到 MISO 线上。 -
主设备在
数据采样时刻读取 MISO 线上的数据位。
-
-
重复步骤4,直到一个完整的数据帧(如8位)传输完毕。
-
结束传输: 主设备拉高 CS 引脚。
关键理解: 由于全双工特性,主设备每发送一个字节,必然会同时收到一个字节 。这个收到的字节可能是从设备的有效数据,也可能是"哑元数据"(Dummy Byte,通常是 0xFF 或 0x00)。具体的命令-响应序列由从设备的通信协议定义。
第三部分:读写验证流程(以读取 SPI Flash 的 ID 为例)
这是一个非常经典且有效的验证 SPI 主从通信是否正常的流程。



硬件准备:
-
连接: 将 GD32F470作为主设备,与一个 SPI Flash 芯片(如 W25Q64)连接。
-
GD32F470.SCLK -> FLASH.CLK
-
GD32F470.MOSI -> FLASH.DI (或 SI)
-
GD32F470.MISO -> FLASH.DO (或 SO)
-
GD32F470.GPIOx (配置为CS) -> FLASH.CS
-
共地。

-
- 供电: 确保双方供电正常(符合 Flash 工作电压)。
软件流程:
步骤 1:初始化
-
启用 GD32F470的 SPI 外设时钟。
-
配置控制 SPI 功能的 GPIO 引脚为复用功能()。
-
配置 SPI 控制器:
-
工作模式: 主模式。
-
时钟频率: 设置为一个较低的频率(如 1 MHz)以便于初始调试。
-
数据位宽: 8 位。
-
时钟模式: 根据 Flash 手册(通常是 Mode 0 或 Mode 3)配置 CPOL 和 CPHA。
-
帧格式: MSB 先行。
-
-
配置一个普通 GPIO 作为 CS 控制引脚,并初始化为高电平(无效状态)。





步骤 2:实现 SPI 基础读写函数
c
// 伪代码,基于寄存器或 HAL 库
uint8_t SPI_TransmitReceiveByte(uint8_t tx_data) {
// 1. 等待发送缓冲区为空(或前一次传输完成)
while(!(SPI_REG->STATUS & TX_EMPTY_FLAG));
// 2. 将要发送的数据写入数据寄存器
SPI_REG->DATA = tx_data;
// 3. 等待接收缓冲区非空(表示数据已收到)
while(!(SPI_REG->STATUS & RX_READY_FLAG));
// 4. 读取数据寄存器,得到接收到的数据
return (uint8_t)(SPI_REG->DATA);
}



步骤 3:实现 Flash 读 ID 命令
SPI Flash 的读制造商和设备 ID 命令通常是 0x9F 或 0x90。以 0x9F (Read JEDEC ID) 为例:
-
发送命令字节
0x9F。 -
连续接收 3 个字节(通常是制造商 ID、内存类型、容量 ID)。
读取设备ID代码说明


读取ID验证

写入数据流程




写数据代码实现:

读取数据流程

读取数据代码实现

读写验证


void Flash_ReadID(uint8_t *id_buf) { // id_buf 是长度为3的数组
// 1. 拉低 CS,选中 Flash
CS_GPIO_LOW();
// 2. 发送命令 0x9F
SPI_TransmitReceiveByte(0x9F);
// 3. 连续接收 3 个字节(发送哑元数据 0xFF 以产生时钟)
id_buf[0] = SPI_TransmitReceiveByte(0xFF); // 接收制造商 ID,如 0xEF (Winbond)
id_buf[1] = SPI_TransmitReceiveByte(0xFF); // 接收内存类型,如 0x40
id_buf[2] = SPI_TransmitReceiveByte(0xFF); // 接收容量 ID,如 0x17 (64Mbit)
// 4. 拉高 CS,结束传输
CS_GPIO_HIGH();
}
步骤 4:判断忙

步骤 5:验证与调试
-
软件验证: 在主程序中调用
Flash_ReadID,并通过串口打印读出的三个字节。对比 Flash 数据手册中的 ID,如果一致,则 SPI 通信成功! -
硬件调试(推荐):
-
逻辑分析仪: 这是最佳工具。连接 SCLK, MOSI, MISO, CS 四根线。触发 CS 下降沿,观察发出的命令
0x9F和返回的三个 ID 字节。检查波形是否符合预期的时序模式(CPOL/CPHA)、数据顺序。 -
示波器: 可以观察信号质量和基本波形。
-
补充说明:多字节读写
对于读写 Flash 数据,流程类似,但更复杂:
-
写使能: 写入前需先发送
0x06(Write Enable) 命令。 -
带地址的读: 命令 (如
0x03) + 3字节地址 + 持续读数据。CS 在读取期间保持低电平。 -
页编程: 命令 (如
0x02) + 3字节地址 + 要写入的数据。需要等待"写忙"状态结束(通过读状态寄存器0x05)。
总结
理解 SPI 的关键在于时钟模式 和全双工特性。对于 GD32F470 这类高性能 MCU,您需要:
-
查手册,正确配置引脚复用。
-
查外设手册,确定 SPI 模式和数据格式。
-
实现底层字节收发函数。
-
遵循从设备的具体命令协议进行封装。
-
利用工具(逻辑分析仪) 进行波形级验证。