首页 > 其他分享 >设计NOR Flash(SPI接口)的Flashloader(MCU: stm32f4)

设计NOR Flash(SPI接口)的Flashloader(MCU: stm32f4)

时间:2024-06-30 18:00:39浏览次数:17  
标签:CS Flash SF SPI SendByte stm32f4 GPIO sf

目录

概述

1 软硬件

1.1 软硬件信息表

1.2 NOR Flash芯片(W25Q64BVSSI)

1.2.1 W25Q64BVSSI芯片介绍

1.2.2 NOR Flash接口

1.3 MCU与NOR Flash接口

2 SPI Flash功能实现

2.1 软件框架结构

2.2 代码实现

2.2.1 Dev_Inf文件

2.2.2 W25QXX驱动程序

2.3 Flash loader驱动接口程序

3 Keil中的参数配置

3.1 配置参数

3.2 编译

4 测试

4.1 准备.stldr文件

4.2  STM32CubeProgrammer中测试Flash loader


源代码下载地址:

W25QXX-STM32F4-ALY:设计NORFlash(SPI接口)的Flashloader(MCU:stm32f4)资源-CSDN文库

概述

本文主要介绍基于STM32F407芯片,NOR Flash芯片为W25Q64(SPI接口)。使用其设计一个Flashloader 程序,并且在STM32CubeProgrammer工具中使用该文件,实现NOR Flash擦除数据,编程数据,读取数据的功能。

1 软硬件

1.1 软硬件信息表

软硬件信息版本信息
STM32 MCUSTM32F407IGTx
NOR FlashW25Q64BVSSI
KeilMDK ARM 5.38
调试工具:st-linkST-LINK/V2-1
STM32CubeProgrammerv2.16.0

1.2 NOR Flash芯片(W25Q64BVSSI)

1.2.1 W25Q64BVSSI芯片介绍

W25Q64BVSSI是一款容量为64Mb(8MB)的串行闪存存储器,由Winbond公司生产。它采用了串行外围设备接口(SPI),可用于存储嵌入式系统的代码和数据。

W25Q64BVSSI具有以下特点:

  1. 容量大:64Mb的存储容量可以存储大量的代码和数据。
  2. 高速访问:采用SPI接口,支持快速的读取和写入操作,具有快速的数据传输速度。
  3. 低功耗:W25Q64BVSSI采用低功耗设计,可以有效地节省系统能源。
  4. 可编程:可以通过软件进行编程和擦除操作,方便灵活的存储管理。
  5. 高可靠性:具有内置的错误检测和纠正机制,可以提高存储器的可靠性和数据完整性。
  6. 多种封装形式:W25Q64BVSSI可提供多种封装形式,例如SOIC、WSON和TFBGA等,以满足不同应用的需求。

W25Q64CV(64M位)串行闪存为具有有限空间、引脚和电源。25Q系列提供了远远超出普通串行闪存的灵活性和性能设备。它们非常适合将代码隐藏到RAM,直接从双/四SPI(XIP)执行代码以及存储语音、文本和数据。该设备在单个2.7V至3.6V的电源上运行,并带有电流功耗低至4mA有源和1µA断电。

W25Q64CV阵列被组织成32768个可编程页面,每个页面256字节。最多256字节可以一次编程。页面可以按16组(4KB扇区擦除)、128组擦除(32KB块擦除)、256组(64KB块擦除(block erase))或整个芯片(chip erase)。W25Q64CV分别具有2048个可擦除扇区和128个可擦除块。较小的4KB扇区允许在需要数据和参数存储的应用程序中具有灵活性。

1.2.2 NOR Flash接口

笔者使用的芯片封装为:

IO 接口介绍

1.3 MCU与NOR Flash接口

NOR Flash和MCU之间通过SPI接口通信,选择STM32F407芯片上的SPI1接口,该通信接口特点如下:

1 STM32F4XX 时钟计算.
        HCLK = 168M
        PCLK1 = HCLK / 4 = 42M
        PCLK2 = HCLK / 2 = 84M

        SPI2、SPI3 在 PCLK1, 时钟42M
        SPI1       在 PCLK2, 时钟84M

        STM32F4 支持的最大SPI时钟为 37.5 Mbits/S, 因此需要分频。

2   开发板口线分配:  串行Flash型号为 W25Q64BVSSIG (80MHz)
        PB3/SPI3_SCK/SPI1_SCK
        PB4/SPI3_MISO/SPI1_MISO
        PB5/SPI3_MOSI/SPI1_MOSI
        PF8/SF_CS

        STM32硬件SPI接口 = SPI3 或者 SPI1,由于SPI1的时钟源是84M, SPI3的时钟源是42M。为了获得更快的速度,软件上选择SPI1。
 

MCU接口NOR Flash
GPIOF_PIN6CS
GPIOB_PIN3SCK
GPIOB_PIN4MISO
GPIOB_PIN5MOSI

硬件电路图如下:

2 SPI Flash功能实现

2.1 软件框架结构

Flash loader的代码分为两个部分

1)Loader: Flash leader的功能代码,定义Flash leader的芯片信息;实现该功能的驱动接口

2)STM32F40X:MCU相关的驱动接口,包括时钟驱动、SPI驱动、GPIO驱动。以及NOR Flash的驱动接口

2.2 代码实现

2.2.1 Dev_Inf文件

