STM32补充——FLASH

devtools/2025/1/24 13:33:02/

目录

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/devtools/153141.html

相关文章

云岚到家项目100问 v1.0

云岚到家项目100问 v1.0 第一章 运营基础管理 你的项目是做什么业务的&#xff1f; 家政服务&#xff0c;我的项目是一个家政o2o平台。 项目涉及哪些角色&#xff1f; 本项目涉及如下角色&#xff1a; 家政需求方&#xff1a;通过用户端小程序完成在线预约下单、支付、评价、…

HTML中的`<!DOCTYPE html>`是什么意思?

诸神缄默不语-个人CSDN博文目录 在学习HTML时&#xff0c;我们经常会看到HTML文档的开头出现<!DOCTYPE html>&#xff0c;它是HTML文件的第一行。很多初学者可能会疑惑&#xff0c;为什么需要这行代码&#xff1f;它到底有什么作用呢&#xff1f;在这篇文章中&#xff0…

冲刺蓝桥杯之速通vector!!!!!

文章目录 知识点创建增删查改 习题1习题2习题3习题4&#xff1a;习题5&#xff1a; 知识点 C的STL提供已经封装好的容器vector&#xff0c;也可叫做可变长的数组&#xff0c;vector底层就是自动扩容的顺序表&#xff0c;其中的增删查改已经封装好 创建 const int N30; vecto…

Julia语言的安全开发

Julia语言的安全开发 引言 在现代软件开发中&#xff0c;安全性是一个不可忽视的重要因素。无论是应用程序的设计、开发还是部署阶段&#xff0c;安全性都应当贯穿整个软件生命周期。近年来&#xff0c;Julia语言逐渐流行&#xff0c;因其高性能和易用性被广泛应用于科学计算…

c# 设置Regex Multiline无效问题

设置RegexOptions.Multiline后正则中$可匹配每一行的结束符,但因为windows系统使用\r\n作为结束符,而 Multiline 是能匹配\n,所以就会导致c#或者说windows开发下会导致Multiline失效. 所以在写的时候把 $ 写成 \r?$ 就可以了. 正则表达式的选项 - .NET | Microsoft Learn 默…

【25考研】也很难!清华大学计算机考研复试难度分析!

一、复试内容 复试考核注意事项&#xff1a; 1、笔试环节&#xff1a;笔试部分包括英语和专业课的考查。其中英语笔试部分把包括英语听力和口语测试&#xff1b;关于专业课考试&#xff0c;有的学校规定了考试范围&#xff0c;考生可以在初试结束后尽快开始复习&#xff1b;对…

聊一聊 CSS 样式的导入方式

一、CSS 的导入方式有哪些 1、内联样式&#xff0c;在HTML 元素上使用 style 属性&#xff0c;设置当前标签元素的样式 <p style"color: red;">Hello world!</p>2、嵌入样式表&#xff0c;直接在head标签内使用style标签定义元素样式 <head><st…

机器学习-使用梯度下降最小化均方误差

前面有一篇文章《机器学习-常用的三种梯度下降法》&#xff0c;这篇文章中对于均方误差的求偏导是错误的&#xff0c;为了澄清这个问题&#xff0c;我再写一篇文章来纠正一下&#xff0c;避免误导大家。 一、批量梯度下降法 我们用 批量梯度下降法 来求解一个简单的 线性回归…