一、SPI协议简介
SPI 协议是一种高速全双工的通信总线。它被广泛地使用在 ADC、 LCD 等设备与 MCU 间,
要求通讯速率较高的场合。
1、SPI物理层
SPI 通讯使用 3 条总线及片选线, 3 条总线分别为 SCK、 MOSI、 MISO,片选线为 SS。
1、SS:当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效。SPI通讯以 SS 线置低电平为开始信号,以 SS 线被拉高作为结束信号。
2、SCK:时钟信号线,用于通讯数据同步。它由通讯主机产生,STM32 的 SPI 时钟频率最大为
fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
3、 MOSI:主设备输出/从设备输入引脚。这条线上数据的方向为主机到从机。
4 、MISO:主设备输入/从设备输出引脚。在这条线上数据的方向为从机到主机。
2、协议层
1. SPI 基本通讯过程
SPI 通讯的通讯时序:
NSS、 SCK、 MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生。
2、CPOL/CPHA 及通讯模式
时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时, SCK 信号线的电平信号。 CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时, MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿” 被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿” 采样。CPHA=0 时的 SPI 通讯模式:
CPHA=1 时的 SPI 通讯模式:
二 、STM32 的 SPI 特性及架构
1、 STM32 的 SPI 架构剖析
(1)通讯引脚:
(2)时钟控制逻辑:
SCK线的时钟信号,由波特率发生器根据“控制寄存器”CR1中的BR[0:2]位控制,该位是对 fpclk 时钟的分频因子,对 fpclk 的分频结果就是 SCK 引脚的输出时钟频率。
(3)数据控制逻辑
(4)整体控制逻辑
三、SPI初始化结构体
SPI_NSS:在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。GPIO端口设置成推挽输出。
四、SPI—读写串行 FLASH
FLSAH 存储器又称闪存,它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH存储器容量普遍大于 EEPROM。在存储控制上,FLASH 芯片只能一大片一大片地写,EEPROM 可以单个字节擦写。
1、硬件设计
FLASH的存储特性:
1、在写入数据之前必须先擦除
2、擦除时会把数据全重置为1
3、写入数据时只能把为1的数据改为0
4、擦除时必须按最小单位来擦除(一般为扇区)
norflash可以一个字节写入,nandflash必须以块或扇区为单位进行读写
2、软件设计
①初始化并使能SPI :
void SPI_GPIO_Config()
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
//SCK
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//MISO
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//MOSI
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
// SPI的CS引脚,软件控制,设置成推挽输出,类似于LED
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//CS引脚设置为高电平
GPIO_SetBits(GPIOB, GPIO_Pin_12);
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;
SPI_InitStructure.SPI_CRCPolynomial=0; //不使用CRC功能,数值随便写
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex; //双线全双工
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_InitStructure.SPI_Mode=SPI_Mode_Master;
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_Cmd(SPI2, ENABLE);
}
②发送并接收一个字节:
uint8_t SPI_FLASH_SendByte(uint8_t data)
{
SPITimeout = SPIT_LONG_TIMEOUT;
//检查并等待至TX缓冲区为空
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE)==RESET);
SPI_I2S_SendData(SPI2, data);
//检查并等待至RX缓冲区非空
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)==RESET);
return SPI_I2S_ReceiveData(SPI2);
}
③接收字节:
uint8_t SPI_FLASH_Read_Byte()
{
SPI_FLASH_SendByte(0x00);
}
④写使能:
void SPI_Write_Enable()
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
SPI_FLASH_SendByte(0x06);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
⑤等待FLASH内部时序完成:
读取状态寄存器时序:
FLASH芯片的状态寄存器:
void SPI_WaitForWriteEnd()
{
uint8_t FLASH_Status = 0;
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
//读取FLASH状态寄存器
SPI_FLASH_SendByte(0x05);
do
{
FLASH_Status=SPI_FLASH_SendByte(0x00);
}
while((FLASH_Status&0x01)==1);//忙碌
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
⑥读FLASH芯片ID:
uint32_t SPI_Read_ID()
{
uint32_t flash_id;
//片选
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
SPI_FLASH_SendByte(0x9F);
//读取FLASH需要不断发送数据,保证时钟,可以发送任意数据
flash_id=SPI_FLASH_SendByte(0x00);
flash_id<<=8;
flash_id|=SPI_FLASH_SendByte(0x00);
flash_id<<=8;
flash_id|=SPI_FLASH_SendByte(0x00);
GPIO_SetBits( GPIOB, GPIO_Pin_12);
return flash_id;
}
⑦擦除FLASH指定扇区
由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。
FLASH内部有128个block。
要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。
void SPI_FLASH_SectorErase(uint32_t addr)
{
SPI_Write_Enable();
SPI_WaitForWriteEnd();
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
SPI_FLASH_SendByte(0x20);
SPI_FLASH_SendByte((addr>>16)&0xff);
SPI_FLASH_SendByte((addr>>8)&0xff);
SPI_FLASH_SendByte(addr&0xff);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
SPI_WaitForWriteEnd();
}
⑧FLASH的页写入
使用页写入命令最多可以一次向 FLASH 传输 256 个字节的数据,我们把这个单位为页大小。
FLASH 页写入的时序:
void SPI_FLASH_PageWrite(uint32_t addr,uint8_t *writeData,uint16_t numToWrite)
{
SPI_Write_Enable();
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
SPI_FLASH_SendByte(0x02);
SPI_FLASH_SendByte((addr>>16)&0xff);
SPI_FLASH_SendByte((addr>>8)&0xff);
SPI_FLASH_SendByte(addr&0xff);
while(numToWrite--)
{
SPI_FLASH_SendByte(*writeData);
writeData++;
}
GPIO_SetBits(GPIOB, GPIO_Pin_12);
SPI_WaitForWriteEnd();
}
⑨多页写入
void SPI_FLASH_BufferWrite(uint32_t addr,uint8_t *writeData,uint16_t numToWrite)
{
uint8_t NumofPage=0,NumofSingle=0,Addr=0,count=0,temp=0;
Addr=addr%256;
count=256-Addr;
NumofPage=numToWrite/256;
NumofSingle=numToWrite%256;
if(Addr==0)
{
if(NumofPage==0)
{
SPI_FLASH_PageWrite(addr,writeData,numToWrite);
}
else
{
while(NumofPage--)
{
SPI_FLASH_PageWrite(addr,writeData,256);
writeData+=256;
addr+=256;
}
SPI_FLASH_PageWrite(addr,writeData,NumofSingle);
}
}
else
{
if(NumofPage==0)
{
temp=numToWrite-count;
SPI_FLASH_PageWrite(addr,writeData,count);
addr+=count;
writeData+=count;
SPI_FLASH_PageWrite(addr,writeData,temp);
}
else
{
numToWrite-=count;
NumofPage=numToWrite/256;
NumofSingle=numToWrite%256;
if(count!=0)
{
SPI_FLASH_PageWrite(addr,writeData,count);
addr+=count;
writeData+=count;
}
while(NumofPage--)
{
SPI_FLASH_PageWrite(addr,writeData,256);
writeData+=256;
addr+=256;
}
if(NumofSingle!=0)
{
SPI_FLASH_PageWrite(addr,writeData,NumofSingle);
}
}
}
}
⑩读取 FLASH内容
void SPI_FLASH_Read_Data(uint32_t addr,uint8_t *readData,uint32_t numToRead)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
SPI_FLASH_SendByte(0x03);
SPI_FLASH_SendByte((addr>>16)&0xff);
SPI_FLASH_SendByte((addr>>8)&0xff);
SPI_FLASH_SendByte(addr&0xff);
while(numToRead--)
{
*readData=SPI_FLASH_SendByte(0x00);
readData++;
}
GPIO_SetBits(GPIOB, GPIO_Pin_12);
SPI_WaitForWriteEnd();
}