一、SPI简介
SPI协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
STM32有3个SPI:SPI1、SPI2和SPI3。其中SPI1挂载在APB2总线上,最高通信速率可达42Mbit/s ;SPI2和SPI3挂载在APB1总线上,最高通信速率可达21Mbit/s。三个SPI除了通讯速率上有差异外,其它功能上没有任何差异。
(1)物理层
SCK:时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如STM32的SPI时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
MOSI:主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
MISO:主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
SS:从设备选择信号线,常称为片选信号线,也称为NSS、CS。每个从设备都有独立的这一条SS信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。SPI协议使用SS信号线来寻址,当主机要选择从设备时,把该从设备的SS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以SS线置低电平为开始信号,以SS线被拉高作为结束信号。
(2)协议层
SPI协议定义了通讯的起始和停止信号,数据有效性、时钟同步等环节。
1》通讯基本过程
标号1处,NSS信号线由高变低,是SPI通讯的起始信号。NSS是每个从机各自独占的信号线,当从机检在自己的NSS线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。
在图中的标号6处,NSS信号由低变高,是SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。
触发:数据无效,进行高低电平的变化。
采样:数据有效,数据保持稳定。
2》CPOL/CPHA
时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、 NSS线为高电平时SCK的状态)。CPOL=0时, SCK在空闲状态时为低电平,CPOL=1时,则相反。
时钟相位CPHA是指数据的采样的时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。当CPHA=1时,数据线在SCK的“偶数边沿”采样。
SCK信号线在空闲状态为低电平时,CPOL=0;空闲状态为高电平时,CPOL=1。
CPHA=0,MOSI和MISO数据线的有效信号在SCK的奇数边沿保持不变,数据信号将在SCK奇数边沿时被采样,在非采样时刻,MOSI和MISO的有效信号才发生切换。
CPHA=1,MOSI和MISO数据线的有效信号在SCK的偶数边沿保持不变,数据信号将在SCK偶数边沿时被采样,在非采样时刻,MOSI和MISO的有效信号才发生切换。
3》通讯模式
由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式0”与“模式3”。
二、功能框图
三、通讯过程
控制NSS信号线,产生起始信号(图中没有画出);
把要发送的数据写入到“数据寄存器DR”中,该数据会被存储到发送缓冲区;
通讯开始,SCK时钟开始运行。MOSI把发送缓冲区中的数据一位一位地传输出去;MISO则把数据一位一位地存储进接收缓冲区中; 当发送完一帧数据的时候,“状态寄存器SR”中的“TXE标志位”会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置1,表示传输完一帧,接收缓冲区非空;
等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE标志位”为1时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。
假如使能了TXE或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入同一个中断服务函数,到SPI中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用DMA方式来收发“数据寄存器DR”中的数据。
只有在发送数据时,才会产生SCK时钟,所以SPI在需要接收数据时,必须先发送一个任意的数据,FLASH会忽略接收的数据,并将指定的数据发送给MCU。
四、原理图
五、FLASH简介
FLASH芯片型号为W25Q128,芯片大小为128M-bit。
共有65536页,每页大小为256bytes。
写入时须按页写入,擦除时必须以16页(4KB)、128页(32KB)、256页(64KB)或整个芯片为单位进行擦除,最常用的是按16页(4KB)为单位进行擦除。
在写入数据之前必须先执行擦除操作。
W25Q128时钟频率最高可达104MHz。
只能使用模式0或模式3。
BUSY标志位为1时,表示正在对芯片进行擦除或写入操作;BUSY标志位为0时,表示擦除或写入操作完成。
对FLASH进行操作的格式:指令 [地址] [数据] //"[]"表示可选
指令代码集:
六、初始化结构体
typedef struct
{
uint16_t SPI_Direction; //设置SPI的通讯方向。
SPI_Direction_1Line_Tx(单线只发送)
SPI_Direction_1Line_Rx(单线只接收)
SPI_Direction_2Lines_RxOnly(双线只接收)
SPI_Direction_2Lines_FullDuplex(双线全双工)
uint16_t SPI_Mode; //设置SPI工作在主机模式或从机模式。
SPI_Mode_Master(主机模式)
SPI_Mode_Slave(从机模式)
uint16_t SPI_DataSize; //设置SPI的数据帧长度。
SPI_DataSize_8b(8位)
SPI_DataSize_16b(16位)
uint16_t SPI_CPOL; //设置SPI的时钟极性
SPI_CPOL_High(高电平)
SPI_CPOL_Low(低电平)
uint16_t SPI_CPHA; //设置SPI的时钟相位
SPI_CPHA_1Edge(在SCK的奇数边沿采集数据)
SPI_CPHA_2Edge(在SCK的偶数边沿采集数据)
uint16_t SPI_NSS; //设置NSS引脚的使用模式。
SPI_NSS_Soft(软件模式)
SPI_NSS_Hard(硬件模式)
uint16_t SPI_BaudRatePrescaler; //设置波特率的分频因子,分频后的时钟即为SPI的SCK信号线的时钟频率。
SPI_BaudRatePrescaler_2(2分频)
SPI_BaudRatePrescaler_4(4分频)
SPI_BaudRatePrescaler_8(8分频)
SPI_BaudRatePrescaler_16(16分频)
SPI_BaudRatePrescaler_32(32分频)
SPI_BaudRatePrescaler_64(64分频)
SPI_BaudRatePrescaler_128(128分频)
SPI_BaudRatePrescaler_256
uint16_t SPI_FirstBit; //设置高位数据先行或低位数据先行。
SPI_FirstBit_MSB(高位数据先行)
SPI_FirstBit_LSB(低位数据先行)
uint16_t SPI_CRCPolynomial; //设置CRC校验的表达式,一般设置为0。
}SPI_InitTypeDef;
七、常用固件库函数
(1)初始化SPI
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
(2)使能SPI
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
(3)获取状态标志
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
(4)清除状态标志
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
(5)发送数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
(6)接收数据
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
八、程序
bsp_spi.h文件
#ifndef __BSP_SPI_H__
#define __BSP_SPI_H__
#include "stm32f4xx_conf.h"
/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
/*片选引脚*/
#define SPI_FLASH_CS_HIGH() GPIO_SetBits(GPIOG,GPIO_Pin_8)
#define SPI_FLASH_CS_LOW() GPIO_ResetBits(GPIOG,GPIO_Pin_8)
#define DUMMY 0xFF
extern void SPI_FLASH_Config(void);
extern uint8_t SPI_FLASH_ReadID(void);
extern void SPI_FLASH_Erase_Sector(uint32_t addr);
extern void SPI_FLASH_Read_Buff(uint32_t addr,uint8_t *buf,uint32_t size);
extern void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size);
#endif
bsp_spi.c文件
#include "./spi/bsp_spi.h"
#include "./usart/bsp_usart.h"
#include "stdio.h"
static __IO uint32_t SPITimeout = SPIT_LONG_TIMEOUT;
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
/*
SPI_CLK ---> PB3 ; SPI_MOSI ---> PB5 ; SPI_MISO ---> PB4 ; SPI_CS ---> PG8
*/
/*****************
功能:配置SPI
参数:无
返回值:无
******************/
void SPI_FLASH_Config(void)
{
GPIO_InitTypeDef initValue;
SPI_InitTypeDef spiInitValue;
/*1、打开时钟(GPIO和SPI的时钟)*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 ,ENABLE);
/*2、配置GPIO的复用功能(CLK、MOSI和MISO)和输出功能(CS)*/
initValue.GPIO_Mode = GPIO_Mode_AF;
initValue.GPIO_OType = GPIO_OType_PP;
initValue.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
initValue.GPIO_PuPd = GPIO_PuPd_NOPULL;
initValue.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&initValue);
initValue.GPIO_Mode = GPIO_Mode_OUT;
initValue.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOG,&initValue);
/*3、选择复用管脚*/
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SAI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SAI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SAI1);
/*4、初始化SPI*/
spiInitValue.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
spiInitValue.SPI_CPHA = SPI_CPHA_1Edge;
spiInitValue.SPI_CPOL = SPI_CPOL_Low;
spiInitValue.SPI_CRCPolynomial = 0;
spiInitValue.SPI_DataSize = SPI_DataSize_8b;
spiInitValue.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spiInitValue.SPI_FirstBit = SPI_FirstBit_MSB;
spiInitValue.SPI_Mode = SPI_Mode_Master;
spiInitValue.SPI_NSS = SPI_NSS_Soft;
SPI_Init(SPI1,&spiInitValue);
/*5、使能SPI*/
SPI_Cmd(SPI1,ENABLE);
}
/************************************
功能:通过SPI发送/接收一个字节的数据
参数:要发送的数据
返回值:接收到的数据
*************************************/
uint8_t SPI_FLASH_SendByte(uint8_t data)
{
uint8_t re_data;
/*1、等待TXE标志*/
SPITimeout = SPIT_FLAG_TIMEOUT;
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET)
{
if(SPITimeout-- == 0)
{
return SPI_TIMEOUT_UserCallback(1);
}
}
/*2、发送数据*/
SPI_I2S_SendData(SPI1,data);
/*3、等待RXNE标志*/
SPITimeout = SPIT_FLAG_TIMEOUT;
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET)
{
if(SPITimeout-- == 0)
{
return SPI_TIMEOUT_UserCallback(2);
}
}
/*4、接收数据*/
re_data = SPI_I2S_ReceiveData(SPI1);
return re_data;
}
/***************************
功能:读取ID(ID0 ~ ID7)
参数:无
返回值:读取到的ID值
****************************/
uint8_t SPI_FLASH_ReadID(void)
{
uint8_t id;
/*1、片选引脚拉低*/
SPI_FLASH_CS_LOW();
/*2、发送指令代码*/
SPI_FLASH_SendByte(0xAB);
SPI_FLASH_SendByte(DUMMY);
SPI_FLASH_SendByte(DUMMY);
SPI_FLASH_SendByte(DUMMY);
/*3、接收FLASH返回的ID值*/
id = SPI_FLASH_SendByte(DUMMY);
/*4、片选引脚拉高*/
SPI_FLASH_CS_HIGH();
return id;
}
/****************
功能:写使能
参数:无
返回值:无
*****************/
void SPI_FLASH_Write_Enable(void)
{
/*1、片选引脚拉低*/
SPI_FLASH_CS_LOW();
/*2、发送指令代码*/
SPI_FLASH_SendByte(0x06);
/*3、片选引脚拉高*/
SPI_FLASH_CS_HIGH();
}
/******************************************
功能:读取状态寄存器1的值,等待直到空闲状态
参数:无
返回值:无
*******************************************/
void SPI_FLASH_Wait_For_Standby(void)
{
uint8_t status;
/*1、片选引脚拉低*/
SPI_FLASH_CS_LOW();
/*2、发送指令代码*/
SPI_FLASH_SendByte(0x05);
/*3、读取状态寄存器1中的值,并判断是否为空闲状态*/
SPITimeout = SPIT_LONG_TIMEOUT;
while(1)
{
status = SPI_FLASH_SendByte(DUMMY);
if((status &0x01) == 0)
{
break;
}
//若SPITimeout为0,表示已检测SPITimeout次都仍为busy,跳出循环
if((SPITimeout--)==0)
{
SPI_TIMEOUT_UserCallback(3);
break;
}
}
/*4、片选引脚拉高*/
SPI_FLASH_CS_HIGH();
}
/********************
功能:擦除扇区
参数:扇区首地址
返回值:无
*********************/
void SPI_FLASH_Erase_Sector(uint32_t addr)
{
/*1、写使能*/
SPI_FLASH_Write_Enable();
/*2、片选引脚拉低*/
SPI_FLASH_CS_LOW();
/*3、发送指令代码*/
SPI_FLASH_SendByte(0x20);
SPI_FLASH_SendByte((addr >> 16) & 0xFF);
SPI_FLASH_SendByte((addr >> 8) & 0xFF);
SPI_FLASH_SendByte(addr & 0xFF);
/*4、片选引脚拉高*/
SPI_FLASH_CS_HIGH();
/*5、等待内部时序完成*/
SPI_FLASH_Wait_For_Standby();
}
/***********************************
功能:从FLASH中读取数据
参数:
addr---要读取的数据的首地址
buf---存储读取到的数据的指针
size---要读取多少个数据
返回值:无
*************************************/
void SPI_FLASH_Read_Buff(uint32_t addr,uint8_t *buf,uint32_t size)
{
/*1、片选引脚拉低*/
SPI_FLASH_CS_LOW();
/*2、发送指令代码*/
SPI_FLASH_SendByte(0x03);
SPI_FLASH_SendByte((addr >> 16) & 0xFF);
SPI_FLASH_SendByte((addr >> 8) & 0xFF);
SPI_FLASH_SendByte(addr & 0xFF);
/*3、读取数据*/
while(size--)
{
*buf = SPI_FLASH_SendByte(DUMMY);
buf++;
}
/*4、片选引脚拉高*/
SPI_FLASH_CS_HIGH();
}
/****************************************
功能:往FLASH中写入数据
参数:
addr---要写入数据的首地址,
buf---要写入的数据的指针
size---要写入多少个数据
返回值:
******************************************/
void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size)
{
uint32_t count=0;
while(size--)
{
count++;
//第一次执行,1,257,256*2+1,256*3+1,addr对齐到4096时
if(count == 1 || (count%256) ==1 || (addr%4096)==0)
{
//结束上一次的页写入指令
SPI_FLASH_CS_HIGH();
//等待上一次页写入的完成
SPI_FLASH_Wait_For_Standby();
//写使能
SPI_FLASH_Write_Enable();
//控制片选引脚
SPI_FLASH_CS_LOW();
//指令代码
SPI_FLASH_SendByte(0x02);
//发送要写入的地址
SPI_FLASH_SendByte((addr>>16) & 0xFF);
SPI_FLASH_SendByte((addr>>8) & 0xFF);
SPI_FLASH_SendByte(addr & 0xFF);
}
SPI_FLASH_SendByte(*buf);
buf++;
addr++;
}
SPI_FLASH_CS_HIGH();
//等待内部时序完成
SPI_FLASH_Wait_For_Standby();
}
/**
* @brief Basic management of the timeout situation.
* @param errorCode:错误代码,可以用来定位是哪个环节出错.
* @retval 返回0,表示SPI读取失败.
*/
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* Block communication and all processes */
printf("SPI 等待超时!errorCode = %d",errorCode);
return 0;
}
main.c文件
#include "./usart/bsp_usart.h"
#include "./spi/bsp_spi.h"
#include "./led/bsp_led.h"
#include "stdio.h"
uint8_t read_buf[4096]={0};
uint8_t write_buf[4096]={0};
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint8_t flash_id = 0;
uint32_t i;
LED_Config();
USART_Config();
SPI_FLASH_Config();
/*
读取FLASH ID测试
*/
printf("\r\n 这是一个FLASH读写测试例程 \r\n");
flash_id = SPI_FLASH_ReadID();
printf("\r\n flash_id = 0x%x\r\n", flash_id);
/*
擦除扇区测试
*/
printf("\r\n擦除开始");
//擦除第0个扇区
SPI_FLASH_Erase_Sector(4096*0);
//擦除第1个扇区
SPI_FLASH_Erase_Sector(4096*1);
printf("\r\n擦除完成");
SPI_FLASH_Read_Buff(0,read_buf,4096);
for(i=0;i<4096;i++)
{
//若不等于0xFF,说明擦除不成功
if(read_buf[i] != 0xFF)
{
printf("\r\n擦除校验失败");
}
}
printf("\r\n擦除校验完成");
/*
写入数据测试
*/
//初始化要写入的数据
for(i=0;i<4096;i++)
{
write_buf[i] = 0x55;
}
printf("\r\n开始写入");
SPI_FLASH_Write_Buff(10,write_buf,4096);
printf("\r\n写入完成");
/*
读取数据测试
*/
SPI_FLASH_Read_Buff(10,read_buf,4096);
printf("\r\n读取到的数据:\r\n");
for(i=0;i<4096;i++)
{
printf("0x%02x ",read_buf[i]);
}
while (1)
{
}
}