Категории публикаций
Подписка на рассылку по Email
новости (подписчиков: 2)
комментарии (подписчиков: 2)

Отменить подписку
Популярные публикации
Интересный опрос
Что вас больше всего интересует?

Заработок в интернете
Радиоэлектроника
Программирование
Операционные системы
Информационная безопасность
Саморазвитие, лайфхак
Спорт
Другое
Поблагодарить автора
donate
1B4ZZ2PXUd9E7AqEB82AqFnkpk2bt5hQG7

Первая программа для AVR микроконтроллера на Ассемблере

Размещено в категории: Микроконтроллеры

Приведен и подробно разобран пример простой программы для AVR микроконтроллера на языке Ассемблер (Assembler). Собираем простую схему на микроконтроллере для мигания светодиодами, компилируем программу и прошиваем ее в микроконтроллер под ОС GNU Linux.

Итак, у нас уже есть настроенный и подключенный к микроконтроллеру программатор, также мы разобрались с программой avrdude, изучили ее настройки и примеры использования. Пришло время разработать свою первую программу, которая будет выполнять какие-то реальные действия с AVR микроконтроллером (МК).

Писать программу мы будем на языке программирования Ассемблер (Assembler, Asm). Основной ее задачей будет заставить поочередно и с установленной задержкой мигать два разноцветных светодиода (красный и синий), имитируя таким образом полицейскую мигалку. В результате у вас получится простая электронная схема, которую можно вмонтировать в какой-то пластмассовый макет полицейского автомобиля и подарить ребенку для забавы.

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

Первый инструмент, который нам понадобится - редактор исходного кода, здесь можно использовать любой текстовый редактор. В одной из прошлых статей мы рассматривали настройку среды разработки программ Geany для программирования AVR микроконтроллеров с использованием языков Ассемблера и Си. В принципе там уже все готово, останется написать код программы и поочередным нажатием двух кнопок (Compile-Flash) скомпилировать и прошить программу в микроконтроллер.

Несмотря на то что у вас уже может быть настроена среда Geany, я приведу все консольные команды которые необходимы для компиляции и прошивки нашей программы в МК.

 

Принципиальная схема и макет

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

схема мигалки на светодиодах и микроконтроллере

Рис. 1. Принципиальная схема мигалки на светодиодах и микроконтроллере ATmega8.

Примечание: принципиальная схема нарисована за несколько минут в программе Eeschema, которая входит в комплекс EDA(Electronic Design Automation) программ KiCAD (для Linux, FreeBSD, Solaris, Windows). Очень мощный профессиональный инструмент, и что не мало важно - свободный!

Схема устройства состоит из микроконтроллера ATmega8 и двух светодиодов, которые подключены через гасящие резисторы. К микроконтроллеру подключен ISP-коннектор для осуществления программирования через программатор. Также предусмотрены клеммы для подключения внешнего источника питания напряжением 5В.

То как выглядит данная схема в сборе на макетной баспаечной панели (BreadBoard) можно посмотреть на рисунке ниже:

LED мигалка на микроконтроллере

Рис. 2. Конструкция светодиодной мигалки на микроконтроллере ATmega8.

К микроконтроллеру подключен программатор USBAsp, используя ISP интерфейс, от него же и будет питаться наша экспериментальная конструкция. Если нужно запитать конструкцию от внешнего источника питания напряжением 5В то достаточно его подключить к + и - линиям питания панели.

 

Исходный код программы на Ассемблере

Разработанная нами программа будет попеременно зажигать и гасить два светодиода. Светодиоды подключены к двум пинам PD0 и PD1 микроконтроллера.

Ниже приведен исходный код программы на Ассебмлере(Assembler, Asm) для микроконтроллера ATmega8. Сохраните этот код в файл под названием leds_blinking.asm для последующей работы.

; Светодиодная мигалка на микроконтроллере ATmega8
; http://ph0en1x.net

.INCLUDEPATH "/usr/share/avra/" ; путь для подгрузки INC файлов
.INCLUDE "m8def.inc"            ; загрузка предопределений для ATmega8
.LIST                           ; включить генерацию листинга

.CSEG                           ; начало сегмента кода
.ORG 0x0000                     ; начальное значение для адресации

