1. 项目概述与EEPROM核心价值在嵌入式系统开发中我们经常需要一种能够“记住”关键数据的存储器。比如一个智能温控器需要记住用户设定的温度模式一个工业传感器需要保存校准系数一个物联网设备需要存储网络连接凭证。这些数据在设备断电后不能丢失但又需要在设备运行时能够被修改。这时候Flash和EEPROM这类非易失性存储器NVM就成为了我们的首选。今天我想深入聊聊TI Tiva™ C系列TM4C129LNCZAD这款微控制器内部集成的6KB EEPROM模块。它远不止是一个简单的数据存储区而是一个配备了完整安全机制、访问控制和寿命管理功能的“智能存储管家”。很多开发者对MCU内部Flash编程比较熟悉但对这颗芯片内置的EEPROM可能只停留在“知道有这个东西”的层面或者仅仅使用厂商库函数进行简单读写。实际上直接操作其寄存器能让我们更精细地控制数据存储行为实现诸如分块加密存储、关键参数防篡改、存储寿命优化等高级功能。尤其是在对数据安全性和可靠性要求严苛的工业控制、汽车电子或医疗设备中理解并善用这些硬件特性至关重要。本文将带你从寄存器层面拆解TM4C129LNCZAD的EEPROM不仅告诉你“怎么用”更重点剖析“为什么这么设计”以及在实际项目中如何避开那些数据手册里没明说、但一踩就痛的“坑”。2. TM4C129LNCZAD EEPROM架构深度解析2.1 物理组织与访问模型TM4C129LNCZAD的EEPROM总容量为6KB但这个6KB的组织方式很有讲究。它并非一个连续的、可以按字节随意寻址的线性空间。其物理结构分为三层理解这个结构是正确编程的基础整体Module6KB总空间。块Block整个EEPROM被均匀划分为96个独立的块Block。这是安全保护和磨损均衡管理的基本单位。字Word每个块包含16个字Word每个字为32位4字节。这是读写操作的最小寻址单元。因此整个EEPROM可以看作一个96行Block、16列Word的二维矩阵。要访问其中任何一个32位数据你必须同时指定两个坐标EEBLOCK块号0-95和EEOFFSET块内字偏移0-15。注意虽然EEPROM支持按字节Byte和半字Half-Word读取但写入操作必须以字32位为单位进行。这是由底层存储单元的物理特性决定的。如果你想修改某个字节标准的操作流程是先读取整个字32位到CPU寄存器在寄存器中修改目标字节再将整个字写回EEPROM。试图直接写入单个字节会导致未定义的行为或数据错误。2.2 核心寄存器组及其协同工作逻辑EEPROM模块通过一组内存映射寄存器进行控制其基地址为0x400A.F000。下面我们重点分析几个最核心的寄存器它们构成了EEPROM访问的“控制中枢”。2.2.1 地址选择寄存器EEBLOCK与EEOFFSETEEPROM当前块寄存器EEBLOCK这个寄存器选择你要操作的块0-95。在写入EEBLOCK后后续的读写操作通过EERDWR或EERDWRINC都将针对该块内的数据进行。EEPROM当前偏移寄存器EEOFFSET这个寄存器选择在已选块内的具体字偏移0-15。它决定了读写操作在块内的精确位置。这里有一个非常重要的机制当使用EERDWRINC带自增的读写寄存器进行访问时EEOFFSET的值会在每次访问后自动加1。当EEOFFSET从15递增到16时它会自动回绕到0但**EEBLOCK的值不会改变**。这意味着EERDWRINC只能用于在一个块内进行连续的顺序访问。如果你想跨块连续访问必须在EEOFFSET回绕到0后手动修改EEBLOCK寄存器。这个设计鼓励开发者以块为单位组织数据符合其保护机制的设计哲学。2.2.2 数据读写寄存器EERDWR与EERDWRINCEEPROM读写寄存器EERDWR这是最基本的读写端口。向该寄存器写入数据即触发一次对当前EEBLOCK和EEOFFSET指定位置的写操作。读取该寄存器则返回对应位置的数据。EEPROM带自增读写寄存器EERDWRINC功能与EERDWR类似但多了一个关键动作每次读写操作完成后EEOFFSET寄存器值自动加1。这极大地简化了连续存储一段数据如一个结构体、一个数组的代码。你只需要设置好起始块和偏移然后循环向EERDWRINC写入即可地址会自动步进。2.2.3 状态与中断寄存器EEDONE与EEINTEEPROM的写入和擦除是物理过程需要一定时间微秒级在此期间CPU不能进行下一次EEPROM操作。因此轮询或中断机制是必须的。EEPROM完成状态寄存器EEDONE这是最重要的状态寄存器。其WORKING位位0指示EEPROM控制器是否繁忙。任何EEPROM操作开始后WORKING位会置1操作完成无论成功失败后该位清零。在进行任何EEPROM操作包括初始化之前必须等待WORKING位为0。该寄存器还包含错误状态位用于指示上次操作是否成功。EEPROM中断寄存器EEINT你可以通过该寄存器使能写入完成中断。当一次写操作完成EEDONE寄存器状态变化时如果中断被使能则会触发Flash控制器的中断注意EEPROM中断与Flash中断共享向量需要通过FCMISC寄存器判断中断源。使用中断可以避免CPU原地空转等待提高系统效率特别是在进行多字写入或需要响应实时任务的系统中。2.3 关键安全机制保护、密码与隐藏TM4C129LNCZAD的EEPROM提供了三层递进的安全保护这在存储敏感数据如加密密钥、产品序列号、安全启动标志时非常有用。2.3.1 块保护EEPROT每个EEPROM块0-95都有一个对应的2位保护字段PROT位于EEPROT寄存器中。这个字段定义了该块的访问权限PROT值无密码时的行为有密码时的行为典型应用场景0x0始终可读、可写。默认模式。始终可读但仅在解锁时可写。存储运行时日志、临时配置。有密码时适合存储可读但不可随意更改的校准数据。0x1无意义通常应避免。完全锁定。仅在解锁时可读、可写。存储高敏感数据如加密私钥。解锁前任何代码包括调试器都无法读取。0x2只读不可写。仅在解锁时可读任何情况下都不可写。存储出厂校准数据或版本信息。防止应用代码意外覆盖但允许在知道密码的情况下读取。2.3.2 密码锁定EEPASSn EEUNLOCK这是更高级的软件锁。你可以为任何块包括块0设置一个32位、64位或96位的密码写入EEPASS0,EEPASS1,EEPASS2寄存器。一旦设置了密码该块就会根据其PROT配置进入锁定状态。块0是主控块为块0设置密码会锁定整个EEPROM模块的控制寄存器如EEBLOCK。在解锁块0之前你甚至无法更改当前块号去访问其他块。这提供了最高级别的全局保护。解锁操作通过向EEUNLOCK寄存器写入密码来解锁。密码长度决定了需要写入的次数32位密码写1次64位写2次96位写3次。必须连续写入中间不能插入其他EEPROM访问。重新锁定向EEUNLOCK寄存器写入0xFFFF.FFFF这是一个无效密码值即可立即新锁定模块或块。2.3.3 块隐藏EEHIDEn隐藏是一种更“决绝”的保护。除了块0任何块都可以被隐藏通过设置EEHIDE0/1/2寄存器中对应的位。一旦隐藏该块将从地址空间完全消失任何读写访问都会失败直到下一次系统复位。这个功能通常用于引导加载程序BootloaderBootloader阶段将关键验证代码或密钥存入某个块验证完成后立即隐藏该块。这样主应用程序乃至调试器都无法再触及这些敏感数据实现了运行时的硬件级隔离。3. EEPROM驱动开发与寄存器级编程实战理解了架构和寄存器我们来动手编写一个稳健的EEPROM底层驱动。直接操作寄存器虽然比调用库函数繁琐但能让你对时序、状态和错误处理有绝对的控制权。3.1 初始化流程绝不能省略的步骤EEPROM模块在上电或复位后需要一段稳定时间。数据手册提供的初始化序列不是建议而是必须严格执行的命令。跳过或错误执行可能导致后续写入的数据损坏甚至永久性损坏EEPROM单元。以下是基于寄存器操作的C语言初始化函数我强烈建议将其放在系统启动最早阶段调用在启用任何中断或复杂外设之前#include stdint.h #include stdbool.h // 用于返回布尔状态 #define SYSCTL_RCGCEEPROM_R (*((volatile uint32_t *)0x400FE658)) // EEPROM时钟门控 #define SYSCTL_SREEPROM_R (*((volatile uint32_t *)0x400FE558)) // EEPROM软件复位 #define EEPROM_EEDONE_R (*((volatile uint32_t *)0x400AF018)) // EEPROM状态 #define EEPROM_EESUPP_R (*((volatile uint32_t *)0x400AF01C)) // EEPROM支持控制与状态 bool EEPROM_Init(void) { // 1. 使能EEPROM模块时钟 SYSCTL_RCGCEEPROM_R | 0x00000001; // 2. 插入延时至少6个空指令周期这里用简单循环替代 // 实际项目中应使用精确的延时函数此处仅为示意 for(volatile int i 0; i 10; i); // 3. 等待EEPROM上电初始化完成 (WORKING位清零) while(EEPROM_EEDONE_R 0x00000001) { // 超时处理应在此添加防止死循环 // if(timeout_expired()) return false; } // 4. 检查上电恢复状态 (PRETRY和ERETRY位) if(EEPROM_EESUPP_R 0x0000000C) { // 检查位2和位3 // PRETRY或ERETRY被置位表明上次可能发生了掉电错误 // 按照手册我们需要尝试软件复位来恢复 } // 5. 执行EEPROM模块软件复位 SYSCTL_SREEPROM_R | 0x00000001; // 置位R0 // 短暂延时 for(volatile int i 0; i 10; i); SYSCTL_SREEPROM_R ~0x00000001; // 清除R0 // 6. 再次等待复位完成 while(EEPROM_EEDONE_R 0x00000001) { // 超时处理 } // 7. 最终检查错误状态位 if(EEPROM_EESUPP_R 0x0000000C) { // 复位后错误位仍然存在这可能意味着EEPROM寿命已尽或存在硬件故障 return false; // 初始化失败 } return true; // 初始化成功 }实操心得第4步和第7步检查EESUPP寄存器的PRETRY和ERETRY位至关重要。如果系统上次运行时发生意外掉电EEPROM内部可能处于一个未完成的操作状态。这两个位就是“掉电恢复标志”。如果它们被置位必须通过上述复位流程尝试清除。如果复位后标志仍在很可能意味着该EEPROM存储单元已经因反复擦写达到了寿命极限或者在掉电时发生了不可恢复的损坏。在生产环境中检测到这种永久性错误后设备应记录故障并进入安全模式。3.2 基础字编程与读取操作初始化完成后就可以进行数据读写了。下面是一个最基本的、带错误检查和等待的“字编程”函数。#define EEPROM_EEBLOCK_R (*((volatile uint32_t *)0x400AF004)) #define EEPROM_EEOFFSET_R (*((volatile uint32_t *)0x400AF008)) #define EEPROM_EERDWR_R (*((volatile uint32_t *)0x400AF010)) #define EEPROM_EEDONE_R (*((volatile uint32_t *)0x400AF018)) bool EEPROM_WordWrite(uint32_t block, uint32_t offset, uint32_t data) { // 参数检查 if(block 96 || offset 16) return false; // 1. 等待任何正在进行的EEPROM操作完成 while(EEPROM_EEDONE_R 0x00000001); // 2. 设置目标地址块和偏移 EEPROM_EEBLOCK_R block; EEPROM_EEOFFSET_R offset; // 3. 写入数据到读写寄存器 EEPROM_EERDWR_R data; // 写入EERDWR寄存器的动作本身就会启动编程周期 // 4. 等待本次写操作完成 while(EEPROM_EEDONE_R 0x00000001); // 5. 检查操作是否成功 (EEDONE寄存器ERROR位) if(EEPROM_EEDONE_R 0x00000002) { // 假设ERROR是位1具体需查手册 // 写入失败可能原因地址受保护、电压不稳等 // 可以在此添加重试逻辑或错误处理 return false; } // 6. (可选) 读回验证 uint32_t readback EEPROM_EERDWR_R; // 注意此时EEBLOCK和EEOFFSET仍指向原地址 if(readback ! data) { // 验证失败数据可能未正确写入 return false; } return true; } uint32_t EEPROM_WordRead(uint32_t block, uint32_t offset) { if(block 96 || offset 16) return 0xFFFFFFFF; // 返回一个错误值 // 等待空闲 while(EEPROM_EEDONE_R 0x00000001); // 设置地址并读取 EEPROM_EEBLOCK_R block; EEPROM_EEOFFSET_R offset; return EEPROM_EERDWR_R; // 读取操作是立即完成的 }3.3 顺序写入与EERDWRINC的高效使用当需要存储一个结构体或数组时使用EERDWRINC寄存器可以大幅简化代码并提高效率。bool EEPROM_SequentialWrite(uint32_t start_block, uint32_t start_offset, const uint32_t *data, uint32_t word_count) { if(start_block 96 || start_offset 16) return false; // 简单检查是否会越界实际应根据数据布局做更严谨检查 if((start_offset word_count) 16) { // 本示例仅处理单块内写入跨块需要额外逻辑 return false; } while(EEPROM_EEDONE_R 0x00000001); // 设置起始地址 EEPROM_EEBLOCK_R start_block; EEPROM_EEOFFSET_R start_offset; for(uint32_t i 0; i word_count; i) { // 关键点使用EERDWRINC寄存器而非EERDWR EEPROM_EERDWRINC_R data[i]; // 写入后EEOFFSET会自动加1 // 等待本次写操作完成 while(EEPROM_EEDONE_R 0x00000001); if(EEPROM_EEDONE_R 0x00000002) { return false; // 某次写入失败 } // 注意这里没有在每次循环中重新设置EEOFFSET } return true; }注意事项使用EERDWRINC时EEOFFSET的自动递增是硬件行为非常可靠。但务必记住它只会在当前块内回绕15-0不会自动跳到下一个块。如果你的数据长度超过一个块的剩余空间必须在软件中处理块切换当检测到EEOFFSET回绕到0后或写入前预计算手动增加EEBLOCK的值并重置EEOFFSET。3.4 配置访问保护与密码锁假设我们想将块5Block 5用于存储设备序列号我们希望它在设置后只能被读取不能被用程序意外修改但允许通过服务工具在知道密码的情况下修改。首先设置保护属性PROT我们希望无密码时只读有密码时解锁可写。根据前表这对应PROT 0x2。// 假设我们要配置块5 #define EEPROM_EEPROT_R (*((volatile uint32_t *)0x400AF030)) // EEPROT寄存器中每个块占2位块5的配置位在[11:10] // 先读取再修改最后写回 uint32_t temp EEPROM_EEPROT_R; temp ~(0x3 10); // 清零块5的旧配置位 temp | (0x2 10); // 设置PROT0x2 EEPROM_EEPROT_R temp;然后为该块设置一个密码我们使用一个64位密码0x12345678, 0x9ABCDEF0。#define EEPROM_EEPASS0_R (*((volatile uint32_t *)0x400AF034)) #define EEPROM_EEPASS1_R (*((volatile uint32_t *)0x400AF038)) #define EEPROM_EEPASS2_R (*((volatile uint32_t *)0x400AF03C)) // 1. 解锁EEPROM模块如果块0有密码需先解锁块0 // 2. 选择要设置密码的块 (EEBLOCK 5) EEPROM_EEBLOCK_R 5; // 3. 写入密码64位需要写两次EEUNLOCK // 注意设置密码本身也是一次写操作需要遵循写流程 // 首先将密码写入EEPASSn寄存器这通常是在制造或初始化阶段完成的一次性操作 // 此处演示流程实际设置密码的完整流程更复杂涉及解锁、写入、验证。日常应用中的解锁操作当服务工具需要更新序列号时。#define EEPROM_EEUNLOCK_R (*((volatile uint32_t *)0x400AF020)) bool UnlockBlock5(void) { // 确保EEPROM空闲 while(EEPROM_EEDONE_R 0x00000001); // 选择块5 EEPROM_EEBLOCK_R 5; // 写入64位密码到EEUNLOCK寄存器连续两次 EEPROM_EEUNLOCK_R 0x12345678; // 第一次写入 // 可选短暂延时或检查状态 EEPROM_EEUNLOCK_R 0x9ABCDEF0; // 第二次写入 // 等待操作完成并检查状态 while(EEPROM_EEDONE_R 0x00000001); if(EEPROM_EEDONE_R ERROR_BIT) { return false; // 解锁失败密码错误或其他错误 } // 解锁成功后在密码有效期间直到写入0xFFFFFFFF重新锁定块5将根据PROT设置允许写入。 return true; }4. 高级主题磨损均衡、中断与调试4.1 理解并利用内置磨损均衡EEPROM的每个存储单元都有擦写次数限制。TM4C129的EEPROM标称寿命是“每个元块Meta-block8个块为一组500K次写入”。这里的“写入”对应用层来说是一次EERDWR操作但对硬件底层可能触发复杂的“写-复制-擦除”循环。核心机制该EEPROM采用了一种“版本化”的写入策略。当你向同一个地址多次写入时硬件并不会直接在原物理单元上擦除重写而是写入到该块内的一个新位置并标记新数据为“当前版本”。当块内用于存储新版本的空间耗尽时EEPROM控制器会自动启动一个“垃圾回收”过程将所有数据的最新版本复制到一个临时缓冲区然后擦除整个块最后将数据复制回来。这个过程对软件完全透明。给开发者的启示避免“热点”写入不要持续向同一个32位字地址如Block 0, Offset 0疯狂写入。这会导致该字所在的元块被频繁回收迅速耗尽该元块的寿命而其他元块却闲置。均衡写入策略对于需要频繁更新的数据如运行时间计数器、事件记录可以采用“循环队列”的方式分布在多个偏移地址甚至多个块中。例如用一个变量记录当前写入索引每次写入后索引加1回绕到起始点。这样就将磨损平均到了多个存储单元上。理解“500K次写入”的含义这是指在理想均衡写入条件下每个元块整体能承受的写入次数。如果你均衡地使用所有96个块整个EEPROM的理论总写入次数会远高于500K * (96/8) 6M次。4.2 使用中断驱动编程轮询EEDONE寄存器虽然简单但在实时性要求高的系统中会浪费CPU周期。使用中断是更优雅的方式。使能EEPROM中断#define EEPROM_EEINT_R (*((volatile uint32_t *)0x400AF040)) #define FLASH_FCIM_R (*((volatile uint32_t *)0x400FD010)) // Flash中断屏蔽 void EEPROM_EnableInterrupt(void) { // 1. 使能EEPROM模块自身的中断生成 EEPROM_EEINT_R | 0x00000001; // 设置INT位 // 2. 在Flash控制器中取消屏蔽EEPROM中断 FLASH_FCIM_R | (1 2); // 设置IM位位2对应EEPROM中断 // 3. 在NVIC中使能Flash/EEPROM中断中断号请查数据手册 // NVIC_EN0_R | (1 (INT_FLASH - 16)); // 示例 }编写中断服务程序ISRvoid FlashEEPROM_ISR(void) { // 1. 检查中断源是否为EEPROM uint32_t status FLASH_FCMISC_R; // Flash屏蔽中断状态 if(status (1 2)) { // EMISC位 // 是EEPROM中断 // 2. 清除EEPROM中断标志写1清除 FLASH_FCMISC_R (1 2); // 3. 检查操作是否成功通过EEDONE的ERROR位 if(!(EEPROM_EEDONE_R 0x00000002)) { // 写入成功可以触发后续任务如发送信号量、设置标志位等 g_eeprom_write_done true; } else { // 写入失败进行错误处理 g_eeprom_error true; } } // 处理其他Flash中断源如果需要 }在主程序中启动写入后即可处理其他任务无需原地等待。4.3 调试与故障排查实录在实际开发中EEPROM操作失败是常见问题。下面是一个排查清单现象可能原因排查步骤与解决方法写入后读回数据不正确1. 未等待WORKING位清零就进行下一次操作。2. 试图写入受保护的块。3. 电源电压在写入期间不稳定。4. 以字节/半字为单位进行写入。1.严格检查EEDONE[WORKING]位在所有EEPROM寄存器操作前等待。2. 检查目标块的EEPROT保护设置和密码锁定状态。3. 确保电源质量尤其在写入操作期间。检查EESUPP寄存器的PRETRY/ERETRY位。4.确保所有写入操作都以32位字为单位。修改字节时遵循“读-改-写”流程。无法更改EEBLOCK或EEOFFSET1. EEPROM模块未正确初始化。2. 主控块Block 0被密码锁定且当前未解锁。1. 确认已调用完整的初始化序列并且EESUPP无错误。2. 如果为块0设置了密码你必须先向EEUNLOCK写入正确密码解锁整个模块才能更改EEBLOCK。EEPROM初始化失败EESUPP错误位始终置位1. EEPROM存储单元已达到或超过其耐久性极限。2. 严重的电源毛刺导致内部状态机损坏。1. 这是一个严重硬件警告。设备应记录此错误并限制EEPROM写入功能或进入降级模式。2. 尝试多次硬件复位并重新初始化。如果问题持续很可能是硬件故障。使用库函数正常但自写寄存器代码失败1. 时序问题未插入必要的延迟。2. 寄存器地址或位定义错误。3. 未考虑编译器优化导致的访问顺序问题。1. 在关键操作如使能时钟、软件复位后加入__nop()或简短循环延时。2.仔细核对数据手册中的寄存器偏移量和位定义使用volatile关键字定义寄存器指针。3. 在连续的、有依赖关系的寄存器操作之间插入内存屏障如__DSB()。在调试器中单步执行时EEPROM操作失败调试器的暂停/断点可能打断了EEPROM编程所需的连续时序。避免在EEPROM写入操作的两条关键指令之间如设置地址/数据到触发写入之间设置断点。最好将EEPROM操作封装成函数在函数入口/出口处打断点。一个常见的坑电压与时钟配置数据手册中的Table 8-4MEMTIM0寄存器配置经常被忽略。EEPROM的访问时序依赖于系统时钟SYSCLK频率。你必须在系统初始化时根据CPU频率正确配置MEMTIM0寄存器中的EWSEEPROM等待状态、EBCE和EBCHT字段。如果配置不当在高速时钟下可能导致EEPROM访问失败或数据错误。TI的TivaWare库中的SysCtlClockSet()函数通常会帮你设置好这个但如果你是自己配置时钟系统务必手动设置MEMTIM0。5. 实战应用设计一个稳健的参数存储系统基于以上所有知识我们来设计一个用于工业控制器的参数存储系统。需求是存储100个不同类型的参数整型、浮点、字符串需要掉电保存支持通过串口命令修改并且要保证至少10年的寿命假设每天最多写100次。设计方案数据分区块0 (Block 0)用作“元数据区”。存储一个结构体包含参数表版本号、当前有效参数所在的块号、写循环索引、CRC校验码等。为块0设置密码PROT0x1解锁后可读可写防止意外篡改元数据。块1-块4 (Blocks 1-4)作为“参数存储区”。采用双备份Double-Buffering或循环队列策略。例如系统总是向“当前活跃块”写入。当该块写满时切换到下一个块并擦除旧块。元数据中记录当前活跃块号。写入策略每次参数变更并不直接覆盖旧值。而是在当前活跃块中寻找空闲位置写入新的“键值对”包含参数ID和值。当块内空间不足时触发“块切换”。将仍有效的所有参数键值对整理到下一个块然后擦除已满的块。这种策略将写入磨损均匀分布到多个块上并且避免了频繁擦除大大延长了EEPROM寿命。安全与可靠性上电恢复系统启动时EEPROM初始化后首先检查EESUPP寄存器。如果有错误尝试恢复流程。然后读取块0的元数据验证CRC。写操作原子性对于关键元数据如活跃块号的更新采用“预写日志”或“两步提交”法。例如先在一个固定位置写入一个“事务开始”标记和准备写入的数据然后更新实际目标最后清除“开始”标记。这样即使在更新过程中掉电也能在下次上电时通过日志恢复到一个一致状态。访问控制通过串口修改参数的命令需要先验证一个预定义的密码软件层面该密码可以与EEPROM块0的硬件密码不同。验证通过后再调用硬件解锁函数解锁块0修改元数据和参数区。代码结构eeprom_driver.c/h包含所有底层寄存器操作函数Init, ReadWord, WriteWord, Unlock, SetProtection等。param_storage.c/h高层应用接口提供Param_Save(),Param_Load(),Param_Get(),Param_Set()等函数。内部实现磨损均衡和元数据管理逻辑。在Param_Save()中每次写入后都进行读回验证。如果验证失败则重试一次最多3次。如果仍然失败则标记该存储单元为坏区并切换到备用块。通过这样的设计我们充分利用了TM4C129LNCZAD EEPROM的硬件特性块保护、密码锁在软件层面实现了磨损均衡和掉电保护构建了一个足以应对严苛工业环境的参数存储系统。这远比简单地调用EEPROMProgram()函数要复杂但也可靠和持久得多。