GD32F303----内部Flash读写
目标:
在0x8010000处写入0x0034567 一共4Byte;
实验:
程序烧录在0x8000000处;
使用的是GD32F303VCT6;用户手册如下:
GD32F303VCT6-MCU选择器-兆易创新 GigaDevice | 官方网站
代码:
#include "gd32f30x.h"
#include "SEGGER_RTT.h"
#define erase_ADDRESS 0x08000000 // 目标地址
#define TARGET_ADDRESS 0x08010000 // 目标地址
/* Base address of the Flash sectors */
#define PAGE_SIZE (0x800) /* 2 Kbytes */
#define FLASH_SIZE (0x40000) /* 256 KBytes */
#define EXE_APP_FLAG ((uint32_t)0x08004000)
#define APP_START_ADDRESS ((uint32_t)0x08010000)
fmc_state_enum CAN_BOOT_ErasePage(u32 StartPageAddr, u32 EndPageAddr);
fmc_state_enum CAN_BOOT_ProgramDatatoFlash(u32 *Address, u32 *Data, u32 DataNum) ;
int main(void) {
uint32_t my_variable = 0x00345678;
uint32_t target_address = TARGET_ADDRESS;
fmc_state_enum status;
// 1. 擦除目标地址所在的页
// 计算目标地址所在页的起始地址(按PAGE_SIZE对齐)
uint32_t page_start = target_address & ~(PAGE_SIZE - 1);// 0x08010000 & ~0x7FF = 0x08010000
status = CAN_BOOT_ErasePage(page_start, page_start+PAGE_SIZE-1);
if (status != FMC_READY) {
// 擦除失败,处理错误
SEGGER_RTT_printf(0, "erase status != FMC_READY");
while(1);
}
// 2. 写入数据到目标地址
status = CAN_BOOT_ProgramDatatoFlash(&target_address, &my_variable, sizeof(my_variable));
if (status != FMC_READY) {
// 写入失败,处理错误
SEGGER_RTT_printf(0, "write status != FMC_READY");
while(1);
}
// 3. 验证写入结果
if (*(__IO uint32_t*)TARGET_ADDRESS == my_variable) {
// 写入成功
SEGGER_RTT_printf(0, "write success");
} else {
// 写入失败
SEGGER_RTT_printf(0, "write fault");
}
while(1);
}
/**
* @brief 擦出指定扇区区间的Flash数据 。
* @param StartPage 起始扇区
* @param EndPage 结束扇区
* @retval 扇区擦出状态
*/
fmc_state_enum CAN_BOOT_ErasePage(u32 StartPageAddr, u32 EndPageAddr)
{
u32 i;
fmc_state_enum FLASHStatus = FMC_READY;
fmc_unlock();
//Clear All pending flags
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_END);
for(i=StartPageAddr; i<=EndPageAddr; i+=PAGE_SIZE)
{
FLASHStatus = fmc_page_erase(i);
if(FLASHStatus != FMC_READY) {
fmc_lock();
return FLASHStatus;
}
}
fmc_lock();
return FLASHStatus;
}
/**
* @brief 将数据烧写到指定地址的Flash中 。
* @param Address Flash起始地址。
* @param Data 数据存储区起始地址。
* @param DataNum 数据字节数。
* @retval 数据烧写状态。
*/
fmc_state_enum CAN_BOOT_ProgramDatatoFlash(u32 *Address, u32 *Data, u32 DataNum)
{
fmc_state_enum FLASHStatus = FMC_READY;
u32 *p_Data=Data;
u32 i;
if(*Address < EXE_APP_FLAG) {
return FMC_PGERR;
}
fmc_unlock();
//Clear All pending flags
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_END);
for(i=0;i<(DataNum>>2);i++)
{
FLASHStatus = fmc_word_program(*Address, *p_Data);
if (FLASHStatus == FMC_READY) {
*Address += 4;
p_Data++;
} else {
fmc_lock();
return FLASHStatus;
}
}
fmc_lock();
return FLASHStatus;
}
结果:
当时遇到一个问题:
能看到 页Page的大小为2KB (0x800)
程序中特意指定了大小:
#define PAGE_SIZE (0x800) /* 2 Kbytes */
擦除时候,还一个起始地址问题
1. 比如说我要擦除
0x0801 0000页; 那么他擦的就是0x0801 0000 - 0x0801 07FF;大小为2KB;
2. 比如说我要擦除
0x0801 000A地址处的页; 那么他擦的依旧是0x0801 0000 - 0x0801 07FF;大小为2KB;
这就是涉及到一个字节对其的问题:
解决方案:位操作(如 address & ~0x7FF
)验证对齐性,避免依赖十进制计算的近似结果。
以下是我搜索的结果:
数据对齐的含义与重要性:
在计算机系统中,“32位数据需要4字节对齐”意味着该数据的起始内存地址必须是4的倍数(即地址的低2位为0b00
)。例如:
-
对齐地址:
0x0800_0000
、0x0800_0004
、0x0800_0008
(地址末位是0x0
,0x4
,0x8
,0xC
)。 -
非对齐地址:
0x0800_0001
、0x0800_0003
(末位是0x1
,0x3
)。
因此说:0x0801 000A地址不是四字节对其,通过 address & ~0x7FF
验证对齐性,
// 1. 擦除目标地址所在的页
// 计算目标地址所在页的起始地址(按PAGE_SIZE对齐)
uint32_t page_start = target_address & ~(PAGE_SIZE - 1);// 0x08010000 & ~0x7FF = 0x08010000
其实也可以看到 在GD32F303手册里面 flash的划分如下:
为什么需要对齐?
-
硬件要求:
-
某些处理器(如ARM Cortex-M)严格要求对齐访问。如果尝试从非对齐地址读取32位数据,会触发硬件错误(如
HardFault
)。 -
例如,在STM32中,访问未对齐的32位数据可能导致异常。
-
-
性能优化:
-
对齐数据允许CPU通过单次内存操作完成读写,而非对齐数据可能需要多次访问并拼接,显著降低效率。
-
例如,从
0x0800_0001
读取32位数据时,CPU可能需要先读0x0800_0000
的4字节,再读0x0800_0004
的4字节,然后拼接出目标数据。
-
-
原子性操作:
-
对齐数据在多线程或中断场景中更容易实现原子操作(如
uint32_t
的原子读写)。
-
如何确保对齐?
-
编译器自动对齐:
-
编译器默认会对变量进行对齐。例如,声明
uint32_t data;
时,编译器会将其分配到4字节对齐的地址。
-
uint32_t data; // 编译器自动对齐到4字节边界
2.手动指定对齐:
-
使用编译器扩展语法强制对齐(如ARM的
__attribute__((aligned(4)))
):
uint32_t data __attribute__((aligned(4))); // 强制4字节对齐
3.结构体对齐控制:
-
结构体成员默认按自然对齐方式排列。可通过
#pragma pack
调整对齐规则:
#pragma pack(1) // 按1字节对齐(取消对齐)
struct {
uint8_t a; // 地址+0
uint32_t b; // 地址+1(非对齐!)
} s;
#pragma pack() // 恢复默认对齐
4.动态内存对齐:
-
动态分配内存时,使用对齐版本的分配函数(如
aligned_alloc(4, size)
):uint32_t* ptr = (uint32_t*)aligned_alloc(4, 1024); // 分配4字节对齐的内存
非对齐访问的后果
硬件异常:
uint32_t* p = (uint32_t*)0x08000001; // 非对齐地址
uint32_t value = *p; // 在Cortex-M中触发HardFault!
性能损失
以下是deepseek的知识;
补充知识
页(Page)与扇区(Sector)的区别和联系:
页(Page)与扇区(Sector)的区别和联系
页和扇区是存储介质(如 Flash、硬盘)中管理和操作数据的基本单位,但它们的定义和应用场景有所不同。以下是详细分析:
1. 定义与典型应用
概念 | 定义 | 典型场景 | 常见大小 |
---|---|---|---|
扇区(Sector) | 机械硬盘(HDD)或传统存储设备的最小读写单位,操作系统通过扇区访问磁盘数据。 | HDD、SD卡、U盘等块设备 | 512 Bytes 或 4 KB(现代设备) |
页(Page) | Flash 存储器(如 NAND/NOR Flash)的最小读写单位,但擦除需以更大的块(Block)为单位。 | SSD、嵌入式 Flash(如 GD32F30x 的片上 Flash) | 通常 4 KB(SSD)、512 B–8 KB(嵌入式 Flash) |
2. 核心区别
特性 | 扇区(Sector) | 页(Page) |
---|---|---|
读写单位 | 读写的最小单位(如每次读写 1 个扇区)。 | 读写的最小单位(如每次写 1 页)。 |
擦除单位 | 无需擦除(机械硬盘直接覆盖数据)。 | 必须按块(Block)擦除,块由多个页组成(如 64 页/块)。 |
物理介质 | 机械硬盘的磁道划分单元。 | Flash 存储器的存储单元结构。 |
寿命管理 | 无擦写次数限制(磁介质)。 | 有擦写次数限制(Flash 的 P/E 周期)。 |
3. 总结
维度 | 扇区(Sector) | 页(Page) |
---|---|---|
本质 | 传统存储设备的最小读写单位。 | Flash 存储器的最小读写单位。 |
擦除特性 | 无需擦除(直接覆盖)。 | 必须按块擦除,无法单页擦除。 |
设计目标 | 兼容传统接口(如 BIOS)。 | 适配 Flash 物理特性(寿命、速度)。 |
关联性 | 文件系统通过扇区抽象底层存储。 | Flash 控制器通过页管理实际数据。 |
关键结论:
-
在机械硬盘或通用存储协议中,扇区是核心操作单位;
-
在 Flash 设备中,页是读写单位,块是擦除单位,而术语“扇区”可能用于描述逻辑或物理划分,需结合上下文理解。
我觉得标红的地方有问题 在手册里明确写了可以进行页擦除;而且在库函数里面
有声明:
具体定义:
内存大小:
1. Total RO Size (Code + RO Data)
-
值:10040 Bytes (约 9.80KB)
-
含义:
-
RO(Read-Only) 表示只读数据,包含 代码(Code) 和 只读数据(RO Data)(如常量、字符串等)。
-
这部分数据存储在 Flash(ROM) 中,程序运行时直接从 Flash 中读取,不可修改。
-
-
作用:
衡量程序实际占用的 Flash 空间大小。若超过芯片 Flash 容量,程序将无法烧录。
2. Total RW Size (RW Data + ZI Data)
-
值:6032 Bytes (约 5.89KB)
-
含义:
-
RW(Read-Write) 表示可读写数据,包含:
-
RW Data:已初始化的全局变量和静态变量(如
int a = 10;
)。 -
ZI Data:未初始化的全局变量和静态变量(如
int b;
),在程序启动时会被清零。
-
-
这部分数据占用 RAM 空间。
-
-
作用:
衡量程序运行时对 RAM 的需求。若超过芯片 RAM 容量,程序可能无法正常运行或崩溃。
3. Total ROM Size (Code + RO Data + RW Data)
-
值:10052 Bytes (约 9.82KB)
-
含义:
-
ROM Size 是 代码(Code)、只读数据(RO Data) 和 已初始化的 RW Data 的总和。
-
RW Data 的初始值 需要存储在 Flash 中(例如
int a = 10;
的初始值10
),程序启动时从 Flash 复制到 RAM。
-
-
作用:
反映程序烧录到 Flash 中的总数据量(代码 + 常量 + RW 初始值)。必须小于芯片的 Flash 容量。