SPI总线协议详解及STM32代码实现
- SPI总线协议详解
- STM32代码实现
本篇博客分为两部分。第一部分讲解SPI总线协议的实现,主要包括硬件连接、工作模式、时序等。第二部分讲解通过STM32以SPI的方式实现对Flash芯片W25Q128的读写,这其中采用了两种方式:第一种方式是采用STM32的GPIO模拟SPI时序的方式进行读写Flash芯片;另一种方式采用STM32片内自带的SPI外设进行读写Flash芯片。切入正题:
一、SPI总线协议详解
1、SPI协议硬件连接
SPI,是英语Serial Peripheral interface的缩写。SPI主要用于MCU和一些外设进行通信的场合,例如:EEPROM、Flash、AD转换器等一些应用中。它是一种高速、全双工、同步的通信总线,这里全双工指的是可以在同一时刻设备进行接收和发送同时进行,它有别于CAN总线或者RS585总线,因为这些总线在同一时刻只能进行数据的单向传输。换句话说,对于一个设备在同一时刻只能接收或者发送数据。同步通信指的是一种比特同步通信技术,要求发收双方具有同频同相的同步时钟信号,SPI是通过CLK和相位实现这一点的。
- 硬件连线
在SPI总线中,一共规定了4条线,分别含义如下:
SCLK —串行时钟同步输出,同步数据传输,使用主机输出;
MOSI —主机输出从机输入,主机通过该线发送数据,从机通过该线接收数据;
MISO —主机输入从机输出,主机通过该线接收数据,从机通过该线发送数据;
SS —片选,主机输出,用来选中具体的从机。
注:(1)SPI会有主从机(或者对于MCU来说会有主模式,从模式)之分,它的区分依据是SCLK同步时钟和SS片选是由谁输出,输出方就会被定义为工作在主机(主模式)状态下。(2)如果在一个系统中只含有一个采用SPI通信的外设,那么我们可以将外设的SS引脚直接连接板子的GND引脚,这样使得外设始终被选中,从而节省了连接线。
具体的SPI硬件接线图形式如下所示:
(1)4线SPI接线
SPI主从设备之间接线如下图所示:
(2)3线SPI接线
将从设备的SS引脚接地,实现3线SPI这样就可以节省芯片资源,减少布线。
(3)一主多从SPI接线(1)
通过将每个从设备的SS引脚分别接到主模式设备,可以达到一个主设备控制多个从设备的目的。
(4)一主多从SPI接线(2)
此方法中加入译码器,可以有效减少片选信号的数量,节省主模式设备的芯片引脚。
2、SPI协议时序
SPI协议时序也被称为工作模式。 SPI一共有四种工作模式,通常情况下,从机的工作模式是固定的,主机需要根据从机的工作模式进行调整自身工作模式,来完成相互之间的通信。SPI的四种工作模式是通过时钟极性(CPOL位)和时钟相位(CPHA位)进行确定的,对于具体的工作模式可以查看下表:
注: CPOL时钟极性选择,=0表示总线空闲为低电平(SCLK时钟空闲状态为低电平,此时MOSI和MISO上的数据可以变化);=1表示总线空闲位高电平(SCLK时钟空闲状态为低电平,此时MOSI和MISO上的数据可以变化)。
注: CPHA时钟相位选择,=0会在SCLK的第一个跳变沿采样,第二个跳变沿输出;=1会在SCLK的第二个跳变沿采样,第一个跳变沿输出。以上说法不好理解,可以这样描述,=0时,在奇数跳变沿采样,偶数跳变沿输出;=1时,则正好相反。
SPI模式 | CPOL | CPHA | 空闲状态时钟极性 | 采样跳变沿 |
---|---|---|---|---|
0 | 0 | 0 | 低电平 | 奇数沿采样,偶数沿输出 |
1 | 0 | 0- | 低电平 | 奇数沿输出,偶数沿采样 |
2 | 1 | 1 | 高电平 | 偶数沿采样,奇数沿输出 |
3 | 1 | 1 | 高电平 | 奇数沿输出,偶数沿采样 |
下面具体看一下各模式的时序图:
(1)模式0:
从图中可以看出,空闲电平是低电平,采样边沿为奇数跳变沿,输出在偶数跳变沿。
(2)模式1:
图中可以看出,空闲电平是低电平;奇数跳变沿输出,偶数跳变沿采样。
(3)模式2:
图中看出,空闲电平是高电平;在奇数跳变沿进行采样,偶数跳变沿进行输出。
(4)模式3:
从图中可以看出,空闲电平是高电平,;在奇数跳变沿进行输出,在偶数跳变沿进行采样。
二、STM32代码实现
本小节介绍使用STM32F1系列MCU对Flash芯片W25Q128进行读写。分别使用了GPIO模拟SPI通信协议和STM32自带的SPI片内外设进行读写。对于W25Q128芯片手册,可以自行到官网下载。
1、GPIO模拟方式
w25qxx_flash_io.h
#include "stm32f10x.h"
#define SPI_CS_0 GPIO_ResetBits(GPIOB, GPIO_Pin_12)
#define SPI_CS_1 GPIO_SetBits(GPIOB, GPIO_Pin_12)
#define SPI_SCK_0 GPIO_ResetBits(GPIOB, GPIO_Pin_13)
#define SPI_SCK_1 GPIO_SetBits(GPIOB, GPIO_Pin_13)
#define SPI_MOSI_0 GPIO_ResetBits(GPIOB, GPIO_Pin_15)
#define SPI_MOSI_1 GPIO_SetBits(GPIOB, GPIO_Pin_15)
#define W25QX_ReadStatus 0x05 //读状态寄存器
#define W25QX_WriteStatus 0x01 //写状态寄存器
#define W25QX_ReadDATA8 0x03 //普读_数据
#define W25QX_FastRead 0x0B //快读_数据
#define W25QX_DualOutput 0x3B //快读_双输出
#define W25QX_Writepage 0x02 //写_数据_0~255个字节
#define W25QX_S_Erase