STM32补充——FLASH

server/2025/1/21 6:49:23/

目录

1.内部FLASH构成(F1)

2.FLASH读写过程(F1)

2.1内存的读取

2.2闪存的写入

2.3FLASH接口寄存器(写入 & 擦除相关)

3.FLASH相关HAL库函数简介(F1/F4/F7/H7)

4.编程实战

代码:


1.内部FLASH构成(F1)

闪存模块的子部分

作用

主存储器

用来存放代码和数据常数(如const类型到的数据)

信息块

分为两个部分:系统存储(启动程序代码)、选项字节(用户选择字节)

闪存存储器接口寄存器

用于控制闪存读写等,是整个闪存模块的控制结构

主存储器:地址范围为0x0800 0000~0x0807 FFFF,分为256页(代码中把页或扇区统称为扇区),每页2KB(小/中容量为1K)(F4/ F7/H7一个扇区为16K)。当BOOT0接地,系统将从0x0800 0000地址处开始读取代码(从主存储器启动)

信息块:
(1)系统存储大小为2KB,用来存储ST自带的启动程序,用来串口下载代码。
(2)选项字节大小为16B,一般用于设置内存的写保护、读保护当BOOT0接VCC,BOOT1接GND (串口下载程序) ,系统运行的就是这部分代码


2.FLASH读写过程(F1)

对FLASH的核心操作就是读和写。

FLASH的物理特性:只能写0,不能写1,写1靠擦除
 

2.1内存的读取

CPU运行速度比FLASH快得多,STM32F103的FLASH最快访问速度≤24MHz,CPU频率超过这个速度,得加入等待时间,否则读写FLASH可能出错,导致死机等情况。

频率范围

等待周期数(LATENCY)

 SYSCLK < 24MHz

0个等待周期

24MHz  SYSCLK ≤ 48MHz

1个等待周期

48MHz  SYSCLK ≤ 72MHz

2个等待周期

正确设置好等待周期后,从地址addr,读取数据(字节为8位,半字为16位,字为32位)

data = *(volatile uint8_t *)addr;		/* 读取一个字节数据 */
data = *(volatile uint16_t *)addr;		/* 读取一个半字数据 */
data = *(volatile uint32_t *)addr;		/* 读取一个字数据 */

 将addr强制转换为uintx_t指针,然后取该指针所指向地址的值,即可获得addr地址的数据。

注意: 在进行写或擦除操作时,不能进行代码或数据的读取操作。


2.2闪存的写入

闪存编程是由FPEC(闪存编程和擦除控制器)模块处理的。

写操作有四步:解锁、擦除、写数据、上锁:

  1. 解锁:将两个特定的解锁序列号(KEY1:0x45670123、KEY2:0xCDEF89AB)依次写入FLASH_KEYR
  2. 擦除:FLASH物理特性(只能写0,不能写1),所以写FLASH之前需要擦除,将要写入的区域变为0xFFFF。擦除操作分为:页擦除和批量擦除
  3. 写数据:擦除完成,可以向FLASH写数据,每次只能以16位方式写入
  4. 上锁:写入数据完成,需要设置FLASH_CR[LOCK]位1,重新上锁,以防数据不小心被修改。

 闪存的擦除:

