Как прочитать память avr

Обновлено: 04.07.2024

Здесь приведен перевод апноута AVR106: C functions for reading and writing to Flash memory [1], посвященного подпрограммам чтения и записи памяти FLASH на языке C для микроконтроллеров AVR. Рассмотрены функции чтения и записи одного байта FLASH, чтение и записи одной страницы FLASH, опциональное восстановление после неожиданного пропадания питания. Рассмотренные функции могут использоваться с любым устройством (микроконтроллером AVR), которое имеет возможность записи памяти программ из кода приложения [2] (это почти вся линейка AVR микроконтроллеров Atmel). Вместе с апноутом AVR106 приведен также проект примера программы [3], использующей секцию памяти программ для сохранения параметров.

Почти все микроконтроллеры AVR® компании Atmel имеют так называемую возможность самопрограммирования (Self programming Program memory). Самопрограммирование является одной из особенностей технологии AVR. Чтобы лучше разобраться с этим, обратитесь к апноуту AVR109 [2]. Самопрограммирование позволяет AVR перепрограммировать собственную память FLASH во время работы программы. Это подходит для приложений, которые нуждаются в самостоятельном обновлении кода firmware (bootloader, загрузчики, бутлоадеры) или сохранении какой-либо информации в области памяти FLASH. Этот апноут предоставляет функции на языке C, которые позволяют получить доступ к области памяти FLASH.

[Ассемблерная инструкция SPM]

Память FLASH может быть запрограммирована с помощью инструкции SPM (Store Program Memory). На устройствах, поддерживающих самопрограммирование, память программ FLASH часто делится на 2 основные секции: секция основной программы (память приложения, Application Flash Section) и секция загрузки (Boot Flash Section). Прим. переводчика: секция основной программы начинается с адреса 0x0000, а секция загрузки занимает маленький блок памяти обычно 2..4 килобайта в конце FLASH.

На устройствах, которые имеют блок памяти загрузки (boot block), инструкция SPM имеет возможность записи во всю область памяти FLASH, но только в том случае, если она выполняется из области памяти загрузки. Выполнение SPM из секции приложения не дает никакого эффекта. На младших устройствах AVR, к которых нет блока загрузки, инструкция SPM может работать из любого места памяти FLASH.

Во время записи FLASH в секции загрузки работа CPU всегда останавливается. Однако большинство устройств могут выполнить код (чтение) из секции загрузки, когда происходит запись области памяти приложения (Application section). Очень важно, чтобы код, выполняемый при записи Application section, не пытался читать из Application section. Если это вдруг произойдет, то может быть нарушено общее выполнение программы.

Размер и область размещения этих двух областей памяти FLASH зависит от типа устройства и установки его фьюзов. Некоторые устройства имеют возможность выполнять инструкцию SPM из любого места области памяти FLASH.

[Процедура записи FLASH]

• Заполнение буфера страницы
• Очистка страницы
• Запись страницы

Как можно заметить, возможна потеря данных, если во время этой процедуры неожиданно произойдет сброс или пропадание питания сразу после очистки страницы. Потери данных можно избежать, если предварительно применить буферизирование записываемых данных в энергонезависимой памяти (nonvolatile memory, обычно EEPROM). Функции записи, содержащиеся в этом апноуте, предоставляют опциональное буферизирование при записи. Эти функции далее рассматриваются в секции "Описание firmware". Для устройств, которые имеют фичу read-while-write (чтение во время записи), бутлоадер может выполняться во время записи, и из функций не произойдет возврат, пока запись не завершится.

[Адресация FLASH]

Память FLASH в AVR поделена на 16-битные слова. Это означает, что каждый адрес FLASH означает ячейку из 2 байт данных. Для ATmega128 можно адресовать до 65k слов или 128k байт данных FLASH. В некоторых случаях память FLASH упоминается адресованной словами, и в других случаях - адресованной побайтно, что вносит некоторую путаницу. Все функции, содержащиеся в этом апноуте, используют байтовую адресацию. Соотношение между байтовым адресом и адресом слова следующее:

