ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OTP与EEPROM:存储原理、I2C读写与量产防呆

OTP与EEPROM:存储原理、I2C读写与量产防呆 前阵子帮朋友排查一台仪表的参数丢失问题现象很刁钻正常关机再开校准值都在但只要重新烧一次固件所有标定数据全部归零。查到最后锅不在EEPROM也不在I2C总线而是出在OTP配置区——升级程序时把芯片的option字节顺带擦掉了而代码里又没做恢复逻辑。这个案子让我想把OTP和EEPROM的读取与处理这件事从头好好捋一遍。很多人一听到OTP就想到动态口令令牌一听到EEPROM就想到AT24C02但真正在项目里把这两类存储用好、不埋坑需要把存储原理、I2C时序、数据布局、量产流程串起来看。这篇文章就是围绕这几件事展开的适合做嵌入式的、写单片机程序的、以及被掉电丢数据折磨过的硬件工程师参考。1. OTP和EEPROM名字里的两笔糊涂账1.1 两个OTPOne-Time Programmable 和 One-Time Password先把这个最容易让人对不上号的事说清楚。行业里OTP有两种完全不同的意思一种是安全认证领域的One-Time Password动态口令就是银行U盾、企业登录用的那种每隔几十秒变一次的验证码搜索热词里的数盾otp基本指这个方向另一种是半导体存储领域的One-Time Programmable即一次性可编程存储器出厂时是空白状态用户可以写一次写完就永远改不了。这篇文章讨论的一是后一种但两拨人经常在同一个项目里碰头安全方案选型说用OTP做动态口令固件开发说用OTP存根密钥如果不在芯片选型阶段把这两种角色分清楚后面的联调会非常痛苦。1.2 EEPROM、OTP、Flash之间的血缘关系从半导体结构上看OTP其实是EEPROM家族的一个特殊变体。标准EEPROM的存储单元使用浮栅晶体管通过隧道效应让电荷在浮栅里进出所以可以反复擦写而OTP在制造时就把擦除通路做了物理上的简化或者干脆不引出擦除控制于是只能写一次。更直白一点用纸笔来做类比EEPROM是铅笔橡皮写完可以擦掉重来代价是纸张绝缘层每次擦写都会有一点磨损OTP是钢笔写完就固定了适合记录序列号、MAC地址、密钥这类永不改变的数据。Flash则是另一个分支它在EEPROM的基础上优化了存储密度但牺牲了字节级擦除能力只能按扇区或者块来擦。不少MCU内部其实是这么组织的程序区用Flash配置区和唯一ID用OTP区域模拟EEPROM则用Flash配合擦写均衡算法来实现。于是MCU里的EEPROM程序、OTP存储区这些说法在同一个芯片手册里同时出现互相之间概念还不太一样这也是很多人读手册读晕的原因。1.3 为什么字节级擦除的EEPROM至今没被淘汰Flash密度大、成本低为什么板上还要外挂一颗几KB的EEPROM关键在于擦除粒度。Flash最小擦除单位通常是4KB扇区如果只想改一个字节的校准系数得先把整个扇区搬到RAM里、擦掉、再整体写回。碰上掉电数据就变成一半新的、一半旧的。EEPROM支持字节级擦写改哪个字节就只操作哪个字节掉电窗口极小配合日志式写入还能进一步做掉电保护。另外EEPROM的擦写寿命普遍在100万次以上而入门级Flash标称通常只有1万次到10万次。有些场景比如计量仪表每天要记录几十次累计数据如果用Flash扇区模拟磨平只是时间问题用EEPROM就能轻松扛住。所以EEPROM文件管理系统这类需求才会持续存在参数存储这块它依然是性价比最高的选择。2. 从浮栅晶体管看EEPROM为什么断电不丢数据2.1 浮栅管把电荷关进绝缘层笼子里EEPROM的核心存储单元是浮栅晶体管。跟普通MOS管相比它在控制栅和沟道之间多了一层浮栅poly硅这层浮栅没有直接的物理引线四周被绝缘的二氧化硅层完全包裹。这种结构决定了它能锁住电荷电荷一旦注入浮栅没有外部电场干预就没有通路让它跑掉断电之后依然保持这就是非易失性的来源。存储的时候如果浮栅里存在多余电子晶体管的阈值电压就会抬高读的时候这个单元导通能力弱记为0如果浮栅里电子被抽走阈值电压低读的时候容易导通记为1。EEPROM的擦除态无电荷读取值通常是0xFF也就是全1这也是为什么很多人发现新片子读出来全是FF的原因——它天生处于擦除态不是出厂预置了数据。2.2 写入与擦除隧道效应里的电压游戏让电子穿过绝缘层进出浮栅靠的是Fowler-Nordheim隧穿效应通俗说就是在控制栅和衬底之间加上一个很高的电压通常在10V到20V级别把绝缘层两侧的能带压到足够薄电子就能像穿墙一样隧穿过去。写入时往控制栅加高电压衬底接地电子从沟道隧穿进入浮栅单元变为0擦除时反着来衬底加高电压、控制栅接地浮栅里的电子被吸出来单元恢复为1。整个过程没有机械运动但绝缘层每次都会受到高电场冲击积累损伤这就是EEPROM寿命有限的物理根源。OTP的只写一次在这个环节就体现得很明显它的绝缘层和器件结构设计得更宽松只需要保证写一次的动作可靠不需要为多次擦除做冗余设计所以同样工艺下OTP区域的存储密度可以做得更高或者可靠性余量更大。工业场景里有些OTP的保持时间规格能到几十年甚至上百年远超普通EEPROM也是这个原因。2.3 10万次擦写寿命从哪来又何时毁掉EEPROM擦写寿命的单位是次/字节不是整个芯片次。数据手册上写的比如100000次指的是同一个字节能被完整擦写10万次。因为每次擦写都会让浮栅周围的氧化层产生一点陷阱电荷和界面态积累到一定程度单元的阈值窗口就不再满足读取判别条件这个字节就算报废了。实际工程里绝大多数EEPROM寿命问题不是一次性冲到上限而是数据分布不均导致的局部磨损。比如一个结构体里的标志位每次开机都写一遍几年下来这个地址的磨损就远高于其他地址。解决办法就是后面要讲的文件系统思路不要把固定数据写死在某几个地址上而是轮换地址写入让整个存储区均匀磨损。2.4 EEPROM与Flash擦除粒度的致命区别再说深一层为什么很多人在内部模拟EEPROM上栽跟头。Flash按扇区擦除做模拟EEPROM时通常做法是要修改某个字节先把这个扇区读进RAM改掉对应字节然后整扇区擦除再整扇区写回。如果中途掉电扇区里的旧数据已经没了、新数据又没写完这块参数区就彻底废了。EEPROM则没有这个问题字节级操作意味着掉电影响最多是当前写的这一个字节而且通过先写新值、再做标志的方式可以把掉电影响降到最低。所以尽管Flash模拟EEPROM在成本上有优势但凡是涉及关键参数、掉电安全、长期可靠性的产品外挂EEPROM或选用内置硬件EEPROM的单片机依然是最稳的选择。3. I2C读写EEPROM的工程细节时序、地址和代码姿势3.1 从AT24C系列看I2C从机地址怎么来I2C总线上每个设备得有唯一地址AT24C系列用器件类型硬件引脚来组成地址字节。比如AT24C02的地址格式是高四位固定1010接下来三位由A2/A1/A0引脚决定最低位是读写方向位。写方向地址是0xA0读方向是0xA1。如果一块板上挂了8颗AT24C02靠A2A1A0的不同接法就能把它们区分开。AT24C16这种容量更大的芯片情况不一样A0/A1不再作为外部引脚芯片封装上直接NC这三位变成内部页地址选择位。也就是说一个器件内部的地址空间被分成了8个256字节的块访问时把块号编进从机地址字节里。很多人读大容量EEPROM时踩坑往往就是漏了这一点读地址0x0100附近的数据直接发了0xA0然后地址填0x0100结果读到的是块内偏移0x00数据完全错乱。正确做法是访问地址0x0100-0x01FF时从机地址应该是0xA2、0xA4、0xA6……依此类推。3.2 页写和tWR连续写入最容易忽略的坑I2C EEPROM支持连续写但不能无限连续。AT24C02的页大小是8字节AT24C04/08/16常见是16字节。所谓页写就是一次连续写入最多不能跨出当前页边界如果写入数据长度跨过了页边界后面的数据会回卷覆盖到该页起始地址破坏已有数据。解决方式有两个一是写之前先做地址对齐拆包把跨页的一次写入拆成多次页内写入二是用驱动层封装把起始地址mask掉低几位得到页基地址再计算页内剩余空间循环写入。这个逻辑必须在驱动里处理干净因为主控侧的DMA或者FIFO可不会管EEPROM的页边界。另一个参数是tWR写周期时间典型值3ms到5ms有些型号在极限温度下可达10ms。关键点在于EEPROM内部写操作完成后芯片才会响应下一个I2C命令。所以连续写入的正确姿势不是延时等固定时间而是发送一个写指令的探测帧如果从机返回ACK说明内部写完了如果返回NACK说明还在忙。这种软件轮询比死等延时快得多在每次要写很多参数的场景下能明显缩短整个流程。3.3 单片机上手示例中颖内置EEPROM与外挂I2C的差异不同厂商的单片机对EEPROM的封装方式差异很大。以热词里提到的中颖单片机为例它内部很多型号带DataFlash官方库里提供类似EEPROM_Write、EEPROM_Read的接口底层的实现原理其实还是按扇区擦除按字节写由库函数内部帮你完成了读改写逻辑。用这种接口的时候要特别注意写数据前必须保证目标扇区已被擦除官方库或初始化代码里通常会在第一次使用时做一次全片擦除但如果你在产品代码里手动调用IAP相关命令这个约束就得自己扛。外挂I2C的EEPROM则不一样它是字节级硬件擦写写指令自带擦除动作不需要软件先擦。所以换成外挂EEPROM之后很多从IAP模拟方式转过来的工程师会不习惯怎么少了擦的步骤对硬件帮你做完了你只管发地址和数据。直接用中颖库函数时还有一个小坑部分系列的库函数要求在操作内部EEPROM之前关闭全局中断否则中断服务函数里碰了别的Flash扇区会出问题。外部I2C芯片就没这么多羁绊走普通GPIO模拟I2C或硬件I2C外设都行跟中断的耦合度低很多。3.4 Verilog控制器状态机划分与ACK轮询的实用姿势如果EEPROM是挂在FPGA上那就得自己写I2C控制器。用Verilog实现I2C读写EEPROM核心不是收发那几根线而是状态机的划分。推荐把控制器拆成两个层次底层是字节收发引擎负责START、STOP、8位数据、ACK采样、SCL时钟产生上层是应用状态机负责从机地址、字地址、数据长度、读/写方向、页写回卷等业务流程。上层状态机建议划分成这些状态IDLE、START、SEND_SLAVE_ADDR、SEND_WORD_ADDR、SEND_DATA、STOP、WAIT_tWR、RESTART、READ_DATA。写时序上有个非常实用的技巧写命令的最后一个字节发完之后不要直接给STOP先发一个STOP然后立刻重新给START和从机地址写方向此时如果EEPROM返回ACK说明内部写完了可以继续下一个操作如果返回NACK保持总线重试。这个ACK轮询状态一定要单独画出来不然你会发现上层逻辑永远在等固定延时效率低还容易在高温下因为延时不足导致写丢。跨页处理也一样在FPGA里最好把本页剩余字节数算出来写够一页就发STOP然后重新START这样即使应用层发来一个长度超页的数据底层也能安全拆包。代码层面还有两个容易被新手忽略的点SDA信号必须在SCL低电平期间切换否则会被判成START/STOPACK采样要在SCL高电平的中段进行不能采样在边沿上。这两个点如果没做对用仿真看波形都对上板就随机出错。4. EEPROM文件管理系统把参数区当小硬盘来设计4.1 裸存储的三大痛点磨损、掉电、版本错乱如果只是存几个固定参数直接放在固定地址就行但一旦参数变多、更新频繁、需要版本管理裸存储的三个问题就会浮出水面磨损集中经常更新的参数永远写在同一个位置寿命最先到掉电丢失写一半断电数据新旧混杂版本混乱结构体换代之后旧固件的数据和新固件的数据对不上设备刷个新版本就变砖。这些问题在热词里被概括为一种EEPROM的文件管理系统本质上就是给EEPROM设计一套轻量级的存储管理方案让这块不大的存储空间像一个小硬盘一样可扩展、可维护、安全掉电。4.2 日志式键值存储新数据写在旧数据后面解决磨损问题的核心思路叫日志式存储或者说追加写。设计上把整个EEPROM划分为若干个固定大小的记录槽每个槽存储一条带头部信息的记录头部包含Magic数、键值或ID、数据长度、CRC校验和数据本身。每次更新数据时不去改写旧记录而是在下一个空闲槽里写入新记录。读取的时候从末端往回扫描找到最近的、CRC校验通过的那条记录即可。EEPROM空间用完后把有效记录搬到RAM里擦除整个区域再把有效记录重新写回完成一次垃圾回收。用这种设计每个参数每次更新都会落在不同的物理地址上整个存储区的磨损被摊开寿命提升接近一个数量级。加上先写数据、最后写有效标志的顺序写一半断电也不会破坏旧数据掉电安全的本质是新数据没写完的时候旧数据还是完整的。4.3 双备份区与CRC数据写到一半断电怎么办日志式存储能解决磨损但解决不了垃圾回收过程中断电这种极端场景。所以很多成熟方案会再加一层双备份区设计把存储区分为A区、B区和切换记录区。写入时交替往A、B区写写完后在切换记录区打一个标记标明当前有效的是哪个区。启动时读取切换记录如果记录完好就加载对应区域如果记录损坏就两个区都扫描一遍取版本号较高的那一个。CRC校验也是必不可少的。EEPROM在高温、长期存储、以及写入瞬间掉电的情况下数据位翻转的概率不可忽略。给每条记录加CRC16读出来先校验校验不过就当数据无效回退到上一个有效备份。这里推荐使用查表法实现CRC代码量小、执行快在单片机上毫无压力。4.4 一个可落地的EEPROM管理模块划分综合下来建议把EEPROM管理系统拆成四层驱动层屏蔽I2C/SPI/内部Flash等硬件差异提供eep_read_bytes、eep_write_bytes这类基础接口传输层处理页写回卷、tWR轮询、总线错误重试存储管理层负责记录槽分配、日志追加、垃圾回收、启动扫描应用接口层向上提供param_get(key, value)、param_set(key, value)业务代码再也不需要关心参数存在哪个地址。这套结构的收益是业务层每次需要新增参数只需要申请一个新的key不用再倒推地址表、不用管磨损。我在实际项目中把一套100多个参数的校准系统从固定地址方案迁移到这个结构之后参数丢失的售后单从每个月五六例降到了零。5. OTP烧录的量产教训不可逆这件事比想象中麻烦5.1 MCU里OTP区域到底存了些什么MCU的OTP区域通常负责两类事情一类是安全配置比如读保护等级、写保护、调试接口开关、代码加密选项另一类是出厂信息比如芯片唯一ID、校准数据、MAC地址、密钥。这意味着OTP里存的东西往往比一份普通数据更关键因为它经常参与设备身份的定义。烧错了不是改一下的事整颗芯片报废。所以正规流程里OTP的烧录要有比普通Flash程序烧录严格得多的防呆机制。很多人不知道的是有些MCU的OTP区只能在特定模式下烧写需要进入ISP模式甚至要额外的编程电压或专门的时序。自己在产线上做离线烧录器时务必先确认目标平台的OTP烧录条件不要想当然地以为走SWD/JTAG口就能顺手把OTP也写了。5.2 保护位、烧录电压与防转储OTP还有一个常被忽略的用途配合代码保护和防转储。典型的思路是把加密用的根密钥或关键哈希值烧到OTP区然后拉高读保护等级之后任何调试接口都无法再读出Flash内容。因为密钥不在Flash里即使有人把Flash完整倒出来没有OTP里的密钥镜像也无法解密运行。这个机制在联网设备、收费终端、版权保护场景里应用非常广泛。具体操作时要严格按数据手册的顺序来比如某款芯片要求先烧OTP密钥再打开RDP保护最后量产测试。顺序反了要么密钥写入失败要么保护等级被锁定成不可逆状态。5.3 量产防呆校验重读与烧录台账OTP不可逆所以量产流程里的校验就不只是例行公事而是一道生死线。建议养成三个习惯烧录后立即回读OTP区内容并比对预期值比对失败直接让产线软件报错不允许流入下一步建立烧录台账记录每片被烧录芯片的唯一ID、烧录时间、烧录固件版本、测试结果后续如果出现批量问题可以精确回溯对bin文件做严格版本管理产线烧录器使用的OTP文件必须锁定哈希任何变更要走审批防止今天调试随意生成的临时bin误烧进量产线。5.4 OTP与EEPROM选型判断表维度OTPEEPROM可写次数1次10万次以上字节级典型用途序列号、密钥、保护位、MAC运行参数、标定值、累计数据掉电保存优秀优秀改写成本改写报废随时改写量产风险高需严格防呆中可重写挽回典型容量几十字节到几KB1KB到1Mb选型的判断逻辑很简单数据一旦写入就不变用OTP省心省面积数据会随运行状态变化哪怕频率很低也要用EEPROM或Flash模拟方案。硬拿EEPROM当OTP用浪费寿命硬拿OTP当EEPROM用那是给自己挖坑。6. 调试现场最容易翻车的六种情况6.1 SDA一直拉低先查上拉电阻和总线挂死I2C总线是开漏结构SCL和SDA必须有上拉电阻才能拉高。用过短的PCB走线、贴了容值过大的去耦电容或者上拉电阻选得过大比如10k以上碰上长线多从机上升沿会变得很缓主控读到的电平就可能错乱。更常见的是从机芯片在某个状态卡死一直把SDA拉低整个总线直接瘫痪。解决手段遇到SDA被拉低先断开疑似从机把SDA单独拉高看看能不能释放。总线挂死时主控可以连续发送9个SCL脉冲让卡在发送位的从机走完当前字节然后把STOP发出来大部分情况下能恢复。批量产线建议在软件里加一个超时重挂逻辑如果连续N次无ACK就复位I2C外设再执行释放操作。6.2 tWR轮询做不对连续写必丢连续写入大量参数时很多人的第一版代码是写完就发下一个写命令结果稳定丢数据。这就是tWR没等够。用固定延时的方案勉强能用但在温度变化、芯片批次差异下延时余量很难估准用ACK轮询的方式则可以自适应每个器件的实际写时间。代码如下I2C写EEPROM通用驱动里最核心的一段逻辑int eeprom_wait_busy(void) { // 发送从机地址写方向如果从机返回ACK表示内部写完成 // 返回NACK则继续重试最多等500次 uint8_t retry 0; while (retry 500) { if (i2c_start() 0) { if (i2c_write_byte(SLAVE_WRITE)0) { i2c_stop(); return 0; // ACK写完成 } } i2c_stop(); retry; delay_us(100); } return -1; // 超时 }6.3 跨页写回卷数据顺序直接错乱页写回卷是另一个高频问题尤其数据长度跨页时。比如AT24C04页大小16字节从地址0x000A开始写12个字节第7个字节开始就跨过了0x0010的页边界EEPROM会把它回卷写到0x0000把旧数据覆盖掉。驱动层必须做拆包逻辑很简单uint16_t page_size 16; uint16_t remain page_size - (addr % page_size); if (len remain) { // 先写页内剩余部分再迭代写后续页 }6.4 地址/数据对齐8位还是16位寻址要想清楚有些EEPROM的字地址是8位如AT24C01容量到AT24C02以上才是16位两字节字地址。驱动里如果统一用先发高位、再发低位的方式处理8位寻址的芯片会把高位地址当成操作码读写结果完全错乱。选型后第一件事就是确认字地址宽度驱动层封装时把地址宽度作为参数不要写死。6.5 擦写次数到了之后的数据假死EEPROM寿命耗尽不会立刻导致芯片完全不可用而是在某个字节上开始出现写完读出来不对的现象。这种问题很难复用因为不同批次、不同温度下表现都不一样。应对思路一是靠文件系统做磨损均衡把压力摊开二是增加CRC校验和自动纠错机制读到校验失败时从备份区恢复三是在量产测试环节做高温老化把早期失效品提前筛出去。6.6 示波器抓I2C时序的两个实战建议调试I2C时序普通逻辑分析仪已经够用但有两个建议值得听抓SDA和SCL时触发方式选择SCL下降沿触发数据会比较稳定测量ACK/NACK时把光标放在SCL高电平区间的中间位置那个点才是数据采样窗口的稳定点不要在边沿读数。另外如果波形看起来像是有毛刺或者额外脉冲先怀疑是不是主控的I2C外设被中断长时间打断SCL时钟被拉长从机超时复位了。这种间歇性随机失败的问题靠示波器看单个波形往往看不出名堂要用长时间深存储模式抓突发错误或者干脆在代码里加错误注入测试来模拟最恶劣时序。收尾的一点心里话跟存储打交道这么多年我最大的一个感受是EEPROM也好OTP也好都不是上电读、下电写这么简单的功能块它们的可靠性边界掌握在懂原理、舍得在驱动层和数据布局上做细节的人手里。固定的地址表适合快速原型但扛不住量产后的各种幺蛾子而一个封装良好的EEPROM管理模块一次投入可以长期受益后面每加一个参数都是在已有的框架上写三行代码的事。如果你手头正好在做参数存储相关的项目建议先把驱动层的页写边界和tWR轮询处理好再考虑要不要上日志式文件系统一步步来数据安全这件事急不得。
返回列表