串行外设接口(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管脚需配置为浮空输入模式,否则有可能数据接收异常。
本章内容每日持续更新,如有兴趣,请关注收藏
更多GD32 MCU相关咨询:https://www.gd32bbs.com/