JNH官网

【ARM】Pack包制作之——Flash编程算法文件(.FLM)的教程

使用 MDK 下载程序时,JNH官网可能需要在 Debug 设置的 Flash Download 子选项卡中,选择编程算法,不过如果JNH官网已经安装了芯片包(DFP 包)以后,就可以直接得到对应的编程算法,并不需要JNH官网去修改它。但是在面对以下某些情况时,JNH官网依然要掌握这一技能,

①:当你是一个芯片包的开发者时;

②:或者你有独特的下载需求(比如在你的程序里加一些校验信息,更改选项字节);

③:或者某些芯片发布的过早,还没有对应的芯片包时;

这些时候就需要去了解学习如何制作 FLM 文件,如图 1 所示(以JNH官网 Emdoor 为例):

图 1

二、制作FLM

(1)、如图 2 所示,制作 FLM 所需要的 Keil 工程文件,可以在 Keil 的安装目录下找到,总共有 2 个地方可以找到,路径如下所示:

①:F:Keil_MDK539ARMFlash_Template

②:F:Keil_MDK539PacksARMCMSIS5.9.0Device_Template_Flash


图 2

(2)、进入模版工程以后,如图 3 可以看到,其实整个工程在结构上只有 3 个主要文件:(如何理解并编写 Flash 编程算法代码请看本文最后部分)

①:FlashPrg.c

②:FlashDev.c

③:FlashOS.H

接下来,JNH官网对这 3 个文件,逐个进行解析。



图 3

(3)、FlashPrg.c

在这个 C 文件里,需要JNH官网包含一些必需的 Flash 编程函数:Init, UnInit, EraseSector, 和 ProgramPage. 然后除了这些必需函数,可以根据设备功能(或为了加快执行速度),可以实现 EraseChip、BlankCheck 和 Verify 功能。

这 7 个函数的功能,分别是:

Init 必需函数 初始化,并准备设备以进行 Flash 编程。

UnInit 必需函数 完成某个 Flash 编程步骤后,取消微控制器的初始化。

EraseSector 必需函数 删除指定扇区的 Flash 内存内容。

ProgramPage 必需函数 将应用程序写入到 Flash 内存中。

BlankCheck 可选函数 检查并比较数据模式。

EraseChip 可选函数 删除整个 Flash 内存的内容。

Verify 可选函数 将 Flash 内存内容与程序代码进行比较。

模版文件中的函数示例如图 4 所示,可以看到,有 4 个必需函数,和 1 个 EraseChip 函数,留给JNH官网往其中增加一些操作代码:

图 4

(4)、FlashDev.c

在这个 C 文件里,如图 5 所示,里面定义了一个 FlashDevice 结构体,这个结构体里面的参数用途,是JNH官网必须要知道,这是非常重要的,涉及到后续很多的操作步骤,每一行的代码描述如下所示:

图 5

第 29 行:FLASH_DRV_VERS, //代表驱动版本,不要轻易修改

第 30 行:"New Device 256kB Flash", //设备名称,这里其实就是在勾选 Flash 烧写算法时,会被软件调用并显示的内容,如下图 6 所示:


图 6

第 31 行:ONCHIP, //设备类型,片上 Flash,也有外部 Flash 选项


第 32 行:0x00000000, //设备起始地址,stm32 在这里一般就是 0x08000000


第 33 行:0x00040000, //设备大小,以字节为单位,这里是 256kB,根据实际容量填写即可


第 34 行:1024, //编程页面大小,因为 Flash 存储是按照 页(Page) 进行编程的,页面太小会增加管理复杂度,浪费存储空间。页面太大则会导致小数据写入时增加不必要的操作时间。1024 字节大小可以在写速率和耗时之间达到良好的平衡。而且页面越大,写入过程中发生断电或干扰导致数据出错的概率越高;而 1024 字节大小已被证实在实际应用中具有较高的可靠性。

第 35 行: 0, //保留位,必须为 0


第 36 行:0xFF, //擦除后内存的初始内容,即所有位均为 1,主要是由 Flash 的工作原理和硬件设计决定的。


第 37 行: 100, //编程页面超时时间(单位:毫秒)防止程序陷入死循环,因为编程和擦除操作通常需要通过轮询硬件状态,如 Flash 的 BSY 标志位,来判断操作是否完成。(后续讲解代码时会有体现)


