STM32 内部 Flash 写入原理与应用详解
一、STM32 Flash 存储架构深入解析
1. 物理结构与电气特性
- NAND vs NOR Flash:STM32 采用 NOR Flash,具有随机访问能力(无需先擦除再写入),但写入速度较慢
- 存储单元结构:
- 基本存储单元为浮动栅极晶体管 (Floating Gate Transistor)
- 编程过程通过热电子注入使浮动栅积累电荷(表示逻辑 0)
- 擦除过程通过隧穿效应释放电荷(恢复逻辑 1)
- 电压要求:
- 读取:1.8V-3.6V(视具体型号而定)
- 编程 / 擦除:内部升压电路产生约 12V 高压(无需外部高压源)
- 温度影响:典型工作温度范围 - 40°C 至 + 125°C,极端温度会影响编程 / 擦除效率
2. 存储器组织方式
- 块 (block) 与扇区 (sector) 划分:
- 以 STM32F407 为例:
- 主存储器分为 12 个扇区:
- 扇区 0-3:16KB / 扇区
- 扇区 4:64KB
- 扇区 5-11:128KB / 扇区
- 系统存储器:用于存储启动代码
- OTP 区域:一次性可编程区域,512 字节
- 主存储器分为 12 个扇区:
- 以 STM32F407 为例:
- 地址映射表:
- 主存储器起始地址:0x0800 0000
- 系统存储器起始地址:0x1FFF F000
- OTP 区域地址:0x1FFF 7800-0x1FFF 7A00
3. 访问权限与保护机制
- 读保护级别:
- Level 0:无保护
- Level 1:读保护(可通过擦除解除)
- Level 2:深度读保护(不可恢复)
- 写保护机制:
- 扇区写保护:通过选项字节 (Option Bytes) 配置
- 写保护寄存器 (WRPR):控制哪些扇区可写
- 编程限制:
- 只能将 1 变为 0,不能直接将 0 变为 1
- 必须按扇区擦除(将所有位变为 1)后才能重新编程
二、Flash 编程核心原理
1. 编程算法详解
- 编程电压生成:
- 内部电荷泵电路将 VDD 升压至约 12V
- 升压时间约 100μs(典型值)
- 编程时序控制:
- 采用增量脉冲编程算法 (IPP)
- 典型编程脉冲宽度:20μs-100μs
- 每次编程后验证数据,未达到目标值则增加脉冲宽度
- 数据验证机制:
- 每写入一个字后自动读取验证
- 允许位翻转容差:±1 位(工业级标准)
2. 擦除过程分析
- 扇区擦除时序:
- 典型擦除时间:10ms-100ms(取决于扇区大小)
- 擦除电压:约 12V
- 擦除验证:
- 擦除后所有位应变为 0xFF
- 若验证失败,自动执行二次擦除
3. Flash 控制器寄存器详解
- 主要寄存器:
- FLASH_ACR:访问控制寄存器
- LATENCY 位:配置等待周期数(与 CPU 频率相关)
- PRFTEN 位:预取缓冲区使能
- ICEN 位:指令缓存使能
- DCEN 位:数据缓存使能
- FLASH_KEYR:密钥寄存器(用于解锁)
- FLASH_OPTKEYR:选项字节密钥寄存器
- FLASH_SR:状态寄存器
- BSY 位:忙标志位
- PGERR 位:编程错误标志
- WRPRTERR 位:写保护错误标志
- EOP 位:操作完成标志
- FLASH_CR:控制寄存器
- PG 位:编程使能
- SER 位:扇区擦除使能
- MER 位:整片擦除使能
- SNB [3:0]:扇区选择位
- STRT 位:启动位
- LOCK 位:锁定位
- FLASH_ACR:访问控制寄存器
三、实际应用方法详解
1. 基于 HAL 库的 Flash 操作示例
/* 包含必要的头文件 */
#include "main.h"
#include "flash.h"
/* 定义Flash操作参数 */
#define FLASH_START_ADDR 0x08070000 // 选择第11扇区(128KB)作为数据存储区
#define FLASH_SECTOR FLASH_SECTOR_11
#define FLASH_DATA_SIZE sizeof(osc_run_msg_def)
/* 定义数据结构 */
typedef struct {
uint8_t run_mode; // 运行模式(0:停止, 1:单触发, 2:连续触发)
uint8_t trig_type; // 触发类型(0:上升沿, 1:下降沿, 2:双边沿)
uint16_t trig_vol_level_ch[2]; // 触发电压阈值(CH1, CH2)
uint16_t sample_rate; // 采样率(100k-10M)
uint16_t record_length; // 记录长度(100-10000点)
uint8_t display_mode; // 显示模式(0:正常, 1:滚动, 2:XY)
uint8_t reserved[10]; // 保留字节
} osc_run_msg_def;
/* 全局变量 */
osc_run_msg_def osc_run_msg; // RAM中的数据缓冲区
uint32_t flash_errors = 0; // Flash操作错误计数
/* 初始化Flash操作 */
void Flash_Init(void)
{
// 使能Flash时钟
__HAL_RCC_FLASH_CLK_ENABLE();
// 配置Flash等待周期(根据CPU频率调整)
// 例如: 168MHz时需配置为5个等待周期
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
// 从Flash读取配置数据到RAM
Flash_ReadConfig(&osc_run_msg);
}
/* 解锁Flash */
HAL_StatusTypeDef Flash_Unlock(void)
{
return HAL_FLASH_Unlock();
}
/* 锁定Flash */
HAL_StatusTypeDef Flash_Lock(void)
{
return HAL_FLASH_Lock();
}
/* 擦除Flash扇区 */
HAL_StatusTypeDef Flash_EraseSector(uint32_t sector, uint8_t nSectors)
{
HAL_StatusTypeDef status;
FLASH_EraseInitTypeDef eraseInit;
uint32_t sectorError = 0;
eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
eraseInit.Sector = sector;
eraseInit.NbSectors = nSectors;
eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 2.7V-3.6V
status = HAL_FLASHEx_Erase(&eraseInit, §orError);
if(status != HAL_OK) {
flash_errors++;
return status;
}
return HAL_OK;
}
/* 写入数据到Flash */
HAL_StatusTypeDef Flash_WriteData(uint32_t address, uint32_t *data, uint32_t size)
{
HAL_StatusTypeDef status;
uint32_t i;
for(i = 0; i < size; i++) {
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address + (i * 4), data[i]);
if(status != HAL_OK) {
flash_errors++;
return status;
}
}
return HAL_OK;
}
/* 从Flash读取数据 */
void Flash_ReadData(uint32_t address, uint32_t *data, uint32_t size)
{
uint32_t i;
for(i = 0; i < size; i++) {
data[i] = *(__IO uint32_t*)(address + (i * 4));
}
}
/* 保存配置到Flash */
HAL_StatusTypeDef Flash_SaveConfig(osc_run_msg_def *config)
{
HAL_StatusTypeDef status;
uint32_t *pConfig = (uint32_t*)config;
uint32_t configSize = FLASH_DATA_SIZE / 4;
// 确保结构体大小是4的倍数
if(FLASH_DATA_SIZE % 4 != 0) {
configSize++;
}
// 解锁Flash
status = Flash_Unlock();
if(status != HAL_OK) {
return status;
}
// 擦除扇区
status = Flash_EraseSector(FLASH_SECTOR, 1);
if(status != HAL_OK) {
Flash_Lock();
return status;
}
// 写入数据
status = Flash_WriteData(FLASH_START_ADDR, pConfig, configSize);
// 锁定Flash
Flash_Lock();
return status;
}
/* 从Flash读取配置 */
void Flash_ReadConfig(osc_run_msg_def *config)
{
uint32_t *pConfig = (uint32_t*)config;
uint32_t configSize = FLASH_DATA_SIZE / 4;
// 确保结构体大小是4的倍数
if(FLASH_DATA_SIZE % 4 != 0) {
configSize++;
}
// 从Flash读取数据
Flash_ReadData(FLASH_START_ADDR, pConfig, configSize);
// 验证数据有效性(简单检查)
if(config->run_mode > 2 || config->trig_type > 2) {
// 数据无效,使用默认值
memset(config, 0, FLASH_DATA_SIZE);
config->run_mode = 1; // 默认单触发模式
config->trig_type = 0; // 默认上升沿触发
config->trig_vol_level_ch[0] = 2048; // 默认触发阈值为中点(12位ADC)
config->trig_vol_level_ch[1] = 2048;
config->sample_rate = 1000; // 默认1MHz采样率
config->record_length = 1000; // 默认1000点记录长度
config->display_mode = 0; // 默认正常显示模式
}
}
/* 示例: 在主程序中使用Flash存储功能 */
void Main_Function(void)
{
// 初始化Flash
Flash_Init();
// 主循环
while(1) {
// 检查是否需要保存配置(例如按键触发)
if(config_change_flag) {
// 保存当前配置到Flash
if(Flash_SaveConfig(&osc_run_msg) == HAL_OK) {
// 保存成功,更新状态LED
HAL_GPIO_WritePin(LED_SUCCESS_GPIO_Port, LED_SUCCESS_Pin, GPIO_PIN_SET);
HAL_Delay(200);
HAL_GPIO_WritePin(LED_SUCCESS_GPIO_Port, LED_SUCCESS_Pin, GPIO_PIN_RESET);
} else {
// 保存失败,错误处理
HAL_GPIO_WritePin(LED_ERROR_GPIO_Port, LED_ERROR_Pin, GPIO_PIN_SET);
// 增加重试逻辑...
}
config_change_flag = 0;
}
// 其他主程序逻辑...
}
}
2. 基于寄存器操作的 Flash 底层实现
/* Flash操作底层函数 */
/* 解锁Flash控制器 */
void FLASH_Unlock(void)
{
// 检查是否已锁定
if(FLASH->CR & FLASH_CR_LOCK) {
// 写入解锁序列
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
}
/* 锁定Flash控制器 */
void FLASH_Lock(void)
{
FLASH->CR |= FLASH_CR_LOCK;
}
/* 等待Flash操作完成 */
uint8_t FLASH_WaitForLastOperation(uint32_t Timeout)
{
uint8_t status = FLASH_COMPLETE;
// 等待操作完成或超时
while((FLASH->SR & FLASH_SR_BSY) && (Timeout != 0)) {
Timeout--;
}
if(Timeout == 0) {
status = FLASH_TIMEOUT;
} else {
// 检查错误标志
if(FLASH->SR & FLASH_SR_PGERR) {
status = FLASH_ERROR_PROGRAM;
}
if(FLASH->SR & FLASH_SR_WRPERR) {
status = FLASH_ERROR_WRITE_PROTECTION;
}
// 清除错误标志
FLASH->SR = FLASH_SR_PGERR | FLASH_SR_WRPERR | FLASH_SR_OPERR;
}
return status;
}
/* 擦除Flash扇区 */
uint8_t FLASH_EraseSector(uint32_t Sector, uint8_t VoltageRange)
{
uint8_t status = FLASH_COMPLETE;
// 检查参数
if((Sector > FLASH_SECTOR_11) ||
(VoltageRange > FLASH_VOLTAGE_RANGE_3)) {
return FLASH_ERROR_INVAL_PARAM;
}
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 设置电压范围
FLASH->CR &= ~FLASH_CR_VRP;
FLASH->CR |= VoltageRange << 14;
// 选择扇区
FLASH->CR &= ~FLASH_CR_SNB;
FLASH->CR |= Sector << 3;
// 使能扇区擦除
FLASH->CR |= FLASH_CR_SER;
// 开始擦除
FLASH->CR |= FLASH_CR_STRT;
// 等待操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
// 清除SER位
FLASH->CR &= ~FLASH_CR_SER;
}
return status;
}
/* 写入半字(16位)到Flash */
uint8_t FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
uint8_t status = FLASH_COMPLETE;
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 使能编程
FLASH->CR |= FLASH_CR_PG;
// 写入数据
*(__IO uint16_t*)Address = Data;
// 等待操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
// 清除PG位
FLASH->CR &= ~FLASH_CR_PG;
}
return status;
}
/* 写入字(32位)到Flash */
uint8_t FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
uint8_t status = FLASH_COMPLETE;
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 使能编程
FLASH->CR |= FLASH_CR_PG;
// 写入低16位
*(__IO uint16_t*)Address = (uint16_t)Data;
// 等待低16位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 写入高16位
*(__IO uint16_t*)(Address + 2) = (uint16_t)(Data >> 16);
// 等待高16位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
}
// 清除PG位
FLASH->CR &= ~FLASH_CR_PG;
}
return status;
}
/* 写入双字(64位)到Flash */
uint8_t FLASH_ProgramDoubleWord(uint32_t Address, uint64_t Data)
{
uint8_t status = FLASH_COMPLETE;
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 使能编程
FLASH->CR |= FLASH_CR_PG;
// 写入低32位
*(__IO uint32_t*)Address = (uint32_t)Data;
// 等待低32位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 写入高32位
*(__IO uint32_t*)(Address + 4) = (uint32_t)(Data >> 32);
// 等待高32位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
}
// 清除PG位
FLASH->CR &= ~FLASH_CR_PG;
}
return status;
}
/* 读取Flash数据 */
uint32_t FLASH_ReadWord(uint32_t Address)
{
return *(__IO uint32_t*)Address;
}
/* 读取Flash半字 */
uint16_t FLASH_ReadHalfWord(uint32_t Address)
{
return *(__IO uint16_t*)Address;
}
四、Flash 操作高级技巧与最佳实践
1. 提高写入效率的技巧
- 批量写入:将多个小数据合并为一次写入操作
- 预取缓冲区优化:
// 使能预取缓冲区 FLASH->ACR |= FLASH_ACR_PRFTEN; // 使能指令缓存 FLASH->ACR |= FLASH_ACR_ICEN; // 使能数据缓存 FLASH->ACR |= FLASH_ACR_DCEN;
- 优化等待周期:根据 CPU 频率调整等待周期数
// 例如: 72MHz时设置为2个等待周期 FLASH->ACR &= ~FLASH_ACR_LATENCY; FLASH->ACR |= FLASH_ACR_LATENCY_2WS;
2. 提高可靠性的方法
- 校验机制:
- CRC 校验:
uint32_t CalculateCRC32(uint32_t *data, uint32_t size) { uint32_t i; uint32_t crc = 0xFFFFFFFF; for(i = 0; i < size; i++) { crc = HAL_CRC_Calculate(&hcrc, &data[i], 1); } return crc; }
- 简单校验和:
uint16_t CalculateChecksum(uint8_t *data, uint32_t size) { uint32_t i; uint16_t checksum = 0; for(i = 0; i < size; i++) { checksum += data[i]; } return checksum; }
- CRC 校验:
- 写入保护:
// 配置扇区写保护 void FLASH_EnableSectorWriteProtection(uint32_t SectorMask) { FLASH_OBProgramInitTypeDef obInit; HAL_FLASHEx_OBGetConfig(&obInit); obInit.WRPState = OB_WRPSTATE_ENABLE; obInit.WRPSector = SectorMask; HAL_FLASHEx_OBProgram(&obInit); }
- 掉电保护:
- 在写入前检查电源电压
if(__HAL_PWR_GET_FLAG(PWR_FLAG_VREFINTRDY) == RESET) { // 电压不稳定,推迟写入操作 return; }
3. 延长 Flash 寿命的策略
- 磨损均衡算法:
// 简化的磨损均衡实现 uint32_t GetNextFlashAddress(void) { static uint32_t currentSector = 0; uint32_t address; // 循环使用不同扇区 address = FLASH_START_ADDR + (currentSector * FLASH_SECTOR_SIZE); currentSector = (currentSector + 1) % NUM_FLASH_SECTORS; return address; }
- 减少写入频率:
- 使用脏标志位,仅在数据真正变化时写入
if(dirty_flag) { Flash_SaveConfig(&config); dirty_flag = 0; }
- 数据压缩:
- 对于大量数据,可先压缩再存储
#include "lz4.h" void CompressAndSaveData(uint8_t *srcData, uint32_t srcSize) { uint8_t compressedData[1024]; int compressedSize; // 压缩数据 compressedSize = LZ4_compress_default((char*)srcData, (char*)compressedData, srcSize, sizeof(compressedData)); if(compressedSize > 0) { // 保存压缩后的数据和大小 Flash_WriteData(FLASH_START_ADDR, (uint32_t*)&compressedSize, 1); Flash_WriteData(FLASH_START_ADDR + 4, (uint32_t*)compressedData, (compressedSize + 3) / 4); } }
五、常见问题与解决方案
1. 写入失败的常见原因及解决
问题表现 | 可能原因 | 解决方案 |
---|---|---|
BSY 位长时间置位 | 电压不稳定 | 检查电源稳定性,添加滤波电容 |
PGERR 错误 | 尝试对已编程位再次编程 | 确保先擦除再写入 |
WRPRTERR 错误 | 扇区被写保护 | 检查选项字节,解除写保护 |
写入数据错误 | 数据对齐问题 | 确保按字 (4 字节) 对齐写入 |
擦除时间过长 | Flash 温度过高 | 检查散热条件,降低工作温度 |
2. 数据损坏的检测与恢复
- 检测方法:
// 读取数据并验证CRC uint32_t storedCRC, calculatedCRC; // 读取存储的CRC storedCRC = FLASH_ReadWord(FLASH_START_ADDR + FLASH_DATA_SIZE); // 读取并计算数据CRC calculatedCRC = CalculateCRC32((uint32_t*)FLASH_START_ADDR, FLASH_DATA_SIZE / 4); if(storedCRC != calculatedCRC) { // 数据损坏,使用默认值 LoadDefaultConfig(); }
- 恢复机制:
// 双备份存储方案 #define FLASH_BACKUP_ADDR (FLASH_START_ADDR + FLASH_SECTOR_SIZE) void SaveConfigWithBackup(osc_run_msg_def *config) { // 先写入主存储区 Flash_SaveConfig(config); // 再写入备份区 Flash_SaveConfigAtAddress(config, FLASH_BACKUP_ADDR); } void LoadConfigWithRecovery(osc_run_msg_def *config) { // 先尝试从主存储区读取 Flash_ReadConfig(config); // 验证数据有效性 if(!IsConfigValid(config)) { // 主存储区数据无效,从备份区恢复 Flash_ReadConfigAtAddress(config, FLASH_BACKUP_ADDR); // 若备份区也无效,使用默认值 if(!IsConfigValid(config)) { LoadDefaultConfig(config); } } }
3. Flash 操作对系统性能的影响
- 性能分析:
- 典型扇区擦除时间:50-100ms
- 典型字写入时间:20-50μs
- 擦除 / 写入期间 Flash 控制器不可访问,可能导致 CPU 等待
- 优化建议:
- 在低优先级任务中执行 Flash 操作
void LowPriorityTask(void) { // 检查是否需要保存配置 if(config_save_pending) { Flash_SaveConfig(&config); config_save_pending = 0; } }
- 使用 DMA 加速数据传输(适用于大容量数据)
// 配置DMA传输 HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)srcBuffer, (uint32_t)dstBuffer, dataSize); // 等待DMA传输完成 HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream0, HAL_DMA_FULL_TRANSFER, 100);
六、实际应用案例
1. 存储配置参数的应用
- 使用场景:
- 示波器参数配置(如您提供的代码示例)
- WiFi 连接信息(SSID、密码)
- 用户偏好设置(显示亮度、音量等)
- 实现步骤:
- 定义配置结构体
- 初始化时从 Flash 读取配置
- 用户修改配置后标记为脏
- 定时或在特定事件(如系统空闲、按键释放)时保存配置
2. 日志记录系统
- 实现思路:
#define LOG_BUFFER_SIZE 1024 #define LOG_FLASH_SECTOR FLASH_SECTOR_10 typedef struct { uint32_t timestamp; uint8_t logType; uint16_t data[8]; } log_entry_t; static log_entry_t logBuffer[LOG_BUFFER_SIZE]; static uint16_t logIndex = 0; static uint8_t logDirty = 0; void AddLogEntry(uint8_t type, uint16_t *data, uint8_t dataSize) { // 添加日志条目到缓冲区 logBuffer[logIndex].timestamp = HAL_GetTick(); logBuffer[logIndex].logType = type; // 复制数据 for(uint8_t i = 0; i < dataSize && i < 8; i++) { logBuffer[logIndex].data[i] = data[i]; } // 更新索引 logIndex = (logIndex + 1) % LOG_BUFFER_SIZE; logDirty = 1; } void SaveLogsToFlash(void) { if(logDirty) { uint32_t flashAddr = FLASH_BASE + (LOG_FLASH_SECTOR * FLASH_SECTOR_SIZE); // 解锁Flash FLASH_Unlock(); // 擦除扇区 FLASH_EraseSector(LOG_FLASH_SECTOR, FLASH_VOLTAGE_RANGE_3); // 写入日志数量 FLASH_ProgramWord(flashAddr, logIndex); flashAddr += 4; // 写入日志数据 for(uint16_t i = 0; i < logIndex; i++) { FLASH_ProgramWord(flashAddr, logBuffer[i].timestamp); flashAddr += 4; FLASH_ProgramHalfWord(flashAddr, logBuffer[i].logType); flashAddr += 2; for(uint8_t j = 0; j < 8; j++) { FLASH_ProgramHalfWord(flashAddr, logBuffer[i].data[j]); flashAddr += 2; } } // 锁定Flash FLASH_Lock(); logDirty = 0; } }
3. 固件更新应用
- 双区存储方案:
#define BOOTLOADER_SIZE 0x4000 // 16KB Bootloader #define APP1_START_ADDR 0x08004000 // 应用程序1起始地址 #define APP2_START_ADDR 0x08040000 // 应用程序2起始地址 #define APP_SIZE 0x3C000 // 每个应用程序最大60KB typedef enum { BOOT_APP1, BOOT_APP2, UPDATE_APP1, UPDATE_APP2 } boot_mode_t; // 启动模式存储在特定Flash位置 #define BOOT_MODE_ADDR 0x1FFF7800 // OTP区域起始地址 void SetBootMode(boot_mode_t mode) { FLASH_Unlock(); FLASH_ProgramHalfWord(BOOT_MODE_ADDR, (uint16_t)mode); FLASH_Lock(); } boot_mode_t GetBootMode(void) { return (boot_mode_t)FLASH_ReadHalfWord(BOOT_MODE_ADDR); } void JumpToApplication(uint32_t appAddress) { typedef void (*pFunction)(void); uint32_t JumpAddress; pFunction Jump_To_Application; // 检查栈顶地址是否合法 if(((uint32_t*)appAddress)[0] > 0x20000000 && ((uint32_t*)appAddress)[0] < 0x20040000) { // 关闭所有中断 __disable_irq(); // 清除所有挂起的中断 NVIC->ICER[0] = 0xFFFFFFFF; NVIC->ICER[1] = 0xFFFFFFFF; NVIC->ICPR[0] = 0xFFFFFFFF; NVIC->ICPR[1] = 0xFFFFFFFF; // 设置MSP __set_MSP(*(uint32_t*)appAddress); // 跳转到应用程序 JumpAddress = *(uint32_t*)(appAddress + 4); Jump_To_Application = (pFunction)JumpAddress; Jump_To_Application(); } }