串行外设接口(Serial Peripheral Interface,缩写为 SPI) 提供了基于SPI 协议的数据发送和接收功能, 可以工作于主机或从机模式。 SPI 接口支持具有硬件 CRC 计算和校验的全双工和单工模式。
8.1.SPI 基础知识
SPI 物理层
SPI接口采用主从模式(Master Slave)架构;支持一主一从模式和一主多从模式,但不支持多主模式。它是一种同步高速全双工的通信总线,总体结构如下图常见的SPI通讯系统所示。

一个主机连接四个从机,其中一个SPI总线一般有四个信号分为:
SCLK:时钟信号,由主机产生并控制。
MOSI:主机数据输出,从机数据输入。
MISO:主机数据输入,从机数据输出。
SS/NSS:从机片选使能信号,由主机控制。在一主对多从的模式下,每一个从机都需要独占一个SS,也就是说有多少个从机就有多少个片选信号。
SPI 协议层
SPI的协议定义了通信的起始信号、停止信号、数据有效性、时钟同步等环节。下面我们分析一下两个设备通过SPI总线通信的过程,SPI通信时序图如下图通讯时序所示:
SPI 通讯时序

这个是一个主机的通信时序,信号线 NSS、SCK、MOSI 都是由主机控制,MISO 是由从机进行控制。其中 MOSI 和 MISO 上的数据仅在 NSS 为低时才有效,并且每个SCK 时钟周期只交换一位数据。
起始信号和停止信号:如_SPI通讯时序图_中①和⑥分别表示通信的起始和结束,这些信号的产生是通过主机将片选信号(NSS)置低和置高实现的。NSS 除了是开始和结束信号的产生者,它也是主机和从机通信的选择者,当一个主机对多个从机通信时,主机通过置低从机的 NSS 信号线来选择与哪个从机进行通信。
时钟同步:SPI 总线是一个同步全双工的通信总线,所以 SPI 的数据传输是需要 SCK 时钟信号严格同步的,每一个 SCK 周期只传输一位数据,这一个周期里要完成数据的准备和采样,且数据的输入和输出是同时进行的。MSB 先行或 LSB先行协议中是没有硬性规定,只需通信双方保持统一即可。SPI 每次数据传输可以是 8 位或 16 位为单位,每次传输的单位数不受限制。
数据有效性:SPI 在 SCK 时钟的同步下进行数据的准备和采样,过程如图 1-2 的②③④⑤所示。在 NSS 为低的情况时,在 SCK 的上升沿时 MISO 和 MOSI 进行数据准备,SCK 的下降沿时读取 MISO 和 MOSI 上的数据。在 NSS 为高时,MISO 和MOSI 上的数据无效。
SPI 工作模式:如_SPI通讯时序图_只是 SPI 的一种工作模式,SPI 一共有四种工作模式。他们的区别是总线空闲时 SCK 的电平状态和数据采样时刻。这四种模式的配置是通过配置“时钟极 性 CKPL”和“时钟相位 CKPH”的电平来实现的。
CKPL=0 时,SCK 引脚在空闲状态保持低电平;
CKPL=1 时,SCK 引脚在空闲状态保持高电平;
CKPH=0 时,SCK 时钟的第一个边沿进行采样;
CKPH =1 时,SCK 时钟的第二个边沿进行采样。
四种模式如下图SPI通讯模式所示:

SPI 数据传输流程
前面我们了解了SPI协议的物理连接方式和SPI的具体协议和SPI的四种工作模式,下面我们从整体上分析一下SPI通信的流程。通过前面的内容可知,在一个SCK周期内,SPI会完成如下操作:
主机通过MOSI线发送1位数据,从机通过该线读取这1位数据。
从机通过MISO线发送1位数据,主机通过该线读取这1位数据。
这是通过移位寄存器来实现的。如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。通信流程如图所示:

8.2.GD32 SPI 外设原理简介
因篇幅有限,本文无法详细介绍GD32所有系列SPI外设接口,下面以GD32F30x为列,着重介绍下GD32F30x的SPI外设简介和结构框图,后介绍下各个系列的差异。
GD32 SPI 主要特性
◼ 具有全双工和单工模式的主从操作;
◼ 16位宽度,独立的发送和接收缓冲区;
◼ 8位或16位数据帧格式;
◼ 低位在前或高位在前的数据位顺序;
◼ 软件和硬件NSS管理;
◼ 硬件CRC计算、发送和校验;
◼ 发送和接收支持DMA模式;
◼ 支持SPI TI模式;
◼ 支持SPI NSS脉冲模式;
◼ 支持SPI四线功能的主机模式(仅在SPI0中)。
GD32的SPI外设还支持I2S功能,I2S功能是一种音频串行通讯协议,如果需要学习请参考各个系列的User_Manual,本文不做过多的介绍。
SPI 结构框图介绍
SPI 通讯模式

