STM32F103 读写内部FLASH
STM32F103C8T6最小系统开发板使用HAL固件库读写内部FLASH
本文将介绍如何使用STM32F103C8T6最小系统开发板,利用HAL固件库实现对内部FLASH的读写操作。通过详细的硬件设计、FLASH读写原理、软件设计及操作步骤,帮助大家快速上手。
1.硬件设计
1.1 内部FLASH存储器简介
在STM32芯片内部有一个FLASH存储器,它主要用于存储代码。
我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中。
由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行。
图:STM32的内部框架图
除了使用外部的工具(如下载器)读写内部FLASH外,STM32芯片在运行的时候,也能对自身的内部FLASH进行读写。
因此,若内部FLASH存储了应用程序后还有剩余的空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。
由于访问内部FLASH的速度要比外部的SPI-FLASH快得多,所以在紧急状态下常常会使用内部FLASH存储关键记录;
为了防止应用程序被抄袭,有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部FLASH的操作。
1.2 内部FLASH存储区
STM32的内部FLASH包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小见 表
区域 | 名称 | 块地址 | 大小 |
---|---|---|---|
主存储器 | 页0 | 0x0800 0000 - 0x0800 07FF | 2 Kbytes |
. | 页1 | 0x0800 0800 - 0x0800 0FFF | 2 Kbytes |
. | 页2 | 0x0800 1000 - 0x0800 17FF | 2 Kbytes |
. | 页3 | 0x0800 1800 - 0x0800 FFFF | 2 Kbytes |
. | . | . | . |
. | . | . | . |
. | 页255 | 0x0807 F800 - 0x0807 FFFF | 2 Kbytes |
系统存储区 | . | 0x1FFF F000 - 0x1FFF F7FF | 2 Kbytes |
选项字节 | . | 0x1FFF F800 - 0x1FFF F80F | 16 bytes |
STM32F103C8T6的内部FLASH存储器分为程序存储区和数据存储区。
程序存储区的起始地址通常是0x08000000,
数据存储区用于存放用户数据,起始地址通常是0x0801F000。
1.主存储器
一般我们说STM32内部FLASH的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的256K FLASH、512K FLASH都是指这个区域的大小。
主存储器分为256页,每页大小为2KB,共512KB。这个分页的概念,实质就是FLASH存储器的扇区,与其它FLASH一样,在写入数据前,要先按页(扇区)擦除。
芯片命名规则
- 型号范例: STM32 F 103 Z E T 6
- 家族:“STM32 “表示32bit的MCU
- 产品类型:“F”表示基础型
- 具体特性:“103”基础型
- 引脚数目:“Z”表示144个引脚, 其他常用的为:
- C :48 引脚
- R :64 引脚
- V :100 引脚
- Z :144 引脚
- B :208 引脚
- N :216 引脚
- FLASH大小:E表示512KB, 其他常用的为:
- 4:16 KB(小容量ld)
- 6:32 KB(小容量ld)
- 8:64 KB(中容量md)
- B:128 KB(中容量md)
- C:256 KB(大容量hd)
- E:512 KB(大容量hd)
- F:768 KB(超大容量xl)
- G:1024 KB(超大容量xl)
- 封装:“T”表示QFP封装,这个是最常用的封装
- 温度:“6”表示温度等级为A :-40~85°
2.系统存储区
系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB以及CAN等ISP烧录功能。
3.选项字节
选项字节用于配置FLASH的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共16字节。可以通过修改FLASH的选项控制寄存器修改。
- 电源连接:确保为STM32F103C8T6开发板提供稳定的电源。
- 下载工具:使用STLink-V2进行程序下载和调试。
2.读写内部FLASH原理
2.1 查看工程的空间分布
由于内部FLASH本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用 内部FLASH存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。
通过查询应用程序编译时产生的“*.map”后缀文件,可以了解程序存储到了哪些区域。
打开map文件后,查看文件最后部分的区域,可以看到一段以“Memory Map of the image”开头的记录
=========================================================
Memory Map of the image //存储分布映像
Image Entry point : 0x08000131
/*程序ROM的加载空间*/
/*
在芯片刚上电运行时,会加载程序及数据,例如它会从程序的存储区域加载到程序的执行区域.
还把一些已初始化的全局变量从ROM复制到RAM空间,以便程序运行时可以修改变量的内容。
加载完成后,程序开始从执行区域开始执行。
*/
/*
加载及执行空间的基地址(Base)都是0x08000000,它正好是STM32内部FLASH的首地址,
即STM32的程序存储空间就直接是执行空间。
*/
/**
它们的大小(Size)分别为0x000017a8及0x0000177c,执行空间的ROM比较小的原因就是因为部分RW-data类型的变量被拷贝到RAM空间了;
它们的最大空间(Max)均为0x00080000,即512K字节,它指的是内部FLASH的最大空间。
*/
/**
计算程序占用的空间时,需要使用加载区域的大小进行计算,
本例子中应用程序使用的内部FLASH是从0x08000000至(0x08000000+0x000017a8)地址的空间区域。
*/
Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000017a8, Max: 0x00080000, ABSOLUTE)
/*程序ROM的执行空间*/
Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x0000177c, Max: 0x00080000, ABSOLUTE)
/*地址分布列表*/
/**
在加载及执行空间总体描述之后,紧接着一个ROM详细地址分布表.
它列出了工程中的各个段(如函数、常量数据)所在的地址Base Addr及占用的空间Size,
列表中的Type说明了该段的类型,CODE表示代码,DATA表示数据,
而PAD表示段之间的填充区域,它是无效的内容,PAD区域往往是为了解决地址对齐的问题。
*/
Base Addr Size Type Attr Idx E Section Name Object
0x08000000 0x00000130 Data RO 3 RESET startup_stm32f10x_hd.o
0x08000130 0x00000000 Code RO 479 * .ARM.Collect$$$$00000000 mc_w.l(entry.o)
0x08000130 0x00000004 Code RO 742 .ARM.Collect$$$$00000001 mc_w.l(entry2.o)
0x08000134 0x00000004 Code RO 745 .ARM.Collect$$$$00000004 mc_w.l(entry5.o)
/*...此处省略大部分内容*/
0x080016e8 0x00000024 Code RO 772 .text mc_w.l(init.o)
0x0800170c 0x00000010 Code RO 483 i.__0printf$bare mc_w.l(printfb.o)
0x0800171c 0x0000000e Code RO 784 i.__scatterload_copy mc_w.l(handlers.o)
0x0800172a 0x00000002 Code RO 785 i.__scatterload_null mc_w.l(handlers.o)
0x0800172c 0x0000000e Code RO 786 i.__scatterload_zeroinit mc_w.l(handlers.o)
0x0800173a 0x00000022 Code RO 490 i._printf_core mc_w.l(printfb.o)
0x0800175c 0x00000020 Data RO 782 Region$$Table anon$$obj.o
这一段是某工程的ROM存储器分布映像,在STM32芯片中,ROM区域的内容就是指存储到内部FLASH的代码。
观察表中的最后一项,它的基地址是 0x0800175c ,大小为 0x00000020 ,可知它占用的最高的地址空间为 0x0800177c 【程序执行地址空间】,
跟执行区域的最高地址 0x0000177c 一样,但它们比加载区域说明中的最高地址 0x80017a8 【程序加载地址空间】要小,所以我们以加载区域的大小为准。
对比上表内部FLASH页地址分布表,可知仅使用页0至页2就可以完全存储本应用程序,所以从页3(地址0x08001800)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。
FLASH的读写需要在特定的操作模式下进行,并且在写入数据时,必须先解锁FLASH写入保护。
每次写入FLASH后,必须等待完成标志才能继续进行其他操作。
3.软件设计
在软件层面,通过HAL固件库的函数进行FLASH的读写操作。
以下是详细的操作步骤和代码示例。
1. 解锁FLASH写保护
在进行FLASH写操作之前,需要解锁FLASH的写保护。
#include "stm32f1xx_hal.h"
// 解锁FLASH以便进行写操作
void FLASH_Unlock(void) {
// 如果FLASH处于锁定状态,进行解锁
if (HAL_FLASH_Unlock() != HAL_OK) {
// 解锁失败处理
Error_Handler();
}
}
2. 擦除FLASH扇区
在写入数据之前,通常需要擦除FLASH中的目标扇区。
STM32F103C8T6的FLASH扇区大小为128KB,具体的擦除扇区可根据需要调整。
// 擦除指定的FLASH扇区
void FLASH_Erase_Sector(uint32_t sector, uint8_t voltage_range) {
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t SectorError;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInitStruct.Sector = sector;
EraseInitStruct.NbSectors = 1;
EraseInitStruct.VoltageRange = voltage_range;
// 擦除扇区
if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) {
// 擦除失败处理
Error_Handler();
}
}
3. 写入数据到FLASH
写入数据到FLASH时,必须确保地址在数据存储区的有效范围内。
此处以向地址0x0801F000写入一个32位数据为例。
// 写入32位数据到指定地址
void FLASH_Write(uint32_t address, uint32_t data) {
// 1.解锁FLASH
FLASH_Unlock();
// 2.擦除页,以便写入
FLASH_Erase_Sector(FLASH_SECTOR_11, FLASH_VOLTAGE_RANGE_3); // 擦除目标页
// 3.写入数据
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {
// 写入失败处理
Error_Handler();
}
// 4.锁定FLASH
if (HAL_FLASH_Lock() != HAL_OK) {
// 锁定失败处理
Error_Handler();
}
}
4. 从FLASH读取数据
读取FLASH数据是一个简单的操作,可以直接通过指针访问指定地址的数据。
// 从FLASH读取32位数据
uint32_t FLASH_Read(uint32_t address) {
return *(volatile uint32_t*)address; // 直接通过指针读取数据
}
5. 错误处理
在执行任何FLASH操作时,如果发生错误,可以通过Error_Handler
函数进行错误处理。此函数通常用于调试过程中,打印错误信息或进入死循环。
void Error_Handler(void) {
// 用户可以在此处添加错误处理代码
while (1) {
// 错误处理:可以是点亮LED,或者进入死循环
}
}
6. 示例:读取和写入FLASH
通过上面的函数,我们可以结合使用这些API来实现对FLASH的完整操作。例如:
void FLASH_Operation(void) {
uint32_t address = 0x0801F000; // 数据存储区的起始地址
uint32_t writeData = 0x12345678; // 要写入的数据
// 写入数据到FLASH
FLASH_Write(address, writeData);
// 从FLASH读取数据
uint32_t readData = FLASH_Read(address);
// 输出读取的数据(根据实际需求输出方式进行调整)
printf("Read data from FLASH: 0x%08X\n", readData);
}
4.小结
通过STM32F103C8T6内部FLASH的读写操作,可以实现数据存储功能。本文详细介绍了如何使用HAL固件库进行FLASH的读写操作,涵盖了解锁FLASH、写入数据、读取数据、擦除扇区等基本操作,并提供了相应的示例代码。通过这些操作,可以方便地在STM32F103C8T6中实现对内部FLASH的灵活应用。