В даташите AVR100 рассматриваются следующие возможности по доступу к памяти EEPROM в микроконтроллерах AVR:
• Доступ к ячейкам памяти в произвольном порядке (Random Read/Write). • Последовательное чтение и запись ячеек (Sequential Read/Write). • Рабочий тест/пример работы с памятью EEPROM.
Прим. переводчика: в оригинальном даташите были ошибки и несоответствия с листингом подпрограмм avr100.asm [2]. Все замеченные ошибки исправлены.
Этот апноут содержит готовые подпрограммы для доступа к памяти EEPROM. Реализовано 2 типа доступа на чтение и запись:
• Чтение и запись по произвольному адресу. В этом случае пользователь должен сначала установить данные (в случае записи) и адрес (в случае записи или чтения) перед вызовом подпрограммы чтения или записи. • Последовательное чтение или запись. В этом случае нужно только установить данные (в случае записи). Для всех операций будет использоваться текущий адрес, который автоматически инкрементируется перед доступом. Таким образом, адрес должен быть установлен перед записью (или чтением) первого байта в последовательности.
Апноут AVR100 содержит 4 подпрограммы, которые подробно описываются в последующих секциях. Подпрограммы написаны на языке ассемблера. Этот апноут подойдет для доступа к EEPROM на всех микроконтроллерах AVR.
Примечание: в последних моделях микроконтроллеров бит EEWE в регистре EECR называется EEPE, и EEMWE называется EEMPE. Также в последних моделях регистр EECR содержит 2 дополнительных бита для установки режима программирования: EEPM0 и EEPM1. Эти 2 бита необходимо инициализировать перед установкой EEPE.
[Запись по произвольному адресу: подпрограмма EEWrite]
Перед вызовом этой подпрограммы необходимо сначала установить 3 регистровые переменные:
EEdwr – записываемые данные EEawr – младший байт адреса ячейки, куда осуществляется запись EEawrh – старший байт адреса ячейки, куда осуществляется запись
Перед записью подпрограмма ждет, пока 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 – младший байт адреса ячейки, откуда осуществляется чтение EEardh – старший байт адреса ячейки, откуда осуществляется чтение
Перед чтением подпрограмма ожидает готовности 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.
Перед вызовом этой подпрограммы необходимо сначала установить 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.
Перед чтением подпрограмма ожидает готовности 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. Затраты процессорного времени и памяти.
Место использования
Размер кода
Затраты циклов
Используемые регистры
Описание
Reset
8 слов
8
R16
Пример инициализации
Main
39 слов
-
R16, R19, R20
Пример программы, которая работает с EEPROM
Всего
87 слов
-
R0, R16..R20, R24, R25
Таблица 2. Использование периферийных ресурсов микроконтроллера.
Периферия
Описание
Использование прерываний
8 ножек порта ввода/вывода B
Выходы для подключения светодиодов, показывающих значение данных (нужно только в качестве примера)
-
1 ножка порта D
Вход для подключения кнопки (только для примера)
-
10 байт EEPROM
Целевые ячейки EEPROM (только для примера)
-
;****************************************************************************;*;* Программа теста и примера работы с памятью EEPROM;*;****************************************************************************;***** Регистровые переменные основной программы.defcounter =r19.deftemp =r20;***** Здесь начинается код программыRESET:;***** Инициализация указателя стека (stack pointer);* В этом примере stack pointer устанавливается на самый старший адрес;* внутренней памяти SRAM. Закомментируйте этот код для устройств,;* у которых отсутствует SRAM.ldir16,high(RAMEND) ; старший байт надо устанавливать только еслиoutSPH,r16; размер RAM больше 256 байтldir16,low(RAMEND)
outSPL,r16;***** Инициализация порта B;* Порт B используется для отображения результатов проверки работы;* подпрограмм чтения и записи EEPROM.ldir16,0xff; DDRB=0xff -> порт B работает как выходoutDDRB,r16;***** Инициализация порта D; Разряд 0 порта D используется для запуска тест-программы (это вход).ldir16,0xff; все pull-up резисторы порта D разрешеныoutPORTD,r16;***** Запуск программыmain:inr16,PIND; ожидание, пока пользователь не нажметsbrcr16,0; кнопку, замыкающую разряд 0 порта Drjmpmain; на GND...;***** Программирование данных EEPROM по произвольному адресуldiEEdwr,$aaldiEEawrh,$00ldiEEawr,$10rcallEEWrite; сохранение $aa в ячейку EEPROM с адресом $0010;***** Чтение по произвольному адресуldiEEardh,$00ldiEEard,$10rcallEERead; чтение по адресу $0010outPORTB,EEdrd; вывод значения в ножки порта B;***** Заполнение памяти EEPROM по адресам 1..128 данными $55, $aa, $55, $aa, ...EEWrite_wait:sbicEECR,EEWE; если EEWE не очищен,rjmpEEWrite_wait; то подождем еще; Предыдущие действия по проверке EEWE на 0 можно пропустить, если; гарантируется, что до этого не было операции записи, когда мы сейчас; будем менять регистры EEARL и EEARH.ldicounter,63; инициализация счетчика циклаclrtempoutEEARH,temp; EEARH < - $00clrtempoutEEARL,temp; EEARL < - $00 (начальный адрес -1)loop1:ldiEEdwr_s,$55rcallEEWrite_seq; запишем в EEPROM байт $55ldiEEdwr_s,$aarcallEEWrite_seq; запишем в EEPROM байт $aadeccounter; декремент счетчикаbrneloop1; если счетчик не равен 0, то продолжим цикл;***** Копирование первых 10 байт из EEPROM в регистры r1-r11EERead_wait:sbicEECR,EEWE; если бит EEWE не очищен,rjmpEERead_wait; то подождем; Предыдущие действия по проверке EEWE на 0 можно пропустить, если; гарантируется, что до этого не было операции записи, когда мы сейчас; будем менять регистры EEARL и EEARH.clrtempoutEEARH,temp; EEARH < - $00lditemp,$00outEEARL,temp; EEARL < - $00 (начальный адрес -1)clrZHldiZL,1; Z-pointer указывает на регистр r1loop2:rcallEERead_seq; прочитать данные из EEPROMstZ,EEdrd_s; сохранить их в SRAMincZLcpiZL,12; дошли до конца?brneloop2; если нет, продолжим циклforever:rjmpforever; В это место управление попадет тогда, когда; все действия программы завершены.
Память 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:
Здесь для целей демонстрации применена простейшая структура, в которой кроме CRC находится всего лишь одно полезное поле данных ledstate (поле seedAA необходимо только для корректной инициализации алгоритма подсчета контрольной суммы для того случая, когда все поля полезных полей структуры окажутся нулевыми).
Все подпрограммы для работы с EEPROM размещены в модуле eepromutil.c, это 3 подпрограммы: Save(), Load() и RestoreEEPROM().
Назначение подпрограмм Save(), Load() и RestoreEEPROM() понятно из их названий. Все они работают с глобальной переменной eeprm типа Teeparam. Это структура данных, где хранится копия данных EEPROM. Save() сохраняет переменную eeprm в память EEPROM, записывая при этом корректную контрольную сумму данных. Load() загружает переменную eeprm из памяти EEPROM, делая при этом проверку - если контрольная сумма совпала, то Load() вернет true. RestoreEEPROM() восстанавливает содержимое EEPROM записывая туда значения по умолчанию. Все устроено логично и просто. Вот как используются подпрограммы в основном коде приложения (приведен кусок функции main из проекта [3]):