STM32F103RB与DS28EC20 EEPROM的1-Wire通信实现 1. 项目背景与核心需求在嵌入式系统开发中用户设置和偏好的持久化存储是一个基础但关键的需求。STM32F103RB作为一款经典的Cortex-M3内核微控制器虽然内置了Flash存储器但直接使用Flash存储频繁变更的用户数据存在几个痛点Flash擦写寿命有限约1万次擦除操作必须以扇区为单位写入前需先擦除流程复杂意外断电可能导致数据损坏DS28EC20作为1-Wire接口的EEPROM芯片正好弥补了这些不足。它的主要特性包括20Kbit2560字节存储容量1-Wire接口仅需单线通信10万次擦写寿命数据保存期超过100年每个存储页可独立写入这种组合特别适合需要保存以下类型数据的应用场景用户界面配置亮度、音量等设备校准参数运行日志和状态标记网络连接凭证2. 硬件设计与接口连接2.1 器件选型对比在决定使用DS28EC20前我们对比了几种常见方案方案接口容量擦写寿命典型应用场景片内Flash并行64-512KB1万次固件存储AT24C系列I2C1-512KB100万次通用配置存储DS28EC201-Wire2.5KB10万次小数据量频繁更新FRAM (如FM24CL16B)I2C16KB1万亿次极高耐久性需求选择DS28EC20的关键考量是STM32F103RB的I2C接口可能已被其他外设占用项目只需要存储少量配置数据1KB1-Wire总线可简化PCB布线2.2 电路连接实现典型连接方式如下图所示文字描述STM32F103RB DS28EC20 GPIO_PA5 ------- DQ (数据线) GND ------- GND 4.7kΩ上拉电阻 (接3.3V)关键设计要点选择具有外部中断能力的GPIO如PA5必须添加4.7kΩ上拉电阻至3.3V总线长度建议小于30cm避免与高频信号线平行走线注意1-Wire总线对时序要求严格建议使用示波器验证信号质量特别是上升沿时间应在1μs以内。3. 1-Wire协议底层驱动实现3.1 基础时序控制1-Wire协议的核心是精确的时序控制。以下是使用STM32标准外设库实现的典型时序函数#define DS28EC20_DQ_PIN GPIO_Pin_5 #define DS28EC20_PORT GPIOA // 复位脉冲480μs低电平 uint8_t OW_Reset(void) { GPIO_InitTypeDef GPIO_InitStruct; // 配置为开漏输出 GPIO_InitStruct.GPIO_Pin DS28EC20_DQ_PIN; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_OD; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); // 拉低480μs GPIO_ResetBits(DS28EC20_PORT, DS28EC20_DQ_PIN); Delay_us(480); // 释放总线 GPIO_SetBits(DS28EC20_PORT, DS28EC20_DQ_PIN); Delay_us(70); // 切换为输入模式检测存在脉冲 GPIO_InitStruct.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); // 检测60-240μs内的低电平响应 if(!GPIO_ReadInputDataBit(DS28EC20_PORT, DS28EC20_DQ_PIN)) { Delay_us(400); // 等待存在脉冲结束 return 1; // 器件响应 } return 0; // 无器件响应 }3.2 读写字节实现写时序需要区分写1和写0void OW_WriteByte(uint8_t byte) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin DS28EC20_DQ_PIN; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_OD; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); for(uint8_t i0; i8; i) { GPIO_ResetBits(DS28EC20_PORT, DS28EC20_DQ_PIN); if(byte 0x01) { Delay_us(5); // 写1保持5μs低电平 GPIO_SetBits(DS28EC20_PORT, DS28EC20_DQ_PIN); Delay_us(60); // 总周期65μs } else { Delay_us(60); // 写0保持60μs低电平 GPIO_SetBits(DS28EC20_PORT, DS28EC20_DQ_PIN); Delay_us(5); // 总周期65μs } byte 1; } }读时序需要精确采样uint8_t OW_ReadByte(void) { uint8_t byte 0; GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin DS28EC20_DQ_PIN; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_OD; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); for(uint8_t i0; i8; i) { GPIO_ResetBits(DS28EC20_PORT, DS28EC20_DQ_PIN); Delay_us(2); // 拉低2μs GPIO_SetBits(DS28EC20_PORT, DS28EC20_DQ_PIN); // 切换为输入模式 GPIO_InitStruct.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); Delay_us(8); // 等待8μs后采样 if(GPIO_ReadInputDataBit(DS28EC20_PORT, DS28EC20_DQ_PIN)) { byte | (1i); } Delay_us(50); // 总周期60μs // 切换回输出模式 GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_OD; GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); } return byte; }实测经验在STM32F103RB72MHz下使用SysTick实现的微秒级延迟需要校准。建议通过GPIO翻转示波器测量实际延迟时间必要时插入NOP指令微调。4. DS28EC20高级操作与数据管理4.1 存储器架构解析DS28EC20的内部结构如下总容量2560字节分页80页×32字节/页额外1页页80用于特殊功能每页可独立写入典型命令集命令代码功能描述执行时间(典型)0x0F写暂存器5ms0x55复制暂存器到EEPROM10ms0xF0读存储器1ms0xCC跳过ROM匹配-4.2 数据写入流程优化安全写入流程应包含校验机制#define EEPROM_PAGE_SIZE 32 uint8_t EEPROM_WritePage(uint8_t page_num, uint8_t *data) { uint8_t scratchpad[EEPROM_PAGE_SIZE]; // 1. 发送写暂存器命令 OW_Reset(); OW_WriteByte(0xCC); // 跳过ROM OW_WriteByte(0x0F); // 写暂存器命令 OW_WriteByte(page_num); // 目标页地址 OW_WriteByte(0x00); // 页内偏移(0-31) // 2. 写入数据到暂存器 for(uint8_t i0; iEEPROM_PAGE_SIZE; i) { OW_WriteByte(data[i]); } // 3. 读回暂存器校验 OW_Reset(); OW_WriteByte(0xCC); OW_WriteByte(0xAA); // 读暂存器命令 uint8_t es OW_ReadByte(); // 状态字节 uint8_t ta1 OW_ReadByte(); // 目标地址 uint8_t ta2 OW_ReadByte(); // 保留 for(uint8_t i0; iEEPROM_PAGE_SIZE; i) { scratchpad[i] OW_ReadByte(); if(scratchpad[i] ! data[i]) { return 0; // 校验失败 } } // 4. 复制到EEPROM OW_Reset(); OW_WriteByte(0xCC); OW_WriteByte(0x55); // 复制命令 OW_WriteByte(es); // 必须匹配状态字节 Delay_ms(12); // 等待写入完成 return 1; // 成功 }4.3 磨损均衡策略虽然DS28EC20的擦写寿命较高但对频繁更新的数据仍建议实现简单的磨损均衡#define WEAR_LEVELING_PAGES 4 typedef struct { uint8_t data[28]; // 用户数据 uint16_t counter; // 更新计数器 uint8_t checksum; // 校验和 } UserConfig_t; uint8_t SaveConfig(UserConfig_t *config) { static uint8_t current_page 0; uint8_t buffer[32]; uint16_t max_counter 0; uint8_t target_page 0; // 计算校验和 config-checksum 0; for(uint8_t i0; isizeof(UserConfig_t)-1; i) { config-checksum ((uint8_t*)config)[i]; } // 查找最新有效页 for(uint8_t i0; iWEAR_LEVELING_PAGES; i) { EEPROM_ReadPage(i, buffer); UserConfig_t *stored (UserConfig_t*)buffer; if(ValidateConfig(stored) stored-counter max_counter) { max_counter stored-counter; target_page i; } } // 写入下一个可用页 target_page (target_page 1) % WEAR_LEVELING_PAGES; config-counter max_counter 1; memcpy(buffer, config, sizeof(UserConfig_t)); return EEPROM_WritePage(target_page, buffer); }5. 系统集成与性能优化5.1 与STM32 HAL的整合对于使用STM32CubeMX/HAL的项目建议封装为独立模块// ds28ec20.h #ifdef __cplusplus extern C { #endif typedef enum { OW_OK 0, OW_ERROR_NO_DEVICE, OW_ERROR_CRC, OW_ERROR_WRITE_VERIFY } OW_StatusTypeDef; OW_StatusTypeDef OW_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); OW_StatusTypeDef OW_Write(uint8_t page, uint8_t offset, uint8_t* data, uint8_t len); OW_StatusTypeDef OW_Read(uint8_t page, uint8_t offset, uint8_t* data, uint8_t len); #ifdef __cplusplus } #endif5.2 抗干扰设计在实际工业环境中1-Wire总线易受干扰可采取以下措施总线加屏蔽层在GPIO引脚添加TVS二极管实现软件CRC校验DS28EC20支持CRC8关键数据三重备份投票机制CRC校验示例uint8_t OW_CRC8(uint8_t *data, uint8_t len) { uint8_t crc 0; while(len--) { uint8_t inbyte *data; for(uint8_t i8; i; i--) { uint8_t mix (crc ^ inbyte) 0x01; crc 1; if(mix) crc ^ 0x8C; inbyte 1; } } return crc; }5.3 功耗管理技巧对于电池供电设备在两次访问间将GPIO设为模拟输入模式以降低功耗合并多次小数据写入为单次页写入实现数据变更队列仅在系统空闲时批量写入典型低功耗访问流程void LowPower_WriteConfig(void) { GPIO_InitTypeDef GPIO_InitStruct; // 唤醒总线 GPIO_InitStruct.Pin DS28EC20_DQ_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); // 正常通信 EEPROM_WritePage(...); // 返回低功耗状态 GPIO_InitStruct.Mode GPIO_MODE_ANALOG; HAL_GPIO_Init(DS28EC20_PORT, GPIO_InitStruct); }6. 调试技巧与常见问题6.1 典型故障排查表现象可能原因解决方案无器件响应上拉电阻过大/过小使用4.7kΩ±5%电阻数据校验失败时序不精确用示波器校准延迟函数偶尔写入成功电源噪声增加0.1μF去耦电容页写入超时未等待足够复制时间复制命令后延迟至少12ms多器件冲突ROM匹配不完整实现完整1-Wire搜索算法6.2 逻辑分析仪调试使用Saleae逻辑分析仪捕获的典型通信波形复位脉冲存在脉冲写0/1时序对比完整页写入时序建议捕获以下关键参数复位脉冲宽度480μs±10%存在脉冲响应时间60-240μs读采样点位置从下降沿开始15μs内6.3 实际项目经验在智能温控器项目中总结的几点经验避免在中断服务程序中执行EEPROM写入操作对关键参数采用写入新值→验证→标记生效三步流程上电时先读取所有配置到RAM减少运行时访问每月执行一次全页校验预防数据衰减void ConfigManager_Task(void) { static uint32_t last_check 0; if(HAL_GetTick() - last_check 30*24*3600*1000) { for(uint8_t i0; iCONFIG_PAGE_COUNT; i) { if(!ValidatePage(i)) { RestoreFromBackup(i); } } last_check HAL_GetTick(); } }