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 {6 v* f } j2 H/ | - #define EEPROM_BYTE_SIZE 0x0FFF
复制代码 以上定义EEPROM区的起始位置和大小,给定偏移量之后,可以按字节/半字/字/双字方式读出,但要注意的是最好偏移地址都按四字节对齐,以免产生总线访问错误或是取不正确:- /*------------------------------------------------------------
3 L1 y ^) p3 |# Y$ H& N& F1 z - Func: EEPROM数据按字节读出 0 u' ?# w! L0 u
- Note:
, m9 b) s9 C2 y/ X6 W - -------------------------------------------------------------*/ ( x2 C, ]9 H: d6 e# E# ^
- void EEPROM_ReadBytes(uint16 Addr,uint8 *Buffer,uint16 Length)
" |* }- T1 k& a5 v# j - { 1 U8 w J" V9 B- H
- uint8 *wAddr;
' g8 D3 \3 X3 @) ?: G% X5 a - wAddr=(uint8 *)(EEPROM_BASE_ADDR+Addr); 6 `+ Z) V8 R' P8 q. j
- while(Length--){ 6 \9 i, C# h( K2 c
- *Buffer++=*wAddr++; + w; o& a4 K2 c' d; R: r9 E! O- t
- } : P! ]* b' L) y. G: B! n5 G
- }
复制代码- /*------------------------------------------------------------
! Y" M/ A0 C }. t# f - Func: EEPROM数据读出
7 f) m6 a( f. _8 F/ a - Note:% J, d) W+ Y$ \$ n1 S
- -------------------------------------------------------------*/ & u0 \, C, L2 m, V2 s
- void EEPROM_ReadWords(uint16 Addr,uint16 *Buffer,uint16 Length)
7 a* {+ G, ~$ V: f8 F! ] - {
6 S5 k0 t/ Y( g* U( e0 W# | - uint32 *wAddr;
^7 Z- c" `+ g) c - wAddr=(uint32 *)(EEPROM_BASE_ADDR+Addr); " g, k0 \) x# M: T7 ]' t! k% p
- while(Length--){
9 F0 R. b3 _/ L) d. @! d' a - *Buffer++=*wAddr++; 9 Z. ~3 e4 T e% b
- }
# V+ ?- F: V* o/ N1 J - }
复制代码 以上方法使用字节和字方式读出,在后面方法中,在一个字的存储空间内只使用了16个位,另16位不用,这样以避免产生对齐问题。 EEPROM的编程比读操作要复杂的多,本质上来说,擦除操作和写入操作是一样的,擦除只是在相应的地方写入0x00000000,但在STM32L的实现上,根据其手册说明貌似把这种擦除和写入区分开了,当写入0x00或0x0000或0x00000000时,自动执行一次擦除操作,在值为非0时,才执行一次所谓的写入操作。数据的写入过程先要对EEPROM进行解锁,这通过对特殊寄存器写入特殊序列实现,然后在写入之前进行擦除操作,其擦除是按字/ 双字/页进行的,推荐使用页擦除方式进行,先把参数读到内存,并修改,再进行页擦除,最后将参数写回,这种方式比较通用,否则很容易出现地址对齐或长度问题。在数据擦除完成之后,即可进行写入,每写一字节/半字/双字,都需要判断其是否写入完成,这和内部高压擦写电路有关,只有在上次操作完成之后再进行其它操作才有意义。最后,对EEPROM进行加锁,以保护数据。 下是手册给出的解锁命令码: - #define PEKEY1 0x89ABCDEF //FLASH_PEKEYR
& Z; m0 ~ u& D6 Z& }, W - #define PEKEY2 0x02030405 //FLASH_PEKEYR
复制代码 以下分别实现按字节和字方式写入: - /*------------------------------------------------------------
. ?* E) c6 Q4 P# A4 O$ n - Func: EEPROM数据按字节写入
# O, I/ [- x8 ~* B0 r1 I! b6 E - Note:
: L4 ~0 d2 G1 a: q% A0 V0 J& h - -------------------------------------------------------------*/ 3 e& o2 N7 P( p( c' [
- void EEPROM_WriteBytes(uint16 Addr,uint8 *Buffer,uint16 Length) $ C6 D q; a$ c0 p2 U+ W
- { ! P3 m8 q( k7 r) j4 ]+ p5 U
- uint8 *wAddr; : p2 X0 N$ Z, k! `" s
- wAddr=(uint8 *)(EEPROM_BASE_ADDR+Addr); 3 t* g* i; e" H1 @
- DIS_INT
" Y- b" h" n) l/ B7 [/ H" H9 d& t- m - FLASH->PEKEYR=PEKEY1; //unlock
) K6 X4 {$ \0 J+ C; L& V( z6 e - FLASH->PEKEYR=PEKEY2; $ R9 J" |7 h. Z7 B" t
- while(FLASH->PECR&FLASH_PECR_PELOCK);
& m. Q) \9 u! C: ` - FLASH->PECR|=FLASH_PECR_FTDW; //not fast write / D9 c6 c V. x
- while(Length--){
! Z- N% X% ~ P9 z3 W. M G& t - *wAddr++=*Buffer++; ) R$ |1 J$ n; Z9 ]7 Z0 q. v, B2 [
- while(FLASH->SR&FLASH_SR_BSY); $ P6 ^9 n7 B. M# i# Z5 x
- }
) v; D' J1 |- E( w7 f5 f1 | - FLASH->PECR|=FLASH_PECR_PELOCK; ) A) ?# r8 ^+ H* I3 e+ Z7 S
- EN_INT 9 z$ [' |; G' C; X1 w
- }
复制代码- /*------------------------------------------------------------' X J* z9 B2 ^$ ]/ K
- Func: EEPROM数据按字写入' |2 e+ t6 X( o0 g: m# n5 N
- Note: 字当半字用
6 v$ e s+ d9 N X+ s! f. ` - -------------------------------------------------------------*/
2 y, U8 v! d) l y - void EEPROM_WriteWords(uint16 Addr,uint16 *Buffer,uint16 Length) $ f* Y* R' V. d6 T% @; r5 ]- X
- { # e2 p. F; s5 Z: W. x9 Q
- uint32 *wAddr;
5 f) w. C; a0 U! Y* G; M& v/ o - wAddr=(uint32 *)(EEPROM_BASE_ADDR+Addr);
: d, g9 }, \6 A/ Z - DIS_INT " p: `7 A, A7 x! {6 s
- FLASH->PEKEYR=PEKEY1; //unlock
- s5 Q- j. L9 b* }$ F" Q! T - FLASH->PEKEYR=PEKEY2;
5 a( a/ p; U9 y b$ Y5 D u - while(FLASH->PECR&FLASH_PECR_PELOCK);
0 I N a$ A" V. m" j) b - FLASH->PECR|=FLASH_PECR_FTDW; //not fast write
$ d: e$ u' p# {, ] ^ F - while(Length--){
' Q0 m3 ?% M; n% U- r - *wAddr++=*Buffer++;
k: v6 @5 m: }4 ?/ L! s/ z6 \ - while(FLASH->SR&FLASH_SR_BSY);
6 M: C) D: A+ p) y! y1 Y0 K9 p - }
7 y+ q7 @% e: ]4 z - FLASH->PECR|=FLASH_PECR_PELOCK;
1 G8 H4 S/ d3 N- A - EN_INT
# G; P( S! i) B6 u" ? - }
复制代码 6 Q; |5 Y9 \; F6 z2 N
以上代码中,在写入数据之前先关闭系统中断DIS_INT,写入完成之后打开系统中断EN_INT,这样避免在执行写操作的过程中被中断过程所打断,引起CPU异常或锁死,在在使用中一定要注意。在MDK环境中,两个可以这样定义: p/ @4 \. z* ?2 i
- #define EN_INT __enable_irq(); //系统开全局中断
& R5 v% C) V: N2 x# y; B6 Q6 V - #define DIS_INT __disable_irq(); //系统关全局中断
复制代码
/ ^' s* |3 s7 @5 p |