GD32实战11__SPI & FLASH

知识点

1. 理解SPI总线原理
2. 强化按时序图编程
3. 掌握FLASH

SPI原理

  1. SPI(Serial Peripheral Interface)串行外设接口,是Motorola公司推出的一种同步串行接口技术。具体高速、全双工、同步的特点。总线本身并没有提供流控、应答确认和校验机制,需要特别注意。

  2. 如图,SPI是主从型总线有且只有一个主设备,可以有1个或多个从设备GD32实战11__SPI & FLASH_第1张图片

  3. SPI至少需要4根线,分别是

    1. SCK 时钟信号,由主设备产生
    2. MOSI (Master Out Slave In) 主设备输出,从设备输入线
    3. MISO (Master In Slave Out) 主设备输入,从设备输出线
    4. CS(可以有多根) 片选信号线,用于控制跟哪个从设备通信
  4. 如图,因SPI同步双工串行的特性,其内部原理非常简洁GD32实战11__SPI & FLASH_第2张图片

  5. 从上面,可以理解SPI的收发其实就是在时钟信号下采样过程,那么我们是在时钟的上升沿、下降沿、高电平、低电平采样呢?SPI按时钟极性(CPOL)和时钟相位(CPHA)分成4种模式用于控制时钟采样,通信双方模式必须一致,具体如下

    1. 模式 CPOL CPHA
      Mode0 0 0
      Mode1 0 1
      Mode2 1 0
      Mode3 1 1
    2. 时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位CPHA
      是用来配置在第几个边沿采样数据。
      CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
      CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
      CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿
      CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿

    3. 主机和从机的发送数据是同时完成的,两者的接收数据也是同时完成的。所以为了保证主从机正确通信,应使得它们的SPI具有相同的时钟极性和时钟相位。

    4. SPI协议并没有规定数据一定是8位的,也可以是16位的,要看通信双方支持哪种。

    5. 传输时是高位先传还是低位先传,协议也没强制要求,也要看双方支持。

  6. 至此,SPI协议部分已经全部介绍完了,可见SPI非常的简洁高效,且弹性很大,需要结合具体的应用配置。

FLASH芯片手册导读

​ GD25Q40芯片是一款Nor FLash,Nor Flash的特点是以‘块’为操作的最小擦写单元,字节为最小读取单元。例如,该款芯片有512KByte=4094Kbit=4MBit,每页大小256Byte,每页就是Nand Flash的最下操作‘块’,具体如图在这里插入图片描述

​ 而且需要遵循先擦后写的操作。flash进行写操作时,只能将相应的位由1变0,而擦除才能把块内所有位由0变1。所有写入数据时,如果该页已经存在数据,必须先擦除再写。

​ 既然Flash有如此特性,芯片是如何管理该芯片的呢?如下图,芯片通过一个命令和逻辑控制器与真正的存储空间进行交互,也可以这么理解,外部MCU要通过SPI总线,向FLASH芯片发送各式各样的命令来控制芯片的读写擦操作。

GD32实战11__SPI & FLASH_第3张图片

​ 具体命令如下,命令很多,初看有点眼花,可以先看我标红的几个命令,可以看出,命令无非是写使能/去使能,读芯片状态,读数据,写数据,擦除命令。再仔细看其他命令也无法这几类,只是在不同模式的下操作罢了。注:此处提到的模式时芯片提供的,例如快速读写模式等等,有兴趣可阅读芯片手册,我选的是标准模式。

GD32实战11__SPI & FLASH_第4张图片

最后,只要安装命令的时序要求写成代码就可以了,具体时序在下面说。

通过SPI读写FLASH

​ 用SPI总线读写FLASH芯片是非常常见的应用

硬件设计

GD32实战11__SPI & FLASH_第5张图片

软件设计

​ GD32是支持SPI控制器的,为了加深对SPI协议的理解,我会软件模拟一个SPI,也会用SPI控制器来实现一个,毕竟在实际应用中多少用硬件SPI控制器的。

软件GPIO模拟SPI

​ 从手册里可以查到,支持mode 0和3,此处我选mode 0,即CPOL和CPHA都为0。在这里插入图片描述

注意,时序参数要求必须按照手册描述写,如下GD32实战11__SPI & FLASH_第6张图片GD32实战11__SPI & FLASH_第7张图片