通讯引脚:如_SPI通讯模式图_的①所示,GD32硬件接口SCK、NSS、MOSI、MISO为标准的SPI协议的四条信号线;IO2、IO3为GD32的SPI四线模式使用到的引脚,分别为:发送或接收数据2线和3线(在GD32F30x中仅SPI0支持四线主机模式)。各个系列的SPI个数不同,SPI接口和芯片I/O口的对应关系,可查阅各个系列的Datasheet。
时钟生成器:如_SPI通讯模式图_的②所示,SCK线的时钟信号,是由波特率发生器根据“控制寄存器0(SPI_CTL0)”中的PSC[2:0]位控制的。具体分频选择如下
000:PCLK/2 100:PCLK/32
001:PCLK/4 101:PCLK/64
010:PCLK/8 110:PCLK/128
011:PCLK/16 111:PCLK/256
当使用SPI0时,PCLK=PCLK2,当使用SPI1和SPI2时,PCLK=PCLK1。
数据通讯单元:如_SPI通讯模式图_的③所示,SPI的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源及目标接收、发送缓冲区以及MISO、MOSI线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。
通过写SPI的“数据寄存器(SPI_DATA)”把数据填充到发送缓冲区中,通讯读“数据寄存器(SPI_DATA)”,可以获取接收缓冲区中的内容。其中数据帧长度可以通过“控制寄存器0(SPI_CTL0)”的“FF16位”配置成8位及16位模式;配置“LF位”可选择MSB先行还是LSB先行。
下面以SPI作为主机MSB先行收发数据来分析一下通讯流程:
控制NSS信号线进入低电平,选中从器件发出通信开始信号;
检查“发送缓冲区”是否为空(SPI_STAT的TBE是否为1),如果为空,将所需要发送的数据写入“发送缓冲区”;
“发送缓冲区”里的数据一次性写入“移位寄存器”,一旦“发送缓冲区”里的数据写入“移位寄存器”SPI通信正式开始;
“移位寄存器”通过MOSI信号线从高位一位一位的发送到接收方,由于SPI的通信时全双工的,所以MOSI每发出一位MISO就接收一位存入移位寄存器;
直到一个数据单元发完(数据单元大小8位/16位可配置)。“移位寄存器”里接收回来的数据将一次性写入“接收缓冲区”,这时SPI_STAT的RBNE位将置1。也就是说“接收缓冲区”已有数 据。这时就可以读取数据了。
如果要发多组数据或者收多组数据,只需重复第2,3,4,5步。注意如果只收不发时,只需发送0xFF即可;
当所有数据都通信完成控制NSS信号进入高电平,通信正式结束。
各系列 SPI 功能差异
GD32系列MCU有关SPI外设各系列功能差异如下表所示

8.3.硬件连接说明
SPI 串行 Flash 硬件连接图

