摘要
这章记录STM32F103C8T6的Flash进行程序内读写操作。程序源码基于STM32CubeMX系列教程8:配置工程模板(串口+不定长数据收发+DMA+IDLE中断+软中断)工程,在工程中添加Flash读写的驱动文件。通过调用调试好的读写API完成Flash的读写操作。这里仅作基础操作的实现,具体的细节需要根据实际情况增加。
Flash的操作流程一般为 解锁->擦除->写入->上锁 四个步骤。如果出现数据无法写入,可检查在写入之前是否进行了擦除操作,注意:这里的擦除最小单位为页擦除,所以会将数据整页擦除,如果需要对数据进行增量式写入,可将每页数据先读取进内存,擦除整页后,在进行写入。
查阅相关数据
通过查阅数据手册,可获取单片机的Flash空间的分布情况,每种型号的单片机Flash空间不同,分布也不同,因此各种单片机的Flash读写操作的代码不通用,所以需要根据单片机型号修改相关的函数功能,但大致流畅相同,因此修改起来也不麻烦。
STM32F103C8T6单片机的flash空间通过页进行操作,Flash空间有64Kb,属于小容量单片机,因此Flash空间的每页大小为1K字节,中容量与大容量的单片机每页大小为2K字节,各型号单片机需要查阅相关手册。
数据读取
这里先完成相对简单的Flash空间的读取操作。
创建FlashRW.h
头文件与FlashRW.c
文件,在头文件中定义一些宏。
//第一个页的基地址
#define ADDR_PAGE_0 ((uint32_t)0x08000000)
//FLASH_PAGE_SIZE Flash空间的页大小,已经在stm32f1xx_hal_flash_ex.h中定义
//小容量与中容量每页有1K(0x400)字节,大容量每页2K(0x800)字节。
//ADDR_PAGE(n) 计算每个页的地址
#define ADDR_PAGE(n) (ADDR_PAGE_0 + (uint32_t)(n) * FLASH_PAGE_SIZE)
//允许作为EEPROM的Flash空间的基地址
#define STM32_EEPRAM_FLASH_BASE ADDR_PAGE(32)
//FLASH等待超时时间
#define FLASH_WAITETIME 50000
在FlashRW.c
文件中创建数据读取函数。
void STMFLASH_Read(uint32_t ReadAddr,uint32_t *pBuffer,uint32_t NumToRead)
{
uint32_t i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]= *(__IO uint32_t*)ReadAddr; //读取4个字节.
ReadAddr+=4;//偏移4个字节.
}
}
在主函数中,调用这个函数,输入地址参数、保存数据的指针、读取的数据的个数。这里注意,地址必须要四字节对齐。
数据擦除与解锁
Flash的操作流程一般为 解锁->擦除->写入->上锁 四个步骤。
在FlashRW.c
文件中创建Flash擦除函数。
HAL_StatusTypeDef STMFLASH_Erase(uint32_t PageAddr,uint32_t Num)
{
HAL_StatusTypeDef status;
FLASH_EraseInitTypeDef FlashEraseInit;
uint32_t PageError=0;
HAL_FLASH_Unlock(); //解锁
FlashEraseInit.TypeErase=FLASH_TYPEERASE_PAGES;
FlashEraseInit.PageAddress=PageAddr;
FlashEraseInit.NbPages=Num;
status = HAL_FLASHEx_Erase(&FlashEraseInit,&PageError);
if(status !=HAL_OK)
{
HAL_FLASH_Lock(); //上锁
return status;//发生错误了
}
FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成
HAL_FLASH_Lock(); //上锁
return status;
}
这里使用了一个FLASH_EraseInitTypeDef
结构体,这个结构体是Hal库提供的Flash擦除操作的一个参数类型,主要有四个参数,分别为
参数 | 说明 |
---|---|
TypeErase | 擦除类型 这里选择页擦除模式。 |
Banks | 因为擦除类型选择的是页擦除模式,因此这个参数不需要设置。 |
PageAddress | 页地址,需要擦除的页的地址。 |
NbPages | 需要擦除的页的数量,大于0,小于最大页数。 |
HAL_FLASH_Unlock()
与HAL_FLASH_Lock()
函数为Hal库提供的Flash的解锁与上锁API,在擦除与写入Flash时,必须要先解锁Flash,擦除与写入完毕后,需要再次上锁。
数据写入
在FlashRW.c
文件中创建Flash写入函数。函数中对写入地址进行了简单校验,如果地址非法,则函数跳出。
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite)
{
uint32_t endaddr=0;
if(WriteAddr<STM32_EEPRAM_FLASH_BASE||WriteAddr%4) return; //非法地址
HAL_FLASH_Unlock(); //解锁
endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
while(WriteAddr<endaddr)//写数据
{
if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,WriteAddr,*pBuffer)!=HAL_OK)//写入数据
{
break; //写入异常
}
WriteAddr+=4;
pBuffer++;
}
HAL_FLASH_Lock(); //上锁
}
测试程序
通过对一页flash进行读取,擦除,写入,读取,擦除,在写入,可看出,上面写的三个函数能够完成对单片机flash的读写操作。
完整代码前往Gitee仓库获取 STM32F103C8T6_FlashRW
STMFLASH_Read(ADDR_PAGE(32),r_buf,256); //从第32页读取1KB数据,这里数据类型为32位数据,因此数量为256个。
printf("\r\n****\r\n");
for(i=0; i<16; i++)
{
for(j=0; j<16; j++)
printf("0x%04x ",r_buf[i*16+j]);
printf("\r\n");
}
printf("\r\n****\r\n");
if(STMFLASH_Erase(ADDR_PAGE(32),1) != HAL_OK) //擦除第32页的数据
{
printf("ERASE ERR\r\n");
}
for(i=0;i<256;i++)
{
w_buf[i] = i;
}
STMFLASH_Write(ADDR_PAGE(32),w_buf,256); //写入1KB字节新的数据 这里数据类型为32位数据,因此数量为256个。
STMFLASH_Read(ADDR_PAGE(32),r_buf,256); //从第32页读取1KB数据 这里数据类型为32位数据,因此数量为256个。
printf("\r\n****\r\n");
for(i=0; i<16; i++)
{
for(j=0; j<16; j++)
printf("0x%04x ",r_buf[i*16+j]);
printf("\r\n");
}
printf("\r\n****\r\n");
if(STMFLASH_Erase(ADDR_PAGE(32),1) != HAL_OK){ //擦除第32页的数据
printf("ERASE ERR\r\n");
}
for(i=0;i<256;i++)
{
w_buf[i] = i+0xff00;
}
STMFLASH_Write(ADDR_PAGE(32),w_buf,256); //写入1KB字节新的数据 这里数据类型为32位数据,因此数量为256个。
STMFLASH_Read(ADDR_PAGE(32),r_buf,256); //从第32页读取1KB数据 这里数据类型为32位数据,因此数量为256个。
printf("\r\n****\r\n");
for(i=0; i<16; i++)
{
for(j=0; j<16; j++)
printf("0x%04x ",r_buf[i*16+j]);
printf("\r\n");
}
printf("\r\n****\r\n");
if(STMFLASH_Erase(ADDR_PAGE(32),1) != HAL_OK) //擦除第32页的数据
{
printf("ERASE ERR\r\n");
}
关于HardFault_Handler报错
程序调试过程中,发现在读取flash时
总会死机,排查后发现是进入了HardFault_Handler中断的wiile中,经过排查后发现是由于ADDR_PAGE_0
的基地址多写了一个0导致。由于地址错误,指针指向了一个非Flash空间的地址,因此进入HardFault_Handler中断。