本应用笔记介绍如何管理带有页面擦除(PE)闪存的MAXQ7665闪存微控制器(μC)中的内部数据和程序闪存。此讨论包括有关执行程序闪存的应用程序内编程 (IAP) 的一般信息。
内存映射
本节详细介绍MAXQ7665 μC系列各种存储器尺寸的一般闪存信息和存储器结构。MAX7665器件提供三种类型的闪存:扇区擦除、单字擦除和两页擦除。本文档仅介绍带PE闪存和两页擦除数据闪存的MAXQ7665器件;该讨论不适用于仅扇区擦除设备或具有单字可擦除数据闪存的设备。
表1至表3分别显示了从程序闪存、实用程序ROM和数据SRAM执行代码时16KB器件的存储器映射。图 1 和图 2 显示了 16KB 和 256B 程序闪存的扇区和页面结构。其他闪光灯选项可用;请参考MAXQ7665数据资料获取完整列表。
表 1.16KB 内存空间 — 从程序闪存执行
表 2.16KB 内存空间 — 从实用程序 ROM 执行
表 3.16KB 内存空间 — 从数据 SRAM 执行
图1.16KB程序闪存的扇区/页面结构。
图2.256B数据闪存的扇区/页面结构。
使用数据闪存存储数据
闪存可用于可靠地存储系统数据,这些数据需要在系统运行期间进行一次或定期编程。与EEPROM不同,MAXQ7665上的PE闪存不能被字节/字擦除。必须一次擦除两页。这通常需要 10 毫秒,但在最坏的情况下可能需要更长的时间。在此期间,用户代码将停止,因此不会进行其他处理。
有多种存储数据到闪存的技术,在为应用程序选择适当的方法时,必须考虑几个因素。应用程序是否需要确保在新数据完全写入之前当前数据保持不变?如果答案是肯定的,那么某种形式的组切换技术是必要的,这样在写入新数据时不会丢失当前数据。在产品的生命周期内将发生多少个擦除/写入周期?如果擦除/写入周期数将超过数据手册中规定的最大值,则使用绑定队列方法可以将这些周期分散到数据闪存的多页上,从而显著增加总擦除/写入周期。对于大多数周期性数据存储需求,银行交换和/或有界队列技术满足系统的可靠性要求和需求。以下是每种技术的简单示例。
银行切换
存储体交换是在擦除/写入周期中防止数据丢失或损坏的有效方法。此方法通过始终维护至少一个数据副本来很好地工作。组交换的缺点是它需要的数据闪存量是有界队列方法的两倍。事实上,银行切换实际上只是队列大小为 2 的有界队列方法。因此,有关如何实现有界队列的详细信息,请参阅下一节。
有界队列
有界队列是受固定数量的项限制的队列,通常在处理定期数据时使用。有界队列是通过创建与所需数据块大小相等的“条目”的固定长度队列来形成的。条目大小特定于应用程序,必须向上舍入到最接近的页面擦除边界。注意:可以擦除的最小大小为两页/字。虽然可以根据应用程序要求以多种方式对数据闪存进行分区,但由于数据闪存施加的两页擦除限制,条目被限制为两页的倍数。例如,512 x 16 数据闪存可以分为 32 个 16 字条目,这将产生表 4 中的内存映射。
初始化后,启动例程可以扫描队列条目以确定队列中的下一个可用条目。一旦队列已满,就可以使其包装回开头。擦除数据闪存条目后,可以写入新条目。图 3 说明了进入有界队列的条目流。
闪光灯[ ] | |
队列索引 | 数据闪存地址 |
31 | 0xC1F0-0xC1FF |
30 | 0xC1E0-0xC1EF |
29 | 0xC1D0-0xC1DF |
. . . . | . . . . |
2 | 0xC020-0xC05F |
1 | 0xC010-0xC03F |
0 | 0xC000-0xC00F |
图3.有界队列流的图示。
实用程序 ROM 闪存例程
为了编程、擦除和验证闪存,MAXQ7665微控制器在ROM (只读存储器)中提供了片内闪存支持程序。有两种方法可以访问这些例程:直接访问和通过查找表间接访问。最快的方法是直接访问,即直接调用例程。为此,请提供包含以下行的头文件:
u16 flashErasePage(void *); u16 flashEraseSector(void *); u16 flashEraseAll(void); u16 dataFlashWrite(u16 *pAddress, u16 iData); u16 dataFlashErasePage(void *); u16 dataFlashEraseSector(void *); u16 dataFlashEraseAll(void);
接下来,添加链接器定义以为每个例程分配适当的地址。对于 IAR 链接器文件,添加的行如下所示:
-DflashEraseSector=0x8XXX -DflashErasePage=0x8XXX -DflashEraseAll=0x8XXX
将 0x8XXX 替换为每个例程的相应内存地址。其他编译器可能会使用不同的方法来添加这些引用。
flashWrite() 实用程序例程不能直接从 C 调用,因为参数传递与 C 语言不兼容。必须编写如下所示的小型程序集例程才能调用此函数。
注意:直接访问方法不提供与未来 ROM 版本的向前兼容性。
第二种方法是通过表查找进行间接访问。此方法提供了与未来ROM版本的更大兼容性,但消耗更多的执行时间。在下面描述的每个例程之后,程序集例程使用表查找方法来获取 ROM 实用程序例程的地址。表 5 显示了实用程序 ROM 提供的闪存例程。有关实用程序ROM例程的完整列表,请参考MAXQ7665用户指南。
例程编号 | 例程名称 | 入口点可 ROMTable = ROM[800Dh] | 入口点物理地址 |
1 | 闪写 | 罗姆[可浪漫] | 0x8XXX |
2 | 闪光擦除页面 | 罗姆[可浪漫 + 1] | 0x8XXX |
3 | 闪光擦除全部 | 罗姆[可浪漫 + 2] | 0x8XXX |
4 | 移动DP0 | 罗姆[可浪漫 + 3] | 0x8XXX |
16 | 闪存擦除扇区 | 罗姆[可浪漫 + 15] | 0x8XXX |
17 | 数据闪写 | 罗姆[可浪漫 + 16] | 0x8XXX |
19 | 数据闪光擦除页面 | 罗姆[可浪漫 + 18] | 0x8XXX |
20 | 数据闪存擦除扇区 | 罗姆[可浪漫 + 19] | 0x8XXX |
21 | 数据闪存擦除全部 | 罗姆[可浪漫 + 20] | 0x8XXX |
常规: | u16 flashWrite(u16 *pDest, u16 *pSrc) |
总结: | 对单页(32 字)的程序闪存进行编程。 |
输入: |
DP[0] - 闪存中的目标地址。 DP[1]—SRAM 中的源地址,包含 32 个要写入的数据字。 |
输出: |
携带:错误时设置,成功时清除。如果设置,则 A[0] 包含以下错误代码之一: 1:由于软件超时而导致的故障 2:硬件 (DQ5/FERR) 报告故障 4: 不支持 命令 SW_FERR - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
下面的汇编代码示例使用间接寻址方法(查找表)调用 flashWrite() 实用例程。此例程可由 C 代码调用。
; This routine is callable by C code using the following prototype ; u16 flashWrite(u16 *pDest, u16 *pSrc); ; flashWrite: move APC, #0 ; No auto inc/dec of accumulator. move AP, #2 ; Set ACC to A[2]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #14 ; Add the index to the flashWrite routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. push DP[1] ; Save Frame Pointer on the stack. move DP[0],A[0] ; Move argument 0(dest address) to DP[0]. move DP[1],A[1] ; Move argument 1(src address) to DP[1]. call ACC ; Execute the routine. pop DP[1] ; Restore Frame Pointer. ret ; Status returned in A[0].
常规: | u16 闪存擦除页面(无效 *pAddress) |
总结: | 擦除两页的程序闪存块。 |
输入: | A[0] - 位于要擦除的两页块中的地址,即要擦除第 0 页和第 1 页,A[0] 可以包含从 0x0000 到 0x001F 的任何地址。 |
输出: |
携带:错误时设置,成功时清除。如果设置,则 A[0] 包含以下错误代码之一: 1:由于软件超时而导致的故障 2:硬件 (DQ5/FERR) 报告故障 4: 不支持 命令 SW_FERR - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
; This routine is callable by C code using the following prototype ; u16 flashErasePage(void *pAddress); ; flashErasePage: move APC, #0 ; No auto inc/dec of accumulator. move AP, #1 ; Set ACC to A[1]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #1 ; Add the index to the flashEraseSector routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret ; Status returned in A[0].
常规: | 虚空闪光擦除全部(虚空) |
总结: | 擦除整个程序和数据闪存。此例程只能从 RAM 调用。 |
输入: | 没有 |
输出: |
携带:错误时设置,成功时清除。 SW_FERR - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
; This routine is callable by C code using the following prototype ; void flashEraseAll(void); ; flashEraseAll: move APC, #0 ; No auto inc/dec of accumulator. move AP, #0 ; Set ACC to A[0]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #2 ; Add the index to the flashEraseAll routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret
常规: | 移动DP0 |
总结: | 读取闪存的一个字。 |
输入: | DP[0] - 闪存中的源地址。添加0x8000以读取程序闪存。 |
输出: | GR将包含指定地址的数据。 |
笔记: | 不能直接从 C 调用此函数,因为参数和返回寄存器与 C 调用约定不兼容。 |
下面的汇编代码示例将 moveDP0 转换为 C 可调用例程。如果速度对应用程序至关重要,则应为特定任务编写自定义汇编语言例程。有几个像这样的实用程序ROM例程将有助于从闪存写入有效的数据传输。
; This routine is callable by C code using the following prototype ; u16 flashRead(u16 *pAddress); ; flashRead: move APC, #0 ; No auto inc/dec of accumulator. move AP, #1 ; Set ACC to A[1]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #3 ; Add the index to the moveDP0 routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. push DP[1] ; Save Frame Pointer on the stack. move DP[0],A[0] ; Move argument 0(src address) to DP[0]. call ACC ; Execute the routine. pop DP[1] ; Restore Frame Pointer. move A[0],GR ret ; Data word returned in A[0].
常规: | u16 闪存擦除扇区(无效 *pAddress) |
总结: | 擦除程序闪存的单个扇区。 |
输入: | A[0] - 位于要擦除的扇区中的地址。 |
输出: |
携带:错误时设置,成功时清除。如果设置,则 A[0] 包含以下错误代码之一: 1:由于软件超时而导致的故障 2:硬件 (DQ5/FERR) 报告故障 4: 不支持 命令 SW_FERR - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
; This routine is callable by C code using the following prototype ; u16 flashEraseSector(void *pAddress); ; flashEraseSector: move APC, #0 ; No auto inc/dec of accumulator. move AP, #1 ; Set ACC to A[1]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #15 ; Add the index to the flashEraseSector routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret ; Status returned in A[0].
常规: | u16 dataFlashWrite(void *pAddress, u16 *pData) |
总结: | 对单个字的数据闪存进行编程。 |
输入: |
A[0] - 闪存中要写入的字地址。 A[1] — 要写入闪存的字值。 |
输出: |
携带:错误时设置,成功时清除。如果设置,则 A[0] 包含以下错误代码之一: 1:由于软件超时而导致的故障 2:硬件 (DQ5/FERR) 报告的故障 4: 不支持 命令 SW_FER - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
下面的汇编代码示例使用间接寻址方法(查找表)调用 dataFlashWrite() 实用工具例程。此例程可由 C 代码调用。
; This routine is callable by C code using the following prototype ; u16 dataFlashWrite(void *pAddress, u16 iData); ; dataFlashWrite: move APC, #0 ; No auto inc/dec of accumulator. move AP, #2 ; Set ACC to A[2]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #16 ; Add the index to the flashWrite routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret ; Status returned in A[0].
常规: | u16 dataFlashErasePage(void *pAddress) |
总结: | 擦除两页数据闪存。 |
输入: | A[0] - 位于要擦除的两页块中的地址,即要擦除第 0 页和第 1 页,A[0] 可以包含地址0x4000或0x4001。 |
输出: |
携带:错误时设置,成功时清除。如果设置,则 A[0] 包含以下错误代码之一: 1:由于软件超时而导致的故障 2:硬件 (DQ5/FERR) 报告故障 4: 不支持 命令 SW_FERR - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
; This routine is callable by C code using the following prototype ; u16 dataFlashErasePage(void *pAddress); ; dataFlashErasePage: move APC, #0 ; No auto inc/dec of accumulator. move AP, #1 ; Set ACC to A[1]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #18 ; Add the index to the dataFlashErasePage routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret ; Status returned in A[0].
常规: | u16 dataFlashEraseSector(void *pAddress) |
总结: | 擦除数据闪存的单个扇区。 |
输入: | A[0] - 位于要擦除的扇区中的地址。 |
输出: |
携带:错误时设置,成功时清除。如果设置,则 A[0] 包含以下错误代码之一: 1:由于软件超时而导致的故障 2:硬件 (DQ5/FERR) 报告故障 4: 不支持 命令 SW_FERR - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
; This routine is callable by C code using the following prototype ; u16 dataFlashEraseSector(void *pAddress); ; dataFlashEraseSector: move APC, #0 ; No auto inc/dec of accumulator. move AP, #1 ; Set ACC to A[1]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #19 ; Add the index to the dataFlashEraseSector routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret ; Status returned in A[0].
常规: | void dataFlashEraseAll(void) |
总结: | 擦除整个数据闪存。 |
输入: | 没有 |
输出: |
携带:错误时设置,成功时清除。 SW_FERR - 出错时设置,成功时清除。 |
笔记: | 监视器不得处于活动状态,或者监视器超时必须设置足够长的时间才能在不触发重置的情况下完成此例程。 |
; This routine is callable by C code using the following prototype ; void dataFlashEraseAll(void); ; dataFlashEraseAll: move APC, #0 ; No auto inc/dec of accumulator. move AP, #0 ; Set ACC to A[0]. move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #20 ; Add the index to the flashEraseAll routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret
应用程序内编程 (IAP)
大多数基于闪存的系统的一个重要要求是能够在系统安装在最终产品中时更新固件。此过程称为应用程序内编程 (IAP)。本节将概述创建 IAP 应用程序的一般准则。
上一节中概述的实用程序 ROM 闪存例程执行擦除和写入闪存 ROM 所需的所有操作。因此,最终用户应用程序可以在闪存上执行操作。与任何其他子例程调用一样,控件将在例程完成后返回到最终用户的代码。
对于可靠的 IAP,引导加载程序应用程序必须与主应用程序分开。这确保了即使在发生不完整的重编程序列后,也可以重试重编程过程。
引导加载程序
ROM 在初始化后跳转到寻址0x0000。因此,引导加载程序应用程序的入口点必须放在0x0000处。引导加载程序应用程序可以根据需要扩展到任意数量的闪存扇区/页面,但使用的任何页面都不适用于用户的应用程序代码。表6列出了擦除和写入闪存时必须满足的具体要求。
不能从执行代码的同一闪存页中擦除或编程。这通常不是问题,因为在 IAP 期间绝不应擦除闪存引导加载程序应用程序。 |
在调用 flashEraseSector() 或 flashErasePage() 例程之前,必须将监视器超时设置得足够长,以便完成此例程,而不会触发重置。如果在擦除完成之前出现看门狗超时,它将重置器件。 |
因为系统控制寄存器位,SC。UPA,必须设置为 0 才能访问实用程序 ROM,实用程序 ROM 例程不能直接从程序内存地址调用≥ 0x8000。如果需要从上层内存 (≥ 0x8000) 中的程序访问实用程序 ROM 例程,则程序必须通过驻留在下层内存中的例程间接调用 ROM 例程 (< 0x8000)。此限制有效地将引导加载程序限制为 = 64KB (32KB x 16)。 |
图4中的流程图显示了MAXQ7665退出复位状态时的作用。在诊断ROM本身并验证闪存已准备就绪后,ROM初始化代码将直接跳转到地址0x0000。
图4.简化ROM初始化的流程图。
图 5 流程图显示了一个简单的引导加载程序应用程序如何运行。一个简单的应用程序标头可能如下所示:
typedef struct { u16 iSize; // The size of the application in words u32 iCRC; // The CRC of the application u8 ID[8]; // ID string for current application } APPLICATION_HEADER;
使用此标头中的信息,引导加载程序可以检查主应用程序的有效性,并在请求时报告版本标识。
图5.简化的闪存引导加载程序的流程图。
编程顺序本身非常简单。通过调用 flashEraseSector() 和/或 flashErasePage() 擦除包含主应用程序代码的每个扇区/页面。然后通过为每 32 个需要编程的单词调用 flashWrite() 一次写一页。我们建议您先擦除包含应用程序标题的页面,最后对CRC数据进行编程,以最大程度地减少CRC匹配错误的可能性。重新刷新通过串行端口获取数据的微控制器的一个非常简单的例程如下所示:
/* // VerySimpleReFlash() // As simple as it gets. // Step 1. Wait for erase command, then erase flash. // Step 2. Wait for program command, program flash one word at a time. */ void VerySimpleReFlash() { u16 iStatus; // The status returned from flash utility ROM calls s32 iSize; // The size of the main code to program u16 *pAddress = 0x2000; // The starting address of the main application u16 i; InitializeCOMM(); // Can be CAN or UART. WaitForEraseCommand(); // Assume that application starts at the beginning of a sector. for (i=C_START_SECTOR;i0) { u16 iData[32]; Get32WordsFromCOMM(iData); iStatus = flashWrite(pAddress, iData); if (iStatus) break; pAddress += 32; iSize -= 32; UpdateWatchdog(); // Prevent timeout } SendFlashWriteResponse(iStatus); ResetMicro(); }
审核编辑:郭婷
全部0条评论
快来发表一下你的评论吧 !