
В программировании микроконтроллеров часто приходится работать с энергонезависимой памятью: читать и писать массивы. Однако порой реализация энергонезависимой памяти накладывает некоторые свои ограничения. Примером тому является EEPROM память.
Постановка задачи
Дана функция чтения и записи EEPROM dword.
bool eeprom_dword_erase(const uint32_t phy_addr);
bool eeprom_dword_write(const uint32_t phy_addr, const uint32_t dword);
bool eeprom_dword_read(const uint32_t phy_addr, uint32_t * const dword);
Причем запись и стирание возможно только пословно по выровненному адресу: 0 4 8 N*4. Где N-натуральное число.
Надо поверх EEPROM API сделать функцию чтения, записи и стирания для произвольных массивов байт, по произвольному адресу. Вот так.
bool eeprom_byte_erase(const uint32_t phy_addr);
bool eeprom_byte_write(const uint32_t phy_addr, const uint8_t byte);
bool eeprom_byte_read(const uint32_t phy_addr, uint8_t * const byte);
Простыми словами надо состыковать Dword API с Byte API. Только и всего.
Стертые данные положим как 0xFF. На адресацию слова условно выделяется 11 бит. Можно адресовать 2048 слов (8192 байт). Процессор порядком байт little-endian.
Исходя из специфики задачи возникают вот такие три неудобных случая.

Терминология
dword (double word) — это 4х байтная переменная (32 бит).
word — это двухбайтная переменная (16 бит)
byte — это 8ми битная переменная
Почему это нужно?
Byte API более универсальный и нужен для запуска NVRAM поверх EEPROM памяти. Дело в том, что API для NVRAM как раз пишет и читает по произвольному смещению и произвольный размер данных.
Решение
Задача по записи чтению массивов сводится к задаче чтения и записи отдельного байта. В связи с этим надо сделать высокоуровневую функцию для чтения, записи и удаления отдельного байта по произвольному адресу EEPROM. Нужен вот такой API.
bool eeprom_byte_erase(const uint32_t phy_addr);
bool eeprom_byte_write(const uint32_t phy_addr, const uint8_t byte);
bool eeprom_byte_read(const uint32_t phy_addr, uint8_t * const byte);
Основная идея в том, что отдельный байт всегда попадет только в один dword. От этого и надо танцевать.
Чтение байта из ЕЕPROM
Учитывая то, что EEPROM, как правило отображается в физическую память микроконтроллера, то можно читать EEPROM память по Cи-шному указателю. Вот так:
uint8_t read_addr_8bit(const uint32_t address) {
uint8_t value = 0u;
volatile uint8_t* p_addr = NULL;
/*MISRA 2012 Rule 11.4: integer should not be converted to pointer */
p_addr = (volatile uint8_t*)address;
if(p_addr) {
value = *p_addr;
}
return value;
}
Записать байт в EEPROM
1--перед записью байта можно проверить нужна ли вообще запись? Дело в том, что с вероятностью 1/256 =0.39 % по заданному адресу уже лежит то же самое значение, которое мы желаем записать. А раз так, то можно и вовсе ничего не делать и отрапортовать наверх об успехе. Это обычно называют lazy write. Если же нам не повезло (вероятность 99.60 %), то надо прибегнуть к следующему алгоритму.
2--Определить dword в котором оказался физический адрес байта
3--Прочитать в RAM dword из EEPROM
4--Записать новый байт в копию dword что сейчас хранится в RAM
5--Стереть старый dword
6--Прописать модернизированный dword.
typedef union {
uint32_t u32;
uint8_t u8[4];
} Type32Union_t;
bool eeprom_byte_write(const uint32_t phy_addr, const uint8_t byte) {
bool res = false;
LOG_DEBUG(EEPROM,"Write [0x%x]=0x%x",phy_addr,byte);
res = eeprom_is_address_range( phy_addr, 1);
if(res) {
/*determine the address of the word in which the byte is
located (dword_phy_addr) */
//clear the two least significant bits
uint32_t dword_phy_addr = phy_addr & (~MASK_2BIT);
// select the two least significant bits
uint32_t byte_index = phy_addr & MASK_2BIT ;
Type32Union_t dWord = {0};
dWord.u32 = 0;
res = eeprom_dword_read(dword_phy_addr, &dWord.u32);
if(res) {
dWord.u8[byte_index] = byte;
res = eeprom_dword_erase(dword_phy_addr);
if(res) {
res = eeprom_dword_write(dword_phy_addr, dWord.u32);
}
}
}
return res;
}
Удалить байт из EEPROM
Стирание байта это по сути запись 0xFF. Поэтому можно просто выполнить функцию
bool eeprom_byte_erase(const uint32_t phy_addr) {
bool res = false;
res = eeprom_byte_write(phy_addr, 0xFF);
return res;
}
Разумеется тут есть простор для оптимизаций. Запросы на запись длинных массивов можно существенно оптимизировать, однако это потребует дополнительных ресурсов по RAM памяти.
Итоги
Удалось придумать binding-и для состыковки Byte API и Dword API. Таким образом EEPROM можно превратить в NVRAM.
Ссылки
№ | Название | URL |
1 | NVRAM для микроконтроллеров | |
2 | NVRAM Поверх off-chip SPI-NOR Flash | |
3 | Открытый проект файловой системы для внутренней памяти STM32H | |
4 | ESP32 и файловая система SPIFFS | |
5 | LittleFS – компактная и экономичная файловая система для ARM микроконтроллеров в составе mbed os | |
6 | Устройство NVRAM в UEFI-совместимых прошивках, часть первая | |
7 | Как избежать износа EEPROM |