代码第5行: 定义设备名称

代码第6行:定义Flash的类型

代码第7行:其实地址

代码第8行:芯片空间大小

代码第9行:芯片page空间大小

代码第13行:芯片sector相关的参数

1)Dev_Inf.c 详细代码内容:

#include "Dev_Inf.h"

/* This structure containes information used by ST-LINK Utility to program and erase the device */
struct StorageInfo const StorageInfo  =  {
   "W25QXX_STM32F407ALY",                  // Device Name + version number
   SPI_FLASH,                              // Device Type
   0x00000000,                             // Device Start Address
   WW25QXX_CHIP_SIZE,                      // Device Size in Bytes (8MBytes/64Mbits)
   W25QXX_PAGE_SIZE,                       // Programming Page Size 256 BYTES
   0xFF,                                   // Initial Content of Erased Memory
    
// Specify Size and Address of Sectors (view example below)
   W25Q16J_SEC_NUM, W25QXX_SECTOR_SIZE,    // Sector Num : 128 ,Sector Size: 64KBytes 
   0x00000000, 0x00000000,
}; 


/* End of this file */

 2)Dev_nf.h 详细代码内容:


#define W25QXX_PAGE_SIZE           (             256)
#define W25QXX_SECTOR_SIZE         (        4 * 1024)
#define W25QXX_SUBBLOCK_SIZE       (       32 * 1024)
#define W25QXX_BLOCK_SIZE          (       64 * 1024)
#define WW25QXX_CHIP_SIZE          ( 8 * 1024 * 1024) 
  
#define W25Q16J_SEC_NUM            (WW25QXX_CHIP_SIZE/W25QXX_SECTOR_SIZE) 


#define    MCU_FLASH   1
#define    NAND_FLASH  2
#define    NOR_FLASH   3
#define    SRAM        4
#define    PSRAM       5
#define    PC_CARD     6
#define    SPI_FLASH   7
#define    I2C_FLASH   8
#define    SDRAM       9
#define    I2C_EEPROM  10

#define SECTOR_NUM     10      // Max Number of Sector types

struct DeviceSectors  
{
  unsigned long  SectorNum;     // Number of Sectors
  unsigned long  SectorSize;    // Sector Size in Bytes
};

struct StorageInfo  
{
   char           DeviceName[100];      // Device Name and Description
   unsigned short DeviceType;           // Device Type: ONCHIP, EXT8BIT, EXT16BIT, ...
   unsigned long  DeviceStartAddress;   // Default Device Start Address
   unsigned long  DeviceSize;           // Total Size of Device
   unsigned long  PageSize;             // Programming Page Size
   unsigned char  EraseValue;           // Content of Erased Memory
   struct         DeviceSectors  sectors[SECTOR_NUM];
};

2.2.2 W25QXX驱动程序

1)创建bsp_spi_flash.c文件,实现如下代码

#include "bsp_spi_flash.h"

/*
    STM32F4XX 时钟计算.
        HCLK = 168M
        PCLK1 = HCLK / 4 = 42M
        PCLK2 = HCLK / 2 = 84M

        SPI2、SPI3 在 PCLK1, 时钟42M
        SPI1       在 PCLK2, 时钟84M

        STM32F4 支持的最大SPI时钟为 37.5 Mbits/S, 因此需要分频。
*/

/*
        STM32-V5 开发板口线分配:  串行Flash型号为 W25Q64BVSSIG (80MHz)
        PB3/SPI3_SCK/SPI1_SCK
        PB4/SPI3_MISO/SPI1_MISO
        PB5/SPI3_MOSI/SPI1_MOSI
        PF8/SF_CS

        STM32硬件SPI接口 = SPI3 或者 SPI1

        由于SPI1的时钟源是84M, SPI3的时钟源是42M。为了获得更快的速度,软件上选择SPI1。
*/
//#define SPI_FLASH_PORT            SPI3
#define SPI_FLASH_PORT              SPI1

//#define ENABLE_SPI_RCC()     RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE)
#define ENABLE_SPI_RCC()     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE)

/*
【SPI时钟最快是2分频,不支持不分频】
如果是SPI1,2分频时SCK时钟 = 42M,4分频时SCK时钟 = 21M
如果是SPI3, 2分频时SCK时钟 = 21M
*/
#define SPI_BAUD                SPI_BaudRatePrescaler_4

/* 片选GPIO端口  */
#define SF_CS_GPIO              GPIOF
#define SF_CS_PIN               GPIO_Pin_8

/* 片选口线置低选中  */
#define SF_CS_LOW()       SF_CS_GPIO->BSRRH = SF_CS_PIN

/* 片选口线置高不选中 */
#define SF_CS_HIGH()      SF_CS_GPIO->BSRRL = SF_CS_PIN

#define CMD_AAI       0xAD        /* AAI 连续编程指令(FOR SST25VF016B) */
#define CMD_DISWR     0x04        /* 禁止写, 退出AAI状态 */
#define CMD_EWRSR     0x50        /* 允许写状态寄存器的命令 */
#define CMD_WRSR      0x01        /* 写状态寄存器命令 */
#define CMD_WREN      0x06        /* 写使能命令 */
#define CMD_READ      0x03        /* 读数据区命令 */
#define CMD_WPAGE     0x02        /* 写page数据区命令 */
#define CMD_RDSR      0x05        /* 读状态寄存器命令 */
#define CMD_RDID      0x9F        /* 读器件ID命令 */
#define CMD_SE        0x20        /* 擦除扇区命令 */
#define CMD_BE        0xC7        /* 批量擦除命令 */
#define DUMMY_BYTE    0xA5        /* 哑命令,可以为任意值,用于读操作 */

