SPI---读写串行FLASH

一、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)
  {   
        
  }  

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值