1.SPI介绍
SPI:串行外设设备接口,是一种高速的,全双工的,同步的通讯总线,并且只在芯片的引脚上占用四根线。主要用用在EEPROM,FLASH,实时时钟,AD转换器之间。
注:(1)SPI会有主从机(或者对于MCU来说会有主模式,从模式)之分,它的区分依据是SCLK同步时钟和SS片选是由谁输出,输出方就会被定义为工作在主机(主模式)状态下。(2)如果在一个系统中只含有一个采用SPI通信的外设,那么我们可以将外设的SS引脚直接连接板子的GND引脚,这样使得外设始终被选中,从而节省了连接线。
通信总是由主设备发起。主设备通过MOSI脚把数据发送给从设备,从设备通过MISO引脚回传数据。这意味全双工通信的数据输出和数据输入是用同一个时钟信号同步的;时钟信号由主设备通过SCK脚提供。
2.SPI框图
3.SPI的工作模式
时钟极性(CPOL):没有数据传送时的空闲电平状态
0:SCK在空闲状态是低电平
1:SCK在空闲状态是高电平
时钟相位(CPHA):时钟线在第几个时钟边沿采样数据
0: SCK的第一(奇数)边沿进行数据位采样,数据在第一个时钟边沿锁存
1: SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿锁存
SPI模式 | CPOL | CPHA | 空闲状态时钟极性 | 采样跳变沿 |
---|---|---|---|---|
0 | 0 | 0 | 低电平 | 上升沿从数据线上采样,下降沿输出数据到数据线(第一个跳变沿) |
1 | 0 | 1 | 低电平 | 下降沿采样,上升沿输出(第二个跳变沿) |
2 | 1 | 0 | 高电平 | 下降沿采样,上升沿输出 |
3 | 1 | 1 | 高电平 | 上升沿采样,下降沿输出 |
以模式0举例:
4.SPI相关寄存器介绍
SPI_CR1 : SPI控制寄存器1 用于配置SPI工作参数
SPI_SR:SPI状态寄存器用于查询当前SPI传输状态(TXE、RXN)
SPI_DR: SPI数据寄存器用于存放待发送数据或接收数据,有两个缓冲区
5.SPI实现方式
第一种方式是采用STM32的GPIO模拟SPI时序的方式进行读写Flash芯片;另一种方式采用STM32片内自带的SPI外设进行读写Flash芯片。
5.1 硬件实现spi
配置板上SPI的寄存器
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
}
//SPI 速度设置函数
//SpeedSet:
//SPI_BaudRatePrescaler_2 2分频
//SPI_BaudRatePrescaler_8 8分频
//SPI_BaudRatePrescaler_16 16分频
//SPI_BaudRatePrescaler_256 256分频
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
SPI2->CR1&=0XFFC7;
SPI2->CR1|=SPI_BaudRatePrescaler; //设置SPI2速度
SPI_Cmd(SPI2,ENABLE);
}
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
5.2 软件实现spi
确定模式、数据高低位、传送速率都由从设备确定。经常使用模式0、3
#define SPI_WP_PIN GPIO_Pin_6
#define SPI_WP_PORT GPIOC
#define SPI_CS_PIN GPIO_Pin_15
#define SPI_CS_PORT GPIOA
#define SPI_CLK_PIN GPIO_Pin_3
#define SPI_CLK_PORT GPIOB
#define SPI_MOSI_PIN GPIO_Pin_12
#define SPI_MOSI_PORT GPIOC
#define SPI_MISO_PIN GPIO_Pin_4
#define SPI_MISO_PORT GPIOB
#define SPI_BASE SPI3
#define SPI_FLASH_CS_LOW() GPIO_ResetBits(SPI_CS_PORT, SPI_CS_PIN) /* Select SPI MEM1: ChipSelect pin low */
#define SPI_FLASH_CS_HIGH() GPIO_SetBits(SPI_CS_PORT, SPI_CS_PIN) /* Deselect SPI MEM1: ChipSelect pin high */
#define SPI_FLASH_WP_LOW() GPIO_ResetBits(SPI_WP_PORT, SPI_WP_PIN) //PC4
#define SPI_FLASH_WP_HIGH() GPIO_SetBits(SPI_WP_PORT, SPI_WP_PIN) //PC4
//模拟MOSI
void SPI_MOSI(unsigned char Status)
{
if(Status)
GPIO_WriteBit(SPI_MOSI_PORT,SPI_MOSI_PIN,Bit_SET);
else
GPIO_WriteBit(SPI_MOSI_PORT,SPI_MOSI_PIN,Bit_RESET);
}
//模拟CLK
void SPI_CLK(unsigned char Status)
{
if(Status)
GPIO_WriteBit(SPI_CLK_PORT,SPI_CLK_PIN,Bit_SET);
else
GPIO_WriteBit(SPI_CLK_PORT,SPI_CLK_PIN,Bit_RESET);
}
//模拟MISO
unsigned char SPI_MISO(void)
{
if(GPIO_ReadInputDataBit(SPI_MISO_PORT,SPI_MISO_PIN))
return 1;
else
return 0;
}
void gd25qxxInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = SPI_MOSI_PIN; //PA5:SCK,PA7:MOSI
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(SPI_MOSI_PORT, &GPIO_InitStructure);
SPI_MOSI(1);
GPIO_InitStructure.GPIO_Pin = SPI_CLK_PIN; //PA5:SCK,PA7:MOSI
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(SPI_CLK_PORT, &GPIO_InitStructure);
SPI_CLK(1);
GPIO_InitStructure.GPIO_Pin = SPI_MISO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(SPI_MISO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = SPI_CS_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(SPI_CS_PORT, &GPIO_InitStructure);
SPI_FLASH_CS_HIGH();
GPIO_InitStructure.GPIO_Pin = SPI_WP_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(SPI_WP_PORT, &GPIO_InitStructure);
SPI_FLASH_WP_HIGH();
}
uint8_t gd25qxxSendByte(uint8_t byte)// 模式0 上升沿从数据线采样,下降沿输出数据到数据线
{
unsigned char i,Result = 0;;
for(i=0;i<8;i++)
{
if(byte & 0x80) SPI_MOSI(1);
else SPI_MOSI(0);
byte <<= 1;
SPI_CLK(0);
SPI_CLK(1);
Result <<= 1;
if(SPI_MISO()) Result |= 0x01;
}
SPI_CLK(0);
return(Result);
}
uint32_t gd25qxxReadID(void)
{
uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
/* Select the FLASH: Chip Select low */
SPI_FLASH_CS_LOW();
/* Send "RDID " instruction */
gd25qxxSendByte(0x9F);
/* Read a byte from the FLASH */
Temp0 = gd25qxxSendByte(DUMMY_BYTE);
/* Read a byte from the FLASH */
Temp1 = gd25qxxSendByte(DUMMY_BYTE);
/* Read a byte from the FLASH */
Temp2 = gd25qxxSendByte(DUMMY_BYTE);
/* Deselect the FLASH: Chip Select high */
SPI_FLASH_CS_HIGH();
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}