第 38 行: 3000, // 擦除扇区超时时间(单位:毫秒)防止程序陷入死循环,因为编程和擦除操作通常需要通过轮询硬件状态,如 Flash 的 BSY 标志位,来判断操作是否完成。(后续讲解代码时会有体现)


第 41 行:0x002000, 0x000000, //扇区大小 8kB(8 个扇区),第一个参数代表 8KB,第二个参数代表起始地址为 0x000000,因为第 42 行的代码规定了 64KB 的扇区是从 0x010000 地址开始的,所以留给 8KB 的扇区只有地址为 0x000000 从 0x00FFFF 之间的空间,即 64KB,所以就有 8 个 8KB 的扇区。


第 42 行:0x010000, 0x010000, //扇区大小 64kB (2 个扇区),第一个参数代表 64KB,第二个参数代表起始地址为 0x010000,因为第 43 行的代码规定了 8KB 的扇区是从 0x030000 地址开始的,所以留给 64KB 的扇区只有地址为 0x010000 从 0x02FFFF 之间的空间,即 128KB,所以就有 2 个 64KB 的扇区。


第 43 行:0x002000, 0x030000, //扇区大小 8kB (8 个扇区),第一个参数代表 8KB,第二个参数代表起始地址为 0x030000,因为第 33 行的代码规定了整个设备的大小为 0x040000,所以留给 8KB 的扇区只有地址为 0x030000 从 0x03FFFF 之间的空间,即 64KB,所以就有 8 个 8KB 的扇区。


第 44 行:SECTOR_END//标识符,用于标记扇区配置表的结束,通常出现在定义 Flash 扇区大小和地址的数组或结构体的最后一项,用于通知程序或驱动解析到此处时不再继续解析后续数据。


(5)、FlashOS.H

这个头文件中,如图 7 所示,其实只是一些宏定义和函数的声明之类,没有什么介绍的必要了,读懂前面两个 C 文件以后,这个头文件里的内容自然就会明白了。

图 7


(6)、了解完以上这些文件内的代码后,就可以着手自己编写一个 Flash 编译算法了,不过在编写修改 FlashPrg,FlashDev 之前,JNH官网可能需要将处理器架构选择并配置好,如下图,图 8 所示:(多目标+ifdef 的方式即可)


图 8

以及还要在配置中,设置位置独立代码,如下图图 9 所示:

图 9


(7)、之后就可以调整 FlashPrg 中的编程算法,FlashDev 中的设备参数了。调整完算法和参数之后,可以如图 10 所示一样,将输出文件的名字更改一下,方便在目录中查找最终的文件:

图 10

(8)、点击编译后,keil 就会在编译完成后,调用 user 栏中的 cmd.exe /C copy "Objects%L" ".@L.FLM"命令,在项目目录下,创建一个 MyDevice.FLM 文件,如图 11 所示:

图 11

(9)、接着把JNH官网做好的 FLM 文件,放到 DFP 包的 Flash 路径下,(这里直接就放在 stm32f1 系列下面了),如图 12 所示:

图 12

(10)、接着打开 DFP 包中的 PDSC 文件(这个文件的属性默认是只读的,记得去掉勾选),路径如下图 13 所示:

图 13

(11)、在 PDSC 文件中,JNH官网可以看到所有 F1 系列芯片的 Flash 烧写算法文件,JNH官网如果想要添加一个自己的芯片进去,就可以在如图 14 这里,添加并命名JNH官网的设备,附上对应的 FLM 文件,即可在 keil 软件重启后,看到JNH官网添加的芯片了,对应的,也可以找到JNH官网这个芯片的 Flash 编程算法了,如图 15 所示:


图 14

图 15


(12)、本文再往后面的内容,是官网提供的一个简单的编程算法示例(stm32f1 系列的 flash 编程算法,基本 90%和官网提供的相似,可以对照着一起看),我将对这个代码进行一些解读,方便大家快速学会一个简单的基础,并借此编写出具有更多功能的 Flash 编译算法。

Init函数

Init 函数用于初始化微控制器以进行 Flash 编程。当尝试将程序下载到 Flash 时,将调用此函数。



参数:

