STM8L EEPROM DATA数据读写

一、概要
STM8系列一般拥有如下几种三种数据区

  • 用户启动区域(UBC)
  • 数据EEPROM(DATA)
  • 主程序区
  • 选项字节(Option byte)

用户启动区域(UBC)包含有复位和中断向量表,它可用于存储IAP及通讯程序;
数据EEPROM(DATA)区域可用于存储用户具体项目所需的数据;
主程序区是指在FLASH程序存储器中用于存储应用代码的区域;
选项字节用于配置硬件特性和存储器保护状态。

作为应用而言,一般主要使用EEPROM(DATA),存放各种参数、或者离线数据、状态数据等等。
下面以以STM8L052R8为例,简单说明对其的访问方法。

根据STM8L052R8的手册,其有Memory信息如下:

■ Memories
– 64 KB Flash program memory and 256 bytes data EEPROM with ECC, RWW
– Flexible write and read protection modes
– 4 KB of RAM

可知其具有256字节的EEPROM。并带有ECC校验,和RWW(读同时写)功能。

RWW特性允许户在执行程序和读程序存储器时对DATA EEPROM区域进行写操作,
因此执行的时间被优化了。相反的操作是不允许的:即不允许在写程序寄存器是对其进行读操作。
RWW特性是一直有效的而且可以在任意时刻使用

对EEPROM编程也有如下几种方式,顾名思义,很容易理解其含义。
字节编程方式最易于理解,也最简单。

  • 字节编程
  • 字编程
  • 块编程

二、更深入的细节
STM8系列有存储器存取安全系统(MASS),在复位后,主程序和DATA区域都被自动保护以防止无意的写操作。
在修改其内容前必须对其解锁,而解锁的机制由存储器存取安全系统(MASS)来管理。(UBC始终为写保护)
因此写操作时需要先解除写保护,并在完成写入后恢复写保护(视应用而定)。

Unlock的具体操作是,向FLASH_DUKR寄存器连续写入两个被叫作MASS密钥的值:

  • 第一个硬件密钥: 0b1010 1110 (0xAE)
  • 第二个硬件密钥: 0b0101 0110 (0x56)

如果解锁成功,FLASH_IAPSR中的DUL位被置为1,表示成功。
应用必须检测这个标志才可进行后续操作。
(编程区与之类似,但写入的是PUKR,且2个密钥顺序相反)

对EEPROM的读写其实非常简单,就是直接对地址按字节进行赋值和取值。
但是在操作后,需要等待其操作完成。判断方法是:

  • 对于EEPROM(DATA)数据区:FLASH_IAPSR寄存器的HVOFF(高压结束标志位)变为1
  • 对于编程区:FLASH_IAPSR寄存器的EOP(编程结束标志位)变为1

另外,试图向被保护页进行写操作时,会发生错误,此时FLASH_IAPSR得WR_PG_DIS标志位会置1。
所以,最终的判断方法是:
HVOFF或者WR_PG_DIS被置为1,前者为正常介绍,后者表示出错

三、示例代码

地址范围定义(读写范围为0~127字节)

#define DATA_MEMORY_START_ADDR (FLASH_DATA_EEPROM_START_PHYSICAL_ADDRESS)
#define DATA_MEMORY_STOP_ADDR  (FLASH_DATA_EEPROM_START_PHYSICAL_ADDRESS + 128)

初始化函数

void flash_init(void)
{
    // 设置编程时间,指定标准编程时间即可
    FLASH_SetProgrammingTime(FLASH_ProgramTime_Standard);

    // 解锁EEPROM区域(注意type是Data)
    FLASH_Unlock(FLASH_MemType_Data);

    // 等待解锁成功
    // 本质是判断FLASH->IAPSR寄存器的DUL标志位是否变为1。1表示写保护消除,0为保护中
    // 任何时候都可以通过变更此标志位为0来恢复写保护状态
    while (FLASH_GetFlagStatus(FLASH_FLAG_DUL) == RESET);
}

