你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32笔记:读写内部Flash

[复制链接]
STMCU小助手 发布时间:2023-1-6 16:36

一、介绍

首先我们需要了解一个内存映射:

; p9 c; w+ T# i. m7 d/ U  ?

stm32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,不同的芯片flash大小不同。


. k: m/ V# A+ r( d: W" n

RAM起始地址是0x2000 0000,结束地址是0x2000 0000加上芯片的RAM大小。不同的芯片RAM也不同。

) Y& l7 J/ J5 k5 ?9 Z5 M0 P

Flash中的内容一般用来存储代码和一些定义为const的数据,断电不丢失,

RAM可以理解为内存,用来存储代码运行时的数据,变量等等。掉电数据丢失。


! w8 p4 s1 \, }" C% J

STM32将外设等都映射为地址的形式,对地址的操作就是对外设的操作。

stm32的外设地址从0x4000 0000开始,可以看到在库文件中,是通过基于0x4000 0000地址的偏移量来操作寄存器以及外设的。

, ]% Z( [: ^2 }: @1 l7 C

一般情况下,程序文件是从 0x0800 0000 地址写入,这个是STM32开始执行的地方,0x0800 0004是STM32的中断向量表的起始地址。

在使用keil进行编写程序时,其编程地址的设置一般是这样的:


- t4 [+ n" a4 G" H6 S

程序的写入地址从0x08000000(数好零的个数)开始的,其大小为0x80000也就是512K的空间,换句话说就是告诉编译器flash的空间是从0x08000000-0x08080000,RAM的地址从0x20000000开始,大小为0x10000也就是64K的RAM。这与STM32的内存地址映射关系是对应的。


6 D. \3 V- D  N

M3复位后,从0x08000004取出复位中断的地址,并且跳转到复位中断程序,中断执行完之后会跳到我们的main函数,main函数里边一般是一个死循环,进去后就不会再退出,当有中断发生的时候,M3将PC指针强制跳转回中断向量表,然后根据中断源进入对应的中断函数,执行完中断函数之后,再次返回main函数中。大致的流程就是这样。

) H, a$ ^& S7 w& c  _

1.1、内部Flash的构成:

' M% H# X9 B' e

STM32F429 的内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域,它们的地址分布及大小如下:


1 b; [! z( c/ t% g$ ^# l9 t

STM32F103的中容量内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域,它们的地址分布及大小如下:

+ C6 u6 u, I2 O! a+ W) G

注意STM32F105VC的是有64K或128页x2K=256k字节的内置闪存存储器,用于存放程序和数据。


" K" ?7 W$ N, g1 s: W. g$ E4 }

1.主存储器:一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域它是存储用户应用程序的空间,芯片型号说明中的 1M FLASH、 2M FLASH 都是指这个区域的大小。与其它 FLASH 一样,在写入数据前,要先按扇区擦除,

2.系统存储区:系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB 以及 CAN 等 ISP 烧录功能。

3.OTP 区域:OTP(One Time Program),指的是只能写入一次的存储区域,容量为 512 字节,写入后数据就无法再更改, OTP 常用于存储应用程序的加密密钥。

4.选项字节:选项字节用于配置 FLASH 的读写保护、电源管理中的 BOR 级别、软件/硬件看门狗等功能,这部分共 32 字节。可以通过修改 FLASH 的选项控制寄存器修改。

* \# G6 b4 r; y

1.2、对内部Flash的写入过程:

1. 解锁 (固定的KEY值)

(1) 往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123

(2) 再往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB

/ e( y" q- Z" E8 h# k4 d

2. 数据操作位数

最大操作位数会影响擦除和写入的速度,其中 64 位宽度的操作除了配置寄存器位外,还需要在 Vpp 引脚外加一个 8-9V 的电压源,且其供电间不得超过一小时,否则 FLASH可能损坏,所以 64 位宽度的操作一般是在量产时对 FLASH 写入应用程序时才使用,大部分应用场合都是用 32 位的宽度。


4 C, C. v/ B  g9 f# u6 X+ M

3. 擦除扇区

在写入新的数据前,需要先擦除存储区域, STM32 提供了扇区擦除指令和整个FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。


/ ]7 V, z" A# a* M. u; B

扇区擦除的过程如下:

(1) 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何

Flash 操作;

(2) 在 FLASH_CR 寄存器中,将“激活扇区擦除寄存器位 SER ”置 1,并设置“扇

区编号寄存器位 SNB”,选择要擦除的扇区;

(3) 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;

(4) 等待 BSY 位被清零时,表示擦除完成。


# P7 r/ g' `  G" p- \" z

4. 写入数据

擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下:

(1) 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;

(2) 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1;

(3) 针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作;

(4) 等待 BSY 位被清零时,表示写入完成。

* S. B, u' |5 X' O3 c( h* t7 e

1.3、查看工程内存的分布:


5 u2 a2 A/ W. m2 X8 W% f7 n

由于内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生的“ *.map”后缀文件,

打开 map 文件后,查看文件最后部分的区域,可以看到一段以“ Memory Map of the

image”开头的记录(若找不到可用查找功能定位),


/ S3 y& T6 j7 ^

【注】ROM加载空间


" d; k% _, \$ b' ?' n! o

这一段是某工程的 ROM 存储器分布映像,在 STM32 芯片中, ROM 区域的内容就是 指存储到内部 FLASH 的代码。

在上面 map 文件的描述中,我们了解到加载及执行空间的基地址(Base)都是0x08000000,它正好是 STM32 内部 FLASH 的首地址,即 STM32 的程序存储空间就直接是执行空间;它们的大小(Size)分别为 0x00000b50 及 0x00000b3c,执行空间的 ROM 比较小的原因就是因为部分 RW-data 类型的变量被拷贝到 RAM 空间了;它们的最大空间(Max)均为 0x00100000,即 1M 字节,它指的是内部 FLASH 的最大空间。


' ^9 p: d- j/ P/ Q# D

计算程序占用的空间时,需要使用加载区域的大小进行计算,本例子中应用程序使用

的内部 FLASH 是从 0x08000000 至(0x08000000+0x00000b50)地址的空间区域。

所以从扇区 1(地址 0x08004000)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。

& \. N9 a3 E& g" F! C$ P! l

二、代码拆分介绍(以STM32F105系列为例,如上图表5所示)


# O5 D8 T6 e/ i' I: g0 Y

2.1 读/写入数据流程

                                      写数据流程


) u* ]( l2 `8 [* \: j

2.1.1、Flash 解锁,直接调用#include "stm32f10x_flash.h"中的void FLASH_Unlock(void)函数,这个函数是官方提供的,其内部代码如下:

# ?' |* f0 u  p/ k, U7 ~

View Code


4 {, `7 d& v8 _: I' B* @3 I% \" a

1 k2 {7 x- J0 s2 g

2.1.2、擦除扇区,也是直接调用固件库官方的函数FLASH_Status FLASH_ErasePage(uint32_t Page_Address),这个官方函数代码也贴出来看看,代码如下:

" B* v5 F6 N3 g0 F; \5 q

View Code


3 z& ~8 }" ]" \0 ?

注意这个擦除扇区函数是你提供一个STM32f105系列扇区的开始地址即可,擦除是按照页擦除(每页2KB=1024Byte)或者整个擦除(见STM32参考手册的第二章2.3.3嵌入式闪存部分介绍)


+ Q7 Q; ~1 J' ]/ s7 b! H- T! m

比如我们要擦除互联网型的127页,我们只需要FLASH_ErasePage(0x0803f800);执行后,第127页的0x0803f800-0x0803FFFF数据都将被擦除。


9 _, Z# r! L. V( ?# x5 @0 h! m. ]

当然官方提供的也不知一个擦除函数,而是三个,具体如下,对于32位系统:一个是字节=4byte=32bite;一个是半字=2byte=16bite;一个是字节=1byte=8bite;进行擦除。


* a5 ~: J% u' K

FLASH_Status FLASH_ErasePage(uint32_t Page_Address);

' q# j5 l) n' R/ ?

FLASH_Status FLASH_EraseAllPages(void);


3 n/ B% ^6 o# H; H

FLASH_Status FLASH_EraseOptionBytes(void);

: F! q1 M/ D* D/ D3 n

2.1.3、接下来是写/读数据函数,该函数也是官方给出的,我们只需要用就好了。但要注意,这个是个半字的写操作,威少是uint16_t 的数据算半字呢,因为单片机是32的,对于32位单片机系统来说,一个字是4个字节的,8位的比如51单片机系统一个字就是2位的,64位单片机系统一个字就是8个字节,脱离单片机系统说字是多少个字节是没意义的。所以这里写入/读出半字也就是一次写入2个字节,写完/读出一次地址会加2。


( V7 E+ F! A% n+ J7 }6 y

写数据操作:

' F7 Z  H; A" Z# ~2 Q) i

View Code


' i* C' `' [, {' d' M( t


3 r; G2 ], N3 E9 P

当然官方给的不止是这一个函数写数据,官方提供了3个

: r+ P5 h6 t7 `( y: ~- v* }

FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);//一次写一个字,对于32系统,一次写的是4个字节,uint32_t 变量大小,32bit

