На мой взгляд, в данный момент самое оптимальное решение для таких случаев, это FRAM память. Можно было бы записывать необходимые данные циклически во Flash, постоянно инкрементируя адрес для новых значений, но в данном случае возникала необходимость где-то сохранять указатель на самые последние значения, либо полностью считывать Flash и затем уже извлекать «самые свежие» данные.
У Lapis Semiconductor есть три линейки FRAM микросхем, которые обмениваются с ведущим устройством по I2C или SPI, либо по параллельному интерфейсу. Преимущества последовательных интерфейсов перед параллельным очевидны. Что же касается I2C и SPI, то скорость передачи данных по SPI в 4 раза выше чем по I2C, но и потребление в связи с этим выше практически в 16 раз.
Мне же выбирать не приходилось, в наличии была только MR45V256 c 32 Кб памяти и SPI интерфейсом. 32 Кб для моих нужд более чем достаточно, поэтому оставшуюся память я использовал для записи всевозможной технической информации и логирования команд полученных от оператора.
Работа с FRAM памятью очень простая. Любая операция начинается с перевода линии выбора ведомого устройства CS# в низкое состояние. Затем отправляется одна из команд операций, их всего 6:
— Чтение данных(READ)
— Запись данных(WRITE)
— Запись в регистр статуса FRAM(WRSR)
— Чтение из регистра статуса FRAM(RDSR)
— Установка защиты данных от перезаписи(WRDI)
— Снятие защиты данных от перезаписи(WREN)
Защиту от перезаписи можно установить либо на первую четверть памяти, либо на первую половину или же на всю память полностью. В случаях с командами чтения и записи, после них необходимо отправить еще и шестнадцатеричный адрес, с которого начнется чтение/запись. Команда и адрес отправляются единоразово, в последующем микросхема сама инкрементирует адрес и передает все последующие данные, до тех пор пока CS# не будет переведен в высокое состояние.
Перед тем как начать запись в память, необходимо всегда устанавливать бит разрешения записи(WREN) в регистре статуса MR45V256. И только после этого передавать команду записи(WRITE).

В качестве ведущего устройства использовал микроконтроллер Xmega. SPI микроконтроллеров Xmega практически ничем не отличается от SPI в других микроконтроллерах Atmel. Наиболее заметное отличие это возможность использования DMA, но объемы передаваемой информации в данном случае у меня не большие, поэтому от использования DMA я отказался. Xmega поддерживает все 4 режима работы SPI(Mode 0,1,2,3). Стоит заметить, что MR45V256 поддерживает только Mode 0 и Mode 3.
Модуль SPI в Xmega оснащен одноуровневой буферизацией в канале передачи и двухуровневой в канале приёма. То есть, байты для передачи, нельзя поместить в регистр данных SPI пока полностью не завершится цикл сдвигов. Во время приёма данных, принятую посылку необходимо считать прежде, чем завершится приём следующей посылки. В противном случае, первый байт данных будет потерян. Поэтому работать рекомендуется либо по прерываниям, либо при приёме и передаче контролировать регистр статуса SPI.
// Директивы для SPIE #define FRAMPORT PORTE // FRAM PORT #define SPIRESET 0 #define SPICS 4 #define SPIMOSI 5 #define SPIMISO 6 #define SPISCK 7 // Функция инициализации SPIE(FRAM) void SPIE_init() { FRAMPORT.DIRCLR = (1<<SPIMISO); // Выводы SPI на вход FRAMPORT.DIRSET = (1<<SPIMOSI) | (1<<SPISCK) | (1<<SPICS); // Выводы SPI на выход FRAMPORT.OUTSET = (1<<SPICS); // SPI включен, режим мастера, SPI Mode 0, деление частоты тактирования на 4 SPIE.CTRL = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_MODE_0_gc | SPI_PRESCALER_DIV4_gc; } // Функция записи во FRAM void FRAM_WREN() { SPIE_init(); FRAMPORT.OUTCLR = (1<<SPICS); // Включаю ChipSelect SPIE.DATA = 0x06; // Установка бита разрешения записи while( !(SPIE_STATUS & SPI_IF_bm) ); // Жду отчета об отправке FRAMPORT.OUTSET = (1<<SPICS); // Выключаю ChipSelect PORTE.OUTCLR = (1<<SPICS); // Включаю ChipSelect SPIE.DATA = 0x02; // Отправляю команду на запись while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = 0x00; // Старший байт адреса while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = 0x10; // Младший байт адреса while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = Data1; // Записываю данные while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = Data2; while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = Data3; while( !(SPIE_STATUS & SPI_IF_bm) ); FRAMPORT.OUTSET = (1<<SPICS); // Выключаю ChipSelect SPIE.CTRL = 0x00; FRAMPORT.OUTCLR = (1<<SPIMOSI) | (1<<SPISCK); } // Функция чтения FRAM void FRAM_RD() { SPIE_init(); FRAMPORT.OUTCLR = (1<<SPICS); // Включаю ChipSelect SPIE.DATA = 0x03; while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = 0x00; // Старший байт адреса while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = 0x10; // Младший байт адреса while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = 0x00; // Считываю данные while( !(SPIE_STATUS & SPI_IF_bm) ); Data1 = SPIE.DATA; SPIE.DATA = 0x00; while( !(SPIE_STATUS & SPI_IF_bm) ); Data2 = SPIE.DATA; SPIE.DATA = 0x00; while( !(SPIE_STATUS & SPI_IF_bm) ); Data3 = SPIE.DATA; FRAMPORT.OUTSET = (1<<SPICS); // Выключаем ChipSelect SPIE.CTRL = 0x00; FRAMPORT.OUTCLR = (1<<SPIMOSI) | (1<<SPISCK); // Порты SPI на выход }
Команды для стирания во FRAM не предусмотрено, так как данные можно перезаписывать без предварительного стирания, как например в других видах памяти. Но всё же иногда возникает необходимость полного «стирания» всей FRAM:
void FramErase(void) { SPIE_init(); FRAMPORT.OUTCLR = (1<<SPICS); // Включаю ChipSelect SPIE.DATA = 0x06; // Отправляю команду WREN while( !(SPIE_STATUS & SPI_IF_bm) ); // Жду отчета об отправке FRAMPORT.OUTSET = (1<<SPICS); // Выключаю ChipSelect PORTE.OUTCLR = (1<<SPICS); // Включаю ChipSelect SPIE.DATA = 0x02; // Отправляю команду на запись while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = 0x00; // Старший байт адреса while( !(SPIE_STATUS & SPI_IF_bm) ); SPIE.DATA = 0x00; // Младший байт адреса while( !(SPIE_STATUS & SPI_IF_bm) ); while(FramCounter <= 0x7FFF) { SPIE.DATA = 0x00; while( !(SPIE_STATUS & SPI_IF_bm) ); FramCounter++; } FRAMPORT.OUTSET = (1<<SPICS); // Выключаю ChipSelect FramCounter = 0; SPIE.CTRL = 0x00; // Порты SPI на выход FRAMPORT.OUTCLR = (1<<SPIMOSI) | (1<<SPISCK); }
Если говорить о преимуществах использования FRAM, то в первую очередь это надёжность. Количество циклов записи 10 в 12 степени. Производитель обещает до 10 лет хранения информации без её потери. По сравнению с той же FLASH память FRAM обладает большим быстродействием и куда меньшим потреблением. Из минусов можно отметить если только не большой объём памяти, но с учетом тех задач для которых обычно используют FRAM, большие объемы не так важны.