; -- инициализация стека --
LDI R16, Low(RAMEND)  ; младший байт конечного адреса ОЗУ в R16
OUT SPL, R16          ; установка младшего байта указателя стека
LDI R16, High(RAMEND) ; старший байт конечного адреса ОЗУ в R16
OUT SPH, R16          ; установка старшего байта указателя стека

.equ 	Delay 	= 5   ; установка константы времени задержки

Program_name: .DB "Simple LEDs blinking program"

; -- устанавливаем все пины порта PORTD (PD) на вывод --
LDI R16, 0b00000011   ; поместим в регистр R16 число 3 (0x3)
OUT DDRD, R16         ; загрузим значение из регистра R16 в порт DDRD

; -- основной цикл программы --
Start:
    SBI PORTD, PORTD0 ; подача на пин PD0 высокого уровня
    CBI PORTD, PORTD1 ; подача на пин PD1 низкого уровня
    RCALL Wait        ; вызываем подпрограмму задержки по времени
    SBI PORTD, PORTD1 ; подача на пин PD1 высокого уровня
    CBI PORTD, PORTD0
    RCALL Wait
    RJMP Start        ; возврат к метке Start, повторяем все в цикле

; -- подпрограмма задержки по времени --
Wait:
    LDI  R17, Delay   ; загрузка константы для задержки в регистр R17
WLoop0:  
    LDI  R18, 50      ; загружаем число 50 (0x32) в регистр R18
WLoop1:  
    LDI  R19, 0xC8    ; загружаем число 200 (0xC8, $C8) в регистр R19
WLoop2:  
    DEC  R19          ; уменьшаем значение в регистре R19 на 1
    BRNE WLoop2       ; возврат к WLoop2 если значение в R19 не равно 0 
    DEC  R18          ; уменьшаем значение в регистре R18 на 1
    BRNE WLoop1       ; возврат к WLoop1 если значение в R18 не равно 0
    DEC  R17          ; уменьшаем значение в регистре R17 на 1
    BRNE WLoop0       ; возврат к WLoop0 если значение в R18 не равно 0
RET                   ; возврат из подпрограммы Wait

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

Все строки и части строк что начинаются с символа ";" - это комментарии, в приведенном выше примере старался все прокомментировать. При компиляции и выполнении программы такие строчки игнорируются, они служат для документирования и примечаний.

При помощи директивы ".INCLUDEPATH" мы указываем компилятору путь "/usr/share/avra/" по которому нужно искать файлы, которые будут включены в текущий файл с использованием директив ".INCLUDE". В нашем примере подключается файл, полный путь к которому "/usr/share/avra/m8def.inc".

Директива ".LIST" указывает компилятору что нужно включить генерирование листинга с текущего места в коде, отключить генерирование можно директивой ".NOLIST". Листинг представляет собою файл в котором содержится комбинация ассемблерного кода, адресов и кодов операций. Используется для отладки и других полезных нужд.

Директива ".CSEG" определяет начало программного сегмента (программный код). Для определения сегмента данных или памяти EEPROM используются директивы ".DSEG" и ".ESEG" соответственно. Таким образом выполняется распределение памяти по сегментам. Каждый из сегментов может использоваться в программном коде только раз, по умолчанию если не указана ни одна из директив используется сегмент кода.

При помощи директивы ".ORG" компилятору указывается начальный адрес "0x0000" сегмента данных, в данном случае это сегмент кода (CodeSEGment).

Дальше в коде происходит инициализация стека. Стек (Stack) - это область памяти (как правило у всех AVR чипов размещается в SRAM), которая используется микропроцессором для хранения и последующего считывания адресов возврата из подпрограмм, а также для других пользовательских нужд. Стек работает по принципу LIFO (Last In - First Out, последним пришёл - первым вышел). Для адресации вершины стека используется указатель стека - SP (Stack Pointer), это может быть однобайтовое или двухбайтовое значение в зависимости от доступного количества SRAM памяти в МК.

При помощи инструкции "LDI" мы загружаем значение младшего байта конечного адреса ОЗУ "Low(RAMEND)" (предопределенная константа в файле m8def.inc что содержит адрес последней ячейки SRAM) в регистр R16, а потом при помощи инструкции OUT выполняем вывод данного значения из регистра R16 в порт SPL (Stack Pointer Low). Таким же образом производим инициализацию старшего байта адреса в указателе стека SPH.