+ d- [- d3 r  d' G% c. g! U4 g# N

FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);//一次写一个半字,对于32系统,一次写的是2个字节,uint16_t 变量大小,16bit

# B5 r( x1 a# H# [8 G! q

FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);//一次写一个字节,对于32系统,一次写的是1个字节,uint8_t 变量大小,8bit

  [3 i( l8 E- B9 B0 P, Y

读数据操作:

; ]" d( B5 h5 r+ M# i

读数据的函数,官方并没有给出:下面我们自己给出,具体的读法代码如下

. b/ D+ Q% D- I+ Y

  1. 1 //读取指定地址的半字(16位数据)- m& H  P7 V/ J, l' d
  2. 2 //也是按照半字读出,即每次读2个字节数据返回5 k9 w1 c4 ?: d+ z
  3. 3 uint16_t FLASH_ReadHalfWord(uint32_t address)( M% w( r* I9 |# ^
  4. 4 {
    6 G/ o: \1 f6 s" w" @- w
  5. 5   return *(__IO uint16_t*)address; ; A- D& L3 ~% K9 _& ?( R3 `
  6. 6 }
复制代码

" z# g% |# H( [$ r) N- F  g3 H: Z

如果要连续都区多个地址数据,可以进行如下代码操作

2 F& d3 T- u5 B. F# e, i0 h

  1. 1 //从指定地址开始读取多个数据
    ' s2 J0 F% {, c) {
  2. 2 void FLASH_ReadMoreData(uint32_t startAddress,uint16_t *readData,uint16_t countToRead)
    6 X* N3 E8 h8 ?7 u) e
  3. 3 {2 h# Y1 M+ v3 C8 c
  4. 4   uint16_t dataIndex;1 R* f, C$ A+ `3 t" A' o
  5. 5   for(dataIndex=0;dataIndex<countToRead;dataIndex++)( {* w1 s+ A2 \4 E1 E; u9 c
  6. 6   {
    9 p7 @8 s' I. E. ]
  7. 7     readData[dataIndex]=FLASH_ReadHalfWord(startAddress+dataIndex*2);
    $ [% ]  K# W* L# j4 D
  8. 8   }
    + z0 c; X/ w- u
  9. 9 }
复制代码

" n3 j, d! s4 n$ r, D. m, I

2.1.4、这步骤应该就是再次上锁,保护存储区不被重写覆盖了,直接使用官方的函数即可:FLASH_Lock();//上锁写保护


. Z; I( m9 ?, E: M7 Y

具体官方代码贴出如下


4 `5 N# z; M/ h" m

  1. 1 /**
    & y2 d1 M- p2 W7 T
  2. 2   * @brief  Locks the FLASH Program Erase Controller.
    . }) c% T9 r( T, R% j4 V& t
  3. 3   * @note   This function can be used for all STM32F10x devices.$ N" U6 c- n6 G+ ~, B
  4. 4   *         - For STM32F10X_XL devices this function Locks Bank1 and Bank2.
    & a' z' ^' n( l  v
  5. 5   *         - For all other devices it Locks Bank1 and it is equivalent
    $ J0 _8 y+ R5 u6 Y3 |
  6. 6   *           to FLASH_LockBank1 function.
    " K& G8 K) t6 q
  7. 7   * @param  None1 k) |# K/ ~1 m* F' K' ]
  8. 8   * @retval None
    - `# y/ b5 r; l; e1 ?- p
  9. 9   */
    ( }/ N' z1 a9 l. |' j. y( Z: s$ o1 u7 ?
  10. 10 void FLASH_Lock(void)& a; g4 X6 ]7 W6 a
  11. 11 {. Z; ]( L& Q- S8 ]
  12. 12   /* Set the Lock Bit to lock the FPEC and the CR of  Bank1 */
    6 F% A* B% [. B9 b& ?
  13. 13   FLASH->CR |= CR_LOCK_Set;
    5 A! `% }4 e. Z: m. q9 Y
  14. 14 . {& Z! g' ^( o- [0 \( x+ L' W
  15. 15 #ifdef STM32F10X_XL+ p! J( u, U  d) F
  16. 16   /* Set the Lock Bit to lock the FPEC and the CR of  Bank2 */  E; X' M1 G8 I5 f6 N# t
  17. 17   FLASH->CR2 |= CR_LOCK_Set;
    7 M3 A% M8 }0 x4 W
  18. 18 #endif /* STM32F10X_XL */0 x0 R" K( s. a
  19. 19 }
复制代码


6 M4 T8 T1 _' [+ Z" J" z

三、简单的小例程代码实现


' x9 `# B% _: z4 w: |0 o; D# t" X

例子功能:


3 z8 K. h& J& D9 o

       1、将数据存储在stm32F105单片机的主存储区0x08036000地址开始的扇区,(0x08036000应该是该单片机大约108个扇区的开始地址位置即页108起始地址)。

0 x- O, e( U, s. n6 z! v

       2、将该单片机的页108(page108=0x08036000)处的数据再读出来;

8 Y% L4 d; [. W4 B2 m9 @) ]2 _

具体实现代码如下,作为例子,只进行了半字的读写操作,我们写的数据buff为空,内容默认值为0


4 t" y* R4 R- K, w8 Y

  1. 1 #include "stm32f10x_flash.h"
    / Y; L, T9 F: y* K. g
  2. 2 # @' K9 H6 e) V: h/ ]
  3. 3 #define StartServerManageFlashAddress    ((u32)0x08036000)//读写起始地址(内部flash的主存储块地址从0x08036000开始)! I3 m) X7 I) A0 {" {7 c( R- d8 R/ X* s
  4. 4
    2 O% p& U7 Q. \! l& D
  5. 5 //从指定地址开始写入多个数据
    * U; y4 w8 X# u3 Q  o& n
  6. 6 void FLASH_WriteMoreData(uint32_t startAddress,uint16_t *writeData,uint16_t countToWrite)2 K1 w7 I* O6 E" y
  7. 7 {    4 Q8 [6 Y" l: d4 i" \4 e: ^: H3 h
  8. 8     uint32_t offsetAddress=startAddress - FLASH_BASE;               //计算去掉0X08000000后的实际偏移地址' e* m( R7 W/ S  ?+ D* E
  9. 9   uint32_t sectorPosition=offsetAddress/SECTOR_SIZE;            //计算扇区地址,对于STM32F103VET6为0~255
    0 W" d+ y; m7 \7 C* A5 z, P
  10. 10   uint32_t sectorStartAddress=sectorPosition*SECTOR_SIZE+FLASH_BASE;    //对应扇区的首地址
    5 Q1 Q0 \* Q. |5 K' k
  11. 11     uint16_t dataIndex;
    : s" ^) C8 r9 g* l/ e3 x
  12. 12     8 Y  e$ ?$ E2 @; _9 N/ Z, n/ b( Y
  13. 13   if(startAddress<FLASH_BASE||((startAddress+countToWrite*2)>=(FLASH_BASE + SECTOR_SIZE * FLASH_SIZE))): _1 ?. j' a( `2 y
  14. 14   {4 l: o6 _* \, _; A% F; J$ Z7 N  P* u
  15. 15     return;//非法地址
    ' H. E! u. u( c( E
  16. 16   }9 e9 Q6 A) r0 l
  17. 17   FLASH_Unlock();         //解锁写保护
    % j; h& g' ^  X! F/ z- `' z5 |& _$ I
  18. 18 , L3 k9 ^  g  t8 H) `7 A
  19. 19   FLASH_ErasePage(sectorStartAddress);//擦除这个扇区
    & z; n0 J2 Q  r3 m: r: F( O
  20. 20   5 |+ E: |8 V7 w; e3 Y. N2 X% k
  21. 21   for(dataIndex=0;dataIndex<countToWrite;dataIndex++)
    3 w1 i* i% |* l( d2 g. B# a
  22. 22   {; u8 l! a) y. H& n$ }+ i+ V
  23. 23     FLASH_ProgramHalfWord(startAddress+dataIndex*2,writeData[dataIndex]);
    / m2 Q+ v% ^' ~
  24. 24   }
    . i# t5 I* B- |4 j$ v8 |; R
  25. 25   
    / a# q1 \) J8 I- c+ e% u
  26. 26   FLASH_Lock();//上锁写保护* y" L: y( ]6 ]
  27. 27 }
    " E/ X; G+ q4 b$ T0 H, O" c
  28. 28 3 {! b& S% i: l
  29. 29 //读取指定地址的半字(16位数据)" i+ |& @7 g% P, z# D/ p/ t/ w5 R
  30. 30 uint16_t FLASH_ReadHalfWord(uint32_t address)
    ; I- K( H, |. f) z4 W0 B
  31. 31 {6 e0 X; h' t# E6 i
  32. 32   return *(__IO uint16_t*)address;
    # w2 f6 U- e! P
  33. 33 }
    4 C3 n; @9 b% ]9 C7 F1 f- F
  34. 34
    9 O4 U( _& w+ \0 |. N0 @
  35. 35 //从指定地址开始读取多个数据- W% J6 W1 }# {+ r) Y% L# v
  36. 36 void FLASH_ReadMoreData(uint32_t startAddress,uint16_t *readData,uint16_t countToRead)
    + R' w" E( y6 c, N; l" Y2 S( q
  37. 37 {- U+ _2 L2 D/ {
  38. 38   uint16_t dataIndex;
    / ?* C7 P0 F6 D1 s
  39. 39   for(dataIndex=0;dataIndex<countToRead;dataIndex++)- L& ]% }; n# D0 ~% [# C
  40. 40   {/ l/ U1 l6 o/ a6 b3 h+ v& s5 `
  41. 41     readData[dataIndex]=FLASH_ReadHalfWord(startAddress+dataIndex*2);
    ' F" W9 h" s/ s
  42. 42   }
    % F7 z" v% V0 l9 x4 w' U5 F. r) Z
  43. 43 }5 L* V$ C5 d' t2 t  J7 T
  44. 44 4 R% N8 Q8 \' F# \& Y
  45. 45 void write_to_flash(void)
    ' t8 ]$ j. G( M8 R
  46. 46 {7 x5 I6 y0 B9 U
  47. 47     u16 buff[1200];
    9 e! s9 E0 X& j
  48. 48     u16 count_len = 2272 / 2;* e5 k+ Y/ @6 I$ }/ P6 h1 s( _  O
  49. 50     FLASH_WriteMoreData(StartServerManageFlashAddress,buff,count_len);8 [) p# L  p. k
  50. 55 }: m* I& ?) B9 ]& ^6 F
  51. 56 + z' Z9 M# t% U
  52. 57 void read_from_flash(void)
    ! R& t! `* U3 G) i
  53. 58 {
    ; s! ~% x3 J! G
  54. 59     u16 buff[1200];
    8 ^- {9 B0 }( Y
  55. 60     u16 count_len = 2272 / 2;" j" I$ Z' ^1 ]' K4 M  S
  56. 61     FLASH_WriteMoreData(StartServerManageFlashAddress,buff,count_len);
    ; m5 O$ U2 E9 @8 a4 ^9 ^7 }
  57. 66 % v6 F5 k/ ]+ C) O7 M- x4 j9 I
  58. 67  }
复制代码
  1. 1 void mian(void)  y5 U" q- B1 a5 O
  2. 2 {
    : B$ I. y. [) |
  3. 3     .........//初始化其他外设4 t/ ]* y& D6 s6 s
  4. 4     while(1)9 i+ q6 D. J- u  m7 X0 I# {
  5. 5     {  z! P) l1 [0 T6 _/ c) ?
  6. 6         ...........//其他外设执行函数; ]0 I4 m+ v# F" w2 U5 G
  7. 7         if(满足条件真)//写数据操作9 \7 v1 A! d9 Y( i; o4 H/ \7 H6 a: u
  8. 8        {
    8 O' X! y, ^- M5 o% b7 Q" P
  9. 9             write_to_flash();( D3 O) @3 X2 f- a% z# |
  10. 10         }* y6 F2 g2 o$ F/ M  e9 K* `
  11. 11         else //读数据操作
      s# S- f' j$ b, w4 S- f
  12. 12         {
    . @5 I$ z* E$ @! Z; x
  13. 13             read_from_flash();" I1 g# |& m+ n8 M4 ]8 B1 e1 c$ h3 R( g
  14. 14         }
    , K4 L7 i, W: F( f& `
  15. 15 3 M$ Q* N# x) g6 d/ [, \
  16. 16     }
      R; r1 E. f- k* x( C
  17. 17  }
复制代码

6 H3 Y6 a  h8 g

四、附言


& q# T2 n: s6 L

值得的注意的是,我们读写的地址是0x08036000,读写方式是半字,这里地址空间对于stm32f105芯片来说是第108扇区,每个扇区2KB,stm32F105VC总共是256KB空间,128页。所以地址能取到0x08036000,像小中容量stm32f103单片机,64KB和128KB的主存储区地址都是到不了0x08036000,除非是stm32f103VE的256KB芯片的主存储快,0x08036000才是有效的存储地址,中小型这个地址都不是有效的主存储开地址

" I6 _' |' B; ]4 p0 G; P

转载自:xqhrs232

! K+ S9 D+ e7 @2 l
收藏 评论0 发布时间:2023-1-6 16:36

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版