如图所示,为典型的SPI外设硬件连接图:GD25Q40是一种使用 SPI通讯协议的NOR FLASH存储器,它的CS/SCLK/SI/SO引脚分别连接到了GD32对应的SPI引脚NSS/SCK/MOSI/MISO上,其中GD32的NSS引脚是一个普通的GPIO,不是SPI的专用NSS引脚,所以程序中我们要使用软件控制的方式。若硬件设计中为SPI_NSS可以程序里可以配置为硬件控制方式。
读者可以根据典型硬件连接图和相应系列的Datasheet设计出自己的硬件连接方式。
8.4.软件配置说明
本小节讲解SPI_Example历程中SPI模块的配置说明,主要包括外设时钟配置、GPIO引脚配置、SPI外设配置、主函数介绍以及运行结果。本例程主要介绍GD32 MCU各系列SPI0模块的数据发送,有关SPI其他功能例程可参考各系列固件库例程。
外设时钟配置
外设时钟配置如代码清单例程时钟配置所示,在GD32全系列MCU中需打开GPIOA和SPI0的时钟,由于使用到PA3/PA5/PA7引脚以及SPI0模块,另外,在GD32F10X、GD32F20X、GD32F30X、GD32E10X中需要打开AF时钟。
void rcu_config(void) { #if defined GD32F10X_HD || GD32F30X_HD || GD32F20X_CL || GD32E10X || GD32F1X0 || GD32F4XX || GD32F3X0 || GD32E23X rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_SPI0); #if defined GD32F10X_HD || GD32F30X_HD || GD32F20X_CL || GD32E10X rcu_periph_clock_enable(RCU_AF); #elif defined GD32F1X0 || GD32F4XX || GD32F3X0 || GD32E23X #endif #endif }
GPIO 引脚配置
GPIO引脚配置如代码清单SPI例程GPIO引脚配置所示,GD32F10X、GD32F30X、GD32F20X、GD32E10X系列GPIO配置相同,PA5、PA7需配置为复用推挽输出、PA6需配置为浮空输入、PA3作为片选控制引脚配置为推挽输出模式;GD32F1X0、GD32F4XX、GD32F3X0、 GD32E23X系列GPIO配置基本相同,不同在于PA5/PA6/PA7引脚的AF复用功能配置不同,在GD32F1X0、GD32F3X0和GD32E23X上,需要配置为AF0模式,在GD32F4XX上需要配置为AF5模式。GPIO配置完成后,例程中将CS片选信号拉高。
void gpio_config(void) { #if defined GD32F10X_HD || GD32F30X_HD || GD32F20X_CL || GD32E10X /* SPI0 GPIO config:SCK/PA5, MISO/PA6, MOSI/PA7 */ gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_7); gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6); /* PA3 as NSS */ gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3); #elif defined GD32F1X0 || GD32F4XX || GD32F3X0 || GD32E23X #if defined GD32F1X0 || GD32F3X0 || GD32E23X /* SPI0 GPIO config: SCK/PA5, MISO/PA6, MOSI/PA7 */ gpio_af_set(GPIOA, GPIO_AF_0, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); #elif defined GD32F4XX gpio_af_set(GPIOA, GPIO_AF_5, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); #endif gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3); #endif SET_SPI0_NSS_HIGH }
SPI 外设配置
SPI外设配置如代码清单SPI例程SPI外设配置所示。GD32全系列MCU中SPI外设配置基本相同,在本例程中,SPI0作为主机全双工模式,GD32标准库提供了SPI初始化结构体及初始化函数来配置SPI外设,其初始化结构体说明如表 0-13 SPI初始化结构体说明列表所示,SPI初始化结构体填充完成后,调用spi_init函数进行SPI外设配置,配置完成后,调用spi_enable使能SPI外设。
void spi_config(void) { #if defined GD32F10X_HD|| GD32F30X_HD || GD32F1X0 || GD32F20X_CL || GD32F4XX || GD32F3X0 || GD32E10X || GD32E23X spi_parameter_struct spi_init_struct; /* SPI0 parameter config */ spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; spi_init_struct.device_mode = SPI_MASTER; spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; spi_init_struct.nss = SPI_NSS_SOFT; spi_init_struct.prescale = SPI_PSC_256; spi_init_struct.endian = SPI_ENDIAN_MSB; spi_init(SPI0, &spi_init_struct); spi_enable(SPI0); #endif }
SPI 初始化结构体说明列表

主函数说明
主函数如代码清单SPI例程主函数所示,该主函数主要分成四部分,RCU时钟配置、 GPIO 配置、SPI外设配置和while(1)主函数,前三部分已在前三小节介绍,在while(1)主循环中采用查询的方法循环发送SPI数据,单次循环数据填充完成后,查询RBNE和TRANS标志位判断数据发送 完成,然后拉高CS片选,完成单次循环发送。
int main(void) { /* peripheral clock enable */ rcu_config(); /* GPIO config */ gpio_config(); /* SPI config */ spi_config(); while(1) { SET_SPI0_NSS_LOW /* wait for transmit complete */ while(send_n < arraysize){ while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI0, spi0_send_array[send_n++]); } while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE)); while(RESET != spi_i2s_flag_get(SPI0, SPI_FLAG_TRANS)) {} SET_SPI0_NSS_HIGH send_n = 0; } }
运行结果
将SPI_Example例程按照对应的芯片工程编译完成后,下载到对应芯片中,采用示波器或者逻辑分析仪查看SPI_CS、SPI_CLK、SPI_MOSI引脚波形,如下图SPI发送逻辑分析仪抓取波形图所示,通过协议解析后,SPI数据发送正确。

8.5.SPI 使用注意事项
(1) 在切换SPI时钟前要关闭SPI,切换完成后再使能SPI。
(2) 在采用SPI发送数据时,发送buf空标志TBE置位,并不代表数据发送完成,仅代表数据从发送数据寄存器移到发送移位寄存器中,如果通过查询TBE标志来拉高CS片选,由于GD32系列MCU代码执行效率较高,当发送速率较低时可能会出现当TBE置位时,拉高CS片选,此时数据还未完成发送,造成从机接受数据出错。可以通过查询接收数据寄存器非空RBNE和TRANS标志位来判断数据发送完成,然后再拉高CS片选。
(3) SPI的MISO管脚需配置为浮空输入模式,否则有可能数据接收异常。
-
单片机
+关注
关注
6035文章
44553浏览量
634709 -
mcu
+关注
关注
146文章
17132浏览量
351013 -
SPI
+关注
关注
17文章
1706浏览量
91514
发布评论请先 登录
相关推荐
GD32 MCU 入门教程】GD32 MCU 常见外设介绍(12)FMC 模块介绍

《GD32 MCU原理及固件库开发指南》 + 初读感悟
兆易创新GD32 MCU选型手册,适用于GD32全系列MCU
【GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(1)使用Keil开发GD32

【GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(2)使用 IAR 开发 GD32

【GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(3)使用 Embedded Builder 开发 GD32

【GD32 MCU 入门教程】GD32 MCU 常见外设介绍(14)RTC 模块介绍

【GD32 MCU入门教程】GD32 MCU GPIO 结构与使用注意事项

评论