如何基于Mbed在Nucleo开发板内部存储器记录时间日期并保存100条记录
嘿,我来帮你搞定这个需求——基于Mbed框架在Nucleo板的内部存储器里存储100条时间日期记录。我之前做过类似的项目,下面一步步给你拆解思路,再附上可落地的C/C++代码指导:
整体实现思路
要完成这个功能,核心要解决三个问题:时间获取、数据存储结构设计、内部Flash操作,具体拆解如下:
- 时间来源:用Nucleo板自带的硬件RTC(实时时钟),Mbed框架有现成的API可以直接调用,比软件模拟的RTC精度高得多。
- 存储介质:优先选STM32芯片的内部Flash作为非易失存储(多数Nucleo板的STM32没有内置EEPROM,用Flash模拟非易失存储是最常用的方案)。Flash的扇区大小足够存100条记录(每条按8字节算,100条才800字节,远小于最小的Flash扇区)。
- 数据管理:设计固定格式的时间记录结构体,用一个计数器记录当前存储的条目数,达到100条时可以选择循环覆盖(避免Flash空间不足),每次写入前要确保Flash扇区已擦除(Flash特性:只能从1写0,必须先擦除才能写入新数据)。
具体C/C++代码实现指导
下面的代码基于Mbed OS 6.x编写,适配大多数Nucleo板(比如STM32F4、F1系列),你可以根据自己的芯片微调参数。
1. 基础依赖与定义
首先引入必要的头文件,定义时间记录结构体和Flash存储的关键参数:
#include "mbed.h" #include "FlashIAP.h" // 定义时间记录结构体,内存对齐后每条占8字节 typedef struct { uint16_t year; // 年(比如2024) uint8_t month; // 月(1-12) uint8_t day; // 日(1-31) uint8_t hour; // 时(0-23) uint8_t minute; // 分(0-59) uint8_t second; // 秒(0-59) } TimeRecord; // Flash存储配置:根据你的芯片调整起始地址(查芯片手册找空闲扇区) FlashIAP flash; const uint32_t FLASH_STORAGE_START = 0x080E0000; // STM32F4最后一个扇区起始地址 const uint32_t MAX_RECORDS = 100; const uint32_t RECORD_SIZE = sizeof(TimeRecord); const uint32_t TOTAL_STORAGE_SIZE = MAX_RECORDS * RECORD_SIZE; uint32_t current_record_count = 0; // 当前已存储的记录数
2. 初始化存储系统
上电后先初始化Flash,读取已存储的记录数,第一次使用时擦除扇区并初始化计数器:
void init_storage() { flash.init(); // 读取存储在Flash末尾的计数器 flash.read(¤t_record_count, FLASH_STORAGE_START + TOTAL_STORAGE_SIZE, sizeof(current_record_count)); // 如果是首次使用,Flash默认值为0xFFFFFFFF,初始化计数器并擦除扇区 if (current_record_count == 0xFFFFFFFF) { current_record_count = 0; uint32_t sector_size = flash.get_sector_size(FLASH_STORAGE_START); flash.erase(FLASH_STORAGE_START, sector_size); // 写入初始计数器值 flash.program(¤t_record_count, FLASH_STORAGE_START + TOTAL_STORAGE_SIZE, sizeof(current_record_count)); } }
3. 获取当前RTC时间
通过Mbed的时间API获取格式化的时间记录:
TimeRecord get_current_time() { TimeRecord record; time_t current_epoch = time(NULL); struct tm* time_info = localtime(¤t_epoch); record.year = time_info->tm_year + 1900; // tm_year是从1900年开始的偏移量 record.month = time_info->tm_mon + 1; // tm_mon是0-11,要+1 record.day = time_info->tm_mday; record.hour = time_info->tm_hour; record.minute = time_info->tm_min; record.second = time_info->tm_sec; return record; }
4. 写入时间记录到Flash
处理记录写入逻辑,达到最大条数时循环覆盖:
bool write_time_record(TimeRecord record) { // 达到最大记录数时,擦除扇区并重置计数器(循环覆盖) if (current_record_count >= MAX_RECORDS) { current_record_count = 0; uint32_t sector_size = flash.get_sector_size(FLASH_STORAGE_START); flash.erase(FLASH_STORAGE_START, sector_size); } // 计算当前记录的写入地址 uint32_t write_addr = FLASH_STORAGE_START + current_record_count * RECORD_SIZE; // 写入记录到Flash flash.program(&record, write_addr, RECORD_SIZE); // 更新计数器并写入Flash current_record_count++; flash.program(¤t_record_count, FLASH_STORAGE_START + TOTAL_STORAGE_SIZE, sizeof(current_record_count)); return true; }
5. 读取所有存储的记录
从Flash中读取已存储的时间记录:
void read_all_records(TimeRecord records[], uint32_t* out_count) { *out_count = current_record_count > MAX_RECORDS ? MAX_RECORDS : current_record_count; for (uint32_t i = 0; i < *out_count; i++) { uint32_t read_addr = FLASH_STORAGE_START + i * RECORD_SIZE; flash.read(&records[i], read_addr, RECORD_SIZE); } }
6. 主函数示例
整合所有功能,实现定时写入和读取演示:
int main() { // 初始化RTC:设置初始时间为2024-01-01 00:00:00(Unix时间戳) set_time(1704067200); // 初始化存储系统 init_storage(); // 读取已存储的记录并打印 TimeRecord stored_records[MAX_RECORDS]; uint32_t record_count; read_all_records(stored_records, &record_count); printf("已存储 %d 条记录:\n", record_count); for (uint32_t i = 0; i < record_count; i++) { printf("%04d-%02d-%02d %02d:%02d:%02d\n", stored_records[i].year, stored_records[i].month, stored_records[i].day, stored_records[i].hour, stored_records[i].minute, stored_records[i].second); } // 每隔10秒写入一条新记录 while (1) { ThisThread::sleep_for(10s); TimeRecord new_record = get_current_time(); write_time_record(new_record); printf("已写入新记录: %04d-%02d-%02d %02d:%02d:%02d\n", new_record.year, new_record.month, new_record.day, new_record.hour, new_record.minute, new_record.second); } }
关键注意事项
- Flash地址调整:不同STM32芯片的Flash扇区地址和大小不同,比如STM32F1的扇区大小是1KB/2KB,你需要查芯片参考手册找到合适的空闲扇区,或者用
flash.get_sector_start()动态获取。 - Flash寿命:内部Flash的擦写次数约为10万次,循环覆盖可以延长使用寿命,如果需要更专业的磨损均衡,可以设计多个扇区轮流使用。
- RTC保持:Nucleo板的RTC断电后,如果没有外接后备电池,上电需要重新设置时间,你可以通过串口或按键输入来设置初始时间。
- 数据完整性:如果写入过程中断电,可能会损坏记录,你可以给每条记录加校验位(比如CRC),读取时验证完整性。
内容的提问来源于stack exchange,提问作者joe adamu




