#include "stm32f1xx.h"
#include <stdint.h>
#include <string.h>
// 定义 Flash 页大小 (根据具体型号修改,常见为 1024 或 2048)
#define FLASH_PAGE_SIZE 1024
// 定义要操作的页索引 (0 表示第一页)
// 注意:不要操作存放当前代码的页!通常使用最后几页作为数据存储区
#define TARGET_PAGE_INDEX 62 // 例如操作倒数第二页 (假设 128KB 容量共 64 页)
// 计算目标页的起始地址
// STM32F103 Flash 基地址通常为 0x08000000
#define FLASH_BASE_ADDR 0x08000000
#define TARGET_PAGE_ADDR (FLASH_BASE_ADDR + (TARGET_PAGE_INDEX * FLASH_PAGE_SIZE))
// 定义一个缓冲区用于暂存数据 (必须大于等于页大小)
uint8_t page_buffer[FLASH_PAGE_SIZE];
/**
* @brief 解锁 Flash
*/
void Flash_Unlock(void) {
if (FLASH->CR & FLASH_CR_LOCK) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
}
/**
* @brief 锁定 Flash
*/
void Flash_Lock(void) {
FLASH->CR |= FLASH_CR_LOCK;
}
/**
* @brief 等待 Flash 操作完成
* @return 0: 成功, 1: 错误 (如写保护)
*/
uint8_t Flash_WaitReady(void) {
volatile uint32_t timeout = 0xFFFFF;
while ((FLASH->SR & FLASH_SR_BSY) && (timeout--));
if (timeout == 0) return 1; // 超时
if (FLASH->SR & (FLASH_SR_WRPRTERR | FLASH_SR_PGERR)) {
FLASH->SR |= (FLASH_SR_WRPRTERR | FLASH_SR_PGERR); // 清除错误标志
return 1;
}
return 0;
}
/**
* @brief 擦除指定的一页
* @param PageAddress: 页的起始地址 (必须是页对齐的)
* @return 0: 成功, 1: 失败
*/
uint8_t Flash_Erase_Page(uint32_t PageAddress) {
Flash_Unlock();
// 等待就绪
if (Flash_WaitReady()) {
Flash_Lock();
return 1;
}
// 设置页擦除模式 (PER)
FLASH->CR |= FLASH_CR_PER;
// 设置要擦除的地址 (AR 寄存器)
FLASH->AR = PageAddress;
// 开始擦除 (STRT)
FLASH->CR |= FLASH_CR_STRT;
// 等待擦除完成
if (Flash_WaitReady()) {
FLASH->CR &= ~FLASH_CR_PER; // 清除 PER 位
Flash_Lock();
return 1;
}
// 清除 PER 位
FLASH->CR &= ~FLASH_CR_PER;
Flash_Lock();
return 0;
}
/**
* @brief 向 Flash 写入半字 (2 字节)
* @param Address: 写入地址 (必须 2 字节对齐)
* @param Data: 要写入的数据
* @return 0: 成功, 1: 失败
*/
uint8_t Flash_Write_HalfWord(uint32_t Address, uint16_t Data) {
Flash_Unlock();
if (Flash_WaitReady()) {
Flash_Lock();
return 1;
}
// 设置编程模式 (PG)
FLASH->CR |= FLASH_CR_PG;
// 写入数据 (直接操作地址)
*(__IO uint16_t*)Address = Data;
// 等待写入完成
if (Flash_WaitReady()) {
FLASH->CR &= ~FLASH_CR_PG;
Flash_Lock();
return 1;
}
// 验证数据 (可选,但推荐)
if (*(__IO uint16_t*)Address != Data) {
FLASH->CR &= ~FLASH_CR_PG;
Flash_Lock();
return 1;
}
FLASH->CR &= ~FLASH_CR_PG;
Flash_Lock();
return 0;
}
/**
* @brief 模拟修改 Flash 中部分数据 (读-改-写)
* @param offset: 页内的偏移量
* @param data: 新数据指针
* @param len: 数据长度
*/
void Flash_Modify_Data(uint16_t offset, uint8_t* data, uint16_t len) {
uint32_t i;
uint32_t page_start_addr = TARGET_PAGE_ADDR;
// 1. 边界检查
if (offset + len > FLASH_PAGE_SIZE) return;
// 2. 【读】将整页数据复制到 RAM 缓冲区
// 注意:如果该页是全新的(0xFFFF),直接复制即可
memcpy(page_buffer, (uint8_t*)page_start_addr, FLASH_PAGE_SIZE);
// 3. 【改】在 RAM 中修改数据
for (i = 0; i < len; i++) {
page_buffer[offset + i] = data;
}
// 4. 【擦】擦除整个页
// !!! 关键步骤:此时该页所有数据变为 0xFFFF
if (Flash_Erase_Page(page_start_addr) != 0) {
return; // 擦除失败处理
}
// 5. 【写】将缓冲区数据写回 Flash
// Flash 写入必须以 2 字节 (Half Word) 为单位
for (i = 0; i < FLASH_PAGE_SIZE; i += 2) {
uint16_t word_to_write;
// 组合两个字节为一个半字 (小端模式)
word_to_write = page_buffer | (page_buffer[i+1] << 8);
// 如果写入全 0xFFFF,理论上可以跳过以节省时间,但为了数据一致性通常全写
if (word_to_write != 0xFFFF) {
if (Flash_Write_HalfWord(page_start_addr + i, word_to_write) != 0) {
// 写入失败处理
break;
}
}
}
}
// 使用示例
/*
uint8_t new_data[] = {0xAA, 0xBB, 0xCC};
// 修改目标页偏移量为 10 的位置开始的 3 个字节
Flash_Modify_Data(10, new_data, 3);
*/
|