两引脚自供电串行EEPROM:1-Wire总线如何让IoT存储省掉电源脚

📅 2026/8/27 19:15:48
两引脚自供电串行EEPROM:1-Wire总线如何让IoT存储省掉电源脚
做物联网设备这些年我对非易失性存储的要求越来越“抠门”容量不需要多大掉电不能丢数据最好引脚少、功耗低、布线简单。后来在几批电池供电的传感器节点上我换上了两引脚自供电串行EEPROM——系统里只占一根数据线和一根地线连独立的电源脚都省了靠1-Wire总线的寄生供电机制从数据线上取能量。这篇文章就把这个方案掰开揉碎讲清楚包括工作原理、驱动代码、FPGA实现思路和几个必须避开的坑适合正在给IoT设备选存储方案或者想折腾1-Wire接口的朋友参考。1. 为什么IoT设备会需要这种“小存储”1.1 一片EEPROM在物联网设备里到底存什么很多人一听到EEPROM第一反应是“老古董”单片机早就内置了。但放到物联网设备里外部小容量非易失存储依然是刚需。我手上大部分节点的存储内容并不是固件本身而是三类东西一是身份信息比如设备ID、MAC地址后几位、站点编号二是配置参数比如校准系数、采样阈值、上报周期三是运行状态比如累计运行时间、故障计次、OTA版本号和回滚标志。这些数据有三个共同点量不大几百字节足够需要频繁或不频繁地改断电之后必须留着。Flash不是不能干这些事但用起来别扭。内部Flash通常要按扇区擦除写一个字节可能要把整个扇区读出来改完再写回去麻烦且容易出错。EEPROM的好处是按字节擦写某个参数变了就改那一个地址不用管旁边的数据。而且在掉电保存场景里EEPROM的写功耗比Flash低时序上更容易在小电容后备电源下完成一次保存动作。很多经验丰富的硬件工程师会在MCU外面再放一颗小EEPROM不是因为MCU里没有而是为了把“业务数据”和“程序存储”彻底分开换MCU型号时迁移成本也低。1.2 EEPROM和Flash的本质区别在哪里从存储原理上讲EEPROM和Flash都属于浮栅晶体管存储器都是通过往浮栅里注入电子或把电子拉出来来表示0和1。但两者的阵列结构和操作方式完全不同。Flash为了提高密度把很多单元共享同一根擦除线做不到按字节擦除必须整片或整扇区操作。EEPROM每个字节都单独引出了控制栅和擦除栅电路更复杂、密度低、成本高却换来了灵活的字节操作能力。还有一个关键差异是寿命和功耗。一般串行EEPROM的擦写寿命是10万次到100万次相比普通Flash的1万次左右要高一个数量级。IoT设备里有些参数可能每次开机都写一天几百次一年下来就是几十万次如果塞在Flash里很快就磨穿了。功耗方面EEPROM的读取电流通常是微安级写入时也不过毫安级对两节电池供电的设备很友好。所以如果你要做的设备有大量小数据、需要频繁改写外部串行EEPROM依然是“看起来老但实际最稳”的选择。1.3 两个引脚能解决什么现实痛点在物联网产品设计中MCU的通用引脚永远不会嫌多。一个I2C接口的EEPROM占SCL和SDA两根线还需要共地SPI接口更夸张至少四根线。对于PCB面积紧张、或者只有一个GPIO可以牺牲的小型传感器模组来说少一根线就意味着少一个过孔、少一段走线、少一份干扰风险。两引脚自供电方案在系统层面只占一根GPIO和地线省掉电源脚之后周边电路也简化了不需要单独放退耦电容数据线上固有的寄生电容已经能充当小容量储能不需要电源管理芯片给存储芯片单独供电甚至可以让存储器件跨越不同供电域只要主控IO电平合适就行。这不是理论上的噱头而是实际产品里能省真金白银的改动。批量生产时少一个器件、少几条走线可靠性和良率都能提升。而且这类器件的封装通常很小适合贴放在板边或传感器探头上读取距离比I2C更有优势。当然它也有不少限制比如速率不高、不适合大块数据搬运这个后面展开讲。2. 两引脚自供电串行EEPROM的工作原理拆解2.1 两个引脚到底从哪来我这里说的“两引脚”指的是在系统上只占用两个连接点一根数据线和一根地线。市面上典型的器件方案采用1-Wire接口数据引脚同时承担通信和供电两个任务。数据线在空闲时被外部上拉到高电平器件内部有整流和充电电路利用高电平期间给片上小电容充电主机把数据线拉低时器件靠电容里存的一点能量维持逻辑和内部状态。真正工作时唯一的外部连接就是数据IO和GND芯片自带的VDD脚可以悬空或者干脆在封装里不引出。这种设计最典型的代表是Maxim/Dallas的1-Wire EEPROM系列比如DS2431。这颗器件逻辑上就两个关键脚DQ数据和GND。封装会留出NC或者VDD等额外引脚主要是为了兼容自动贴片和PCB布线但核心电路完全可以在两线制下运行。很多人第一次看引脚表会懵VDD不接真的能写数据吗能但这恰恰是整个方案最精妙的地方它把“电源”这个概念抽象成了总线电平器件不再从固定电源轨取电而是从通信总线里“回收”能量。2.2 寄生供电是如何工作的要理解自供电就得先理解1-Wire总线的空闲状态。1-Wire总线外部必须接一个上拉电阻通常4.7kΩ从主控电源拉到数据线上。总线上没有数据时电阻把数据线拉高到3.3V或5V挂在这根线上的器件如果内部有储能电容就会被充电。当主机开始通信时数据线被反复拉低和释放虽然低电平期间没有能量供给但之前电容里存储的电荷足以支撑器件在低电平期间的逻辑操作和RAM暂存。这个思路有点像给手机做无线充电手机里先存一点电然后一边通信一边用存下来的电工作。EEPROM芯片内部会做电压校检和上电复位只有总线电压稳定在一定阈值以上内部逻辑才允许执行读操作写操作更讲究因为需要高压脉冲把电子注入浮栅所以写入前必须保证总线高电平时间足够长让储能电容充到足以完成一次擦写周期的水平。这就是为什么许多1-Wire EEPROM的写周期比普通I2C芯片慢写一次要几毫秒甚至几十毫秒它需要把能量“攒够了”再干活。如果主机侧的MCU使用推挽输出而不是开漏输出那事情就麻烦了。推挽输出会在高电平时强行拉高低电平时强行拉低虽然也能临时供电但反应太快、驱动太强容易把总线上的电容尖峰搞得很大甚至超过芯片绝对最大额定值。所以我通常建议配这种芯片的主控GPIO一定要设置为开漏模式配合外部上拉电阻使用。这样总线电平由电阻决定下拉由MCU控制时序更干净也更接近官方手册的参考设计。2.3 存储单元与寿命的底层逻辑讲了供电还得讲一下EEPROM存储单元本身。EEPROM的每个位都是一个带浮栅的MOS管浮栅被绝缘层完全包裹正常状态下没有电荷通路可以保存电荷很多年。写入0时在控制栅和漏极之间加一个高压让电子通过隧穿氧化层注入浮栅相当于把电荷关进了一个绝缘牢笼写入1或擦除时反过来用相反方向的高压把电子拽出来。因为每次操作都要对氧化层施加高压氧化层会逐渐损伤这就是EEPROM寿命的来源。1-Wire EEPROM和普通I2C EEPROM在存储单元上没有本质区别但它更强调能量效率。写入时芯片内部要临时产生编程高压这个高压源由储能电容供电。如果电容充得不够编程电压达不到阈值写入就会失败或者数据不完整。因此实操中有一个硬经验写完一个字节一定要等待足够的内部写时间通常数据手册给的是10ms级别不能跟I2C那样写完立刻读否则读回来的很可能是上一个旧值或者中间态。如果你发现写后回读偶尔出错第一件事不是怀疑芯片坏了而是把写后的延时拉长一倍试试。3. 实操在主控上驱动两引脚EEPROM3.1 硬件连接最小系统与注意事项先说连接方式。数据IO接主控的一个GPIO这个GPIO必须支持开漏输出和输入读取地线直接和主控共地数据线上到电源之间接一只4.7kΩ上拉电阻。如果你用3.3V系统4.7kΩ没问题如果用5V系统也可以保持4.7kΩ只要芯片支持宽压就行。很多人会问要不要在IO脚再放一个小电容滤波我建议不要特地加因为1-Wire总线的时序对上升沿要求比较宽松但过大的线下电容会导致高电平还没充到阈值就被拉低通信失败。线缆越长对充电时间和上拉电阻的要求越高长距离传输时上拉电阻可以适当减小到2.2kΩ。调试时最好在IO引脚上留一个测试点方便示波器或逻辑分析仪夹住观察波形。我踩过一个坑用普通杜邦线飞线时线间的耦合电容太大导致写操作偶尔失败一开始怎么调代码都没用。后来用示波器一看下降沿有振铃上升沿又慢得离谱把上拉电阻从4.7kΩ换成2.2kΩ问题立刻消失了。所以这类低速总线的硬件调整往往比代码更重要。3.2 1-Wire时序详解复位、存在脉冲、读写时隙驱动1-Wire器件的基础是四个动作复位脉冲、存在检测、写时隙、读时隙。理解这四个动作就相当于学会了和芯片“对话”的节奏。复位脉冲由主机发起主机先把总线拉低至少480μs然后释放。释放后主机释放总线进入高阻状态总线被上拉电阻拉高。挂在总线上的EEPROM检测到这个低电平脉冲后会在60μs左右拉低总线一段时间形成一个“存在脉冲”。主机在释放后等待60-120μs再采样读到低电平就说明有设备应答。写时隙和读时隙是1-Wire的核心。写0时隙主机拉低总线60-120μs然后释放写1时隙主机只拉低1-15μs就释放让总线在高电平状态维持到整个时隙结束。读时隙类似写1主机拉低1-15μs后释放然后在释放后的15μs内采样总线电平读到的就是器件输出的位。每个时隙之间至少要留一个恢复时间通常建议整个时隙周期保持60μs以上保证总线稳定。可以这样记1-Wire的“写1”和“读”从波形上看几乎一样都是低脉冲后释放区别只在主机释放后有没有采样而“写0”就是长时间拉低。所有命令和数据的字节都按这个位时序往总线上发先写低位还是高位要看手册一般是LSB first但最好以具体芯片手册为准。3.3 C语言参考实现可直接抄下面给一段基于STM32 HAL库风格的C代码用GPIO模拟1-Wire时序。因为不同编译器延时函数不同这里用delay_us占位实际使用时应换成你自己的微秒延时函数。#define OW_GPIO_GPIOx GPIOB #define OW_GPIO_PIN GPIO_PIN_6 // 缩写辅助函数 #define OW_OUT_LOW() HAL_GPIO_WritePin(OW_GPIO_GPIOx, OW_GPIO_PIN, GPIO_PIN_RESET) #define OW_OUT_HIGH() HAL_GPIO_WritePin(OW_GPIO_GPIOx, OW_GPIO_PIN, GPIO_PIN_SET) #define OW_IN_READ() HAL_GPIO_ReadPin(OW_GPIO_GPIOx, OW_GPIO_PIN) static void OW_Set_Output(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin OW_GPIO_PIN; gpio.Mode GPIO_MODE_OUTPUT_OD; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(OW_GPIO_GPIOx, gpio); } static void OW_Set_Input(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin OW_GPIO_PIN; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_NOPULL; HAL_GPIO_Init(OW_GPIO_GPIOx, gpio); } int OW_Reset(void) { OW_Set_Output(); OW_OUT_LOW(); delay_us(600); // 复位脉冲 OW_Set_Input(); // 释放总线 delay_us(60); int presence (OW_IN_READ() 0); // 读到低电平表示存在 delay_us(480); // 等待总线恢复 return presence; } void OW_WriteBit(int bit) { OW_Set_Output(); if (bit) { OW_OUT_LOW(); delay_us(6); OW_Set_Input(); // 释放 delay_us(55); } else { OW_OUT_LOW(); delay_us(60); OW_Set_Input(); delay_us(10); } } int OW_ReadBit(void) { OW_Set_Output(); OW_OUT_LOW(); delay_us(6); OW_Set_Input(); delay_us(9); int val OW_IN_READ(); delay_us(55); return val; } void OW_WriteByte(uint8_t data) { for (int i 0; i 8; i) { OW_WriteBit(data 0x01); data 1; // 先发低位 } } uint8_t OW_ReadByte(void) { uint8_t data 0; for (int i 0; i 8; i) { data 1; if (OW_ReadBit()) { data | 0x80; } } return data; }注意这段代码里的延时是“意思到位”的版本真正量产时需要用参数更精确的延时并用示波器校准。命令流程上通常先OW_Reset然后发跳过ROM命令0xCC再发具体的读写命令和地址。比如写一个字节到DS2431大概是复位 → 0xCC → 0x0D写暂存器命令→ 目标地址 → 数据 → 0x0F复制暂存器命令→ 等待写周期读则是复位 → 0xCC → 0x0F读暂存器命令→ 地址 → 数据。不同芯片命令不一样千万别照搬一定要去查对应芯片手册。3.4 用Verilog在FPGA上实现控制器时要注意什么不少工程师习惯在FPGA里用I2C读写EEPROM网上现成代码也多比如“i2c读写eeprom代码 verilog”一搜一大把。但遇到SPI Flash不够用、引脚又紧张的板卡时1-Wire EEPROM控制器用Verilog写起来其实也不难关键是把状态机理顺。我建议把控制器拆成几个状态IDLE、RESET_LOW、RESET_WAIT、RESET_SAMPLE、WRITE_BIT、READ_BIT、WAIT_RECOVERY、DONE。系统时钟可能几十MHz而1-Wire时序要到微秒级所以需要用计数器分频产生微秒基准。对外IO建议用三态逻辑输出使能时由FPGA驱动输出禁止时释放总线。这里给一个顶层接口示意module ow_eeprom_ctl ( input clk, input rst_n, input start, input write_en, input [7:0] wdata, output reg [7:0] rdata, output reg done, inout ow_io ); // 内部用计数器延时产生微秒级时序 // 状态机按位读/写完成一个字节后返回done endmodule写时序时先拉低ow_io并保持若干微秒然后释放再根据数据位决定保持低电平的时间读时序时拉低一小段时间后释放释放后再等一小段时间采样输入。由于FPGA并行特性状态机里要注意每个状态停留的时钟周期数最好用参数化常量定义方便在仿真时快速调节。仿真阶段不要只用模型验证逻辑建议配合行为级模型把完整读写流程跑一遍我通常会在testbench里模拟一个简单的从机响应检验主机状态机是否会在错误分支卡住。4. 把它用进IoT项目选型、部署和运维4.1 典型应用场景拆解两引脚自供电EEPROM的第一个典型应用是电池传感器节点。节点MCU睡眠时总线可以保持在高电平这种芯片几乎不耗电节点醒来时需要更新运行计数或校准参数主控拉一下GPIO就能读写。相比I2C器件它少了一个上拉电阻和可能的电源路径待机电流更容易压到微安级。第二个场景是资产标签和识别模块。因为1-Wire总线天然支持单主机多从机每颗EEPROM内部有唯一的64位ROM序列号只需要一根线就能挂多颗芯片在导轨式传感器、配电柜端子排这类场景里很有用。主机读取每个从机的64位序列号就知道是哪块板卡、哪个传感器不需要额外编址。第三个场景是OTA固件升级的“最后一道保险”。我们之前在AWS IoT上做过一版固件升级云端侧配置了严格的用户策略和授权角色但设备端也需要记录“当前运行的是哪个版本”“升级后第一次启动是否成功”。这些状态只有一两百字节但非常重要。放Flash里怕频繁改写浪费寿命放RAM里一断电就丢最后我选了外部两脚EEPROM保存版本号和回滚计数。升级前把新版本号先写进去升级失败后上电读出来就知道要回滚到哪个旧版本。云端策略负责“谁允许升级”端侧EEPROM负责“升级状态怎么恢复”两边配合起来整个OTA闭环才算完整。4.2 选型对照表两引脚自供电 vs I2C vs SPI不同的串行EEPROM各有各的适用场景。写了一块对照表方便你快速判断。对比项两引脚/1-Wire EEPROMI2C EEPROMSPI EEPROM系统占用引脚1根数据线地2根SCL/SDA地4根CS/SCLK/MOSI/MISO地通信速率低速典型15-100kbps400kbps/1MHz1MHz以上供电方式寄生供电无需独立电源脚需要VCC和GND需要VCC和GND功耗最低较低中等抗干扰单线多点较依赖上拉和布线较好最好典型容量几百字节到几Kb2Kb-2Mb很大适合场景电池设备、身份识别、参数存储通用中低速存储高速数据暂存从表里能看出来两引脚方案在“极简系统”里优势巨大但不是万能的。如果你要存几十KB的数据或者需要频繁大块搬运SPI EEPROM会更合适如果你的MCU只有标准I2C外设不模拟时序那I2C方案开发成本最低。选型本质上是围绕“系统里到底有几根线可以分给存储芯片”来做的不要太迷信某一种接口。4.3 STC32G等MCU内置EEPROM与外置串行EEPROM的选择STC32G系列单片机内部有独立EEPROM区很多用户会觉得“既然内置了何必外挂”。实际项目里内置EEPROM有两个尴尬一是容量固定通常几KB想多存一点就不够二是它跟MCU是同一个器件MCU换批次、升级型号时内部存储映射和擦写时机可能变化代码要跟着改。外置串行EEPROM把存储独立出去硬件和驱动都更通用哪怕以后把单片机从STC32G换成STM32或ESP32存储部分基本不用动。另外内置EEPROM在程序频繁擦写时要小心占用CPU时间因为执行擦写期间CPU可能被卡住。外置EEPROM虽然也要等待写周期但主控可以边等边做别的事甚至通过定时器轮询整体调度更灵活。所以我的一般建议是产品原型阶段用内置EEPROM没问题但一旦进入批量或者要做多型号兼容给参数存储留一个外置接口是值得的。4.4 在BeagleBone Black等Linux板卡上的使用除了单片机很多IoT网关用的是BeagleBone Black这类Linux单板。BeagleBone Black本身板载了EEPROM用于记录cape扩展板的型号和配置系统启动时会自动读取。如果你要外接一个两引脚自供电EEPROM思路也简单选一个空闲GPIO配置成开漏加外部上拉然后在内核设备树里打开w1-gpio节点把该GPIO编号填进去。加载w1-gpio驱动后系统会在/sys/bus/w1/devices/目录下自动生成一个以64位ROM号命名的目录直接用echo/cat读写文件就能操作EEPROM。用Linux操作1-Wire器件时有个坑sysfs接口读写的是“位流”或者按块导出的属性文件不同芯片、不同驱动版本导出的文件名不一样比如有时是eeprom有时是rdata。建议先读一下/sys/bus/w1/devices/XX/下的目录内容确认实际导出的属性名再在程序里打开对应文件。还有w1-gpio驱动默认的弱上拉可能不适合远距离或大电容必要时要在GPIO外部并联一个更小的上拉电阻。总之Linux下挂这类器件很省事但一定要先看内核配置别指望每个内核版本都默认支持。5. 常见问题与排查技巧速查表5.1 典型问题汇总现象可能原因解决办法复位时检测不到存在脉冲总线电容过大充电太慢减小上拉电阻缩短线缆检查IO是否设为开漏写入后回读旧数据写等待时间不够将写周期延时增加到手册值的2倍以上偶发读写错误时序毛刺、GPIO驱动方式不正确用开漏输出检查上拉电阻用示波器观察边沿高温或长距离下数据不稳定外部干扰或供电电容不足加ESD保护缩短总线增大总线高电平恢复时间多设备挂载后地址冲突忘了先读64位ROM序列号每次操作前执行ROM搜索命令按ROM号寻址功耗异常增高总线一直被拉低检查代码里是否没有释放总线或外部器件短路这张表是我从几个项目里总结出来的。写操作不成功往往不是芯片问题而是“能量不够”只要总线高电平时间不足、上拉电阻太大、线缆太长都会导致储能电容充不满。尤其是使用电池供电的设备如果电池电压已经跌到临界值高电平可能连芯片的复位阈值都够不到这种情况换一颗新电池立刻就好容易误判成芯片损坏。5.2 我调了两天发现的几个小细节第一个细节GPIO推挽输出是头号杀手。我在一个样品上用标准推挽GPIO直接驱动结果总线上出现莫名的持续低电平芯片频繁复位。换成开漏模式并重新初始化GPIO后一切恢复正常。原因很简单推挽输出在拉高瞬间的驱动能力太强叠加寄生电感产生了振铃而这个振铃让芯片误判成总线命令。第二个细节存在脉冲采样时机不要死板。手册上写释放后等60μs再采样但我有一次因为延时函数不准确实际等了80多μs也没问题。关键在于采样点必须在器件输出存在脉冲的窗口内而不是越早越好。如果总线电容大、上升沿慢就多等一点如果总线很短很干净稍微提前采样也没关系。最好的做法是用逻辑分析仪抓一次完整复位波形看存在脉冲的实际位置再倒推采样延时。第三个细节写入前最好给总线“充满电”。我习惯在写命令之前故意让总线保持高电平几百微秒让储能电容充分充电然后再进入写时序。这个操作对写成功率提升非常明显尤其在电池电压偏低或者线缆偏长时。很多芯片手册里的参考例程也暗示了这一点写操作前需要等待充电时间。这是1-Wire EEPROM区别于I2C EEPROM的独特之处不了解的人很容易忽视。5.3 结合热词里的常见实战STC32G和BeagleBone Black如果你用的是STC32G想外挂这类芯片可以参考它的I/O配置将引脚设为双向口准双向或开漏外部加上拉然后照搬上面的C时序代码。STC32G的GPIO速度很快驱动时要注意由软件控制GPIO方向切换的延迟。另外STC32G的定时器精度很高建议用定时器中断做微秒延时而不是纯软件循环否则编译器优化等级一变时序就漂了。在BeagleBone Black上我之前给一块扩展板加了一颗DS2431存板卡型号设备树里配置好w1-gpio后开机就能在/sys/bus/w1/devices/下面看到设备文件。读取时用cat即可但写入要注意文件节点可能只支持块写不能像普通文件一样随机改单个字节。如果应用层需要频繁更新最稳的方法是读整个块到用户态修改后整体写回。还有一点Linux驱动的默认搜索策略可能比较慢如果只有一颗器件建议在设备树里把search_rom设为禁止直接按已知ROM号访问能省不少时间。调试工具方面我常用的是一台USB逻辑分析仪专门抓1-Wire波形如果现场没有逻辑分析仪也可以把总线信号接到USB转串口工具的一个输入线上配合串口监视软件看电平翻转不过精度有限只适合判断“有没有波形”。虚拟串口和蓝牙串口终端这类工具主要用来收主控日志不直接参与总线调试但可以在代码里把每个时隙的返回状态打印出来配合日志定位是哪一步卡住。最后再分享两个小经验第一点这类两引脚自供电EEPROM最适合做“少量关键数据的最后防线”。不要指望它当大容量Flash用读写速率有限协议也复杂但在电池设备里它带来的省引脚、低功耗和抗掉电优势非常显著。我个人选型时凡是设备需要保存的关键参数不超过1KB、而且对功耗敏感我第一反应就是拉一根GPIO上1-Wire EEPROM后续维修和升级时几乎没有硬件烦恼。第二点画PCB的时候一定要在数据线上留测试点最好靠近器件引脚。1-Wire时序虽然看起来简单但真正出问题时没有测试点就只能盲猜。后来我习惯在每一版板上都预留这个小焊盘成本几乎为零排查问题能省半天时间。这个方案后续还可以扩展如果一根线上要挂多颗器件记得先做ROM搜索把每颗芯片的64位序列号读到系统里再决定访问策略。