STM32L011K4 Flash 操作

/ 1评 / 0

在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;

开锁顺序我也整理了一下.

  1. 编程EEPROM:PEKEY
  2. 编程Flash扇区:PEKEY+PRGKEY
  3. 编程OP字节:PEKEY+PRGKEY+OPTKEY
  4. 编程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状态寄存器的内容比较复杂.

  1. FWWER 标志,当写入/擦除过程中有Flash指令读取,就会置位,这个正常操作中不应该出现,可以在编程Flash时候关闭所有中断.
  2. NOTZEROERR  标志,当写入的位置不是0,就是不能写入,不应该编程这些位,如果发现可能是坏块产生了,也可能是逻辑错误.
  3. RDERR 标志,当Flash区域有读保护时候,读取这些内容就会出错.置位这个.
  4. OPTVERR 标志,写OP字节时候,因为OP字节格式不对,所以错误,程序设计问题了,当然对于我们做实验,OP可以自行用工具设置.
  5. SIZERR 标志,只能编译uint32_t格式数据,其他都会这个错,程序可以避免.
  6. PGAERR 标志,编程不对齐,程序逻辑问题.
  7. WRPERR 标志,有写保护,所以错误.
  8. READY 标志,储存器可用.
  9. ENDHV 标志,编程中有高压.
  10. EOP 标志,编程结束.
  11. 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有效的位.

  1. NZDISABLE 检查是不是已经完整擦除
  2. OBL_LAUNCH 让OB生效,然后会产生复位
  3. ERRIE 错误中断使能
  4. EOPIE 编程完成中断使能
  5. FPRG Half-Page 加速编程模式
  6. ERASE 擦除
  7. FIX 修正一些BUG,加长编程时间.
  8. DATA 编程的位置是EEPROM,然而我测试写不写没所谓.他们地址都不同.
  9. PROG 编程标志
  10. 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)
    {
    }
}

 

  1. […] 百度查询,https://www.lijingquan.net/2017/ … %E7%BC%96%E7%A8%8B/ […]

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注