航顺FLASH结构
本文内容如标题所示,主要是对航顺MCU的FLASH进行编程操作,进行引脚复用,此前在网上希望找相关实现代码,但是感觉可参考的资料不多,尤其对于特殊的MCU芯片,会有一些独特的需要注意的细节。
这里使用的航顺HK32F0301MXXXC系列MCU,官方数据手册中对其FLASH结构进行了说明:
主FLASH块可以分为64页,每页256字节;FLASH选项字节域共有28个字节。
选项字节域说明
上述已经提到,FLASH选项字节域共有28个字节,按照4字节一字分为7个字。
我们这里希望将FLASH读保护打开,并配置PA0引脚进行复用
通过对手册的阅读,我们了解到RDP
字节即为芯片的读保护等级,nRDP
为RDP
按位取反的结果。
当RDP
字节内容为0xAA
时,为级别0,即没有读保护,可以对主FLASH区域和选项字节进行读写和擦除操作;
RDP
字节内容为0xAA
和0xCC
之外时,即为级别1,仿真调试模式下将无法读取主FLASH,且取消读保护级别时,会首先擦除FLASH,避免代码泄露;
RDP
字节内容为0xCC
时,为保护等级2,且无法再设置为其他保护等级,具体请参照数据手册。
选项字节修改基本流程
一般来说,对FLASH的编程操作包括如下几步:
- 解锁FLASH
- 擦除目标区域
- 对目标区域进行编程写入
- 重新上锁
对于FLASH的解锁,需要向指定寄存器按照顺序写入对应的关键字,如果顺序有误或写入关键字不符合数据手册要求,将会触发硬件错误中断,这里用到的航顺MCU要求写入关键字如下所示。
这里对于选项字节的操作,还需要额外向FLASH_OPTKEYR寄存器写入关键字以解锁FLASH,需要特别注意。
在解锁FLASH之后,一般会先进行擦除操作,这里对其进行一个简要的讲解:
由于FLASH存储器的特性和工作机制,对FLASH存储单元的编程只能由1改为0,而不能将0改为1。
这就表明如果希望正确写入要求的数据,FLASH存储单元内原本的数据应该全是1,在16进制表示下,也就是我们看到的数据应该是满屏的ff,才能保证正确写入数据,否则可能会与预期所不同。
最后在对FLASH目标区域完成编程后,进行上锁以防止预期之外的操作,这样就完成了一次比较标准的FLASH编程操作。
代码实现分析
考虑到我们希望开启读保护(等级为1),并复用PA0引脚,通过对数据手册的阅读,我们可以确定我们需要修改选项字节域中的RDP
和NRST_IOEN
字节。
其中RDP
可以写入0xBB
(与库函数相同),NRST_IOEN
需要写入0x3456
,此时PA0引脚才能通过IOMUX配置为IO口,见下图说明。
代码示范
这里简要的展示
/********************************* 函数 ****************************************
* @brief 写入FLASH
* @note 对FLASH的选项字节进行修改,使能PA0的IO功能
* @param FlashAddress: FLASH地址
* @param WriteBuff: 写入数据
* @retval None
*/
void Write_Flash(uint32_t FlashAddress,uint32_t WriteBuff)
{
FLASH_Unlock(); // 解锁FLASH
FLASH_OB_Unlock(); // 解锁选项字节
// FLASH_OB_Erase(); // 擦除RDP外的选项字节
// 此处将FLASH_OB_Erase()函数内部代码拷贝到此处,并修改了读保护选项字节
FLASH_Status status = FLASH_COMPLETE;
FLASH->CR |= FLASH_CR_OPTER;
FLASH->AR = 0x1FFFF000;
FLASH->CR |= FLASH_CR_STRT;
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
if(status == FLASH_COMPLETE)
{
/* If the erase operation is completed, disable the OPTER Bit */
FLASH->CR &= ~FLASH_CR_OPTER;
/* Enable the Option Bytes Programming operation */
FLASH->CR |= FLASH_CR_OPTPG;
OB->USER_RDP = 0x00FF44BB;
}
FLASH->CR &= ~FLASH_CR_OPTPG;
FLASH_OB_ProgramData(FlashAddress, WriteBuff); // 设置NRST_IOEN
FLASH_OB_Lock();
FLASH_Lock();
}
int main(void)
{
// 向flash地址0x1FFFF018写入0x3456,用以打开PA0的IO功能
uint32_t data = 0x3456ffff;
Write_Flash(0x1FFFF018, data);
while(1)
{
// ...业务代码
}
}
相关说明
这里对代码做一些说明:
可以看到我们本来只需要修改NRST_IOEN
所在的两字节长的位置,但是这里我们写入了一个uint32_t
类型数据,这里是使用了航顺对应的库函数,具体库函数实现如下。
/**
* @brief Programs a half word at a specified Option Byte Data address.
* @note To correctly run this function, the FLASH_OB_Unlock() function must be called before.
* @note Call the FLASH_OB_Lock() to disable the flash control register access and the option
* bytes (recommended to protect the FLASH memory against possible unwanted operation)
* @param Address: specifies the address to be programmed.
* This parameter can be 0x1FFFF004 to 0x1FFFF017.
* @param Data: specifies the data to be programmed.
* @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
* FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_OB_ProgramData(uint32_t Address, uint32_t Data)
{
FLASH_Status status = FLASH_COMPLETE;
/* Check the parameters */
assert_param(IS_OB_DATA_ADDRESS(Address));
if(status == FLASH_COMPLETE)
{
/* Enables the Option Bytes Programming operation */
FLASH->CR |= FLASH_CR_OPTPG;
*(__IO uint32_t*)Address = Data;
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
if(status != FLASH_TIMEOUT)
{
/* If the program operation is completed, disable the OPTPG Bit */
FLASH->CR &= ~FLASH_CR_OPTPG;
}
}
/* Return the Option Byte Data Program Status */
return status;
}
我们看到该函数要求传入的是uint32_t
类型数据,并在修改前通过assert_param(IS_OB_DATA_ADDRESS(Address));
函数对写入范围进行了检查,确保我们正在修改的是选项字节区域(具体可以进一步查看库函数相关实现文件中的定义)。
之后,通过语句*(__IO uint32_t*)Address = Data;
,将Data
的内容传到Address
变量所指示的地址处,这里可能会要求字节对齐等,尚未进行比较完全的测试,各位感兴趣可以尝试看看能不能直接写入uint16_t
类型数据。
代码中有一段注释掉的FLASH_OB_Erase();
语句,这里简要解释一下为什么直接把库函数的一些实现修改了之后放到这里。
通过对库函数的阅读(源码如下),我们发现该函数在完成擦除后,会将读保护等级设置为0,如果此前我们将读保护等级改为1,那么这边改为0之前会使得FLASH首先进行全片擦除,也就是我们烧录进的程序将会清空,因此这里手动设置在对选项字节进行擦除后,读保护等级依然设置为1。
当然这里肯定会有更简洁的写法,本文章仅对FLASH选项字节编程进行一个示意性的说明展示。
/**
* @brief Erases the FLASH option bytes.
* @note To correctly run this function, the FLASH_OB_Unlock() function must be called before.
* @note Call the FLASH_OB_Lock() to disable the flash control register access and the option
* bytes (recommended to protect the FLASH memory against possible unwanted operation)
* @note This functions erases all option bytes except the Read protection (RDP).
* @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
* FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_OB_Erase(void)
{
FLASH_Status status = FLASH_COMPLETE;
FLASH->CR |= FLASH_CR_OPTER;
FLASH->AR = 0x1FFFF000;
FLASH->CR |= FLASH_CR_STRT;
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
if(status == FLASH_COMPLETE)
{
/* If the erase operation is completed, disable the OPTER Bit */
FLASH->CR &= ~FLASH_CR_OPTER;
/* Enable the Option Bytes Programming operation */
FLASH->CR |= FLASH_CR_OPTPG;
OB->USER_RDP = 0x00FF55AA;
}
FLASH->CR &= ~FLASH_CR_OPTPG;
return status;
}
结果验证
这里我们使用航顺官方提供的HK Config软件,在正确选择了所使用的MCU类型之后,我们可以点击连接按键,之后选择读取FLASH选项字节,按下读取按键,即可查看当前MCU的选项字节情况。
如下如所示,可以看到对于一个尚未进行FLASH选项字节编程操作的MCU,它的读保护等级默认为0,对应RDP-nRDP
字节域值为0xaa 0x55
,最后两字节为NRST_IOEN
字,默认为0xff 0xff
。
在烧录编写了FLASH选项字节编程代码的程序后,我们再次读取,应当可以看到RDP-nRDP
、NRST_IOEN
字节数据发生了变化,与我们程序中所进行的编写操作一致。
注意此时开启了读保护之后,若使用ARM仿真器和keil(仅对本人遇到的情况进行陈述,不清楚其他烧录方式具体会有什么差异)再次进行程序下载,将会出现以下提示。
这时我们可以继续使用HK Config软件,点击全片擦除,即可将FLASH区域清空,并重新设定读保护等级为0,,这时我们又可以进行程序的下载。
此外,我们也可以使用HK Config
软件直接设置FLASH选项字节,如下图所示,我们切换到Option Bytes
页面,可以选择读保护等级,勾选上NRST_IOEN
以复用PA0引脚,之后我们点击编程按键,即可直接修改MCU的FLASH选项字节。
其他注意事项
尽管FLASH编程操作在程序中可以进行,但是由于在编程前一般需要擦除操作,而有时我们不能保证MCU供电情况绝对稳定,有时会出现在完成FLASH擦除后,尚未开始进行编程就发生断电,使得数据丢失或程序未按照预期情况执行。
对于这样的情况,假如我们是希望在FLASH上保存记录数据等,可以通过使用两块FLASH空间,进行交替读写、记录等,保证即使发生擦除后断电,也可以有一份完整的记录数据仍然存在。而选项字节的操作,最好能够在烧录时进行设定,之后不会再做更改,可以考虑PowerWriter等软件,修改烧录文件的选项字节设置等(尽管本人现在也不太会用这个软件)。
此外,航顺MCU的选项字节是在设置之后,复位才会生效(如下图所示),有时如果觉得FLASH选项字节编程正确完成,但是依然没看到预期效果时,可以考虑是否是这一点的问题。
注意要复位系统后才生效!
参考资料
HK32F0301MxxxxC数据手册V1.5
HK32F0301MxxxxC用户手册V1.1