一、介绍:
PY32F403的FLASH内部空间是384K,代码参考了STM32的代码(具体是哪位大哥的不记得),同时做小调整,修改成PY单片机的能够直接使用的代码,只要用HAL库都能用,只需要修改一点内存存放位置即可,
flash内容暂时设定为380K-384K 的内存空间中。
修改内存的参数方式就是先更新结构体内容。然后调用my_flash_write的接口函数即可。
末尾有整个程序的下载链接。。。
二、主要程序
#include "flash.h"
savedata_t save_param;
/**
* @brief 向指定闪存地址写入数据
* @param addr 要写入数据的起始闪存地址
* @param Data 指向要写入数据数组的指针,数据类型为 uint32_t
* @param L 要写入的 uint32_t 数据的数量
* @retval 无
*/
void WriteFlash(uint32_t addr,uint32_t *Data,uint32_t L)
{
uint32_t i = 0;
// 解锁闪存,允许对闪存进行擦除和写入操作
HAL_FLASH_Unlock();
// 等待上一次闪存操作完成,超时时间为 FLASH_TIMEOUT_VALUE
FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
// 定义闪存擦除初始化结构体,用于配置擦除参数
FLASH_EraseInitTypeDef FlashSet;
// 设置擦除类型为页擦除
FlashSet.TypeErase = FLASH_TYPEERASE_PAGEERASE;
// 设置要擦除的闪存页起始地址
FlashSet.PageAddress = addr;
// 设置要擦除的页数为 1 页
FlashSet.NbPages = 1;
// 用于存储擦除过程中可能出现的页错误信息
uint32_t PageError = 0;
// 底层 HAL 库可能存在 BUG,擦除前需要先解锁
__HAL_UNLOCK(&pFlash);
// 执行闪存页擦除操作
int ret = HAL_FLASHEx_Erase(&FlashSet, &PageError);
// 检查擦除操作是否成功
if(ret != HAL_OK) {
// 若擦除失败,打印错误信息
printf("Erase Error[%d]\n",ret);
}
// 循环将数据写入闪存
for(i = 0; i < L; i++) {
// 按页编程方式将数据写入闪存指定地址
HAL_FLASH_Program(FLASH_TYPEPROGRAM_PAGE, addr + 4 * i, &Data[i]);
}
// 锁定闪存,防止意外修改
HAL_FLASH_Lock();
}
/**
* @brief 从指定闪存地址读取数据
* @param address 要读取数据的起始闪存地址
* @param data 指向用于存储读取数据的数组指针,数据类型为 uint32_t
* @param length 要读取的 uint32_t 数据的数量
* @retval 无
*/
void Flash_Read(uint32_t address, uint32_t *data, uint16_t length)
{
// 定义循环计数器,用于遍历要读取的数据数量
uint16_t i;
// 循环读取指定长度的数据
for (i = 0; i < length; i++) {
// 通过指针访问的方式,从指定闪存地址读取一个 uint32_t 数据
// __IO 是一个宏,通常用于标记该变量为易变的,告诉编译器不要对其进行过度优化
// (address + (i * 4)) 计算当前要读取数据的地址,每个 uint32_t 占 4 字节
data[i] = *(__IO uint32_t *)(address + (i * 4));
}
}
/**
* @brief 将 save_param 结构体数据写入闪存
* @note 该函数会先将结构体数据复制到 uint32_t 数组,再调用 WriteFlash 函数写入闪存
* @retval 无
*/
void my_flash_write(void)
{
// 打印当前函数名,用于调试,方便确认函数是否被调用
printf("%s\n",__func__);
// 向上取整计算所需的 uint32_t 元素数量,确保数组有足够空间存储 savedata_t 结构体数据
size_t num_elements = (sizeof(savedata_t) + sizeof(uint32_t) - 1) / sizeof(uint32_t);
// 定义一个 uint32_t 类型的数组,用于存储要写入闪存的结构体数据
uint32_t flash_data[num_elements];
// 将 save_param 结构体的数据复制到 flash_data 数组中
// sizeof(savedata_t) 表示要复制的数据大小
memcpy(flash_data, &save_param, sizeof(savedata_t));
// 调用 WriteFlash 函数,将 flash_data 数组中的数据写入指定的闪存地址
// FLASH_SAVE_ADDR 为闪存写入的起始地址
// num_elements 为要写入的 uint32_t 数据的数量
WriteFlash(FLASH_SAVE_ADDR, flash_data, num_elements);
}
/**
* @brief 从闪存中读取数据到 save_param 结构体
* @note 该函数会先从指定闪存地址读取数据到 uint32_t 数组,再将数组数据复制到 save_param 结构体
* @retval 无
*/
void my_flash_read(void)
{
// 向上取整计算所需的 uint32_t 元素数量,确保数组有足够空间存储 savedata_t 结构体数据
size_t num_elements = (sizeof(savedata_t) + sizeof(uint32_t) - 1) / sizeof(uint32_t);
// 定义一个 uint32_t 类型的数组,用于存储从闪存读取的数据
uint32_t flash_data_read[num_elements];
// 调用 Flash_Read 函数,从指定闪存地址 FLASH_SAVE_ADDR 读取 num_elements 个 uint32_t 数据到 flash_data_read 数组
Flash_Read(FLASH_SAVE_ADDR, flash_data_read, num_elements);
// 将 flash_data_read 数组中的数据复制到 save_param 结构体中
// sizeof(savedata_t) 表示要复制的数据大小
memcpy(&save_param, flash_data_read, sizeof(savedata_t));
}
/**
* @brief 初始化 save_param 结构体
* @note 将 save_param 结构体的所有成员初始化为默认值
* @retval 无
*/
void save_param_init(void)
{
memset(&save_param,0,sizeof(save_param));
save_param.verif_code = 0x55AA;
sprintf((char *)save_param.version,"MAGEAA");
sprintf((char *)save_param.password,"123456");
}
/**
* @brief 验证闪存中存储的参数版本信息
* @note 从闪存读取参数,检查版本验证码是否正确,若不正确则初始化参数并写入闪存
* @retval 无
*/
void flash_verify(void)
{
my_flash_read();
/*判断版本是否为相同*/
if(save_param.verif_code != 0x55AA) {
save_param_init();
my_flash_write();
}
flash_display_param();
}
/**
* @brief 显示闪存中存储的版本和密码信息
* @note 该函数会先从闪存读取最新的参数信息,然后打印版本和密码
* @retval 无
*/
void flash_display_param(void)
{
my_flash_read();
printf("version[%s]\n",save_param.version);
printf("password=[%s]\n",save_param.password);
}
三、头文件代码
#ifndef __FLASH_H
#define __FLASH_H
#include "py32f4xx_hal.h"
#include "main.h"
#define FLASH_SAVE_ADDR (0x0805F000) // 保存数据的起始地址 380K~384K
typedef struct {
uint32_t verif_code;
uint8_t version[7];
uint8_t password[7];
} __attribute__((packed)) savedata_t;
extern savedata_t save_param;
void WriteFlash(uint32_t addr,uint32_t *Data,uint32_t L);
void my_flash_write(void);
void my_flash_read(void);
void save_param_init(void);
void flash_verify(void);
void flash_display_param(void);
#endif
四、主程序代码
实现功能:
1、开机验证验证码,验证不通过,则进行程序参数初始化。
2、按键0按下LED0灯状态跳转,并且修改结构体密码为999999。按复位按钮观察按键内部参数是否正常更新。
3、按键1按下LED1灯状态跳转,并且修改结构体密码为666666。按复位按钮观察按键内部参数是否正常更新。
int main(void)
{
_system_clock_config();
HAL_Init(); /*hal库初始化*/
usart_init(115200); /*UART1初始化,PA9,PA10*/
led_init(); /*LED外设初始化*/
key_init(); /*按键外设初始化*/
printf("Hello World!\n");
uint8_t key_val = 0;
flash_verify();
while(1){
key_val = key_scan(0);
if(key_val) printf("keyval=[%d]\n",key_val);
switch(key_val){
case KEY0_PRES:
LED0_TOGGLE();
sprintf((char *)save_param.password,"999999");
my_flash_write();
break;
case KEY1_PRES:
LED1_TOGGLE();
sprintf((char *)save_param.password,"666666");
my_flash_write();
break;
defult:
break;
}
}
return 0;
}
五、打印结果
可以看见开机初始时,密码被写入123456,然后在按下按键2后,密码被修改为666666,重启观察也是666666.也就写入flash内了,掉电也能给生效。
同理,按按键1时,密码修改为999999,重启后也是生效的。所以这个实验是成功的。