В одном из приборов, возникла необходимость полного восстановления предыдущего режима работы в случае какого либо сбоя по питани�� или кратковременному отключению. Можно было конечно заложить источник резервного питания, но его использование было ограничено, так скажем, конструктивными особенностями прибора. Как результат, было решено записывать ряд необходимых для восстановления значений в память. Так как обновлять значения для восстановления я собирался часто, в связи с ограниченным количеством циклов записи, использование Flash и EEPROM даже не рассматривалось.

На мой взгляд, в данный момент самое оптимальное решение для таких случаев, это 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).

image

В качестве ведущего устройства использовал микроконтроллер 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, большие объемы не так важны.