У ATmega328P есть всего 1 КБ энергонезависимой памяти EEPROM. Этого достаточно, чтобы хранить настройки, но если мы запишем туда структуры данных, мы сможем эмулировать простую файловую систему с метаданными и проверкой целостности.
Проблема стандартного подхода: Мы просто пишем байты в EEPROM. Если мышь случайно изменит один байт, мы потеряем все настройки, и контроллер будет работать неправильно.
Хак: Создаем «суперблок» — структуру в начале EEPROM, которая хранит:
- Сигнатуру («ARH» — ARDUINOHACKS).
- Версию прошивки.
- CRC-контрольную сумму всех данных.
- Указатели на записи (как в FAT-таблице).
Код мини-файловой системы:
#include <EEPROM.h>
#include <avr/wdt.h> // Сторожевой таймер для защиты
// Структура суперблока
struct SuperBlock {
uint8_t signature[3]; // "ARH"
uint8_t version;
uint16_t crc; // Контрольная сумма всех данных
uint8_t dataSize; // Общий размер данных
uint8_t recordCount; // Количество записей (до 10)
};
// Структура записи (например, настройки датчика)
struct Record {
uint8_t id;
uint16_t value;
uint8_t enabled;
uint16_t timestamp;
};
// Глобальные переменные
SuperBlock sb;
Record records[10];
// Функция расчета CRC16 (простой, но надежный)
uint16_t calculateCRC(uint8_t *data, uint8_t len) {
uint16_t crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
// Загрузка данных из EEPROM
bool loadConfig() {
// Читаем суперблок
EEPROM.get(0, sb);
// Проверяем сигнатуру
if (sb.signature[0] != 'A' || sb.signature[1] != 'R' || sb.signature[2] != 'H') {
return false; // EEPROM пуст или поврежден
}
// Читаем все записи
for (uint8_t i = 0; i < sb.recordCount; i++) {
EEPROM.get(sizeof(SuperBlock) + i * sizeof(Record), records[i]);
}
// Проверяем CRC
uint16_t computedCRC = calculateCRC((uint8_t*)records, sb.recordCount * sizeof(Record));
if (computedCRC != sb.crc) {
return false; // Повреждение данных!
}
return true;
}
// Сохранение данных в EEPROM
void saveConfig() {
// Заполняем суперблок
sb.signature[0] = 'A';
sb.signature[1] = 'R';
sb.signature[2] = 'H';
sb.version = 1;
sb.recordCount = 5; // Сохраняем 5 записей
// Вычисляем CRC
sb.crc = calculateCRC((uint8_t*)records, sb.recordCount * sizeof(Record));
// Сохраняем в EEPROM (с защитой от сбоев питания)
uint8_t* sbPtr = (uint8_t*)&sb;
for (uint8_t i = 0; i < sizeof(SuperBlock); i++) {
EEPROM.write(i, sbPtr[i]);
wdt_reset(); // Сбрасываем сторожевой таймер при длительной записи
_delay_ms(2); // Ждем завершения записи
}
// Сохраняем записи
for (uint8_t i = 0; i < sb.recordCount; i++) {
uint8_t* recPtr = (uint8_t*)&records[i];
for (uint8_t j = 0; j < sizeof(Record); j++) {
EEPROM.write(sizeof(SuperBlock) + i * sizeof(Record) + j, recPtr[j]);
_delay_ms(2);
}
}
}
void setup() {
Serial.begin(115200);
if (loadConfig()) {
Serial.println("Конфигурация загружена успешно!");
for (uint8_t i = 0; i < sb.recordCount; i++) {
Serial.print("Запись ");
Serial.print(i);
Serial.print(": ID=");
Serial.print(records[i].id);
Serial.print(", Value=");
Serial.println(records[i].value);
}
} else {
Serial.println("EEPROM поврежден! Создаем новые данные...");
for (uint8_t i = 0; i < 5; i++) {
records[i].id = i + 1;
records[i].value = random(1000);
records[i].enabled = 1;
records[i].timestamp = millis() / 1000;
}
saveConfig();
Serial.println("Данные сохранены!");
}
}
void loop() {
// Ваш основной код здесь
}
Результат: Вы защищены от случайных сбоев. Если данные повреждены, система создаст новые настройки по умолчанию. Отличное решение для промышленных проектов или метеостанций в диких условиях.