记录STM32硬件SPI和W25Q64通讯关键处,及模拟SPI和硬件SPI的不同之处。若有不同见解,还望留言一同交流学习。
STM32主机初始化
对于硬件SPI来说主机初始化需要配置较多东西,而且其中有些配置存在争议,所以在此做一个详细记录。
void spi_init(void)
{
SPI_InitTypeDef SPI2_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
/////////scl,mosi
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/////////miso,最好根据手册设置为上拉,有些程序设置成复用推挽输出,因为芯片结构的原因所以也可行
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
///////////////NSS,选择为soft模式后需要人工进行片选操作
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_12);//操作前先不选择从器件
SPI2_InitStructure.SPI_Mode=SPI_Mode_Master;
SPI2_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
SPI2_InitStructure.SPI_DataSize=SPI_DataSize_8b;
//以下两个配置关系到芯片何时进行数据采集(采样),后做详解
SPI2_InitStructure.SPI_CPOL=SPI_CPOL_Low;
SPI2_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;
//该处设置必须为软件模式,也就是人为操作CS引脚进行片选,而不是芯片根据自己时序操作,NSS引脚作为普通GPIO口置低电平进行片选
SPI2_InitStructure.SPI_NSS=SPI_NSS_Soft;
SPI2_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;
SPI2_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
//CRC校验,这一个参数在整个操作中并没用使用上,对CRC校验该处的设置持默认态度
SPI2_InitStructure.SPI_CRCPolynomial=7;
SPI_Init(SPI2, &SPI2_InitStructure);
SPI_Cmd(SPI2, ENABLE);
}
SPI2_InitStructure.SPI_CPOL=SPI_CPOL_Low;和SPI2_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;是分别对SPI的时钟极性和时钟相位进行配置,主要有4种模式(0,0),(0,1),(1,0),(1,1)这四种模式要根据从器件的采集时序(时钟)来配置,需要和从设备相同(例如从设备只支持(0,0)模式那这里就需要配置为(0,0))。下图对这四种模式做详细介绍:
写入数据
根据STM32参考手册SPI部分可以知道硬件SPI需要检测的标志位相对于硬件I2C来说少了很多,所以以32作为主机做硬件SPI的编码会更需要从器件的事件发生时序入手,下面是一个写入操作,配DATESHEET事件图:
void SPI_Flash_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16 i;
SPI_FLASH_Write_Enable();
SPI_FLASH_CS_CLR;
SPI2_ReadWriteByte(0x02);
SPI2_ReadWriteByte((u8)((WriteAddr)>>16));
SPI2_ReadWriteByte((u8)((WriteAddr)>>8));
SPI2_ReadWriteByte((u8)WriteAddr);
for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);
SPI_FLASH_CS_SET;
SPI_Flash_Wait_Busy();
}
因为nor flash写入前必须擦除,根据DATESHEET描述在擦除操作前需要先写使能,所以先执行写使能函数,再发送一个0x02擦除4K存储地址命令,接下来发送由三个8位数据组成的地址,最后发送写入的数据,关片选,忙检测。
基本写入函数(可以看出根据32的手册分别做了标志位检测):
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData);
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2);
}
写使能函数:
void SPI_FLASH_Write_Enable(void)
{
SPI_FLASH_CS_CLR;
SPI2_ReadWriteByte(0x06);
SPI_FLASH_CS_SET;
}
忙检测,需要特殊强调下。这里的忙检测并不是对32来说的所以不可以用32库中的SPI_I2S_FLAG_BSY标志来判别,此处是对从设备的忙检测,检测从设备是否操作完成,这一步需要通过读取W25Q64的状态寄存器实现
void SPI_Flash_Wait_Busy(void)
{
while ((SPI_Flash_ReadSR()&0x01)==0x01);
}
u8 SPI_Flash_ReadSR(void)
{
u8 byte=0;
SPI_FLASH_CS_CLR;
SPI2_ReadWriteByte(0x05);//读取状态标志位寄存器命令
byte=SPI2_ReadWriteByte(0Xff);
SPI_FLASH_CS_SET;
return byte;
}
这里有个发送0XFF数据,这里必须的。必须向从器件发送一个无关的数据,以便产生SCLK时钟。硬件SPI发送时才会产生SCLK时钟,所以在读取时要同时发送一个数据。这也是硬件SPI和模拟SPI差别最大的地方。
读取数据
数据的读取也是依据DATESHEET上的事件产生时间来操作,需要注意的依然是读取数据时还需要写入无关数据
void SPI_Flash_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16 i;
SPI_FLASH_CS_CLR;//将NSS引脚置低电平,选择从设备
SPI2_ReadWriteByte(0x03);
SPI2_ReadWriteByte((u8)((ReadAddr)>>16));
SPI2_ReadWriteByte((u8)((ReadAddr)>>8));
SPI2_ReadWriteByte((u8)ReadAddr);
for(i=0;i<NumByteToRead;i++)
{
pBuffer[i]=SPI2_ReadWriteByte(0XFF);
}
SPI_FLASH_CS_SET;//将NSS引脚置高电平,不选择该从设备
}
由于SPI是高速通讯协议,所以能用硬件SPI就尽量采用硬件解决,此处就不贴出模拟SPI的程序了。