写点什么

STM32L051 测试 (四、Flash 和 EEPROM 的读写)

作者:矜辰所致
  • 2022-10-13
    江苏
  • 本文字数:11096 字

    阅读完需:约 36 分钟

STM32L051测试 (四、Flash和EEPROM的读写)
..修改文中以前的问题,增加后续问题说明(数据存储大小端模式)           ..增加后续问题2.4.3(STM32L071RBT6 EEPROM读写全字问题)              ..更新一下STM32L071 EEPROM读写全字问题最后解决说明             
复制代码


前言

本文目的在于测试说明 STM32L051 的 flash 的读写,为了在今后使用中能够掉电保存需要的数据。


本系列博文目录:

STM32L051 测试 (一、使用 CubeMX 生成工程文件 — ST 系列芯片通用)

https://xie.infoq.cn/article/fc5e31ea41e0b33f64b9ee537

STM32L051 测试 (二、开始添加需要的代码)

https://xie.infoq.cn/article/26e83bbc53646042a66e6fb8f

STM32L051 测试 (三、I2C 协议设备的添加测试)

https://xie.infoq.cn/article/76cbd45207fe8f4a6d2d9b99d


1、STM32L051 内部存储模块地址范围

开始找到 F103 的 FLASH 图复习了一遍,然后 L051C8T6,64KB 的 flash, 然后我惊奇的发现还有 2KB 的 EEPROM。


发现 L051 系列的地址与 F103 完全不同,F103 的 flash 每页的长度有 1KB(小容量<=64KB)和 2KB(大容量 128KB 起)查看各种资料,查了 2 个小时,


还是不知道 L051 的 flash 每页长度是 128Byte 还是 256Byte????????????????????????


还是请教了一下大佬,发现直接在 J-Flash 中可以找到答案,先上个 F103 的图:



然后来看个 L051 的图:



图中 64KB 的 flash 和 2KB 的 EEPROM 都能都明显的看出地址,flash 512 页,每页 128bytes,EEPROM 只有 4 页,每页 512bytes.知道了基本的地址,就可以操作起来了。


最后还需要确定的一点事,最小擦除单元是 128bytes,还是 256bytes,按以前的认知,删除是按照一个 Sector 擦除的,也就是 128bytes,但是我查看了一些网上的例子和资料,有的是说 256bytes,所以后面需要自己确定一下。


其实在 HAL 库的 stm32l0xx_hal_flash.h 文件中有过 FLASH_PAGE_SIZE 的定义,就是 128bytes :


#define FLASH_SIZE                (uint32_t)((*((uint32_t *)FLASHSIZE_BASE)&0xFFFF) * 1024U)#define FLASH_PAGE_SIZE           ((uint32_t)128U)  /*!< FLASH Page Size in bytes */
复制代码


对于 flash 的操作,有一些基础知识补充一下:


Read interface organized by word, half-word or byte in every area

• Programming in the Flash memory performed by word or half-page

• Programming in the Option bytes area performed by word

• Programming in the data EEPROM performed by word, half-word or byte

• Erase operation performed by page (in Flash memory, data EEPROM and Optionbytes)


STM32L051 写 Flash 必须字,读 字节、半字、字都支持。(这句话也是错误的,这是以前哪里看到的,实际测试写可以根据字,半字,字节来写)


一些基本概念:


定义字是根据处理器的特性决定的。


首先 ARM 是 32bit 处理器,所以它的字是 32bit 的。半字自然就是 16bit;字节不论在哪个 CPU 上都是 8bit。


1 Byte = 8 bits(即 1B=8b)1 KB = 1024 Bytes


Bit 意为“位”或“比特”,是计算机运算的基础,属于二进制的范畴;


Byte 意为“字节”,是计算机文件大小的基本计算单位;

2、读写函数的设计

HAL 库中肯定是有对 flash 和 EEPROM 进行操作的函数,我们这里新建一个 stml0_flash.c 和 stml0_flash.h 函数分别放在对应位置,进行自己的函数设计。


库中 Flash 与 EEPROM 的函数看样子是分别放在stm32l0xx_hal_flash.cstm32l0xx_hal_flash_ex.c 中的,我们先使用 EEPROM,因为提供 EEPROM,就是让用户可以保存一些掉电后的数据的嘛。


