在 STM32 微控制器中,闪存是一种用来存储程序代码和非易失性数据的存储器,这意味着即使在断电后数据也不会丢失。
目录
闪存操作基础
-
解锁闪存 (
FLASH_Unlock
):- 首先调用
FLASH_Unlock
函数解锁闪存控制器,以允许写操作。 - 如果闪存控制寄存器被锁定,这一步是必须的,否则无法进行写操作。
- 首先调用
-
清除标志 (
FLASH_ClearFlag
):- 清除闪存操作的各种标志位(包括忙标志、操作结束标志、编程错误标志和写保护错误标志),确保没有残留的错误标志影响后续操作。
-
擦除闪存页 (
FLASH_ErasePage
):- 擦除包含目标地址的闪存页。
- 在 STM32 微控制器中,闪存是以页为单位进行擦除的,而不是按字节或字进行擦除。这一步是为了确保目标地址上的旧数据被清除。
-
写入数据 (
FLASH_ProgramHalfWord
):- 将 16 位数据(半字)写入指定地址。
- 这个操作在目标地址写入新的数据。
-
再次清除标志 (
FLASH_ClearFlag
):- 再次清除所有闪存操作标志,确保操作完成后没有残留的错误标志。
-
锁定闪存 (
FLASH_Lock
):- 最后调用
FLASH_Lock
函数重新锁定闪存控制器,以防止意外的写操作。
- 最后调用
下面具体使用按键控制LED的程序来展示flash的基础用法
完整工程如下:
文件束
flash.c
FLASH_W
函数的作用是将 16 位的数据写入到指定的闪存地址,通过解锁闪存、清除标志位、擦除页、写入数据、再次清除标志位和锁定闪存的步骤来确保数据写入的正确性和安全性。FLASH_R
函数的作用是从指定的闪存地址读取 16 位的数据,并返回读取的数据。
#include "flash.h"
//闪存写入函数
void FLASH_W(u32 add, u16 dat) {
FLASH_Unlock(); // 解锁闪存
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 清除闪存标志位
FLASH_ErasePage(add); // 擦除闪存页
FLASH_ProgramHalfWord(add, dat); // 写入数据到闪存
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 再次清除闪存标志位
FLASH_Lock(); // 锁定闪存
}
//闪存读取函数
u16 FLASH_R(u32 add) {
u16 a;
a = *(u16*)(add); // 从指定地址读取16位数据
return a; // 返回读取的数据
}
main.c
这段代码的核心逻辑是通过按键输入来增加一个计数值,并通过 LED 显示该计数值。同时,将计数值存储在闪存中,以便在下次上电时可以读取并继续使用。具体操作步骤包括系统初始化、读取闪存数据、检测按键、更新计数值、显示在 LED 上以及将新值写入闪存。这种方式确保了计数值在断电后不会丢失。
#include "stm32f10x.h"
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "flash.h"
#define FLASH_START_ADDR 0x0801f000 // 闪存起始地址
int main (void) {
u16 a; // 定义变量,用于存储从闪存读取的值
RCC_Configuration(); // 配置系统时钟
LED_Init(); // 初始化LED
KEY_Init(); // 初始化按键
a = FLASH_R(FLASH_START_ADDR); // 从闪存读取数据
GPIO_Write(LEDPORT, a); // 将读取的数据写入LED端口,点亮对应的LED
while (1) { // 主循环
if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { // 检测按键是否按下
delay_ms(20); // 延时去抖
if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { // 再次检测按键,确保按键稳定按下
a++; // 增加计数值
if (a > 3) { // 如果计数值超过3,重置为0
a = 0;
}
GPIO_Write(LEDPORT, a); // 更新LED显示
FLASH_W(FLASH_START_ADDR, a); // 将新的计数值写入闪存
while (!GPIO_ReadInputDataBit(KEYPORT, KEY1)); // 等待按键松开
}
}
}
}
key.h
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
//#define KEY1 PAin(0)// PA0
//#define KEY2 PAin(1)// PA1
#define KEYPORT GPIOA // 按键端口
#define KEY1 GPIO_Pin_0 // 按键1对应的引脚
#define KEY2 GPIO_Pin_1 // 按键2对应的引脚
void KEY_Init(void); // 按键初始化函数声明
#endif
key.c
#include "key.h"
void KEY_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2; // 配置KEY1和KEY2引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 设置为上拉输入模式
GPIO_Init(KEYPORT, &GPIO_InitStructure); // 初始化引脚
}
led.h
#ifndef __LED_H
#define __LED_H
#include "sys.h"
//#define LED1 PBout(0)// PB0
//#define LED2 PBout(1)// PB1
#define LEDPORT GPIOB // LED端口
#define LED1 GPIO_Pin_0 // LED1对应的引脚
#define LED2 GPIO_Pin_1 // LED2对应的引脚
void LED_Init(void); // LED初始化函数声明
#endif
led.c
#include "led.h"
void LED_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); // 使能GPIOA、GPIOB和GPIOC时钟
GPIO_InitStructure.GPIO_Pin = LED1 | LED2; // 配置LED1和LED2引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为50MHz
GPIO_Init(LEDPORT, &GPIO_InitStructure); // 初始化引脚
}
flash.h
#ifndef __FLASH_H
#define __FLASH_H
#include "sys.h"
void FLASH_W(u32 add, u16 dat); // 闪存写入函数声明
u16 FLASH_R(u32 add); // 闪存读取函数声明
#endif
总结:
如此看来,Flash 的操作方法很简单,只要在程序中想存代者数据的地方加入一行 Flash 写操作,给出地址和数据即可。注意:要写入的数据必须是 16 位的,程序中为写入数据还特别定义了 16 位的变量 a。下一步是读出,在程序中想读出的地方调用 FLASH R 函数:给出读取地址就能从中读出数据。
关于 Flash 操作需要注意几个细节。第一是写入地址不能和用户程序相冲突。刚才说过,在 Flash中有地址分布规则,下载的用户程序(HEX文件)是要在第 0开始写入的。根据程序大小,会占用不同大小的 Flash 空间。如果你想在运行时保存临时数据,你的数据就不能和下载程序空间重叠,否则会破坏程序内容。所以建议把临时数据放到比较靠后的地址中。在今后的项目开发中,如果要存储临时数据,你就需要考虑用户程序所占用的空间大小,然后在用户程序没有5用的空白区域存放临时数据.