我的开发板是stm32f103xx,所以片内flash主存储器大小128K,单页为1K。不同的芯片flash大小不同,页大小也不同。
主存储器
用来存储Rom程序,烧写的区域。当然也可以存一些其他东西,比如做nvram使用。
信息块
这个区域分两个部分。
存储区,用户不可写区域,用来存储ISP下载时用到的程序。当用ISP模式进行烧写时,自动调用里面的程序下载数据并烧写到flash里。
用户选项字,存储硬件配置信息。某些复用的外设可能在上电复位后就要立即知道它被用作其中哪个用途。
用户选项字默认不可写,它上电自动加锁。只有通过向FLASH_OPTKEYR依次写入约定解锁码KEY1和KEY2后才能解锁
寄存器
FPEC控制器
FPEC键寄存器(FLASH_KEYR) 写入键值解锁。
选项字节键寄存器(FLASH_OPTKEYR) 写入键值解锁选项字节操作。
闪存控制寄存器(FLASH_CR) 选择并启动闪存操作。
闪存状态寄存器(FLASH_SR) 查询闪存操作状态。
闪存地址寄存器(FLASH_AR) 存储闪存操作地址。
选项字节寄存器(FLASH_OBR) 选项字节中主要数据的映象。
写保护寄存器(FLASH_WRPR) 选项字节中写保护字节的映象。
读保护
读保护默认开启,开启后:
如果CPU从SRAM取指,程序不能操作flash,只能整片格式化flash。
使用j-tag接口,不能访问flash。
写保护
如果CPU从片内flash的主存区取指,程序禁止擦写前4K地址。
每一个4K的区域都对应一个位,用来区该区域进行写保护。
如果控制寄存器通过置位加锁,想要写FLASH_CR寄存器,必须先开锁。解锁的办法是向寄存器FLASH_KEYR依次写入两个约定的数值。
写操作和擦除操作后都可以引发中断。
半周期用来在低速时钟下操作flash,预取缓存用来加快读写速度。
片内Flash的读取很简单,只要通过访问映射给flash的地址,系统就会自动识别并读取数据。而片内Flash又不需要初始化,所以上层需要的接口也比较简单,基本上只需要写操作、中断控制、锁控制3个功能。
flash编程函数:
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
HAL_StatusTypeDef HAL_FLASH_Program_IT(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
TypeProgram指定了写半字,全字还是双字,所以Data是一个64位的变量。后一个函数是指写操作时使能中断。
这里简要说明一下两个函数的区别。
由于一次只能写入16bit的数据,当要写入超过半字的数据时,非中断用的方法是在函数里等待上一次写操作完成,然后再继续写入半字。
for (index = 0; index < nbiterations; index++)
{
FLASH_Program_HalfWord((Address + (2*index)), (uint16_t)(Data >> (16*index)));
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE);
/* If the program operation is completed, disable the PG Bit */
CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
/* In case of error, stop programation procedure */
if (status != HAL_OK)
{
break;
}
}
中断方式是写入后不等待,等发生中断后再继续写下一个半字。
函数调用写半字的操作后,直接返回:
/*Program halfword (16-bit) at a specified address.*/
FLASH_Program_HalfWord(Address, (uint16_t)Data);
return status;
然后在中断里继续写入余下数据:
if(pFlash.ProcedureOnGoing == FLASH_PROC_PAGEERASE)
{
/* Nb of pages to erased can be decreased */
pFlash.DataRemaining--;
/* Indicate user which page address has been erased*/
HAL_FLASH_EndOfOperationCallback(pFlash.Address);
......
两种方式都由错误处理机制,只有当一个写入操作正确返回时,下一个写入操作才会被进行。
在非中断模式下,callback函数用来在写函数返回前进行一些操作。
/* Callbacks in non blocking modes */
void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue);
void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue);
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主存区和选项字的加锁和解锁。另外HAL_FLASH_OB_Launch是指重新读取选项字,该操作会重置系统。
/* FLASH IRQ handler method */
void HAL_FLASH_IRQHandler(void);
用来处理写中断。
HAL.FLASH的接口较为简单,在HAL的设计思想里,可以看到它一直试图将接口简化,把更多的复杂度推入私有成员里。它较之Stdlib在接口函数上使用起来更加简洁,这也是硬件抽象层的核心思想。随着芯片性能的提升,开发者希望把更多的精力花在功能的实现上,而非性能的优化上。牺牲一点执行时间,换取代码结构的清晰,增加代码的可维护性,是完全值得的。