ARTICLE DETAIL

资讯详情

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

从浮栅到I2C:EEPROM选型、单片机驱动与FPGA读写实战

从浮栅到I2C:EEPROM选型、单片机驱动与FPGA读写实战 1. EEPROM到底是什么从存储单元结构说起很多刚接触单片机的朋友第一次用EEPROM基本都是被掉电不丢数据这五个字吸引过来的。确实RAM一断电数据就没了Flash又不太好天天擦写这时候EEPROM就成了保存校准参数、设备序列号、累计运行时长、用户设置项的首选。但真到了项目里问题就来了为什么我写一个字节要等5毫秒为什么写了几十万次之后某个地址就再也写不进去了为什么同样的代码在A板子上跑得好好的换到B板子读出来全是0xFF这些问题的答案全都藏在EEPROM的存储单元结构里。我接触EEPROM是从早期的24C02开始的那时候还没有什么HAL库全靠自己翻时序图、数SCL的边沿。后来做工业设备要在现场环境下保存几十个标定系数才真正下功夫把它的物理机制、寿命模型、访问时序都捋了一遍。这一节就先把底层的为什么讲清楚后面再谈具体怎么做读写。1.1 浮栅晶体管一个能锁住电子的开关EEPROM的每个存储单元核心是一个带浮栅的MOS管。普通MOS管只有一个栅极加电压就导通而存储管的栅极做了两层——靠近沟道的那层叫浮栅它被一层极薄的氧化层厚度通常在10纳米量级和衬底隔开上面再叠一个控制栅。浮栅四周被绝缘介质包围相当于一个悬空的小岛一旦有电子被送进去又没有外部能量帮它们翻过势垒这些电子就会被困在里面出不来。写入编程时控制栅加高压漏极也加高压沟道里的电子获得足够能量穿过薄氧化层注入浮栅这个过程叫热电子注入也有器件走Fowler-Nordheim隧穿。浮栅积累负电荷后存储管的阈值电压被抬高在同样的控制栅电压下它就不容易导通。擦除则是反过来把电子从浮栅抽走阈值电压降回去。读出时给控制栅一个介于两种阈值之间的电压看管子导通与否就能判断这一位是0还是1。这个过程不需要外部持续供电来维持所以EEPROM掉电以后数据还在理论上能保存十年往上。行业内常见的指标是数据保持能力10年、擦写寿命10万次工业级器件会标到100万次擦写和40年保持具体看厂家工艺。1.2 为什么EEPROM能按字节改Flash不行这是最常被问到的问题。EEPROM的每个存储单元除了存储管通常还带一个选择管两个管子协同工作使得每一个字节都能被独立寻址、独立擦写。你只想改地址0x08的一个字节那就只操作那一个字节对应的单元其他字节完全不受影响。Nor Flash和Nand Flash为了追求容量和成本把选择管砍掉了单元结构更简单、密度更高但代价是擦除必须按扇区或者块来。也就是说Flash的写操作本质上是先整块擦掉再逐字节写回最小擦除粒度动辄4KB、64KB。这就是为什么很多时候我们把Flash划出一块区域用软件模拟成EEPROM来用也就是常说的模拟EEPROM本质是用空间换灵活性。所以选型时有个基本判断如果你的数据是几十字节到几KB量级、需要频繁按字节改写、要求写的时候不能影响别的数据那真EEPROM合适如果是几MB的固件、日志、字库那就该用Flash别拿EEPROM去堆。1.3 容量、寿命、速度的真实账本新手容易踩的坑是只看容量不看寿命和速度。我整理了一张常用的对比表都是实际选型时绕不开的几个维度维度典型表现实际影响容量片内几十字节到几KB外挂24C系列2Kb到1Mb决定能存多少参数注意单位是bit还是Byte擦写寿命10万次到100万次整片或单字节维度不同高频写场景必须做磨损均衡数据保持常温10年高温环境大幅缩水工业现场必须降额评估写周期单字节约3到5毫秒页写可摊薄写一个字节就阻塞主循环5毫秒很奢侈读速度I2C 100kHz/400kHz/1MHz下随机读微秒级读基本不构成瓶颈工作电压1.8V到5.5V低压档速度会受限3.3V和5V混用时要看总线电平这张表里最容易被忽略的是写周期。AT24C02一次字节写之后芯片内部要花最多5毫秒完成实际编程这段时间它对I2C总线是不响应的你要么死等要么用应答轮询去判断写完没有。5毫秒看着不多但如果你的主循环是1毫秒一轮的任务调度一次写就把时序全打乱了。这也是后面要专门讲页写和应答轮询的原因。2. 真机选型片内EEPROM和外挂芯片怎么选搞清楚原理之后剩下的第一件事就是选型。摆在面前的路其实就三条用MCU片内自带的EEPROM、用片内Flash划一块模拟EEPROM、外挂一颗I2C接口的EEPROM芯片。这三条路我都走过也都在项目里翻过车下面把各自的适用边界说清楚。2.1 片内EEPROM中颖单片机这类方案的实际用法不少国产MCU会在片内集成一块独立于程序Flash的EEPROM区域中颖的SH79F、SH88F系列就是典型代表。它和程序存储区在物理上是分开的擦写时不需要停下来也不需要把整个扇区搬来搬去用起来比模拟EEPROM干净得多。访问方式一般是寄存器间接寻址先往地址寄存器写目标偏移再往数据寄存器写或读最后通过一组控制寄存器触发操作。以这类片内EEPROM的常见设计为例操作前需要按手册要求逐级写入解锁序列把控制寄存器一依次填上固定值再对最后一个控制寄存器写入操作码来触发读、写或者扇区擦除。解锁序列存在的意义是防止程序跑飞时误擦数据这一点在做安全相关的产品时非常关键。写操作完成后芯片会有一个内部编程周期此时寄存器会有一个标志位或者直接靠延时等待。中颖的手册里通常会给出一个编程时间典型值我印象里在几毫秒量级实际项目里我一般会在写完后轮询状态位而不是拍脑袋延时因为不同批次的芯片这个时间可能有差异。片内EEPROM最大的好处是省了一颗芯片、省了I2C总线、省了PCB面积在成本敏感的量产产品里优势明显。缺点是容量小通常只有几百字节到几KB而且不同型号之间的访问方式不通用换型号基本要重写底层驱动。2.2 外挂AT24C系列引脚、地址和总线上限外挂方案里AT24C系列几乎是事实标准24C02到24C512是主力型号。它的引脚非常固定A0、A1、A2三个地址脚SDA、SDA、WP写保护电源地。总线上挂几颗芯片就靠A0到A2拉高拉低来区分最多8颗。器件地址的高四位是固定的1010低三位就是A2到A1到A0最低位是读写方向位。举个例子三根地址脚全部接地写地址就是0xA0读地址就是0xA1如果A0接高、A1和A2接地那写地址就是0xA2。很多新手算地址的时候栽跟头是因为把7位地址和8位读写字节搞混了或者把A0当成最高位。记住顺序是A2A1A0A0是最低位这样就不容易错。WP脚也很值得说一句。拉高时器件进入写保护只能读不能写拉低才允许写。做产品的时候如果参数区只在出厂标定阶段写量产后就可以把WP拉高避免现场程序异常把参数改掉。我见过一个案例设备在强干扰环境下跑飞把标定参数写成了乱码最后整机返修其实WP接高就能规避。选容量时还有个细节AT24C02的页大小是8字节24C16是16字节24C64及以上是32字节跨页写会回卷到页首覆盖已有数据这个后面会详细讲。2.3 选型时我会重点确认的几件事从我个人经验出发选型阶段至少要确认下面这几点缺一个都可能在后期返工容量单位手册上写的2K是2K bit还是2K ByteAT24C02是2K bit即256字节别想当然按2KB去规划数据。页大小和写周期页大小决定一次能连写多少字节写周期决定阻塞时长这两个直接决定驱动怎么写。供电和总线电平3.3V器件在5V总线上用要确认引脚是否耐压必要时加电平转换。擦写寿命与写频率按每天写多少次、要跑多少年去反推决定是否需要磨损均衡。是否支持应答轮询支持的话可以用ACK检测代替固定延时提高响应速度。工作温度范围商用0到70度工业级-40到85度别为了省几毛钱用在户外设备上。3. I2C总线在EEPROM上的落地细节EEPROM的通信协议里I2C是绝对主流。协议本身不复杂两根线、几个时序组合但真写到能稳定跑通、还要跑几年不出问题细节很多。这一节把I2C在EEPROM场景下的关键时序拆开讲包括那些时序图上画得很清楚但实际容易搞错的地方。3.1 起始、停止、应答三个基本动作I2C的空闲状态是SCL和SDA都被上拉电阻拉到高电平。起始条件是SCL为高时SDA从高跳到低停止条件是SCL为高时SDA从低跳到高。这两个条件里SCL必须保持高如果SCL和SDA同时变化器件可能识别不到。数据位的规则是SCL低电平期间SDA可以变化SCL高电平期间SDA必须保持稳定。也就是说发送方在SCL拉低时更新数据在SCL拉高时接收方采样。很多用IO口模拟的做法在这里出问题往往是先动SDA再动SCL顺序反了在低速下也许能跑一上400kHz就挂。应答位是每发送8位数据后的第9个时钟。发送方把SDA释放为高由外部上拉拉高接收方如果拉低表示ACK保持高表示NACK。这里有个非常容易忽略的点主机读数据时最后一个字节主机必须回NACK告诉从机我不要了然后再发停止条件。如果最后一个字节回了ACK从机可能继续往后吐数据把总线状态搞乱。上拉电阻的选择值得单独说。4.7k是常见值对应100kHz到400kHz。总线电容大、走线长的时候要适当减小到2.2k甚至1k电池供电、要求低功耗的场景可以放大到10k。上拉太小会增加静态功耗太大则上升沿变缓高速下波形爬不起来。示波器上看一下SCL的上升沿如果达到高电平阈值的时间超过时钟周期的三分之一就该换电阻了。3.2 单字节写、页写、随机读、顺序读EEPROM的访问就这四种基本事务全部掌握了剩下的都是组合。单字节写起始 器件地址末位0 ACK 字地址 ACK 数据 ACK 停止。整个过程主机发从机答。收到停止条件后器件开始内部写周期这期间不响应任何总线请求。页写和单字节写几乎一样区别是数据之后继续发下一个字节直到发满页或者发停止条件。从机每收一个字节回一个ACK。这里的关键约束是页边界地址低几位达到页末时下个字节会回卷到本页开头而不是自动进下一页。假设AT24C02页大小8字节从地址0x06开始连写4个字节实际落点是0x06、0x07、0x00、0x01不是0x06到0x09。这个坑我踩过当时做参数批量保存跨页以后数据错位排查了好久才反应过来。随机读起始 器件地址写 ACK 字地址 ACK 起始重复起始 器件地址读 ACK 数据 NACK 停止。注意这里有一个重复起始条件不是先发停止再发起始顺序反了器件会丢失内部地址指针。顺序读在随机读的基础上继续给SCL从机会自动递增地址继续输出数据直到主机回NACK然后发停止。读整个参数区的标准做法就是这个。3.3 写周期与应答轮询的实现前面反复提到写周期。器件收到停止条件后进入内部编程最长时间按手册一般是5毫秒有些型号10毫秒。这期间的两种处理方式第一种是固定延时简单粗暴delay_ms(5)然后继续。缺点是浪费了实际器件可能1毫秒就写完了多等4毫秒。第二种是应答轮询也叫ACK查询。做法是持续发起始条件加器件地址写方向如果器件回ACK说明内部编程已经完成可以发停止条件然后继续如果没ACK就等一小段时间再试。这个方式在实际工程里更推荐尤其在需要频繁保存数据的场景能把写等待时间压缩一大截。应答轮询有个细节每次尝试失败后必须发停止条件或者至少让总线回到空闲否则时钟和数据线可能被拉在半路。我一般的实现是循环里发起始、发地址、检测ACK、发停止间隔几微秒重试超时按手册最大写周期乘以2作为兜底。4. 单片机端的驱动实现协议理解到位以后落到代码上还有不少抉择用硬件I2C还是软件模拟片内EEPROM怎么操作数据安全怎么保证这一节把这几件事都过一遍代码我会写得尽量接近能直接用的程度。4.1 中颖片内EEPROM的寄存器操作过程片内EEPROM的写法各家不一样但套路是共通的设地址、设数据、解锁、触发、等完成。以这类芯片常见的寄存器命名为例大概是这样一套流程先把目标单元的地址偏移写进地址寄存器把要写的数据写进数据寄存器然后依次向IB_CON1、IB_CON2、IB_CON3写入手册规定的解锁序列最后向IB_CON4写入写操作码触发编程之后轮询状态位等它结束。读操作的差别只在于触发的是读操作码然后从数据寄存器取结果。扇区擦除则是触发另一个操作码把一整片擦干净擦除后单元内容一般是0xFF。这里必须强调一句解锁序列的具体数值、触发码、编程等待时间必须以你手上那颗芯片的最新数据手册为准不同型号甚至同型号不同版本都可能不一样我上面说的只是这类操作的通用形态照抄别的型号的数值是最容易翻车的地方。绕过手册还有一个土办法就是利用编译器提供的固件库或者厂家给的例程先跑通一个最简单的读写再用示波器或者串口打印的方式确认时间参数这比啃手册快。4.2 硬件I2C和软件模拟什么时候用哪个MCU一般都会带一个硬件I2C外设配好时钟、地址、中断就能用CPU占用低速度稳定。但硬件I2C有两个常见问题一是某些厂商的硬件I2C中断标志位设计有缺陷在从机拉长时钟或者总线异常时容易卡死需要额外写超时恢复逻辑二是引脚位置固定PCB布线不灵活遇到引脚冲突就得改板。软件模拟就是拿两个普通IO口按位操作SCL和SDA。优点是引脚随便选、平台移植容易、出问题好调试缺点是占CPU、速度上限低、对时序精度要求高。我的习惯是低速、偶发访问的参数存储用软件模拟就够了稳当高频、连续读写的场合才考虑硬件I2C。下面是一段软件模拟的写字节核心逻辑写得比较朴素但很实用/* 软件模拟I2C向EEPROM写一个字节 */ static void i2c_delay(void) { /* 400kHz 时约 1~2us可按实际主频微调 */ unsigned char i 4; while (i--) { } } static void sda_out(unsigned char level) { if (level) { SDA_PIN 1; /* 先写1再切输出避免拉低瞬间的毛刺 */ SDA_DIR 0; } else { SDA_DIR 1; /* 输出低 */ SDA_PIN 0; } } static void i2c_start(void) { sda_out(1); scl_out(1); i2c_delay(); sda_out(0); i2c_delay(); /* SCL高时SDA下降沿 */ scl_out(0); i2c_delay(); } static unsigned char i2c_write_byte(unsigned char dat) { unsigned char i, ack; for (i 0; i 8; i) { sda_out((dat 0x80) ? 1 : 0); dat 1; i2c_delay(); scl_out(1); i2c_delay(); /* 拉高采样 */ scl_out(0); i2c_delay(); } sda_out(1); /* 释放SDA准备读ACK */ i2c_delay(); scl_out(1); i2c_delay(); ack SDA_READ(); /* 0表示ACK */ scl_out(0); i2c_delay(); return ack; }这段代码里两个点最值得注意。一是sda_out(1)时先把引脚寄存器写1再切成输出这样切方向的一瞬间不会出现从高电平被强行拉低的毛刺总线异常很多时候就出在这个瞬间。二是ACK采样是在SCL高电平期间读的时钟没拉高之前读到的值不算数。4.3 数据校验、掉电保护和磨损均衡裸写裸读的EEPROM代码在实验室没问题到现场就会出各种幺蛾子。必要的加固手段有三个。校验是底线。每个数据块配一个累加和或者CRC16读出来先校验再使用校验不过就回退到默认值或者用备份副本。累加和实现简单对单字节翻转的检出率还行但对字节顺序调换这种错误无能为力重要参数我一般上CRC16。掉电保护要解决的是写到一半断电的问题。EEPROM单字节写是原子的但多字节的数据块写到一半断了整块数据就废了。常用做法是双备份A区、B区各存一份每份带序号和校验。写的时候先写序号小的那份成功后更新标记读的时候挑校验通过且序号最新的那份。这样任意时刻断电总有一份是完整的。磨损均衡针对的是高频写场景。EEPROM整片寿命10万次是单字节维度的但如果你天天往同一个地址写累计运行时间那个字节可能几个月就被写穿了而其他字节还好好的。解决办法是把这块区域做成环形缓冲区每次写下一个位置同时维护一个指针读的时候找最新那条。代价是多占点空间换来的是寿命被平均摊到所有单元上。5. FPGA侧用Verilog写I2C主控读写EEPROM很多做FPGA的朋友会遇到这种情况板子上挂了一颗EEPROM存配置或者标定数据但没有CPU得自己用Verilog把I2C主控写出来。这件事比单片机端麻烦因为硬件描述语言里没有延时函数和阻塞赋值按顺序执行的概念所有时序都是状态机加计数器堆出来的。这一节我按实际做过的顺序把设计思路、代码框架和调试方法都讲一遍。5.1 状态机怎么划分才不容易乱新手写I2C最容易犯的错是把整个事务写成一个巨大状态机几十个状态互相跳一出问题完全无法定位。我推荐按事务类型分层底层是一个字节级的状态机负责发送一个字节、接收一个字节、产生起始停止条件上层是一个事务状态机负责编排整个过程。字节级状态机大概就这几个状态IDLE空闲、START产生起始、SEND发送8位、ACK采样应答、STOP产生停止。事务级状态机根据读写方向编排写事务是START、SEND器件地址、ACK、SEND字地址、ACK、SEND数据、ACK、STOP读事务多一次重复起始和方向切换。分层的另一个好处是可复用。一块板子上挂多颗I2C器件的时候字节级模块完全不用改只改事务级的编排逻辑就行。5.2 时钟分频与SCL产生SCL是主控产生的频率由计数器分频得到。假设系统时钟50MHz目标SCL 100kHz那么半个SCL周期就是250个系统时钟。计数到249就翻转一次SCL这样上下沿对称占空比50%。localparam DIV_HALF 8d249; // 50MHz / (2 * 100kHz) 250 reg [7:0] div_cnt; reg scl_r; always (posedge clk or negedge rst_n) begin if (!rst_n) begin div_cnt 8d0; scl_r 1b1; end else if (div_cnt DIV_HALF) begin div_cnt 8d0; scl_r ~scl_r; // 产生100kHz方波 end else begin div_cnt div_cnt 1b1; end end实际用的时候不要直接拿这个分频时钟去驱动逻辑而是把它当作节拍使能用一个tick信号去驱动状态机。用门控时钟在FPGA里是忌讳容易出时序问题。5.3 SDA双向端口的处理SDA是双向线Verilog里用inout声明但内部必须拆成三部分输出值sda_out、输出使能sda_oe、输入值sda_in。写法是assign sda sda_oe ? sda_out : 1bz; assign sda_in sda; // 始终回读总线电平发数据的时候sda_oe为1驱动总线读数据或者等待ACK的时候sda_oe为0让外部上拉把线拉高同时从sda_in读从机的响应。这个释放的动作经常被忘掉结果主机一直强驱高电平从机想拉低表示ACK都拉不下来读到的永远是1表现为死活收不到ACK。5.4 一个可参考的字节发送状态机下面把字节发送和ACK采样的核心部分写出来结构上分成节拍计数、位计数和状态转移三块localparam S_IDLE 3d0, S_START 3d1, S_SEND 3d2, S_ACK 3d3, S_STOP 3d4; reg [2:0] state, next_state; reg [2:0] bit_cnt; reg [7:0] shift_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; scl_r 1b1; sda_out 1b1; sda_oe 1b1; bit_cnt 3d0; end else begin case (state) S_START: begin sda_oe 1b1; if (tick) begin sda_out 1b0; /* SCL高时SDA下降沿 */ next_state S_SEND; end end S_SEND: begin if (tick) begin if (scl_r 1b0) begin sda_out shift_reg[7]; /* SCL低时更新数据 */ shift_reg {shift_reg[6:0], 1b0}; end else begin if (bit_cnt 3d7) begin bit_cnt 3d0; sda_oe 1b0; /* 释放SDA读ACK */ next_state S_ACK; end else begin bit_cnt bit_cnt 1b1; end end end end S_ACK: begin if (tick scl_r 1b1) begin ack_flag sda_in; /* 0为ACK */ sda_oe 1b1; next_state S_STOP; end end S_STOP: begin if (tick) begin sda_out 1b0; if (scl_r 1b1) begin sda_out 1b1; /* SCL高时SDA上升沿 */ next_state S_IDLE; end end end default: ; endcase state next_state; end end这段是骨架实际工程里还要补上超时计数、NACK中止、读事务的重复起始处理。重点是理解一个原则所有SDA的变化都必须发生在SCL低电平期间唯一的例外就是起始和停止条件。写Verilog的时候把这条规则刻在脑子里状态机就不会乱。5.5 上板调试的顺序和方法FPGA的I2C不像单片机能打断点出问题只能看波形。我的调试顺序一般是这样的第一步先不接EEPROM用示波器或者逻辑分析仪单独看SCL确认频率、占空比、上升沿形态对不对。SCL没出来后面全白搭。第二步接上EEPROM只测写一个字节。抓SCL和SDA的波形一个时钟一个时钟地对着时序图看起始条件、器件地址、字地址、数据、ACK、停止逐段核对。这段最花时间但也最锻炼人。第三步用FPGA厂商自带的在线逻辑分析仪比如Intel的SignalTap或者Xilinx的ILA把状态机的状态、位计数、ACK标志都加进采样列表。这样能看到卡在哪个状态比单纯看总线波形效率高得多。第四步验证ACK标志。如果ACK一直是1NACK先检查器件地址算得对不对再检查SDA的上拉电阻和释放逻辑最后检查从机是不是在写周期里没响应——这时候加个延时或者做ACK轮询就好了。6. 常见问题与排查技巧实录这一节我把这些年真正踩过的坑整理出来做成一张速查表后面再补充几条排查思路。很多问题不是代码写错了而是对器件行为的假设错了看一眼表格能省下大半天时间。6.1 高频问题速查表现象可能原因排查与解决读出来全是0xFF器件没应答或未选中查器件地址A2A1A0、查WP引脚、用示波器看ACK读出来全是0x00SDA被持续拉低检查输出使能是否一直有效、检查短路写进去再读出来是旧值写周期没等完就发起读加应答轮询或按手册最大写周期加延时连写多字节后数据错位跨页回卷按页边界拆分写入每页单独起一次事务偶尔写失败重启后正常总线被干扰拉死加超时检测异常时手动发9个时钟恢复总线用一段时间后某地址写不进单字节被写穿引入环形缓冲做磨损均衡5V器件挂3.3V总线读不到电平不匹配加电平转换或选3.3V型号高温环境数据丢失数据保持能力降额提高校验强度重要数据双备份6.2 总线拉死怎么救I2C有个经典故障主机在发数据途中被复位SDA被从机拉低主机重新初始化后看到的SDA一直是低判定总线忙再也不敢发起始条件。这种情况在带看门狗的系统里特别容易发生。救的办法是手动恢复把SCL配成普通输出连续发9个时钟脉冲让从机把没发完的位全部吐完它自然就释放SDA了然后自己产生一个停止条件总线回到空闲。这段恢复代码建议写进初始化函数里开机先跑一遍能解决相当一部分设备偶发不通信的现场问题。还有一个预防手段就是所有I2C操作都加超时。我曾经写过一个驱动没有超时保护某次从机因为供电异常没响应主机在while循环里死等ACK整个看门狗都被喂饱了系统假死。后来统一加了超时计数器超时就发停止条件并返回错误码稳定性好了很多。6.3 几个文档上不写的经验第一写入频率要往上报不要往少算。做寿命评估的时候把用户可能会连续点几次保存这种场景算进去实际写入次数往往是理论值的几倍。第二参数区的第一件事是先写一个魔数。一个固定标识加版本号加校验读的时候第一关就看魔数对不上直接判定为空片走默认参数初始化流程。这能避免把随机数据当参数用。第三标定数据这种写一次就不改的内容生产环节写完以后把WP拉高焊死比任何软件保护都可靠。第四调试阶段串口打印非常有用把每一步的ACK、读回来的字节都打出来即使总线波形看不清也能大致判断问题在哪一环。6.4 一个容易忽略的时序陷阱最后说一个我调了整整一天才找到的问题。用软件模拟I2C的时候如果主循环里有中断而且中断服务函数执行时间比较长恰好打断了SCL高电平期间从机就会认为时钟被拉长了。标准I2C从机本来支持时钟同步问题不大但有些器件对最长高电平时间有限制超过就会复位内部状态机。表现就是大部分时候读写正常偶尔抽风。解决办法有两个要么在I2C的临界段关中断把整个字节传输保护起来代价是中断延迟变大要么把硬件I2C用起来把时序交给外设。我现在的习惯是只要系统里有中断软件模拟的I2C就一定要做临界段保护哪怕会牺牲一点实时性稳定性优先。写到这儿关于EEPROM从存储原理到选型、从单片机驱动到FPGA的Verilog实现基本就串起来了。我个人在实际项目里的体会是这东西难的不是协议而是对边界条件的敬畏页边界、写周期、寿命、掉电时刻、总线异常每一个都对应一类现场故障。把这些边界都处理掉一颗几毛钱的EEPROM能安安稳稳陪你跑十年处理不掉它就会在某个客户现场突然给你上一课。
返回列表