目录
一、Flash简介
(一)概述
STM32的Flash是一种非易失性存储器,用于存储程序代码、常量数据和用户配置信息等。即使在芯片断电后,Flash中的内容也不会丢失,这使得它在嵌入式系统中非常重要。例如,当你开发一个智能家居控制系统,使用STM32作为控制器,将控制程序存储在Flash中,每次上电后,芯片就能自动运行程序。
(二)存储结构
STM32的Flash通常被划分为多个扇区(Sector)。不同型号的STM32芯片,其Flash的扇区大小和数量会有所不同。以常见的STM32F103系列为例,Flash容量从32KB到512KB不等,扇区大小一般为1KB或2KB。这种扇区划分的方式方便了对Flash的擦除操作,因为擦除操作通常是按扇区进行的。 - 例如,如果你要更新程序中的某一部分代码,只需要擦除包含该部分代码的扇区,然后写入新的代码,而不必擦除整个Flash。
(三)操作方式
编程(写入)操作:在STM32中,向Flash写入数据不是字节级别的随意写入。一般需要先解锁Flash,然后按照一定的步骤和对齐要求进行写入。写入时通常是以半字(16位)或字(32位)为单位。而且写入操作不能直接在已有数据上进行覆盖,需要先擦除相应的扇区,因为Flash的写入原理是基于电擦除可编程(EEPROM)技术,擦除后的内容为全1,写入操作是将某些位变为0。
擦除操作:擦除操作只能以扇区为单位进行。这是因为Flash的存储单元是基于浮栅晶体管结构,擦除是将整个扇区的存储单元恢复到初始状态(全1)。在进行擦除操作之前,同样需要先解锁Flash。擦除操作需要一定的时间,不同的芯片和扇区大小擦除时间会有所不同,在程序设计中需要考虑这个时间延迟,以确保擦除操作完成后再进行后续的写入操作。
读取操作:读取Flash中的数据相对简单。由于Flash的地址空间是线性的,只要知道数据存储的地址,就可以像读取普通内存一样读取Flash中的内容。在程序中,可以通过指针直接访问Flash中的常量数据或者程序代码中的指令。
(四) 用途
存储程序代码:这是Flash最主要的用途。当你使用开发工具(如Keil)编译好STM32程序后,会生成一个二进制文件,这个文件最终会被下载并存储到Flash中。芯片的内核(如Cortex - M3、Cortex - M4等)会从Flash中读取指令并执行,从而实现各种功能,比如控制外部设备、进行数据处理等。
存储常量数据:可以将一些不会改变的常量数据存储在Flash中,如查找表(Lookup Table)。例如,在一个数字信号处理应用中,将正弦波的采样值存储在Flash中的查找表中,程序运行时可以快速读取这些值来生成波形信号。
存储用户配置信息:用于保存用户的一些配置参数,如设备的ID、校准值、工作模式等。这些配置信息在设备下次上电时可以被读取并用于初始化设备的工作状态。
(五)与其他存储器的比较
与SRAM(静态随机存取存储器)相比,Flash的优点是是非易失性的,而SRAM需要持续供电来保存数据。但Flash的读写速度比SRAM慢很多。SRAM常用于存储程序运行时的临时变量和数据缓存,而Flash用于长期存储程序和重要的数据。
与EEPROM(电可擦除可编程只读存储器)相比,STM32的Flash在容量上通常更大,适用于存储大量的程序代码和数据。不过,EEPROM在某些情况下可能具有更灵活的写入方式,有些EEPROM可以实现字节级别的写入,而Flash一般需要按扇区擦除后再写入。
二、HAL库实现
(一)CUBE配置
选中我们的板子后进入配置界面,首先配置我们的RCC为高速外部晶振
因为我们后面的调试将使用STlink,所以我们将SYS配置为Serial Wire
然后配置我们的系统时钟树,按照下图步骤配置就行
然后进行项目配置,注意绿框中的数据,这代表我们将堆栈改为了4K
接下来点击右上角生成我们的代码
(二)代码编写
生成我们的代码将其打开后,在代码中添加我们的flash代码
flash.c
/*
* flash.c
*
* Created: 2018-01-29
* Author: zhanglifu
*/
/*********************************************************************/
// 头文件
/*********************************************************************/
#include "flash.h"
// 不检查的写入
// WriteAddr:起始地址
// pBuffer: 数据指针
// NumToWrite:字节数数
void FlashWriteNoCheck( uint32_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite )
{
uint16_t i;
for( i=0; i<NumToWrite; i+=4 )
{
while( HAL_OK != HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr+i,*(uint32_t *)(pBuffer+i) ) );
}
}
extern void FLASH_PageErase(uint32_t PageAddress);
void FlashWriteBuff( const uint32_t destination_address, uint8_t *const buffer,uint32_t length )
{
uint16_t i;
uint8_t FlashBuff[FMC_SECTOR_SIZE];
uint32_t StartAddress = destination_address - destination_address%FMC_SECTOR_SIZE;
uint16_t Offset = destination_address - StartAddress;
uint8_t *pBuf = buffer;
uint32_t remaindNum = length;
HAL_StatusTypeDef status = HAL_ERROR;
// 地址检查
if( (destination_address < FMC_FLASH_BASE) || ( destination_address + length >= FMC_FLASH_END) || (length <= 0) )
return;
HAL_FLASH_Unlock(); // 解锁
do
{
// 读出一页数据
for(i=0; i < FMC_SECTOR_SIZE; i += 4 )
*(uint32_t *)(FlashBuff+i) = *(uint32_t *)(StartAddress+i);
// 修改要改入的数据
for ( i=0; (i+Offset)<FMC_SECTOR_SIZE && i< remaindNum; i++ )
*(FlashBuff+Offset+i) = *(pBuf+i);
// 擦除一ROW数据
FLASH_PageErase( StartAddress );
// HAL库 FLASH_PageErase有BUFF,要加上下面三行代码
while( status != HAL_OK )
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
CLEAR_BIT(FLASH->CR, FLASH_CR_PER);
// 写入数据
FlashWriteNoCheck( StartAddress,FlashBuff,FMC_SECTOR_SIZE );
// 为下一页做准备
StartAddress += FMC_SECTOR_SIZE;
remaindNum -= i;
pBuf += i;
Offset = 0;
} while( remaindNum > 0 );
HAL_FLASH_Lock(); // 上锁
}
// 从FLASH中读指定长度数据
void FlashReadBuff(const uint32_t source_address,uint8_t *const buffer,uint16_t length)
{
uint16_t i;
uint8_t Offset = 0;
uint32_t StartAddress = source_address;
uint16_t data;
// 地址检测
if( source_address + length > FMC_FLASH_END ) return;
// 如果没有对16齐
if( source_address & 1 )
{
Offset = 1;
StartAddress = source_address-1;
}
// flash的操作要求16对齐 最小读写操作16个比特
if ( Offset )
{
data = *(uint16_t *)(StartAddress);
buffer[0] = data >> 8;
StartAddress += 2;
}
for ( i = 0; i < (length-Offset); i += 2)
{
data = *(uint16_t *)(StartAddress+i);
buffer[i+Offset] = (data & 0xFF);
if ( (i+Offset) < (length - 1) )
buffer[i + Offset + 1] = (data >> 8);
}
}
flash.h
/*
* flash.h
*
* Created: 2016-04-22
* Author: zhanglifu
*/
#ifndef _flash_H_
#define _flash_H_
#include "stm32f1xx_hal.h"
/*********************************************************************/
// 变量定义
/*********************************************************************/
//-- 用途划分
// 0x0800FC00-0x0800FFFF -- 使用最后4k 个字节用来存放电机信息
#define FMC_FLASH_BASE 0x08000000 // FLASH的起始地址
#define APP_MAX_SIZE 0x00010000 //
#define FMC_FLASH_END 0x08010000 // FLASH的结束地址 256
#define DEVICE_INFO_ADDRESS 0x0800C000 //(STM32_FLASH_END - DEVICE_INFO_SIZE) // 设备信息起始地址
#define DEVICE_LOG_ADDRESS 0x0800E000 //(STM32_FLASH_END - 2*DEVICE_INFO_SIZE) // 设备日志起始地址
#define FMC_FLASH_SIZE 64 // 定义Flash大小,单位KB
#if FMC_FLASH_SIZE < 256
#define FMC_SECTOR_SIZE 1024 // 字节
#define MOD_SECTOR_SIZE 0X3FF
#define PAGE_COUNT_BY16 512
#else
#define FMC_SECTOR_SIZE 2048
#define MOD_SECTOR_SIZE 0X7FF
#define PAGE_COUNT_BY16 1024
#endif
/*********************************************************************/
// 函数声明
/*********************************************************************/
void FlashWriteBuff( const uint32_t destination_address, uint8_t *const buffer,uint32_t length );
void FlashReadBuff(const uint32_t source_address,uint8_t *const buffer,uint16_t length);
#endif
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
** This notice applies to any and all portions of this file
* that are not between comment pairs USER CODE BEGIN and
* USER CODE END. Other portions of this file, whether
* inserted by the user or by software development tools
* are owned by their respective copyright owners.
*
* COPYRIGHT(c) 2019 STMicroelectronics
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "flash.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 */
uint8_t FlashWBuff [255];
uint8_t FlashRBuff [255];
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t i;
uint8_t FlashTest[] = "Hello This is xingyejin Flash Test DEMO";
/* 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 */
FlashWriteBuff( DEVICE_INFO_ADDRESS, FlashTest,sizeof(FlashTest) ); // 写入数据到Flash
for(i=0;i<255;i++)
FlashWBuff[i] = i;
FlashWriteBuff( DEVICE_INFO_ADDRESS + sizeof(FlashTest), FlashWBuff,255 ); // 写入数据到Flash
FlashReadBuff( DEVICE_INFO_ADDRESS + sizeof(FlashTest),FlashRBuff,255 ); // 从Flash中读取数
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* 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 CPU, AHB and APB busses clocks
*/
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 busses 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 */
/* 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,
tex: 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****/
(三)结果演示
我们用STlink进行德debug调试后,找到下图所示的窗口
打开后,将我们的地址填入进去0x0800C000,记得要回车,然后选择ASCII码
接下来找到watch窗口
将变量FlashWBuff 和 FlashRBuff加入到 Watch 1 观察窗口
然后我们点击全速运行,就能发现地址被写入了
可以看到FlashWBuff被成功写入到了FlashRBuff里面。
我们再断电重新上电后,可以看到写入的flash还存储在相同的位置。
三、总结
本文主要介绍了在 STM32 中利用 HAL 库读写 Flash 的相关知识。首先详细阐述了 Flash 的基本特性,包括其作为非易失性存储器的重要性,在嵌入式系统中用于存储程序代码、常量数据和用户配置信息等,即使断电数据也不会丢失。其存储结构按扇区划分,不同型号 STM32 芯片的扇区大小和数量各异,这种结构方便了擦除操作。操作方式上,写入需先解锁,以半字或字为单位且要先擦除扇区,擦除只能按扇区进行,读取则较为简单,可像读普通内存一样通过地址访问。同时对比了 Flash 与 SRAM、EEPROM 的优缺点,Flash 虽非易失但读写速度慢于 SRAM,容量通常大于 EEPROM 但写入方式灵活性稍逊。
接着通过 CUBE 配置和代码编写展示了如何利用 HAL 库实现对 Flash 的读写操作。在 CUBE 配置中,针对板子进行了 RCC、SYS 等相关配置并生成代码。代码编写方面,在flash.c
中实现了FlashWriteNoCheck
、FlashWriteBuff
和FlashReadBuff
等函数,分别用于不同场景下的 Flash 写入和读取操作,在flash.h
中定义了相关变量和函数声明,main.c
中则进行了数据写入 Flash、读取 Flash 以及变量定义和系统时钟配置等操作。
最后通过结果演示,利用 STlink 调试,在特定窗口中填入地址、选择 ASCII 码,在 Watch 窗口观察变量,展示了数据成功写入 Flash 以及断电重启后数据仍存储在相同位置,验证了 Flash 读写操作的正确性和非易失性特点。通过本文的介绍,读者能够对 STM32 中 Flash 的读写有较为全面的了解和掌握,为在嵌入式开发中有效利用 Flash 存储数据和程序提供了参考和指导,有助于在实际项目中更好地设计和优化数据存储方案,确保系统的稳定运行和数据的可靠保存。