1. 项目概述与核心价值
在嵌入式开发,尤其是汽车电子和工业控制这类对可靠性要求极高的领域,微控制器(MCU)的Flash存储器远不止是一个简单的代码和数据仓库。它更像是一个需要严密守护的“保险库”,既要保证固件在复杂电磁环境或异常掉电情况下的绝对安全,又要支持在线升级、故障诊断等高级功能。飞思卡尔(现为NXP)的PXD10系列微控制器,其Flash模块的设计就深刻体现了这种高可靠性的设计哲学。
很多开发者初次接触这类MCU的参考手册时,面对动辄几十页的Flash控制器章节,尤其是其中密密麻麻的寄存器描述,往往会感到无从下手。手册提供了所有位域的定义,但寄存器之间如何协同工作、操作时序的“潜规则”、以及那些手册里不会明说的“坑”,才是真正决定开发成败的关键。比如,你以为简单地写一个值到锁定寄存器就能保护代码区,结果发现操作无效;或者在进行Flash自检时,读回来的签名总是不对,却找不到原因。
本文将聚焦于PXD10 Flash模块中一组至关重要的寄存器:锁存寄存器(Locking Registers)、选择寄存器(Select Registers)和用户测试寄存器(User Test Registers)。我不会仅仅复述手册中的位定义,而是会结合我多年在汽车ECU开发中的实际经验,深入剖析这些寄存器背后的设计逻辑、它们之间的联动关系,以及一套经过验证的、可落地的配置与操作流程。无论你是在开发Bootloader、实现安全启动、还是设计在线诊断功能,理解这些内容都将帮助你绕过许多暗礁,写出更健壮、更可靠的底层驱动。
2. Flash保护机制深度解析:锁存与选择寄存器的协同
PXD10的Flash保护机制是一个多层次、精细化的系统。简单来说,它通过两把“锁”和一个“开关”来管理对Flash存储器的修改权限。理解这三者的关系,是掌握其Flash操作的基础。
2.1 核心保护逻辑:LML, SLL与操作使能
首先,我们需要建立核心概念。Flash的每个物理块(Block)是否被锁定,由两个锁存寄存器的“或(OR)”运算结果决定:
- 主锁存寄存器(LML, Low/Mid address space block Locking register):这是主要的、易失性的锁。通过软件在运行时配置,复位后丢失(除非从非易失性区域加载)。
- 次级锁存寄存器(SLL, Secondary Low/Mid address space block Locking register):这是一个备用的、同样易失性的锁。它还有一个对应的非易失性镜像NVSLL,其值在芯片复位时会被自动加载到SLL中,提供了上电即有的默认保护状态。
最终锁定状态 = LML.LLK[x] OR SLL.SLK[x](对于低地址空间块为例) 这意味着,只要LML或SLL中任意一个寄存器将某个块标记为锁定(值为1),该块就被保护起来,无法被编程或擦除。这种“或”逻辑提供了冗余的保护路径。
然而,仅有锁存状态还不够。要对这些锁存寄存器(LML或SLL)进行写入(即上锁或解锁),还必须先打开对应的“写使能开关”:
- LML.LLE:必须为1,才能写入LML中的锁定位(LLK)。
- SLL.SLE:必须为1,才能写入SLL中的锁定位(SLK, SMK, STSLK)。
开启这些使能位的方法,正是手册中提到的密码验证机制。这是一种硬件级别的安全互锁(Interlock),防止软件意外或恶意修改锁定状态。
实操心得:密码写入的“一次性”与时机手册上只说了写入特定密码(如对SLE写入
0xC3C33333)可以使能SLE位。但实际操作中有一个关键细节:这个密码写入操作本身,也必须在没有其他Flash高电压操作(编程/擦除)挂起、且MCR.DONE标志为1(表示前一个操作已完成)时进行。通常,我们在系统初始化阶段、进行任何Flash修改操作之前,就先完成所有必要的锁存寄存器使能和配置。一旦使能位被置起,它将保持有效直到下一次系统复位。不要尝试在Flash操作间隙反复使能,这可能导致不可预知的行为。
2.2 地址空间划分与寄存器映射
PXD10将Flash地址空间划分为几个区域,不同的锁存和选择寄存器管理不同的区域:
- 低地址空间(Low Address Space):通常映射用户应用程序代码。由LML.LLK[15:0]和SLL.SLK[15:0]管理锁定,由LMS.LSL[15:0]管理擦除选择。
- 中地址空间(Mid Address Space):在某些内存配置中存在。由LML.MLK[1:0]和SLL.SMK[1:0]管理锁定,由LMS.MSL[1:0]管理擦除选择。
- 高地址空间(High Address Space):由单独的HBL.HLK[5:0]管理锁定,由HBS.HSL[5:0]管理擦除选择。注意,HBL的使能位是HBE,密码是
0xB2B22222。 - 测试/影子空间(Test/Shadow Space):用于存储出厂配置、校准数据或Bootloader。由LML.TSLK和SLL.STSLK共同管理锁定。
一个重要提示:在您提供的80 KB Flash配置中,手册多次提到“All the HLK5-0 are not used for this memory cut that is all mapped in low address space”。这意味着对于这个具体的芯片型号,所有Flash物理上都映射在低地址空间。因此,高地址空间和中地址空间的锁定/选择寄存器(HLK, HSL, SMK, MSL)可能是只读且固定值的。在编程时,我们必须主要关注低地址空间相关的寄存器(LLK, SLK, LSL)以及测试空间锁(TSLK, STSLK)。在编写通用驱动时,最好通过读取芯片的配置寄存器(如SIM_FCFG1)来动态判断可用的地址空间,而不是写死。
2.3 非易失性锁存(NVSLL)的妙用与风险
NVSLL是一个位于测试Flash区块中的非易失性寄存器。它的值在每次芯片复位时,都会自动加载到易失性的SLL寄存器中。这带来了一个强大的功能:定义上电默认保护状态。
典型应用场景: 假设你的产品中,Bootloader和出厂校准数据存放在固定的Flash块中,这些内容在产品的整个生命周期内都不应被修改。你可以在生产烧录阶段,通过特殊工具(在SLE使能的情况下)配置好SLL寄存器中对应块的锁定位(SLK[x] = 1),然后将这个SLL的值编程到NVSLL所在的测试Flash区域。这样,每次芯片上电,这些关键区块就会自动被SLL锁定。而用户应用程序在运行时,只能操作LML寄存器来动态锁定/解锁其他区域(如用于存储动态数据的EEPROM模拟区)。
重大风险与注意事项:
警告:对NVSLL的编程属于对测试Flash区块的操作,通常需要特定的工厂编程模式或更高的访问权限。错误的操作可能导致测试区块损坏,进而影响NVSLL的加载,使芯片失去上电默认保护,甚至引发更严重的故障。
- 非标准操作:用户模式下的标准Flash驱动库可能不包含对NVSLL编程的接口。这通常是在芯片出厂前,由产线编程器完成的。
- 不可逆性:测试Flash区块的擦写次数可能远低于主Flash阵列。反复对NVSLL编程会消耗其寿命。
- 依赖复位:NVSLL的值只在复位阶段加载。如果你在运行时修改了SLL,然后发生了局部复位(如看门狗复位)而非上电复位,SLL可能会被重新加载为NVSLL的值,导致运行时配置的锁定状态丢失。在设计安全关键系统时,必须考虑这种场景。
3. 锁存与选择寄存器的实战配置流程
理解了原理,我们来看如何在实际代码中操作这些寄存器。以下是一个典型的配置流程,旨在保护Bootloader区域(假设位于低地址空间的Block 0和1),并允许应用程序擦写其他区域。
3.1 初始化:使能锁存寄存器写入权限
在系统启动早期(例如在启动代码或Flash驱动初始化函数中),我们需要使能LML和SLL的写入权限。
/** * @brief 使能Flash锁存寄存器的写权限 * @note 必须在任何尝试修改LML或SLL之前调用。假设寄存器基地址为FLASH_BASE。 */ void Flash_EnableLockWrite(void) { volatile uint32_t *flash_base = (volatile uint32_t *)FLASH_BASE; // 1. 确保Flash模块处于就绪状态(MCR.DONE = 1) // 通常上电后默认就是1,但良好实践是先检查。 while(!(flash_base[MCR_OFFSET] & MCR_DONE_MASK)) { // 等待就绪,可加入超时机制 } // 2. 使能LML寄存器写入 (解锁主锁) // 向LML寄存器地址写入特定密码 flash_base[LML_OFFSET] = 0xA5A55555; // LLE的密码,假设值,需查确切手册 // 检查LLE是否被置位 if(!(flash_base[LML_OFFSET] & LML_LLE_MASK)) { // 使能失败,处理错误 Error_Handler(); } // 3. 使能SLL寄存器写入 (解锁次级锁) // 向SLL寄存器地址写入特定密码 flash_base[SLL_OFFSET] = 0xC3C33333; // SLE的密码 // 检查SLE是否被置位 if(!(flash_base[SLL_OFFSET] & SLL_SLE_MASK)) { // 使能失败,处理错误 Error_Handler(); } // 4. (可选)如果需操作高地址空间,使能HBL // flash_base[HBL_OFFSET] = 0xB2B22222; // HBE的密码 }3.2 配置锁定状态:保护关键区块
假设我们要永久锁定Block 0和1(通过NVSLL实现上电默认锁),并在运行时动态管理Block 2(用于参数存储)。
场景一:生产端配置NVSLL(模拟流程,实际通常在编程器脚本中完成)此操作风险极高,仅示意流程。
// !!!此代码仅为原理示意,切勿在用户模式下直接使用 !!! void Production_ConfigureNVSLL(void) { // 0. 进入特殊的工厂测试模式(方法依芯片和工具链而定) EnterFactoryMode(); // 1. 使能测试Flash块的编程/擦除能力(可能需要操作LML.TSLK等) // 2. 解锁并擦除包含NVSLL的测试Flash扇区 // 3. 构建要写入NVSLL的值:我们希望Block0和1永远锁定,即SLK0=1, SLK1=1 uint32_t nvsll_value = 0x00000000; nvsll_value |= (1 << 0); // SLK0 = 1 nvsll_value |= (1 << 1); // SLK1 = 1 // SLE位在NVSLL中也是可编程的默认值,但通常我们不想默认开启写权限,所以设为0。 // 其他位根据需求设置。 // 4. 编程NVSLL寄存器所在的物理地址(如0x403DF8) ProgramFlashWord(0x403DF8, nvsll_value); // 5. 重新锁定测试Flash块,退出工厂模式 ExitFactoryMode(); }场景二:应用程序运行时动态管理锁定(更常用)
/** * @brief 配置运行时Flash块锁定状态 * @param block_mask 位掩码,bit0对应Block0,bit1对应Block1,以此类推。1表示锁定,0表示解锁。 */ void Flash_ConfigureRuntimeLock(uint32_t block_mask) { volatile uint32_t *flash_base = (volatile uint32_t *)FLASH_BASE; uint32_t current_lml; // 1. 确保LLE已使能(见上一节初始化) if(!(flash_base[LML_OFFSET] & LML_LLE_MASK)) { Flash_EnableLockWrite(); // 或返回错误 } // 2. 读取当前的LML值 current_lml = flash_base[LML_OFFSET]; // 3. 清除低16位锁定位(LLK[15:0]),然后应用我们的掩码。 // 注意:我们只操作低地址空间锁,并保留其他位(如TSLK, MLK等)。 current_lml &= ~(0x0000FFFF); // 清除LLK[15:0] current_lml |= (block_mask & 0x0000FFFF); // 设置新的锁定位 // 4. 写入新的LML值 flash_base[LML_OFFSET] = current_lml; // 5. 验证写入(可选但推荐) if((flash_base[LML_OFFSET] & 0x0000FFFF) != (block_mask & 0x0000FFFF)) { // 锁定状态配置失败 Error_Handler(); } } // 应用示例:在应用程序初始化时,解锁用于参数存储的Block 2 int main(void) { // ... 其他初始化 Flash_EnableLockWrite(); // 解锁Block2 (bit2 = 0),其他块保持原状或锁定。假设我们只想让Block2可写。 // 即掩码中,除了bit2为0,其他需要锁定的位为1。 // 例如,我们希望锁定Block0,1,3,4,5...,只解锁Block2: uint32_t lock_mask = 0x0000FFFF & (~(1 << 2)); // 所有位为1,然后清除bit2 Flash_ConfigureRuntimeLock(lock_mask); // ... 后续操作 }3.3 擦除操作:选择寄存器的正确使用
擦除操作不是直接向地址写数据,而是一个需要预先选择目标块的序列化过程。LMS(低/中地址空间选择寄存器)和HBS(高地址空间选择寄存器)就是用于此目的。
擦除单个块(例如Block 2)的标准流程:
Flash_StatusTypeDef Flash_EraseBlock(uint8_t block_num) { volatile uint32_t *flash_base = (volatile uint32_t *)FLASH_BASE; uint32_t temp; // 1. 检查目标块是否被锁定(LML OR SLL) uint32_t lock_status = (flash_base[LML_OFFSET] & (1 << block_num)) | (flash_base[SLL_OFFSET] & (1 << block_num)); if(lock_status) { return FLASH_ERROR_LOCKED; } // 2. 确保Flash就绪且无挂起操作 if(!(flash_base[MCR_OFFSET] & MCR_DONE_MASK)) { return FLASH_ERROR_BUSY; } // 3. 配置块选择寄存器(LMS) // 先读取当前值,然后只设置目标块的选择位,清除其他位(确保只擦除一个块)。 temp = flash_base[LMS_OFFSET]; temp &= ~(0x0000FFFF); // 清除所有低地址空间选择位 temp |= (1 << block_num); // 设置目标块选择位 flash_base[LMS_OFFSET] = temp; // 4. 设置擦除操作选择位(MCR.ERS) flash_base[MCR_OFFSET] |= MCR_ERS_MASK; // 5. 启动擦除操作(设置MCR.EHV) flash_base[MCR_OFFSET] |= MCR_EHV_MASK; // 6. 等待操作完成(MCR.DONE == 1) while(!(flash_base[MCR_OFFSET] & MCR_DONE_MASK)) { // 此处应加入超时检测,防止死等 } // 7. 清除选择位和操作位(可选,但为下次操作做好准备) flash_base[MCR_OFFSET] &= ~(MCR_EHV_MASK | MCR_ERS_MASK); flash_base[LMS_OFFSET] &= ~(1 << block_num); // 8. 检查错误标志(如MCR.PEG, MCR.RWE等) if(flash_base[MCR_OFFSET] & (MCR_PEG_MASK | MCR_RWE_MASK | MCR_EER_MASK)) { // 处理错误,读取ADR寄存器获取失败地址 uint32_t fail_address = flash_base[ADR_OFFSET]; return FLASH_ERROR_OPERATION; } return FLASH_OK; }关键细节:选择寄存器的“一次性”手册明确指出:“The blocks must be selected (or unselected) before doing an erase interlock write as part of the Erase sequence. The select register is not writable once an interlock write is completed or if a high voltage operation is suspended.” 这意味着:
- 顺序至关重要:必须在设置
MCR.EHV(启动高电压操作)之前配置好LMS/HBS。一旦MCR.EHV置位,选择寄存器就被锁死,直到操作完成(MCR.DONE=1)或操作被取消。- 擦除多个块:如果需要一次性擦除多个块,可以在步骤3中一次性设置多个选择位(例如,
temp |= (1<<2) | (1<<5) | (1<<7);)。但务必谨慎,确保这些块都是确定需要擦除且未被锁定的。
4. 高级诊断功能:用户测试寄存器实战指南
用户测试��存器(UT0-UT2)和多重输入签名寄存器(UMISR0-4)是PXD10 Flash模块提供给开发者的强大诊断工具。它们主要用于生产测试、固件完整性验证和ECC功能校验,在研发和故障分析阶段极其有用。
4.1 阵列完整性检查与Margin Read
阵列完整性检查(Array Integrity Check)是一种非破坏性的自检,用于验证Flash存储单元的可靠性。Margin Read则是更严格的读取,使用比正常读电压更高或更低的阈值,来检测处于“临界”状态的存储单元,提前发现潜在故障。
操作流程如下:
/** * @brief 执行Flash阵列完整性检查 * @param use_sequential 0=使用专有序列(更全面),1=使用顺序序列(更快) * @param start_block 起始块号 * @param end_block 结束块号 * @return 成功返回0,失败返回错误码。成功后可通过ReadMISR()获取签名。 */ int Flash_ArrayIntegrityCheck(uint8_t use_sequential, uint8_t start_block, uint8_t end_block) { volatile uint32_t *flash_base = (volatile uint32_t *)FLASH_BASE; // 0. 准备工作:确保目标块未被锁定且被选择(用于检查) // 这里的选择逻辑与擦除不同,是通过UT0.AIE启动的检查会自动扫描所有未锁定的块? // 手册描述是“on all selected and unlocked blocks”。但“selected”机制在此处未明确。 // 一种常见实现是,阵列检查会检查所有地址空间。安全做法是确保待检查区域未锁定。 for(int i=start_block; i<=end_block; i++) { if((flash_base[LML_OFFSET] & (1<<i)) || (flash_base[SLL_OFFSET] & (1<<i))) { return -1; // 区块被锁定,无法检查 } } // 1. 确保Flash就绪(MCR.DONE=1)且无其他高电压操作 if(!(flash_base[MCR_OFFSET] & MCR_DONE_MASK)) { return -2; } if(flash_base[MCR_OFFSET] & (MCR_ERS_MASK | MCR_PGM_MASK | MCR_EHV_MASK)) { return -3; } // 2. 使能用户测试功能(UTE) flash_base[UT0_OFFSET] = 0xF9F99999; // 写入UTE密码 if(!(flash_base[UT0_OFFSET] & UT0_UTE_MASK)) { return -4; // UTE使能失败 } // 3. 配置检查模式 uint32_t ut0_val = flash_base[UT0_OFFSET]; ut0_val &= ~(UT0_AIS_MASK); // 清除AIS位 if(use_sequential) { ut0_val |= UT0_AIS_MASK; // 设置为顺序序列 } // 如果要做Margin Read,在此处设置UT0.MRE和UT0.MRV // ut0_val |= UT0_MRE_MASK; // 使能Margin Read // ut0_val &= ~(UT0_MRV_MASK); // 设置为检查编程电平(0),或置1检查擦除电平 flash_base[UT0_OFFSET] = ut0_val; // 4. (可选)为MISR设置种子值。如果不设置,默认为0。 flash_base[UMISR0_OFFSET] = 0x12345678; // 示例种子 flash_base[UMISR1_OFFSET] = 0x9ABCDEF0; // ... 设置UMISR2-4 // 5. 启动阵列完整性检查(设置UT0.AIE) flash_base[UT0_OFFSET] |= UT0_AIE_MASK; // 6. 等待检查完成(轮询UT0.AID) while(!(flash_base[UT0_OFFSET] & UT0_AID_MASK)) { // 加入超时机制!这个检查可能耗时较长。 } // 7. 检查完成后,UT0.AID会自动置1。此时可以读取MISR值进行分析。 // 8. 清除AIE位,结束操作 flash_base[UT0_OFFSET] &= ~UT0_AIE_MASK; // 9. (可选)关闭用户测试功能 // flash_base[UT0_OFFSET] &= ~UT0_UTE_MASK; // UTE位只能写0清除 return 0; // 成功 } /** * @brief 读取MISR签名 */ void Flash_ReadMISR(uint32_t misr[5]) { volatile uint32_t *flash_base = (volatile uint32_t *)FLASH_BASE; misr[0] = flash_base[UMISR0_OFFSET]; misr[1] = flash_base[UMISR1_OFFSET]; misr[2] = flash_base[UMISR2_OFFSET]; misr[3] = flash_base[UMISR3_OFFSET]; misr[4] = flash_base[UMISR4_OFFSET]; }如何解读MISR结果?MISR是一个基于线性反馈移位寄存器(LFSR)的签名。在相同的输入序列(即Flash内容)和相同的种子下,每次计算都会产生一个唯一的、确定性的签名。
- 预期一致:如果Flash内容完好无损,且检查参数(序列模式、起始结束地址)完全一致,每次得到的MISR签名应该相同。你可以在生产测试阶段,对已知完好的芯片运行一次检查,记录下这个“黄金签名”(Golden Signature)。
- 签名不符:如果后续检查中签名与“黄金签名”不匹配,则表明Flash内容或存储单元本身发生了改变或损坏,可能是位翻转、物理损坏或程序异常写入导致。
4.2 ECC逻辑校验(ECC Logic Check)
ECC逻辑校验是一种验证片上ECC编解码电路是否正常工作的方法,而不是检查Flash存储的数据。它通过UT1和UT2寄存器向ECC逻辑注入模拟的“数据”,通过UT0注入模拟的“校验位(Syndrome)”,然后观察ECC逻辑是否能正确检测或纠正错误。
基本操作思路:
- 使能用户测试(UTE)。
- 在UT1(DAI31-0)和UT2(DAI63-32)中设置模拟的64位数据。
- 在UT0(DSI7-0)中设置模拟的8位ECC校验位(或错误模式)。
- 使能ECC逻辑检查(UT0.EIE = 1)。
- 启动操作(UT0.AIE = 1)。注意,此时AIS应选择顺序模式,且不应设置MRE(Margin Read)。
- 操作完成后,检查MCR中的ECC错误标志(EDC, EER)和ADR寄存器中的地址。由于我们注入的是模拟数据,这个地址可能无实际意义,但标志位的变化可以验证ECC逻辑的响应是否正确。
这个功能非常底层,主要用于芯片生产测试或极度深度的故障诊断,普通应用开发中极少使用。
5. 常见问题排查与实战避坑指南
在实际开发中,仅仅按照手册配置寄存器往往不够,很多问题源于对交互逻辑和时序的忽视。下面是我总结的几个典型问题及解决方案。
5.1 问题:锁存寄存器(LML/SLL)写入成功,但锁定不起作用
现象:代码中已经成功写入了LML或SLL寄存器(读取回写值确认),但对应的Flash块仍然可以被编程或擦除。
排查步骤:
- 检查“或”逻辑:确认你检查的是最终锁定状态。锁定状态由
LML.LLK[x] OR SLL.SLK[x]决定。你可能只修改了LML,但SLL中对应的位在上电时从NVSLL加载了0(解锁状态)。始终读取两个寄存器并计算“或”值来判断是否锁定。 - 检查使能位状态:LML.LLE和SLL.SLE是易失性的。你的写入操作可能发生在使能位有效期间,但随后如果发生了某些操作(如Flash擦写)导致MCU局部复位,使能位会清零,但锁存位的值可能被保留?不,手册指出复位会从非易失性区域重新加载SLL,而LML是易失的,复位后可能清零。确保在每次需要判断锁定状态前,你的使能逻辑是清晰的,并且考虑了复位场景。
- 确认物理地址映射:你尝试锁定的块号(bit index)是否确实映射到了你想要保护的物理地址区间?参考芯片的内存映射图,确认应用程序或Bootloader的链接脚本定义的段,确实落在你锁定的块内。
- 检查操作序列:对Flash进行编程/擦除的操作序列,是否完全遵守了手册的步骤?特别是在启动高电压操作(EHV)前,是否正确地通过选择寄存器(LMS/HBS)选择了目标块?即使一个块被锁定,如果你错误地没有在LMS中选择它,擦除操作可能会跳过它而不会报错(因为根本没选它),造成“锁定失效”的错觉。
5.2 问题:擦除或编程操作总是失败,MCR.PEG标志被清除
现象:启动擦除/编程后,等待DONE标志超时,或完成后发现MCR.PEG位为0,表示Flash编程/擦除错误。
排查步骤:
- 首要检查锁定状态:这是最常见的原因。使用上述方法,计算目标块的最终锁定状态。如果被锁定,操作会静默失败或报告错误。
- 检查供电和时钟���Flash操作对电源电压和时钟稳定性非常敏感。确保在操作期间,内核电压和Flash供电电压在规格范围内,且系统时钟没有发生异常变化。
- 检查操作间隔:连续的Flash操作之间需要满足最小时间间隔。在启动一次操作(EHV置位)后,即使DONE标志很快置起,也应等待手册规定的最小间隔时间(tERSH, tPROG等)后再进行下一次操作。在DONE置位后,插入一个几十微秒的软件延时,是简单有效的避坑方法。
- 检查地址对齐:编程操作必须是双字(64位)对齐的,擦除操作必须是块对齐的。传入的地址不符合要求会导致失败。
- 查看ADR寄存器:当PEG标志为0时,ADR寄存器会保存第一个失败操作的地址。读取这个地址,可以帮助你定位是哪个存储单元出了问题。
- 检查代码执行位置:手册强调:“The Modify Operation commands must be executed from another Memory (internal Ram or external Memory).”执行Flash擦写操作的代码本身,绝对不能位于正在被擦写的Flash区域中。通常的做法是将Flash驱动函数链接到RAM中,或者在执行操作前,将关键的指令序列复制到RAM中运行。
5.3 问题:用户测试功能(如阵列检查)无法启动或结果异常
现象:配置UT0等寄存器后,AIE置位,但AID标志永远不置起,或MISR签名每次都不一样。
排查步骤:
- 确认UTE已使能:这是前提。写入密码后,务必检查UT0.UTE位是否为1。
- 检查DONE和AID状态:UT0.AID和MCR.DONE是互斥的?不,手册说UT0-2和UMISR在
MCR.DONE或UT0.AID为低时不可访问。在启动测试(AIE置位)前,必须确保MCR.DONE=1且UT0.AID=1(表示上次测试已完成)。在测试进行中(UT0.AID=0),你无法读写这些测试寄存器。 - 检查操作冲突:在设置AIE时,必须确保
MCR.ERS、MCR.PGM和MCR.EHV全部为0。任何正在进行或挂起的Flash修改操作都会与测试冲突。 - 理解MISR的随机性:如果你没有为UMISR0-4设置初始种子值,那么每次上电后第一次运行阵列检查,由于MISR初始值为0,得到的签名是确定的。但如果你在单次上电中,多次运行检查且没有重置MISR种子,第二次的签名将是基于第一次结果累积计算的,自然会不同。在每次启动新的、独立的完整性检查前,最好重新初始化(写入种子值)UMISR寄存器。
- 检查测试范围:阵列完整性检查是针对“所有被选择且未锁定的块”。你需要确认你希望检查的块确实处于未锁定状态。如果整个Flash都被锁了,检查操作可能立即完成(因为无块可检),得到的MISR签名可能就是初始种子值,容易被误认为异常。
5.4 寄存器访问时机总结表
下表总结了关键寄存器在何种状态下可写,这是避免硬件互锁违规的关键。
| 寄存器/位域 | 可写入条件 | 不可写入条件 | 说明 |
|---|---|---|---|
| LML.LLK[x], SLE | LLE=1 | LLE=0 | 必须先通过密码使能LLE |
| SLL.SLK[x], STSLK, SMK[x] | SLE=1 | SLE=0 | 必须先通过密码使能SLE |
| HBL.HLK[x] | HBE=1 | HBE=0 | 必须先通过密码使能HBE |
| LMS.LSL[x], MSL[x] | MCR.DONE=1且MCR.EHV=0且无挂起操作 | MCR.EHV=1或 有挂起操作 | 必须在启动擦除(EHV)前配置 |
| MCR.PGM, ERS | MCR.DONE=1且MCR.EHV=0 | MCR.EHV=1 | 设置操作类型 |
| MCR.EHV | MCR.DONE=1且 操作位(PGM/ERS)已设 | MCR.DONE=0 | 启动操作的最后一步 |
| UT0-UT2, UMISR0-4 | MCR.DONE=1且UT0.AID=1且UT0.UTE=1 | MCR.DONE=0或UT0.AID=0 | 用户测试寄存器有独立的状态机 |
| UT0.AIE | MCR.ERS=0,MCR.PGM=0,MCR.EHV=0 | 任何Flash修改操作进行中 | 启动测试的条件更严格 |
掌握这张表,你在编写Flash驱动时就能清晰地规划操作序列,避免状态冲突。归根结底,与PXD10的Flash模块打交道,需要像与一个严谨的协议状态机对话,每一步都必须满足前置条件,任何“抢跑”或“遗忘”都会导致操作失败。