Если не произвести инициализацию стека то возврат из подпрограмм станет невозможным, к примеру в приведенном коде после выполнения инструкции перехода к подпрограмме "RCALL Wait" ворзврат не будет произведен и программа не будет работать как нужно. Новички часто упускают эту особенность и появляются вопросы на подобии: "В программе у меня все верно но она почему-то не работает в МК?".

Директива ".equ" выполняет присвоение указанному символьному имени "Delay" числового значения "5". Имя должно быть уникальным, а присвоенное значение не может быть изменено в процессе работы программы.

При помощи директивы ".DB" выполняется выделение байтов (для резервирования слов Double Word используется директива ".DW") в программной памяти или в EEPROM под указанные данные или же просто для резервирования и последующего использования. В данном случае у нас во FLASH-память вместе с программным кодом будет записана строка "Simple LEDs blinking program", которая содержит название программы. При каждом резервировании данных с использованием директивы ".DB" или ".DW" должна предшествовать уникальная метка которая пригодится когда нам нужно будет работать с этими данными, в нашем случае это "Program_name:".

Дальше мы устанавливает два канала (пины PD0, PD1) порта DDRD (PortD) на вывод, делается это загрузкой двоичного значения 0b00000011 (0x3, число 3) в регистр R16 с последующим выводом этого значения из него в порт DDRD при помощи команды OUT. По умолчанию все каналы (пины) порта настроены на ввод. При помощи двоичного числа 0b00000011, где последние биты установлены в 1, мы переводим каналы PD0 и PD1 в режим вывода. 

Начиная с метки "Start:" идет основной код программы.

При помощи инструкции "SBI" выполняем установку бита PORTD0 (предопределен в файле m8def.inc) в порте PORTD чем подаем на пин PD0 высокий уровень. Используя инструкцию "CBI" выполняется очистка указанного (PORTD1) бита в порте PORTD и тем самым подаем низкий уровень на пин PD1.

Дальше с помощью инструкции RCALL выполняем относительный вызов подпрограммы которая начинается с метки "Wait:".

После завершения подпрограммы (в нашем случае это задержка по времени) программа вернется к позиции где был выполнен вызов подпрограммы и с этого места продолжится выполнение последующих операторов.

После вызова подпрограммы задержки "Wait" следуют вызовы инструкций SBI и CBI в которых выполняется установка битов порта PORTD таким образом что теперь на пин PD0 у нас поступит низкий уровень, а на пине PD1 будет высокий уровень.

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

Таким образом выполняется реализация бесконечного цикла в котором будут "дергаться" пины порта PORTD  микроконтроллера и поочередно то зажигаться то гаснуть светодиоды что подключены к каналам данного порта (пины PD0, PD1).

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

При помощи инструкций "BRNE" (переход на метку если не равно) производит сравнение операндов и анализ установленных флагов процессора. При ее помощи выполняется контроль за значением флага нуля (Zero Flag, ZF), который будет установлен если после выполнения команды "DEC" над значением регистра в нем появится 0. Инструкция "BRNE" требует 1/2 такта процессора.

Таким образом, использовав несколько вложенных циклов, ми заберем у ЦПУ некоторое количество тактов и выполняем некоторую задержку по времени которая требуется на обработку данных во вложенных циклах. По умолчанию, без установки фьюзов что задают источник и частоту тактового генератора, в микроконтроллере ATmega8 используется откалиброванный внутренний RC-генератор с частотой 1МГц. Если же мы изменим частоту МК на 4Мгц то наши светодиоды начнут мигать в 4 раза быстрее, поскольку на каждую операцию вычитания и сравнения будет тратиться в 4 раза меньше времени.

Завершается подпрограмма инструкцией "RET", которая выполняет возврат из подпрограммы и продолжение выполнения инструкций с того места где эта подпрограмма была вызвана (на основе сохраненного адреса возвращения, который сохранился в стеке при вызове инструкции "RCALL").

 

Документация по Ассемблеру

Разобраться с основами языка программирования Ассемблер в пределах одной статьи достаточно сложно, без практики здесь никак, но тем не менее на начальном этапе и для нашего эксперимента приведенных знаний вполне достаточно. У вас уже будет базовое представление что такое программа на Ассемблере и как используются директивы и инструкции.

Процесс дальнейшего изучения Ассемблера для AVR микроконтроллеров полностью в ваших руках. Есть достаточно много полезных ресурсов в интернете, книг и материалов с примерами и пояснениями.

Приведу несколько полезных документов, которые вы можете скачать и использовать для справки при разработке программ на AVR ASM.

Справка по Ассемблеру для Atmel AVR (перевод Руслана Шимкевича): atmel-avr-assembler-quick-doc-ru.zip (16Кб, HTML, RU).

Справка по инструкциям Atmel Assembler: atmel-avr-instruction-set-manual-en.pdf.zip (700Кб, PDF, EN, 2015).

 

Работа с числами в Hex, Bin и Dec

В коде программы для загрузки значений в регистры используются числа и в скобках приведены их значения в шестнадцатеричной системе счисления, например: "50 (0x32, )". В двоичной системе счисления числа указываются в формате "0b00000011". Для удобной переконвертации чисел из шестнадцатеричной системы счисления в десятичную, двоичную и наоборот отлично подходит программный калькулятор из среды рабочего окружения KDE - KCalc.

KCalc

Рис. 3. KCalc - простое и эффективное решение для пересчета между разными системами счисления.

В настройках (Settings) нужно выбрать режим (Numeral System Mode), после чего программа приобретет вид что на рисунке выше. Переключаться между системами счисления можно устанавливая флажки в полях "Dec", "Hex", "Bin". Для примера: переключаемся в Hex и набираем "FF", потом переключаемся в Dec и видим число в десятичной системе счисления - 255, просто и удобно.

В операционной системе GNU Linux с рабочей средой GNOME (например Ubuntu) также есть подобный калькулятор, это программа - galculator.

 

Компиляция и прошивка программы в МК

 Итак, у нас уже есть полный код программы, который мы сохранили в файл с именем "leds_blinking.asm". Теперь самое время скомпилировать его, делается это нажатием кнопки "Compile" в предварительно настроенной среде Geany или же отдельной командой в консоли:

avra --includepath /usr/share/avra/ leds_blinking.asm

Если результат выполнения будет без ошибок то мы получим файл прошивки в формате Intel HEX - "leds_blinking.hex", который уже можно прошивать во флешь-память микроконтроллера.

Примечание: опцию "--includepath /usr/share/avra/" можно и не указывать, поскольку в файле с исходным кодом уже была указана директива ".INCLUDEPATH" для поиска файлов с предопределениями для разных моделей МК.

Осталось прошить микроконтроллер используя полученный файл "leds_blinking.hex". В примере я использую программатор USBAsp и микроконтроллер ATmega8, вот так выглядит команда для записи получившегося файла во флешь-память МК:

avrdude -p m8 -c usbasp -P usb -U flash:w:leds_blinking.hex

Примечание: в команде используется относительный путь к файлу leds_blinking.hex, поэтому для успешного выполнения команды нужно перейти в терминале(консоли) в директорию где находится данный файл.

Сразу же после прошивки флешь-памяти на микроконтроллер поступит команда сброса (RESET) и программа начнет выполняться, об єтом будут свидетельствовать два попеременно мелькающих светодиода.

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

 

Заключение

Увеличив значение константы "Delay" можно уменьшить частоту мерцания светодиодов, а уменьшив - увеличить частоту. Также можете попробовать добавить несколько светодиодов к свободным каналам порта (PD2-PD7) и модифицировать программу таким образом чтобы получить бегущий огонь из светодиодов.

В заключение приведу краткое видео работы рассмотренной схемы на двух светодиодах:

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

Начало цикла статей: Программирование AVR микроконтроллеров в Linux на языках Asembler и C.

 (0/5) голосов: 0   просмотров: 686



Комментарии к публикации (2)
renych 1 renych 
16 Ноябрь 2016 13:07

Стэк работает по принципу FILO, а не FIFO/.

+1 
ph0en1x 2 ph0en1x 
16 Ноябрь 2016 15:18

Благодарю за замечание, исправил вот так: LIFO (last in - first out, последним пришёл - первым вышел).

0 

Добавить комментарий captcha