Байтовый адрес = Адрес слова * 2

Байтовый адрес = номер страницы * размер страницы (в байтах)

Пример байтовой адресации: размер страницы FLASH у ATmega128 равен 256 байт. Байтовый адрес 0x200 (512) укажет на следующее:

Когда адресуется страница ATmega128, младший байт адреса всегда 0. Когда адресуется слово, то младший бит (LSB) адреса всегда 0.

[Реализация проекта примера, работающего с FLASH]

Проект firmware сделан для компилятора IAR. Функции могут быть портированы на другие компиляторы, но могут потребоваться некоторые усилия, поскольку используется специфическая мнемоника (intrinsic functions) компилятора IAR. Функции доступны через подключение файла Self_programming.h в главный модуль C, и добавление модуля Self_programming.c к проекту. Когда используется Self-programming (самопрограммирование), то важно, чтобы функции для записи размещались в пределах секции загрузки (Boot section) памяти FLASH. Размещение функций управляется путем использования определений сегмента памяти в файле конфигурации линкера (*.xcl). Все другие необходимые конфигурации, касающиеся firmware, сделаны в файле Self_programming.h.

PAGESIZE. Константа PAGESIZE должна быть определена равной размеру страницы FLASH (в байтах) используемого устройства.

__FLASH_RECOVER. Определение константы __FLASH_RECOVER разрешает опцию восстановления Flash, чтобы избежать потери данных при случайных пропаданиях питания. Когда восстановление разрешено, одна страница Flash выделена под буфер восстановления. Значение __FLASH_RECOVER будет определять адрес страницы FLASH, которая используется для этого. Адрес должен быть байтовым, указывающим на начало страницы FLASH, и функции записи не смогут записывать в эту страницу. Восстановление FLASH происходит вызовом функции RecoverFLASH() при старте программы (program startup).

ADR_LIMIT_LOW и ADR_LIMIT_HIGH. Диапазон памяти, в котором функциям разрешено записывать FLASH, задается константами ADR_LIMIT_LOW и ADR_LIMIT_HIGH. Функции могут записывать по адресам больше или равным ADR_LIMIT_LOW и меньше или равным ADR_LIMIT_HIGH.

[Размещение всего кода внутри секции загрузки]

Необходимо перезадать диапазон сегментов, определенных в файле настройки линкера по умолчанию (файл *.xcl), чтобы разместить весь код приложения в секции загрузки (Boot section) FLASH. Размещение и размер Boot section зависит от устройства и установок его фьюзов. Программирование фьюза BOOTRST перенесет вектор сброса в начало секции загрузки. Также можно перенести все вектора прерывания в секцию загрузки. Обратитесь к секции даташита на устройство, посвященной прерываниям, за инструкциями, как это сделать. Определения сегмента, которые должны быть изменены для размещения всего программного кода в секции загрузки:

TINY_F, NEAR_F, SWITCH, DIFUNCT, CODE, FAR_F, HUGE_F, INITTAB, TINY_ID, NEAR_ID и CHECKSUM.

В этом апноуте предоставляется в качестве примера файл lnkm128s.xcl, где задано размещение всего кода программы в 8 килобайт секцию загрузки Atmega128. Этот файл можно просто модифицировать для использования с другими устройствами, и в нем содержатся инструкции, как это осуществить.

[Размещение в секции загрузки только некоторых функций]

В качестве альтернативы можно разместить только некоторые функции в заданном сегменте памяти FLASH. В нашем случае в секции загрузки нужно разместить только функции записи. Это можно сделать заданием нового сегмента FLASH, эквивалентного области памяти загрузки, и использование оператора @ для размещения нужных функций в этом сегменте. Оператор @ не распространяется на функции, которые вызываются внутри функций, помеченных @.

Определение сегмента загрузки (Boot segment) в файле *.xcl для ATmega128 с размером 8 килобайт:

1. Сделайте новое определение размера секции загрузки.