#define WIP_FLAG      0x01        /* 状态寄存器中的正在编程标志(WIP) */

SFLASH_T g_tSF;

void sf_ReadInfo(void);
static uint8_t sf_SendByte(uint8_t _ucValue);
static void sf_WriteEnable(void);
static void sf_WriteStatus(uint8_t _ucValue);
static void sf_WaitForWriteEnd(void);
static void bsp_CfgSPIForSFlash(void);

/*
*********************************************************************************************************
*    函 数 名: bsp_InitSpiFlash
*    功能说明: 初始化串行Flash硬件接口(配置STM32的SPI时钟、GPIO)
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitSFlash(void)
{
    /*
        STM32-F407 开发板口线分配:  串行Flash型号为 W25Q64BVSSIG (80MHz)
        PB3/SPI3_SCK
        PB4/SPI3_MISO
        PB5/SPI3_MOSI
        PF8/SF_CS

        STM32硬件SPI接口 = SPI3
    */
    {
        GPIO_InitTypeDef GPIO_InitStructure;

        /* 使能GPIO 时钟 */
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOF, ENABLE);

//         /* 配置 SCK, MISO 、 MOSI 为复用功能 */
//         GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI3);
//         GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI3);
//         GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI3);
        
        /* 配置 SCK, MISO 、 MOSI 为复用功能 */
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
        GPIO_Init(GPIOB, &GPIO_InitStructure);

        /* 配置片选口线为推挽输出模式 */
        SF_CS_HIGH();        /* 片选置高,不选中 */
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
        GPIO_Init(GPIOF, &GPIO_InitStructure);
    }

    /* 配置SPI硬件参数用于访问串行Flash */
    bsp_CfgSPIForSFlash();

    sf_ReadInfo();                    /* 自动识别芯片型号 */

    SF_CS_LOW();                      /* 软件方式,使能串行Flash片选 */
    sf_SendByte(CMD_DISWR);        /* 发送禁止写入的命令,即使能软件写保护 */
    SF_CS_HIGH();                      /* 软件方式,禁能串行Flash片选 */

    sf_WaitForWriteEnd();          /* 等待串行Flash内部操作完成 */

    sf_WriteStatus(0);              /* 解除所有BLOCK的写保护 */
}

/*
*********************************************************************************************************
*    函 数 名: bsp_CfgSPIForSFlash
*    功能说明: 配置STM32内部SPI硬件的工作模式、速度等参数,用于访问SPI接口的串行Flash。
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
static void bsp_CfgSPIForSFlash(void)
{
    SPI_InitTypeDef  SPI_InitStructure;

    /* 打开SPI时钟 */
    ENABLE_SPI_RCC();

    /* 配置SPI硬件参数 */
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;     /* 数据方向:2线全双工 */
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                             /* STM32的SPI工作模式 :主机模式 */
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                       /* 数据位长度 : 8位 */
    /* SPI_CPOL和SPI_CPHA结合使用决定时钟和数据采样点的相位关系、
       本例配置: 总线空闲是高电平,第2个边沿(上升沿采样数据)
    */
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;            /* 时钟上升沿采样数据 */
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;        /* 时钟的第2个边沿采样数据 */
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;              /* 片选控制方式:软件控制 */

    /* 设置波特率预分频系数 */
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BAUD;

    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;        /* 数据位传输次序:高位先传 */
    SPI_InitStructure.SPI_CRCPolynomial = 7;                      /* CRC多项式寄存器,复位后为7。本例程不用 */
    SPI_Init(SPI_FLASH_PORT, &SPI_InitStructure);

    SPI_Cmd(SPI_FLASH_PORT, DISABLE);              /* 先禁止SPI  */
    SPI_Cmd(SPI_FLASH_PORT, ENABLE);                /* 使能SPI  */
}