读函数

uint8_t flash_read(uint32_t FlashAddr, uint8_t *dest, uint8_t nbyte)
{
    uint8_t i = 0;
    // 越界判断
    if((FlashAddr < DATA_MEMORY_START_ADDR)||(FlashAddr+ nbyte > DATA_MEMORY_STOP_ADDR)) {
        return FALSE;
    }
    // 按字节读
    for(i=0; i<nbyte; i++) {
        *(dest+i)=FLASH_ReadByte(FlashAddr+i);

        // 等待操作完成,此处未处理错误
        FLASH_WaitForLastOperation(FLASH_MemType_Data);
    }

    return nbyte;
}

写函数

uint8_t flash_write(uint32_t FlashAddr, uint8_t *source, uint8_t nbyte)
{
    uint8_t i = 0;
    // 越界判断
    if((FlashAddr < DATA_MEMORY_START_ADDR)||(FlashAddr+ nbyte > DATA_MEMORY_STOP_ADDR)) {
        return FALSE;
    }
    // 按字节写
    for(i=0;i<nbyte;i++) {
        FLASH_ProgramByte((FlashAddr+i),*(source + i));

        // 等待操作完成,此处未处理错误
        FLASH_WaitForLastOperation(FLASH_MemType_Data);
    }
    return nbyte;
}

四、库函数实现解析

FLASH_Unlock函数

void FLASH_Unlock(FLASH_MemType_TypeDef FLASH_MemType)
{
  /* Unlock program memory */
  if(FLASH_MemType == FLASH_MemType_Program)
  {
    FLASH->PUKR = FLASH_RASS_KEY1;
    FLASH->PUKR = FLASH_RASS_KEY2;
  }

  /* Unlock data memory */
  // 连续两次赋值密钥(固定值)
  if(FLASH_MemType == FLASH_MemType_Data)
  {
    FLASH->DUKR = FLASH_RASS_KEY2; /* Warning: keys are reversed on data memory !!! */ /* 0xAE */
    FLASH->DUKR = FLASH_RASS_KEY1; /* 0x56 */
  }
}

FLASH_ReadByte、FLASH_ProgramByte、FLASH_EraseByte
由下可知,读写擦出均为直接操作地址。

uint8_t FLASH_ReadByte(uint32_t Address)
{
  /* Read byte */
  return(*(PointerAttr uint8_t *) (MemoryAddressCast)Address);
}

void FLASH_ProgramByte(uint32_t Address, uint8_t Data)
{
  *(PointerAttr uint8_t*) (MemoryAddressCast)Address = Data;
}

void FLASH_EraseByte(uint32_t Address)
{
  *(PointerAttr uint8_t*) (MemoryAddressCast)Address = FLASH_CLEAR_BYTE; /* Erase byte */
}

FLASH_WaitForLastOperation 操作等待

FLASH_Status_TypeDef FLASH_WaitForLastOperation(FLASH_MemType_TypeDef FLASH_MemType))
{
  uint16_t timeout = OPERATION_TIMEOUT;
  uint8_t flagstatus = 0x00;

  /* Wait until operation completion or write protected page occurred */
  // 程序区等待IAPSR的EOP或者WR_PG_DIS标识位被置为1
  if(FLASH_MemType == FLASH_MemType_Program)
  {
    while((flagstatus == 0x00) && (timeout != 0x00))
    {
      flagstatus = (uint8_t)(FLASH->IAPSR & (uint8_t)(FLASH_IAPSR_EOP |
                                                       FLASH_IAPSR_WR_PG_DIS));
      timeout--;       // 贴心的超时处理
    }
  }
  else
  {
   // 数据区的话,等待IAPSR的HVOFF或者WR_PG_DIS标识位被置为1
    while((flagstatus == 0x00) && (timeout != 0x00))
    {
      flagstatus = (uint8_t)(FLASH->IAPSR & (uint8_t)(FLASH_IAPSR_HVOFF |
                                                      FLASH_IAPSR_WR_PG_DIS));
      timeout--;        // 贴心的超时处理
    }
  }

  if(timeout == 0x00)
  { 
    // 超时
    flagstatus = FLASH_Status_TimeOut;
  }

  return((FLASH_Status_TypeDef)flagstatus);
}

