stm32-SPI

记录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的程序了。