(一)闪存
在之前的学习中,已经学习过了W25Q64这个外挂闪存,在stm32内部也有一块闪存,其主要用于存放我们编译的代码,如果我们需要一些掉电不丢失的数据,但是又懒得外挂一块闪存,就可以把少量数据写入stm32内部的闪存中,其操作与写入外设闪存的操作基本相同,且有库函数封装,使用的函数也很简单
void FLASH_Unlock(void);
// 解锁 FLASH 编写擦除控制器
void FLASH_Lock(void);
// 锁定 FLASH 编写擦除控制器
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
// 擦除一页
FLASH_Status FLASH_EraseAllPages(void);
// 擦除所有
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
// 写入32位数据
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
// 写入16位数据
(二)读取、写入、擦除
(1)读取
对于读取闪存,没有封装的库函数用于读取,因为要读取只要给定指针即可,通过解引用一个地址指针则可以读取任意位置任意大小的数据
uint32_t flash_read_word(uint32_t address)
{
return *(uint32_t*)address;
}
uint16_t flash_read_halfword(uint32_t address)
{
return *(uint16_t*)address;
}
uint8_t flash_read_byte(uint32_t address)
{
return *(uint8_t*)address;
}
这里分别读取32比特(1字)、16比特(半字)、8比特(1字节),接收的是一个32位的无符号整形数字,里面存放要读取的地址,随后强转换为不同数据大小的指针,最后解引用读取该指针地址下的数据
(2)写入
写入只需要调用库函数即可,写入之前要先解锁编写擦除控制器,写完后要对编写擦除控制器上锁,stm还对写入等操作进行了加锁,还有写入等待事件等问题,但是这些封装的库函数都帮我们完成了
void flash_write_half_word(uint32_t address, uint16_t inf)
{
FLASH_Unlock();
FLASH_ProgramHalfWord(address, inf);
FLASH_Lock();
}
void flash_write_word(uint32_t address, uint32_t inf)
{
FLASH_Unlock();
FLASH_ProgramWord(address, inf);
FLASH_Lock();
}
这里分别写入16位数据和32位数据
(3)擦除
擦除有所有擦除和按页擦除,同样,擦除操作之前要解锁编写擦除控制器,擦除完成后要对其上锁
void flash_erase_page(uint32_t address)
{
FLASH_Unlock();
FLASH_ErasePage(address);
FLASH_Lock();
}
void flash_erase_all()
{
FLASH_Unlock();
FLASH_EraseAllPages();
FLASH_Lock();
}
(三)不覆盖写入
我们在写入之前必须执行擦除操作,如果我们在上电后写入,有可能把上次存储的掉电不丢失的数据擦除,我们可以在上电后进行一次初始化,把里面的数据都搬到数据里,然后再执行写入等操作
这里只操作闪存中的最后一页,将其最开始的16位作为标志位,判断之前是否写过,0x5555即原本有数据,0xFFFF为无数据
#define last_page 0x0801FC00
uint16_t information[128] = {0};
void op_flash_init()
{
uint16_t i;
if (flash_read_halfword(last_page) == 0x5555)
{
for (i = 0; i < 128; i++)
{
information[i] = flash_read_halfword(last_page+i*2);
}
}
}
在写入数据之前,把标志位置为0x5555,随后擦除页,写入数据所有数据,我们在主程序中想要修改数据可以直接修改数组即可
void op_write_halfword()
{
uint16_t i;
information[0] = 0x5555;
flash_erase_page(last_page);
for (i = 0; i < 128; i++)
{
flash_write_half_word(last_page + i*2, information[i]);
}
}
再写一个读写函数,方便读取最后一页的第几个16位数据
uint16_t op_read_half_word(int place)
{
int i;
for (i = 0; i < 128; i++)
{
information[i] = flash_read_halfword(last_page+i*2);
}
return information[place];
}
这样我们就可以在主函数里先调用初始化函数,然后可以写入要写的数据,这样即使掉电也不会丢失
(四)读取芯片ID号
对于stm32芯片,每个芯片都有独一无二的芯片ID号,称为产品的唯一身份标识,总共有96位数据,其基地址为0x1FFF F7E8,可以通过这个地址加适当的地址偏移来读取芯片ID,芯片的ID有这些用途
这里就先读取芯片ID来显示一下
读取芯片ID和读取闪存一样,只要一个指针即可
#include "stm32f10x.h" // Device header
#include "OLED.h"
#define base_loc 0x1FFFF7E8
int main()
{
OLED_Init();
OLED_ShowHexNum(1, 1, *(uint16_t*)base_loc, 4);
OLED_ShowHexNum(2, 1, *(uint16_t*)(base_loc+0x02), 4);
OLED_ShowHexNum(3, 1, *(uint32_t*)(base_loc+0x04), 8);
OLED_ShowHexNum(4, 1, *(uint32_t*)(base_loc+0x08), 8);
while (1)
{
}
return 0;
}
这样我们就可以读取自己stm32的身份标识了
(五)总结
通过读写stm32的闪存部分和读取芯片的身份标识,我们了解了读写stm32闪存部分的基本操作和方法