一、简介
在STM32芯片内部有一个 FLASH 存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中,由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。
STM32 的内部 FLASH 包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小见下表
- 主存储器
一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、512K FLASH 都是指这个区域的大小。
主存储器分为 256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除。
注意上表中的主存储器是本实验板使用的 STM32VET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。
主存储器是以页为单位划分的。stm32根据FLASH主存储块容量、页面的不同,系统存储器的不同,分为小容量、中容量、大容量、互联型,共四类产品。
- 小容量产品:主存储块1-32KB, 每页1KB。系统存储器2KB
- 中容量产品:主存储块64-128KB, 每页1KB。系统存储器2KB
- 大容量产品:主存储块256KB以上, 每页2KB。系统存储器2KB
- 互联型产品:主存储块256KB以上, 每页2KB。系统存储器18KB
- 系统存储区
系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB 以及 CAN 等 ISP 烧录功能。
- 选项字节
选项字节用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16 字节。可以通过修改 FLASH 的选项控制寄存器修改。
二、新建工程
1. 打开 STM32CubeMX 软件,点击“新建工程”
2. 选择 MCU 和封装
3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
三、添加串口打印
串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用
四、生成代码
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的
’.c/.h’
文件不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
五、查看工程的空间分布
由于内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生
的“*.map”后缀文件,可以了解程序存储到了哪些区域。
打开 map 文件后,查看文件最后部分的区域,可以看到一段以 “Memory Map of the image” 开头的记录(若找不到可用查找功能定位)
观察表中的最后一项,它的基地址是 0x0800175c,大小为 0x00000020,可知它占用的
最高的地址空间为 0x0800177c,跟执行区域的最高地址 0x0000177c 一样,但它们比加载
区域说明中的最高地址 0x80017a8 要小,所以我们以加载区域的大小为准。对比表 45-1 的
内部 FLASH 页地址分布表,可知仅使用页 0 至页 2 就可以完全存储本应用程序,所以从页
3(地址 0x08001800)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程
序空间的数据。
六、官方HAL库Flash操作常见函数
//源文件: stm32f1xx_hal_flash.c和stm32f1xx_hal_flash_ex.c
HAL_FLASH_Unlock(void); //解锁函数
HAL_FLASH_Lock(void); //锁定函数
HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data); //写操作函数
HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError); //擦除函数
HAL_FLASH_WaitForLastOperation(uint32_t Timeout); //等待操作完成函数
HAL库中定义了一个Flash初始化结构体,如下:
/**
* @brief FLASH Erase structure definition
*/
typedef struct
{
uint32_t TypeErase; /*!< Mass erase or page erase.
This parameter can be a value of @ref FLASH_Type_Erase */
uint32_t Banks; /*!< Select bank to erase.
This parameter must be a value of @ref FLASH_Banks
(FLASH_BANK_BOTH should be used only for mass erase) */
uint32_t Page; /*!< Initial Flash page to erase when page erase is disabled
This parameter must be a value between 0 and (max number of pages in the bank - 1)
(eg : 255 for 1MB dual bank) */
uint32_t NbPages; /*!< Number of pages to be erased.
This parameter must be a value between 1 and (max number of pages in the bank - value of initial page)*/
} FLASH_EraseInitTypeDef;
七、读取Flash
7.1 读取函数
/* FLASH大小:STM32F103VET6:256K */
#define STM32FLASH_SIZE 0x00040000UL
/* FLASH起始地址 */
#define STM32FLASH_BASE FLASH_BASE
/* FLASH结束地址 */
#define STM32FLASH_END (STM32FLASH_BASE | STM32FLASH_SIZE)
/* FLASH页大小:1K */
#define STM32FLASH_PAGE_SIZE FLASH_PAGE_SIZE
/* FLASH总页数 */
#define STM32FLASH_PAGE_NUM (STM32FLASH_SIZE / STM32FLASH_PAGE_SIZE)
#define WRITE_START_ADDR ((uint32_t)0x08008000)
#define WRITE_END_ADDR ((uint32_t)0x0800C000)
/**
@brief 内部Flash读取
@param address -[in] 读取的地址
@param pData -[out] 指向需要操作的数据
@param dataLen -[in] 数据长度
@return 读出成功的字节数
*/
uint32_t Internal_ReadFlash(uint32_t addrStart, void *pData, uint32_t dataLen)
{
uint32_t nread = dataLen;
uint8_t *pBuffer = (uint8_t *)pData;
const uint8_t *pAddr = (const uint8_t *)addrStart;
if(!pData || addrStart < STM32FLASH_BASE || addrStart > STM32FLASH_END)
{
return 0;
}
while(nread >= sizeof(uint32_t) && (((uint32_t)pAddr) <= (STM32FLASH_END - 4)))
{
*(uint32_t *)pBuffer = *(uint32_t *)pAddr;
pBuffer += sizeof(uint32_t);
pAddr += sizeof(uint32_t);
nread -= sizeof(uint32_t);
}
while(nread && (((uint32_t)pAddr) < STM32FLASH_END))
{
*pBuffer++ = *pAddr++;
nread--;
}
return dataLen - nread;
}
8、写入Flash
8.1 写入过程
8.1.1 解锁
由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置 FLASH 的控制寄存器,从而不能修改 FLASH 中的内容。
所以对 FLASH 写入数据前,需要先给它解锁。解锁的操作步骤如下:
- 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
- 再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB
8.1.2 页擦除
在写入新的数据前,需要先擦除存储区域,STM32 提供了页(扇区)擦除指令和整个 FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。
页擦除的过程如下:
- 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
- 在 FLASH_CR 寄存器中,将“激活页擦除寄存器位 PER ”置 1;
- 用 FLASH_AR 寄存器选择要擦除的页;
- 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
- 等待 BSY 位被清零时,表示擦除完成。
/**
@brief 内部Flash页擦除
@param pageAddress -[in] 擦除的起始地址
@param nbPages -[in] 擦除页数
@return 0 - 成功;-1 - 失败
*/
int Internal_ErasePage(uint32_t pageAddress, uint32_t nbPages)
{
uint32_t pageError = 0;
FLASH_EraseInitTypeDef eraseInit;
eraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
eraseInit.PageAddress = pageAddress;
eraseInit.Banks = FLASH_BANK_1;
eraseInit.NbPages = 1;
if(HAL_FLASHEx_Erase(&eraseInit, &pageError) != HAL_OK)
{
return -1;
}
return 0;
}
8.1.3 写入数据
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还需要配置一系列的寄存器,步骤如下:
- 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
- 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1;
- 向指定的 FLASH 存储器地址执行数据写入操作,每次只能以 16 位的方式写入;
- 等待 BSY 位被清零时,表示写入完成。
8.2 写入函数
/**
@brief 内部Flash写入
@param address -[in] 写入的地址
@param pData -[in] 指向需要操作的数据
@param dataLen -[in] 数据长度
@return 实际写入的数据量,单位:字节
*/
uint32_t Internal_WriteFlash(uint32_t addrStart, const uint16_t *pData, uint32_t dataLen)
{
uint32_t i = 0;
uint32_t pagepos = 0; // 页位置
uint32_t pageoff = 0; // 页内偏移地址
uint32_t pagefre = 0; // 页内空余空间
uint32_t offset = 0; // Address在FLASH中的偏移
uint32_t nwrite = dataLen; // 记录剩余要写入的数据量
const uint16_t *pBuffer = (const uint16_t *)pData;
/* 非法地址 */
if(addrStart < STM32FLASH_BASE || addrStart > (STM32FLASH_END - 2) || dataLen == 0 || pData == NULL)
{
return 0;
}
/* 解锁FLASH */
HAL_FLASH_Unlock();
/* 计算偏移地址 */
offset = addrStart - STM32FLASH_BASE;
/* 计算当前页位置 */
pagepos = offset / STM32FLASH_PAGE_SIZE;
/* 计算要写数据的起始地址在当前页内的偏移地址 */
pageoff = ((offset % STM32FLASH_PAGE_SIZE) >> 1);
/* 计算当前页内空余空间 */
pagefre = ((STM32FLASH_PAGE_SIZE >> 1) - pageoff);
/* 要写入的数据量低于当前页空余量 */
if(nwrite <= pagefre)
{
pagefre = nwrite;
}
while(nwrite != 0)
{
/* 检查是否超页 */
if(pagepos >= STM32FLASH_PAGE_NUM)
{
break;
}
/* 读取一页 */
Internal_ReadFlash(STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE, FlashBuffer, STM32FLASH_PAGE_SIZE);
/* 检查是否需要擦除 */
for(i = 0; i < pagefre; i++)
{
if(*(FlashBuffer + pageoff + i) != 0xFFFF) /* FLASH擦出后默认内容全为0xFF */
{
break;
}
}
if(i < pagefre)
{
uint32_t count = 0;
uint32_t index = 0;
uint32_t PageError = 0;
FLASH_EraseInitTypeDef pEraseInit;
/* 擦除一页 */
pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
pEraseInit.PageAddress = STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE;
pEraseInit.Banks = FLASH_BANK_1;
pEraseInit.NbPages = 1;
if(HAL_FLASHEx_Erase(&pEraseInit, &PageError) != HAL_OK)
{
break;
}
/* 复制到缓存 */
for(index = 0; index < pagefre; index++)
{
*(FlashBuffer + pageoff + index) = *(pBuffer + index);
}
/* 写回FLASH */
count = Internal_WriteFlashNoCheck(STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE, FlashBuffer, STM32FLASH_PAGE_SIZE >> 1);
if(count != (STM32FLASH_PAGE_SIZE >> 1))
{
nwrite -= count;
break;
}
}
else
{
/* 无需擦除,直接写 */
uint32_t count = Internal_WriteFlashNoCheck(addrStart, pBuffer, pagefre);
if(count != pagefre)
{
nwrite -= count;
break;
}
}
pBuffer += pagefre; /* 读取地址递增 */
addrStart += (pagefre << 1); /* 写入地址递增 */
nwrite -= pagefre; /* 更新剩余未写入数据量 */
pagepos++; /* 下一页 */
pageoff = 0; /* 页内偏移地址置零 */
/* 根据剩余量计算下次写入数据量 */
pagefre = nwrite >= (STM32FLASH_PAGE_SIZE >> 1) ? (STM32FLASH_PAGE_SIZE >> 1) : nwrite;
}
/* 加锁FLASH */
HAL_FLASH_Lock();
return ((dataLen - nwrite) << 1);
}
九、举例
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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 */
uint8_t in_data[5]={11,22,33,44,55};//要写入的数据
uint8_t out_data[5];//读存放
int i;
uint32_t STATUS = 0;
STATUS = Internal_WriteFlash(0x08001800, (uint16_t *)in_data, 5);
HAL_Delay(1000);
if(STATUS)
{
Internal_ReadFlash(0x08001800, (uint16_t *)out_data, 5);
printf("\r\n The Five Data Is : \r\n");
for(i = 0; i < 5; i++)
{
printf("\r %d \r", out_data[i]);
}
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
查看打印:
十、工程代码
链接:https://pan.baidu.com/s/1zfp9AkJ5jiaugWfdKaxfQg?pwd=9kpr 提取码:9kpr
十一、注意事项
用户代码要加在 USER CODE BEGIN N
和 USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
• 由 Leung 写于 2023 年 2 月 7 日
• 参考:STM32CubeMX | STM32F1系列HAL库读写内部FLASH
STM32CubeMX系列|STM32内部FLASH
网友评论