2. Задайте новый сегмент для всей секции загрузки, основанный на определении шага 1.

Размещение функций C в заданном сегменте:

Пример кода на языке C, приведенный выше, разместит функцию ExampleFunction() в определенный сегмент памяти BOOT_SEGMENT.

[Описание firmware]

Код firmware состоит из 5 функций на языке C, и один проект примера для IAR версии 2.28a / 3.10c для ATmega128. Проект примера сконфигурирован так, чтобы разместить код всего приложения в секцию загрузки (Boot section) памяти FLASH. Этот код можно использовать как стартовую точку Вашей программы, которая может записывать FLASH. В таблице показаны функции, которые осуществляют доступ к памяти FLASH.

Тип данных MyAddressType определен в Self_programming.h. Размер этого типа зависит от используемого устройства. Для устройств, у которых FLASH больше 64 килобайт, он будет задан как long int, и int для устройств, у которых объем FLASH меньше или равен 64 килобайта (адрес байта 16-битный). Типы данных в реальности используются как указатели __flash или __farflash (соответственно 16 и 24 бит). Причина определения нового типа в том, что использование целочисленных типов более удобно, чем типов указателя.

ReadFlashByte() возвращает 1 байт, который размещен по адресу FLASH, указанном во входном аргументе.

ReadFlashPage() читает одну страницу памяти FLASH от адреса ucFlashStartAdr, и сохраняет данные в массиве pucDataPage[]. Количество сохраняемых байт зависит от размера страницы FLASH. Функция вернет FALSE, если входной адрес не является адресом страницы FLASH, иначе вернет TRUE.

WriteFlashByte() запишет байт ucData по адресу FLASH ucFlashAddr. Функция вернет FALSE, если входной адрес не является допустимым адресом FLASH для записи, иначе вернет TRUE.

WriteFlashPage() запишет данные из массива pucDataPage[] в страницу FLASH по адресу ucFlashStartAdr. Количество записываемых байт зависит от размера страницы FLASH. Функция вернет FALSE, если входной адрес не является допустимым адресом страницы FLASH для записи, иначе вернет TRUE.

RecoverFlash() прочитает переменную состояния в EEPROM, и восстановит страницу FLASH, если это необходимо. Функция должна быть вызвана в начале программы, если разрешена опция восстановления FLASH (__FLASH_RECOVER). Функция вернет TRUE, если имело место восстановление, иначе вернет FALSE.

[Как работает восстановление FLASH]

Когда разрешена опция восстановления FLASH, запись страницы вовлекает предварительное сохранение данных в выделенную страницу восстановления FLASH, перед тем как произойдет действительная запись во FLASH. Адрес записываемой страницы сохраняется в EEPROM совместно с байтом статуса, который показывает, что страница восстановления FLASH содержит данные. Этот байт состояния будет очищен, когда произойдет успешное завершение операции записи указанной страницы. Переменные в EEPROM и буфер восстановления FLASH используются функцией восстановления RecoverFlash(), чтобы восстановить данные, если это необходимо. Запись одного байта в EEPROM занимает примерно такое же время, как и запись всей страницы во FLASH. Так что нужно иметь в виду, что разрешение опции восстановления приведет к значительному увеличению времени записи. EEPROM используется вместо FLASH, потому что выделение нескольких байт в FLASH исключит гибкое использование всей страницы, которая будет содержать эти байты.

Теперь пришло время рассмотреть более подробно последнюю архитектуру AVR. Пожалуй, это сегодня самая популярная архитектура у любителей благодаря Arduino. Но в ее основе все таки лежат обычные микроконтроллеры, которые выпускает Atmel. Точнее выпускала, так теперь это принадлежит Microchip.

AVR устроены немного проще, чем две уже рассмотренные архитектуры. Но своих хватает и здесь. Да, AVR тоже бывают разные. Как минимум, большинство вспомнят ATtiny и ATmega. Но между ними нет столь принципиальных различий, как между PIC. Если говорить про архитектуру памяти. Поэтому я буду просто упоминать различия в ходе изложения.