adr:设备基地址——clk:时钟频率 (以 Hz 为单位)——fnc:功能代码

含义:

第 4 行:FLASH->ACR = 0x00000000;

FLASH->ACR 是 Flash 访问控制寄存器(Access Control Register),用于配置 Flash 访问的等待周期。设置为 0x00000000 表示配置 零等待状态,即 CPU 访问 Flash 时无需额外的等待周期。

第 7 行:FLASH->KEYR = FLASH_KEY1;

第 8 行:FLASH->KEYR = FLASH_KEY2;

写入两个解锁密钥 FLASH_KEY1 和 FLASH_KEY2 到 FLASH->KEYR(Flash 密钥寄存器)。FLASH_KEY1 和 FLASH_KEY2 是特定的解锁值,用于解除 Flash 写保护;解锁后可以对 Flash 进行写入或擦除操作。如果没有正确解锁,后续的编程或擦除操作将无法执行。如图 16 所示,F1 系列的 FLM 文件中已给出密钥的宏定义,同样,在 STM32F10xxx 闪存编程参考手册中,也有记录。



图 16

第 11 行:if ((FLASH->OBR & 0x04) == 0x00)

FLASH->OBR 是 Flash 的选项字节寄存器(Option Byte Register),其中位 2 用于指示独立看门狗(IWDG)的运行模式:

如果位 2 为 0,表示 IWDG 处于硬件模式(HW mode),始终运行。

如果位 2 为 1,表示 IWDG 处于软件模式(SW mode),可以通过软件控制使能或禁用。

这里主要检查是否 IWDG 在硬件模式下运行,如下图 17 所示:


图 17

第 13 行: IWDG->KR = 0x5555; // Enable write access to IWDG_PR and IWDG_RLR

第 14 行: IWDG->PR = 0x06; // Set prescaler to 256

第 15 行: IWDG->RLR = 4095; // Set reload value to 4095

这里是在配置独立看门狗(IWDG)超时时间,如果 IWDG 在硬件模式下运行,配置其超时时间为 约 26.2144 秒:配置了较长的超时时间以适应 Flash 编程可能需要较长时间完成。防止 IWDG 在 Flash 编程时误触发系统复位;

IWDG->KR 写入 0x5555,是表示允许对 IWDG 的预分频寄存器(IWDG_PR)和重装载寄存器(IWDG_RLR)进行写操作。如图 18 所示:

图 18

IWDG->PR 写入 0x06 是为了配置预分频器值为 256(分频系数为 2^8),将时钟周期放大到较低的频率。如图 19 所示。

IWDG->RLR 写入 4095 配置重装载值为最大值 4095。如图 20 所示。

设置完成以后,就可以根据公式,计算出 IWDG 的超时时间为:256*4096/40000=26.2144s,具体计算可以参考图 21 所示。

图 19


图 20



图 21


UnInit函数



参数:

fnc:功能代码

含义:

第 4 行:FLASH->CR |= FLASH_LOCK;

反初始化,它的实现很简单,主要完成以下任务:FLASH->CR |= FLASH_LOCK,其实就是锁定 Flash 模块,因为 FLASH_LOCK 是 Flash 控制寄存器(FLASH->CR)中的一个位,它的作用是锁定 Flash 模块,以防止未经授权的写操作。在锁定状态下,Flash 不允许进行写操作(如擦除或编程),只能进行读取操作。如图 22 所示:


图 22

EraseSector函数

函数 EraseSector 删除从参数 adr 指定的地址开始的扇区的内容。每当使用 uVision 菜单 Flash - Erase 时,或者每当尝试将程序下载到 Flash 并且已在 Flash 下载设置对话框中设置了选项 Erase Sectors 时,都会调用该函数。



参数:

adr:扇区地址


含义:

第 3 行: FLASH->CR |= FLASH_PER;

启用页擦除功能,如图 25 所示,FLASH_PER 的值在图 23 中。

第 4 行:FLASH->AR = adr;

FLASH->AR 是一个地址寄存器,用于指定要擦除的 Flash 页面(或扇区)的起始地址。如图 24 所示。

第 5 行:FLASH->CR |= FLASH_STRT;

启动擦除操作,FLASH_STRT 是控制寄存器中的启动位。当该位被置位时,Flash 模块开始执行擦除操作。Flash 硬件会根据地址寄存器的值自动确定擦除的范围。如图 25 所示。