测试完 EEPROM,再去测试下 flash,因为怕有时候数据不够放……

2.1 读取函数

//读取指定地址的半字(16位数据)uint16_t FLASH_ReadHalfWord(uint32_t address){  return *(__IO uint16_t*)address; }
//读取指定地址的全字(32位数据)uint32_t FLASH_ReadWord(uint32_t address){ return *(__IO uint32_t*)address;}
复制代码


简单测试一下:


u32 read_data1=0XFFFFFFFF;u32 read_data2=0XFFFFFFFF;...read_data1 = FLASH_ReadWord(DATA_EEPROM_START_ADDR);printf("the DATA_EEPROM_START_ADDR is: 0x %x \r\n",read_data1);read_data2 = FLASH_ReadWord(DATA_EEPROM_START_ADDR + EEPROM_PAGE_SIZE);printf("the EEPROM sceond page test data is: 0x %x \r\n",read_data2);
复制代码


没有写入数据读取的值应该都是 0。

2.2 EEPROM 写函数

对 EEPROM 的写函数:stm32l0xx_hal_flash_ex.h 中函数如下:


HAL_StatusTypeDef HAL_FLASHEx_DATAEEPROM_Unlock(void);HAL_StatusTypeDef HAL_FLASHEx_DATAEEPROM_Lock(void);
HAL_StatusTypeDef HAL_FLASHEx_DATAEEPROM_Erase(uint32_t Address);HAL_StatusTypeDef HAL_FLASHEx_DATAEEPROM_Program(uint32_t TypeProgram, uint32_t Address, uint32_t Data);
复制代码


通过函数看来,可以直接用,但是这里有一个问题


需要测试一下,擦除是否会擦除整个扇区,有待验证!!


答:EEPROM 的擦除可以直接擦除某个地址的字,不会擦除整个片区


EEPROM 的操作相对 Flash,比较简单,直接使用 HAL 库中的函数即可完成


HAL_FLASHEx_DATAEEPROM_Unlock();HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, DATA_EEPROM_START_ADDR, write_data1);HAL_FLASHEx_DATAEEPROM_Lock();...if(btn_getState(&K1_BUTTON_150mS) == BTN_EDGE2){        printf(" K1 150ms button!,EEPROM_Erase test\r\n");        HAL_FLASHEx_DATAEEPROM_Unlock();        HAL_FLASHEx_DATAEEPROM_Erase(DATA_EEPROM_START_ADDR+4);        HAL_FLASHEx_DATAEEPROM_Lock();        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);    }...if(btn_getState(&K2_BUTTON_150mS) == BTN_EDGE2){        printf(" K2 150ms button!EEPROM_read test\r\n");        read_data1 = FLASH_ReadWord(DATA_EEPROM_START_ADDR);        printf("the DATA_EEPROM_START_ADDR is: 0x %x \r\n",read_data1);    }
复制代码


按照上面的例子,擦除 DATA_EEPROM_START_ADDR+4 的地址不会影响 DATA_EEPROM_START_ADDR 地址的开始写入的数据写入一样,如果不在意以前的数据,直接写入就可以。


总结来说 EEPROM 的使用还是很好用而且简单的。


而且 EEPROM 是可以按照字节,半字,全字写入的,测试了一下,是右对齐


右对齐什么意思呢,打个比方就是如果在地址 addr 中,本来写入全字 0x12345678 然后直接在 EEPROM 的 addr 这个地址,写入半字 0xFFFF, 再读取全字的话,addr 地址的全字就变成了 0x1234FFFF ,这个具体的为什么在地址写入半字,不会直接从地址开始占用 2 个字节,是因为地址的类型为 uint32_t ,所以该地址就是 4 个字节类型的数。

写入问题说明修改


修改说明 上面一段的解释有误,这里修改一下,不是因为地址类型为 uint32_t,地址类型永远是这个,是因为定义的数据类型为 uint32_t ,然后 STM32 又是小端模式,所以保存的方式是从地址的最后开始保存,4 个字节的全字,第一个字节放在地址开始+4 的位置,第二个字节放在地址开始+3 的位置,所以如果调用HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, DATA_EEPROM_START_ADDR, write_data1);


