Ассемблер для AVR (быстрый старт)

ram_chip

 

Данный пост это шпаргалка для тех, кто начинает осваивать ассемблер микроконтроллеров серии AVR. Используйте его как заготовку для ваших прошивок.

В качестве примера я использую ATmega8 и среду Atmel Studio.

Если вам нужно изучить или освежить память по ассемблерным командам AVR, рекомендую использовать:

Для понимания из чего состоит микроконтроллер, какая в нем периферия и как с ней работать, очень рекомендую ознакомиться с электронной книгой

 

Итак, ассемблерная заготовка для будущих программ

 

Начнем с комментариев. В ассемблере как и в других языках, традиционно есть два вида комментариев, однострочный «;» и многострочный «/* */«.

Код начинается с директивы «.include «m8def.inc»», которая подключает ассемблерный файл «m8def.inc» с константами имен регистров и их битов. Для каждого контроллера заголовочный файл свой.

Директивы «.nolist» и «.list» не обязательны, они несут вспомогательную роль. Ассемблерный код, который находится между данными директивами не выводится в выходной листинг (т.е. в файл *.lss). Это нужно для отладки.

Директивы «.set», «.equ», «.def» задают константы с значениями. В основном используют:

  • «.equ» — для обозначения имен регистров и их бит для портов ввода/вывода и периферии;
  • «.def» — для обозначения имен регистров общего назначения (т.е. для R0R31)

Кстати, подключаемый ранее файл «m8def.inc», как раз содержит «.equ» и «.def» описывающие регистры, для удобного обращения к ним по имени, не используя адрес. Например, регистр DDRB (для настройки пинов порта B на вход или выход), это просто адрес 0x17.

Вы можете найти файл «m8def.inc» в директории  вашего компилятора, если посмотрите в него, то увидите примерно следующее:

m8def

Это просто список констант с адресами регистров.

 

Директива «.cseg» обозначает начало программного сегмента, все что ниже, относиться к коду (или константам, об этом позже). Программатор разместит данный сегмент в FLASH памяти микроконтроллера, а по какому адресу? За это, отвечает следующая директива «.org» с параметром «0x00». В данном случае, её использовать не обязательно, т.к. начала программного кода всегда записывается с адреса 0x00.

Программный код микроконтроллера обязательно начинается с таблицы прерываний (или вектора прерываний). Каждая строка (а их 19) это команда для микроконтроллера, что делать, когда наступает, то или иное событие. Эти события называются прерываниями, они могут возникать от внутренних элементов периферии (таймеры, модуль SPI, модуль UART, компаратор, АЦП, EERPROM, I2C) и внешних устройств (вход INT0 и INT1 микроконтроллера). Вектор прерываний представляет собой, как правило, команды относительного перехода «rjmp» по меткам. Метка — это символьное обозначения адреса, при компиляции метка заменяется на адрес, это очень удобно, но можно использовать и абсолютный адрес. Очередность команд в векторе изменять не стоит как и положение самого вектора в FLASH памяти, сам вектор так и последовательность команд в нем железно прикреплены к тому или иному прерыванию.

 

При возникновения прерывания, например от модуля SPI, текущее выполнение программы прерывается и выполняется соответствующая строка из вектора прерывания. В ATmega8 это 11 строка (или 11 команда относительно начала FLASH памяти) с командой rjmp  0. Это возврат на первую команду, другими словами это программная заглушка. Т.к. этот код — заготовка, в вашем коде вместо 0 должна быть метка на программу обработчик данного прерывания. Обработчик может быть размещен в любом месте FLASH памяти, главное, перед первой командой обработчика установить его метку:

метка: команда
команда

команда

В комментариях вектора, я показал как это может выглядеть.

 

После вектора прерывания следует директива «.org 0x20», она здесь не обязательна, таким образом, я хотел показать, что программный сегмент можно помещать в разные области в памяти FLASH. Первая мысль, что 0x20 это адрес в байтах, но на самом деле в FLASH Atmega адресация происходит по словам (машинная инструкция), одно слово это 2 байта. Выходит, следующий фрагмент кода будет располагаться с 0x40 ого байта. Скомпилированный код, можно запустить в модели ATmega8 в программе Proteus, там же есть возможность посмотреть что находится в FLASH памяти контроллера. Зеленым я обозначил инструкции, которые представляют собой вектор прерывания, а голубым инструкции отвечающие за установка стека. Видно, что между инструкциями есть разрыв в виде 0xFF и инструкции по установки стека начинаются с адрес 0x40, как мы и установили.

HEX_org

Если закомментировать «.org 0x20» и скомпилировать код, инструкции в FLASH  буду расположены следующим образом.

