zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

【GD32】从0开始学GD32单片机(9)—— SPI外设详解+主机从机发送和接收例程

单片机 详解 开始 发送 主机 接收 例程 SPI
2023-09-11 14:21:44 时间

简介

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是一个全双工的通信协议,但通过配置可以使它工作在全双工、半双工和单工的状态,配置十分灵活。
全双工的接线示意图

在这里插入图片描述

半双工接线示意图
在这里插入图片描述

单工接线示意图
在这里插入图片描述

基本发送和技术流程

主机发送

简要流程:

  1. 配置SPI外设的时序,使能SPI外设
  2. 拉低对应从机NSS线电平
  3. 软件写一个数据到发送缓冲区
  4. 数据帧从数据缓冲区加载到移位寄存器中,开始发送加载的数据
  5. 数据帧的第一位发送之后,TBE位置1
  6. 如果有更多数据则重复第3-5步
  7. 等待TRANS置0,关闭时钟信号输出,释放片选线,数据传输结束

说明:
TBE全称Transmit Buffer Empty,“发送缓冲区空”标志位。
TRANS,“通信进行中”标志位。

主机接收

简要流程:

  1. 配置SPI外设的时序,使能SPI外设
  2. 拉低对应从机NSS线电平
  3. 输出SCK时钟信号
  4. 接收到的数据将从移位寄存器存入到接收缓冲区,且RBNE位置1
  5. 软件读取接收缓冲区,RBNE位置0
  6. 如果有更多数据则重复第4-5步
  7. 等待TRANS置0,关闭时钟信号输出,释放片选线,数据传输结束

说明:
RBNE全称Read Buffer Not Empty,"接收缓冲区非空"标志位。

从机发送

简要流程:

  1. 配置SPI外设的时序,使能SPI外设
  2. 软件写第一个数据到发送缓冲区
  3. 等待NSS线电平为低,SCK线开始翻转
  4. 数据帧从数据缓冲区加载到移位寄存器中,开始发送加载的数据
  5. 数据帧的第一位发送之后,TBE位置1
  6. 如果有更多数据,在每一次TBE置1时都可将数据写入发送缓冲区
  7. 等待TRANS置0,数据传输结束

从机接收

简要流程:

  1. 配置SPI外设的时序,使能SPI外设
  2. 等待NSS线电平为低,SCK线开始翻转
  3. 接收到的数据将从移位寄存器存入到接收缓冲区,且RBNE位置1
  4. 软件读取接收缓冲区,RBNE位置0
  5. 如果有更多数据则重复第4-5步
  6. 等待TRANS置0,数据传输结束

例程

STM32F103C8T6有两个SPI外设,对应管脚映射如下:

外设NSSSCKMISOMOSI
SPI0PA4PA5PA6PA7
SPI1PB12PB13PB14PB15

主机和从机全双工通信

现象:主机从机每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文件

#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);
    }
}

在这里插入图片描述