目录
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 MCU | STM32F407IGTx |
NOR Flash | W25Q64BVSSI |
Keil | MDK ARM 5.38 |
调试工具:st-link | ST-LINK/V2-1 |
STM32CubeProgrammer | v2.16.0 |
1.2 NOR Flash芯片(W25Q64BVSSI)
1.2.1 W25Q64BVSSI芯片介绍
W25Q64BVSSI是一款容量为64Mb(8MB)的串行闪存存储器,由Winbond公司生产。它采用了串行外围设备接口(SPI),可用于存储嵌入式系统的代码和数据。
W25Q64BVSSI具有以下特点:
- 容量大:64Mb的存储容量可以存储大量的代码和数据。
- 高速访问:采用SPI接口,支持快速的读取和写入操作,具有快速的数据传输速度。
- 低功耗:W25Q64BVSSI采用低功耗设计,可以有效地节省系统能源。
- 可编程:可以通过软件进行编程和擦除操作,方便灵活的存储管理。
- 高可靠性:具有内置的错误检测和纠正机制,可以提高存储器的可靠性和数据完整性。
- 多种封装形式: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 = 84MSPI2、SPI3 在 PCLK1, 时钟42M
SPI1 在 PCLK2, 时钟84MSTM32F4 支持的最大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_CSSTM32硬件SPI接口 = SPI3 或者 SPI1,由于SPI1的时钟源是84M, SPI3的时钟源是42M。为了获得更快的速度,软件上选择SPI1。
MCU接口 | NOR Flash |
---|---|
GPIOF_PIN6 | CS |
GPIOB_PIN3 | SCK |
GPIOB_PIN4 | MISO |
GPIOB_PIN5 | MOSI |
硬件电路图如下:
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)读出写入的文件
通过比较二者文件,其完全一样。