AVR100: доступ к энергонезависимой памяти EEPROM |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||
В даташите AVR100 рассматриваются следующие возможности по доступу к памяти EEPROM в микроконтроллерах AVR: • Доступ к ячейкам памяти в произвольном порядке (Random Read/Write). Прим. переводчика: в оригинальном даташите были ошибки и несоответствия с листингом подпрограмм avr100.asm [2]. Все замеченные ошибки исправлены. Этот апноут содержит готовые подпрограммы для доступа к памяти EEPROM. Реализовано 2 типа доступа на чтение и запись: • Чтение и запись по произвольному адресу. В этом случае пользователь должен сначала установить данные (в случае записи) и адрес (в случае записи или чтения) перед вызовом подпрограммы чтения или записи. Апноут AVR100 содержит 4 подпрограммы, которые подробно описываются в последующих секциях. Подпрограммы написаны на языке ассемблера. Этот апноут подойдет для доступа к EEPROM на всех микроконтроллерах AVR. Примечание: в последних моделях микроконтроллеров бит EEWE в регистре EECR называется EEPE, и EEMWE называется EEMPE. Также в последних моделях регистр EECR содержит 2 дополнительных бита для установки режима программирования: EEPM0 и EEPM1. Эти 2 бита необходимо инициализировать перед установкой EEPE. [Запись по произвольному адресу: подпрограмма EEWrite] Перед вызовом этой подпрограммы необходимо сначала установить 3 регистровые переменные: EEdwr – записываемые данные Перед записью подпрограмма ждет, пока EEPROM не будет готова к программированию путем опроса бита EEWE (EEPROM Write Enable) в регистре управления EEPROM (EEPROM Control Register, EECR). Когда EEWE равен 0, содержимое EEdwr передается в регистр данных EEPROM (EEPROM Data Register, EEDR), и содержимое регистровой пары EEawrh:EEawr передается в регистр адреса EEPROM (EEPROM Address Register, EEARH:EEARL). Сначала устанавливается бит разрешения записи EEMWE (EEPROM Master Write Enable), затем устанавливается бит строба записи EEWE в регистре EECR. Алгоритм работы подпрограммы см. на рис. 1. Рис. 1. Алгоритм работы подпрограммы EEWrite. [Чтение по произвольному адресу: подпрограмма EERead] Перед вызовом этой подпрограммы необходимо сначала установить 2 регистровые переменные: EEard – младший байт адреса ячейки, откуда осуществляется чтение Перед чтением подпрограмма ожидает готовности EEPROM путем опроса бита EEWE (EEPROM Write Enable) в регистре управления EEPROM (EEPROM Control Register, EECR). Когда EEWE равен 0, содержимое регистровой пары EEardh:EEard передается в регистр адреса EEPROM (EEPROM Address Register, EEARH:EEARL). Затем устанавливается бит строба чтения EERE в регистре EECR. В следующей инструкции содержимое регистра EEDR передается в регистровую переменную EEdrd - это считанные данные. Алгоритм работы подпрограммы см. на рис. 2. Рис. 2. Алгоритм работы подпрограммы EERead. [Последовательная запись: подпрограмма EEWrite_seq] Перед вызовом этой подпрограммы необходимо сначала установить 1 регистровую переменную: EEdwr_s – записываемые данные Перед записью подпрограмма ожидает готовности EEPROM к программированию путем опроса бита EEWE (EEPROM Write Enable) в регистре управления EEPROM (EEPROM Control Register, EECR). Когда EEWE равен 0, содержимое регистра адреса EEPROM (EEPROM Address Register, EEARH:EEARL) читается в регистровую переменную EEwtmph:EEwtmp, она инкрементируется и записывается обратно в EEARH:EEARL. Этим инкрементируется текущий адрес EEPROM на единицу. Содержимое EEdwr_s передается в регистр данных EEPROM (EEPROM Data Register, EEDR), затем по порядку устанавливаются сначала бит EEWE в регистре EECR, затем бит EEMWE. Алгоритм работы подпрограммы см. на рис. 3. Рис. 3. Алгоритм работы подпрограммы EEWrite_seq. [Последовательное чтение: подпрограмма EERead_seq] Перед чтением подпрограмма ожидает готовности EEPROM путем опроса бита EEWE (EEPROM Write Enable) в регистре управления EEPROM (EEPROM Control Register, EECR). Когда EEWE равен 0, текущий адрес EEPROM инкрементируется следующим образом: содержимое регистра адреса EEARH:EEARL передается в регистровую переменную EErtmph:EErtmp, эта переменная увеличивается на 1 и записывается обратно в EEARH:EEARL. Затем подпрограмма устанавливает строб чтения EEPROM (EEPROM Read Strobe, EERE). И наконец, прочитанные данные EEPROM передаются из EEDR в регистровую переменную EEdrd_s.Алгоритм работы подпрограммы см. на рис. 4. Рис. 4. Алгоритм работы подпрограммы EERead_seq. [Оптимизация кода под различные типы микроконтроллеров] Не все действия в подпрограмме требуются для всех типов микроконтроллеров. Если объем памяти EEPROM не более 256 байт (или если Вам больше 256 байт не требуется), то не нужно менять старший байт регистра адреса EEPROM. Его можно установить в 0 при запуске программы. В микроконтроллере AT90S1200, к примеру, нет бита EEMWE в регистре EECR, так что его устанавливать не нужно. Подробнее про работу с битами регистров EEPROM на чтение и запись проконсультируйтесь с даташитом на соответствующий микроконтроллер. В файле исходного кода на ассемблере avr100.asm [2] есть готовая программа, которая вызывает все 4 описанные функции в качестве теста и примера для использования. Тест можно скомпилировать и запустить в симуляторе среды разработки AVR Studio®. Тест также содержит комментарии, касающиеся портирования кода на другие микроконтроллеры семейства AVR. Прим. переводчика: если Вы счастливый обладатель аппаратного отладчика наподобие AVR Dragon или JTAGICE mkII, и используете современный микроконтроллер с возможностью отладки, то можете запустить тест прямо в микроконтроллере. Примечание: если Ваш код начнет запись в EEPROM сразу после сброса (включения питания), то имейте в виду следующее: если содержимое EEPROM было ранее запрограммировано на заводе во время производства, то микроконтроллер может быстро изменить код за короткое время после программирования. Когда программатор затем проверит содержимое EEPROM, то проверка может не совпасть, потому что содержимое EEPROM уже может быть изменено микроконтроллером. Также имейте в виду, что некоторые программаторы ISP (In-System Programmer, программатор, который может программировать микроконтроллер прямо в рабочей системе) могут позволить микроконтроллеру короткое время выполнения кода после каждого шага в процессе программирования и верификации. Таблица 1. Затраты процессорного времени и памяти.
Таблица 2. Использование периферийных ресурсов микроконтроллера.
Память EEPROM предназначена для хранения неких данных, которые должны сохраняться между выключением и включением питания системы. При этом нужно решить 2 вопроса - как быть с достоверностью данных, и как инициализировать данные, когда система запускается в первый раз? Проверка достоверности данных. Уже давно придумана технология, которая позволяет проверить целостность некоторого блока данных - контрольная сумма. Есть множество вариантов подсчета контрольных сумм, но для AVR вполне подойдет стандартный алгоритм CRC-16-CCITT (полином x16 + x12 + x5 + 1, этот алгоритм реализован в модуле crc16.c проекта [3]). Принцип проверки целостности данных прост - при включении питания проверяется контрольная всех необходимых данных в EEPROM. Если контрольная сумма совпала, то запускается нормальный рабочий режим. Если контрольная сумма не совпала, то программа предполагает, что возможно произошла ошибка, либо это было первое включение. В этом случае программа принудительно инициализирует EEPROM начальными значениями по умолчанию, и инициализирует для них контрольную сумму, и дальше запускается нормальный рабочий режим. Если необходимо поменять какой-то параметр в EEPROM, то он изменяется вместе с контрольной суммой - сначала меняется значение параметра, потом пересчитывается контрольная сумма, и измененные данные вместе с контрольной суммой записываются в EEPROM. Инициализация данных EEPROM. Когда в EEPROM хранятся некоторые данные, то логично предположить, что у них должно быть некоторое значение по умолчанию - например, для ситуации, когда прибор только что изготовлен и включается в первый раз. Само собой, инициализацию EEPROM можно выполнить с помощью программатора - при прошивке микроконтроллера прошивается не только память программ FLASH, но и память EEPROM. Это довольно неудобно, потому что нужно выполнить как минимум 2 операции по записи и 2 операции проверки. Гораздо удобнее, когда программа микроконтроллера сама позаботится об инициализации своих данных EEPROM, и загрузит в них установки по умолчанию. С помощью контрольной суммы довольно просто определить необходимость такой инициализации - когда не совпала контрольная сумма защищенных данных EEPROM. Для целей инициализации в памяти программ FLASH может храниться копия массива данных по умолчанию в виде удобной для использования структуры, либо эти данные можно просто инициализировать нужными значениями в специальной процедуре восстановления значений EEPROM. При этом отпадает необходимость инициализировать программатором данные EEPROM - программа микроконтроллера позаботится об этом самостоятельно. В этом примере будет рассмотрен исходный код инициализации и защиты данных EEPROM, примененный в проекте [3]. В файле types.h определена структура для хранения данных в EEPROM: #ifndef __TYPES__ #define __TYPES__ #include < inttypes.h > #define u8 unsigned char #define u16 unsigned int #define u32 uint32_t #define false 0 #define true 1 typedef struct _eeparam { u8 seedAA; u8 ledstate; //состояние светодиода // .. тут можно добавить свои параметры u16 crc; }Teeparam; #endif //__TYPES__ Здесь для целей демонстрации применена простейшая структура, в которой кроме CRC находится всего лишь одно полезное поле данных ledstate (поле seedAA необходимо только для корректной инициализации алгоритма подсчета контрольной суммы для того случая, когда все поля полезных полей структуры окажутся нулевыми). Все подпрограммы для работы с EEPROM размещены в модуле eepromutil.c, это 3 подпрограммы: Save(), Load() и RestoreEEPROM(). #include < avr/eeprom.h > #include < string.h > #include "types.h" #include "crc16.h" #include "vars.h" #include "strval.h" void Save (void) { u16 crc; u8* eeadr; crc = 0; eeadr = 0; //записываем все данные целиком, кроме CRC eeprom_write_block(&eeprm, eeadr, sizeof(Teeparam)-sizeof(u16)); AddCRC16(&eeprm, sizeof(Teeparam)-sizeof(u16), &crc); eeadr += sizeof(Teeparam)-sizeof(u16); //записываем CRC eeprom_write_block(&crc, eeadr, sizeof(crc)); } u8 Load (void) { u16 crccalc; u8* eeadr; crccalc = 0; eeadr = 0; //читаем все данные целиком eeprom_read_block(&eeprm, eeadr, sizeof(Teeparam)); //посчитаем CRC AddCRC16(&eeprm, sizeof(Teeparam)-sizeof(u16), &crccalc); return (eeprm.crc == crccalc); } void RestoreEEPROM (void) { eeprm.seedAA = 0xAA; eeprm.ledstate = 0; Save(); } Назначение подпрограмм Save(), Load() и RestoreEEPROM() понятно из их названий. Все они работают с глобальной переменной eeprm типа Teeparam. Это структура данных, где хранится копия данных EEPROM. Save() сохраняет переменную eeprm в память EEPROM, записывая при этом корректную контрольную сумму данных. Load() загружает переменную eeprm из памяти EEPROM, делая при этом проверку - если контрольная сумма совпала, то Load() вернет true. RestoreEEPROM() восстанавливает содержимое EEPROM записывая туда значения по умолчанию. Все устроено логично и просто. Вот как используются подпрограммы в основном коде приложения (приведен кусок функции main из проекта [3]): ... int main(void) { u8 loaded = false; //флаг успешной загрузки //настраиваем ножки portsInit(); //грузим настройки while (!loaded) { loaded = Load(); if (!loaded) { //инициализируем EEPROM ON_RED(); RestoreEEPROM(); OFF_RED(); } } eeprm.ledstate?ON_RED():OFF_RED(); //включаем ватчдог wdt_enable(WDTO_1S); ... [Ссылки] 1. AVR100: Accessing the EEPROM. |