目录

  • 简介
  • 片选线
  • 时序
  • 运行模式
  • 基本发送和技术流程
    • 主机发送
    • 主机接收
    • 从机发送
    • 从机接收
  • 例程
    • 主机和从机全双工通信

简介

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外设,对应管脚映射如下:

外设 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.5MHzspi_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.5MHzspi_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);}
}

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

  1. 【GD32】从0开始学GD32单片机栏目目录 | 兆易创新32位微处理器

    目录 前言 为什么写这个教程 GD32与STM32的区别 教程 入门教程(基于GD32F103C8T6单片机) 进阶教程 高级教程 前言 为什么写这个教程 作为一名物联网专业的在读大学生,与单片机打交 ...

  2. 入木三分学网络第一篇--VRRP协议详解-----(1)

    原帖:http://blog.chinaunix.net/uid-11654074-id-2857384.html 目录 入木三分学网络第一篇--VRRP协议详解 1. VRRP产生背景及应用环境 1 ...

  3. 单片机_PWM输出原理详解

    单片机_PWM输出原理详解 理论篇   博主自己的经历告诉我,PWM波的理解和应用确实还是挺重要的,这里专门花一期详细介绍一下 什么是PWM?   PWM,英文名Pulse Width Modulat ...

  4. MATLAB与51单片机进行串口通信详解

    目录 一.51单片机与电脑进行串口通信 二.MATLAB串口通信函数 三.串口属性 四.示例Demo 4.1 MATLAB接收单片机发来的数据 4.2 MATLAB向单片机发送数据控制LED 五.总结 ...

  5. TI Cotex M3/4单片机关于寄存器操作详解

    TI Cotex M3/4单片机关于寄存器操作详解 前备知识 寄存器与偏移量 位带操作.位带区 TI单片机寄存器操作详解 快速在TI的手册里面确认某外设某寄存器的确切地址 寄存器操作介绍 本文参考 前 ...

  6. 用Advanced Installer制作DotNetBar for Windows Forms 12.0.0.1_冰河之刃重打包版详解

    关于 DotNetBar for Windows Forms 12.0.0.1_冰河之刃重打包版 --------------------11.8.0.8_冰河之刃重打包版-------------- ...

  7. vue设置html自动跳转路由器,vue2.0项目实现路由跳转的方法详解

    一.安装 1.安装路由vue-router: npm install vue-router vue项目的依赖文件node_modules存在vue-router依赖,说明安装成功 2.vue项目引入v ...

  8. 华娱水浒无双1java,魔兽争霸3真水浒无双1.0正式版开局单刷完美通关图文详解

    魔兽争霸3真水浒无双1.0正式版开局单刷怎么打?有什么技巧?很多玩家可能还不太清楚,下面小编为大家带来了魔兽争霸3真水浒无双1.0正式版开局单刷完美通关图文详解,感兴趣的玩家一起来看看吧! 魔兽争霸3 ...

  9. 巧学宾语从句――宾语从句语法详解 [宾语从句歌诀]

    巧学宾语从句――宾语从句语法详解 [宾语从句歌诀] 2011年10月01日 重要提醒:系统检测到您的帐号可能存在被盗风险,请尽快查看风险提示,并立即修改密码. | 关闭 网易博客安全提醒:系统检测到您 ...

最新文章

  1. Transformer的PyTorch实现
  2. CF1B Spreadsheets
  3. 零基础学Python(第八章 for循环·超重点,本章会有几个简单的单层循环练习,后续会有针对算法的单独章节)
  4. 简单说说通讯设备的热设计
  5. hadoop应用开发技术..._Hadoop
  6. JS中的关于类型转换的性能优化
  7. 小程序 php wecahtpay,【PHP】laravel 实现微信小程序微信支付功能
  8. python映射实体类_将Python(django)实体类转换为Java的好方法 - java
  9. vue-amap - 基于Vue2.0和高德地图的地图组件
  10. 三星手机怎么把html,三星手机怎么连接电脑 只要四步轻松搞定【图文】
  11. ehlib的DBGridEh控件中使用过滤功能的方法
  12. Demo:第四章:Gateway网关
  13. 基于51单片机ds18b20智能温控风扇Proteus仿真
  14. 网上体育商城的设计与实现
  15. Android| failed to connect to /10.0.2.2 (port 80) after 10000ms
  16. LeetCode Task04 数组数组二分查找 0704二分查找 0069Sqrt(x)
  17. Hutool(Excel工具使用)
  18. 编写注册表文件修改注册表项
  19. 使用Fastdfs上传文件时出现中文乱码。工具类中的this.getClass().getResource(“/“).getPath()获取配置文件的路径时中文乱码。
  20. 南邮校园网CSDN 等部分网站图片无法加载的问题

热门文章

  1. 100多个flash常见问题大收集
  2. C++ stack pop()返回值 error: cannot initialize a variable of type ‘int‘ with an rvalue of type ‘void‘
  3. Matlab 入门 - data函数
  4. 软件项目管理进度计划-Gantt图和工程网络图,工程网络是系统分析和系统设计的强有力的工 具
  5. 如何克服卡尔曼滤波发散问题
  6. 阅读nutch.Analysis.jj
  7. Ubuntu_XMMS播放器使用
  8. 深入Unreal蓝图开发:自定义蓝图节点(上)
  9. web网页设计期末课程大作业:动物主题设计与实现——鲸鱼html+css
  10. 美度舵手系列多功能计时腕表伴你行走世界