关键在于FLASH_TYPEPROGRAMDATA_WORD以全字方式写入半字,那么内核会自动分配 4 个字节的宽度,半字的第一个字节放在写入地址+4 的位置,第二个字节放在写入地址+3 的位置,所以导致了上面的结果


  HAL_FLASHEx_DATAEEPROM_Unlock();  HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, DATA_EEPROM_START_ADDR, 0X88);  HAL_FLASHEx_DATAEEPROM_Lock();  read_data1 = FLASH_ReadWord(DATA_EEPROM_START_ADDR);  printf("the DATA_EEPROM_START_ADDR is: 0x %x \r\n",read_data1);
复制代码


最后在 stml0_flash.h 中把函数完善声明一下,使得主函数中的程序更加简洁。


void MY_DATAEEPROM_Program(uint32_t TypeProgram, uint32_t Address, uint32_t Data)   {  HAL_FLASHEx_DATAEEPROM_Unlock();                 HAL_FLASHEx_DATAEEPROM_Program(TypeProgram, Address, Data);  HAL_FLASHEx_DATAEEPROM_Lock();} 
复制代码


那么 L051 的 EEPROM 的测试就到这里,其实有 EEPROM,在项目中的保存数据的功能就问题不大了,但是我们既然开始了,就把 L051 Flash 的读写也测试一下。

2.3 Flash 写函数

Flash 的读取其实和 EEPROM 一样,主要是写函数,来看一下 stm32l0xx_hal_flash.h 中有哪些函数


/* IO operation functions *****************************************************/HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint32_t Data);HAL_StatusTypeDef HAL_FLASH_Program_IT(uint32_t TypeProgram, uint32_t Address, uint32_t Data);
/* FLASH IRQ handler function */void HAL_FLASH_IRQHandler(void);/* Callbacks in non blocking modes */void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue);void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue);
/** * @} */
/** @addtogroup FLASH_Exported_Functions_Group2 * @{ *//* Peripheral Control functions ***********************************************/HAL_StatusTypeDef HAL_FLASH_Unlock(void);HAL_StatusTypeDef HAL_FLASH_Lock(void);HAL_StatusTypeDef HAL_FLASH_OB_Unlock(void);HAL_StatusTypeDef HAL_FLASH_OB_Lock(void);HAL_StatusTypeDef HAL_FLASH_OB_Launch(void);
复制代码


有点忙,Flash 的后面再也,看了几个 demo,只需要做几个测试就可以;


今天有空来接着测试一下 L051 Flash 的读写,看了下 HAL_FLASH_Program 函数:


HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint32_t Data){  HAL_StatusTypeDef status = HAL_ERROR;    /* Process Locked */  __HAL_LOCK(&pFlash);
/* Check the parameters */ assert_param(IS_FLASH_TYPEPROGRAM(TypeProgram)); assert_param(IS_FLASH_PROGRAM_ADDRESS(Address));
/* Wait for last operation to be completed */ status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE); if(status == HAL_OK) { /* Clean the error context */ pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;
/*Program word (32-bit) at a specified address.*/ *(__IO uint32_t *)Address = Data;
/* Wait for last operation to be completed */ status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE); }
/* Process Unlocked */ __HAL_UNLOCK(&pFlash);
return status;}
复制代码


这里也再次说明了,L051 的写必须以字的方式写入。


不管了,先测试一下,不擦除直接写入,这里先定义一下写入的地址,前面我们已经知道了 L051 flash 一共 512 页,每页 128bytes,所以我们直接拿最后面的几页来测试


#define ADDR_FLASH_PAGE_505      0X08000000 + 128*504   //#define ADDR_FLASH_PAGE_506      0X08000000 + 128*505   //#define ADDR_FLASH_PAGE_507      0X08000000 + 128*506   //#define ADDR_FLASH_PAGE_508      0X08000000 + 128*507   //#define ADDR_FLASH_PAGE_509      0X08000000 + 128*508   //#define ADDR_FLASH_PAGE_510      0X08000000 + 128*509   //#define ADDR_FLASH_PAGE_511      0X08000000 + 128*510   //#define ADDR_FLASH_PAGE_512      0X08000000 + 128*511   //最后一页
复制代码


开始先不擦除,直接在最后一页写入一个字读取一下试试,整理一下写入函数:


void MY_DATAFLASH_Program(uint32_t Address, uint32_t Data)   {  HAL_FLASH_Unlock();               if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data) == HAL_OK){    printf("write data 0x%x OK\n", Data);  }  else{    printf("failed!!!\n");  }  HAL_FLASH_Lock();} 
复制代码


