EEPROM

Наш контроллер печи почти готов – однако пока он остаётся контроллером-«золотой рыбкой», который помнит все настройки только пять минут до первого выключения питания. Чтобы запомнить наши настройки, значение заданной температуры и точки калибровки даже после отключения питания, нужно использовать энергонезависимую память – EEPROM.
Очень хорошо о работе с EEPROM написано у наших товарищей тут и тут.

Главное, что нам нужно знать – что память EEPROM лучше рассматривать не как «просто память», а как отдельное внутреннее устройство в микросхеме.
У EEPROM отдельное адресное пространство, не имеющее никакого отношения к адресному пространству процессора (FLASH и SRAM); для того, чтобы получить доступ к данным по определённому адресу в энергонезависимой памяти, нужно выполнить определённую последовательность действий с использованием целого ряда регистров (регистров адреса EEARH и EEARL, регистра данных EEDR и регистра управления EECR).
Согласно даташиту, для записи байта по определённому адресу в EEPROM нужно выполнить следующее:

  1. ждём готовности EEPROM к записи данных (сброса бита EEPE регистра EECR);
  2. ждём окончания записи в FLASH-память (сброса бита SELFPRGEN регистра SPMCSR) – нужно выполнить, если в программе присутствует загрузчик;
  3. записываем новый адрес в регистр EEAR (при необходимости);
  4. записываем байт данных в регистр EEDR (при необходимости);
  5. устанавливаем в единицу бит EEMPE регистра EECR;
  6. в течение четырёх тактов после установки флага EEMPE записываем в бит EEPE регистра EECR логическую единицу.

После этого процессор пропускает 2 такта перед выполнением следующей инструкции.
Второй пункт нужно выполнять при наличии загрузчика в программе – дело в том, что запись в EEPROM не может выполняться одновременно с записью во FLASH-память, поэтому перед записью в EEPROM нужно убедиться, что программирование FLASH-памяти завершено; если же микроконтроллер не имеет загрузчика, то он никогда не изменяет содержимое FLASH-памяти (помним, что avr имеет гарвардскую архитектуру: память программ (FLASH) и память данных (SRAM) разделены).
Длительность цикла записи зависит от частоты внутреннего RC-генератора микросхемы, напряжения питания и температуры; обычно для моделей ATmega48x/88x/168x это составляет 3.4 мс (!), для некоторых старых моделей – 8.5 мс (!!!).
Кроме того, при записи в EEPROM могут возникнуть проблемы с вызовом прерываний в процессе выполнения последовательности действий выше – так что прерывания в процессе записи в EEPROM лучше запретить.
Чтение энергонезависимой памяти происходит чуть проще:

  1. ждём готовности EEPROM к чтению данных (сброса бита EEWE регистра EECR);
  2. записываем адрес в регистр EEAR;
  3. устанавливаем в единицу бит EERE регистра EECR;
  4. считываем данные из регистра EEDR (на самом деле, когда запрошенные данные будут перемещены в регистр данных, происходит аппаратный сброс бита EERE; но отслеживать состояние этого бита не требуется, так как операция чтения из EEPROM всегда выполняется за один такт).

После установки бита в EERE в единицу процессор пропускает 4 такта перед началом выполнения следующей инструкции.
Как мы видим, работа с энергонезависимой памятью – процесс времязатратный; если мы часто будем записывать-считывать данные с EEPROM – программа может начать тормозить.

Однако мы пишем программу в среде IAR, и нам повезло: всю работу с чтением-записью из EEPROM будет выполнять среда разработки – в iar есть модификатор «__eeprom», который создает переменные в энергонезависимой памяти – а далее нам нужно будет просто или считывать из «постоянных» переменных в «текущие» (при инициализации контроллера), или записывать из «текущих» переменных в «постоянные» – то есть, при изменении текущего значения нужно изменять и значение переменной в энергонезависимой памяти.
Выглядеть новые переменные будут вот так:

__eeprom uint16_t EEP_MinTemperature;

Ещё пару общих слов: и хотя указатели на eeprom-переменные у нас не предполагаются, нужно помнить, что eeprom – отдельное адресное пространство, и чтобы создать указатель на eeprom (и это позволяет нам сделать компилятор), необходимо указывать, что это указатель на адрес в eeprom:

uint16_t __eeprom *EEP_MinTemperatureAdr;

Возвращаемся к контроллеру печки и EEPROM. В нашем случае, для EEPROM никакой виртуальной машины, конечно, не предполагается; более того, стоит подумать, нужна ли отдельная библиотека для работы с энергонезависимой памятью – уж больно «разбросаны» по программе записи важных настроек; если пытаться сделать отдельную библиотеку, то придётся делать перекрестные ссылки: в библиотеке для EEPROM подключать библиотеки АЦП, нагревательного элемента, глобальных настроек; а в этих библиотеках периферии подключать библиотеку EEPROM – такой подход не очень хорош.
Другой вариант – дописать в каждую библиотеку, где нужно сохранять настройки, eeprom-переменную, и сохранять соответствующие настройки прямо в виртуальных машинах. Мы реализуем этот вариант.
Сначала перечислим, какие переменные нам нужно сохранять в EEPROM:

  1. точки калибровки
  2. значения максимальной-минимальной задаваемой температуры и шага настройки температуры
  3. значение заданной температуры
  4. коэффициенты ПИД-регулятора

Значение кухонного таймера не сохраняем – будем считать, что пользователь после выключения питания должен сам каждый раз настраивать таймер печки.
Все эти настройки задаются пользователем посредством поворотов энкодера и дальнейшего краткого нажатия на пользовательскую кнопку. При этом помним, что количество циклов чтения-записи EEPROM все-таки ограничено, поэтому лишний раз одну и ту же информацию (например, если пользователь выбрал то же самое значение какой-то настройки, что и было) не перезаписывать. Поэтому перед каждым изменением __eeprom-переменной проверяем, а нужно ли её переписывать:

//если значение изменилось - перезаписываем в энергонезависимой памяти
if (ADCTemperature.atMinTemperatureValue != (uint16_t)VMEncoderCounter.ecntValue)
{
ADCTemperature.atMinTemperatureValue = (uint16_t)VMEncoderCounter.ecntValue;
EEP_MinTemperature = ADCTemperature.atMinTemperatureValue; 
}

С чтением настроек из EEPROM тоже все просто – при инициализации «текущих» настроек мы просто считываем значение из энергонезависимой памяти:

ADCTemperature.atMinTemperatureValue = EEP_MinTemperature;

Для того, чтобы наше устройство с самого начала имело какие-нибудь настройки в EEPROM, проект для первой загрузки можно скомпилировать с инициализацией этих переменных:

__eeprom uint16_t EEP_MinTemperature = 20;
…
//массив для хранения точек калибровки в энергонезависимой памяти
__eeprom TCalibrationData EEP_CalibrationData[2] = {{20, 1300}, {300, 4092}};

В этом случае компилятор инициализирует __eeprom переменные до начала работы с основной функцией. Чтобы получить файл с энергонезависимой памятью (.eep), нужно залезть в следующие настройки:
Project->Options..->Linker->Extra Options
Если галочка «Use command line options» не стоит, поставьте её и добавьте строку
-Ointel-standard,(XDATA)=.eep
Компилируем сначала проект с инициализированными переменными, сохраняем eep-файл отдельно; затем убираем инициализацию при создании переменных.

Вот файл "с памятью", проект и прошивка.

Вот и все – наша печка готова!

Наверх

Автор - Moriam  =ˆˆ=

Обсудить на форуме