STM32L系列单片机内部提供了EEPROM存储区域,但实质上,其FLASH也是EEPROM类型,只不过有一块区域被开放出来专门用作EEPROM操作而已。STM32L的EEPROMSTM32L使用寿命设计为100000次擦写以上,容量为2K-4K,这对于一般设备的参数存储来说是非常理想的。但从EEPROM使用方式看,其不适用于被反复修改的数据存储使用,一般作为配置参数,其修改次数往往是比较少量的。 STM32L的EEPROM和FLASH是统一编址,操作共用同一个读写电路,所以在EEPROM读写的时候STM32L核对于FLASH的一切访问和操作都将暂停,只有当EEPROM的操作完成后,才继续执行后续代码,在这期间只有EEPROM的读写电路工作,CPU处于挂起状态。 读操作,和FLASH以及内存一样,EEPROM的数据读取直接用总线读周期读出即可,不需要进行额外操作和设置。 - #define EEPROM_BASE_ADDR 0x08080000
6 t8 m+ g& f, O; I) u; x1 L - #define EEPROM_BYTE_SIZE 0x0FFF
复制代码 以上定义EEPROM区的起始位置和大小,给定偏移量之后,可以按字节/半字/字/双字方式读出,但要注意的是最好偏移地址都按四字节对齐,以免产生总线访问错误或是取不正确:- /*------------------------------------------------------------
; @! ^1 o9 h# K. { - Func: EEPROM数据按字节读出
% y# u1 t2 o% m( d - Note: $ k% A9 d( y% a3 |4 d- j2 N! H
- -------------------------------------------------------------*/
8 L) x# S1 _4 ? - void EEPROM_ReadBytes(uint16 Addr,uint8 *Buffer,uint16 Length) : M! f* t, S1 f- O4 h/ b- C
- {
$ t7 F) \, t+ V) i' l$ t' |* ] - uint8 *wAddr; / E+ |8 @) J9 x+ q
- wAddr=(uint8 *)(EEPROM_BASE_ADDR+Addr);
: f$ |5 c# f4 T - while(Length--){ 9 U$ P* Q( s4 ~9 _0 w7 I# R
- *Buffer++=*wAddr++;
( g+ c& [( u( L; Q6 U$ Z - } ; Y1 X8 f; _6 i% D& A
- }
复制代码- /*------------------------------------------------------------
) ]3 L9 e4 S7 z) o - Func: EEPROM数据读出% q' `5 Q. y4 ~; O, N0 ^2 P' u r
- Note:1 ~* X N- Z! d6 d4 f% [; o
- -------------------------------------------------------------*/ : ?) a6 o% R: G* t+ i1 g
- void EEPROM_ReadWords(uint16 Addr,uint16 *Buffer,uint16 Length) : ?1 z8 g* r/ j$ O- p; t/ k7 l
- { 4 w" `1 Q) ]6 _3 B& c5 R
- uint32 *wAddr; 2 Z$ t& V9 K0 O _- Y7 x
- wAddr=(uint32 *)(EEPROM_BASE_ADDR+Addr); 9 q4 o. J9 x- t9 b; Z S+ S1 K
- while(Length--){ % X8 t7 E" M2 S1 A3 U+ \
- *Buffer++=*wAddr++;
' J+ W; v1 h. O$ P - }
. ~! v2 j+ s5 Y, [ S" V7 E - }
复制代码 以上方法使用字节和字方式读出,在后面方法中,在一个字的存储空间内只使用了16个位,另16位不用,这样以避免产生对齐问题。 EEPROM的编程比读操作要复杂的多,本质上来说,擦除操作和写入操作是一样的,擦除只是在相应的地方写入0x00000000,但在STM32L的实现上,根据其手册说明貌似把这种擦除和写入区分开了,当写入0x00或0x0000或0x00000000时,自动执行一次擦除操作,在值为非0时,才执行一次所谓的写入操作。数据的写入过程先要对EEPROM进行解锁,这通过对特殊寄存器写入特殊序列实现,然后在写入之前进行擦除操作,其擦除是按字/ 双字/页进行的,推荐使用页擦除方式进行,先把参数读到内存,并修改,再进行页擦除,最后将参数写回,这种方式比较通用,否则很容易出现地址对齐或长度问题。在数据擦除完成之后,即可进行写入,每写一字节/半字/双字,都需要判断其是否写入完成,这和内部高压擦写电路有关,只有在上次操作完成之后再进行其它操作才有意义。最后,对EEPROM进行加锁,以保护数据。 下是手册给出的解锁命令码: - #define PEKEY1 0x89ABCDEF //FLASH_PEKEYR
- x& Y1 g B3 i: d2 p - #define PEKEY2 0x02030405 //FLASH_PEKEYR
复制代码 以下分别实现按字节和字方式写入: - /*------------------------------------------------------------
4 t) E# K. d! M) A4 J" E - Func: EEPROM数据按字节写入& d" B6 {9 p4 {5 T! b( F: w
- Note:: @7 N- u+ j( ~) U* x$ J* i
- -------------------------------------------------------------*/
0 b( |( X. {8 H$ A9 k8 s$ l - void EEPROM_WriteBytes(uint16 Addr,uint8 *Buffer,uint16 Length)
6 F7 I: F4 g1 k+ N - {
" z8 @+ _% }2 x8 {! y$ l1 c9 o - uint8 *wAddr;
0 b$ r# q2 G e% P! K( j - wAddr=(uint8 *)(EEPROM_BASE_ADDR+Addr);
6 m: a- ~, v# L* ?$ j - DIS_INT
( D1 z% W0 e( i% p. F$ B - FLASH->PEKEYR=PEKEY1; //unlock
7 k! S9 b- N6 Y4 [% V0 A - FLASH->PEKEYR=PEKEY2; _' U$ M- a( A5 X1 q7 v. ]& s
- while(FLASH->PECR&FLASH_PECR_PELOCK);
: m- s' g3 @' \* x, l% A, T - FLASH->PECR|=FLASH_PECR_FTDW; //not fast write " F, Y+ I8 k$ _" j3 \
- while(Length--){ . }# {3 `- b5 Z3 `3 ?% O
- *wAddr++=*Buffer++;
( ?6 t+ u+ e% I - while(FLASH->SR&FLASH_SR_BSY); / ]$ h2 h1 @3 ?3 l& E
- } ! d3 _; W5 ~' a& a
- FLASH->PECR|=FLASH_PECR_PELOCK; ; ^5 F4 b0 {7 R% [+ b/ N
- EN_INT
& a* t* Y/ r9 C" S) _" k3 f% ^ - }
复制代码- /*------------------------------------------------------------3 i+ O7 H$ o, Y! n+ F% E
- Func: EEPROM数据按字写入
4 W$ ]& d+ E* F. @8 K - Note: 字当半字用
8 ?3 a2 W. @; z# o* z+ z5 a - -------------------------------------------------------------*/
9 [' B1 X4 _, ]# ] - void EEPROM_WriteWords(uint16 Addr,uint16 *Buffer,uint16 Length) - n1 W& b, D) @4 b; X
- { * \5 I9 f) ]% z4 g0 {2 ?
- uint32 *wAddr;
- [- x: j. _, l - wAddr=(uint32 *)(EEPROM_BASE_ADDR+Addr);
8 X' K0 L$ }2 C3 A5 T( B' f: w3 | - DIS_INT 3 B5 W2 C9 g: R; k V- i' \7 b
- FLASH->PEKEYR=PEKEY1; //unlock
) I. x$ \" g2 x0 S( T - FLASH->PEKEYR=PEKEY2;
9 t9 |7 y0 u; x* A+ \. W0 A# H5 g - while(FLASH->PECR&FLASH_PECR_PELOCK);
2 j; c) Z0 I1 G5 S, |1 G - FLASH->PECR|=FLASH_PECR_FTDW; //not fast write
- S% z2 P. _9 P3 v$ z - while(Length--){
- c6 e" d/ ^" q; u, q* K0 Z. J& _ - *wAddr++=*Buffer++; & R" e- N t3 `
- while(FLASH->SR&FLASH_SR_BSY); ' c5 i( `- G! Y6 n: s0 c' Z, A! {
- } 6 w* I& ]+ H+ D$ H4 \6 ]6 S
- FLASH->PECR|=FLASH_PECR_PELOCK;
; q( S1 W% S+ W - EN_INT 3 ^" d: v& X6 i& l# A" b
- }
复制代码
: p# \6 Z( Q- C; A0 v 以上代码中,在写入数据之前先关闭系统中断DIS_INT,写入完成之后打开系统中断EN_INT,这样避免在执行写操作的过程中被中断过程所打断,引起CPU异常或锁死,在在使用中一定要注意。在MDK环境中,两个可以这样定义:% n9 ^- o+ |" g
- #define EN_INT __enable_irq(); //系统开全局中断
4 R6 I7 z+ [# Z, s3 H# g - #define DIS_INT __disable_irq(); //系统关全局中断
复制代码 / X, x. p# ~+ M2 b! L# c3 U
|