HEX_no_org

Т.е. каждая инструкция следует одна за другой, без разрывов. Кстати, возможно вы заметили, но самый первый байт изменился с 0x1F на 0x12. 

«0x1FC0» и «0x12С0» это ассемблерная команда «rjmp  initial». Команда «rjmp» это «0xCo», а метка «initial» это «0x1F» (или 0x12). Т.к. код с меткой мы сдвинули с помощью «.org 0x20», то и адрес этой метки также изменился.

 

Далее следует обязательный код

Команды ldi и out отвечают за загрузку данных, первый параметр куда, второй, откуда данные брать.

Почему они имеют разное имя, если выполняют одно и тоже действие?

В ассемблере AVR все команды можно поделить на 4-е лагеря:

  • арифметические/логические,
  • команды перехода,
  • команды копирования,
  • команды по работе с битами.

Инструкции ldi и out относятся к командам копирования, различие заключается в том, что ldi загружают константу в выбранный «старшие» регистры общего назначения (от R16 до R31)), а команда out загружает значение из выбранного регистра общего назначения в регистры периферии контроллера (эти регистры еще называют регистры ввода/вывода). Различие только в источниках и приемниках данных. Т.к. ассемблер является низкоуровневым языком, то за каждой командой стоит реальный код инструкции процессора микроконтроллера. При копирование данных из разных источников в разные приемники, процессор выполняет разную последовательность действий, этим обусловлены, что команды копирования могут различаться.

Этот код поочередно загружает младший и старший байт 2-ух байтовой константы RAMEND в регистры общего назначения, а потом в регистр SPH.

Регистр SPH устанавливает вершину стека, а константа RAMEND определена в «m8def.inc» и хранит адрес последнего байта SRAM памяти. Выходит, мы просто устанавливаем вершину стека (вместо RAMEND вы можете использовать любой другой адрес).

Все что ниже, это ваши инструкции, здесь вы можете делать все что хотите. Как правило (но это не обязательно), сначала идет код инициализации периферии микроконтроллера (настройка таймеров, портов вввода/вывода, интерфейсов), далее код основной программы.

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

 

Для примера я приведу программу мигания светодиодом.

 

Этот код можно скомпилировать в Atmel Studio и запустить в программе моделирования Proteus.

Результат будет следующий:

led_assembler

 

Скачать HEX файл, который вы сможете запустить в модели Atmega8 в Proteus или прошить ваш Atmega8.

 

P.S. Данный код, заставит мигать светодиод с частотой близкой к тактовой, на реальном устройстве мигание вы не заметите. Для «замедления» желательно использовать программную задержку

 


 

Ниже, я разместил небольшую памятку по работе с FLASH и SRAM памятью на ассемблере. В main демонстрируется:

  • как считать данные из программной памяти FLASH в регистры;
  • как записывать данные из SRAM в регистры и наоборот, как считывать эти данные из SRAM в регистры.

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

 

Итак, сам код

 

Директива «.dseg» означает начало секции для SRAM, дальше с помощью меток и директивы .byte с параметром, мы можем резервировать в ОЗУ необходимые количество байт. Это аналог переменных в языках высокого уровня, имя метки это подобие имя переменных. Например

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

 

Для секции кода «.cseg», т.е. для FLASH памяти, также возможно резервировать области памяти, но с помощью директив «.db» и «.dw».

  • «.db»  — Резервирует байт, параметр это значение, которое будет находится в данной области. Значение должно быть размером не более одного байта.
  • «.dw» — Резервирует слово, другими словами 2-а байта, параметр это значение, которое будет находится в данной области. Значение должно быть размером не более 2-ух байт.



Буду признателен если вы поделитесь данным постом

Комментарии
  1. Виталий пишет:

    ЧЕМ! Чем компилировать? Какой оболочкой!?

  2. admin пишет:

    Добрый день!

    В начале статьи написано, что я использую Atmel Studio

  3. Андрей пишет:

    Вы допустили несколько ошибок в тексте, не буду говорить о незначительных, но дна очень серьёзная: вы пишете «Инструкции ldi и out относятся к командам копирования, различие заключается в том, что ldi загружают константу в выбранный регистр общего назначения (т.е. от R0 до R31), а команда out загружает значение из выбранного регистра общего назначения в регистры периферии контроллера (эти регистры еще называют регистры ввода/вывода).»

    Инструкция ldi применима лишь к старшим регистрам общего назначения, т.е. от R16 до R31.

  4. admin пишет:

    Спасибо за комментарий, исправил ошибку.



Ваш комментарий


Ответ в цифрах

 
© s-engineer.ru, 2012-2017 | Все права защищены