Общие замечания по архитектуре AVR

В Atmel очень хотели сделать микроконтроллер достаточно универсальным и при этом простым. И это им удалось. Классическая Гарвардская архитектура сочетается с не столь строгими ограничениями в RISC подходе. Это еще не позволяет отнести микроконтроллер к CISC, но зато делает его "продвинутым RISC" (advanced RISC, как говорит сама Atmel). В общем, популярность AVR заслужена.

Здесь нет единого адресного пространства, как в STM8. И нет многоуровневого конвейера. А команды нельзя выполнять из ОЗУ. Зато здесь процессор имеет гораздо больше регистров, так что их даже можно (хоть и чрезвычайно условно, с большой натяжкой) назвать подобием кэш памяти данных. Хотя это, собственно говоря, просто расширение понятия регистр-аккумулятор.

При этом здесь нет и банков, и страниц, что сильно путают и пугают новичков в PIC. Зато есть нелогичное адресное пространство ввода-вывода. Почему не логичное скажу чуть позже. И есть "двойные стандарты" при распределении регистров внешних устройств. Но давайте со всем разбираться по порядку.

Регистры процессора AVR

Я опять не буду перечислять абсолютно все регистры, которые можно отнести к процессору. Но назову основные, которые важны для нас в контексте рассматриваемого в статье вопроса. Все регистры процессора имеют свои адреса, но говорить об этом я буду немного позже, когда доберемся до адресных пространств.

Регистр адреса команды PC

Как и во всех микроконтроллерах является указателем в адресном пространстве команд и содержит адрес команды, которая будет выбираться в следующем машинном цикле.

Регистр флагов состояния SREG

Как и следует из названия, хранит признаки состояния результата. Такие как перенос, перенос между тетрадами, признаки нулевого и отрицательного результата, переполнения. Прочие биты этого регистра будут обсуждаться в статьях посвященных системе команд и работе с прерываниями.

Указатель стека SP

Сам стек в AVR располагается в памяти данных, обычно, в самом ее конце . А регистр SP является указателем на вершину стека . Собственно говоря, регистр SP является регистровой парой SPH:SPL , причем SPH может отсутствовать в микроконтроллерах с малым объемом ОЗУ. Просто количество бит регистра SP может быть разным, в зависимости от доступного объема памяти данных.

Для работы со стеком существуют команды POP и PUSH.

Регистры общего назначения

Индексные регистры

Часть регистров общего назначения выполняют и специальные функции - индексных регистров. Всего есть три 16-битных индексных регистра .

Регистры R26 и R27 образуют регистровую пару R27:R26, которая соответствует паре XH:XL соответствующей индексному регистру X.

Регистры R28 и R29 образуют регистровую пару R29:R28, которая соответствует паре YH:YL соответствующей индексному регистру Y.

Регистры R30 и R31 образуют регистровую пару R31:R30, которая соответствует паре ZH:ZL соответствующей индексному регистру Z.

Таким образом, если вы используете индексные регистры для доступа к данным в ОЗУ использование соответствующих регистров общего назначения для других целей становится невозможным.

Адресное пространство ввода-вывода

В микроконтроллерах AVR есть команды IN и OUT, которые работают в адресном пространстве ввода вывода. Всего в этом пространстве может быть до 64 регистров внешних устройств .

При этом здесь же располагаются и уже упомянутые выше регистры процессора (SREG, SP). А это уже не логично. Но и это еще не все. Однако, для этого мы должны посмотреть на адресное пространство данных.

Адресное пространство данных (на самом деле объединенное)

Кроме собственно данных здесь же располагается стек , о чем я уже говорил ранее. Но кроме того, сюда же отображается и адресное пространство регистров общего назначения процессора, и адресное пространство ввода-вывода . Получается вот такая картина