测试一下;


  MY_DATAFLASH_Program(ADDR_FLASH_PAGE_512,write_data2);  read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_512);  printf("the ADDR_FLASH_PAGE_512 is: 0x %x \r\n",read_data1);
复制代码


在没有写入 flash 之前,该地址读出来的数据为 0,写入后读出来是正常写入的数据


这里有个疑问,按理来说,flash 存储器有个特点,就是只能写 0,不能写 1,所以 flash 的写入,比需先擦除,或者至少检查一下该数据区是否可以写入,但是 L051 怎么初始的时候读出来是 0? 难道 L051 的有区别,需要测试一下。


先在一个地址写 0XFFFFFFFF ,然后写完了再写一次别的数据看看能否直接写入,结果是写入了 0XFFFFFFFF ,不能继续直接写数据,说明,估计 L051 是相反的,这里具体是不是我只看测试结果,结论的话我自己知道就可以应用,希望有权威大神指正。


这里我们得用到一个关键的函数 ,这个函数是在stm32l0xx_hal_flash_ex.c 这个文件中的,是 flash 的擦除函数:


HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
复制代码


所以这里我们知道了以后,可以优化一下写入函数,我们项目中用到的是可以直接对某个地址的写入,然后也不需要保存,此页其他的数据,所以我们把函数改成如下:


void MY_DATAFLASH_Program(uint32_t Address, uint32_t Data)   {  FLASH_EraseInitTypeDef EraseInitStruct;  uint32_t checkdata;  uint32_t PAGEError = 0;  checkdata = FLASH_ReadWord(Address);  HAL_FLASH_Unlock();   /*如果是0,直接写*/  if(checkdata == 0){                if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data) == HAL_OK){      printf("write data 0x%x OK\n", Data);    }    else{      printf("failed!!!\n");    }  }  /*否则擦除再写*/  else{     __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; // 刷除方式 EraseInitStruct.PageAddress = Address; // 起始地址 EraseInitStruct.NbPages = 1;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) { // 如果刷除错误 printf("\r\n FLASH Erase Fail\r\n"); printf("Fail Code:%d\r\n",HAL_FLASH_GetError()); printf("Fail Page:%d\r\n",PAGEError); }
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data) == HAL_OK){ printf("write data 0x%x OK\n", Data); } else{ printf("failed!!!\n"); }
} HAL_FLASH_Lock();}
复制代码


自己修改了一个函数, 改成这样以后,就能直接在想写入的地方写入数据了,到这里,flash 足够我项目中的使用了。但是还有最后一个疑问,就是擦除的一页到底是不是 128bytes 我来验证一下。