参考上表,我把延时时间固定5us,这样代码会简单很多,且满足了上述ns级最小延时的要求,虽然有几项有最大时间要求,却不影响我们编码。代码如下:

U8 DRV_SPI_SwapByte(IN U8 byte)
{
    U8 i = 0;
    U8 inDate = byte;
    U8 outBit = 0;
    U8 outDate = 0;
    
    /* SCKPL = 0; SCKPH = 0 */
    for (i = 0; i < 8; i++)
    {
        if (inDate & 0x80)
        {
            SPI_MOSI_HIGH;
        }
        else
        {
            SPI_MOSI_LOW;
        }
        
        SPI_Delay(5);
        SPI_SCK_HIGH;
        outBit = SPI_MISO_READ;
        if (outBit)
        {
            outDate |= 0x1;
        }
        SPI_Delay(5);
        SPI_SCK_LOW;
        SPI_Delay(5);
        inDate <<= 1;
        if (i <7)
        {
            outDate <<= 1;
        }
    }

    return outDate;
}
硬件SPI

​ 详细阅读SPI配置部分代码,与前面原理部分说的完全一致,且参数GD32手册里也有详细说明。

U8 DRV_SPI_SwapByte(IN U8 byte)
{
    while (SPI_I2S_GetBitState(SPI1, SPI_FLAG_TBE) == RESET);

    SPI_I2S_SendData(SPI1, byte);

    while (SPI_I2S_GetBitState(SPI1, SPI_FLAG_RBNE) == RESET);

    return SPI_I2S_ReceiveData(SPI1);
}

