简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线。
SPI总共需要4根线来实现通信,NSS:片选线,用于选择需要通信的从机;CLK:同步时钟线,用于提供同步时钟信号;MISO:主机读从机写线;MOSI:主机写从机读线。
GD32F103系列的SPI最高速度为18MHz。
片选线
SPI的片选逻辑要比I2C的简单得多,通常一个SPI外设会有多条片选线,如下图。
所以我们想要与哪个从机进行通信,那么只需要拉低对应从机的片选线即可,无需像I2C那样要通过传送从机地址来片选对应的从机。
不过GD32的SPI外设好像只有一条片选线,目前我还没有见过有多条片选线的SPI外设,可能高级一点的型号会有吧
时序
因为SPI为同步通信协议,因此通信要配合同步时钟信号,时序如下图。
值得注意的是时钟线有4种不同的时序,通过CKPH和CKPL两个配置位进行配置,CKPH配置的是第一或第二个时钟边沿为有效采样边沿,CKPL配置的是空闲状态时时钟线的电平。
运行模式
SPI外设有好几种运行模式,分别有——全双工主机模式、单向线连接主机发送模式、单向线连接主机接收模式、双向线连接主机发送模式、双向线连接主机接收模式、全双工从机模式、单向线连接从机发送模式、单向线连接从机接收模式、双向线连接从机发送模式、双向线连接从机接收模式。
其实简单点说就是,虽然SPI是一个全双工的通信协议,但通过配置可以使它工作在全双工、半双工和单工的状态,配置十分灵活。
全双工的接线示意图
半双工接线示意图
单工接线示意图
基本发送和技术流程
主机发送
简要流程:
- 配置SPI外设的时序,使能SPI外设
- 拉低对应从机NSS线电平
- 软件写一个数据到发送缓冲区
- 数据帧从数据缓冲区加载到移位寄存器中,开始发送加载的数据
- 数据帧的第一位发送之后,TBE位置1
- 如果有更多数据则重复第3-5步
- 等待TRANS置0,关闭时钟信号输出,释放片选线,数据传输结束
说明:
TBE全称Transmit Buffer Empty,“发送缓冲区空”标志位。
TRANS,“通信进行中”标志位。
主机接收
简要流程:
- 配置SPI外设的时序,使能SPI外设
- 拉低对应从机NSS线电平
- 输出SCK时钟信号
- 接收到的数据将从移位寄存器存入到接收缓冲区,且RBNE位置1
- 软件读取接收缓冲区,RBNE位置0
- 如果有更多数据则重复第4-5步
- 等待TRANS置0,关闭时钟信号输出,释放片选线,数据传输结束
说明:
RBNE全称Read Buffer Not Empty,"接收缓冲区非空"标志位。
从机发送
简要流程:
- 配置SPI外设的时序,使能SPI外设
- 软件写第一个数据到发送缓冲区
- 等待NSS线电平为低,SCK线开始翻转
- 数据帧从数据缓冲区加载到移位寄存器中,开始发送加载的数据
- 数据帧的第一位发送之后,TBE位置1
- 如果有更多数据,在每一次TBE置1时都可将数据写入发送缓冲区
- 等待TRANS置0,数据传输结束
从机接收
简要流程:
- 配置SPI外设的时序,使能SPI外设
- 等待NSS线电平为低,SCK线开始翻转
- 接收到的数据将从移位寄存器存入到接收缓冲区,且RBNE位置1
- 软件读取接收缓冲区,RBNE位置0
- 如果有更多数据则重复第4-5步
- 等待TRANS置0,数据传输结束
例程
STM32F103C8T6有两个SPI外设,对应管脚映射如下:
外设 | NSS | SCK | MISO | MOSI |
---|---|---|---|---|
SPI0 | PA4 | PA5 | PA6 | PA7 |
SPI1 | PB12 | PB13 | PB14 | PB15 |
主机和从机全双工通信
现象:主机从机每2秒同时发送一组数据,同时接收一组数据
spi.c文件
#include "spi.h"
void SPI_MasterInit(void)
{
/* 初始化GPIO */
rcu_periph_clock_enable(RCU_GPIOA);
/* SPI0 GPIO: NSS/PA4, SCK/PA5, MISO/PA6, MOSI/PA7 */
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
/* 初始化SPI */
rcu_periph_clock_enable(RCU_SPI0);
spi_parameter_struct spi_init_struct = {0};
spi_struct_para_init(&spi_init_struct);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全双工模式
spi_init_struct.device_mode = SPI_MASTER; // 主机模式
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; // 空闲电平为低,第一个边沿采样
spi_init_struct.nss = SPI_NSS_HARD; // 硬件片选
spi_init_struct.prescale = SPI_PSC_8; // 时钟8分频,108MHz / 8 = 13.5MHz
spi_init_struct.endian = SPI_ENDIAN_MSB; // 大端模式
spi_init(SPI0, &spi_init_struct);
spi_nss_output_enable(SPI0);
spi_enable(SPI0);
}
void SPI_SlaveInit(void)
{
/* 初始化GPIO */
rcu_periph_clock_enable(RCU_GPIOB);
/* SPI0 GPIO: NSS/PB12, SCK/PB13, MISO/PB14, MOSI/PB15 */
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_14);
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_15);
/* 初始化SPI */
rcu_periph_clock_enable(RCU_SPI1);
spi_parameter_struct spi_init_struct = {0};
spi_struct_para_init(&spi_init_struct);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全双工模式
spi_init_struct.device_mode = SPI_SLAVE; // 从机模式
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; // 空闲电平为低,第一个边沿采样
spi_init_struct.nss = SPI_NSS_HARD; // 硬件片选
spi_init_struct.prescale = SPI_PSC_8; // 时钟8分频,108MHz / 8 = 13.5MHz
spi_init_struct.endian = SPI_ENDIAN_MSB; // 大端模式
spi_init(SPI1, &spi_init_struct);
spi_enable(SPI1);
}
void SPI_MasterSlaveFullduplex(void)
{
uint8_t master_tx_buf[16] = {0};
uint8_t master_rx_buf[16] = {0};
uint8_t slave_tx_buf[16] = {0};
uint8_t slave_rx_buf[16] = {0};
memset(master_tx_buf, 0x00, sizeof(master_tx_buf));
memset(master_rx_buf, 0x00, sizeof(master_rx_buf));
memset(slave_tx_buf, 0x00, sizeof(slave_tx_buf));
memset(slave_rx_buf, 0x00, sizeof(slave_rx_buf));
for(uint8_t i = 0; i < 16; ++i)
{
master_tx_buf[i] = 0x80 + i;
slave_tx_buf[i] = 0x40 + i;
}
printf("Master send: ");
for(uint8_t i = 0; i < 16; ++i)
{
printf("%x ", master_tx_buf[i]);
}
printf("\n");
printf("Slave send: ");
for(uint8_t i = 0; i < 16; ++i)
{
printf("%x ", slave_tx_buf[i]);
}
printf("\n");
for(uint8_t i = 0; i < 16; ++i)
{
while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI2, slave_tx_buf[i]);
while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI0, master_tx_buf[i]);
while(RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_RBNE));
slave_rx_buf[i] = spi_i2s_data_receive(SPI2);
while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE));
master_rx_buf[i] = spi_i2s_data_receive(SPI0);
}
printf("Master receive: ");
for(uint8_t i = 0; i < 16; ++i)
{
printf("%x ", master_rx_buf[i]);
}
printf("\n");
printf("Slave receive: ");
for(uint8_t i = 0; i < 16; ++i)
{
printf("%x ", slave_rx_buf[i]);
}
printf("\n");
}
main.c文件文章来源:https://www.toymoban.com/news/detail-403450.html
#include "gd32f10x.h"
#include "main.h"
#include "systick.h"
#include "usart.h"
#include "spi.h"
#include <stdio.h>
#include <string.h>
int main(void)
{
systick_config();
USART_Config();
SPI_MasterInit();
SPI_SlaveInit();
while(1)
{
SPI_MasterSlaveFullduplex();
delay_ms(2000);
}
}
文章来源地址https://www.toymoban.com/news/detail-403450.html
到了这里,关于【GD32】从0开始学GD32单片机(9)—— SPI外设详解+主机从机发送和接收例程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!