个人上篇文章 搭建STM32F407的SPI-Flash(基于STM32CubeMX)_小刚学長的博客-CSDN博客
主要是解决STM32CubeMX这边的配置,对code端侧是简单介绍了下
实际项目上一般都是拿片外flash存储一些东西,比如一些比较多的配置、参数,偶尔修改下,还有一些程序文件,日志之类的。存在读写操作,而nor flash是每次擦除后才可以写入(也就是说你要更新内容,也是要先擦除再写入新的数据),往往这种操作比较麻烦(最近简单的方法,先把当簇内容读出来,然后对相应的位置进行修改,然后先擦除当前簇,再写入),如果更新内容存在跨簇,那更麻烦点,需要判断。另外,还有个特点,nor flash存在擦除次数上限,如果超过一定次数,此区域容易坏。
对于windows、linux,存在文件系统,文件系统就可以很好规避以上问题,使用时也不用去关心、去计算这些值。对于STM32显然文件系统开销很大,不太合适
那么STM32有没有轻量点库之类的,答案是有的:
FlashDB + SFUD + SPI Flash
FlashDB就是个简单版本的DB,也是专门适合MCU环境,而且针对Flash特点,对相同簇写入次数也有优化(写入均衡)。FlashDB 支持kv类型数据库、也支持时间序列数据库,一般也足够用了。关键还是占资源少,写flash均衡,这样应用者可更关注逻辑部分。是个好东西,记得早些年,都是自己造轮子的,又无法证明自己造轮子是可靠的,因此经常被吊。
当然,FlashDB 不仅仅是 通过SFUD去访问SPI Flash,也可以通过自己封装的SPI FLASH驱动代码去访问,也支持文件系统(文件方式)去访问外部flash。但经典组合,还是
由于芯片本身不同,这里芯片不仅仅是FLASH芯片,还包括MCU,因此要实现自己的代码,需要做相应的改进(调整),以下的方案是基于STM32F407芯片,具体硬件配置,参考上篇
1. 把相关代码复制到工程里面:(包括对应 h文件)
2. fal_cfg.h fal 配置
#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_
#include "main.h"
#define FAL_DEBUG 1
#define FAL_PART_HAS_TABLE_CFG
#define FAL_USING_SFUD_PORT
/* ===================== Flash device Configuration ========================= */
extern struct fal_flash_dev nor_flash0;
/* flash device table */
#define FAL_FLASH_DEV_TABLE \
{ \
&nor_flash0, \ //nor flash 基本信息以及读写擦除接口等。是fal_flash_sfud_port.c 有描述,也是需要初始化的对象
}
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WROD, "fdb_kvdb1", "norflash0", 0, 1024*1024, 0}, \
/* 1M(FLASH 地址0~1M) 大小的table, 这里只是一张表,根据实际情况配置*/
}
#endif /* FAL_PART_HAS_TABLE_CFG */
#endif /* _FAL_CFG_H_ */
3. fal_flash_sfud_port.c 修改,印象中只修改过 init
#ifndef FAL_USING_NOR_FLASH_DEV_NAME
#define FAL_USING_NOR_FLASH_DEV_NAME "norflash0"
#endif
static int init(void);
static int read(long offset, uint8_t *buf, size_t size);
static int write(long offset, const uint8_t *buf, size_t size);
static int erase(long offset, size_t size);
static sfud_flash_t sfud_dev = NULL;
struct fal_flash_dev nor_flash0 =
{
.name = FAL_USING_NOR_FLASH_DEV_NAME,
.addr = 0,
.len = 1024 * 1024,
.blk_size = 4096,
.ops = {init, read, write, erase},
.write_gran = 1
};
static int init(void)
{
#ifdef RT_USING_SFUD
/* RT-Thread RTOS platform */
sfud_dev = rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME);
#else
/* bare metal platform */
extern sfud_flash flash_table[1]; // 这里table实际上是 指 SFUD_FLASH_DEVICE_TABLE
sfud_dev = &flash_table[0]; // sfud_dev 指向 table[0] 这个非常重要
#endif
if (NULL == sfud_dev)
{
return -1;
}
/* update the flash chip information */
nor_flash0.blk_size = sfud_dev->chip.erase_gran;
nor_flash0.len = sfud_dev->chip.capacity;
return 0;
}
static int read(long offset, uint8_t *buf, size_t size)
{
assert(sfud_dev);
assert(sfud_dev->init_ok);
sfud_read(sfud_dev, nor_flash0.addr + offset, size, buf);
return size;
}
static int write(long offset, const uint8_t *buf, size_t size)
{
assert(sfud_dev);
assert(sfud_dev->init_ok);
if (sfud_write(sfud_dev, nor_flash0.addr + offset, size, buf) != SFUD_SUCCESS)
{
return -1;
}
return size;
}
static int erase(long offset, size_t size)
{
assert(sfud_dev);
assert(sfud_dev->init_ok);
if (sfud_erase(sfud_dev, nor_flash0.addr + offset, size) != SFUD_SUCCESS)
{
return -1;
}
return size;
}
4. sfud_cfg.h 配置 SFUD_FLASH_DEVICE_TABLE
#ifndef _SFUD_CFG_H_
#define _SFUD_CFG_H_
#define SFUD_DEBUG_MODE
#define SFUD_USING_SFDP
#define SFUD_USING_FLASH_INFO_TABLE
enum {
SFUD_W25Q128_DEVICE_INDEX = 0, // 如果多个,往下加,名字自己取,但要跟其他对应好
};
// 上下要对应好
#define SFUD_FLASH_DEVICE_TABLE \
{ \
[SFUD_W25Q128_DEVICE_INDEX] = {.name = "W25Q128B", .spi.name = "SPI1"}, \
}
#endif /* _SFUD_CFG_H_ */
5. sfud_port.c 修改,就是要实现 对接SPI接口
要做:
SPI相关配置:是通过HAL访问SPI,还是其他?片选信号对应GPIO信息
SPI延时函数、锁、解锁等
SPI 读写接口封装
#include "spi.h"
typedef struct {
SPI_HandleTypeDef *spix;
GPIO_TypeDef *cs_gpiox;
uint32_t cs_gpio_pin;
} spi_user_data, *spi_user_data_t;
void sfud_log_debug(const char *file, const long line, const char *format, ...);
static void spi_configuration(spi_user_data_t spi) {
}
static void spi_lock(const sfud_spi *spi) {
__disable_irq();
}
static void spi_unlock(const sfud_spi *spi) {
__enable_irq();
}
/**
* 这个函数要重新写
*/
static sfud_err spi_write_read(const sfud_spi *spi, uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
size_t read_size) {
sfud_err result = SFUD_SUCCESS;
spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
uint16_t blockSize = 0;
uint32_t offset = 0;
if (write_size) {
SFUD_ASSERT(write_buf);
}
if (read_size) {
SFUD_ASSERT(read_buf);
}
// HAL - 片选拉低
HAL_GPIO_WritePin(spi_dev->cs_gpiox,spi_dev->cs_gpio_pin,GPIO_PIN_RESET);
if (write_size)
{
if (HAL_SPI_Transmit(spi_dev->spix, write_buf, write_size, 100) != HAL_OK)
{
result = SFUD_ERR_WRITE;
goto exit;
}
}
while (read_size)
{
blockSize = read_size > 0xFFFF ? 0xFFFF : read_size;
read_size -= blockSize;
if (HAL_SPI_Receive(spi_dev->spix, read_buf + offset, blockSize, 100) != HAL_OK)
{
result = SFUD_ERR_READ;
goto exit;
}
offset += blockSize;
}
exit:
// HAL - 片选拉高
HAL_GPIO_WritePin(spi_dev->cs_gpiox,spi_dev->cs_gpio_pin,GPIO_PIN_SET);
return result;
}
/* about 100 microsecond delay */
static void retry_delay_100us(void) {
DWT_Delay(100); // 这里要自己去实现一个,主要是100微秒,不是100ms
}
// spi 关键信息配置,对应spi多少,片选多少
static spi_user_data spi1 = { .spix = &hspi1, .cs_gpiox = SPI_FLASH_CS_GPIO_Port, .cs_gpio_pin = SPI_FLASH_CS_Pin };
sfud_err sfud_spi_port_init(sfud_flash *flash) {
sfud_err result = SFUD_SUCCESS;
switch (flash->index) {
case SFUD_W25Q128_DEVICE_INDEX: {
/* SPI 外设初始化 */
spi_configuration(&spi1);
/* 同步 Flash 移植所需的接口及数据 */
flash->spi.wr = spi_write_read;
flash->spi.lock = spi_lock;
flash->spi.unlock = spi_unlock;
flash->spi.user_data = &spi1;
/* about 100 microsecond delay */
flash->retry.delay = retry_delay_100us;
/* about 60 seconds timeout */
flash->retry.times = 60 * 10000;
break;
}
}
return result;
}
/**
* This function is print debug info.
*
* @param file the file which has call this function
* @param line the line number which has call this function
* @param format output format
* @param ... args
*/
void sfud_log_debug(const char *file, const long line, const char *format, ...) {
va_list args;
printf("[%s,%d] ", file, line);
va_start(args, format);
printf(format, args);
va_end(args);
printf("\n");
}
/**
* This function is print routine info.
*
* @param format output format
* @param ... args
*/
void sfud_log_info(const char *format, ...) {
va_list args;
va_start(args, format);
printf(format, args);
va_end(args);
printf("\n");
}
以上信息,一般网上例子就最多那么多,实际还有两个关键:
1. printf 要重定向,还不能用Micro LIB的方式
在usart.c 添加如下代码,并不要勾选Micro LIB!!
/* USER CODE BEGIN 1 */
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
/* USER CODE END 1 */
2. 增加芯片信息 SFUD_FLASH_CHIP_TABLE 最后一个就是我的芯片信息!!如果没有,需要手动添加,注意 "W25Q128B" 的name 关键字,这个也不是随便取的,要注意与前面的对应!!
#define SFUD_FLASH_CHIP_TABLE \
{ \
{"AT45DB161E", SFUD_MF_ID_ATMEL, 0x26, 0x00, 2*1024*1024, SFUD_WM_BYTE|SFUD_WM_DUAL_BUFFER, 512, 0x81}, \
{"W25Q40BV", SFUD_MF_ID_WINBOND, 0x40, 0x13, 512*1024, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"SST25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2*1024*1024, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \
{"M25P32", SFUD_MF_ID_MICRON, 0x20, 0x16, 4*1024*1024, SFUD_WM_PAGE_256B, 64*1024, 0xD8}, \
{"EN25Q32B", SFUD_MF_ID_EON, 0x30, 0x16, 4*1024*1024, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"GD25Q64B", SFUD_MF_ID_GIGADEVICE, 0x40, 0x17, 8*1024*1024, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"S25FL216K", SFUD_MF_ID_CYPRESS, 0x40, 0x15, 2*1024*1024, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"A25L080", SFUD_MF_ID_AMIC, 0x30, 0x14, 1*1024*1024, SFUD_WM_PAGE_256B, 4096, 0x20}, \
{"F25L004", SFUD_MF_ID_ESMT, 0x20, 0x13, 512*1024, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \
{"PCT25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2*1024*1024, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \
{"W25Q128B", SFUD_MF_ID_WINBOND, 0x40, 0x18, 16*1024*1024, SFUD_WM_PAGE_256B, 4096, 0x20}, \
}
这样应该可以了。我记得就这么多!