static VOID SPI_Configuration(VOID)
{
    SPI_InitPara  SPI_InitStructure;
    
    RCC_APB2PeriphClock_Enable(RCC_APB2PERIPH_SPI1, ENABLE);  
    
    SPI_InitStructure.SPI_TransType = SPI_TRANSTYPE_FULLDUPLEX;
    SPI_InitStructure.SPI_Mode = SPI_MODE_MASTER;
    SPI_InitStructure.SPI_FrameFormat = SPI_FRAMEFORMAT_8BIT;
    SPI_InitStructure.SPI_SCKPL = SPI_SCKPL_LOW;
    SPI_InitStructure.SPI_SCKPH = SPI_SCKPH_1EDGE;
    SPI_InitStructure.SPI_SWNSSEN = SPI_SWNSS_SOFT;
    SPI_InitStructure.SPI_PSC = SPI_PSC_32; 
    SPI_InitStructure.SPI_FirstBit = SPI_FIRSTBIT_MSB;
    SPI_InitStructure.SPI_CRCPOL = 7;
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Enable(SPI1, ENABLE);      
}
FLASH芯片操作
  1. 写使能,图中1,2,3步所示,代码也是按这个时序写的,下面的命令就不再画图说明了GD32实战11__SPI & FLASH_第8张图片

    static VOID GD25Q40_WriteEnable(VOID)
    {
        GD25Q40_CS_LOW();
        DRV_SPI_SwapByte(WREN);
        GD25Q40_CS_HIGH();
    }
    
  2. 等待写操作结束,代码使用的是05H,所以只回一个字节GD32实战11__SPI & FLASH_第9张图片

    static VOID GD25Q40_WaitForWriteEnd(VOID)
    {
        U8 FLASH_Status = 0;
    
        GD25Q40_CS_LOW();
        DRV_SPI_SwapByte(RDSR);
    
        do
        {
            FLASH_Status = DRV_SPI_SwapByte(Dummy_Byte);
        }
        while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress */
    
        GD25Q40_CS_HIGH();
    }
    
  3. Sector擦除,擦除可以理解为一个特殊的写过程,即写FF的过程,所以在下一个操作之前需要等待写完成GD32实战11__SPI & FLASH_第10张图片

    VOID DRV_GD25Q40_SectorErase(U32 SectorAddr)
    {
        GD25Q40_WriteEnable();
    
        GD25Q40_CS_LOW();
        DRV_SPI_SwapByte(SE);
        DRV_SPI_SwapByte((SectorAddr & 0xFF0000) >> 16);
        DRV_SPI_SwapByte((SectorAddr & 0xFF00) >> 8);
        DRV_SPI_SwapByte(SectorAddr & 0xFF);
        GD25Q40_CS_HIGH();
    
        GD25Q40_WaitForWriteEnd();
    }
    
  4. Block擦除GD32实战11__SPI & FLASH_第11张图片

    VOID DRV_GD25Q40_BulkErase(VOID)
    {
        GD25Q40_WriteEnable();
    
        GD25Q40_CS_LOW();
        DRV_SPI_SwapByte(BE);
        GD25Q40_CS_HIGH();
    
        GD25Q40_WaitForWriteEnd();
    }
    
  5. 读数据,读的时候可以指定任意地址读,且可以按字节读GD32实战11__SPI & FLASH_第12张图片

    VOID DRV_GD25Q40_BufferRead(U8* pBuffer, U32 ReadAddr, U16 NumByteToRead)
    {
        GD25Q40_CS_LOW();
        DRV_SPI_SwapByte(READ);
        DRV_SPI_SwapByte((ReadAddr & 0xFF0000) >> 16);
        DRV_SPI_SwapByte((ReadAddr& 0xFF00) >> 8);
        DRV_SPI_SwapByte(ReadAddr & 0xFF);
        while (NumByteToRead--) 
        {
            *pBuffer = DRV_SPI_SwapByte(Dummy_Byte);
            pBuffer++;
        }
        GD25Q40_CS_HIGH();
    }
    
  6. 按页写GD32实战11__SPI & FLASH_第13张图片

    VOID DRV_GD25Q40_PageWrite(U8* pBuffer, U32 WriteAddr, U16 NumByteToWrite)
    {
        GD25Q40_WriteEnable();
    
        GD25Q40_CS_LOW();
        DRV_SPI_SwapByte(WRITE);
        DRV_SPI_SwapByte((WriteAddr & 0xFF0000) >> 16);
        DRV_SPI_SwapByte((WriteAddr & 0xFF00) >> 8);
        DRV_SPI_SwapByte(WriteAddr & 0xFF);
    
        while (NumByteToWrite--)
        {
            DRV_SPI_SwapByte(*pBuffer);
            pBuffer++;
        }
        GD25Q40_CS_HIGH();
    
        GD25Q40_WaitForWriteEnd();
    }
    
  7. 写入一段buf,该接口是在按页的基础上,增加是否换页写的封装接口

    VOID DRV_GD25Q40_BufferWrite(U8* pBuffer, U32 WriteAddr, U16 NumByteToWrite)
    {
        U8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    
        Addr = WriteAddr % GD25Q40_PageSize;
        count = GD25Q40_PageSize - Addr;
        NumOfPage =  NumByteToWrite / GD25Q40_PageSize;
        NumOfSingle = NumByteToWrite % GD25Q40_PageSize;
         /* WriteAddr is GD25Q40_PageSize aligned  */
        if (Addr == 0)
        {   
            /* NumByteToWrite < GD25Q40_PageSize */
            if (NumOfPage == 0) 
            {
                DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
            }
            else /* NumByteToWrite > GD25Q40_PageSize */
            {
                while (NumOfPage--)
                {
                    DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, GD25Q40_PageSize);
                    WriteAddr +=  GD25Q40_PageSize;
                    pBuffer += GD25Q40_PageSize;
                }
                DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumOfSingle);
            }
        }
        else /* WriteAddr is not GD25Q40_PageSize aligned  */
        {
            if (NumOfPage == 0)
            {
                /* (NumByteToWrite + WriteAddr) > GD25Q40_PageSize */
                if (NumOfSingle > count) 
                {
                    temp = NumOfSingle - count;
                    DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, count);
                    WriteAddr +=  count;
                    pBuffer += count;
                    DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, temp);
                }
                else
                {
                    DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
                }
            }
            else /* NumByteToWrite > GD25Q40_PageSize */
            {
                NumByteToWrite -= count;
                NumOfPage =  NumByteToWrite / GD25Q40_PageSize;
                NumOfSingle = NumByteToWrite % GD25Q40_PageSize;
    
                DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, count);
                WriteAddr +=  count;
                pBuffer += count;
    
                while (NumOfPage--)
                {
                    DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, GD25Q40_PageSize);
                    WriteAddr +=  GD25Q40_PageSize;
                    pBuffer += GD25Q40_PageSize;
                }
    
                if (NumOfSingle != 0)
                {
                    DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumOfSingle);
                }
            }
        }
    }
    
