cathy 在 提交
不同的STM32单片机的flash大小不同,这个需要查阅芯片手册或者查看STM32CubeMX软件。
STM32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,要操作flash时注意不要超出此范围。
Flash中的内容一般用来存储代码和一些定义为const的数据,和一些用户自定义的保存数据,它断电不丢失。
不同型号的单片机对flash的操作方式略有不同。下面我以自己用到的STML4R9VIT6为例贴上代码。
STM32的内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域,具体分部查看响应的数据手册。
flash的写机制,只能将存储区域的1改为0,从0改为1只能选择擦除(页擦除或者扇区擦除),一次性擦除多个字节。
flash的读机制,只需要从flash的地址中读取数据就行了,可以字节读取,也可以按字读取。具体操作看代码。
首先需要说明的是,STM32内部flash空间包含多个用途,在保存自定义的数据时,千万不要去操作正在使用的区域。
其次,STM32是小端模式,数据的高字节保存在内存的高地址中, 而数据的低字节保存在内存的低地址中, 这种存储模式将地址的高低和数据位权有效地结合起来, 高地址部分权值高,低地址部分权值低。
//针对STM32L4R9VIT6单片机,通过地址获取所在页的函数如下 uint32_t getPage(uint32_t Address) //获取地址所在的 { uint32_t page = 0; if (Address < (FLASH_BASE + FLASH_BANK_SIZE)) page = (Address - FLASH_BASE) / FLASH_PAGE_SIZE; else page = (Address - (FLASH_BASE + FLASH_BANK_SIZE)) / FLASH_PAGE_SIZE; return page; }
//这是获取bank的代码 uint32_t GetBank(uint32_t Addr) { uint32_t bank = 0; if (READ_BIT(SYSCFG->MEMRMP, SYSCFG_MEMRMP_FB_MODE) == 0) { if (Addr < (FLASH_BASE + FLASH_BANK_SIZE))/* No Bank swap */ bank = FLASH_BANK_1; else bank = FLASH_BANK_2; } else { if (Addr < (FLASH_BASE + FLASH_BANK_SIZE))/* Bank swap */ bank = FLASH_BANK_2; else bank = FLASH_BANK_1; } return bank; }
//这是擦除函数,stm32在写数据之前,都需要先把地址中的数据擦除成0xFF,然后才能写数据 FLASH_EraseInitTypeDef EraseInitStruct; uint8_t eraseFlash(uint32_t start_addr,uint32_t end_addr) //擦除flash { uint32_t BANK; uint32_t FirstPages = 0,LastPages = 0, NbPages = 0; uint32_t pageError = 0; HAL_FLASH_Unlock(); //首先解锁flash FirstPages = getPage(start_addr); //获取要擦除的第一个页 LastPages = getPage(end_addr); NbPages = LastPages - FirstPages + 1; //获取擦除的页数量 BANK = GetBank(start_addr); //判断地址的Banks是1还是2 /* Fill EraseInit structure*/ EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; //页擦除 EraseInitStruct.Banks = BANK; EraseInitStruct.Page = FirstPages; EraseInitStruct.NbPages = NbPages; if(HAL_FLASHEx_Erase(&EraseInitStruct, &pageError) != HAL_OK) { HAL_FLASH_Lock(); //上锁 return 2; //擦除有错误,返回2 } //下面这些是清除标志位 __HAL_FLASH_DATA_CACHE_DISABLE(); __HAL_FLASH_INSTRUCTION_CACHE_DISABLE(); __HAL_FLASH_DATA_CACHE_RESET(); __HAL_FLASH_INSTRUCTION_CACHE_RESET(); __HAL_FLASH_INSTRUCTION_CACHE_ENABLE(); __HAL_FLASH_DATA_CACHE_ENABLE(); HAL_FLASH_Lock(); //上锁 return 0; }
其中有个重要的东西是FLASH_EraseInitTypeDef结构体
typedef struct { uint32_t TypeErase; //页擦除和块擦除 uint32_t Banks; //选择擦除的bank区域 uint32_t Page; //擦除的起始页 uint32_t NbPages; //要擦除的页数 } FLASH_EraseInitTypeDef;
然后下面是写flash操作
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);//清除flash标志位 eraseFlash(ADDR_FLASH_START,ADDR_FLASH_END);//擦除要写入的flash地址的数据 HAL_FLASH_Unlock();//解锁flash HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, ADDR_FLASH_START, (uint64_t)data);//写数据 HAL_FLASH_Lock();//上锁
这里详细解说一下HAL_FLASH_Program()函数,函数原型如下
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data); /* 返回值类型如下 typedef enum { HAL_OK = 0x00, HAL_ERROR = 0x01, HAL_BUSY = 0x02, HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef; 可以用返回值是否等于HAL_OK来判断是否写入成功. 参数TypeProgram是按什么类型写入数据,不同的stm32支持的数据类型不尽相同,需要自己查看程序定义, 支持的数据类型有按字节/半字/字/双字. 参数Address是要写的flash地址,这里需要注意的是,如果要写入两次数据,第二次写入时地址需要做偏移.一个地址对应一个字节.见如下实例. */ //如果是按字节写入(STM32L4R9VIT6不支持这种方式) uint8_t a=1; uint8_t b=2; HAL_FLASH_Program(FLASH_TYPEPROGRAMDATA_BYTE, addr, a);//第一次写数据 HAL_FLASH_Program(FLASH_TYPEPROGRAMDATA_BYTE, addr+1, b);//第二次写数据,需要地址偏1 //如果是按双字写入(STM32L4R9VIT6支持) uint64_t a=0x12345678; uint64_t b=0x87654321; HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, a);//第一次写数据 HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr+8, b);//第二次写数据,需要地址偏8 //这里不同的是,按字节写入,占用一个地址的数据,所以偏移1,按双字写入,占用8个地址的数据,所以偏移8
读数据就很简单了
//读取操作中不需要加锁和解锁,直接一句代码即可完成一次数据的读取操作 //按字节读取 uint8_t a = 0; a = *(__IO uint8_t*)(start_addr); //按双字读取 uint64_t a = 0; a = *(__IO uint64_t*)(start_addr); //这里依然要注意读取多个数据时地址偏移量的问题,字节偏移1,半字偏移2,字偏移4,双字偏移8
另外,一个比较方便的操作,是读写结构体。这种操作可以很容易的获取和保存多个数据,从而不需要数组赋值
比如我们要保存如下结构体
typedef struct _userinfo//保存的信息 { uint8_t id; uint8_t ip[16]; //ip uint16_t port; //端口 }USERINFO; USERINFO User_Info = {1,"192.168.0.1",8808};
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR); eraseFlash(ADDR_FLASH_USER_START,ADDR_FLASH_USER_END); HAL_FLASH_Unlock(); for(uint8_t i = 0;i < sizeof(USERINFO);i+=8) { if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, ADDR_FLASH_USER_START+i,*((__IO uint64_t*)(&userinfo->id+i)))!= HAL_OK)//这里的数据由于是结构体,需要从里面取值,一定要以第一个成员的地址开始偏移,不能使用结构体自身的地址来偏移,否则数据会出错 { HAL_FLASH_Lock(); return 1; } } HAL_FLASH_Lock();
//为了避免数据出错,读操作一定要和写操作对应,写是按双字写,读就要按双字读,否则就需要解决大小端的问题 for(uint8_t i = 0;iid+i)) = *(__IO uint64_t*)(ADDR_FLASH_USER_START + i);//注意赋值的左边,必须要用结构体第一个成员的地址来偏移,双字偏移量是8 } //这样获取的结构体内容,可以直接通过结构体变量或者结构体指针来访问了.
版权声明:本文为CSDN博主「瑟寒凌风」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sehanlingfeng/article/details/106226223
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。