蓝桥杯嵌入式省赛第二场,我是如何用STM32G431搞定那个“坑人”的EEPROM的?
2026/4/25 12:42:45 网站建设 项目流程

蓝桥杯嵌入式竞赛实战:STM32G431的EEPROM深度优化指南

去年参加蓝桥杯嵌入式省赛时,那个看似简单的EEPROM读写功能差点让我翻车。当其他选手已经完成LCD界面和按键交互时,我还在为数据莫名其妙丢失的问题抓狂。这篇文章将分享我从崩溃边缘到完美实现的完整心路历程,特别是针对STM32G431这款芯片的EEPROM特殊处理技巧。

1. 硬件连接与初始化陷阱

第一次拿到开发板时,我理所当然地认为I2C接口就是PB6和PB7——毕竟大多数教程都这么写。但实际接线时发现根本检测不到设备,后来才明白蓝桥杯官方板子的特殊设计。

1.1 硬件I2C的替代方案

官方开发板通常有两种I2C连接方式:

  • 标准配置:PB6(SCL)、PB7(SDA)
  • 特殊配置:需要短路帽连接PA15(SCL)、PB7(SDA)

我采用的软件模拟I2C方案,核心代码如下:

// 软件I2C初始化 void I2C_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); // PB6作为SCL, PB7作为SDA GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始状态置高 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET); }

1.2 首次上电检测机制

比赛题目要求设备能区分首次上电和后续启动,我的解决方案是在EEPROM固定地址写入特定魔数(magic number):

#define MAGIC_NUM_1 0xAA #define MAGIC_NUM_2 0x55 #define MAGIC_ADDR 0x00 uint8_t check_first_boot(void) { uint8_t buf[2]; EEPROM_Read(buf, MAGIC_ADDR, 2); if(buf[0] == MAGIC_NUM_1 && buf[1] == MAGIC_NUM_2) { return 0; // 非首次启动 } else { buf[0] = MAGIC_NUM_1; buf[1] = MAGIC_NUM_2; EEPROM_Write(buf, MAGIC_ADDR, 2); return 1; // 首次启动 } }

2. 读写时序的魔鬼细节

在初赛测试时,我的程序随机出现数据损坏,经过三天排查才发现是EEPROM的时序问题。

2.1 必须遵守的延时规则

24C02系列EEPROM的典型时序要求:

操作类型最小延时(ms)建议延时(ms)
单字节写510
页写入515
读取操作01

实际代码中的延时处理:

void EEPROM_Write(uint8_t *data, uint8_t addr, uint8_t len) { I2C_Start(); I2C_SendByte(0xA0); I2C_WaitAck(); I2C_SendByte(addr); I2C_WaitAck(); for(int i=0; i<len; i++) { I2C_SendByte(data[i]); I2C_WaitAck(); } I2C_Stop(); HAL_Delay(10); // 关键延时! }

2.2 页写入的边界处理

24C02的页大小为8字节,跨页写入会导致数据回卷。我的解决方案:

void Safe_EEPROM_Write(uint8_t *data, uint8_t addr, uint8_t len) { while(len > 0) { uint8_t chunk_size = 8 - (addr % 8); chunk_size = (len < chunk_size) ? len : chunk_size; EEPROM_Write(data, addr, chunk_size); data += chunk_size; addr += chunk_size; len -= chunk_size; HAL_Delay(15); } }

3. 数据一致性的保障策略

在决赛现场,我遇到了更棘手的问题——突然断电导致数据半写入状态。为此我开发了多级保护机制。

3.1 校验和机制

每批数据写入时附加CRC8校验:

uint8_t calc_crc(uint8_t *data, uint8_t len) { uint8_t crc = 0x00; while(len--) { crc ^= *data++; for(uint8_t i=0; i<8; i++) { crc = (crc & 0x80) ? ((crc << 1) ^ 0x07) : (crc << 1); } } return crc; } void Write_With_CRC(uint8_t *data, uint8_t addr, uint8_t len) { uint8_t buffer[len+1]; memcpy(buffer, data, len); buffer[len] = calc_crc(data, len); Safe_EEPROM_Write(buffer, addr, len+1); }

3.2 数据版本控制

为防止错误数据覆盖,我引入了版本号机制:

typedef struct { uint8_t version; uint8_t data[32]; uint8_t crc; } EEPROM_Block; void Update_Data(EEPROM_Block *new_data) { EEPROM_Block old_data; EEPROM_Read((uint8_t*)&old_data, 0, sizeof(EEPROM_Block)); if(new_data->version > old_data.version) { new_data->crc = calc_crc((uint8_t*)new_data, sizeof(EEPROM_Block)-1); Safe_EEPROM_Write((uint8_t*)new_data, 0, sizeof(EEPROM_Block)); } }

4. 性能优化实战技巧

比赛最后阶段,我发现EEPROM操作拖慢了整个系统响应速度,经过以下优化将速度提升3倍。

4.1 缓存机制实现

建立RAM缓存减少EEPROM访问:

typedef struct { uint8_t data[64]; uint8_t dirty; // 标记位 uint8_t addr; } EEPROM_Cache; EEPROM_Cache cache; void Cache_Init(void) { EEPROM_Read(cache.data, 0, sizeof(cache.data)); cache.dirty = 0; cache.addr = 0; } void Cache_Flush(void) { if(cache.dirty) { Safe_EEPROM_Write(cache.data, cache.addr, sizeof(cache.data)); cache.dirty = 0; } } uint8_t Cache_Read(uint8_t addr) { return cache.data[addr]; } void Cache_Write(uint8_t addr, uint8_t val) { if(cache.data[addr] != val) { cache.data[addr] = val; cache.dirty = 1; } }

4.2 批量操作优化

将分散的小数据包合并为批量操作:

void Batch_Write(uint8_t addr, uint8_t *data, uint8_t len) { uint8_t temp[64]; uint8_t max_len = (addr + len > 64) ? (64 - addr) : len; // 先读取可能被部分覆盖的区域 if(addr % 8 != 0 || max_len < 8) { EEPROM_Read(temp, addr, max_len); memcpy(temp, data, max_len); Safe_EEPROM_Write(temp, addr, max_len); data += max_len; addr += max_len; len -= max_len; } // 处理完整的8字节块 while(len >= 8) { Safe_EEPROM_Write(data, addr, 8); data += 8; addr += 8; len -= 8; } // 处理剩余字节 if(len > 0) { EEPROM_Read(temp, addr, len); memcpy(temp, data, len); Safe_EEPROM_Write(temp, addr, len); } }

在比赛最后的压力测试环节,这些优化使我的作品在频繁数据存储时仍保持流畅的界面响应,最终获得了省赛一等奖。最让我自豪的不是奖项本身,而是从EEPROM这个"小坑"里爬出来的过程中积累的实战经验——有时候最基础的外设反而最能考验工程师的真正功底。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询