功能测试举例
VOID APP_SPI_Test(VOID)
{
    U32 Manufact_ID = 0;
    U8  Tx_Buffer[256];
    U8  Rx_Buffer[256];
    U16 i = 0;
    
    DRV_GD25Q40_Init();
    
    printf("\n\rGD32103C-EVAL-V1.1 SPI Flash: configured...\n\r");
    Manufact_ID = DRV_GD25Q40_ReadID();
    printf("\n\rThe Flash_ID:0x%X\n\r", Manufact_ID);
  
    if (Manufact_ID == sFLASH_ID)   
    {
        printf("\n\rWrite to Tx_Buffer:\n\r");
        for(i=0; i<=255; i++)    
        {
            Tx_Buffer[i] = i;
            printf("0x%02X ",Tx_Buffer[i]);
            if(i%16 == 15)
            {
                printf("\n\r");
            }
        }
        printf("\n\rRead from Rx_Buffer:\n\r");
        DRV_GD25Q40_SectorErase(FLASH_WriteAddress);
        DRV_GD25Q40_BufferWrite(Tx_Buffer,FLASH_WriteAddress, 256);
        APP_Delay(10);
        DRV_GD25Q40_BufferRead(Rx_Buffer,FLASH_ReadAddress, 256);  
    
        for(i=0; i<=255; i++) 
        {	
            printf("0x%02X ", Rx_Buffer[i]);
            if(i%16 == 15)
            {
                printf("\n\r");
            }
        }
        printf("\n\rSPI Flash: Initialize Successfully!\n\r");
    }
    else
    {
        printf("\n\rSPI Flash: Initialize Fail!\n\r");
    }


}

补充,Nor Flash和Nand Flash

  1. 结构方面
    1. Nor Flash采用内存的随机读取技术。各单元之间是并联的,对存储单元进行统一编址,所以可以随机访问任意一个字。既然是统一编址,Nor Flash就可以芯片内执行,即应用程序可直接在flash内运行,而无需先拷贝到RAM。
    2. Nand Flash数据线和地址线共用I/O线,需额外联接一些控制的输入输出。
  2. 读写速度方面
    1. Nor Flash有更快的读取速度
    2. Nand Flash有更快的写、擦除速度。
  3. 寿命(耐用性)
    1. Flash写入和擦除数据时会导致介质的氧化降解。这方面Nor Flash尤甚,所以Nor Flash不适合频繁擦写。
    2. Nor的擦写次数是10万次,Nand的擦写次数是100万次。
  4. 坏块处理
    1. Nand器件的坏块是随机分布的,在使用过程中,难免会产生坏块。所以在使用时要进行坏块管理以保障数据可靠。
  5. 成本和容量
    1. 在面积和工艺相同的情况下,Nand的容量比Nor大的多,成本更低。
    2. Nor Flash可直接通过程序编程,根据地址直接读取,容量一般是M级别的
    3. Nand Flash是根据数据块来设计的,所有Nand Flash容量更大,一般是G级别的。
  6. 易用性
    1. Nor Flash有专用的地址引脚来寻址,较容易和其他芯片联接,还支持本地执行。
    2. Nand Flash的IO端口采用复用的数据线和地址线,必须先通过寄存器串行地进行数据存取。各厂商对信号的定义不同,增加了应用的难度。
  7. 编程角度
    1. Nor Flash采用统一编址(有独立地址线),可随机读取每个“字”,但NOR flash不能像RAM以字节改写数据,只能按“页”写,故Nor Flash不能代替RAM。擦除既可整页擦除,也可整块擦除。
    2. Nand Flash共用地址线和数据线,页是读写数据的最小单元,块是擦除数据的最小单元。
    3. 另外,Flash进行写操作时,只能将相应的位由1变0,而擦除才能把块内所有位由0变1。所有写入数据时,如果该页已经存在数据,必须先擦除再写。

代码路径

https://github.com/YaFood/GD32F103/tree/master/TestSPI

你可能感兴趣的:(ARM)