本文章技术信息均出自:STM32F103系列超详细参考手册-中文版
STM32F103标准库开发—目录
STM32存储器分为以下两种:
具体分类如下:
型号 | ROM容量(字节) | RAM容量(字节) |
---|---|---|
stm32f103x6(小容量) | 32K | 10K |
stm32f103x8(中容量) | 64K | 20K |
stm32f103xB(中容量) | 128K | 20K |
stm32f103xC(大容量) | 256K | 48K |
stm32f103xE(大容量) | 512K | 64K |
stm32f103xG(大容量) | 1024K | 96K |
本文使用是STM32F103C8T6芯片,keil5环境下默认的内存配置见下图:
内部Flash就是STM32存储器的ROM区域,掉电数据不丢失,但是Flash在STM32中比较重要,程序也是保存在这个地方,所以轻易不让用户进行随意的读写,以避免不必要的问题。
按内部Flash的容量大小,分类如下:
型号 | 容量(字节) | 页(字节) | 启动程序 | 宏定义 |
---|---|---|---|---|
stm32f103X6(小容量) | 32K | 1K | startup_stm32f10x_ld.s | STM32F10X_LD |
stm32f103X8(中容量) | 64K | 1K | startup_stm32f10x_md.s | STM32F10X_MD |
stm32f103XB(中容量) | 128K | 1K | startup_stm32f10x_md.s | STM32F10X_MD |
stm32f103XC(大容量) | 256K | 2K | startup_stm32f10x_hd.s | STM32F10X_HD |
stm32f103XE(大容量) | 512K | 2K | startup_stm32f10x_hd.s | STM32F10X_HD |
对于内部Flash的读取操作比较简单,可以直接指针寻址读取数据。
具体程序如下:
/**
*@功能:从内部Flash读取num字节数据
*@参数1:ReadAddress:数据起始地址
*@参数2:*dest_Data: 读取到的数据缓存首地址
*@参数3:num: 读取字节个数
*/
void ReadFlashData(uint32_t ReadAddress, uint8_t *dest_Data, uint32_t num)
{
for(uint32_t i=0;i<num;i++)
{
dest_Data[i]=*(uint8_t*)(STARTADDR+ReadAddress+i); //读取数据
}
}
内部Flash 写入数据之前需要擦除 以前的数据为0xFF,不然写入数据只会覆盖上一次写入的数据,会产生数据错误。
例如:
上一次存数据为0x52,下次直接存储数据0x01。
如果不擦除,就写入数据,结果读出来就是0x51。
由于擦除操作,最少是一页擦除。所以我们写入数据也要一整页的写入,不可以分多次对一页写入数据,因为下一次的擦除会把上一次的数据擦除。
void FLASH_Unlock(void); //解锁函数:在对Flash操作之前必须解锁
void FLASH_Lock(void); //锁定函数:同理,操作完Flash之后必须重新上锁
void FLASH_ClearFlag(uint32_t FLASH_FLAG);
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位半字写入函数
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data); //用户选择字节写入函数
注:这里需要说明,32 位字节写入实际上是写入的两次 16 位数据,写完第一次后地址+2,这与我们前面讲解的 STM32 闪存的编程每次必须写入 16 位并不矛盾。写入 8位实际也是占用的两个地址了,跟写入 16 位基本上没啥区别。
FLASH_Status FLASH_GetStatus(void);
获取Flash状态函数,主要是为了获取Flash的状态,以便于根据状态对Flash进行操作。该函数返回值是通过枚举类型定义的,在代码中可以看到FLASH_Status类型定义如下(具体含义看注释即可):
typedef enum {
FLASH_BUSY = 1, //忙
FLASH_ERROR_PG, //编程错误
FLASH_ERROR_WRP, //写保护错误
FLASH_COMPLETE, //操作完成
FLASH_TIMEOUT //操作超时
}FLASH_Status;
FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);
注:在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。所以在每次操作之前,我们都要等待上一次操作完成这次操作才能开始。
此次操作Flash使用的MCU是STM32F103C8T6,在数据手册中,可以看到STM32F103C8T6的flash起始地址是0x0800 0000,而STM32F103C8T6的Flash大小为64K,可以计算出STM32F103C8T6的Flash地址范围是:0x0800 0000——0x0800 FFFF。这里选取0x0800 F000作为读写操作的起始地址,对于C8T6这款MCU,操作这个起始地址应该算是很安全的范围了。
#define STARTADDR 0x0800F000 //STM32F103C8T6适用
/**
*@功能:向内部Flash写入数据
*@参数1:WriteAddress: 数据要写入的目标地址(偏移地址)
*@参数2: *data: 写入的数据首地址
*@参数3: num: 写入数据的个数
*/
void WriteFlashData(uint32_t WriteAddress, uint16_t *data, uint32_t num)
{
uint16_t sign = 0; //标志位
FLASH_Unlock(); //解锁Flash
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 清除所有标志
sign = FLASH_ErasePage(STARTADDR); //擦除整页
if(sign == FLASH_COMPLETE) //Flash操作完成
{
for(uint32_t i=0;i<num;i++)
{
FLASH_ProgramHalfWord(STARTADDR+WriteAddress+i*2, data[i]); //写入数据
}
}
FLASH_Lock(); //重新锁定Flash
}