/*
*********************************************************************************************************
*    函 数 名: sf_EraseSector
*    功能说明: 擦除指定的扇区
*    形    参:  _uiSectorAddr : 扇区地址
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_EraseSector(uint32_t _uiSectorAddr)
{
    sf_WriteEnable();                                               /* 发送写使能命令 */

    /* 擦除扇区操作 */
    SF_CS_LOW();                                                      /* 使能片选 */
    sf_SendByte(CMD_SE);                                            /* 发送擦除命令 */
    sf_SendByte((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    sf_SendByte((_uiSectorAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
    sf_SendByte(_uiSectorAddr & 0xFF);                      /* 发送扇区地址低8bit */
    SF_CS_HIGH();                                                      /* 禁能片选 */

    sf_WaitForWriteEnd();                                          /* 等待串行Flash内部写操作完成 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_EraseChip
*    功能说明: 擦除整个芯片
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_EraseChip(void)
{
    sf_WriteEnable();                                  /* 发送写使能命令 */

    /* 擦除扇区操作 */
    SF_CS_LOW();                                        /* 使能片选 */
    sf_SendByte(CMD_BE);                            /* 发送整片擦除命令 */
    SF_CS_HIGH();                                        /* 禁能片选 */

    sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_PageWrite
*    功能说明: 向一个page内写入若干字节。字节个数不能超出页面大小(4K)
*    形    参:      _pBuf : 数据源缓冲区;
*                _uiWriteAddr :目标区域首地址
*                _usSize :数据个数,不能超过页面大小
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)
{
    uint32_t i, j;

    if (g_tSF.ChipID == SST25VF016B_ID)
    {
        /* AAI指令要求传入的数据个数是偶数 */
        if ((_usSize < 2) && (_usSize % 2))
        {
            return ;
        }

        sf_WriteEnable();                                  /* 发送写使能命令 */

        SF_CS_LOW();                                        /* 使能片选 */
        sf_SendByte(CMD_AAI);                            /* 发送AAI命令(地址自动增加编程) */
        
        sf_SendByte((_uiWriteAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
        sf_SendByte((_uiWriteAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */
        sf_SendByte(_uiWriteAddr & 0xFF);                      /* 发送扇区地址低8bit */
        sf_SendByte(*_pBuf++);                            /* 发送第1个数据 */
        sf_SendByte(*_pBuf++);                             /* 发送第2个数据 */
        
        SF_CS_HIGH();                                          /* 禁能片选 */

        sf_WaitForWriteEnd();                              /* 等待串行Flash内部写操作完成 */

        _usSize -= 2;                                          /* 计算剩余字节数 */

        for (i = 0; i < _usSize / 2; i++)
        {
            SF_CS_LOW();                                    /* 使能片选 */
            sf_SendByte(CMD_AAI);                        /* 发送AAI命令(地址自动增加编程) */
            sf_SendByte(*_pBuf++);                  /* 发送数据 */
            sf_SendByte(*_pBuf++);                    /* 发送数据 */
            SF_CS_HIGH();                                    /* 禁能片选 */
            sf_WaitForWriteEnd();                        /* 等待串行Flash内部写操作完成 */
        }

        /* 进入写保护状态 */
        SF_CS_LOW();
        sf_SendByte(CMD_DISWR);
        SF_CS_HIGH();

        sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    }
    else    /* for MX25L1606E 、 W25Q64BV */
    {
        for (j = 0; j < _usSize / 256; j++)
        {
            sf_WriteEnable();                                  /* 发送写使能命令 */

            SF_CS_LOW();                                        /* 使能片选 */
            sf_SendByte(0x02);                                /* 发送AAI命令(地址自动增加编程) */
            sf_SendByte((_uiWriteAddr & 0xFF0000) >> 16);      /* 发送扇区地址的高8bit */
            sf_SendByte((_uiWriteAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
            sf_SendByte(_uiWriteAddr & 0xFF);                        /* 发送扇区地址低8bit */

            for (i = 0; i < 256; i++)
            {
                sf_SendByte(*_pBuf++);             /* 发送数据 */
            }

            SF_CS_HIGH();                                    /* 禁止片选 */

            sf_WaitForWriteEnd();                        /* 等待串行Flash内部写操作完成 */

            _uiWriteAddr += 256;
        }

        /* 进入写保护状态 */
        SF_CS_LOW();
        sf_SendByte(CMD_DISWR);
        SF_CS_HIGH();

        sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    }
}


/*
*********************************************************************************************************
*    函 数 名: sf_ReadBuffer
*    功能说明: 连续读取若干字节。字节个数不能超出芯片容量。
*    形    参:      _pBuf : 数据源缓冲区;
*                _uiReadAddr :首地址
*                _usSize :数据个数, 可以大于PAGE_SIZE,但是不能超出芯片总容量
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
{
    /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
    {
        return;
    }

    /* 擦除扇区操作 */
    SF_CS_LOW();                                                    /* 使能片选 */
    sf_SendByte(CMD_READ);                                      /* 发送读命令 */
    sf_SendByte((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    sf_SendByte((_uiReadAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
    sf_SendByte(_uiReadAddr & 0xFF);                      /* 发送扇区地址低8bit */
    while (_uiSize--)
    {
        *_pBuf++ = sf_SendByte(DUMMY_BYTE);                /* 读一个字节并存储到pBuf,读完后指针自加1 */
    }
    SF_CS_HIGH();                                                    /* 禁能片选 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_CmpData
*    功能说明: 比较Flash的数据.
*    形    参:      _ucpTar : 数据缓冲区
*                _uiSrcAddr :Flash地址
*                _uiSize :数据个数, 可以大于PAGE_SIZE,但是不能超出芯片总容量
*    返 回 值: 0 = 相等, 1 = 不等
*********************************************************************************************************
*/
uint8_t sf_CmpData(uint32_t _uiSrcAddr, uint8_t *_ucpTar, uint32_t _uiSize)
{
    uint8_t ucValue;

    /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    if ((_uiSrcAddr + _uiSize) > g_tSF.TotalSize)
    {
        return 1;
    }

    if (_uiSize == 0)
    {
        return 0;
    }

    SF_CS_LOW();                                                    /* 使能片选 */
    sf_SendByte(CMD_READ);                                      /* 发送读命令 */
    sf_SendByte((_uiSrcAddr & 0xFF0000) >> 16);        /* 发送扇区地址的高8bit */
    sf_SendByte((_uiSrcAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
    sf_SendByte(_uiSrcAddr & 0xFF);                          /* 发送扇区地址低8bit */
    while (_uiSize--)
    {
        /* 读一个字节 */
        ucValue = sf_SendByte(DUMMY_BYTE);
        if (*_ucpTar++ != ucValue)
        {
            SF_CS_HIGH();
            return 1;
        }
    }
    SF_CS_HIGH();
    return 0;
}



/*
*********************************************************************************************************
*    函 数 名: sf_ReadID
*    功能说明: 读取器件ID
*    形    参:  无
*    返 回 值: 32bit的器件ID (最高8bit填0,有效ID位数为24bit)
*********************************************************************************************************
*/
uint32_t sf_ReadID(void)
{
    uint32_t uiID;
    uint8_t id1, id2, id3;

    SF_CS_LOW();                                              /* 使能片选 */
    sf_SendByte(CMD_RDID);                                 /* 发送读ID命令 */
    id1 = sf_SendByte(DUMMY_BYTE);                    /* 读ID的第1个字节 */
    id2 = sf_SendByte(DUMMY_BYTE);                    /* 读ID的第2个字节 */
    id3 = sf_SendByte(DUMMY_BYTE);                    /* 读ID的第3个字节 */
    SF_CS_HIGH();                                              /* 禁能片选 */

    uiID = ((uint32_t)id1 << 16) | ((uint32_t)id2 << 8) | id3;

    return uiID;
}

/*
*********************************************************************************************************
*    函 数 名: sf_ReadInfo
*    功能说明: 读取器件ID,并填充器件参数
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_ReadInfo(void)
{
    /* 自动识别串行Flash型号 */
    {
        g_tSF.ChipID = sf_ReadID();    /* 芯片ID */

        switch (g_tSF.ChipID)
        {
            case SST25VF016B_ID:
                strcpy(g_tSF.ChipName, "SST25VF016B");
                g_tSF.TotalSize = 2 * 1024 * 1024;      /* 总容量 = 2M */
                g_tSF.SectorSize = 4 * 1024;                  /* 页面大小 = 4K */
                g_tSF.PageSize   = 256;
                break;

            case MX25L1606E_ID:
                strcpy(g_tSF.ChipName, "MX25L1606E");
                g_tSF.TotalSize = 2 * 1024 * 1024;      /* 总容量 = 2M */
                g_tSF.SectorSize = 4 * 1024;                /* 页面大小 = 4K */
             g_tSF.PageSize   = 256;
                break;

            case W25Q64BV_ID:
                strcpy(g_tSF.ChipName, "W25Q64BV");
                g_tSF.TotalSize = 8 * 1024 * 1024;      /* 总容量 = 8M */
                g_tSF.SectorSize = 4 * 1024;                /* 页面大小 = 4K */
              g_tSF.PageSize   = 256;
                break;

            default:
                strcpy(g_tSF.ChipName, "Unknow Flash");
                g_tSF.TotalSize = 2 * 1024 * 1024;
                g_tSF.SectorSize = 4 * 1024;
              g_tSF.PageSize   = 256;
                break;
        }
    }
}

void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
  /*!< Enable the write access to the FLASH */
    sf_WriteEnable();                                    /* 发送写使能命令 */

  /*!< Select the FLASH: Chip Select low */
  SF_CS_LOW();                
  /*!< Send "Write to Memory " instruction */
  sf_SendByte(CMD_WPAGE);
  /*!< Send WriteAddr high nibble address byte to write to */
  sf_SendByte((WriteAddr & 0xFF0000) >> 16);
  /*!< Send WriteAddr medium nibble address byte to write to */
  sf_SendByte((WriteAddr & 0xFF00) >> 8);
  /*!< Send WriteAddr low nibble address byte to write to */
  sf_SendByte(WriteAddr & 0xFF);

  /*!< while there is data to be written on the FLASH */
  while (NumByteToWrite--)
  {
    /*!< Send the current byte */
    sf_SendByte(*pBuffer);
    /*!< Point on the next byte to be written */
    pBuffer++;
  }

  /*!< Deselect the FLASH: Chip Select high */
    SF_CS_HIGH();    

  /*!< Wait the end of Flash writing */
    sf_WaitForWriteEnd();        
}

void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
  uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % sFLASH_SPI_PAGESIZE;
  count = sFLASH_SPI_PAGESIZE - Addr;
  NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
  NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

  if (Addr == 0)             /*!< WriteAddr is sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      while (NumOfPage--)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else /*!< WriteAddr is not sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */
      {
        temp = NumOfSingle - count;

        sFLASH_WritePage(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;

        sFLASH_WritePage(pBuffer, WriteAddr, temp);
      }
      else
      {
        sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
      NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

      sFLASH_WritePage(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;

      while (NumOfPage--)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      if (NumOfSingle != 0)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

/*
*********************************************************************************************************
*    函 数 名: sf_SendByte
*    功能说明: 向器件发送一个字节,同时从MISO口线采样器件返回的数据
*    形    参:  _ucByte : 发送的字节值
*    返 回 值: 从MISO口线采样器件返回的数据
*********************************************************************************************************
*/
static uint8_t sf_SendByte(uint8_t _ucValue)
{
    /* 等待上个数据未发送完毕 */
    while (SPI_I2S_GetFlagStatus(SPI_FLASH_PORT, SPI_I2S_FLAG_TXE) == RESET);

    /* 通过SPI硬件发送1个字节 */
    SPI_I2S_SendData(SPI_FLASH_PORT, _ucValue);

    /* 等待接收一个字节任务完成 */
    while (SPI_I2S_GetFlagStatus(SPI_FLASH_PORT, SPI_I2S_FLAG_RXNE) == RESET);

    /* 返回从SPI总线读到的数据 */
    return SPI_I2S_ReceiveData(SPI_FLASH_PORT);
}

/*
*********************************************************************************************************
*    函 数 名: sf_WriteEnable
*    功能说明: 向器件发送写使能命令
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
static void sf_WriteEnable(void)
{
    SF_CS_LOW();                                    /* 使能片选 */
    sf_SendByte(CMD_WREN);              /* 发送命令 */
    SF_CS_HIGH();                                    /* 禁能片选 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_WriteStatus
*    功能说明: 写状态寄存器
*    形    参:  _ucValue : 状态寄存器的值
*    返 回 值: 无
*********************************************************************************************************
*/
static void sf_WriteStatus(uint8_t _ucValue)
{

    if (g_tSF.ChipID == SST25VF016B_ID)
    {
        /* 第1步:先使能写状态寄存器 */
        SF_CS_LOW();                                    /* 使能片选 */
        sf_SendByte(CMD_EWRSR);              /* 发送命令, 允许写状态寄存器 */
        SF_CS_HIGH();                                    /* 禁能片选 */

        /* 第2步:再写状态寄存器 */
        SF_CS_LOW();                                    /* 使能片选 */
        sf_SendByte(CMD_WRSR);                /* 发送命令, 写状态寄存器 */
        sf_SendByte(_ucValue);              /* 发送数据:状态寄存器的值 */
        SF_CS_HIGH();                                    /* 禁能片选 */
    }
    else
    {
        SF_CS_LOW();                                    /* 使能片选 */
        sf_SendByte(CMD_WRSR);              /* 发送命令, 写状态寄存器 */
        sf_SendByte(_ucValue);                /* 发送数据:状态寄存器的值 */
        SF_CS_HIGH();                                    /* 禁能片选 */
    }
}

/*
*********************************************************************************************************
*    函 数 名: sf_WaitForWriteEnd
*    功能说明: 采用循环查询的方式等待器件内部写操作完成
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
static void sf_WaitForWriteEnd(void)
{
    SF_CS_LOW();                                         /* 使能片选 */
    sf_SendByte(CMD_RDSR);                         /* 发送命令, 读状态寄存器 */
    while((sf_SendByte(DUMMY_BYTE) & WIP_FLAG) == SET);    /* 判断状态寄存器的忙标志位 */
    SF_CS_HIGH();                                        /* 禁能片选 */
}

/* End of this file */

2)创建bsp_spi_flash.h文件,实现如下代码

#ifndef _BSP_SPI_FLASH_H
#define _BSP_SPI_FLASH_H

#include "stm32f4xx.h"
#include <stdio.h>
#include <string.h>

#define sFLASH_SPI_PAGESIZE       0x100

/* 定义串行Flash ID */
enum
{
    SST25VF016B_ID = 0xBF2541,
    MX25L1606E_ID  = 0xC22015,
    W25Q64BV_ID    = 0xEF4017
};


typedef struct
{
    uint32_t ChipID;          /* 芯片ID */
    char ChipName[16];        /* 芯片型号字符串,主要用于显示 */
    uint32_t TotalSize;       /* 总容量 */
    uint16_t SectorSize;      /* sector size */
    uint16_t PageSize;        /* 页面大小 */
}SFLASH_T;

extern SFLASH_T g_tSF;

void bsp_InitSFlash(void);
uint32_t sf_ReadID(void);
void sf_EraseChip(void);
void sf_EraseSector(uint32_t _uiSectorAddr);
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize);
uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);
void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);


void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);



#endif    /* _BSP_SPI_FLASH_H */

2.3 Flash loader驱动接口程序

在Loader_Src.c程序中实现如下代码

#include "bsp_spi_flash.h"

/**
  * Description :
  * Initilize the MCU Clock, the GPIO Pins corresponding to the
  * device and initilize the FSMC with the chosen configuration 
  * Inputs    :
  *      None
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *                             "0"             : Operation failure
  * Note: Mandatory for all types of device 
  */
int Init (void)
{  
    /* Set MCU Clock */
    SystemInit();
    bsp_InitSFlash();
    return 1;
}

/**
  * Description :
  * Read data from the device 
  * Inputs    :
  *      Address       : Write location
  *      Size          : Length in bytes  
  *      buffer       : Address where to get the data to write
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *               "0"             : Operation failure
  * Note: Mandatory for all types except SRAM and PSRAM    
  */
int Read (uint32_t Address, uint32_t Size, uint8_t* Buffer)
{    
    sf_ReadBuffer(Buffer, Address, Size);
    return 1;
} 

/**
  * Description :
  * Write data from the device 
  * Inputs    :
  *      Address       : Write location
  *      Size          : Length in bytes  
  *      buffer        : Address where to get the data to write
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *               "0"             : Operation failure
  * Note: Mandatory for all types except SRAM and PSRAM    
  */
int Write (uint32_t Address, uint32_t Size, uint8_t* Buffer)
{  
    sFLASH_WriteBuffer(Buffer, Address, Size);
    return 1;
} 

/**
  * Description :
  * Erase a full sector in the device
  * Inputs    :
  *     None
  * outputs   :
  *     R0             : "1" : Operation succeeded
  *              "0" : Operation failure
  * Note: Not Mandatory for SRAM PSRAM and NOR_FLASH
  */
int MassErase (void)
{  
    sf_EraseChip();
    return 1;    
}

/**
  * Description :
  * Erase a full sector in the device
  * Inputs    :
  *      SectrorAddress    : Start of sector
  *      Size          : Size (in WORD)  
  *      InitVal       : Initial CRC value
  * outputs   :
  *     R0             : "1" : Operation succeeded
  *              "0" : Operation failure
  * Note: Not Mandatory for SRAM PSRAM and NOR_FLASH
  */
int SectorErase (uint32_t EraseStartAddress ,uint32_t EraseEndAddress)
{      
  EraseStartAddress = EraseStartAddress -  EraseStartAddress%0x10000;
  while (EraseEndAddress>=EraseStartAddress)
  {
    sf_EraseSector(EraseStartAddress);
    EraseStartAddress += 0x10000;
  }
    
  return 1;    
}


/**
  * Description :
  * Calculates checksum value of the memory zone
  * Inputs    :
  *      StartAddress  : Flash start address
  *      Size          : Size (in WORD)  
  *      InitVal       : Initial CRC value
  * outputs   :
  *     R0             : Checksum value
  * Note: Optional for all types of device
  */
uint32_t CheckSum(uint32_t StartAddress, uint32_t Size, uint32_t InitVal)
{
    uint8_t missalignementAddress = StartAddress%4;
    uint8_t missalignementSize = Size ;
    int cnt;
    uint32_t Val;
    uint8_t value;
    
    StartAddress-=StartAddress%4;
    Size += (Size%4==0)?0:4-(Size%4);

    for(cnt=0; cnt<Size ; cnt+=4)
    {
        sf_ReadBuffer(&value, StartAddress ,1);
        Val = value;
        sf_ReadBuffer(&value, StartAddress + 1,1);
        Val+= value<<8;
        sf_ReadBuffer(&value, StartAddress + 2,1);
        Val+= value<<16;
        sf_ReadBuffer(&value, StartAddress + 3,1);
        Val+= value<<24;
        
        if(missalignementAddress)
        {
              switch (missalignementAddress)
              {
                case 1:
                  InitVal += (uint8_t) (Val>>8 & 0xff);
                  InitVal += (uint8_t) (Val>>16 & 0xff);
                  InitVal += (uint8_t) (Val>>24 & 0xff);
                  missalignementAddress-=1;
                  break;
                case 2:
                  InitVal += (uint8_t) (Val>>16 & 0xff);
                  InitVal += (uint8_t) (Val>>24 & 0xff);
                  missalignementAddress-=2;
                  break;
                case 3:   
                  InitVal += (uint8_t) (Val>>24 & 0xff);
                  missalignementAddress-=3;
                  break;
              }  
        }
        else if((Size-missalignementSize)%4 && (Size-cnt) <=4)
        {
              switch (Size-missalignementSize)
              {
                case 1:
                  InitVal += (uint8_t) Val;
                  InitVal += (uint8_t) (Val>>8 & 0xff);
                  InitVal += (uint8_t) (Val>>16 & 0xff);
                  missalignementSize-=1;
                  break;
                case 2:
                  InitVal += (uint8_t) Val;
                  InitVal += (uint8_t) (Val>>8 & 0xff);
                  missalignementSize-=2;
                  break;
                case 3:   
                  InitVal += (uint8_t) Val;
                  missalignementSize-=3;
                  break;
              } 
        }
        else
        {
              InitVal += (uint8_t) Val;
              InitVal += (uint8_t) (Val>>8 & 0xff);
              InitVal += (uint8_t) (Val>>16 & 0xff);
              InitVal += (uint8_t) (Val>>24 & 0xff);
        }
        StartAddress+=4;
    }

    return (InitVal);
}


/**
  * Description :
  * Verify flash memory with RAM buffer and calculates checksum value of
  * the programmed memory
  * Inputs    :
  *      FlashAddr     : Flash address
  *      RAMBufferAddr : RAM buffer address
  *      Size          : Size (in WORD)  
  *      InitVal       : Initial CRC value
  * outputs   :
  *     R0             : Operation failed (address of failure)
  *     R1             : Checksum value
  * Note: Optional for all types of device
  */
uint64_t Verify (uint32_t MemoryAddr, uint32_t RAMBufferAddr, uint32_t Size, uint32_t missalignement)
{
    uint32_t InitVal = 0;
    uint32_t VerifiedData = 0;
    uint8_t TmpBuffer = 0x00;
    uint64_t checksum;
    Size*=4;
        
    checksum = CheckSum((uint32_t)MemoryAddr + (missalignement & 0xf), Size - ((missalignement >> 16) & 0xF), InitVal);

    while (Size>VerifiedData)
    {
        sf_ReadBuffer(&TmpBuffer, MemoryAddr+VerifiedData, 1);
             
        if (TmpBuffer != *((uint8_t*)RAMBufferAddr+VerifiedData))
          return ((checksum<<32) + MemoryAddr+VerifiedData);
            
        VerifiedData++;  
    }
       
    return (checksum<<32);
}

3 Keil中的参数配置

3.1 配置参数

1)选择MCU内核

2)输出文件的名称

3)复制输出文件的位置

cmd.exe /C copy "!L" "..\..\..\@L.stldr"

4)编译的.c文件配置为Read-only

5)编译的.a文件配置为Read-only

 6) 配置link文件

 3.2 编译

在Keil完成以上配置之后,就可以编译代码,编译成功后会生成一个.stldr文件,该文件就是flashloader。

4 测试

4.1 准备.stldr文件

编译SPI NOR 源代码,并得到.stldr文件,将该文件放在STM32Cube\STM32CubeProgrammer\bin\ExternalLoader

4.2  STM32CubeProgrammer中测试Flash loader

1)打开STM32CubeProgrammer,加载.stldr文件

2)擦除芯片内的数据

 3)确认芯片内的数据已经被擦除

4)烧写测试文件

 5)读出写入的文件