u32 write_data1 = 0X12345678;u32 write_data2 = 0X87654321;u32 write_data3 = 0XFFFFFFFF;
MY_DATAFLASH_Program(ADDR_FLASH_PAGE_508 + 124,0X508508FF);MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509,write_data3);MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509+4,write_data2);MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509+8,write_data1);MY_DATAFLASH_Program(ADDR_FLASH_PAGE_510,0X510510FF);
read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_508 + 124); printf("the ADDR_FLASH_PAGE_508 last is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509); printf("the ADDR_FLASH_PAGE_509 1 is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 4); printf("the ADDR_FLASH_PAGE_509 2 is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 8); printf("the ADDR_FLASH_PAGE_509 3 is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_510); printf("the ADDR_FLASH_PAGE_510 first is: 0x %x \r\n",read_data1);
if(btn_getState(&K2_BUTTON_150mS) == BTN_EDGE2){ printf(" K2 150ms button!EEPROM_read test\r\n"); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_508 + 124); printf("the ADDR_FLASH_PAGE_508 last is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509); printf("the ADDR_FLASH_PAGE_509 1 is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 4); printf("the ADDR_FLASH_PAGE_509 2 is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 8); printf("the ADDR_FLASH_PAGE_509 3 is: 0x %x \r\n",read_data1); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_510); printf("the ADDR_FLASH_PAGE_510 first is: 0x %x \r\n",read_data1);
}if(btn_getState(&K1_BUTTON_150mS) == BTN_EDGE2){ // printf(" K1 150ms button!,EEPROM_Erase test\r\n"); // MY_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, DATA_EEPROM_START_ADDR, write_data1); printf(" K1 150ms button!,flash write test\r\n"); MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509,0X33333333); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); }
复制代码


因为地址位置如果有数据的话会擦除一页再写入,所以我们看了一下 508 页最后一个地址,和 510 页的第一个地址数据,对 509 页的数据进行了操作,结果发现不会改变 508 和 510 的数据,509 页的数据会全部清除!




所以得出的结论显而易见,flash 擦除按照一页一页擦除,在 L051 上面一页为 128bytes,而且擦除后数据全部为 0;

2.4 读写 EEPROM 的后续问题

最近有一个项目,因为缺货 STM32L051R 系列缺货,买了 STM32L071RBT6 替代:



看了一下地址,其实我们上面所有的测试代码基本都是可以直接用的,代码直接用 STM32L051 的代码也是可以的,实际项目中,还是使用 EEPROM 比较方便,所以在使用 EEPROM 的时候,发现了一个问题(并不是换了芯片的问题),还是数据读取和写入的问题。

2.4.1 问题的出现和解决

下面程序的硬件平台是 STM32L071RBT6,首先在程序中,有一个写 ID 的函数:



写入 IO 是通过字节的方式 byte 写入的,写了 6 个字节(蓝牙设备的 ID)。


然后最初读取的函数用的是:



使用这个读 ID 的函数,问题就出来了。


上图代码中,我读取 ID 使用的是半字读取,处理方式是把读到的半字前面 8 位给 ID1, 后面 8 位给 ID2, 但是测试中发现数据读出来与想要的相反,什么意思呢,看下面的测试说明:


上电打印出 EEPROM 中读取的一个地址的,每个 ID(每个 ID 是 uint8_t 类型)的数据,在代码开始定义了测试数据:


BlueID_STRUCT test;.../*CHBlueID_STRUCT Flash_PowerOn_BlueCheck(){
CHBlueID_STRUCT PowerOn_ID; PowerOn_ID.CH1ID = FLASH_blueIDRead(CH1_ID_ADDR); PowerOn_ID.CH2ID = FLASH_blueIDRead(CH2_ID_ADDR); PowerOn_ID.CH3ID = FLASH_blueIDRead(CH3_ID_ADDR); PowerOn_ID.CH4ID = FLASH_blueIDRead(CH4_ID_ADDR); PowerOn_ID.CH5ID = FLASH_blueIDRead(CH5_ID_ADDR); PowerOn_ID.CH6ID = FLASH_blueIDRead(CH6_ID_ADDR); PowerOn_ID.CH7ID = FLASH_blueIDRead(CH7_ID_ADDR); PowerOn_ID.CH8ID = FLASH_blueIDRead(CH8_ID_ADDR); PowerOn_ID.CH9ID = FLASH_blueIDRead(CH9_ID_ADDR); PowerOn_ID.CH10ID = FLASH_blueIDRead(CH10_ID_ADDR);
return PowerOn_ID;}*/BlueChipID = Flash_PowerOn_BlueCheck(); //上电先把ID读出来做比较//打印一个出来测试,看结果printf("BlueChipID.CH1ID is 0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\r\n",BlueChipID.CH1ID.ID1,BlueChipID.CH1ID.ID2,BlueChipID.CH1ID.ID3,BlueChipID.CH1ID.ID4,BlueChipID.CH1ID.ID5,BlueChipID.CH1ID.ID6);
test.ID1= 0XFF; //结构体每个元素是 uint8_t 类型test.ID2= 0XEE;test.ID3= 0XDD;test.ID4= 0XCC;test.ID5= 0XBB;test.ID6= 0XAA;
while (1){...}
复制代码


在程序中通过操作,写入测试数据,调用上面提到过的写 ID 的函数,写 ID 的函数是每个字节每个字节写的:



按我们希望的结果来说,读出来按照顺序打印,应该是:0xff,0xee,0xdd,0xcc,0xbb,0xaa 。测试实际上是:



为了确实是读的问题还是写的问题,添加了读字节的函数:




打印的结果:



说明确实是 ID 的读取函数出的问题,是因为使用的半字读取,问题处理不麻烦,我们把读取函数修改一下:


BlueID_STRUCT FLASH_blueIDRead(uint32_t address){  BlueID_STRUCT u48id;  // uint16_t temp1,temp2,temp3;  /**(__IO uint16_t*)address; */  // temp1=*(__IO uint16_t*)address;   // u48id.ID1 = (uint8_t)(temp1>>8);  // u48id.ID2 = (uint8_t)(temp1&0X00FF);  // temp2=*(__IO uint16_t*)(address+2);  // u48id.ID3 = (uint8_t)(temp2>>8);  // u48id.ID4 = (uint8_t)(temp2&0X00FF);  // temp3=*(__IO uint16_t*)(address+4);  // u48id.ID5 = (uint8_t)(temp3>>8);  // u48id.ID6 = (uint8_t)(temp3&0X00FF); 
u48id.ID1 = FLASH_Readbyte(address); u48id.ID2 = FLASH_Readbyte(address+1); u48id.ID3 = FLASH_Readbyte(address+2); u48id.ID4 = FLASH_Readbyte(address+3); u48id.ID5 = FLASH_Readbyte(address+4); u48id.ID6 = FLASH_Readbyte(address+5);
return u48id;}
复制代码


测试结果才正常了,如下图:


2.4.2 问题的分析(大小端模式数据格式)

出现上面的问题,其实是和我们经常说的大端模式和小端模式有关的。


STM32 使用的是小端模式,简单介绍一下大端模式小端模式数据存放的方式,如下图:



知道上面的知识,我们在开始的读取函数中是直接读取的半字(__IO uint16_t*)address; ,但是我们写入的时候是一个字节一个字节写入,上面的例子所以我们内存中的数据实际上如下图所示:



使用(__IO uint16_t*)address; 去读取,读取出来的数值一个uint16_t类型的数值:


假设是 a,a=*(__IO uint16_t*)addr; 会有 a = 0xEEFF; 所以高 8 位变成了 0xEE。 OK! 解释清楚!问题解决!


至此,我们基本上把 STM32L051 的 EEPROM 和 Flash 功能都测试过了,把工程中需要用到的功能做了测试,也学到了一些新的知识,还是实践出结论啊,当初没有自己测试之前看了网上的有关类似的介绍,还是很多误解,这下全部清晰了。

2.4.3 STM32L071RBT6 EEPROM 读写全字问题

读问题的出现

前面其实测试过,读取全字是可以的,直接使用return *(__IO uint32_t*)address;:便可以读到该地址的全字:


uint32_t  FLASH_ReadWord(uint32_t address){  return *(__IO uint32_t*)address;}
复制代码


所以在后面使用过程中,有这么一个函数:



一开始还真的不知道哪里出了问题?折腾了好一阵才发现调用函数读取 ID 时就会卡死。


问题的解决:


因为还有另外一个蓝牙版本的产品,如果是蓝牙设备的 ID,因为蓝牙设备的 ID 是 6 位的,所以当时读取蓝牙的 ID 的时候使用的是(蓝牙版本是没问题的):



其实折腾了好一会儿,后来想着蓝牙是读一个字节,要不要试着把 全字 分为 4 个字节,单独读取试一试?


于是学蓝牙把程序分为 4 个字节读取:



代码也放一下,方便复制:


u32 FLASH_ReadEnoceanID(uint32_t address){  u32 keepid;  u8 i;  keepid = FLASH_Readbyte(address);  i = FLASH_Readbyte(address + 1);  keepid = (i<<8)|keepid;  i = FLASH_Readbyte(address + 2);  keepid = (i<<16)|keepid;  i = FLASH_Readbyte(address + 3);  keepid = (i<<24)|keepid;  return keepid;}
CHID_STRUCT Flash_PowerOn_Check(){
CHID_STRUCT PowerOn_ID; PowerOn_ID.CH1ID = FLASH_ReadEnoceanID(CH1_ID_ADDR); PowerOn_ID.CH2ID = FLASH_ReadEnoceanID(CH2_ID_ADDR); PowerOn_ID.CH3ID = FLASH_ReadEnoceanID(CH3_ID_ADDR); PowerOn_ID.CH4ID = FLASH_ReadEnoceanID(CH4_ID_ADDR); PowerOn_ID.CH5ID = FLASH_ReadEnoceanID(CH5_ID_ADDR); PowerOn_ID.CH6ID = FLASH_ReadEnoceanID(CH6_ID_ADDR); PowerOn_ID.CH7ID = FLASH_ReadEnoceanID(CH7_ID_ADDR); PowerOn_ID.CH8ID = FLASH_ReadEnoceanID(CH8_ID_ADDR); PowerOn_ID.CH9ID = FLASH_ReadEnoceanID(CH9_ID_ADDR); PowerOn_ID.CH10ID = FLASH_ReadEnoceanID(CH10_ID_ADDR);
return PowerOn_ID;}
复制代码


测试一下,发现就好了,至于原因,目前还不知道为什么……(最后问题解决有说明,内存字节对齐问题)

写问题的出现

本来以为解决了上面问题 OK 了,可是后面测试的时候发现写的时候也有问题:


一直用的写全字函数为:


FLASH_Status  FLASH_WriteWord(uint32_t Address, uint32_t Data){
FLASH_Status i = FLASH_COMPLETE;
HAL_FLASHEx_DATAEEPROM_Unlock(); while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, Address, Data) != HAL_OK); HAL_FLASHEx_DATAEEPROM_Lock();
return i;}
复制代码


