STM32HAL 移植 EasyFlash 开源框架(裸机开发神器)
概述本篇文章介绍如何使用STM32内部Flash,将日志信息保存在Flash中,使用时再去读取。这样做得好处,就是非常适合物联网终端设备使用,使得日志可以更加容易的存储在非文件系统中,并具有历史日志检索的功能。GitHub:https://github.com/armink/EasyLogger/blob/master/docs/zh/port/flash.md硬件:STM32F103CBT6最小
目录
概述
本篇文章介绍如何使用STM32内部Flash,将日志信息保存在Flash中,使用时再去读取。这样做得好处,就是非常适合物联网终端设备使用,使得日志可以更加容易的存储在 非文件系统 中,并具有历史日志检索的功能。
GitHub:https://github.com/armink/EasyFlash
硬件:STM32F103CBT6最小系统板
软件:Keil 5.29 + STM32CubeMX6.01
一、使用方法
EasyFlash是一款开源的轻量级嵌入式Flash存储器库,方便开发者更加轻松的实现基于Flash存储器的常见应用开发。非常适合智能家居、可穿戴、工控、医疗、物联网等需要断电存储功能的产品,资源占用极低,支持各种 MCU 片上存储器。该库主要包括 三大实用功能 :
- ENV 快速保存产品参数,支持 写平衡(磨损平衡) 及 掉电保护 功能
EasyFlash不仅能够实现对产品的 设定参数 或 运行日志 等信息的掉电保存功能,还封装了简洁的 增加、删除、修改及查询 方法, 降低了开发者对产品参数的处理难度,也保证了产品在后期升级时拥有更好的扩展性。让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。
- IAP 在线升级再也不是难事儿
该库封装了IAP(In-Application Programming)功能常用的接口,支持CRC32校验,同时支持Bootloader及Application的升级。
- Log 无需文件系统,日志可直接存储在Flash上
非常适合应用在小型的不带文件系统的产品中,方便开发人员快速定位、查找系统发生崩溃或死机的原因。同时配合EasyLogger(我开源的超轻量级、高性能C日志库,它提供与EasyFlash的无缝接口)一起使用,轻松实现C日志的Flash存储功能。
详细操作,还是去官网阅读。
通过以上介绍初步了解,想使用此插件,必须要有EasyLogger核心代码,为了减少工作量,你也阔以按照我写《STM32HAL 移植 EasyLogger 极简美开源打印文件日志输出(裸机开发神器)》这篇文章操作,或者下载我这篇文章代码,在它基本进行添加Flash 插件即可。好了,废话有点多。下面进入主题。
二、STM32CubeMx配置
三、Examples
简单粗暴,直接上代码
打开STM32CubeMX生成的Keil工程,如下所示:
把仓库中的代码下载下来,找到easyflash文件,复制到项目中来,只需添加下面四个文件即可。
添加头文件路径
此时,编译会出错,无需紧张,按照下面步骤慢慢解决
1、找到ef_cfg.h文件,在以下几个地方添加相应的参数即可。
- #define EF_ERASE_MIN_SIZE 1024 根据实际情况填写最小擦除粒度, 我是用的是
F103CB
所以填写的是1024
, 如果你使用的是F103ze
就填写2048,其它型号芯片具体看datasheet,flash内容。
EF_WRITE_GRAN
填32
(上面有注释,如果是stm32f4就要填8)- 填写
easyflash
的开始地址,在这我划分的分区是后面64K, 所以填(0x08000000UL + EF_ERASE_MIN_SIZE * 64)
ENV_AREA_SIZE
填写(EF_ERASE_MIN_SIZE * 2)
#define LOG_AREA_SIZE
没有使用到, 把它注释掉即可
/*
* This file is part of the EasyFlash Library.
*
* Copyright (c) 2015-2019, Armink, <armink.ztl@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* 'Software'), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Function: It is the configure head file for this library.
* Created on: 2015-07-14
*/
#ifndef EF_CFG_H_
#define EF_CFG_H_
/* using ENV function, default is NG (Next Generation) mode start from V4.0 */
#define EF_USING_ENV
#ifdef EF_USING_ENV
/* Auto update ENV to latest default when current ENV version number is changed. */
/* #define EF_ENV_AUTO_UPDATE */
/**
* ENV version number defined by user.
* Please change it when your firmware add a new ENV to default_env_set.
*/
#define EF_ENV_VER_NUM 0 /* @note you must define it for a value, such as 0 */
/* MCU Endian Configuration, default is Little Endian Order. */
/* #define EF_BIG_ENDIAN */
#endif /* EF_USING_ENV */
/* using IAP function */
/* #define EF_USING_IAP */
/* using save log function */
/* #define EF_USING_LOG */
/* The minimum size of flash erasure. May be a flash sector size. */
#define EF_ERASE_MIN_SIZE 1024 /* @note you must define it for a value */
/* the flash write granularity, unit: bit
* only support 1(nor flash)/ 8(stm32f4)/ 32(stm32f1) */
#define EF_WRITE_GRAN 32 /* @note you must define it for a value */
/*
*
* This all Backup Area Flash storage index. All used flash area configure is under here.
* |----------------------------| Storage Size
* | Environment variables area | ENV area size @see ENV_AREA_SIZE
* |----------------------------|
* | Saved log area | Log area size @see LOG_AREA_SIZE
* |----------------------------|
* |(IAP)Downloaded application | IAP already downloaded application, unfixed size
* |----------------------------|
*
* @note all area sizes must be aligned with EF_ERASE_MIN_SIZE
*
* The EasyFlash add the NG (Next Generation) mode start from V4.0. All old mode before V4.0, called LEGACY mode.
*
* - NG (Next Generation) mode is default mode from V4.0. It's easy to settings, only defined the ENV_AREA_SIZE.
* - The LEGACY mode has been DEPRECATED. It is NOT RECOMMENDED to continue using.
* Beacuse it will use ram to buffer the ENV and spend more flash erase times.
* If you want use it please using the V3.X version.
*/
/* backup area start address */
#define EF_START_ADDR (0x08000000UL + EF_ERASE_MIN_SIZE * 64) /* 64k */ /* @note you must define it for a value */
/* ENV area size. It's at least one empty sector for GC. So it's definition must more then or equal 2 flash sector size. */
#define ENV_AREA_SIZE (2 * EF_ERASE_MIN_SIZE) /* 8k */ /* @note you must define it for a value if you used ENV */
/* saved log area size */
#define LOG_AREA_SIZE (10 * EF_ERASE_MIN_SIZE) /* 20K */ /* @note you must define it for a value if you used log */
/* print debug information of flash */
#define PRINT_DEBUG
#endif /* EF_CFG_H_ */
再次编译,发现无错误
2、接着打开 ef_port.c文件,在几个函数中填写相应的语句即可。
分别是以下几个函数:
ef_port_init
(如果使用FAL, 则需要配置,本例程不需要配置)ef_port_read
ef_port_erase
ef_port_write
/*
* This file is part of the EasyFlash Library.
*
* Copyright (c) 2015-2019, Armink, <armink.ztl@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* 'Software'), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Function: Portable interface for each platform.
* Created on: 2015-01-16
*/
#include <easyflash.h>
#include <stdarg.h>
#include "stm32f1xx_hal.h"
/* default environment variables set for user */
static const ef_env default_env_set[] = {
#if 0
{"iap_need_copy_app","0"},
{"iap_copy_app_size","0"},
{"stop_in_bootloader","0"},
{"device_id","1"},
{"boot_times","0"},
#else
{"reboot time", "\0"} //Used to store startup times
#endif
};
/**
* Flash port for hardware initialize.
*
* @param default_env default ENV set for user
* @param default_env_size default ENV size
*
* @return result
*/
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {
EfErrCode result = EF_NO_ERR;
*default_env = default_env_set;
*default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);
return result;
}
/**
* Read data from flash.
* @note This operation's units is word.
*
* @param addr flash address
* @param buf buffer to store read data
* @param size read bytes size
*
* @return result
*/
EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size) {
EfErrCode result = EF_NO_ERR;
/* You can add your code under here. */
uint8_t *buf_8 = (uint8_t *)buf;
size_t i;
for (i = 0; i < size; i++, addr ++, buf_8++) {
*buf_8 = *(uint8_t *) addr;
}
return result;
}
/**
* Erase data on flash.
* @note This operation is irreversible.
* @note This operation's units is different which on many chips.
*
* @param addr flash address
* @param size erase bytes size
*
* @return result
*/
EfErrCode ef_port_erase(uint32_t addr, size_t size) {
EfErrCode result = EF_NO_ERR;
/* make sure the start address is a multiple of EF_ERASE_MIN_SIZE */
EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);
/* You can add your code under here. */
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
/* Erase FLASH*/
FLASH_EraseInitTypeDef FlashSet;
FlashSet.TypeErase = FLASH_TYPEERASE_PAGES;
FlashSet.PageAddress = addr;
FlashSet.NbPages = (size + EF_ERASE_MIN_SIZE -1)/ EF_ERASE_MIN_SIZE;
/*Set PageError and call the erase function*/
uint32_t PageError = 0;
if (HAL_FLASHEx_Erase(&FlashSet, &PageError) != HAL_OK)
result = EF_ERASE_ERR;
HAL_FLASH_Lock();
return result;
}
/**
* Write data to flash.
* @note This operation's units is word.
* @note This operation must after erase. @see flash_erase.
*
* @param addr flash address
* @param buf the write data buffer
* @param size write bytes size
*
* @return result
*/
EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size) {
EfErrCode result = EF_NO_ERR;
/* You can add your code under here. */
size_t i;
uint32_t read_data;
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
for (i = 0; i < size; i += 4, buf++, addr += 4) {
/* write data */
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, (uint64_t)*buf);
read_data = *(uint32_t *)addr;
/* check data */
if (read_data != *buf) {
result = EF_WRITE_ERR;
break;
}
}
HAL_FLASH_Lock();
return result;
}
/**
* lock the ENV ram cache
*/
void ef_port_env_lock(void) {
/* You can add your code under here. */
}
/**
* unlock the ENV ram cache
*/
void ef_port_env_unlock(void) {
/* You can add your code under here. */
}
/**
* This function is print flash 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 ef_log_debug(const char *file, const long line, const char *format, ...) {
#ifdef PRINT_DEBUG
va_list args;
/* args point to the first variable parameter */
va_start(args, format);
/* You can add your code under here. */
va_end(args);
#endif
}
/**
* This function is print flash routine info.
*
* @param format output format
* @param ... args
*/
void ef_log_info(const char *format, ...) {
va_list args;
/* args point to the first variable parameter */
va_start(args, format);
/* You can add your code under here. */
va_end(args);
}
/**
* This function is print flash non-package info.
*
* @param format output format
* @param ... args
*/
void ef_print(const char *format, ...) {
va_list args;
/* args point to the first variable parameter */
va_start(args, format);
/* You can add your code under here. */
va_end(args);
}
3、main.c文件
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <stdlib.h>
#include "easyflash.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
int fgetc(FILE * f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
void test_env(void) {
uint32_t i_boot_times = NULL;
char *c_old_boot_times, c_new_boot_times[11] = {0};
/* get the boot count number from Env */
c_old_boot_times = ef_get_env("boot_times");
assert_param(c_old_boot_times);
i_boot_times = atol(c_old_boot_times);
/* boot count +1 */
i_boot_times ++;
printf("The system now boot %d times\n\r", i_boot_times);
/* interger to string */
sprintf(c_new_boot_times, "%ld", i_boot_times);
/* set and store the boot count number to Env */
ef_set_env("boot_times", c_new_boot_times);
ef_save_env();
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint32_t Reboot_Time = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
#if 0
if(easyflash_init() == EF_NO_ERR) // Initialization Data Succeed
{
ef_get_env_blob("reboot_time", &Reboot_Time, 8, NULL); // Read the value of reboot time
}
Reboot_Time++; // The reboot time value is incremented by 1
ef_set_env_blob("reboot_time",&Reboot_Time,8); // Save the value of reboot time
printf("Reboot_Time is %d\n",Reboot_Time); // Print the value of reboot time
#else
if (easyflash_init() == EF_NO_ERR) {
/* test Env demo */
test_env();
}
#endif
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(1000);
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
四、运行结果
传送门->代码
参考文章:https://blog.csdn.net/weixin_41294615/article/details/104015782
五、总结
好了,就介绍到此,有了这个神器,裸机开发就简单很多了,特别适合物联网终端设备日志保存的使用场景。
更多推荐
所有评论(0)