在L011中,除了普遍的Flash和OB外,还有EEPROM,而且还可以让Flash掉电运行.所以,他其实有4个密码,没错,是4个密码啊.如果Flash掉电了,那么就要RAM运行,也可以省一些电的.在Keil中这样定义RAM中运行代码.
__attribute__ ((section (".ramcode"))) void MyFunc(void){ while(1); }
不过这2K RAM,是不是真的有点小呢,对于如果在RAM运行,就看要运行的小代码多大了.当然建议把Flash操作过程代码都放在RAM调试,当OK了再转移到Flash,不然调试过程因为Flash没法获取就没法调试了.
说回来那4个钥匙,手册也给了我们.
FLASH->PDKEYR = 0x04152637; FLASH->PDKEYR = 0xFAFBFCFD; FLASH->PEKEYR = 0x89ABCDEF; FLASH->PEKEYR = 0x02030405; FLASH->PRGKEY = 0x8C9DAEBF; FLASH->PRGKEY = 0x13141516; FLASH->OPTKEY = 0xFBEAD9C8; FLASH->OPTKEY = 0x24252627;
开锁顺序我也整理了一下.
- 编程EEPROM:PEKEY
- 编程Flash扇区:PEKEY+PRGKEY
- 编程OP字节:PEKEY+PRGKEY+OPTKEY
- 编程Flash RunMode 下掉电模式:PDKEY
EEPROM的地址范围是0x08080000 - 0x080801FF,测试程序如下.
uint8_t i = 0; __attribute__ ((section (".ramcode"))) void EEPROM_WriteBytes(uint32_t addr, uint8_t code) { /* 储存总线没有进行写入/擦除工作. */ while (FLASH->SR & FLASH_SR_BSY); /* 输入密码解锁储存器 */ FLASH->PEKEYR = 0x89ABCDEF; FLASH->PEKEYR = 0x02030405; /* 等待解锁成功 */ while(FLASH->PECR & FLASH_PECR_PELOCK); /* 编程内容 */ *(uint8_t *)(addr) = code; /* 等待编程完成 */ while (FLASH->SR & FLASH_SR_BSY); /* 加锁储存器 */ FLASH->PECR |= FLASH_PECR_PELOCK; } int main(void) { for(i = 0; i < 0xff; i++) { EEPROM_WriteBytes(0x08080000, i); if(*(uint8_t *)(0x08080000) != i) { while(1); /* 错误发生 */ } } while (1) { } }
当然了,实际操作中,可以全部写完再加锁,甚至可以不加(就是有点不安全吧).EEPROM的寿命是100K次的,也就是10万次,寿命是很足够的记录很多数据.大致推算也有几十MB的写入寿命了,这EEPROM又这么慢,很耐用的.
接下来看看Flash.其中已知Flash编程需要2个Key.根据Flash特性还要先擦后写.每个扇区大小是128B,所以这一系列器件最大的竟然可以到1536扇区.但是这个Flash只支持一次性编程32Bit或者一次性编程16 Word(Half Page).一次性编程Half Page肯定比32Bit不断来回节省的.
#include "stm32l0xx.h" __attribute__ ((section (".ramcode"))) void Flash_WriteBytes(uint32_t addr, uint32_t code) { /* 储存总线没有进行写入/擦除工作. */ while (FLASH->SR & FLASH_SR_BSY); /* 输入密码解锁储存器 */ FLASH->PEKEYR = 0x89ABCDEF; FLASH->PEKEYR = 0x02030405; FLASH->PRGKEYR = 0x8C9DAEBF; FLASH->PRGKEYR = 0x13141516; /* 等待解锁成功 */ while(FLASH->PECR & FLASH_PECR_PELOCK); /* 设置清除位 */ SET_BIT(FLASH->PECR, FLASH_PECR_ERASE); /* 设置编程位 */ SET_BIT(FLASH->PECR, FLASH_PECR_PROG); /* 给首地址写0x00000000,这样可以擦除这个扇区. */ *(__IO uint32_t *)addr = 0x00000000U; while (FLASH->SR & FLASH_SR_BSY); /* 清除编程位 */ CLEAR_BIT(FLASH->PECR, FLASH_PECR_PROG); CLEAR_BIT(FLASH->PECR, FLASH_PECR_ERASE); /* 编程内容 */ *(__IO uint32_t *)addr = code; /* 等待编程完成 */ while (FLASH->SR & FLASH_SR_BSY); /* 加锁储存器 */ FLASH->PECR |= FLASH_PECR_PELOCK; FLASH->PECR |= FLASH_PECR_PRGLOCK; } int main(void) { /* 编程第二Word */ Flash_WriteBytes(0x08003F84, 0x87654321); /* 编程第三Word时候,因为第一Byte被擦,所以内容会没了. */ Flash_WriteBytes(0x08003F88, 0x87654321); /* 当然编程第一Word也没问题. */ while (1) { } }
也可以连续写16Word,用Hlaf-Page模式,在需要写16Word时候,这样比较节省时间,只占用一次编程时间.但是16Word需要连续在一个半页范围,不能是一个Page中间抽取一半.
#include "stm32l0xx.h" __attribute__ ((section (".ramcode"))) void Flash_WriteBytes(uint32_t addr, uint32_t code) { uint8_t i = 0; /* 储存总线没有进行写入/擦除工作. */ while (FLASH->SR & FLASH_SR_BSY); /* 输入密码解锁储存器 */ FLASH->PEKEYR = 0x89ABCDEF; FLASH->PEKEYR = 0x02030405; FLASH->PRGKEYR = 0x8C9DAEBF; FLASH->PRGKEYR = 0x13141516; /* 等待解锁成功 */ while(FLASH->PECR & FLASH_PECR_PELOCK); /* 设置清除位 */ SET_BIT(FLASH->PECR, FLASH_PECR_ERASE); /* 设置编程位 */ SET_BIT(FLASH->PECR, FLASH_PECR_PROG); /* 给首地址写0x00000000,这样可以擦除这个扇区. */ *(__IO uint32_t *)addr = 0x00000000U; while (FLASH->SR & FLASH_SR_BSY); /* 清除编程位 */ CLEAR_BIT(FLASH->PECR, FLASH_PECR_PROG); CLEAR_BIT(FLASH->PECR, FLASH_PECR_ERASE); /* 设置一次性编程半页 */ SET_BIT(FLASH->PECR, FLASH_PECR_FPRG); /* 编程内容(16Word) */ for(i = 0; i < 16; i++) { *(__IO uint32_t *)(addr + i * 4) = code; } /* 等待编程完成 */ while (FLASH->SR & FLASH_SR_BSY); /* 加锁储存器 */ FLASH->PECR |= FLASH_PECR_PELOCK; FLASH->PECR |= FLASH_PECR_PRGLOCK; } int main(void) { /* 编程 */ Flash_WriteBytes(0x08003F80, 0x89ABCDEF); while (1) { } }
如果跨越半Page,也不会出什么问题,只是时间不再是那么优化.编程几个Byte的时候估计也有用,不过就是15Byte优化程度最高.
因为L0不支持8Bit/16Bit/64Bit编程的,所以会出现如图问题.
#include "stm32l0xx.h" __attribute__ ((section (".ramcode"))) void Flash_WriteBytes(uint32_t addr, uint32_t code) { /* 储存总线没有进行写入/擦除工作. */ while (FLASH->SR & FLASH_SR_BSY); /* 输入密码解锁储存器 */ FLASH->PEKEYR = 0x89ABCDEF; FLASH->PEKEYR = 0x02030405; FLASH->PRGKEYR = 0x8C9DAEBF; FLASH->PRGKEYR = 0x13141516; /* 等待解锁成功 */ while(FLASH->PECR & FLASH_PECR_PELOCK); /* 设置清除位 */ SET_BIT(FLASH->PECR, FLASH_PECR_ERASE); /* 设置编程位 */ SET_BIT(FLASH->PECR, FLASH_PECR_PROG); /* 给首地址写0x00000000,这样可以擦除这个扇区. */ *(__IO uint32_t *)addr = 0x00000000U; while (FLASH->SR & FLASH_SR_BSY); /* 清除编程位 */ CLEAR_BIT(FLASH->PECR, FLASH_PECR_PROG); CLEAR_BIT(FLASH->PECR, FLASH_PECR_ERASE); /* 编程内容 */ *(__IO uint8_t *)(addr) = code; /* 等待编程完成 */ while (FLASH->SR & FLASH_SR_BSY); if(FLASH->SR & FLASH_SR_SIZERR) { while(1); /* 因为不能以8bit编程,所以会错误,停在这里.编程不了的. */ } /* 加锁储存器 */ FLASH->PECR |= FLASH_PECR_PELOCK; FLASH->PECR |= FLASH_PECR_PRGLOCK; } int main(void) { /* 编程 */ Flash_WriteBytes(0x08003F80, 0x89ABCDEF); while (1) { } }
其中SR状态寄存器的内容比较复杂.
- FWWER 标志,当写入/擦除过程中有Flash指令读取,就会置位,这个正常操作中不应该出现,可以在编程Flash时候关闭所有中断.
- NOTZEROERR 标志,当写入的位置不是0,就是不能写入,不应该编程这些位,如果发现可能是坏块产生了,也可能是逻辑错误.
- RDERR 标志,当Flash区域有读保护时候,读取这些内容就会出错.置位这个.
- OPTVERR 标志,写OP字节时候,因为OP字节格式不对,所以错误,程序设计问题了,当然对于我们做实验,OP可以自行用工具设置.
- SIZERR 标志,只能编译uint32_t格式数据,其他都会这个错,程序可以避免.
- PGAERR 标志,编程不对齐,程序逻辑问题.
- WRPERR 标志,有写保护,所以错误.
- READY 标志,储存器可用.
- ENDHV 标志,编程中有高压.
- EOP 标志,编程结束.
- BSY 标志,繁忙标志.程序常用来判断的标志.
这么看来,除了NOTZEROERR,其他只要程序没问题,就不会发生,而NOTZEROERR,是用久了之后发生的.
下面看看编程OB,编程OB需要3把钥匙.然而其实很简单,主要是要知道OB的作用.
#include "stm32l0xx.h" void OptionByteProg(uint8_t index, uint16_t data); __INLINE __attribute__ ((section (".ramcode"))) void OptionByteProg(uint8_t index, uint16_t data) { FLASH->PEKEYR = 0x89ABCDEF; FLASH->PEKEYR = 0x02030405; FLASH->PRGKEYR = 0x8C9DAEBF; FLASH->PRGKEYR = 0x13141516; FLASH->OPTKEYR = 0xFBEAD9C8; FLASH->OPTKEYR = 0x24252627; /* 前16Bit是反码,后16Bit是正常,用来做校验. */ *(__IO uint32_t *)(OB_BASE + index * 4) = (uint32_t)((~data << 16) | data); while(FLASH->SR & FLASH_SR_BSY); } int main(void) { /* 编程OB的第二个字节,设置位00000000 00001000,就是给96 - 127扇区写保护. */ OptionByteProg(2, 0x0008); /* 刷新OB,会复位MCU,当然这个程序会导致反复复位. */ SET_BIT(FLASH->PECR, FLASH_PECR_OBL_LAUNCH); /* 这里永远执行不了. */ while (1) { } }
然而OB中的IWDG_ULP 被删除了.
还有一个是Flash的PD位.在RUN模式下,让Flash掉电,那么程序只能在RAM运行了.掉电后就这样了.
#include "stm32l0xx.h" __attribute__ ((section (".ramcode"))) void Flash_PD_Test(void) { uint32_t i = 0; FLASH->PDKEYR = 0x04152637; FLASH->PDKEYR = 0xFAFBFCFD; FLASH->ACR |= FLASH_ACR_RUN_PD; for(i = 0;i<10;i++); } int main(void) { Flash_PD_Test(); while (1) { } }
剩下两个寄存器,分析一下,写这些程序就像填空题.
其中针对STM32L011K4有部分是无效的,就不介绍,下面介绍一些在L011K4有效的位.
- NZDISABLE 检查是不是已经完整擦除
- OBL_LAUNCH 让OB生效,然后会产生复位
- ERRIE 错误中断使能
- EOPIE 编程完成中断使能
- FPRG Half-Page 加速编程模式
- ERASE 擦除
- FIX 修正一些BUG,加长编程时间.
- DATA 编程的位置是EEPROM,然而我测试写不写没所谓.他们地址都不同.
- PROG 编程标志
- OPTLOCK/PRGLOCK/PELOCK 锁定标志
接着是FLASH_ACR标志.
这里主要是RUN_PD和SLEEP_PD,他们定义在RUN或者SLEEP模式时候,Flash的工作状态,是掉电还是工作中.然后LATENCY是延迟,看现在是Nucleo板子,电压比较高,除了跑32MHz,其他时候都不用.
缓存对于程序有降低功耗和各种的功能.不过如果没FLASH等待的话,性能不会因为缓存而提升.
为什么会降低功耗,因为不用去Flash读取东西了啊.就省了一些.BUF里面有6个缓冲区,当然比什么Cache小很多了,但是也不错了,每个缓冲区大小32Bit,就算存8Bit数据,也是32Bit的.
开Buf是简单事情.
#include "stm32l0xx.h" int main(void) { FLASH->ACR = FLASH_ACR_PRE_READ | FLASH_ACR_PRFTEN; while (1) { } }
拓展一下,如果EEPROM是每次都解锁加锁,也太麻烦了.所以,可以一次性解锁,没事时候不加锁,不过就是写程序就是要谨慎一些了.
#include "stm32l0xx.h" uint32_t i = 0; int main(void) { FLASH->PEKEYR = 0x89ABCDEF; FLASH->PEKEYR = 0x02030405; for(i = 0x08080000; i < 0x080801ff; i++) { *(uint8_t *)(i) = 0xFF - (i & 0x000000FF); while (FLASH->SR & FLASH_SR_BSY); } while (1) { } }
[…] 百度查询,https://www.lijingquan.net/2017/ … %E7%BC%96%E7%A8%8B/ […]