在程序中会调用此函数进行 ID 保存:



但是使用时候发现:



问题的解决:


其实这个问题也莫名其妙,真是说不出来为什么,估计是得详细的查看数据手册,但是还是 因为 在蓝牙的版本上面没有此类问题:



所以这里还是尝试改成 以 字节 方式写入:


FLASH_Status  FLASH_WriteWord(uint32_t Address, uint32_t Data){
FLASH_Status state = FLASH_COMPLETE; u8 i = 0; u8 writedata[6]={0};
writedata[0] = (u8)Data; writedata[1] = (u8)(Data>>8); writedata[2] = (u8)(Data>>16); writedata[3] = (u8)(Data>>24);
HAL_FLASHEx_DATAEEPROM_Unlock(); for(i=0; i<4; i++){ while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, Address + i, writedata[i]) != HAL_OK); } HAL_FLASHEx_DATAEEPROM_Lock();
return state;}
复制代码


使用此种方式写入,就不会出现问题!


其实也可以尝试修改地址,使得成为 4 的倍数,可能也不会出问题,这里就不测试了(最后问题解决有还是测试了,内存字节对齐问题)


2.4.3 小结使用的芯片为 STM32L071RBT6

最后问题的解决

先直接说结论,就是 EEPROM 地址定义的问题,应该是 4 字节对齐(4 的整数倍),读取全字的操作才能正常!