Распределение адресного пространства данных микроконтроллеров AVR с необходимым количеством регистров оборудования не превышающих 64. Иллюстрация моя Распределение адресного пространства данных микроконтроллеров AVR с необходимым количеством регистров оборудования не превышающих 64. Иллюстрация моя

Я не стал здесь показывать область стека. Она обычно располагается в самом конце памяти данных. Однако, стек можно разместить в любом месте памяти данных просто проинициализировав регистр SP соответствующим значением при запуске программы. Сам же микроконтроллер загружает в SP при старте адрес последней ячейки памяти данных.

Обратите внимание, что здесь воедино соединены сразу три сущности! Во первых, регистры общего назначения. Во вторых, адреса ввода-вывода, но со смещением 20h. В третьих, собственно память данных (включая стек) начиная с адреса 60h .

Таким образом, мы можем обратиться к любому регистру общего назначения и просто указав его номер в соответствующей команде, и по его адресу в пространстве данных (например, загрузив его адрес в индексный регистр).

Более того, есть два способа получения доступа и к регистрам оборудования. Так регистр SREG имеет адрес 3Fh в пространстве ввода-вывода. И к нему можно обратиться например так

Но одновременно, он же доступен по адресу 3Fh+20h= 5Fh. А значит, возможно и такое

Но и это еще не все! Микроконтроллеры получали все новые встроенные модули и адресного пространства ввода-вывода стало не хватать. Поэтому часть регистров оборудования в некоторых моделях микроконтроллеров вынесли в отдельный блок "расширения регистров ввода-вывода". И разместился этот блок с адреса 60h .

Распределение адресного пространства данных микроконтроллеров AVR с необходимым количеством регистров оборудования больше 64. Иллюстрация моя Распределение адресного пространства данных микроконтроллеров AVR с необходимым количеством регистров оборудования больше 64. Иллюстрация моя

Всего может быть до 160 дополнительных регистров оборудования. И доступ к ним командами IN и OUT уже невозможен. А собственно память данных (включая стек) теперь начинается с адреса 100h .

Как определить, есть в микроконтроллере этот дополнительный блок регистров, или нет? Посмотрев в документации, это самый надежный способ. Дело в том, что название начинающееся с ATmega еще ни о чем не говорит, в данном случае. Так у ATmega32A дополнительного блока нет и данные можно размещать с адреса 60h. А вот ATmega328 или ATmega 644 такой блок имеют, а значит и данные могут размещаться только с адреса 100h.

Ничего данная организация памяти не напоминает? Нет, я не про STM8 с его единым адресным пространством и выделенным под регистры оборудования диапазоном. Я про PIC. Если убрать лишнее (по своей сути) пространство ввода-вывода, то мы получим тот же принцип размещения регистров и данных в едином пространстве!

Режимы адресации операндов в памяти данных

Я опущу описания неявной адресации, равно как и непосредственной (константа или литерал). Здесь нет никаких отличий от уже многократно рассмотренных случаев.

Я думаю, может не все, но очень многие знают, что в контроллерах AVR помимо основной оперативной памяти, а также памяти для хранения прошивки существует ещё и энергонезависимая память типа EEPROM. Данная память сделана по технологии электрического стирания информации, что в отличие от её предшественника EPROM, в котором стирание производилось только при помощи ультрафиолетовых лучей, позволило использовать данный тип памяти практически повсеместно. Как мы знаем, ещё существует энергонезависимая память типа Flesh, которая стоит намного дешевле, но у которой также есть существенный минус. Там невозможно стереть отдельный байт, стирание производится только блоками, что не совсем удобно в некоторых случаях, особенно когда информации требуется хранить немного, и информация данная представляет собой небольшие настроечные параметры. Поэтому нам стоит также остановиться на данном типе памяти. И причем не только из-за того, что он присутствует в контроллере, а из-за того, что это очень удобно для хранения некоторых величин, которые нужны нам будут даже после того, как контроллер потерял питание.

