arduino установка на линукс

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

от автора

в

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

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

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

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

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

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

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

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

Попробуем зажечь светодиод, к примеру на PB1. Логично, что если мы хотим использовать порт на вывод данных (отправить логическую единицу, т.е. 5В на выход порта), то нам совсем не нужен регистр PIN, зато очень нужны DDR и PORTB. Смотрим на Arduino Uno, и ничего не понимаем. Где же PB1, который явно видно в даташите? Для удобства и создания своей экосистемы, компания 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 ide, использует в качестве компилятора avr-gcc, поэтому ничего нового здесь изобретать не нужно, все команды из даташита на atmega, нативно работают и тут. Правда для вставки асм-кода необходимо использовать конструкцию

asm volatile
(
” asm-code ” “\n”
);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Комментарии

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