通过比较二者文件,其完全一样。

标签:CS,Flash,SF,SPI,SendByte,stm32f4,GPIO,sf
From: https://blog.csdn.net/mftang/article/details/140032478

相关文章

  • IMX6ULL开发板spi OLED驱动
    本文是IMX6ULL开发板spiOLED驱动学习笔记,方便后面查看时快速的回顾,而不需要一点点的看视频视频地址:https://www.bilibili.com/video/BV1Yb4y1t7Uj?p=144&spm_id_from=pageDriver&vd_source=1d93d6a5e22d4b223c6c3ac4f5727eb8视频选集:P141-P1501、将文件上传到虚拟机共享目......
  • flask 闪现(flash)
    闪现(flash)flask中得闪现存放数据的地方,一旦取了,数据就没了实现跨请求间传递数据基本使用设置:flash('欢迎你')取:get_flashed_messages()fromflaskimportFlask,request,render_template,redirect,flash,get_flashed_messagesapp=Flask(__name__)app.debug......
  • Flash均衡读写
    #defineFLASH_INITIAL_BYTE0xff #definePAGE_NUM_PER_CONFIG       2 #defineCONFIG_FLASH_PAGE_START     508#defineCONFIG_BUF_SIZEsizeof(CFG_CHARGE_ST)/2#define  CONFIG_FLASH_ADDRESS_START......
  • STM32通过SPI硬件读写W25Q64
    文章目录1. W25Q642.硬件电路3. 软件/硬件波形对比4.STM32中的SPI外设5.代码实现5.1MyI2C.c5.2 MyI2C.h5.3W25Q64.c5.4 W25Q64.h5.5 W25Q64_Ins.h5.6main.c1. W25Q64对于SPI通信和W25Q64的详细解析可以看下面这篇文章STM32单片机SPI通信详解-CSDN......
  • STM32通过SPI软件读写W25Q64
    文章目录1.W25Q642.硬件电路3. W25Q64框架图4. 软件/硬件波形对比5.代码实现5.1MyI2C.c5.2 MyI2C.h5.3W25Q64.c5.4 W25Q64.h5.5 W25Q64_Ins.h5.6main.c1.W25Q64对于SPI通信和W25Q64的详细解析可以看下面这篇文章STM32单片机SPI通信详解-CSDN博客......
  • 通讯协议大全(UART,RS485,SPI,IIC)
    参考自: 常见的通讯协议总结(USART、IIC、SPI、485、CAN)-CSDN博客UART那么好用,为什么单片机还需要I2C和SPI?_哔哩哔哩_bilibili5分钟看懂!串口RS232RS485最本质的区别!_哔哩哔哩_bilibili喜欢几位博主老师老师的还请看原贴/原视频数据通信 数据通信是指通过某种传......
  • [题解]AT_abc236_f [ABC236F] Spices
    思路首先对所有的\(c\)从小到大排序,然后对于每一个值如果之前能凑出就不选,否则就选。这样做显然是对的。令\(p_1,p_2,\dots,p_{2^n-1}\)表示将\(c\)排序之后,对应原来的下标;\(S\)表示选出数的集合;\(S'\)表示最终选出数的集合。可以证明两个问题:如果\(p_i\)可以被已选......
  • STM32单片机SPI通信详解
    文章目录1.SPI通信概述2.硬件电路3.移位示意图4.SPI时序基本单元5.SPI时序6.Flash操作注意事项7.SPI外设简介8.SPI框图9.SPI基本结构10. 主模式全双工连续传输11. 非连续传输12. 软件/硬件波形对比13.代码示例1.SPI通信概述SPI(SerialPeriphera......
  • 【SPIE出版】第六届无线通信与智能电网国际会议(ICWCSG 2024,7月26-28)
    随着科技的飞速发展和能源需求的日益增长,智能电网技术逐渐成为电力行业的重要发展方向。与此同时,无线通信技术在近年来也取得了显著的进步,为智能电网的发展提供了强有力的支持。为了进一步推动无线通信与智能电网的结合与发展,第六届无线通信与智能电网国际会议(ICWCSG2024......
  • Cannot generate SSPI context
         1.分析  1)获取服务器的SPN    a)通过SQLCheck.exe(需要到微软官网进行下载)    b)通过setspn工具  2)判断SPN是否正确,如果不正确则需要矫正SPN    a)如果好的连接找不到任何SPN则会使用NLTM    b)如果好的连接能找到......