随言:
在外地出差,闲着也是闲着,写写笔记。
STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64:STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_sudaroot的博客-优快云博客_qspi内存映射
MDK KEIL 下载算法外部存储QSPI FLASH:MDK KEIL 下载算法程序 外部存储QSPI FLASH_sudaroot的博客-优快云博客
STM32CubeProgrammer STM32CubeIDE下载算法 外部存储QSPI Flash:STM32CubeProgrammer STM32CubeIDE下载算法 外部存储QSPI Flash_sudaroot的博客-优快云博客
理论来说,STM32CubeProgrammer和 STM32 ST-LINK Utility下载算法程序是一样的,都能相互使用。
但是不知道为什么,STM32CubeProgrammer调用外部存储下载程序没有问题,STM32 ST-LINK Utility调用却失败。
不过也没关系了。不过没关系了,生产就用STM32CubeProgrammer,开发就用STM32CubeIDE。
所以一开始我是以STM32 ST-LINK Utility的例程去编程,然后无论我怎么测试都不行。但是我把生成的stldr下载算法文件放到
STM32CubeProgrammer这个软件试试,结果能正常下载,真的是耽误了我不少时间。
至于STM32 ST-LINK Utility为什么不行,搜索了一下,都没找解决相关问题的资料,故我放弃了,改用STM32CubeProgrammer。
结束:
下面是已经编辑好的源码,
工程有HAL库版本的,寄存器版本的(移植某点原子)。
可以根据自己的硬件自行更改。
附上链接:STM32CubeProgrammerFlashAlgorithm.rar_stm32cubeide外部flash下载-嵌入式文档类资源-优快云下载
硬件:STM32H743IIT6 + W25Q64
/**QUADSPI GPIO Configuration
PF6 ------> QUADSPI_BK1_IO3
PF7 ------> QUADSPI_BK1_IO2
PF8 ------> QUADSPI_BK1_IO0
PF9 ------> QUADSPI_BK1_IO1
PB2 ------> QUADSPI_CLK
PB6 ------> QUADSPI_BK1_NCS
正文:
1、资料
文档:
《UM0892 User manual STM32 ST-LINK utility software description》
例程:
我是用STM32 ST-LINK Utility安装路径下的N25Q512A_STM32F769I-EVAL,因为这个是用HAL库写的。
C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ExternalLoader\N25Q512A_STM32F769I-EVAL
2、Dev_Inf.c file
该文件中定义了存储信息结构。
下面显示了此构造函数定义的信息类型的示例:
#if defined (__ICCARM__)
__root struct StorageInfo const StorageInfo = {
#else
struct StorageInfo const StorageInfo = {
#endif
"External_Loader_Name", // 设备名 + 版本号
MCU_FLASH, // 设备类型
0x08000000, // 设备开始地址
0x00100000, // 设备大小 (1MBytes/8Mbits)
0x00004000, // 页编程大小 16KBytes
0xFF, // 擦除后的默认值
// 指定扇区的大小和地址(下面的示例)
0x00000004, 0x00004000, // Sector Num : 4 ,Sector Size: 16KBytes
0x00000001, 0x00010000, // Sector Num : 1 ,Sector Size: 64KBytes
0x00000007, 0x00020000, // Sector Num : 7 ,Sector Size: 128KBytes
0x00000000, 0x00000000, // 结束
比如现在用的是W25Q64 是QSPI FLASH芯片,大小是8MByte,编程页大小是256字节,最小擦除单位是4KB。
下面我在编程页写4096字节,是为了提高写入的效率。
/* This structure containes information used by ST-LINK Utility to program and erase the device */
#if defined (__ICCARM__)
__root struct StorageInfo const StorageInfo =
{
#else
struct StorageInfo const StorageInfo =
{
#endif
"STM32H743_W25Q64_REG", // Device Name + version number
SPI_FLASH, // Device Type
0x90000000, // Device Start Address
0x00800000, // Device Size in Bytes (8MBytes/64Mbits)
0x1000, // Programming Page Size 4096Bytes
0xFF, // Initial Content of Erased Memory
// Specify Size and Address of Sectors (view example below)
0x00000800, 0x00001000, // Sector Num : 2048 ,Sector Size: 4KBytes
0x00000000, 0x00000000,
};
3、Loader_Src.c file
- int Init (void):Init函数,定义用于连接到外部存储器的GPIO,进行初始化,使用的IP的时钟。
- int Write (uint32_t Address, uint32_t Size, uint8_t* buffer):它对用RAM范围内的地址定义的缓冲区进行编程。
- int SectorErase (uint32_t StartAddress, uint32_t EndAddress):它擦除由起始地址和结束地址定义的存储扇区。
- int Read (uint32_t Address, uint32_t Size, uint16_t* buffer):其中“地址” =读取操作的起始地址,“大小” =读取操作的大小,“缓冲区” =指向读取数据的指针。注意:对于QSPI / OSPI(Quad-SPI / Octo-SPI)存储器,可以在Init函数中定义存储器映射模式,在这种情况下,Read函数无效。
- uint64_t Verify (uint32_t FlashAddr, uint32_t RAMBufferAddr, uint32_t Size):选择“编程时验证”模式时将调用此功能。这个功能检查编程的存储器是否对应于存储器中定义的缓冲区内存。
- int MassErase (void):全部擦除,删除全部内存。
4、编程:
这次移植一下寄存器版本。HAL版本移植很简单,把缺失的文件补上,然后上根据硬件修改QSPI参数。
1、先把STM32 ST-LINK utility安装文件夹下的例程复制到桌面,重命名为test。
C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ExternalLoader\N25Q512A_STM32F769I-EVAL
2、重新选择芯片设备。
把红框的文件都删除了。
找到正点原子的寄存器QSPI测试代码。把下面文件夹的代码复制到我们的test文件夹,添加到工程中。
Dev_Inf.c 上面写出来了,下面是Loader_Src.c
#include "sys.h"
#include "sys.h"
#include "delay.h"
#include "w25qxx.h"
#include "led.h"
#include "qspi.h"
#include "usart.h"
#define SPI_FLASH_CHECK 0x0FFFFFFF
/**
* 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)
{
__disable_irq();
Stm32_Clock_Init(100, 1, 2, 2); //设置时钟,400Mhz
delay_init(400); //延时初始化
W25QXX_Init(); //初始化W25QXX
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)
{
Address = Address & SPI_FLASH_CHECK;
W25QXX_Read(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)
{
Address = Address & SPI_FLASH_CHECK;
W25QXX_Write_NoCheck(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)
{
W25QXX_Erase_Chip();
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 & SPI_FLASH_CHECK;
EraseEndAddress = EraseEndAddress & SPI_FLASH_CHECK;
EraseStartAddress = EraseStartAddress - EraseStartAddress % W25QxJV_SUBSECTOR_SIZE; //Erase 4KBytes
while (EraseEndAddress >= EraseStartAddress)
{
W25QXX_Erase_Sector(EraseStartAddress);
EraseStartAddress += W25QxJV_SUBSECTOR_SIZE; //Erase 4KBytes
}
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 & SPI_FLASH_CHECK;
StartAddress -= StartAddress % 4;
Size += (Size % 4 == 0) ? 0 : 4 - (Size % 4);
for(cnt = 0; cnt < Size ; cnt += 4)
{
W25QXX_Read(&value, StartAddress, 1);
Val = value;
W25QXX_Read(&value, StartAddress + 1, 1);
Val += value << 8;
W25QXX_Read(&value, StartAddress + 2, 1);
Val += value << 16;
W25QXX_Read(&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;
MemoryAddr = MemoryAddr & SPI_FLASH_CHECK;
checksum = CheckSum((uint32_t)MemoryAddr + (missalignement & 0xf), Size - ((missalignement >> 16) & 0xF), InitVal);
while (Size > VerifiedData)
{
W25QXX_Read(&TmpBuffer, MemoryAddr + VerifiedData, 1);
if (TmpBuffer != *((uint8_t*)RAMBufferAddr + VerifiedData))
return ((checksum << 32) + MemoryAddr + VerifiedData);
VerifiedData++;
}
return (checksum << 32);
}
在当前工程文件夹中生成stldr文件,cmd.exe /C copy "!L" ".\@L.stldr"
把生成好的stldr放到STM32CubeProgrammer安装文件夹的ExternalLoader里面。
打开STM32CubeProgrammer软件。如下图操作。
如果连接不上0x90000000,尝试多几次连接。
如果下载校验都成功,那么就成功了。
如果是使用STM32CubeIDE下载,则先要把stldr文件放到
C:\ ST \ STM32CubeIDE_1.3.0 \ STM32CubeIDE \ plugins \ com.st.stm32cube.ide.mcu.externaltools.cubeprogrammer.win32_1.3.0.202002181050 \ tools \ bin \ ExternalLoader \
打开STM32CubeIDE软件,修改下载配置
如果擦除下载和校验都顺利的话,那么就成功了。
但是程序肯定不会运行的,缺少一个启动程序MemBoot。
同理,仿真也不行,必须有启动程序MemBoot才能仿真。
移植到其他芯片
如果移植到其他芯片的话,不是和我使用的H743一致,只需按照下面修改即可。
比如我借了一块的H750板子,下面是引脚分配图。FLASH芯片也是W25Q64
PE2 ------> QUADSPI_BK1_IO2
PB2 ------> QUADSPI_CLK
PD11 ------> QUADSPI_BK1_IO0
PD12 ------> QUADSPI_BK1_IO1
PD13 ------> QUADSPI_BK1_IO3
PB6 ------> QUADSPI_BK1_NCS
以W25Q64_STM32H743I_HAL工程这个代码为例。
1、修改设备芯片STM32H750VB
2、修改QSPI引脚,因为H750板子还是W25Q64,并不是其他的芯片,故W25Q64驱动无需改动,否则改之。
把回调函数void HAL_QSPI_MspInit(QSPI_HandleTypeDef* hqspi);
void HAL_QSPI_MspDeInit(QSPI_HandleTypeDef* hqspi)引脚修改完成即可。
3、编译,放到STM32CubeProgrammer安装文件夹的ExternalLoader里面文件夹下面。
4、测试下载。
5、如果不成功有可能void SystemClock_Config(void)这个时钟函数有问题,
我一般是用Cube系列软件生成的,再粘贴过来。
全篇完。
本人是一个嵌入式未入门小白,博客仅仅代表我个人主观见解,记录成长笔记。
笔记是以最简单的方式,只展示最核心的原理。
若有与 大神大大 见解有歧义,我绝对坚信 大神大大 见解是对的,我的是错的。
若无积分等无法下载源码,可加入QQ群657407920下载交流经验。感谢~!