以上,相比直接操作寄存器,用库做STM开发还是比较有效率的。

好的,在介绍EEPROM读写代码前,我们先了解一下EEPROM的基本概念。 EEPROM(Electrically Erasable Programmable Read-Only Memory)是一种电可擦可编程只读存储器。与传统只读存储器(ROM)不同,EEPROM可以通过特定的编程方式对其中存储的数据进行读写操作,并且这些数据可以电擦写。因此,EEPROM具有随机访问,即可以通过地址直接访问其中存储的任意数据。 在STM32开发板中,EEPROM的读写操作需要借助I2C总线进行。I2C总线具有双向传输数据的能力,因此可以实现EEPROM的读取和写入操作。 以下是代码详解: 1. 首先定义一些相关的宏定义,包括I2C总线的地址和EEPROM中存储数据的地址等。 #define I2C_ADDRESS 0xA0 // I2C总线地址 #define EEPROM_ADDRESS 0x00 // EEPROM存储数据的起始地址 2. 然后定义一个函数用于向EEPROM中写入数据,函数原型如下: void EEPROM_Write(uint16_t addr, uint8_t data); 其中,addr为写入数据的地址,data为写入的数据。 函数实现代码如下: void EEPROM_Write(uint16_t addr, uint8_t data) { uint8_t I2C_Buffer[3]; I2C_Buffer[0] = (addr >> 8) & 0xFF; // 高位地址 I2C_Buffer[1] = addr & 0xFF; // 低位地址 I2C_Buffer[2] = data; // 数据 while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // 等待总线空闲 I2C_GenerateSTART(I2C1, ENABLE); // 产生START信号 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待MASTER模式选择 I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Transmitter); // 发送设备地址 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待进入主发送模式 I2C_SendData(I2C1, I2C_Buffer[0]); // 发送高位地址 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待字节发送完成 I2C_SendData(I2C1, I2C_Buffer[1]); // 发送低位地址 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待字节发送完成 I2C_SendData(I2C1, I2C_Buffer[2]); // 发送数据 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待字节发送完成 I2C_GenerateSTOP(I2C1, ENABLE); // 产生STOP信号 } 3. 定义一个函数用于从EEPROM中读取数据,函数原型如下: uint8_t EEPROM_Read(uint16_t addr); 其中,addr为读取数据的地址。 函数实现代码如下: uint8_t EEPROM_Read(uint16_t addr) { uint8_t I2C_Buffer[2]; I2C_Buffer[0] = (addr >> 8) & 0xFF; // 高位地址 I2C_Buffer[1] = addr & 0xFF; // 低位地址 while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // 等待总线空闲 I2C_GenerateSTART(I2C1, ENABLE); // 产生START信号 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待MASTER模式选择 I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Transmitter); // 发送设备地址 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待进入主发送模式 I2C_SendData(I2C1, I2C_Buffer[0]); // 发送高位地址 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待字节发送完成 I2C_SendData(I2C1, I2C_Buffer[1]); // 发送低位地址 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待字节发送完成 I2C_GenerateSTART(I2C1, ENABLE); // 产生START信号 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待MASTER模式选择 I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Receiver); // 发送设备地址 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // 等待进入主接收模式 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); // 等待字节接收完成 uint8_t data = I2C_ReceiveData(I2C1); // 读取数据 I2C_AcknowledgeConfig(I2C1, DISABLE); // 关闭I2C应答位 I2C_GenerateSTOP(I2C1, ENABLE); // 产生STOP信号 return data; } 如果您还有其他问题,请继续提问。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值