上面 STM32L071RBT6 EEPROM 读写全字问题的关键在于,存储地址的定义上,如上面一张图所示:


(我在 EEPROM 区域定义了 10 个地址,用来存放无线设备的 ID 数据,如果是蓝牙芯片,那么 ID 为 6 个字节,如果是 Enocean 芯片,那么 ID 为 4 个字节,为了保持代码的统一,我在使用保存 4 个字节的 ID 数据的地址定义时候沿用的是蓝牙的 EEPROM 区域定义)



那么正如我图中猜想的一样,蓝牙的 ID 6 个字节,我是都是通过一个字节一个字节操作,组合起来进行的,所以一切正常。


但是对于 4 个字节的 ID ,期初是用的 全字的方式,就出问题了,换成一个字节一个字节的操作,看上去是解决问题了。


<font color=#0033FF>但是实际上多了一些隐藏问题,暂时也说不清楚,在产品使用的时候,读写 ID 还是会有莫名其妙的问题,最终还是对当初的这个猜想,地址是不是也需要 4 字节对齐?进行了修改测试,于是乎,对于 4 个字节 ID 的处理,地址改成:



把地址修改成 4 的倍数以后,上面的读取全字的两个函数便可以正常使用,而不会出上面莫名其妙的问题。

总结

在 STM32L0 系列读取 EEPROM 的时候,需要注意:


字节操作,传入合理范围内的任何地址参数都可以;


半字操作,地址需要 2 字节对齐,就是 2 的倍数;


全字操作,地址需要 4 字节对齐,就是 4 的倍数;

发布于: 2022-10-13阅读数: 21
用户头像

矜辰所致

关注

CSDN、知乎、微信公众号: 矜辰所致 2022-08-02 加入

不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...

评论

发布
暂无评论
STM32L051测试 (四、Flash和EEPROM的读写)_EEPROM_矜辰所致_InfoQ写作社区