闪存的写入:(注意是写半字

2.3FLASH接口寄存器(写入 & 擦除相关)

FLASH寄存器

作用

FLASH_ACR

用于使能/关闭加速功能(DCEN/ICEN/PRFTEN),且可根据fCPU控制FLASH访问时间(LATENCY)

FLASH_KEYR

用来解锁FLASH_CR,需依次写入特定序列(KEY1:0x45670123KEY2:0xCDEF89AB)

FLASH_SR

用来提供正在执行的编程和擦除操作的相关信息(BSY位指示FLASH操作正在进行)

FLASH_CR

用于配置和启动FLASH操作(LOCK/STRT/PSIZE/SNB/SER/PG

对于H7还有FLASH_CCR 清除与控制寄存器用于清除相关错误。 

 FLASH_CR各位:

  1. LOCK:指示FLASH_CR寄存器是否被锁住(1锁 0未锁)
  2. STRT:用于开始一次擦除操作(1开始)
  3. PSIZE:用于设置编程宽度(3.3V PSIZE为2)
  4. SNB:用于选择要擦除的扇区编号 SER:用于选择扇区擦除操作(页擦除置1)
  5. PG:用于选择编程操作,往FLASH写数据需置1



3.FLASH相关HAL库函数简介(F1/F4/F7/H7)

驱动函数

关联寄存器

功能描述

HAL_FLASH_Unlock(…)

FLASH_KEYR

用于解锁FLASH_CR的访问

HAL_FLASH_Lock(…)

FLASH_CR

用于锁定FLASH_CR的访问

HAL_FLASH_Program(…)

FLASH_CR

用于FLASH的写入

HAL_FLASHEx_Erase(…)

FLASH_CR/AR

用于大量擦除或擦除指定的闪存扇区

FLASH_WaitForLastOperation(…)

FLASH_SR

等待操作完成

FLASH相关结构体:FLASH_EraseInitTypeDef

F1: 
uint32_t    TypeErase         /* 擦除类型 */
uint32_t    Banks                /* 擦除的bank编号(整片擦除) */
uint32_t    PageAddress    /* 擦除页面地址 */

uint32_t    NbPages           /* 擦除的页面数 */

F4/F7/H7:
uint32_t    TypeErase         
uint32_t    Banks        
uint32_t     Sector    
uint32_t     NbSectors
uint32_t    VoltageRange    



4.编程实战

F103ZET6 stmflash_write思路:

每个扇区(页)是2KB,也就是2048个地址

写任何一个地址前,如果该地址的值并不是0xFF,需先擦除再写


  1. 根据w_addr,确定No.sector号以及w_addr在该sector的偏移
  2. 根据w_addr和length,确定写入的内容是否跨sector
  3. 确定好要操作的sector以及sector的地址范围
  4. 遍历要写的地址区域数据是否都是0xFF,如果都是不用擦除,否则需要先读出保存在buf后擦除
  5. 把该sector要操作的数据,也写到buf,最后一次性把buf写入到这个对应的sector即可

跨扇区:需要注意偏移(扇区地址 / 扇区中的偏移 / 写入数据的偏移 / 写地址偏移 / 写入长度) 


代码:

/*** @brief       从指定地址读取一个半字 (16位数据)* @param       faddr   : 读取地址 (此地址必须为2的倍数!!)* @retval      读取到的数据 (16位)*/
uint16_t stmflash_read_halfword(uint32_t faddr)
{return *(volatile uint16_t *)faddr;
}
/*** @brief       从指定地址开始读出指定长度的数据* @param       raddr : 起始地址* @param       pbuf  : 数据指针* @param       length: 要读取的半字(16位)数,即2个字节的整数倍* @retval      无*/
void stmflash_read(uint32_t raddr, uint16_t *pbuf, uint16_t length)
{uint16_t i;for (i = 0; i < length; i++){pbuf[i] = stmflash_read_halfword(raddr);    /* 读取2个字节 */raddr += 2; /* 偏移2个字节 */}
}
/*** @brief       不检查的写入这个函数的假设已经把原来的扇区擦除过再写入* @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)* @param       pbuf    : 数据指针* @param       length  : 要写入的 半字(16位)数* @retval      无*/
void stmflash_write_nocheck(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{uint16_t i;for (i = 0; i < length; i++){HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, waddr, pbuf[i]);waddr += 2;     /* 指向下一个半字 */}
}
/*** @brief       在FLASH 指定位置, 写入指定长度的数据(自动擦除)*   @note      该函数往 STM32 内部 FLASH 指定位置写入指定长度的数据*              该函数会先检测要写入的扇区是否是空(全0XFFFF)的?, 如果*              不是, 则先擦除, 如果是, 则直接往扇区里面写入数据.*              数据长度不足扇区时,自动被回擦除前的数据* @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)* @param       pbuf    : 数据指针* @param       length  : 要写入的 半字(16位)数* @retval      无*/
uint16_t g_flashbuf[STM32_SECTOR_SIZE / 2]; /* 最多是2K字节 */
void stmflash_write(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{uint32_t secpos;        /* 扇区地址 */uint16_t secoff;        /* 扇区内偏移地址(16位字计算) */uint16_t secremain;     /* 扇区内剩余地址(16位字计算) */uint16_t i;uint32_t offaddr;       /* 去掉0X08000000后的地址 */FLASH_EraseInitTypeDef flash_eraseop;uint32_t erase_addr;    /* 擦除错误,这个值为发生错误的扇区地址 */if (waddr < STM32_FLASH_BASE || (waddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE))){return;     /* 非法地址 */}HAL_FLASH_Unlock();     /* FLASH解锁 */offaddr = waddr - STM32_FLASH_BASE;             /* 实际偏移地址. */secpos = offaddr / STM32_SECTOR_SIZE;           /* 扇区地址  0~255 for STM32F103ZET6 */secoff = (offaddr % STM32_SECTOR_SIZE) / 2;     /* 在扇区内的偏移(2个字节为基本单位.) */secremain = STM32_SECTOR_SIZE / 2 - secoff;     /* 扇区剩余空间大小 */if (length <= secremain){secremain = length; /* 不大于该扇区范围 */}while (1){stmflash_read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 读出整个扇区的内容 */for (i = 0; i < secremain; i++)                                                              /* 校验数据 */{if (g_flashbuf[secoff + i] != 0XFFFF){break;      /* 需要擦除 */}}if (i < secremain)  /* 需要擦除 */{ flash_eraseop.TypeErase = FLASH_TYPEERASE_PAGES;     /* 选择页擦除 */flash_eraseop.NbPages = 1;flash_eraseop.PageAddress = secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE;  /* 要擦除的扇区 */HAL_FLASHEx_Erase( &flash_eraseop, &erase_addr);for (i = 0; i < secremain; i++)         /* 复制 */{g_flashbuf[i + secoff] = pbuf[i];}stmflash_write_nocheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 写入整个扇区 */}else{stmflash_write_nocheck(waddr, pbuf, secremain); /* 写已经擦除了的,直接写入扇区剩余区间. */}if (length == secremain){break; /* 写入结束了 */}else       /* 写入未结束 */{secpos++;               /* 扇区地址增1 */secoff = 0;             /* 偏移位置为0 */pbuf += secremain;      /* 指针偏移 */waddr += secremain * 2; /* 写地址偏移(16位数据地址,需要*2) */length -= secremain;    /* 字节(16位)数递减 */if (length > (STM32_SECTOR_SIZE / 2)){secremain = STM32_SECTOR_SIZE / 2; /* 下一个扇区还是写不完 */}else{secremain = length; /* 下一个扇区可以写完了 */}}}HAL_FLASH_Lock(); /* 上锁 */
}


http://www.ppmy.cn/server/160104.html

相关文章

麦田物语学习笔记:创建TransitionManager控制人物场景切换

基本流程 制作场景之间的切换 1.代码思路 (1)为了实现不同场景切换,并且保持当前的persistentScene一直存在,则需要一个Manager去控制场景的加载和卸载,并且在加载每一个场景之后,都要将当前的场景Set Active Scene,保证其为激活的场景,在卸载的时候也可以方便调用当前激活的场…

excel实用工具

持续更新… 文章目录 1. 快捷键1.1 求和 2. 命令2.1 查找 vloopup 1. 快捷键 1.1 求和 windows: alt mac : command shift T 2. 命令 2.1 查找 vloopup vlookup 四个入参数 要查找的内容 &#xff08;A2 6xx1&#xff09;查找的备选集 &#xff08;C2:C19&#xff09;…

Perl语言的数据库编程

Perl语言的数据库编程 近年来&#xff0c;随着互联网和数据技术的发展&#xff0c;数据库编程变得越来越重要。在众多编程语言中&#xff0c;Perl因其强大的文本处理能力而受到许多开发者的青睐。虽然Perl在网页开发和系统管理中起着重要的作用&#xff0c;但在数据库编程方面…

Redis集群部署详解:主从复制、Sentinel哨兵模式与Cluster集群的工作原理与配置

集群部署形式 1、主从复制1.1 工作机制1.2 配置实现1.3 优缺点1.4 部署形式1.5 主从复制优化 2、Sentinel 哨兵模式2.1 工作机制2.2 配置实现2.3 优缺点2.4 哨兵机制选举流程2.5 脑裂问题解决方案 3、Redis Cluster3.1 工作机制3.2 配置实现3.3 优缺点3.4 故障转移3.5 哈希槽为…

中国石油大学(华东)自动评教工具(涵盖爬虫的基础知识,适合练手)

我开发了一个用于自动评教的工具&#xff0c;大家可以试着用用&#xff0c;下面是链接。 https://github.com/restrain11/auto_teachingEvaluate 可以点个星吗&#xff0c;感谢&#xff01;&#x1fae1; 以下是我在开发过程中学到的知识 以及 碰到的部分问题 目录 动态爬虫和静…

[系统安全] 六十一.恶意软件分析 (12)LLM赋能Lark工具提取XLM代码的抽象语法树(初探)

您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列。因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全、逆向分析和恶意代码检测,“系统安全”系列文章会更加聚焦,更加系…

word转pdf

依赖 先安装好MAVEN https://blog.csdn.net/m0_62214280/article/details/140643392 链接&#xff1a;https://pan.baidu.com/s/1jISO-TPEyLgC8RTmMJGRQw 提取码&#xff1a;9ju8 <dependency><groupId>com.aspose</groupId><artifactId>aspose-words…

Python开源AI自动化测试工具推荐:Browser Use

前言 在当今数字化时代&#xff0c;自动化技术的发展日新月异&#xff0c;Python 凭借其简洁的语法和丰富的库资源&#xff0c;成为众多开发者实现自动化任务的首选语言。 今天要为大家介绍一款令人瞩目的 Python 开源 AI 自动化工具 ——Browser Use。这款工具将 AI 能力融入…