ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

AT24C02+1602LCD按键计数:从I2C时序到断电存储的完整方案

AT24C02+1602LCD按键计数:从I2C时序到断电存储的完整方案 简介一份基于89C51/89C52单片机的AT24C02读写应用资源面向51单片机学习者和电子设计入门者演示如何将按键次数写入AT24C02存储芯片再读出并显示在1602LCD液晶屏上。工程基于Keil5编写C语言程序配套Proteus 7.8仿真环境适合需要快速理解I2C通信、掉电保存与液晶显示结合的实践项目。压缩包共5个文件包含C源码、hex烧录文件、Proteus仿真工程dsn、原理图备份dbk与调试信息pwi整体仅39KB轻量易用。c文件为核心控制代码hex可直接烧录至单片机dsn文件可在Proteus中打开查看电路连接与运行效果。目前已有941人学习下载可用于课程设计、毕业设计或日常练手。资源内提供了完整工程与仿真读者可直观看到AT24C02的读写时序、1602LCD的驱动方法以及按键计数逻辑还能直接打开仿真验证效果并根据源码修改扩展功能。无论硬件实验还是纯仿真学习这套工程都能帮助夯实单片机外围接口编程基础。1. 设备计数不上电就丢AT24C02 存的就是这个“记忆”做嵌入式的人大概率遇到过这个场景样机装到现场跑了一周用户问“这按键到底按了多少次”你发现程序里counter跑得挺欢可一断电所有计数归零。次数这种东西存在 RAM 里等于没存。把按键次数写进 AT24C02再读出来用 1602LCD 显示是 51/STM32 学习板、智能工位计数器、考勤签到机、小家电寿命测试台上最常见的落地方案也是理解 I2C 从机通信、EEPROM 的页写与读改写机制最直接的一个练习。先说结论这个项目不难但坑不少。AT24C02 的 I2C 时序、写周期等待、页边界回卷、按键消抖与边沿检测、1602 的忙标志处理任何一环偷懒都能让你调半天。下面按“硬件连接 → I2C 读写 → 按键计数策略 → 调试验证”的顺序把整条链路拆开讲透每一段都有可以直接抄走的代码和参数说明。2. 先搞清 AT24C02 和 1602LCD 的硬件连接引脚不能凭感觉接2.1 AT24C02 的器件地址与 WP 引脚是第一个容易翻车的地方AT24C02 是 2Kbit256 字节的串行 EEPROM走 I2C 接口。它一共有三个地址引脚 A0、A1、A2用来决定器件在 I2C 总线上的从机地址。很多开发板上这三个引脚已经接地所以 7 位地址固定是0x50加上读写位后写地址是0xA0读地址是0xA1。但你如果自己搭板子必须确认这三个引脚的电平状态否则时序再对器件也永远不会应答。WP写保护引脚同样关键。WP 接高电平时整个芯片被写保护写操作会被静默忽略而读操作不受影响。常见做法是把 WP 直接接地但如果你设计的系统里有一段代码需要长期保护配置区不被误写可以考虑用 GPIO 控制 WP只在写操作的 5ms 窗口内拉低写完立刻拉高。这个做法在固件升级场景里很实用但这个项目里直接接地即可。SDA 和 SCL 需要接上拉电阻典型值是 4.7kΩ 到 10kΩ。如果你用的是现成开发板板上通常已经焊好但如果用面包板搭忘了上拉电阻会导致 I2C 通信随机失败——表现为主机发送地址后收不到 ACK或者偶尔正常偶尔超时。2.2 1602LCD 的接线重点在 RW 引脚和背光供电1602LCD 是 16 字符 × 2 行的字符型液晶模块数据总线有 8 位和 4 位两种模式。做按键计数显示这种数据量很小的项目建议直接用 4 位模式省 4 个 IO。需要接的引脚一共 6 个RS、RW、E、D4、D5、D6、D7。注意这里其实是 7 根线因为 4 位数据模式下 D0~D3 不接。RS 接高电平表示传输的是数据接低电平表示传输的是指令RW 接低电平表示写接高电平表示读。很多教程直接把 RW 接地只做写入这样也能工作但你就失去了读取忙标志BF的能力每次写指令和数据前只能靠固定延时。延时方案当然能用但有局限性——如果你把主频从 12MHz 改到 11.0592MHz或者换了更慢的 IO 操作库固定的延时裕量就会变紧。我一般会建议把 RW 接出来用读忙标志的方式替代延时稳定性和自适应性都更好。背光引脚A/K 或 LED/LED-串一个 10Ω 到 100Ω 的电阻接电源用来限流。1602 的工作电压通常是 5V如果主控是 3.3V 的 STM32背光可以接 5V但逻辑引脚需要确认是否容忍 5V——大多数 STM32 的 IO 不是 5V 容忍的需要电平转换或者用 3.3V 供电的 1602 模块。2.3 整体接线表照着对一遍再上电下面给出一组适用于 STM32F103 或 STC89C52 的典型接线参考具体引脚以你自己的板子丝印为准模块引脚连接到主控说明AT24C02SCLPB6I2C1_SCL接 4.7kΩ 上拉到 VCCAT24C02SDAPB7I2C1_SDA接 4.7kΩ 上拉到 VCCAT24C02A0/A1/A2GND从机地址定为 0x50AT24C02WPGND直接接地允许写入1602LCDRSPB0寄存器选择1602LCDRWPB1读/写选择1602LCDEPB2使能脉冲1602LCDD4~D7PB3~PB64 位数据线注意 PB6 同时用于 I2C SCL这里要避开上面表格里有一个非常隐蔽的坑PB6 是 I2C1 的 SCL 复用引脚同时也被我用在了 1602 的 D6 上——这显然是冲突的。实际接线时务必先查主控的引脚复用表把 I2C 引脚和 LCD 数据引脚错开。比如把 1602 的数据线挪到 PA0~PA3 或 PC0~PC3别从 PB 口一路顺下去。// 引脚定义以 STM32F103 为例使用标准外设库 #define LCD_RS_PORT GPIOA #define LCD_RS_PIN GPIO_Pin_0 #define LCD_RW_PORT GPIOA #define LCD_RW_PIN GPIO_Pin_1 #define LCD_E_PORT GPIOA #define LCD_E_PIN GPIO_Pin_2 #define LCD_D4_PORT GPIOA #define LCD_D4_PIN GPIO_Pin_3 #define LCD_D5_PORT GPIOA #define LCD_D5_PIN GPIO_Pin_4 #define LCD_D6_PORT GPIOA #define LCD_D6_PIN GPIO_Pin_5 #define LCD_D7_PORT GPIOA #define LCD_D7_PIN GPIO_Pin_6提示I2C 的 SCL/SDA 一定要查数据手册确认是否有硬件 I2C 外设可以复用。如果你用的是软件模拟 I2C任意两个普通 GPIO 都可以就不存在引脚冲突问题。3. I2C 读写 AT24C02 的核心代码地址和页写边界是分水岭3.1 为什么推荐软件模拟 I2C而不是硬件 I2CSTM32 的硬件 I2C 外设口碑有点两极分化。老款 F1 系列在总线错误恢复上确实有些麻烦加上寄存器配置复杂很多工程师包括我在做这种小项目时更倾向用 GPIO 模拟 I2C 时序。软件模拟的好处是引脚随意、时序可控、出问题容易用逻辑分析仪排查而且代码移植到任何平台都只需要改引脚定义。缺点是 CPU 占用高但在这种按键计数的场景下完全不是问题。I2C 的时序本质就是四件事起始条件、停止条件、发送字节并等待 ACK、接收字节并发送 ACK。只要这四件事的时序满足 AT24C02 数据手册里的参数要求就能稳定通信。// I2C 引脚宏定义与延时函数 #define I2C_SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_6) #define I2C_SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_6) #define I2C_SDA_H() GPIO_SetBits(GPIOB, GPIO_Pin_7) #define I2C_SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_7) #define I2C_SDA_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) // 延时函数I2C 时钟频率约 100kHz即每 bit 约 10us static void i2c_delay(void) { // 空循环延时约 5us 72MHz for (volatile int i 0; i 10; i); }3.2 按字节写和当前地址读最小可用的存取函数写一个字节到 AT24C02 需要三个步骤先发起始条件再发器件写地址0xA0接着发目标存储地址最后发数据字节然后停止。器件在接收到停止条件后进入内部写周期大约需要 5ms这个时间内芯片不响应任何命令必须等待。// 向 AT24C02 指定地址写入一个字节 // addr: 0~255, data: 待写入数据 // 返回值: 0 成功, 1 失败 uint8_t at24c02_write_byte(uint8_t addr, uint8_t data) { i2c_start(); // 发送器件写地址等待 ACK if (i2c_send_byte(0xA0) ! 0) { i2c_stop(); return 1; } // 发送片内存储地址 if (i2c_send_byte(addr) ! 0) { i2c_stop(); return 1; } // 发送数据字节 if (i2c_send_byte(data) ! 0) { i2c_stop(); return 1; } i2c_stop(); // 等待内部写周期完成 at24c02_wait_write_complete(); return 0; }at24c02_wait_write_complete()的实现方式是不断尝试发送器件写地址直到收到 ACK 为止。因为器件在内部写周期内对任何地址都不应答所以“发地址等 ACK”本身就是一个查询机制。注意用软件 I2C 做这个查询时要带超时机制防止死循环卡死系统。读一个字节相对简单先发一个伪写操作把片内地址指针定位然后重新发起始条件发送器件读地址0xA1最后读取一个字节并发送 NACK 表示结束。// 从 AT24C02 指定地址读取一个字节 uint8_t at24c02_read_byte(uint8_t addr) { uint8_t data 0xFF; i2c_start(); i2c_send_byte(0xA0); // 伪写定位地址 i2c_send_byte(addr); i2c_start(); // 重新起始 i2c_send_byte(0xA1); // 读地址 data i2c_receive_byte(); // 读一个字节 i2c_send_nack(); // 发送 NACK 结束 i2c_stop(); return data; }注意读操作最后必须发 NACK否则器件会认为主机还想继续读下一个地址的数据总线释放流程就会出现问题。这是新手最容易漏的一步。3.3 读改写是计数存档的关键不能只写一个字节按键次数的存档逻辑和普通数据写入有一个本质区别每次按键都需要“把原来的值读出来加一再写回去”。这个读改写read-modify-write操作天然存在一个时间窗口如果在“读出来”和“写回去”之间发生了两次按键第二次按键读到的还是旧值加一后写回计数就少了一次。解决这个问题的思路有两个层面。简单场景把读改写放在一个不可被中断打断的临界区内先关中断读、加、写、等待写完成再开中断。对于按键这种低频事件完全够用。但要注意AT24C02 的写周期最长 5ms关中断 5ms 对实时性要求高的系统可能不能接受。更稳妥的方案是按键只负责把计数递增并写入一个内存变量存档操作放到主循环的 10ms 周期任务里做配合一个“脏标记”判断是否需要写。这个方案不关中断也不丢数缺点是断电瞬间可能有最多 10ms 的数据未落盘。对于按键次数统计这种场景丢一次两次是能容忍的但对不允许丢数的场景就得用“掉电检测 超级电容”的方案了。3.4 页写跨边界回卷问题一次写超过 8 字节时必现AT24C02 有个页写特性一次写操作最多可以连续写 8 字节但如果跨过了页边界即从地址 7 写到 8或者从 15 写到 16依此类推数据并不会自动跨页连续写入而是会回卷到当前页的开头把之前写过的数据覆盖掉。// 向 AT24C02 写入一组数据自动处理页边界 // addr: 起始地址, buf: 数据缓冲区, len: 数据长度 void at24c02_write_page_safe(uint8_t addr, uint8_t *buf, uint16_t len) { while (len 0) { // 计算当前页剩余空间 uint8_t page_remain 8 - (addr % 8); uint8_t chunk (len page_remain) ? len : page_remain; i2c_start(); i2c_send_byte(0xA0); i2c_send_byte(addr); for (uint8_t i 0; i chunk; i) { i2c_send_byte(buf[i]); } i2c_stop(); at24c02_wait_write_complete(); addr chunk; buf chunk; len - chunk; } }上面的代码先把“当前地址所在页还剩多少字节”算出来每次最多写到页边界就停等写周期结束后再继续。注意addr % 8的求余运算在单片机上有一定的开销但按键计数场景下不是热点路径无所谓。这个函数用在你需要把“自上次清零以来的累积数据”批量写入时很有用比如每分钟写一条带时间戳的记录到 EEPROM做历史曲线回放。4. 按键计数与消抖设计边沿触发和“一直按着只算一次”是核心诉求4.1 按键为什么必须消抖机械抖动的时间尺度是多少机械按键按下和释放的瞬间触点会因为弹片的物理形变产生 5~20ms 的抖动表现为一连串的高频通断信号。如果直接把 GPIO 的电平变化当成有效触发一次物理按动会被识别成多次触发计数会明显偏大。热词里的“按键一直按下只触发一次程序”说的就是这个场景——长按或者按住不松手时程序必须只计一次数。最常用的消抖方案是“延时确认法”检测到电平变化后延时 10~20ms再次读取电平如果和第一次相同就认定为有效跳变。这个方案实现简单缺点是延时期间 CPU 是阻塞的如果系统还要处理其他任务就不合适了。进阶方案是用定时器扫描按键状态每 5ms 扫描一次按键连续读到 3 次相同状态才确认电平切换这样既消抖又不用阻塞。4.2 用状态机实现单次触发长按不连发实现“按一下只计一次数”的关键是检测“释放”而不是检测“按下”。如果按下时触发一次那么抖动导致的电平反复会被消抖处理掉但如果在消抖后仍然检测到低电平就触发长按时会连续触发。状态机思路是把这个过程拆成两个状态松开态和按下态。只有从“松开态”迁移到“按下态”时才计数在“按下态”内无论电平怎么变化只要没恢复高电平并再次确认都不计数。// 按键状态机边沿检测核心逻辑 // 返回值: 1 表示一次有效按键, 0 表示无事件 uint8_t key_scan_edge(void) { static uint8_t key_state KEY_RELEASED; static uint8_t key_confirm 0; uint8_t key_level GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN); uint8_t event 0; // 消抖确认连续多次读到相同电平才认为稳定 if (key_level key_confirm) { switch (key_state) { case KEY_RELEASED: // 在松开态读到稳定低电平说明按下触发一次计数 if (key_level KEY_PRESSED_LEVEL) { key_state KEY_PRESSED; event 1; // 只在状态迁移点产生事件 } break; case KEY_PRESSED: // 在按下态读到稳定高电平说明释放回到松开态 if (key_level KEY_RELEASED_LEVEL) { key_state KEY_RELEASED; } break; } } else { key_confirm key_level; // 电平还在抖动继续跟踪 } return event; }这段代码里的key_confirm变量做得是“最近一次读取电平”key_state记录的是当前的稳定状态。每次扫描都做一次比较只有连续两次读到相同电平时才认为状态稳定。这样 5ms 扫描一次10ms 的抖动刚好能被过滤掉。定时器中断里每 5ms 调用一次key_scan_edge()返回值放进一个队列主循环处理计数。注意按键扫描中断里不要直接操作 I2C。I2C 时序对延时敏感中断里插入 I2C 通信会让时序被高优先级中断打断可能导致 SCL 高电平期间 SDA 变化造成通信错误。正确做法是中断里只做状态机把“需要加一”的标记置位主循环处理计数和 EEPROM 写入。4.3 短路保护按键的 GPIO 也需要防护热词里有“按键保护电路”和“按键电路”这两个查询说明很多人调不通按键不是因为逻辑代码而是硬件上缺少基本的防护。独立按键一端接 GPIO另一端接地GPIO 内部需要启用上拉电阻。如果按键引线较长或者使用环境有静电风险建议在 GPIO 对地并联一个 100nF 的电容和消抖配合使用硬件上把高频抖动先滤掉一层软件消抖压力小很多。另外按键引线的在线长度也是独立的因素。如果按键和主板之间的连接线超过 20cm建议不要用普通杜邦线而是用双绞线或者带屏蔽的线缆否则在电机、继电器等电磁干扰源附近GPIO 上会感应出尖峰噪声直接导致按键次数乱跳。这种问题靠软件消抖很难彻底解决必须在硬件上处理。5. 上电读出并显示到 1602LCD存档地址规划和初始化顺序别搞反5.1 1602LCD 的 4 位模式初始化时序一步都不能少1602LCD 上电后有大约 15ms 的内部复位时间期间不能发送任何命令。我见过不少人在这个时序上翻车上电后立刻发初始化命令结果显示屏上全是乱码。标准做法是上电后延时 40ms 以上然后再依次发送初始化序列。// 1602LCD 4 位模式初始化 void lcd_init(void) { delay_ms(50); // 等待模块内部复位完成 // 设置为 8 位模式前三次都是 0x30 lcd_write_cmd_4bit(0x03); delay_ms(5); lcd_write_cmd_4bit(0x03); delay_ms(5); lcd_write_cmd_4bit(0x03); delay_ms(5); // 切换为 4 位模式 lcd_write_cmd_4bit(0x02); delay_ms(2); // 以下指令都以 4 位模式发送完整命令 lcd_write_cmd(0x28); // 4 位模式2 行5x8 点阵 lcd_write_cmd(0x08); // 显示关闭 lcd_write_cmd(0x01); // 清屏 lcd_write_cmd(0x06); // 地址指针自动加一写入后光标右移 lcd_write_cmd(0x0C); // 显示开启光标关闭不闪烁 }lcd_write_cmd_4bit(0x03)发送的是“高 4 位”即一次只往数据线上送命令字节的高 4 位低 4 位不管。前三次发 0x03 是让模块无论当前处于什么状态都恢复到 8 位模式然后发 0x02 切到 4 位模式。如果跳过前三次的 0x03 直接发 0x28模块会因为在错误的总线宽度下工作而无法正确解析指令显示自然不对。5.2 存储区规划地址 0x00 放计数主值地址 0x01 放备份AT24C02 有 256 字节存一个按键次数uint32_t最大 42 亿次只需要 4 字节。但如果你想做到“存储区域稳健”就不能只写一份。EEPROM 有一个物理特性每个字节的擦写寿命是 100 万次左右如果你的系统每秒按一次键4 字节的同一地址一天要写 86400 次11 天就逼近寿命上限了。简单做法把计数用 4 字节存储在主地址 0x00~0x03备份到 0x04~0x07。每次写入时先写主区再写备份区。上电读取时先读主区并校验值范围如果主区数据全是 0xFFFFFFFF 或 0x00000000典型擦除态或损坏态就尝试读备份区。这个策略不是最完善的但能应对大部分写中断导致的数据损坏场景。另一种做法是磨损均衡wear leveling把 256 字节全部分成 32 个槽位每槽 8 字节循环写入同时用一个 2 字节的计数器记录当前写到哪个槽了。这样总寿命放大了 32 倍。对于工业计数器应用这个方案在可靠性上明显更优但代码会增加几十行本项目的复杂度下可以不做但关于磨损的概念应该留着等以后产品化时用得上。5.3 启动流程拼接读 EEPROM → 显示初值 → 主循环响应按键键计数的完整启动流程其实有个顺序问题。如果你先把 1602 初始化了再去读 EEPROM用户看到的黑屏时间会比先读 EEPROM 再初始化 LCD 要长几十毫秒体验不明显但确实有。更重要的是EEPROM 的读操作不应放在 LCD 初始化之前因为在 LCD 还没有初始化的阶段IO 口处于默认状态如果此时 EEPROM 的 SDA 引脚被复用或者存在上拉冲突读出的数据可能不稳。推荐的顺序是先初始化 EEPROM 的 I2C 引脚和 LCD 的 GPIO 引脚 → 读 EEPROM 中的按键次数 → 初始化 1602LCD → 显示字符串 → 进入主循环。注意“初始化引脚”和“初始化显示器”是两回事引脚上电时就可以配好显示器的内部状态机要按 5.1 节的时序来。int main(void) { uint32_t press_count 0; // 1. 初始化 GPIO 和 I2C 引脚 gpio_init(); i2c_gpio_init(); // 2. 从 AT24C02 读取上次掉电前的按键次数 press_count eeprom_read_counter(0x00); // 3. 初始化 1602LCD 并显示 lcd_init(); lcd_show_count(press_count); // 4. 主循环扫描按键更新计数异步存档 while (1) { if (key_scan_edge() 1) { press_count; lcd_show_count(press_count); eeprom_write_flag 1; // 置脏标记 } // 每 50ms 检查一次是否需要写入 EEPROM if (eeprom_write_flag (get_tick() - last_save_tick 50)) { eeprom_write_counter(0x00, press_count); eeprom_write_flag 0; last_save_tick get_tick(); } delay_ms(5); // 主循环节拍与按键扫描定时器错开 } }这段代码里按键扫描写在主循环里每 5ms 轮询一次等效于前文状态机代码的调用方式。实际项目中如果把按键扫描放在定时器中断里主循环也可以保持同样的结构只需要把key_scan_edge()的返回值换成从队列里取。存档的 50ms 延零加上 EEPROM 写周期 5ms最大掉电窗口是 55ms 左右。如果你担心这个窗口丢数就进入临界区或使用掉电检测电路。6. 一种更可靠的验证手段写时间戳按“读回比对”确认链路完整6.1 用两个存储地址配合自检区分“没写进去”和“没读出来”这个项目的调试阶段最让人头疼的问题就是1602 上显示的数字不变化到底是因为按键消抖没通过、计数变量没递增还是 EEPROM 写入失败、LCD 显示逻辑出错三层问题叠加在一起靠肉眼观察很难定位。我的做法是在完成基本功能后专门写一个“自检模式”每次按键都在 EEPROM 的固定地址写当前系统运行时间戳毫秒下一次按键时先读上次的时间戳再写新的。如果两次时间戳差值正常递增说明按键、计数、EEPROM 写入、读出、LCD 显示整条链路都是通的如果时间戳老是不变问题一定在按键或写入环节。时间戳方案还有一个额外的好处它可以用来验证 EEPROM 的擦写寿命底数。对同一个地址按循环压测一万次写入再读出校验用统计数据判断当前芯片是否存在坏块或位翻转问题。注意 AT24C02 是 EEPROM不是 FRAM写周期是 5ms一万次写大约要 50 秒压测时要有耐心。// 记录带时间戳的自检数据 // 返回 0 表示上一次的数据读回正确1 表示数据不一致 uint8_t eeprom_selftest(uint32_t timestamp_ms) { uint8_t buf[6]; uint8_t readback[6]; // 布局: [0..1]标志字, [2..5]毫秒时间戳 buf[0] 0x5A; buf[1] 0xA5; buf[2] (timestamp_ms 24) 0xFF; buf[3] (timestamp_ms 16) 0xFF; buf[4] (timestamp_ms 8) 0xFF; buf[5] timestamp_ms 0xFF; // 写入后立即读回比对 at24c02_write_bytes(0x40, buf, 6); at24c02_read_bytes(0x40, readback, 6); for (int i 0; i 6; i) { if (buf[i] ! readback[i]) { return 1; // 不一致说明写入或读取链路有问题 } } return 0; }这段代码把时间戳存到地址0x40十进制 64避开了前 8 字节的计数主区也避开了常见的 EEPROM 首址。at24c02_write_bytes和at24c02_read_bytes是前文write_page_safe的多字节包装。调试完成后你可以在产品出厂前把这段自检代码保留在固件里用串口或一个隐藏按键组合触发方便售后远程排查“存储是否坏了”的问题。6.2 边界条件测试表按键数和存储地址的极限组合强中断和异常掉电的测试往往比功能验证更容易发现问题。下面是一个可以直接抄的验证清单测试场景操作方式预期结果常见失败原因按键消抖用信号发生器模拟 10ms 低频抖动或快速点按计数只加 1消抖时间不够或边沿检测逻辑错误断电保持计数到 100断电 5 秒后上电显示 100掉电瞬间主循环尚未存档页写跨边界从地址 7 连续写 4 字节写入正确且不回卷覆盖page_remain计算错误写周期等待连续快速写两个字节间隔 1ms第二次写成功没有等待 ACK器件忙导致的写入丢失地址冲突总线上同时挂两个 AT24C02A2 电平不同两个芯片都能独立读写从机地址未正确区分LCD 初始化反复上电 100 次每次都能正常显示无乱码上电延时不足或初始化序列错位以上清单里最容易踩的是“断电保持”这一条。很多人测试时是正常断电然后等几秒再上电结果发现计数丢失立刻就怀疑 EEPROM 坏了。实际上问题出在主循环的存档节拍上——你的程序可能每 50ms 才写一次断电瞬间正好落在两次存档之间自然会丢。这种丢数不是 EEPROM 的写入可靠性问题而是存档策略没有满足“实时性”要求。判断方法很简单把存档周期改到 10ms 再测如果丢数概率明显下降说明策略问题而不是硬件问题。如果还是丢就得考虑用掉电检测中断 储能电容来保证最后这一次写入的供电裕量。6.3 用“F1 键”思维简化操作热词里的按键切换在自检中的应用热搜词里有“F1 按键怎么切换”和“双手打字按键位置”这样无关嵌入式的内容但“按键切换”这个思路本身是值得做进这个项目的如果你有多个按键完全可以给系统增加一个模式切换能力。比如设计三个按键KEY_A负责计数KEY_B负责清零长按 3 秒清零并从 0 重新开始显存KEY_C负责在“显示计数 / 显示自检信息”两个界面之间切换。这个做法看似在扩功能实际上对调试很有用不需要改代码重新烧录就能在同一个板子上观察不同阶段的运行状态。按键切换配合 1602LCD 的显示比如第一行显示计数CNT: 000123第二行显示自检状态EEP: OK一眼就能看出系统在哪一层出了问题。如果你在 STM32 上做加了中断做按键检测也不怕长按因为中断里只置位标志模式切换在主循环完成不会因为长按导致反复触发。本文还有配套的精品资源点击获取
返回列表