- #define SFUD_USING_SFDP
- enum {
- SFUD_M25P_DEVICE_INDEX = 0,
- };
- #define SFUD_FLASH_DEVICE_TABLE \
- { \
- [SFUD_M25P_DEVICE_INDEX] = {.name = "M25P80", .spi.name = "SPI2"}, \
- }
需要适配平台的接口文件 sfud_port.c 中的 sfud_err sfud_spi_port_init(sfud_flash *flash) 方法,它是库提供的底层接口操作函数,在里面完成各个设备 SPI 读写驱动(必选)、重试次数(必选)、重试接口(可选)及 SPI 锁(可选)的配置。其中,SPI 读写驱动使用的是轮询方式,SPI 锁驱动为操作总中断的开启与关断,如果要开启调试的话则需要适配好串口打印信息的接口,具体代码如下:
- /**
- * add your spi flash user data struct typedef
- */
- typedef struct {
- SPI_TypeDef *spix;
- GPIO_TypeDef *cs_gpiox;
- uint16_t cs_gpio_pin;
- } spi_user_data, *spi_user_data_t;
- static spi_user_data spi2 = { .spix = SPI2, .cs_gpiox = GPIOB, .cs_gpio_pin = GPIO_Pin_12 };
- static char log_buf[256];
- void sfud_log_debug(const char *file, const long line, const char *format, ...);
- /**
- * SPI write data then read data
- */
- static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
- size_t read_size) {
- sfud_err result = SFUD_SUCCESS;
- uint8_t send_data, read_data;
- spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
- if (write_size) {
- SFUD_ASSERT(write_buf);
- }
- if (read_size) {
- SFUD_ASSERT(read_buf);
- }
- // GPIO_ResetBits(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin);/* 软件NSS */
- SPI_CSInternalSelected(SPI2, ENABLE);/* 硬件NSS */
- /* 开始读写数据 */
- for (size_t i = 0, retry_times; i < write_size + read_size; i++) {
- /* 先写缓冲区中的数据到 SPI 总线,数据写完后,再写 dummy(0xFF) 到 SPI 总线 */
- if (i < write_size) {
- send_data = *write_buf++;
- } else {
- send_data = SFUD_DUMMY_DATA;
- }
- /* 发送数据 */
- retry_times = 1000;
- while (SPI_GetFlagStatus(spi_dev->spix, SPI_FLAG_TXEPT) == RESET) {
- SFUD_RETRY_PROCESS(NULL, retry_times, result);
- }
- if (result != SFUD_SUCCESS) {
- goto exit;
- }
- SPI_SendData(spi_dev->spix, send_data);
-
- /* 接收数据 */
- retry_times = 1000;
- while (SPI_GetFlagStatus(spi_dev->spix, SPI_FLAG_RXAVL) == RESET) {
- SFUD_RETRY_PROCESS(NULL, retry_times, result);
- }
- if (result != SFUD_SUCCESS) {
- goto exit;
- }
- read_data = SPI_ReceiveData(spi_dev->spix);
- /* 写缓冲区中的数据发完后,再读取 SPI 总线中的数据到读缓冲区 */
- if (i >= write_size) {
- *read_buf++ = read_data;
- }
- }
- exit:
- // GPIO_SetBits(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin);
- SPI_CSInternalSelected(SPI2, DISABLE);
-
- return result;
- }
- /**
- * add your spi flash user function
- */
- /* about 100 microsecond delay */
- static void retry_delay_100us(void) {
- uint32_t delay = 80; /* 120 [url=home.php?mod=space&uid=442618]@72mhz[/url] */
- while(delay--);
- }
- static void spi_lock(const sfud_spi *spi) {
- __disable_irq();
- }
- static void spi_unlock(const sfud_spi *spi) {
- __enable_irq();
- }
- sfud_err sfud_spi_port_init(sfud_flash *flash) {
- sfud_err result = SFUD_SUCCESS;
- switch (flash->index) {
- case SFUD_M25P_DEVICE_INDEX: {
- SPI_FLASH_Init();
- /* 同步 Flash 移植所需的接口及数据 */
- flash->spi.wr = spi_write_read;
- flash->spi.lock = spi_lock;
- flash->spi.unlock = spi_unlock;
- flash->spi.user_data = &spi2;
- /* about 100 microsecond delay */
- flash->retry.delay = retry_delay_100us;
- /* adout 60 seconds timeout */
- flash->retry.times = 60 * 10000;
- break;
- }
- }
- return result;
- }
有了以上的适配移植代码,基本就可以在开发板上使用 SFUD 库来操作 Flash 芯片了,在主函数中编写个简单的功能测试样例,且添加上 #include <sfud.h> 包含语句。测试读写功能前,需要调用 sfud_init() 接口来初始化接口和整个库,具体的测试代码在此不贴出来可以参照附件工程。
三、移植 Littlefs 到 SPI Flash 上
前面提到过内嵌 Flash 存储数据时通常需要考虑用均衡负载和磨损等算法来延长 Flash 的寿命,其实在 SPI Nor Flash 上同样需要考虑这些因素,除了之前提过的小组件,还有一些嵌入式文件系统比较适合用于扩展 SPI Flash 中,这里先提到 littlefs 和 filex+levelx 2种方案,下面会着重讲述前者。Littlefs 是 Mbed OS 中的高完整性嵌入式文件系统,经过优化可与有限数量的 RAM 和 ROM 一起使用。它避免了递归,将动态内存限制为可配置的缓冲区,并且绝不会将整个存储块存储在 RAM 中。通过专注于一小组多用途数据结构,这种高完整性嵌入式文件系统使用的 ROM 比 FAT 少 13K,RAM 少 4K。Littlefs 可以用在自身不带坏块处理、磨损平衡等功能的内存芯片上;同时 Littlefs 也充分考虑了异常掉电情况下的数据保护。 该组件有以下特性:
- 断电恢复。它需要强有力的保证文件系统保持一致,并且数据被刷新到底层存储。
- 磨损均衡。 通常存储支持每个块的有限擦除次数,因此使用整个存储设备对可靠性很重要。
- 占用空间小。物联网设备受到 ROM 和 RAM 的限制,占用资源小可以节省成本。
- 坏块处理。
在 Git 仓中获取到源码 :https://github.com/littlefs-project/littlefs
将获取到的源码包添加到上面的工程中,并且添加头文件路径。这里可以将前面的 FAL 、SFUD 以及 littlefs 的关系捋一下:
在适配 littlefs 的过程中,主要需要实现以下几个与平台有关的接口,分别为 Flash 的读、写、擦除以及同步,前3个是必须的,最后一个可以直接返回 LFS_ERR_OK:
- static int lfs_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size);
- static int lfs_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size);
- static int lfs_flash_erase(const struct lfs_config *c, lfs_block_t block);
- static int lfs_flash_sync(const struct lfs_config *c);
具体实现代码中使用到了 SFUD 的 API 函数,如下:
- static int lfs_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
- {
- uint32_t addr;
- addr = ((uint32_t)block << (uint32_t)12);
- addr += off;
- const sfud_flash *flash = sfud_get_device_table() + 0;
- #ifdef SFUD_DEBUG_MODE
- char message[64];
- if (Flash_Debug_Flag)
- {
- memset(message, 0x00, sizeof(message));
- sprintf(message, "Flash Read Block is %d, Offset is %d, Size is %d\n\r", block, off, size);
- debug_log(message, strlen(message));
- }
- #endif
- sfud_read(flash, addr, size, (uint8_t *)buffer);
- return LFS_ERR_OK;
- }
- static int
- lfs_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
- {
- uint32_t addr;
- addr = ((uint32_t)block << (uint32_t)12);
- addr += off;
- const sfud_flash *flash = sfud_get_device_table() + 0;
- #ifdef SFUD_DEBUG_MODE
- char message[64];
- if (Flash_Debug_Flag)
- {
- memset(message, 0x00, sizeof(message));
- sprintf(message, "Flash Prog Block is %d, Offset is %d, Size if %d\n\r", block, off, size);
- debug_log(message, strlen(message));
- }
- #endif
- sfud_write(flash, addr, size, (uint8_t *)buffer);
- return LFS_ERR_OK;
- }
- static int lfs_flash_erase(const struct lfs_config *c, lfs_block_t block)
- {
- const sfud_flash *flash = sfud_get_device_table() + 0;
- #ifdef SFUD_DEBUG_MODE
- char message[64];
- if (Flash_Debug_Flag)
- {
- memset(message, 0x00, sizeof(message));
- sprintf(message, "Erase Block is %d\n\r", block);
- debug_log(message, strlen(message));
- }
- #endif
- sfud_erase(flash, block << 12, BLOCK_SIZE);
- return LFS_ERR_OK;
- }
- static int lfs_flash_sync(const struct lfs_config *c)
- {
- return LFS_ERR_OK;
- }
移植完接口函数后,需要使用 lfs_mem_init、lfs_mount、lfs_format 这几个 API 函数来对 littlefs 文件系统进行初始化和格式化,然后编写读写文件的功能测试函数,这里只做简单评估,并未开启时间戳计算操作时间,具体代码同样见附件,可以通过板载的 TFT-LCD 直接查看测试结果:
另外,再给大家分享一个东西,一般用到文件系统了就会想到 FAT 格式的能在电脑上查看文件的,但是 littlefs 是专门为嵌入式控制器而设计的,它的文件数据需要被 MTD 后才能被电脑识别,为了简化这个流程,有大神专门设计了一个 littlefs explorer for windows 上位机工具。使用该工具的流程如下:
- 下载并安装好 osfmount.exe 文件系统挂载工具,按照 https://bluscape.blog/2019/10/01 ... r-lfse-for-windows/ 中提到的说明进行操作
2. 挂载好文件系统后之前的数据被分成了一个虚拟硬盘,可以打开 littlefs explorer for windows 上位机工具查看该盘中的文件情况
3. 可以通过该上位机查看到 littlefs 内的文件了,但不知道是否我系统不适配的原因,无法通过记事本打开该文件,这是个疑问点。后面在重新打开该工具时,发现一直会弹出一个错误警告“无法删除前面的一个盘内容,退出”,一番操作后才知道,原来是需要删除工具目录下单 Temp 文件夹即可再次开启
四、将 bin 文件烧录到 SPI Flash 上的不同方法
前面几点内容都与应用相关,接着再来聊聊与工具相关的话题。大家都知道烧写片内 Flash 时一般都是用 Jlink 等烧录工具通过 JTAG/SWD 口配合一些上位机来完成的,但如何将已有的 bin 文件烧录到外部扩展的 SPI Nor Flash 中呢?是否也可以使用 Jlink ?做为 ARM 仿真调试器,Jlink 还真能烧写 SPI Flash 和读取存储器中的数据,这里会用到 JLink 软件包含的 JFlashSPI 工具中。
Jlink 内部集成了 SPI 协议,部分接口是做为 SPI 复用功能的,具体硬件连接如下图所示:
根据 MB-039 原理图找到对应接口后正确连接好,然后启动 JFlashSPI.exe 图形化工具,接着像操作熟悉的 J-Flash 一样,连接目标,读取整个芯片信息、数据保存以及烧录数据等等操作都极为雷同,下图为正确读出的 SPI Flash 信息,还包括了生产厂商信息,当通过观看丝印不能辨别时可以用此方法确定型号:
另外一种烧录外扩 SPI Flash 的方法是 使用 KEIL MDK 中的 FLM 烧录算法在烧录程序的时候同时将 c 数组数据烧录到外部 Flash 中,在安装目录下有提供模板工程,也可以参照 ST 的实现方式。简单来说,该方案实现框图如下:
具体实现流程可以参照 ST 的模板,我这里重点说明一下几点注意事项:
- 编写 FlashPrg.c 文件的底层 SPI Flash 驱动函数时,尽可能使用寄存器方式而不用库函数,且该 SPI 底层配置是参照 MB-039 开发板制作的,如果换到其它板子上需要先看 SPI 引脚是否一致;
- FlashPrg.c 文件中的几个 API 原型函数都必须得保留,因为 MDK 在驱动烧录算法工作时会有调用,如果删掉了则会导致整个流程中断 ;
- 在分散加载文件的选择上,模板工程中的 Targetlin.sct 文件必须得用上 ;
- 在需要烧录数据的工程中,data.c 文件属性中的 Memory Assignment 设置需要与工程设置中的 ROM IROM RAM IRAM 相匹配,特别注意 default 选项需要对应起来,另外在烧录工程中需要添加上前面生成的 FLM 算法且还可以指定数据需要烧录的位置 ;
- 需要先使用 FLM data 工程进行烧录数据然后再运行前面的 sfud_littlefs 工程来验证烧录算法是否起到作用,也可以将这 2 个工程合并为一个工程 ;
- 该 SPI Flash 能够被烧录 APP 代码,但是不能像 FSMC Nor Flash 一样能够在其中运行代码,因为与 AHB 不在同一总线上不能通过片内 flash 的 BootLoader 跳到片外 SPI Flash 去运行,除非是支持 XIP 的 QSPI 总线形式的外扩 Flash 。
- const unsigned char flm_data[4*1024] 这种定义数组的形式需要注意如果未别代码使用,会有被编译器优化的可能 ;
下面是烧录算法工程中的 FlashPrg.c 和 FlashDev.c 实现代码:
- <blockquote>#include "FlashOS.H" // FlashOS Structures[backcolor=rgb(255, 255, 255)]struct FlashDevice const FlashDevice = {[/backcolor]
- [backcolor=rgb(255, 255, 255)] FLASH_DRV_VERS, // Driver Version, do not modify![/backcolor]
- [backcolor=rgb(255, 255, 255)] "MM32F3270_M25P80 SPI Flash",//Device Name[/backcolor]
- [backcolor=rgb(255, 255, 255)] EXTSPI, // Device Type[/backcolor]
- [backcolor=rgb(255, 255, 255)] 0xC0000000, // Device Start Address[/backcolor]
- [backcolor=rgb(255, 255, 255)] 0x00800000, // Device Size in Bytes (8MB)[/backcolor]
- [backcolor=rgb(255, 255, 255)] 256, // Programming Page Size[/backcolor]
- [backcolor=rgb(255, 255, 255)] 0, // Reserved, must be 0[/backcolor]
- [backcolor=rgb(255, 255, 255)] 0xFF, // Initial Content of Erased Memory[/backcolor]
- [backcolor=rgb(255, 255, 255)] 6000, // Program Page Timeout 6000 mSec[/backcolor]
- [backcolor=rgb(255, 255, 255)] 6000, // Erase Sector Timeout 6000 mSec[/backcolor]
- [backcolor=rgb(255, 255, 255)]// Specify Size and Address of Sectors[/backcolor]
- [backcolor=rgb(255, 255, 255)] 0x1000, 0x000000, // Sector Size 4kB (2048 Sectors)[/backcolor]
- [backcolor=rgb(255, 255, 255)] SECTOR_END[/backcolor]
- [backcolor=rgb(255, 255, 255)]};[/backcolor]
- /******************************************************************************/
- /* This file is part of the ARM Toolchain package */
- /* Copyright KEIL ELEKTRONIK GmbH 2003 - 2008 */
- /******************************************************************************/
- /* */
- /* FlashPrg.C: Flash Programming Functions adapted for External SPI Flash */
- /* M25P80 programming with MM32F3270 MB-039 Board */
- /* */
- /******************************************************************************/
- #include "spi_flash.h"
- #include "FlashOS.H" // FlashOS Structures
- #define BUFFER_SIZE 256
- #define BLOCK_SIZE 4096
- #define BOLCK_NUM 2048 // 2M--512 8M--2048 16M--4096
- #define M8(adr) (*((vu8 *) (adr)))
- #define M16(adr) (*((vu16 *) (adr)))
- #define M32(adr) (*((vu32 *) (adr)))
- unsigned char aux_buf[256];
- unsigned long base_adr; // Base Address
- /*- Init (...) -----------------------------------------------------------------
- *
- * Initialize Flash Programming Functions
- * Parameter: adr: Device Base Address
- * clk: Clock Frequency (Hz)
- * fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
- * Return Value: 0 - OK, 1 - Failed
- */
- int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
-
- base_adr = adr;
- SPI_FLASH_Init();
- return (0);
- }
- /*- UnInit (...) ---------------------------------------------------------------
- *
- * De-Initialize Flash Programming Functions
- * Parameter: fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
- * Return Value: 0 - OK, 1 - Failed
- */
- int UnInit (unsigned long fnc) {
- // SPI_FLASH_Init();
- return (0);
- }
- /*
- * Erase complete Flash Memory
- * Return Value: 0 - OK, 1 - Failed
- */
- int EraseChip (void) {
- /* Add your Code */
- SPI_FLASH_ChipErase();
- return (0); // Finished without Errors
- }
- /*- EraseSector (...) ----------------------------------------------------------
- *
- * Erase Sector in Flash Memory
- * Parameter: adr: Sector Address
- * Return Value: 0 - OK, 1 - Failed
- */
- int EraseSector (unsigned long adr)
- {
- SPI_FLASH_SectorErase((adr-base_adr));
-
- return (0); // Done
- }
- /*- BlankCheck (...) -----------------------------------------------------------
- *
- * Blank Check Checks if Memory is Blank
- * Parameter: adr: Block Start Address
- * sz: Block Size (in bytes)
- * pat: Block Pattern
- * Return Value: 0 - OK, 1 - Failed
- */
- int BlankCheck (unsigned long adr, unsigned long sz, unsigned char pat) {
- return (1); // Always Force Erase
- }
- /*- ProgramPage (...) ----------------------------------------------------------
- *
- * Program Page in Flash Memory
- * Parameter: adr: Page Start Address
- * sz: Page Size
- * buf: Page Data
- * Return Value: 0 - OK, 1 - Failed
- */
- int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {
- SPI_FLASH_PageProgram(adr-base_adr,buf,sz);
-
- return (0); // Done
- }
- /*- Verify (...) ---------------------------------------------------------------
- *
- * Verify Flash Contents
- * Parameter: adr: Start Address
- * sz: Size (in bytes)
- * buf: Data
- * Return Value: (adr+sz) - OK, Failed Address
- */
- unsigned long Verify (unsigned long adr, unsigned long sz, unsigned char *buf) {
- int i;
- SPI_FLASH_ReadData(adr-base_adr,aux_buf,sz);
-
- for (i = 0; i< 256; i++) {
- if (aux_buf[i] != buf[i])
- return (adr+i); // Verification Failed (return address)
- }
- return (adr+sz); // Done successfully
- }
可以通过验证工程进行测试验证,数据是否有被下载到指定的外扩 SPI Flash 地址中去:
至此,验证 ok ,已经全部分享完了如何换个方式去玩 SPI Nor Flash ,在附件中还会分享一个正点原子的工具 C2B.exe ,该工具可以将 bin 文件和 c 数组来回转换,极大方便了用户。
五、附件内容
附件资源包中有以下内容可供参考下载:
- 基于 MB-039 开发板的 SPI Flash 测试工程、烧录算法工程以及数据烧录工程 —— 01. F3270_M25P80_SFUD_LittleFS_FLM Data.zip
- 文中提及的 littlefs 上位机工具以及bin转数组工具 —— 02. Tools 下载地址.zip
- MB-039 开发板的原理图以及 M25P80 SPI Nor Flash 手册及其它应用说明参考文档 —— 03. 硬件原理图以及参考 AN 和 SPEC.zip
01. F3270_M25P80_SFUD_LittleFS_FLM Data.zip
(6.63 MB, 下载次数: 47)
02. Tools 下载地址.zip
(312 Bytes, 下载次数: 12)
03. 硬件原理图以及参考 AN 和 SPEC.zip
(4.23 MB, 下载次数: 11)