图 23



图 24



图 25

第 7 行: while (FLASH->SR & FLASH_BSY) {

等待擦除完成,FLASH_BSY 是 Flash 状态寄存器(FLASH->SR)中的一位,表示 Flash 当前正在执行擦除或编程操作。通过循环检查该位,程序可以等待擦除操作完成。如图 26 所示。


图 26

第 8 行: IWDG->KR = 0xAAAA;

在循环中,使用 IWDG->KR = 0xAAAA 喂狗,避免擦除时间过长触发看门狗复位(IWDG 是独立看门狗模块)。如图 27 所示。




图 27

第 11 行:FLASH->CR &= ~FLASH_PER;

关闭页擦除功能,如图 25 所示,擦除操作完成后,将 FLASH_PER 位清零,关闭页面擦除模式,防止后续误操作。FLASH_PER 的值在图 23 中。

ProgramPage函数

ProgramPage 函数用于将数据写入 Flash 存储器。它在下载程序到 Flash 时被调用。由于 Flash 存储器通常按块或页组织,因此函数 ProgramPage 的参数不能跨越这些 Flash 页的对齐边界。页大小由结构体 FlashDevice 的 ProgramPageSize 值指定。


参数:

adr 页的起始地址——sz 页的大小——buf 待写入的数据

含义:

第 3 行:sz = (sz + 1) & ~1;

Flash 的编程单元是 半字(16 位,2 字节),因此写入的大小 sz 必须是偶数。如果输入的大小是奇数,通过 (sz + 1) & ~1 向上对齐为偶数。

第 5 行: while (sz) {

循环写入数据,循环处理输入缓冲区的每个半字,直到所有数据写入完成。

第 7 行:FLASH->CR |= FLASH_PG;

启用编程功能 ,FLASH_PG 是控制寄存器中的一位,用于启用 Flash 编程模式。在写入数据之前,必须启用此功能。如图 28 所示。


图 28

第 9 行:M16(adr) = *((unsigned short *)buf);

M16(adr) 是一个宏,用于访问指定地址处的 16 位(半字)数据,如图 29 所示。等号右边将缓冲区中的数据(2 字节,前面提到过)强制转换为 unsigned short 类型,然后写入到目标地址。写入操作会自动触发 Flash 的编程机制。



图 29

第 10 行:while (FLASH->SR & FLASH_BSY);

等待编程完成(不再多说,前面有类似的提到过)

第 12 行:FLASH->CR &= ~FLASH_PG;

禁用编程功能(不再多说,前面有类似的提到过)

第 15 行:if (FLASH->SR & (FLASH_PGERR | FLASH_WRPRTERR)) {

第 16 行:FLASH->SR |= FLASH_PGERR | FLASH_WRPRTERR;

第 17 行:return (1);

如果状态寄存器中存在以下错误标志,则说明写入失败:如图 30 和 31 所示

FLASH_PGERR:编程错误,可能是写入地址不对齐。

FLASH_WRPRTERR:写保护错误,目标区域被保护,无法写入。

即:如果发生错误,清除错误标志并返回 1 表示失败。


图 30



图 31

第 21 行: adr += 2;

第 22 行: buf += 2;

第 23 行: sz -= 2;

这一段其实就是循环移动到下一个半字地址,更新目标地址 adr,缓冲区指针 buf,以及剩余大小 sz:

即:地址加 2 字节(一个半字)、缓冲区指针移动到下一个 2 字节数据、写入大小减少 2 字节。

三、讨论分析

问 ①:任何版本的 MDK 都可以制作 FLM 文件吗?

答 ①:MDK-Lite 不支持创建 Flash 编程算法。如图 32 所示:


图 32


四、结论

本文详细的介绍了 Flash 编程算法,主要讲解了如何制作和使用 FLM 文件。以及详细的算法代码解析,通过这篇文档,芯片开发者可以更好地理解如何进行 Flash 编程操作。对应本文开头,可以方便想要制作 pack 包的开发人员,做一些独特下载的开发人员,和尽快上手调试未上市的高新芯片,都有极大的帮助。

jnh官网 jnh官网 jnh官网 jnh官网 金年会 金年会 金年会 金年会