完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
本章节为大家讲解内部Flash的基础知识和对应的HAL库API。
目录
第70章 STM32H7的内部Flash基础知识和HAL库API
70.1 初学者重要提示
70.2 内部Flash基础知识
70.2.1 内部Flash的硬件框图
70.2.2 内部Flash框架
70.2.3 内部Flash读操作
70.2.4 内部Flash写入和擦除操作
70.2.5 内部Flash读保护
70.2.6 内部Flash选项字节
70.2.7 内部Flash的ECC校验
70.3 内部Flash的HAL库用法
70.3.1 内部Flash结构体FLASH_TypeDef
70.3.2 内部Flash擦除结构体FLASH_EraseInitTypeDef
70.3.3 内部Flash的操作总结
70.4 内部Flash源文件stm32h7xx_hal_flash.c
70.4.1 函数HAL_FLASH_Lock
70.4.2 函数HAL_FLASH_Unlock
70.4.3 函数HAL_FLASH_Program
70.4.4 函数HAL_FLASHEx_Erase
70.5 总结
认识一个外设,最好的方式就是看它的框图,方便我们快速的了解内部Flash的基本功能,然后再看手册了解细节。
通过这个框图,我们可以得到如下信息:
D1域总线时钟。
Power on reset 上电复位。
D1域系统复位。
flash中断请求输出。
STM32H7的两个Flash BANK是独立的,读写和擦除互补影响,256bit带宽,CPU访问是采用的两个64bit AXI总线。
关于内部Flash的框架,了解以下几个知识点即可:
BANK1的地址范围:0x0800 0000到0x080F FFFF。
BANK2的地址范围:0x0810 0000到0x081F FFFF。
STM32H7的内部Flash读操作跟内部RAM的读操作是一样的,支持64bit,32bit,16bt和8bit,使用比较简单。这里我们重点普及一个知识点,H7的内部Flash在不同主频下需要做的延迟参数:
对于上面的表格,大家可以看到,当延迟等待设置为0的时候,即无等待,单周期访问,速度可以做到70MHz。增加1个Flash周期后,访问速度可以做到140MHz。当增加到3个或4个Flash周期后,最高速度可以做到225MHz。
了解了这个知识点后,再来看下面的时序,非常具有参考意义:
注:ACLK、ARADDR、ARVALID、RDATA、RVALID 和RLAST是AXI总线信号。Flash读和Flash数据是 Flash 接口信号。
关于这个时序要要认识到以下几点:
下面是连续读取8个64bit数据的时序图,跟连续读取4个64bit数据基本是一样的,只是多读取了4组数据。
最重要的知识点放在开头说:STM32H7内部Flash的写操作地址必须是32字节对齐(此地址对32求余数为0),写入的数据量也必须是32字节整数倍,不足32字节整数倍,补0也要是整数倍。
这里我们重点了解Flash的写入和擦除流程。Flash的写入扇区流程如下:
Flash的擦除流程如下:
内部Flash支持三级读保护RDP(read out protection)。
默认设置,所有读写和擦除操作都可以正常支持。
特别注意:Level2修改是永久性的,一旦配置为Level2将不再支持被修改。
如果大家要设置读保护的话,使用HAL的API可以设置,也可以使用STM32CubeProg设置:
Flash选项字节主要用于boot地址设置,安全保护,Flash扇区保护等,涉及到的选项比较多。如果大家打算了解这一部分的话,使用STM32CubeProg进行设置即可,也比较方便。
这里先说下为什么内部Flash要带ECC校验,因为随着芯片的制造工艺水平越高,带电粒子产生的位翻转就越多,此时的ECC是必须要有的,一般可以纠正1-2个bit,安全等级高的Flash类存储器和RAM类都是必须要带ECC的。
对于STM32H7带的ECC校验,一般不需要用户去管理。
关于ECC方面的知识,专门整理了一个帖子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86777
内部Flash相关的寄存器是通过HAL库中的结构体FLASH_TypeDef定义的,在stm32h743xx.h中可以找到这个类型定义:
typedef struct
{
__IO uint32_t ACR;
__IO uint32_t KEYR1;
__IO uint32_t OPTKEYR;
__IO uint32_t CR1;
__IO uint32_t SR1;
__IO uint32_t CCR1;
__IO uint32_t OPTCR;
__IO uint32_t OPTSR_CUR;
__IO uint32_t OPTSR_PRG;
__IO uint32_t OPTCCR;
__IO uint32_t PRAR_CUR1;
__IO uint32_t PRAR_PRG1;
__IO uint32_t SCAR_CUR1;
__IO uint32_t SCAR_PRG1;
__IO uint32_t WPSN_CUR1;
__IO uint32_t WPSN_PRG1;
__IO uint32_t BOOT_CUR;
__IO uint32_t BOOT_PRG;
uint32_t RESERVED0[2];
__IO uint32_t CRCCR1;
__IO uint32_t CRCSADD1;
__IO uint32_t CRCEADD1;
__IO uint32_t CRCDATA;
__IO uint32_t ECC_FA1;
uint32_t RESERVED1[40];
__IO uint32_t KEYR2;
uint32_t RESERVED2;
__IO uint32_t CR2;
__IO uint32_t SR2;
__IO uint32_t CCR2;
uint32_t RESERVED3[4];
__IO uint32_t PRAR_CUR2;
__IO uint32_t PRAR_PRG2;
__IO uint32_t SCAR_CUR2;
__IO uint32_t SCAR_PRG2;
__IO uint32_t WPSN_CUR2;
__IO uint32_t WPSN_PRG2;
uint32_t RESERVED4[4];
__IO uint32_t CRCCR2;
__IO uint32_t CRCSADD2;
__IO uint32_t CRCEADD2;
__IO uint32_t CRCDATA2;
__IO uint32_t ECC_FA2;
} FLASH_TypeDef;
这个结构体的成员名称和排列次序和CPU的寄存器是一 一对应的。
__IO表示volatile, 这是标准C语言中的一个修饰字,表示这个变量是非易失性的,编译器不要将其优化掉。core_m7.h 文件定义了这个宏:
#define __O volatile /*!< Defines 'write only' permissions */
#define __IO volatile /*!< Defines 'read / write' permissions */
下面我们看下Flash的定义,在stm32h743xx.h文件。
#define PERIPH_BASE (0x40000000UL)
#define D1_AHB1PERIPH_BASE (PERIPH_BASE + 0x12000000UL)
#define FLASH_R_BASE (D1_AHB1PERIPH_BASE + 0x2000UL)
#define FLASH ((FLASH_TypeDef *) FLASH_R_BASE) <----- 展开这个宏,(FLASH_TypeDef *)0x52002000
我们访问Flash的CR1寄存器可以采用这种形式:FLASH->CR1 = 0。
下面是做内部Flash擦除的结构体,用到的地方比较多:
typedef struct
{
uint32_t TypeErase;
uint32_t Banks;
uint32_t Sector;
uint32_t NbSectors;
uint32_t VoltageRange;
} FLASH_EraseInitTypeDef;
下面将结构体成员逐一做个说明:
用于选择BANK擦除还是扇区擦除,H743有两个BANK,每个BANK有个8个扇区,每个扇区128KB。具体支持的参数如下:
#define FLASH_TYPEERASE_SECTORS 0x00U /* 扇区方式擦除 */
#define FLASH_TYPEERASE_MASSERASE 0x01U /* BANK方式擦除 */
用于选择要擦除的BANK,或者两个BANK都选择:
#define FLASH_BANK_1 0x01U /* Bank 1 */
#define FLASH_BANK_2 0x02U /* Bank 2 */
#define FLASH_BANK_BOTH (FLASH_BANK_1 | FLASH_BANK_2) /* Bank1 和 Bank2 */
用于选择要擦除的扇区:
#define FLASH_SECTOR_0 0U /* Sector Number 0 */
#define FLASH_SECTOR_1 1U /* Sector Number 1 */
#define FLASH_SECTOR_2 2U /* Sector Number 2 */
#define FLASH_SECTOR_3 3U /* Sector Number 3 */
#define FLASH_SECTOR_4 4U /* Sector Number 4 */
#define FLASH_SECTOR_5 5U /* Sector Number 5 */
#define FLASH_SECTOR_6 6U /* Sector Number 6 */
#define FLASH_SECTOR_7 7U /* Sector Number 7 */
用于设置要擦除的扇区个数,对于STM32H743来说,范围1到8。
用于设置编程的并行位数,电压不同,位数不同:
#define FLASH_VOLTAGE_RANGE_1 0x00000000U /* Flash program/erase by 8 bits */
#define FLASH_VOLTAGE_RANGE_2 FLASH_CR_PSIZE_0 /* Flash program/erase by 16 bits */
#define FLASH_VOLTAGE_RANGE_3 FLASH_CR_PSIZE_1 /* Flash program/erase by 32 bits */
#define FLASH_VOLTAGE_RANGE_4 FLASH_CR_PSIZE /* Flash program/erase by 64 bits */
使用方法由HAL库提供:
此文件涉及到的函数较多,这里把几个常用的函数做个说明:
函数原型:
HAL_StatusTypeDef HAL_FLASH_Lock(void)
{
/* 设置FLASH Bank1控制寄存器Lock位,即禁止访问 */
SET_BIT(FLASH->CR1, FLASH_CR_LOCK);
/* 验证Flash Bank1是否已经被锁住 */
if (READ_BIT(FLASH->CR1, FLASH_CR_LOCK) == 0U)
{
return HAL_ERROR;
}
/* 设置FLASH Bank2控制寄存器Lock位,即禁止访问 */
SET_BIT(FLASH->CR2, FLASH_CR_LOCK);
/* 验证Flash Bank2是否已经被锁住 */
if (READ_BIT(FLASH->CR2, FLASH_CR_LOCK) == 0U)
{
return HAL_ERROR;
}
return HAL_OK;
}
函数描述:
用于Flash加锁,加锁后将不能对Flash进行编程和擦除。
函数原型:
HAL_StatusTypeDef HAL_FLASH_Unlock(void)
{
if(READ_BIT(FLASH->CR1, FLASH_CR_LOCK) != 0U)
{
/* 允许访问Flash Bank1 */
WRITE_REG(FLASH->KEYR1, FLASH_KEY1);
WRITE_REG(FLASH->KEYR1, FLASH_KEY2);
/* 验证是否已经解锁 */
if (READ_BIT(FLASH->CR1, FLASH_CR_LOCK) != 0U)
{
return HAL_ERROR;
}
}
if(READ_BIT(FLASH->CR2, FLASH_CR_LOCK) != 0U)
{
/* 允许访问Flash Bank2 */
WRITE_REG(FLASH->KEYR2, FLASH_KEY1);
WRITE_REG(FLASH->KEYR2, FLASH_KEY2);
/* 验证是否已经解锁 */
if (READ_BIT(FLASH->CR2, FLASH_CR_LOCK) != 0U)
{
return HAL_ERROR;
}
}
return HAL_OK;
}
函数描述:
此函数用于Flash解锁,解锁后可以对Flash进行擦除和编程。
函数原型:
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t FlashAddress, uint32_t DataAddress)
{
HAL_StatusTypeDef status;
__IO uint32_t *dest_addr = (__IO uint32_t *)FlashAddress;
__IO uint32_t *src_addr = (__IO uint32_t*)DataAddress;
uint32_t bank;
uint8_t row_index = FLASH_NB_32BITWORD_IN_FLASHWORD;
/* 检测参数 */
assert_param(IS_FLASH_TYPEPROGRAM(TypeProgram));
assert_param(IS_FLASH_PROGRAM_ADDRESS(FlashAddress));
/* 上锁 */
__HAL_LOCK(&pFlash);
#if defined (FLASH_OPTCR_PG_OTP)
if((IS_FLASH_PROGRAM_ADDRESS_BANK1(FlashAddress)) || (IS_FLASH_PROGRAM_ADDRESS_OTP(FlashAddress)))
#else
if(IS_FLASH_PROGRAM_ADDRESS_BANK1(FlashAddress))
#endif
{
bank = FLASH_BANK_1;
}
else
{
bank = FLASH_BANK_2;
}
/* 错误标识,无错误 */
pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;
/* 等待操作完成 */
status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, bank);
if(status == HAL_OK)
{
if(bank == FLASH_BANK_1)
{
#if defined (FLASH_OPTCR_PG_OTP)
if (TypeProgram == FLASH_TYPEPROGRAM_OTPWORD)
{
/* 设置OTP寄存器的PG位,使能可以编程 */
SET_BIT(FLASH->OPTCR, FLASH_OPTCR_PG_OTP);
}
else
#endif
{
/* 设置PG位,使能可编程 */
SET_BIT(FLASH->CR1, FLASH_CR_PG);
}
}
else
{
/* 设置PG位 */
SET_BIT(FLASH->CR2, FLASH_CR_PG);
}
__ISB();
__DSB();
#if defined (FLASH_OPTCR_PG_OTP)
if (TypeProgram == FLASH_TYPEPROGRAM_OTPWORD)
{
/* 编程OTP(16 bits) */
*(__IO uint16_t *)FlashAddress = *(__IO uint16_t*)DataAddress;
}
else
#endif
{
/* 对Flash进行编程 */
do
{
*dest_addr = *src_addr;
dest_addr++;
src_addr++;
row_index--;
} while (row_index != 0U);
}
__ISB();
__DSB();
/* 等待最后一次操作完成 */
status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, bank);
#if defined (FLASH_OPTCR_PG_OTP)
if (TypeProgram == FLASH_TYPEPROGRAM_OTPWORD)
{
/* 如果编程操作完成,关闭OTP PG位 */
CLEAR_BIT(FLASH->OPTCR, FLASH_OPTCR_PG_OTP);
}
else
#endif
{
if(bank == FLASH_BANK_1)
{
/* 如果操作完成,关闭PG位 */
CLEAR_BIT(FLASH->CR1, FLASH_CR_PG);
}
else
{
/* 如果操作完成,关闭PG位 */
CLEAR_BIT(FLASH->CR2, FLASH_CR_PG);
}
}
}
/* 解锁 */
__HAL_UNLOCK(&pFlash);
return status;
}
函数描述:
此函数主要用于Flash编程,固定编程32个字节数据。
函数参数:
注意事项:
函数原型:
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError)
{
HAL_StatusTypeDef status = HAL_OK;
uint32_t sector_index;
/* 检查参数 */
assert_param(IS_FLASH_TYPEERASE(pEraseInit->TypeErase));
assert_param(IS_FLASH_BANK(pEraseInit->Banks));
/* 上锁 */
__HAL_LOCK(&pFlash);
/* 无错误 */
pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;
/* 等待BANK1的操作完成 */
if((pEraseInit->Banks & FLASH_BANK_1) == FLASH_BANK_1)
{
if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_1) != HAL_OK)
{
status = HAL_ERROR;
}
}
/* 等待BANK2的操作完成 */
if((pEraseInit->Banks & FLASH_BANK_2) == FLASH_BANK_2)
{
if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_2) != HAL_OK)
{
status = HAL_ERROR;
}
}
if(status == HAL_OK)
{
if(pEraseInit->TypeErase == FLASH_TYPEERASE_MASSERASE)
{
/* 整个BANK1或者BANK2擦除 */
FLASH_MassErase(pEraseInit->VoltageRange, pEraseInit->Banks);
/* 等待操作完成 */
if((pEraseInit->Banks & FLASH_BANK_1) == FLASH_BANK_1)
{
if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_1) != HAL_OK)
{
status = HAL_ERROR;
}
/* 如果擦除完成,关闭BANK1的BER位 */
FLASH->CR1 &= (~FLASH_CR_BER);
}
if((pEraseInit->Banks & FLASH_BANK_2) == FLASH_BANK_2)
{
if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_2) != HAL_OK)
{
status = HAL_ERROR;
}
/* 如果擦除操作完成,关闭BANK2的BER位 */
FLASH->CR2 &= (~FLASH_CR_BER);
}
}
else
{
/* 初始化扇区错误码 */
*SectorError = 0xFFFFFFFFU;
/* 扇区方式擦除 */
for(sector_index = pEraseInit->Sector; sector_index < (pEraseInit->NbSectors + pEraseInit->Sector);
sector_index++)
{
FLASH_Erase_Sector(sector_index, pEraseInit->Banks, pEraseInit->VoltageRange);
if((pEraseInit->Banks & FLASH_BANK_1) == FLASH_BANK_1)
{
/* 等待BANK1操作完成 */
status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_1);
/* 如果擦除操作完成,禁止SER位 */
FLASH->CR1 &= (~(FLASH_CR_SER | FLASH_CR_SNB));
}
if((pEraseInit->Banks & FLASH_BANK_2) == FLASH_BANK_2)
{
/* 等待BANK2操作完成 */
status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_2);
/* 如果擦除操作完成,禁止SER位 */
FLASH->CR2 &= (~(FLASH_CR_SER | FLASH_CR_SNB));
}
if(status != HAL_OK)
{
/* 如果擦除出错,停止后续擦除,返回擦除异常的扇区号 */
*SectorError = sector_index;
break;
}
}
}
}
/* 解锁 */
__HAL_UNLOCK(&pFlash);
return status;
}
函数描述:
用于内部Flash的批量擦除(BANK擦除)和扇区方式擦除。
函数参数:
本章节就为大家讲解这么多,对于内部Flash编程来说,掌握本章节的这些知识点就够用了,更多的知识点可以看STM32H7的参考手册学习。