Так как мы работаем с контроллером Atmega8A, техническую документацию данного МК мы и откроем и увидим там, что всего такой памяти у нас 512 байт. Это тем не менее не так мало. Если мы, например будем какой-нибудь будильник программировать, чтобы данные установки не потерялись после отключения питания, мы вполне можем с вами обратиться к данной памяти. Также в документации написано, что данная память гарантированно переживёт 100000 циклов записи/считывания.

Теперь напрашивается вопрос. Как же организован процесс работы с данной памятью в микроконтроллере AVR? Как всегда, компания Atmel об этом позаботилась и организовала данный процесс на аппаратном уровне, что очень радует потому, что нам постоянно приходится беречь ресурсы контроллера. Для управления данным аппаратным уровнем существуют определенные регистры.

image00

Как именно мы будем адресоваться, мы увидим в процессе программирования EEPROM.

image01

В данный регистр мы будем записывать данные для того чтобы записать их в определённый адрес памяти EEPROM, а также чтобы считать их из определённого адреса той же самой памяти.

Ну и как водится, практически ни одна периферия и технология, организованная на аппаратном уровне, не обходится без управляющего регистра. У нас управляющим регистром является регистр EECR

image02

Давайте сразу немного познакомимся с битами данного регистра.

данных SRAM (ОЗУ), энергонезависимая EEPROM-память данных, а также конфигурационные ячейки, ячейки защиты, калибровочные ячейки и ячейки идентификаторов. Возможность доступа к различным ресурсам памяти приведена в табл.1.

Табл.1. Возможность доступа к различным ресурсам памяти.

Последовательное
программирование при низком напряжении

Параллельное и последовательное
программирование при высоком напряжении

Калибровочные
ячейки и ячейки идентификатора

Память программ FLASH

Коды программ микроконтроллера размещаются в энергонезависимом ПЗУ, выполненной по технологии FLASH. При нормальных условиях эксплуатации, FLASH-память позволяет сохранять свое содержимое в неизменном виде в течение 40 лет и допускает как минимум 10000 циклов стирания/записи.

Организация FLASH памяти программ ATmega8


Рис.5 Организация FLASH памяти программ ATmega8

Организация памяти программ, на примере ATmega8, приведена на рис.5. Размер FLASH-памяти этой модели составляет 8192 байт. Но, поскольку каждая команда занимает 2 или 4 байта, то по отношению к AVR точнее будет говорить об объеме в 4096 слов. Такая размерность определяет максимально возможное число слов команд (кодов операций), доступное при написании программы.

Для адресации памяти программ используется программный счетчик PC (Program Counter) (другое название: регистр или счетчик команд). Он представляет собой регистр, в котором находится текущей адрес команды во FLASH-памяти. Таким образом, счетчик команд адресует 16-разрядные слова, а не байты, и имеет переменную разрядность, которая зависит от размера FLASH. Так у ATmega8 (4096 слов), PC имеет разрядность 12 бит, у ATmega16 (8192 слова) - 13 бит и т.д. В архитектуре AVR, PC является недоступным для программиста регистром.

Помимо своего основного назначения (хранение команд), FLASH-память AVR-микроконтроллеров также позволяет хранить пользовательские данные произвольного типа: константы, таблицы, постоянные коэффициенты и т.д. Минимальный адресуемый элемент в этом случае 1 байт. Доступ к таким данным из прикладной программы производится различными модификациями инструкции lpm.

Как правило, алгоритмы работы микроконтроллера, записанные в памяти программ, не должны изменяться во времени. Однако у AVR все-таки имеется возможность модифицировать содержимое FLASH посредством собственного программного обеспечения. Обсуждению этого вопроса посвящена глава «Самопрограммирование микроконтроллеров AVR». Там же показан и другой способ разделения FLASH на секцию прикладной программы (Application Section) и секцию загрузчика (Boot Loader Section). Изменять содержимое памяти программ можно только из области загрузчика.

В процессе программирования запись во FLASH-память происходит постранично. Размер страницы зависит от объема памяти конкретной модели и может быть равен 32, 64 или 128 слов.

Читайте также: