Posted by lex232 on

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Правильно было бы изучать работу микронтроллера на языках более низкого уровня, чтобы немного иметь представление как логическая 1 прилетает на конретный pin нашей микросхемы. Современные среды разработки позволяют отправить логическую 1 на выход одной командой, но новички должны понимать, для чего это вообще нужно, ведь зажигать светодиоды — одно, а разбираться с библиотекой, которая отправляет 0 и 1 в огромном количестве на дисплей или датчик — другое дело.

Понимая работу команд на низком уровне, можно будет разобраться с ошибками, которые выйдут за пределы стандартных уроков, когда вы будете конструировать что-то своё. И так, наша подопытная плата будет Arduino Uno, а в её сердце находится atmega328, это 8-битный микроконтроллер AVR. Откроем даташит на неё, и посмотрим ключевые моменты, которые могут нам помочь начать ориентироваться в этой микросхеме.

Самые главные блоки, где хранятся данные в процессоре — это регистры. В нашем микронтроллере, регистров общего назначения (РОН) — 32 штуки, все они 8-битные, за некоторым исключением. 8 битные — значит можно временно хранить данные вида 0b01010101, что в десятеричном представлении будет от 0 до 255. Называются эти регистры R0 — R31, но программируя в arduino, никто и никогда не использует их, за вас это делает компилятор. Он сам решает, какие нужно задействовать, когда вы назначаете значение переменной или выполняете простые арифметические операции. Эти регистры мы используем для хранения данных.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Кстати, про исключения — последняя группа регистров R26-R31 можно использовать для указания адреса при косвенной адресации SRAM, группируются из 8 битных и называются X,Y,Z. Если проще, для них есть инструкции, чтобы делать операции смещения, декремента и инкремента, но также к ним можно обращаться, и как к 8-битным обычным регистрам.

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

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

1. DDR (Data Direction Register)— регистр направления данных — от его инициализации зависит, будет работать порт на ввод данных или вывод.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

PIN (Port INput) — регистр для чтения состояния порта. Интересная деталь — считать логический уровень можно при любой конфигурации DDR и PORTB. Даже если порт настроен на выход — всё равно можно узнать его состояние.

Смотрите также:  Демо плата на AVR и глаза робота
Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

PORT (Data Register) — регистр данных порта. Этот регистр позволяет принимать выходное значение порта в виде логической 1 или 0. Но при условии, что DDR у нас установлен на вывод данных. Если же порт сконфигурирован на ввод данных, то от значения регистра PORT будет зависеть, подтянута ли ножка сопротивлением к питанию, или контакт просто висит в воздухе для приёма данных (состояние HI-Z). Кстати в таком состоянии, без внешнего подтягивающего резистора, порт оставлять нежелательно, ибо на входе будет бесконечный поток 0 и 1, что может нежелательно сказаться на стабильности МК.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Попробуем зажечь светодиод, к примеру на PB1. Логично, что если мы хотим использовать порт на вывод данных (отправить логическую единицу, т.е. 5В на выход порта), то нам совсем не нужен регистр PIN, зато очень нужны DDR и PORTB. Смотрим на Arduino Uno, и ничего не понимаем. Где же PB1, который явно видно в даташите? Для удобства и создания своей экосистемы, компания Arduino сделала так сказать ремаппинг пинов, но конечно, исходный пин никуда не пропал, и его легко найти.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Подключаем катод светодиода к выводу GND, а анод через резистор к выводу с номером 9, что и будет являться PB1. Рассмотрим сначала код, который зажжет светодиод на чистом arduino

void setup() {
pinMode(9, OUTPUT);
digitalWrite(9, HIGH);
}
void loop() {}

Первой командой pinMode(9, OUTPUT); устанавливаем пин 9 в режим вывода данных, а второй digitalWrite(9, HIGH); переводим пин9 в состояние логической единицы, т.е. 5В, загружаем в плату, наблюдаем, что светодиод стал светиться. Всё понятно и просто

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Теперь попробуем тоже самое проделать на ассемблере, используя ту же среду ардуино. В принципе arduino ide, использует в качестве компилятора avr-gcc, поэтому ничего нового здесь изобретать не нужно, все команды из даташита на atmega, нативно работают и тут. Правда для вставки асм-кода необходимо использовать конструкцию

asm volatile
(
» asm-code » «\n»
);

Попробуем поработать с командой LDI — Load Immediate , как легко понять из названия она загружает значение в выбранный нами регистр. Помните, схему Регистров общего назначения? Команда LDI, как и ряд других команд, может работать только со старшими регистрами под номерами r16-r31. Попробуем это проверить.

Смотрите также:  что даёт Microsoft Bizspark?
Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

При попытке записать значение в регистр r10, мы получили ошибку от компилятора, причём она даже сообщил нам, что необходим регистр свыше номера 15. Кстати, почему я вообще пытаюсь загрузить число 0b00000010 в Бинарном представлении?, я его так записал для простоты понимания.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Помните мы выбрали PB.1 для нашего светодиода? Нумерация порта следует от 0 до 7, это и есть 8 бит данных порта, значит число 0b00000010, активирует бит номер 1, т.е. записав это значение в правильный регистр DDR, мы сделаем пин 1 порта B, активным на выход. Ещё раз внимательно посмотрим даташит, на странице с DDRB

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Видим странную строчку, что при использовании порта, как порт ввода-вывода, происходит смещение адреса на 0x04. Значит именно туда нам нужно записать значение 0b00000010. Но ведь команда LDI работает только с регистрами от r16, а если посмотреть ещё раз даташит, то r16 это 0x10, значит компилятор опять выдаст ошибку.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Поэтому, наше значение мы записываем в любой регистр от r16 до r31, а в адрес 0x04, мы можем переместить значение другой командой OUT. Команда OUT — записывает данные из регистра в регистры I/O. Применяем команду OUT к регистру 0x04, и переносим туда значение из регистра r16.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Код скомпилировался, но светодиод ещё не горит. Ведь мы назначили направление порта на Вывод, но значение равно 0, т.е. логический ноль. Попробуем разобраться, что поменялось. Запустим proteus

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

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

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Опять видим строчку, что при использованию команд ввода-вывода In и Out, нужно использовать адресацию 0х05. Установим значение PB1 равным 1. Для загрузки значения будем использовать тот же регистр, ведь значение 0b00000010 не поменялось для PB1

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Теперь то светодиод загорелся

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Теперь для наглядности поменяем конфигурацию порта, сделаем все пины порта B на выход, а в логическую 1, переведём PB1 и PB 3. Для двух переменных, задействуем ещё один регистр старше r16, например r17

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Компилируем, и смотрим в Proteus.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Теперь у порта B, нет серых выходов — все синие (т.е. лог. 0), кроме PB1 и PB3. Видите, как точно пины порта повторили переменную 0b00001010. Теперь вернём всё как было, зажжём один светодиод, и сделаем его мигающим. Для этого нам нужно ввести задержку, и если на arduino это одна команда delay(1000); , то на ассемблере, конечно такой команды нет. Но так-как наш урок не об этом, то мы просто возьмём готовый код с Калькулятора задержек AVR и поместим его в наш. На этом сайте можно установить нужную задержку, например 500 миллисекунд. Готовый код содержит в себе три цикла с пустыми командами — естественно это самый простой тип задержки, во время выполнения которого, процессор ни на что не реагирует.

Смотрите также:  Стедикам своими руками
Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Можно даже выбрать наш компилятор avr-gcc, и он заключит код в конструкцию asm volatile, что нам и нужно. В интернете много примеров, как использовать переход на метку rjmp или rcall, но так-как мы в среде arduino, и у нас уже есть бесконечный цикл main loop, то можно выйти из ситуации проще — например инвертировать переменную в конце программы, которую мы загружаем в регистр PORTB. Прямой команды инверсии нужного бита, как в С++ здесь нет, но сделать это можно разным путём — например использовать команду COM — выполняет дополнение до единицы (реализует обратный код) содержимого регистра. Но так мы затронем остальные биты порта, а нам нужно поменять только PB1, лучше использовать EOR — исключающее или, подобрав в дополнение нужную переменную маску. Пусть это будет R21 с таким же значением 0b00000010. Смотрим таблицу истинности

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

eor r17, r21 — в регистре r17, значение будет постоянно меняться с 0b00000010 на 0b00000000, потому-что изначально 1 с 1 даст 0, а потом 0 с 1, даст 1, и так по кругу. В итоге код должен получится такой

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Загружаем в плату — диод мигает, что и требовалось получить.

Зажигаем светодиод в Arduino на ассемблере, изучая даташит на МК.

Конечно, реальный случай использования асм- вставок в ардуино, наверное очень редкий, но если уже написана большая программа в этой среде, и есть какие то глюки или нужно ускорить быстродействие, то возможно это получится реализовать с помощью ассемблера. Можно найти много примеров, какой «грязный» код иногда делает компилятор ардуино. Здесь, я больше хотел показать любителям ардуино, как на деле происходит взаимодействие программы и МК, на примере документации портов ввода-вывода. В следующих статьях будем рассматривать реальные устройства, но не будем забывать обращаться к архитектуре МК.