Доброго дня уважаемые радиолюбители!
Приветствую вас на сайте “Радиолюбитель“
Что такое микроконтроллер, и для чего он нужен. Давайте обратимся к его определению:
Микроконтроллер – микросхема, предназначенная для управления электронными устройствами, или по другому – простенький компьютер (микро-ЭВМ), способный выполнять несложные задачи.
То есть, по сути, микроконтроллер – это устройство, позволяющее воплотить в жизнь наши идеи (даже бредовые), но, естественно, в пределах своих возможностей. И самое главное, воплощение идеи в жизнь достигается не созданием навороченных электронных конструкций, а лишь только, в основном, силой нашей мысли (желаете стать волшебником?).
Наибольшей популярностью у радиолюбителей пользуются два вида микроконтроллеров:
– PIC – фирмы Microchip Technology
– AVR – фирмы Atmel
Сразу хочу сделать небольшое отступление и пояснить одну свою позицию. Я не собираюсь ни сейчас, ни потом, рассуждать о достоинствах того или иного вида микроконтроллеров, того или иного программного обеспечения, и вообще всего, что связано с микроконтроллерами, что-то советовать, но а тем более – навязывать читателям. Все это дело вкуса, личных предпочтений и поставленных конечных целей в изучении микроконтроллеров. Ну а так как “необъятное – не объять”, все свое дальнейшее повествование я буду вести применительно к микроконтроллерам AVR и, не очень распространенной, но мной любимой, программы “Algorithm Builder”. У разных типов микроконтроллеров, программ, есть, конечно, различия, но многое у них и общее. А познавать мир микроконтроллеров мы будем так, чтобы потом, полученные знания можно было бы без проблем применить и к PICам, и к любому программному обеспечению. И еще раз напомню, данная серия статей – моя попытка помочь тем, кто впервые услышал о существовании микроконтроллеров и желает постичь работу с ними.
Что нужно для того, чтобы научиться работать с микроконтроллерами? Я бы выделил несколько, на мой взгляд, главных условий:
1. Желание и настойчивость.
Тут все очень просто: есть желание – все получится. А желание с настойчивостью – вообще, вещь суперская.
2. Знание устройства микроконтроллера.
Здесь не важны глубокие знания (да может и вообще не нужны), но знать, что имеется “на борту” микроконтроллера необходимо. Только зная из чего состоит микроконтроллер, какие устройства в нем есть, их возможности, как они работают – только тогда мы сможем использовать возможности микроконтроллера на полную катушку.
3. Знание языка программирования и команд управления микроконтроллером.
Как будет работать микроконтроллер, какие задачи вы на него возлагаете и как он будет их выполнять, определяется заложенной в него программой – программой которую для микроконтроллера составляете вы сами. И на этом пункте мы остановимся несколько подробней, чтобы рассмотреть вопросы, которые могут появиться в будущем.
Программа (в переводе это слово означает – “предписание”) – предварительное описание предстоящих событий или действий.
К примеру, мы хотим, чтобы микроконтроллер мигал светодиодом. Простенькая задача, но тем не менее, для того, чтобы микроконтроллер выполнил эту задачу, мы предварительно должны, шаг за шагом, описать все действия микроконтроллера, написать программу, которую он должен выполнить для получения нужного нам результата – мигающий светодиод. Нечто, вроде такого:
♦ Зажечь светодиод:
— настроить вывод к которому подключен светодиод для работы на вывод информации
— подать на этот вывод логический уровень, который позволит зажечь светодиод
♦ Подождать некоторое время:
— перейти к подпрограмме формирующей паузу (которую тоже нужно “разжевать”)
— по выполнению подпрограммы паузы вернуться в основную программу
Алгоритм – набор инструкций, описывающих порядок действия для достижения нужного результата.
Если в программе мы подробнейшим образом прописываем действия микроконтроллера, то в алгоритме мы определяем порядок действий микроконтроллера, на основе которых мы потом создадим программу. По аналогии с вышеприведенном примером:
♦ Зажечь светодиод
♦ Подождать некоторое время
♦ Погасить светодиод
и так далее.
Таким образом, алгоритм – это предшественник программы. И чем тщательно и продумано будет создан алгоритм, тем проще будет создавать программу.
Итого, программа для микроконтроллера – это последовательность действий микроконтроллера в виде набора команд и инструкций, которые он должен выполнить для достижения поставленных нами целей.
Команды для микроконтроллера имеют вид набора единичек и нулей:
00110101 011000100
так называемые – коды команд, а коды команд – это язык который понимает микроконтроллер. А для того, чтобы перевести наш алгоритм с русского языка на язык микроконтроллера – в эти самые наборы нулей и единичек, существуют специальные программы.
Эти программы позволяют описать порядок работы для микроконтроллера на более-менее понятном для нас языке, а затем перевести этот порядок на язык понятный микроконтроллеру, в результате чего получается так называемый машинный код – последовательность команд и инструкций (те самые нули и единички) которые только и понимает микроконтроллер. Текст программы, написанный программистом, называется исходным кодом. Перевод программы с языка программирования (исходного кода) на язык микроконтроллера (машинный код) производится трансляторами. Транслятор превращает текст программы в машинные коды, которые потом записываются в память микроконтроллера.
В таких программах порядок работы микроконтроллера описывается специальным языком – языком программирования. Язык программирования отличается от нашего, человеческого языка. Если наш язык общения служит в основном для того, чтобы обмениваться информацией, то:
Язык программирования – это способ передачи команд, инструкций, чёткого руководства к действию для микроконтроллера.
Существует множество языков программирования и их можно разделить на два типа:
– языки программирования низкого уровня
– языки программирования высокого уровня
Чем они отличаются. А отличаются они своей близостью к микроконтроллеру.
01000110
10010011
01010010
Вряд-ли кто сможет разобраться в таком наборе комбинаций из двух цифр, а труд первых программистов был очень трудоемкий. Для облегчения своей жизни, программисты и стали создавать первые языки программирования. Так вот, чем ближе язык программирования к такому набору нулей и единиц тем больше он “низкого уровня”, а чем дальше от них – тем больше “высокого уровня”.
Самые распространенные языки программирования для микроконтроллеров:
— язык низкого уровня – Ассемблер
– язык высокого уровня – С (Си)
Давайте посмотрим на примере их различия (эти примеры абстрактные).
Допустим нам надо сложить два числа: 25 и 35.
В машинных кодах эта команда может выглядеть так:
Ну что, я думаю и тут нам все понятно, – язык программирования изучать надо, по-другому – никак.
Команды и инструкции для управления микроконтроллером.
У микроконтроллеров AVR более 130 различных команд, которые позволяют ему реализовать все заложенные в нем возможности. Но сразу скажу – мало кто из любителей знает их все и тем более пользуется всеми. Обычно, в любительской практике хватает знания и половины команд, а то и меньше. Но изучать команды надо. Чем больше команд вы будете знать, тем изощреннее (в хорошем смысле слова) и элегантнее программы будут получаться.
Итого, будем считать, что желание у нас есть, настойчивость проявим, язык изучим, команды освоим, и, как итог, – одержим победу!
В следующей статье мы побеседуем, о том, что часто вгоняет начинающих в ступор – о системах счисления и их связи с микроконтроллером.
Следующие статьи:
♦ Микроконтроллер и системы счисления
♦ Микроконтроллер и логические операции
♦ Общее устройство микроконтроллера
♦ Арифметико-логическое устройство и организация памяти – память программ, память данных, энергонезависимая память
♦ Регистры общего назначения, регистры ввода/вывода, стек, счетчик команд
♦ Регистр состояния SREG
♦ Порты ввода/вывода микроконтроллера
Эта глава (и несколько следующих) посвящены программированию 8-битных микроконтроллеров семейства ATMega328, выпускаемых фирмой Atmel и использующихся повсеместно во всевозможных преобразователях сигналов и системах управления. Архитектура AVR, на которой основаны упомянутые микроконтроллеры, достаточно проста для использования в учебных целях и достаточно репрезентативна для того, чтобы составить представление об устройстве ЭВМ и об их программировании на уровне машинных кодов.
Для начала перечислим основные характеристики микроконтроллера ATMega328 и кратко прокомментируем их смысл.
Тактовая частота: регулируемая, не более 20 МГц. Частота (в герцах) – количество тактов, подаваемых на микросхему за одну секунду. Исполнение одной команды занимает один или несколько тактов (это утверждение, вообще говоря, не верно для более современных архитектур; например, для x86_64, используемой на персональных компьютерах).
Оперативная память: 2048 байт. Используется для хранения промежуточных данных, используемых для нужд решаемой задачи. Хранит данные только пока устройство включено. При выключении или перезагрузке устройства данные из оперативной памяти теряются.
EEPROM: 1024 байта. Хранит данные даже при выключенном питании. Используется в первую очередь для параметризации решаемой устройством задачи.
Память программы: 32768 байт. Хранит программу в виде последовательности команд. Большинство команд занимают 2 байта. Незначительное количество команд занимают 4 байта. Обычно последние 1-2 килобайта заняты программой-загрузчиком, которая позволяет обновлять программу, записанную в устройство, при помощи последовательного входа.
Первые 256 байт оперативной памяти ATMega328 имеют специальную роль. Байты с 0го по 31й (нумерация байт традиционно начинается с нуля) называются рабочими регистрами. Рабочие регистры могут являться операндами для команд процессора. Всем командам, использующим рабочие регистры, доступны регистры с 16го по 31й. Некоторым командам доступны и первые 16 рабочих регистров. Рабочие регистры имеют традиционные названия: r0
, r1
, r2
, … r31
. Пара регистров
r26
и r27
известна под названием X
. Пара регистров r28
и r29
называется Y
. Последняя пара регистров (r30
и r31
) называется Z
.
Остальные 224 байта называются регистрами ввода-вывода и используются для общения с периферией.
Ассемблером называется набор человекочитаемых названий для команд процессора; язык программирования, основанный на этих названиях; а также – приложение, предназначенное для перевода человекочитаемого текста программы на этом языке в кодировку, понимаемую процессором, для которого эта программа написана.
Для трансляции программ мы будем использовать приложение-ассемблер GNU as. Для Windows это приложение доступно в составе приложения WinAVR. Для POSIX-систем это приложение входит в состав GNU binutils.
Сборка программы происходит в четыре этапа.
Текст программы записывается в текстовый файл (далее – foobar.S
).
Текст программы скармливается приложению avr-as
командой
avr-as foobar.S -o foobar.o
Полученный промежуточный файл скармливается приложению avr-ld
командой
avr-ld foobar.o -o foobar.elf
Полученный файл скармливается приложению avr-objcopy
командой
avr-objcopy -O ihex foobar.elf foobar.hex
Итоговый файл foobar.hex
содержит машинный код программы в формате Intel Hex. Этот формат де факто является стандартным для приложений, записывающих программу в память микроконтроллера.
На сайте доступен эмулятор, который позволяет пошагово исполнять программу, записанную в формате Intel Hex, и иллюстрирует некоторые процессы, происходящие при исполнении этой программы.
Приведём пример программы, складывающей числа 12 и 21.
LDI r16, 12
LDI r17, 21
ADD r16, r17
barrier:
RJMP barrier
В ней мы видим 4 команды и метку barrier
. Метка не соответствует никакой команде процессора и используется только как аргумент для команд перехода.
Собрав эту программу так, как описано в предыдущем разделе, и выполнив её в эмуляторе, можно наблюдать как сначала в регистр r16
попадает число 12, затем в регистр r17
попадает число 21, затем сумма этих чисел записывается в регистр r16
, после чего программа зацикливается на последней инструкции.
Здесь перечислены те команды, которые нам понадобятся на первых порах.
Для перемещения данных используются следующие команды:
LDI куда, что
– перемещение указанного числа в указанный регистрLD куда, откуда
– перемещение байта из оперативной памяти в указанный регистр; второй аргумент – либо X, либо Y, либо Z – задаёт пару регистров, в которой записан адрес ячейки памятиMOV куда, откуда
– перемещение байта из регистра в регистрST куда, откуда
– перемещение байта из регистра в оперативную память; первый аргумент – X, Y или ZАрифметические операции (наиболее важные):
ADD куда, откуда
– прибавление числа из указанного регистра к числу в указанном регистреSUB куда, откуда
– вычитание числа из указанного регистра из числа в указанном регистреSUBI куда, что
– вычитаение указанного числа из числа в указанном регистре (обратите внимание, что команды ADDI
нет; SUBI
для большинства целей заменяет ADDI
)NEG что
– изменение числа в указанном регистре на противоположноеAND куда, откуда
– поразрядная конъюнкция чисел в указанных регистрахOR куда, откуда
– поразрядная дизъюнкция чисел в указанных регистрахEOR куда, откуда
– поразрядная исключающая дизъюнкция чисел в указанных регистрахANDI куда, что
– поразрядная конъюнкция указанного числа и числа в указанном регистреORI куда, что
– поразрядная дизъюнкция указанного числа и числа в указанном регистре (обратите внимание, что команды EORI
нет)Сложение и вычитание производятся в арифметике по модулю 256, т. е с «переходом» через 0. Если такой переход произошёл, то этот факт запоминается выставлением 1 в специальный бит переноса (carry flag). Если перехода не было, в бит переноса попадает 0.
ADC куда, откуда
– прибавление суммы числа из указанного регистра и бита переноса к числу в указанном регистреSBC куда, откуда
– вычитание суммы числа из указанного регистра и бита переноса из числа в указанном регистреINC что
– увеличение значения в указанном регистре на 1; бит переноса не затрагиваетсяDEC что
– уменьшение значения в указанном регистре на 1; бит переноса не затрагиваетсяТакже результат арифметических операций отражается на бите нуля: он выставляется, если результат операции равен нулю.
Эти биты можно исползовать для условных переходов:
BREQ метка
– переход по метке, если выставлен бит нуляBRNE метка
– переход по метке, если не выставлен бит нуляBRCS метка
– переход по метке, если выставлен бит переносаBRCC метка
– переход по метке, если не выставлен бит переносаТакже есть безусловный переход:
RJMP метка
– переход по меткеСкажем напоследок, что микроконтроллеры архитектуры AVR поддерживают ещё несколько более сложных арифметических операций, о которых мы поговорим позже.
При выполнении заданий не забывайте ставить в конце программы барьер вида RJMP
на себя же. Постарайтесь выполнить задания, пользуясь только инструкциями, описанными в предыдущем разделе.
r0
единицу, если число, записанное в r16
, больше числа, записанного в r17
, и 0 – в противном случае.;; Решение. Здесь и далее считается, что входные данные уже заданы.
start: ;; часть меток используется просто в качестве комментариев
SUB r17, r16
BRCC greater_or_equal
less:
LDI r16, 1
MOV r0, r16
RJMP end
greater_or_equal:
LDI r16, 0
MOV r0, r16
end:
RJMP end
r0
единицу, если число, записанное в r16
, не меньше числа, записанного в r17
, и 0 – в противном случае.start:
SUB r16, r17
BRCC greater_or_equal
less:
LDI r16, 0
MOV r0, r16
RJMP end
greater_or_equal:
LDI r16, 1
MOV r0, r16
end:
RJMP end
r0
и r1
сумму чисел, записанных в регистры r16
и r17
. Младшие 8 бит суммы должны оказаться в регистре r0
.start:
LDI r18,0
MOV r1, r18
MOV r0, r16
ADD r0, r17
ADC r1, r18
end:
RJMP end
r0
и r1
произведение чисел, записанных в регистры r16
и r17
. Младшие 8 бит произведения должны оказаться в регистре r0
.start:
LDI r18, 0
MOV r0, r18
MOV r1, r18
;; это довольно стандартная проверка на равенство:
SUB r17, r18
BREQ end
loop:
ADD r0, r16
ADC r1, r18
DEC r17
BRNE loop
end:
RJMP end
Программа получает на вход два числа (в регистрах r16
и r17
). На выходе (в регистре r0
) должен оказаться остаток от деления r16
на r17
. Если r17=0
, то в регистре r1
должен оказаться код ошибки 1. В противном случае код ошибки должен быть равен 0.
Программа получает на вход два числа (в регистрах r16
и r17
). На выходе (в регистре r0
) должно оказаться неполное частное от деления r16
на r17
. Если r17=0
, то в регистре r1
должен оказаться код ошибки 1. В противном случае код ошибки должен быть равен 0.
Программа получает на вход число (в регистре r16
). На выходе (в регистрах r0
и r1
) должна оказаться сумма всех целых чисел от 1 до r16
. Разряд r0
– младший.
Программа получает на вход число (в регистре r16
). На выходе (в регистрах r0
и r1
) должно оказаться произведение всех целых чисел от 1 до r16
(взятое по модулю 65536). Разряд r0
– младший.
Программа получает на вход два числа (в регистрах r16
и r17
). На выходе (в регистре r0
) должен оказаться их НОД. Считается, что НОД пары нулей равен нулю.
Содержание:
Исходные коды
Компилятор работает с исходными файлами, содержащими инструкции, метки и директивы. Инструкции и директивы, как правило, имеют один или несколько операндов.
Строка кода не должна быть длиннее 120 символов.
Любая строка может начинаться с метки, которая является набором символов заканчивающимся двоеточием. Метки используются для указания места, в которое передаётся управление при переходах, а также для задания имён переменных.
Входная строка может иметь одну из четырёх форм:
[метка:] директива [операнды] [Комментарий]
[метка:] инструкция [операнды] [Комментарий]
Комментарий
Пустая строка
Комментарий имеет следующую форму:
; [Текст]
Позиции в квадратных скобках необязательны. Текст после точки с запятой (;) и до конца строки игнорируется компилятором. Метки, инструкции и директивы более детально описываются ниже.
label:════ .EQU var1=100 ; Устанавливает var1 равным 100 (Это директива)
══════════ .EQU var2=200 ; Устанавливает var2 равным 200
test:═════ rjmp test════ ; Бесконечный цикл (Это инструкция)
════════════════════════ ; Строка с одним только комментарием════════════════════════ ; Ещё одна строка с комментарием
Компилятор не требует чтобы метки, директивы, комментарии или инструкции находились в определённой колонке строки.
Ниже приведен набор команд
процессоров AVR, более детальное
описание их можно найти в AVR Data Book.
═
Мнемоника | Операнды | Описание | Операция | Флаги | Циклы |
ADD═ | Rd,Rr═ | Суммирование без переноса | Rd = Rd + Rr═ | Z,C,N,V,H,S═ | 1 |
ADC | Rd,Rr | Суммирование с переносом | Rd = Rd + Rr + C | Z,C,N,V,H,S | 1 |
SUB | Rd,Rr | Вычитание без переноса | Rd = Rd — Rr | Z,C,N,V,H,S | 1 |
SUBI | Rd,K8 | Вычитание константы | Rd = Rd — K8 | Z,C,N,V,H,S | 1 |
SBC | Rd,Rr | Вычитание с переносом | Rd = Rd — Rr - C | Z,C,N,V,H,S | 1 |
SBCI | Rd,K8 | Вычитание константы с переносом | Rd = Rd — K8 - C | Z,C,N,V,H,S | 1 |
AND | Rd,Rr | Логическое И | Rd = Rd ╥ Rr | Z,N,V,S═ | 1 |
ANDI | Rd,K8 | Логическое И с константой | Rd = Rd ╥ K8 | Z,N,V,S | 1 |
OR | Rd,Rr | Логическое ИЛИ | Rd = Rd V Rr | Z,N,V,S | 1 |
ORI | Rd,K8 | Логическое ИЛИ с константой | Rd = Rd V K8 | Z,N,V,S | 1 |
EOR | Rd,Rr | Логическое исключающее ИЛИ | Rd = Rd EOR Rr | Z,N,V,S | 1 |
COM | Rd | Побитная Инверсия | Rd = $FF — Rd | Z,C,N,V,S | 1 |
NEG | Rd | Изменение
знака (Доп.![]() |
Rd = $00 — Rd | Z,C,N,V,H,S | 1 |
SBR | Rd,K8 | Установить бит (биты) в регистре | Rd = Rd V K8 | Z,C,N,V,S | 1 |
CBR | Rd,K8 | Сбросить бит (биты) в регистре | Rd = Rd ╥ ($FF — K8) | Z,C,N,V,S | 1 |
INC | Rd | Инкрементировать значение регистра | Rd = Rd + 1 | Z,N,V,S | 1 |
DEC | Rd | Декрементировать значение регистра | Rd = Rd -1 | Z,N,V,S | 1 |
TST | Rd | Проверка на ноль либо отрицательность | Rd = Rd ╥ Rd | Z,C,N,V,S | 1 |
CLR | Rd | Очистить регистр | Rd = 0 | Z,C,N,V,S | 1 |
SER | Rd | Установить регистр | Rd = $FF | None | 1 |
ADIW | Rdl,K6 | Сложить константу и слово | Rdh:Rdl = Rdh:Rdl + K6═ | Z,C,N,V,S | 2 |
SBIW | Rdl,K6 | Вычесть константу из слова | Rdh:Rdl = Rdh:Rdl — K 6 | Z,C,N,V,S | 2 |
MUL | Rd,Rr | Умножение чисел без знака | R1:R0 = Rd * Rr | Z,C | 2 |
MULS | Rd,Rr | Умножение чисел со знаком | R1:R0 = Rd * Rr | Z,C | 2 |
MULSU | Rd,Rr | Умножение числа со знаком с числом без знака | R1:R0 = Rd * Rr | Z,C | 2 |
FMUL | Rd,Rr | Умножение дробных чисел без знака | R1:R0 = (Rd * Rr) << 1 | Z,C | 2 |
FMULS | Rd,Rr | Умножение дробных чисел со знаком | R1:R0 = (Rd *Rr) << 1 | Z,C | 2 |
FMULSU | Rd,Rr | Умножение дробного числа со знаком с числом без знака | R1:R0 = (Rd * Rr) << 1 | Z,C | 2 |
═
Мнемоника | Операнды | Описание | Операция | Флаги | Циклы |
RJMP | k | Относительный переход | PC = PC + k +1 | None | 2 |
IJMP | Нет | Косвенный переход на (Z) | PC = Z | None | 2 |
EIJMP | Нет | Расширенный косвенный переход на (Z) | STACK = PC+1, PC(15:0) = Z, PC(21:16) = EIND | None | 2 |
JMP | k | Переход | PC = k | None | 3 |
RCALL | k | Относительный вызов подпрограммы | STACK = PC+1, PC = PC + k + 1 | None | 3/4* |
ICALL | Нет | Косвенный вызов (Z) | STACK = PC+1, PC = Z═ | None | 3/4* |
EICALL | Нет | Расширенный косвенный вызов (Z) | STACK = PC+1, PC(15:0) = Z, PC(21:16) =EIND | None | 4* |
CALL | k | Вызов подпрограммы | STACK = PC+2, PC = k | None | 4/5* |
RET | Нет | Возврат из подпрограммы | PC = STACK | None | 4/5* |
RETI | Нет | Возврат из прерывания | PC = STACK | I | 4/5* |
CPSE | Rd,Rr | Сравнить, пропустить если равны═ | if (Rd ==Rr) PC = PC 2 or 3 | None | 1/2/3 |
CP | Rd,Rr | Сравнить | Rd -Rr | Z,C,N,V,H,S | 1 |
CPC | Rd,Rr | Сравнить с переносом | Rd — Rr — C | Z,C,N,V,H,S | 1 |
CPI | Rd,K8 | Сравнить с константой | Rd — K | Z,C,N,V,H,S | 1 |
SBRC | Rr,b | Пропустить если бит в регистре очищен | if(Rr(b)==0) PC = PC + 2 or 3 | None | 1/2/3 |
SBRS | Rr,b | Пропустить если бит в регистре установлен | if(Rr(b)==1) PC = PC + 2 or 3 | None | 1/2/3 |
SBIC | P,b | Пропустить если бит в порту очищен | if(I/O(P,b)==0) PC = PC + 2 or 3 | None | 1/2/3 |
SBIS | P,b | Пропустить если бит в порту установлен | if(I/O(P,b)==1) PC = PC + 2 or 3 | None | 1/2/3 |
BRBC | s,k | Перейти если флаг в SREG очищен | if(SREG(s)==0) PC = PC + k + 1 | None | 1/2 |
BRBS | s,k | Перейти если флаг в SREG установлен | if(SREG(s)==1) PC = PC + k + 1 | None | 1/2 |
BREQ | k | Перейти если равно | if(Z==1) PC = PC + k + 1 | None | 1/2 |
BRNE | k | Перейти если не равно | if(Z==0) PC = PC + k + 1 | None | 1/2 |
BRCS | k | Перейти если перенос установлен | if(C==1) PC = PC + k + 1 | None | 1/2 |
BRCC | k | Перейти если перенос очищен | if(C==0) PC = PC + k + 1 | None | 1/2 |
BRSH | k | Перейти если равно или больше | if(C==0) PC = PC + k + 1 | None | 1/2 |
BRLO | k | Перейти если меньше | if(C==1) PC = PC + k + 1 | None | 1/2 |
BRMI | k | Перейти если минус | if(N==1) PC = PC + k + 1 | None | 1/2 |
BRPL | k | Перейти если плюс | if(N==0) PC = PC + k + 1 | None | 1/2 |
BRGE | k | Перейти если больше или равно (со знаком) | if(S==0) PC = PC + k + 1 | None | 1/2 |
BRLT | k | Перейти если меньше (со знаком) | if(S==1) PC = PC + k + 1 | None | 1/2 |
BRHS | k | Перейти если флаг внутреннего переноса установлен | if(H==1) PC = PC + k + 1 | None | 1/2 |
BRHC | k | Перейти если флаг внутреннего переноса очищен | if(H==0) PC = PC + k + 1 | None | 1/2 |
BRTS | k | Перейти если флаг T установлен | if(T==1) PC = PC + k + 1 | None | 1/2 |
BRTC | k | Перейти если флаг T очищен | if(T==0) PC = PC + k + 1 | None | 1/2 |
BRVS | k | Перейти если флаг переполнения установлен | if(V==1) PC = PC + k + 1 | None | 1/2 |
BRVC | k | Перейти если флаг переполнения очищен | if(V==0) PC = PC + k + 1 | None | 1/2 |
BRIE | k | Перейти если прерывания разрешены | if(I==1) PC = PC + k + 1 | None | 1/2 |
BRID | k | Перейти если прерывания запрещены | if(I==0) PC = PC + k + 1 | None | 1/2 |
* Для операций доступа к данным
количество циклов указано при
условии доступа к внутренней
памяти данных, и не корректно при
работе с внешним ОЗУ. Для
инструкций CALL, ICALL, EICALL, RCALL, RET и RETI,
необходимо добавить три цикла плюс
по два цикла для каждого ожидания в
контроллерах с PC меньшим 16 бит (128KB
памяти программ). Для устройств с
памятью программ свыше 128KB ,
добавьте пять циклов плюс по три
цикла на каждое ожидание.
Мнемоника | Операнды | Описание | Операция | Флаги | Циклы |
MOV | Rd,Rr | Скопировать регистр | Rd = Rr | None | 1 |
MOVW | Rd,Rr | Скопировать пару регистров | Rd+1:Rd = Rr+1:Rr, r,d even | None | 1 |
LDI | Rd,K8 | Загрузить константу | Rd = K | None | 1 |
LDS | Rd,k | Прямая загрузка | Rd = (k) | None | 2* |
LD | Rd,X | Косвенная загрузка | Rd = (X) | None | 2* |
LD | Rd,X+ | Косвенная загрузка с пост-инкрементом | Rd = (X), X=X+1 | None | 2* |
LD | Rd,-X | Косвенная загрузка с пре-декрементом | X=X-1, Rd = (X) | None | 2* |
LD | Rd,Y | Косвенная загрузка | Rd = (Y) | None | 2* |
LD | Rd,Y+ | Косвенная загрузка с пост-инкрементом | Rd = (Y), Y=Y+1 | None | 2* |
LD | Rd,-Y | Косвенная загрузка с пре-декрементом | Y=Y-1, Rd = (Y) | None | 2* |
LDD | Rd,Y+q | Косвенная загрузка с замещением | Rd = (Y+q) | None | 2* |
LD | Rd,Z | Косвенная загрузка | Rd = (Z) | None | 2* |
LD | Rd,Z+ | Косвенная загрузка с пост-инкрементом | Rd = (Z), Z=Z+1 | None | 2* |
LD | Rd,-Z | Косвенная загрузка с пре-декрементом | Z=Z-1, Rd = (Z) | None | 2* |
LDD | Rd,Z+q | Косвенная загрузка с замещением | Rd = (Z+q) | None | 2* |
STS | k,Rr | Прямое сохранение | (k) = Rr | None | 2* |
ST | X,Rr | Косвенное сохранение | (X) = Rr | None | 2* |
ST | X+,Rr | Косвенное сохранение с пост-инкрементом | (X) = Rr, X=X+1 | None | 2* |
ST | -X,Rr | Косвенное сохранение с пре-декрементом | X=X-1, (X)=Rr | None | 2* |
ST | Y,Rr | Косвенное сохранение | (Y) = Rr | None | 2* |
ST | Y+,Rr | Косвенное сохранение с пост-инкрементом | (Y) = Rr, Y=Y+1 | None | 2 |
ST | -Y,Rr | Косвенное сохранение с пре-декрементом | Y=Y-1, (Y) = Rr | None | 2 |
ST | Y+q,Rr | Косвенное сохранение с замещением | (Y+q) = Rr | None | 2 |
ST | Z,Rr | Косвенное сохранение | (Z) = Rr | None | 2 |
ST | Z+,Rr | Косвенное сохранение с пост-инкрементом | (Z) = Rr, Z=Z+1 | None | 2 |
ST | -Z,Rr | Косвенное сохранение с пре-декрементом | Z=Z-1, (Z) = Rr | None | 2 |
ST | Z+q,Rr | Косвенное сохранение с замещением | (Z+q) = Rr | None | 2 |
LPM | Нет | Загрузка из программной памяти | R0 = (Z) | None | 3 |
LPM | Rd,Z | Загрузка из программной памяти | Rd = (Z) | None | 3 |
LPM | Rd,Z+ | Загрузка из программной памяти с пост-инкрементом | Rd = (Z), Z=Z+1 | None | 3 |
ELPM | Нет | Расширенная загрузка из программной памяти | R0 = (RAMPZ:Z) | None | 3 |
ELPM | Rd,Z | Расширенная загрузка из программной памяти | Rd = (RAMPZ:Z) | None | 3 |
ELPM | Rd,Z+ | Расширенная загрузка из программной памяти с пост-инкрементом | Rd = (RAMPZ:Z), Z = Z+1 | None | 3 |
SPM | Нет | Сохранение в программной памяти | (Z) = R1:R0 | None | — |
ESPM | Нет | Расширенное сохранение в программной памяти | (RAMPZ:Z) = R1:R0 | None | — |
IN | Rd,P | Чтение порта | Rd = P | None | 1 |
OUT | P,Rr | Запись в порт | P = Rr | None | 1 |
PUSH | Rr | Занесение регистра в стек | STACK = Rr | None | 2 |
POP | Rd | Извлечение регистра из стека | Rd = STACK | None | 2 |
* Для операций доступа к данным
количество циклов указано при
условии доступа к внутренней
памяти данных, и не корректно при
работе с внешним ОЗУ. Для
инструкций LD, ST, LDD, STD, LDS, STS, PUSH и POP,
необходимо добавить один цикл плюс
по одному циклу для каждого
ожидания.
Мнемоника | Операнды | Описание | Операция | Флаги | Циклы |
LSL | Rd | Логический сдвиг влево | Rd(n+1)=Rd(n), Rd(0)=0, C=Rd(7) | Z,C,N,V,H,S | 1 |
LSR | Rd | Логический сдвиг вправо | Rd(n)=Rd(n+1), Rd(7)=0, C=Rd(0) | Z,C,N,V,S | 1 |
ROL | Rd | Циклический сдвиг влево через C | Rd(0)=C, Rd(n+1)=Rd(n), C=Rd(7) | Z,C,N,V,H,S | 1 |
ROR | Rd | Циклический сдвиг вправо через C | Rd(7)=C, Rd(n)=Rd(n+1), C=Rd(0) | Z,C,N,V,S | 1 |
ASR | Rd | Арифметический сдвиг вправо | Rd(n)=Rd(n+1), n=0,…,6 | Z,C,N,V,S | 1 |
SWAP | Rd | Перестановка тетрад | Rd(3..0) = Rd(7..4), Rd(7..4) = Rd(3..0) | None | 1 |
BSET═ | s | Установка флага | SREG(s) = 1 | SREG(s) | 1 |
BCLR | s | Очистка флага | SREG(s) = 0 | SREG(s) | 1 |
SBI | P,b | Установить бит в порту | I/O(P,b) = 1 | None | 2 |
CBI | P,b | Очистить бит в порту | I/O(P,b) = 0 | None | 2 |
BST | Rr,b | Сохранить бит из регистра в T | T = Rr(b) | T | 1 |
BLD | Rd,b | Загрузить бит из T в регистр | Rd(b) = T | None | 1 |
SEC | Нет | Установить флаг переноса | C =1 | C | 1 |
CLC | Нет | Очистить флаг переноса | C = 0 | C | 1 |
SEN | Нет | Установить флаг отрицательного числа | N = 1 | N | 1 |
CLN | Нет | Очистить флаг отрицательного числа | N = 0 | N | 1 |
SEZ | Нет | Установить флаг нуля | Z = 1 | Z | 1 |
CLZ | Нет | Очистить флаг нуля | Z = 0 | Z | 1 |
SEI | Нет | Установить флаг прерываний | I = 1 | I | 1 |
CLI | Нет | Очистить флаг прерываний | I = 0 | I | 1 |
SES | Нет | Установить флаг числа со знаком | S = 1 | S | 1 |
CLN | Нет | Очистить флаг числа со знаком | S = 0 | S | 1 |
SEV | Нет | Установить флаг переполнения | V = 1 | V | 1 |
CLV | Нет | Очистить флаг переполнения | V = 0 | V | 1 |
SET | Нет | Установить флаг T | T = 1 | T | 1 |
CLT | Нет | Очистить флаг T | T = 0 | T | 1 |
SEH | Нет | Установить флаг внутреннего переноса | H = 1 | H | 1 |
CLH | Нет | Очистить флаг внутреннего переноса | H = 0 | H | 1 |
NOP | Нет | Нет операции | Нет | None | 1 |
SLEEP | Нет | Спать (уменьшить энергопотребление) | Смотрите описание инструкции | None | 1 |
WDR | Нет | Сброс сторожевого таймера | Смотрите описание инструкции | None | 1 |
═
Ассемблер не различает регистр
символов.
Операнды могут быть таких видов:
Rd: Результирующий (и исходный) регистр в регистровом файле
Rr: Исходный регистр в регистровом файле
b: Константа (3 бита), может быть константное выражение
s: Константа (3 бита), может быть константное выражение
P: Константа (5-6 бит), может быть константное выражение
K6; Константа (6 бит), может быть константное выражение
K8: Константа (8 бит), может быть константное выражение
k: Константа (размер зависит от инструкции), может быть константное выражение
q: Константа (6 бит), может быть константное выражение
Rdl:═ R24, R26, R28, R30. Для инструкций ADIW и SBIW
X,Y,Z: Регистры косвенной адресации (X=R27:R26, Y=R29:R28, Z=R31:R30)
Компилятор поддерживает ряд
директив. Директивы не
транслируются непосредственно в
код. Вместо этого они используются
для указания положения в
программной памяти, определения
макросов, инициализации памяти и
т.д. Список директив приведён в
следующей таблице.
═
Все директивы предваряются точкой.
Директива BYTE резервирует байты в ОЗУ. Если вы хотите иметь возможность ссылаться на выделенную область памяти, то директива BYTE должна быть предварена меткой. Директива принимает один обязательный параметр, который указывает количество выделяемых байт. Эта директива может использоваться только в сегменте данных(смотреть директивы CSEG и DSEG). Выделенные байты не инициализируются.
Синтаксис:
МЕТКА: .BYTE выражение
Пример:
. DSEG
var1:═══ .BYTE 1═══════════ ;
резервирует 1 байт для var1
table:══ .BYTE tab_size════ ; резервирует
tab_size байт
.CSEG
════════ ldi r30,low(var1)═ ; Загружает
младший байт регистра Z
════════ ldi r31,high(var1) ; Загружает
старший байт регистра Z
════════ ld r1,Z═══════════ ;
Загружает VAR1 в регистр 1
Директива CSEG определяет начало программного сегмента. Исходный файл может состоять из нескольких программных сегментов, которые объединяются в один программный сегмент при компиляции. Программный сегмент является сегментом по умолчанию. Программные сегменты имеют свои собственные счётчики положения которые считают не побайтно, а по словно. Директива ORG может быть использована для размещения кода и констант в необходимом месте сегмента. Директива CSEG не имеет параметров.
Синтаксис:
.CSEG
Пример:
.DSEG══════════════════════ ;
Начало сегмента данных
vartab: .BYTE 4════════════ ;
Резервирует 4 байта в ОЗУ
.CSEG══════════════════════ ;
Начало кодового сегмента
const:═ .DW 2══════════════ ;
Разместить константу 0x0002 в памяти
программ
═══════ mov r1,r0══════════ ;
Выполнить действия
Директива DB резервирует необходимое количество байт в памяти программ или в EEPROM. Если вы хотите иметь возможность ссылаться на выделенную область памяти, то директива DB должна быть предварена меткой. Директива DB должна иметь хотя бы один параметр. Данная директива может быть размещена только в сегменте программ (CSEG) или в сегменте EEPROM (ESEG).
Параметры передаваемые директиве
— это последовательность выражений
разделённых запятыми. Каждое
выражение должно быть или числом в
диапазоне (-128..255), или в результате
вычисления должно давать результат
в этом же диапазоне, в противном
случае число усекается до байта,
причём БЕЗ выдачи предупреждений.
Если директива получает более одного параметра и текущим является программный сегмент, то параметры упаковываются в слова (первый параметр — младший байт), и если число параметров нечётно, то последнее выражение будет усечено до байта и записано как слово со старшим байтом равным нулю, даже если далее идет ещё одна директива DB.
Синтаксис:
МЕТКА:═ .DB список_выражений
Пример:
.CSEG
consts: .DB 0, 255, 0b01010101, -128, 0xaa
.ESEG
const2: .DB 1,2,3
Директива DEF позволяет ссылаться на регистр через некоторое символическое имя. Назначенное имя может использоваться во всей нижеследующей части программы для обращений к данному регистру. Регистр может иметь несколько различных имен. Символическое имя может быть переназначено позднее в программе.
Синтаксис:
.DEF Символическое_имя = Регистр
Пример:
.DEF temp=R16
.DEF ior=R0
.CSEG
═ldi temp,0xf0═ ; Загрузить 0xf0 в регистр
temp (R16)
═in ior,0x3f═ ; Прочитать SREG в регистр
ior (R0)
═eor temp,ior═ ; Регистры temp и ior
складываются по исключающему или
Директива DEVICE позволяет указать
для какого устройства
компилируется программа. При
использовании данной директивы
компилятор выдаст предупреждение,
если будет найдена инструкция,
которую не поддерживает данный
микроконтроллер. Также будет
выдано предупреждение, если
программный сегмент, либо сегмент
EEPROM превысят размер допускаемый
устройством. Если же директива не
используется то все инструкции
считаются допустимыми, и
отсутствуют ограничения на размер
сегментов.
Синтаксис:
.DEVICE AT90S1200 |AT90S2313 | AT90S2323 | AT90S2333 |
AT90S2343 | AT90S4414 | AT90S4433 | AT90S4434 | AT90S8515 |
AT90S8534 | AT90S8535 | ATtiny11 | ATtiny12 | ATtiny22 |
ATmega603 | ATmega103
Пример:
.DEVICE AT90S1200═ ; Используется AT90S1200
.CSEG
═══════ push r30══ ; Эта инструкция
вызовет предупреждение
══════════════════ ; поскольку
AT90S1200 её не имеет
Директива DSEG определяет начало сегмента данных. Исходный файл может состоять из нескольких сегментов данных, которые объединяются в один сегмент при компиляции. Сегмент данных обычно состоит только из директив BYTE и меток. Сегменты данных имеют свои собственные побайтные счётчики положения. Директива ORG может быть использована для размещения переменных в необходимом месте ОЗУ. Директива не имеет параметров.
Синтаксис:
.DSEG═
Пример:
.DSEG═══════════════════════ ;
Начало сегмента данных
var1:═ .BYTE 1══════════════ ;
зарезервировать 1 байт для var1
table:═ .BYTE tab_size══════ ;
зарезервировать tab_size байт.
.CSEG
═══════ ldi r30,low(var1)═══ ; Загрузить
младший байт регистра Z
═══════ ldi r31,high(var1)══ ; Загрузить
старший байт регистра Z
═══════ ld r1,Z═════════════ ;
Загрузить var1 в регистр r1
Директива DW резервирует
необходимое количество слов в
памяти программ или в EEPROM. Если вы
хотите иметь возможность ссылаться
на выделенную область памяти, то
директива DW должна быть предварена
меткой. Директива DW должна иметь
хотя бы один параметр. Данная
директива может быть размещена
только в сегменте программ (CSEG) или в сегменте
EEPROM (ESEG).
Параметры передаваемые директиве — это последовательность выражений разделённых запятыми. Каждое выражение должно быть или числом в диапазоне (-32768..65535), или в результате вычисления должно давать результат в этом же диапазоне, в противном случае число усекается до слова, причем БЕЗ выдачи предупреждений.
Синтаксис:
МЕТКА: .DW expressionlist
Пример:
.CSEG
varlist:═ .DW 0, 0xffff, 0b1001110001010101, -32768, 65535
.ESEG
eevarlst: .DW 0,0xffff,10
Директива определяет конец макроопределения, и не принимает никаких параметров. Для информации по определению макросов смотрите директиву MACRO.
Синтаксис:
.ENDMACRO═
Пример:
.MACRO SUBI16══════════════ ; Начало
определения макроса
═══════ subi r16,low(@0)═══ ; Вычесть
младший байт первого параметра
═══════ sbci r17,high(@0)══ ; Вычесть
старший байт первого параметра
.ENDMACRO
Директива EQU присваивает метке значение. Эта метка может позднее использоваться в выражениях. Метка которой присвоено значение данной директивой не может быть переназначена и её значение не может быть изменено.
Синтаксис:
.EQU метка = выражение
Пример:
.EQU io_offset = 0x23
.EQU porta════ = io_offset + 2
.CSEG════════════════ ; Начало
сегмента данных
═══════ clr r2═══════ ; Очистить
регистр r2
═══════ out porta,r2═ ; Записать в порт
A
Директива ESEG определяет начало
сегмента EEPROM. Исходный файл может
состоять из нескольких сегментов
EEPROM, которые объединяются в один
сегмент при компиляции. Сегмент EEPROM
обычно состоит только из директив DB,
DW
и меток. Сегменты EEPROM имеют свои
собственные побайтные счётчики
положения. Директива ORG может быть
использована для размещения
переменных в необходимом месте EEPROM.
Директива не имеет параметров.
Синтаксис:
.ESEG═══
Пример:
.DSEG═══════════════════ ; Начало
сегмента данных
var1:══ .BYTE 1═════════ ;
зарезервировать 1 байт для var1
table:═ .BYTE tab_size══ ; зарезервировать
tab_size байт.
.ESEG
eevar1: .DW 0xffff═══════ ;
проинициализировать 1 слово в EEPROM
Встретив директиву EXIT компилятор прекращает компиляцию данного файла. Если директива использована во вложенном файле (см. директиву INCLUDE), то компиляция продолжается со строки следующей после директивы INCLUDE. Если же файл не является вложенным, то компиляция прекращается.
Синтаксис:
.EXIT
Пример:
.EXIT═ ; Выйти из данного файла
Встретив директиву INCLUDE компилятор открывает указанный в ней файл, компилирует его пока файл не закончится или не встретится директива EXIT, после этого продолжает компиляцию начального файла со строки следующей за директивой INCLUDE. Вложенный файл может также содержать директивы INCLUDE.
Синтаксис:
.INCLUDE «имя_файла»
Пример:
; файл iodefs.asm:
.EQU sreg══ = 0x3f════ ; Регистр статуса
.EQU sphigh = 0x3e════ ; Старший байт
указателя стека
.EQU splow═ = 0x3d════ ; Младший байт
указателя стека
; файл incdemo. asm
.INCLUDE iodefs.asm═══ ; Вложить
определения портов
═══════ in r0,sreg════ ; Прочитать
регистр статуса
Директива LIST указывает компилятору на необходимость создания листинга. Листинг представляет из себя комбинацию ассемблерного кода, адресов и кодов операций. По умолчанию генерация листинга включена, однако данная директива используется совместно с директивой NOLIST для получения листингов отдельных частей исходных файлов.
Синтаксис:
.LIST
Пример:
.NOLIST═══════════════ ; Отключить
генерацию листинга
.INCLUDE «macro.inc»══ ; Вложенные
файлы не будут
.INCLUDE «const.def»══ ; отображены в
листинге
.LIST═════════════════ ; Включить
генерацию листинга
После директивы LISTMAC компилятор будет показывать в листинге содержимое макроса. По умолчанию в листинге показывается только вызов макроса и передаваемые параметры.
Синтаксис:
.LISTMAC
Пример:
.MACRO MACX════════ ; Определение
макроса
═══════ add═ r0,@0═ ; Тело макроса
═══════ eor═ r1,@1═
.ENDMACRO══════════ ; Конец
макроопределения
.LISTMAC═══════════ ; Включить
разворачивание макросов
═══════ MACX r2,r1═ ; Вызов макроса (в
листинге будет показано тело
макроса)
С директивы MACRO начинается
определение макроса. В качестве
параметра директиве передаётся имя
макроса. При встрече имени макроса
позднее в тексте программы,
компилятор заменяет это имя на тело
макроса. Макрос может иметь до 10
параметров, к которым в его теле
обращаются через @0-@9. При вызове
параметры перечисляются через
запятые. Определение макроса
заканчивается директивой ENDMACRO.
По умолчанию в листинг включается
только вызов макроса, для
разворачивания макроса необходимо
использовать директиву LISTMAC.
Макрос в листинге показывается
знаком +.
═
Синтаксис:
.MACRO макроимя
Пример:
.MACRO SUBI16══════════════════ ;
Начало макроопределения
═══════ subi @1,low(@0)════════ ;
Вычесть младший байт параметра 0 из
параметра 1
═══════ sbci @2,high(@0)═══════ ;
Вычесть старший байт параметра 0 из
параметра 2
.ENDMACRO══════════════════════ ;
Конец макроопределения
.CSEG══════════════════════════
; Начало программного сегмента
═══════ SUBI16 0x1234,r16,r17══ ; Вычесть
0x1234 из r17:r16
Директива NOLIST указывает компилятору на необходимость прекращения генерации листинга. Листинг представляет из себя комбинацию ассемблерного кода, адресов и кодов операций. По умолчанию генерация листинга включена, однако может быть отключена данной директивой. Кроме того данная директива может быть использована совместно с директивой LIST для получения листингов отдельных частей исходных файлов
Синтаксис:
.NOLIST
Пример:
.NOLIST═══════════════ ; Отключить
генерацию листинга
.INCLUDE «macro.inc»══ ; Вложенные
файлы не будут
.INCLUDE «const.def»══ ; отображены в
листинге
.LIST═════════════════ ; Включить
генерацию листинга
Директива ORG устанавливает
счётчик положения равным заданной
величине, которая передаётся как
параметр. Для сегмента данных она
устанавливает счётчик положения в
SRAM (ОЗУ), для сегмента программ это
программный счётчик, а для сегмента
EEPROM это положение в EEPROM. Если
директиве предшествует метка (в той
же строке) то метка размещается по
адресу указанному в параметре
директивы. Перед началом
компиляции программный счётчик и
счётчик EEPROM равны нулю, а счётчик
ОЗУ равен 32 (поскольку адреса 0-31
заняты регистрами). Обратите
внимание что для ОЗУ и EEPROM
используются побайтные счётчики а
для программного сегмента -
пословный.
Синтаксис:
.ORG выражение
Пример:
.DSEG═══════════════ ; Начало
сегмента данных
.ORG 0x37═══════════ ; Установить
адрес SRAM равным 0x37
variable: .BYTE 1═══ ; Зарезервировать
байт по адресу 0x37H
.CSEG
.ORG 0x10═══════════ ; Установить
программный счётчик равным 0x10
═════════ mov r0,r1═ ; Данная команда
будет размещена по адресу 0x10
Директива SET присваивает имени некоторое значение. Это имя позднее может быть использовано в выражениях. Причем в отличии от директивы EQU значение имени может быть изменено другой директивой SET.
Синтаксис:
.SET имя = выражение
Пример:
.SET io_offset = 0x23
.SET porta════ = io_offset + 2
.CSEG════════════════ ; Начало
кодового сегмента
═══════ clr r2═══════ ; Очистить
регистр 2
═══════ out porta,r2═ ; Записать в порт
A
Компилятор позволяет использовать в программе выражения которые могут состоять операндов, знаков операций и функций. Все выражения являются 32-битными.
Могут быть использованы следующие операнды:
Компилятор поддерживает ряд операций, которые перечислены в таблице (чем выше положение в таблице, тем выше приоритет операции). Выражения могут заключаться в круглые скобки, такие выражения вычисляются перед выражениями за скобками.
Символ: !
Описание:
Возвращает 1 если выражение равно 0,
и наоборот
Приоритет: 14
Пример: ldi r16, !0xf0═ ; В
r16 загрузить 0x00
Символ: ~
Описание:
Возвращает выражение в котором все
биты проинвертированы
Приоритет: 14
Пример: ldi r16, ~0xf0═ ; В
r16 загрузить 0x0f
Символ: —
Описание:
Возвращает арифметическое
отрицание выражения
Приоритет: 14
Пример: ldi r16,-2═ ;
Загрузить -2(0xfe) в r16
Символ: *
Описание:
Возвращает результат умножения
двух выражений
Приоритет: 13
Пример: ldi r30, label*2
Символ: /
Описание:
Возвращает целую часть результата
деления левого выражения на правое
Приоритет: 13
Пример: ldi r30, label/2
Символ: +
Описание:
Возвращает сумму двух выражений
Приоритет: 12
Пример: ldi r30, c1+c2
Символ: —
Описание:
Возвращает результат вычитания
правого выражения из левого
Приоритет: 12
Пример: ldi r17, c1-c2
Символ: <<
Описание:
Возвращает левое выражение
сдвинутое влево на число бит
указанное справа
Приоритет: 11
Пример: ldi r17,
1<<bitmask═ ; В r17 загрузить 1
сдвинутую влево bitmask раз
Символ: >>
Описание:
Возвращает левое выражение
сдвинутое вправо на число бит
указанное справа
Приоритет: 11
Пример: ldi r17,
c1>>c2═ ; В r17 загрузить c1 сдвинутое
вправо c2 раз
Символ: <
Описание:
Возвращает 1 если левое выражение
меньше чем правое (учитывается
знак), и 0 в противном случае
Приоритет: 10
Пример: ori r18,
bitmask*(c1<c2)+1
Символ: <=
Описание:
Возвращает 1 если левое выражение
меньше или равно чем правое
(учитывается знак), и 0 в противном
случае
Приоритет: 10
Пример: ori r18,
bitmask*(c1<=c2)+1
Символ: >
Описание:
Возвращает 1 если левое выражение
больше чем правое (учитывается
знак), и 0 в противном случае
Приоритет: 10
Пример: ori r18,
bitmask*(c1>c2)+1
Символ: >=
Описание:
Возвращает 1 если левое выражение
больше или равно чем правое
(учитывается знак), и 0 в противном
случае
Приоритет: 10
Пример: ori r18,
bitmask*(c1>=c2)+1
Символ: ==
Описание:
Возвращает 1 если левое выражение
равно правому (учитывается знак), и 0
в противном случае
Приоритет: 9
Пример: andi r19,
bitmask*(c1==c2)+1
Символ: !=
Описание:
Возвращает 1 если левое выражение
не равно правому (учитывается знак),
и 0 в противном случае
Приоритет: 9
Пример: . c2)
Символ: |
Описание:
Возвращает результат побитового
ИЛИ выражений
Приоритет: 6
Пример: ldi r18, Low(c1|c2)
Символ: &&
Описание:
Возвращает 1 если оба выражения не
равны нулю, и 0 в противном случае
Приоритет: 5
Пример: ldi r18,
Low(c1&&c2)
Символ: ||
Описание:
Возвращает 1 если хотя бы одно
выражение не равно нулю, и 0 в
противном случае
Приоритет: 4
Пример: ldi r18, Low(c1||c2)
Определены следующие функции:
Этот раздел описывает использование компилятора и встроенного редактора
В WAVRASM могут быть открыты как
новые так и существующие файлы.
Количество открытых файлов
ограничено размером памяти, однако
объём одного файла не может
превышать 28 килобайт (в связи с
ограничением MS-Windows). Компиляция
файлов большего размера возможна,
но они не могут быть редактируемы
встроенным редактором. Каждый файл
открывается в отдельном окне.
После компиляции программы появляется окно сообщений. Все обнаруженные компилятором ошибки будут перечислены в этом окне. При выборе строки с сообщением о ошибке, строка исходного файла, в которой найдена ошибка, становится красной. Если же ошибка находится во вложенном файле, то этого подсвечивания не произойдёт.
Если по строке в окне сообщений клацнуть дважды, то окно файла с указанной ошибкой становится активным, и курсор помещается в начале строки содержащей ошибку. Если же файл с ошибкой не открыт (например это вложенный файл) то он будет автоматически открыт.
Учтите, что если вы внесли изменения в исходные тексты (добавили или удалили строки), то информация о номерах строк в окне сообщений не является корректной.
Некоторые установки программы могут быть изменены через пункт меню «Options».
В поле ввода, озаглавленном
«List-file extension», вводится
расширение, используемое для файла
листинга, а в поле «Output-file
extension» находится расширение для
файлов с результатом компиляции
программы. В прямоугольнике «Output
file format» можно выбрать формат
выходного файла (как правило
используется интеловский). Однако
это не влияет на объектный файл
(используемый AVR Studio), который
всегда имеет один и тот же формат, и
расширение OBJ. Если в исходном файле
присутствует сегмент EEPROM то будет
также создан файл с расширением EEP.
Установки заданные в данном окне
запоминаются на постоянно, и при
следующем запуске программы, их нет
необходимости переустанавливать.
Опция «Wrap relative jumps» даёт возможность «заворачивать» адреса. Эта опция может быть использована только на чипах с объёмом программной памяти 4К слов (8К байт), при этом становится возможным делать относительные переходы (rjmp) и вызовы подпрограмм (rcall) по всей памяти.
Опция «Save before assemble» указывает программе на необходимость автоматического сохранения активного окна (и только его) перед компиляцией.
Если вы хотите, чтобы при закрытии программы закрывались все открытые окна, то поставьте галочку в поле «Close all windows before exit».
Atmel, AVR являются зарегистрированными товарными знаками фирмы Atmel Corporation
Перевод выполнил Руслан Шимкевич, [email protected]
Для начала разберёмся с терминологией.
Машинный код – система команд конкретной вычислительной машины (процессора), которая интерпретируется непосредственно процессором. Команда, как правило, представляет собой целое число, которое записывается в регистр процессора. Процессор читает это число и выполняет операцию, которая соответствует этой команде. Популярно это описано в книге Как стать программистом.
Язык программирования низкого уровня (низкоуровневый язык программирования) – это язык программирования, максимально приближённый к программированию в машинных кодах. В отличие от машинных кодов, в языке низкого уровня каждой команде соответствует не число, а сокращённое название команды (мнемоника). Например, команда ADD – это сокращение от слова ADDITION (сложение).Язык программирования высокого уровня – это язык программирования, максимально приближённый к человеческому языку (обычно к английскому, но есть языки программирования на национальных языках, например, язык 1С основан на русском языке). Язык высокого уровня практически не привязан ни к конкретному процессору, ни к операционной системе (если не используются специфические директивы).
Язык ассемблера – это низкоуровневый язык программирования, на котором вы пишите свои программы. Для каждого процессора существует свой язык ассемблера.
Ассемблер – это специальная программа, которая преобразует (компилирует) исходные тексты вашей программы, написанной на языке ассемблера, в исполняемый файл (файл с расширением EXE или COM). Если быть точным, то для создания исполняемого файла требуются дополнительные программы, а не только ассемблер. Но об этом позже…
В большинстве случаев говорят «ассемблер», а подразумевают «язык ассемблера». Теперь вы знаете, что это разные вещи и так говорить не совсем правильно. Хотя все программисты вас поймут.
ВАЖНО!В этой книге мы будем говорить только о программировании для компьютеров с процессорами Intel (или совместимыми). Для того чтобы на практике проверить приведённые в книге примеры, вам потребуются следующие программы (или хотя бы некоторые из них):
И еще – исходный код, написанный, например для Emu8086, будет немного отличаться от кода, написанного, например, для TASM. Эти отличия будут оговорены.
Большая часть программ, приведённых в книге, написана для MASM. Во-первых, потому что этот ассемблер наиболее популярен и до сих пор поддерживается. Во-вторых, потому что он поставляется с MSDN и с пакетом программ Visual Studio от Microsoft. Ну и в третьих, потому что я являюсь счастливым обладателем лицензионной копии MASM.
Если же у вас уже есть какой-либо ассемблер, не вошедший в перечисленный выше список,
то вам придётся самостоятельно разобраться с его синтаксисом и почитать руководство
пользователя, чтобы научиться правильно с ним работать. Но общие рекомендации, приведённые
в данной книге, будут справедливы для любых (ну или почти для любых) ассемблеров.
Сигнальный вывод датчика подключен к ноге 2 (PIN2) порта PORTD контролера или (что то же самое) к выводу D2 Arduino. Он же через резистор 4.7 kOm “подтянут” на “плюс” питания. Плюс и минус датчика подключены — к соответствующим проводам питания. USB-TTL переходник подключен к выходу Tx USART порта Arduino, что значит PIN1 порта PORTD контроллера.
В собранном виде на breadboard:
Разбираемся с датчиком и смотрим datasheet. Сам по себе датчик несложный, и использует всего один сигнальный провод, который надо подтянуть через резистор к +5V — это будет базовый «высокий» уровень на линии. Если линия свободна — т.е. ни контроллер, ни датчик ничего не передают, на линии как раз и будет базовый «высокий» уровень. Когда датчик или контроллер что-то передают, то они занимают линию — устанавливают на линии «низкий» уровень на какое-то время. Всего датчик передает 5 байт. Байты датчик передает по очереди, сначала показатели влажности, потом температуры, завершает все контрольной суммой, это выглядит как “HHTTXX”, в общем смотрим datasheet. Пять байт — это 40 бит и каждый бит при передаче кодируется специальным образом.
Для упрощения, будет считать, что «высокий» уровень на линии — это «единица», а «низкий» соответственно «ноль». Согласно datasheet для начала работы с датчиком надо положить контроллером сигнальную линию на землю, т.е. получить «ноль» на линии и сделать это на период не менее чем 20 милсек (миллисекунд), а потом резко отпустить линию. В ответ — датчик должен выдать на сигнальную линию свою посылку, из сигналов высокого и низкого уровня разной длительности, которые кодируют нужные нам 40 бит. И, согласно datasheet, если мы удачно прочитаем эту посылку контроллером, то мы сразу поймем что: а) датчик собственно ответил, б) передал данные по влажности и температуре, с) передал контрольную сумму. В конце передачи датчик отпускает линию. Ну и в datasheet написано, что датчик можно опрашивать не чаще чем раз в секунду.
Итак, что должен сделать микроконтроллер, согласно datasheet, чтобы датчик ему ответил — нужно прижать линию на 20 миллисекунд, отпустить и быстро смотреть, что на линии:
Датчик должен ответить — положить линию в ноль на 80 микросекунд (мксек), потом отпустить на те же 80 мксек — это можно считать подтверждением того, что датчик на линии живой и откликается:
После этого, сразу же, по падению с высокого уровня на нижний датчик начинает передавать 40 отдельных бит. Каждый бит кодируются специальной посылкой, которая состоит из двух интервалов. Сначала датчик занимает линию (кладет ее в ноль) на определенное время — своего рода первый «полубит». Потом датчик отпускает линию (линия подтягивается к единице) тоже на определенное время — это типа второй «полубит». Длительность этих интервалов — «полубитов» в микросекундах кодирует что собственно пытается передать датчик: бит “ноль” или бит “единица”.
Рассмотрим описание битовой посылки: первый «полубит» всегда низкого уровня и фиксированной длительности — около 50 мксек. Длительность второго «полубита» определят, что датчик собственно передает.
Для передачи нуля используется сигнал высокого уровня длительностью 26–28 мксек:
Для передачи единицы, длительность сигнала высокого увеличивается до 70 микросекунд:
Мы не будет точно высчитывать длительность каждого интервала, нам вполне достаточно понимания, что если длительность второго «полубита» меньше чем первого — то закодирован ноль, если длительность второго «полубита» больше — то закодирована единица. Всего у нас 40 бит, каждый бит кодируется двумя импульсами, всего нам надо значит прочитать 80 интервалов. После того как прочитали 80 интервалов будем сравнить их попарно, первый “полубит” со вторым.
Вроде все просто, что же требуется от микроконтроллера для того чтобы прочитать данные с датчика? Получается нужно значит дернуть ногой в ноль, а потом просто считать всю длинную посылку с датчика на той же ноге. По ходу, будем разбирать посылку на «полу-биты», определяя где передается бит ноль, где единица. Потом соберем получившиеся биты, в байты, которые и будут ожидаемыми данными о влажности и температуре.
Ок, мы начали писать код и для начала попробуем проверить, а работает ли вообще датчик, для этого мы просто положим линию на 20 милсек и посмотрим на линии, что из этого получится логическим анализатором.
Определения:
Как я уже писал сам датчик подключен на 2 ногу порта D. В Arduino Uno это цифровой выход D2 (смотрим для проверки Arduino Pinout).
Все делаем тупо: инициализировали порт на выход, выставили ноль, подождали 20 миллисекунд, освободили линию, переключили ногу в режим чтения и ждем появление сигналов на ноге.
Смотрим анализатором — а ответил ли датчик?
Да, ответ есть — вот те сигналы после нашего первого импульса в 20 милсек — это и есть ответ датчика. Для просмотра посылки я использовал китайский клон USBEE AX Pro который подключен к сигнальному проводу датчика.
Растянем масштаб так чтобы увидеть окончание нашего импульса в 20 милсек и лучше увидеть начало посылки от датчика — смотрим все как в datasheet — сначала датчик выставил низкий/высокий уровень по 80 мксек, потом начал передавать биты — а данном случае во втором «полубите» передается «0»
Значит датчик работает и данные нам прислал, теперь надо эти данные правильно прочитать. Поскольку задача у нас учебная, то и решать ее будем тупо в лоб. В момент ответа датчика, т.е. в момент перехода с высокого уровня в низкий, мы запустим цикл с счетчиком числа повторов нашего цикла. Внутри цикла, будем постоянно следить за уровнем сигнала на ноге. Итого, в цикле будем ждать, когда сигнал на ноге перейдет обратно на высокий уровень — тем самым определив длительность сигнала первого «полубита». Наш микроконтроллер работает на частоте 16 MHz и за период например в 50 микросекунд контроллер успеет выполнить около 800 инструкций. Когда на линии появится высокий уровень — то мы из цикла аккуратно выходим, а число повторов цикла, которые мы отсчитали с использованием счетчика — запоминаем в переменную.
После перехода сигнальной линии уже на высокий уровень мы делаем такую же операцию– считаем циклы, до момента когда датчик начнет передавать следующий бит и положит линию в низкий уровень. К счастью, нам не надо знать точный временной интервал наших импульсов, нам достаточно понимать, что один интервал больше другого. Понятно, что если датчик передает бит «ноль» то длительность второго «полубита» и соответственно число циклов, которые мы отсчитали будет меньше чем длительность первого «полубита». Если же датчик передал бит «единица», то число циклов которые мы насчитаем во время второго полубита будет больше чем в первым.
И для того что бы мы не висели вечно, если вдруг датчик не ответил или засбоил, сам цикл мы будем запускать на какой-то временной период, но который гарантированно больше самой длинной посылки, чтоб если датчик не ответил, то мы смогли выйти по тайм-ауту.
В данном случае показан пример для ситуации, когда у нас на линии был ноль, и мы считаем сколько раз мы в цикле мы считали состояние ноги контроллера, пока датчик не переключил линию в единицу.
Аналогичная подпрограмма используется для того, чтобы посчитать сколько циклов у нас должно прокрутиться, пока датчик из состояния ноль на линии переложил линию в состояние единицы.
Для расчета временных задержек мы будет использовать тот же подход, который мы использовали при мигании светодиодом — подберем параметры пустого цикла для формирования нужной паузы. Я использовал специальный калькулятор. При желании можно посчитать число рабочих инструкций и вручную.
Памяти в нашем контроллере довольно много — аж 2 (Два) килобайта, так что мы не будем жлобствовать с памятью, и тупо сохраним данные счетчиков относительно наших 80 ( 40 бит, 2 интервала на бит) интервалов в память.
Объявим переменную
CYCLES: .byte 80 ; буфер для хранения числа циклов
И сохраним все считанные циклы в память.
Теперь, для отладки, попробуем посмотреть насколько удачно посчиталось длительность интервалов и понять действительно ли мы считали данные из датчика. Понятно, что число отсчитанных циклов первого «полубита» должно быть примерно одинаково у всех битовых посылок, а вот число циклов при отсчете второго «полубита» будет или существенно меньше, или наоборот существенно больше.
Для того чтобы передавать данные в большой компьютер будем использовать USART контроллера, который через USB кабель будет передавать данные в программу — терминал, например PuTTY. Передаем опять же тупо в лоб — засовываем байт в нужный регистр управления USART-а и ждем, когда он передастся. Для удобства я также использовал пару подпрограмм, типа — передать несколько байт, начиная с адреса в Y, ну и перевести каретку в терминале для красоты.
Отправив в терминал число отсчётов для 80 интервалов, можно попробовать собрать собственно значащие биты. Делать будем как написано в учебнике, т.е. в datasheet — попарно сравним число циклов первого «полубита» с числом циклов второго. Если вторые пол-бита короче — значит это закодировать ноль, если длиннее — то единица. После сравнения биты накапливаем в аккумуляторе и сохраняем в память по-байтово начиная с адреса BITS.
Итак, здесь мы собрали в памяти начиная с метки BITS те пять байт, которые передал контроллер. Но работать с ними в таком формате не очень неудобно, поскольку в памяти это выглядит примерно, как:
34002100ХХ, где 34 — это влажность целая часть, 00 — данные после запятой влажности, 21 — температура, 00 — опять данные после запятой температуры, ХХ — контрольная сумма. А нам надо бы вывести в терминал красиво типа «Temperature = 21.00». Так что для удобства, растащим данные по отдельным переменным.
Определения
И сохраняем байты из BITS в нужные переменные
После этого преобразуем цифры в коды ASCII, чтобы данные можно было нормально прочитать в терминале, добавляем названия данных, ну там «температура» из флеша и шлем в COM порт в терминал.
PuTTY с даннымиДля того, чтобы это измерять температуру регулярно добавляем вечный цикл с задержкой порядка 1200 миллисекунд, поскольку datasheet DHT11 говорит, что не рекомендуется опрашивать датчик чаще чем 1 раз в секунду.
Основной цикл после этого выглядит примерно так:
Прошиваем, подключаем USB-TTL кабель (преобразователь)к компьютеру, запускаем терминал, выбираем правильный виртуальный COM порта и наслаждаемся нашим новым цифровым термометром. Для проверки можно погреть датчик в руке — у меня температура при этом растет, а влажность как ни странно уменьшается.
Ссылки по теме:
AVR Delay Calc
Как подключить Arduino для программирования в Atmel Studio 7
DHT11 Datasheet
ATmega DataSheet
Atmel AVR 8-bit Instruction Set
Atmel Studio
Код примера на github
Формат программ на ассемблере
Программа на ассемблере представляет собой текстовый файл, который состоит из мнемоник – символьных обозначений команд микроконтроллера, меток и директив.
Любая строка может начинаться с метки – строки из символов и (или) цифр, заканчивающейся двоеточием.
Метки используются для обозначения текущей строки некоторым именем для дальнейшего использования в командах условного или безусловного перехода, а также для обозначения участка в памяти для обращения к данным.
Строка исходного текста может иметь один из следующих видов:
1. [метка:] директива [аргументы директивы] [комментарий]
2. [метка:] мнемоника команды [аргументы команды] [комментарий]
3. Комментарий
4. Пустая строка
Комментарии всегда начинаются с символа “;”.
Элементы, заключенные в квадратные скобки, могут отсутствовать. Текст, расположенный после символа “точка с запятой” до конца строки, полностью игнорируется ассемблером. Использование меток, мнемоник команд микроконтроллера и директив ассемблера подробнее будет рассмотрено позже.
Примеры записи строк:
Label1: .EQU var1=100 ;Директива определения символьного
;имени var1, эквивалентного записи ʼʼ100ʼʼ
.EQU var2=200 ;Определение имени var2, соответствующего ʼʼ200ʼʼ
test: rjmp test ;Бесконечный цикл (мнемоника команды)
;Пустая строка
Расположение меток, команд ассемблера и директив несущественно, важен только их порядок.
Транслятор ассемблера позволяет использовать в тексте программы мнемоники (обозначения команд микроконтроллера), полностью совпадающие с их названием в системе команд микроконтроллера.
Множество команд микроконтроллеров AVR семейства Classic можно представить несколькими группами:
· Команды пересылки данных;
· Команды арифметических операций и команды сдвига;
· Команды логических операций;
· Команды передачи управления;
· Команды операций с битами;
· Команды управления системой.
Рассмотрим каждую группу подробнее.
2.3.1. Команды логических операций
Эти команды позволяют выполнять стандартные логические операции над байтами, такие как ʼʼлогическое умножениеʼʼ (И), ʼʼлогическое сложениеʼʼ (ИЛИ), операцию ʼʼисключающее ИЛИʼʼ, а также вычисление обратного и дополнительного кодов числа. К этой группе можно отнести также команды очистки/установки регистров и команд перестановки тетрад. Все операции производятся над регистрами общего назначения, результат сохраняется в одном из РОН. Все логические операции выполняются за один машинный цикл.
Рассмотрим наиболее часто используемые команды логических операций с примерами ( все команды логических операций представлены в таблице 2.1):
1. ʼʼЛогическое Иʼʼ двух РОН- AND Rd, Rr
Операция: Rd=Rd AND Rr
Описание:Выполняет операцию ʼʼЛогическое Иʼʼ между содержимым регистров Rd и Rr
Пример: ldi R16,1 ;загрузить 1 в R16
and R2,R16 ;произвести операцию ʼʼЛогическое Иʼʼ
2. ʼʼЛогическое Иʼʼ РОН и константы- ANDI Rd, K
Операция: Rd=Rd AND K
Описание:Выполняет операцию ʼʼЛогическое Иʼʼ между содержимым регистра Rd и 8- разрядным числом. Результат помещается в регистр Rd. Команда применима только к 16 старшим РОН (R16…R31)
Пример: andi R17,$0F ; обнулить старший полубайт регистра R17
andi R18,$10 ;выделить 4-ый разряд в регистре R18
3. ʼʼЛогическое ИЛИʼʼ двух РОН- OR Rd ,Rr
Операция:Rd=RdÚRr
Описание: Выполняет операцию ʼʼЛогическое ИЛИʼʼ между регистрами Rd и Rr. Результат помещается в регистр Rd
Пример: or R15,R16 ;поразрядное ИЛИ R15 и R16
bst R15,6 ;записать 6-ой разряд регистра R15 в флаг Т
brts ok ;перейти если флаг Т равен ʼʼ1ʼʼ
…
ok:
…
4. ʼʼЛогическое ИЛИʼʼ РОН и константы-ORI Rd, К
Операция:Rd=RdÚK
Описание: Выполняет операцию ʼʼЛогическое ИЛИʼʼ между регистром Rd и константой К. Результат помещается в регистр Rd. Команда применима только к 16 старшим РОН (R16…R31)
Пример:ori R17,$0F ;установить старший полубайт регистра R17
ori R18,1 ;установить 0-й разряд регистра R18
5. ʼʼИсключающее ИЛИʼʼ двух РОН- EOR Rd, Rr
Операция:Rd=RdÅRr
Описание:Выполняет операцию ʼʼИсключающее ИЛИʼʼ между регистрами Rd и Rr. Результат помещается в регистр Rd
Пример:eor R2,R2 ;очистка регистра R2
eor R0,R22 ; побитовое ʼʼИсключающее ИЛИʼʼ между R0 и R22
6. Очистка РОН- CLR Rd
Операция:Rd= RdÅRd
Описание:Сбрасывает все разряды регистра общего назначения путем выполнения операции ʼʼИсключающее ИЛИʼʼ регистра с самим собой
Пример:Организация цикла с заданным числом повторений
clr R18 ;очистить регистр R18
loop:
ink R18 ;R18=R18+1
…
cpi R18,$50 ;завершить цикл?
brne loop
Таблица 2.1. Команды логических операций
Мнемоника | Описание | Операции | Циклы | Флаги |
AND Rd,Rr | ʼʼЛогическое Иʼʼ двух РОН | Rd=Rd*Rr | Z,N,V | |
ANDI Rd,K | ʼʼЛогическое Иʼʼ РОН и константы | Rd=Rd*K | Z,N,V | |
EOR Rd,Rr | ʼʼИсключающее ИЛИʼʼ двух РОН | Rd=RdÅRr | Z,N,V | |
OR Rd,Rr | ʼʼЛогическое ИЛИʼʼ двух РОН | Rd=RdÚRr | Z,N,V | |
ORI Rd,K | ʼʼЛогическое ИЛИʼʼ РОН и константы | Rd=RdÚK | Z,N,V | |
COM Rd | Перевод в обратный код | Rd=$FF-Rd | Z,C,N,V | |
NEG Rd | Перевод в дополнительный код | Rd=$00-Rd | Z,C,N,V,H | |
CLR Rd | Сброс всех разрядов РОН | Rd= RdÅRd | Z,N,V | |
SER Rd | Установка всех разрядов РОН | Rd=$FF | - | |
TST Rd | Проверка РОН на отрицательное или нулевое значение | Rd*Rd | Z,N,V | |
SWAP Rd | Обмен местами тетрад в РОН | Rd(3…0)=Rd(7…4) Rd(7…4)=Rd(3…0) | - |
2. 3.2. Команды арифметических операций и команды сдвига
К данной группе относятся команды, выполняющие такие базовые операции, как сложение, вычитание, сдвиг (вправо и влево), инкремент и декремент. Все операции производятся только над РОН. При этом микроконтроллер позволяет легко оперировать как знаковыми, так и беззнаковыми числами, а также работать с числами, представленными в дополнительном коде.
Все команды рассматриваемой группы выполняются за один машинный цикл, за исключением команд, оперирующих двухбайтовыми значениями, которые выполняются за два цикла.
Рассмотрим наиболее часто используемые команды арифметических операций и команды сдвига с примерами ( все команды арифметических операций и команды сдвига представлены в таблице 2.2):
:
1. Сложение двух РОН- ADD Rd, Rr
Операция: Rd=Rd+Rr
Описание:Выполняет операцию сложения между содержимым регистров Rd и Rr
Пример:add R16,R18 ;сложить два регистра
2. Сложение регистровой пары с константой- ADIW Rd, К
Операция:Rd+1:Rd=Rd+1:Rd+K
Описание:Складывает содержимое регистровой пары Rd+1:Rd с 6-разрядным числом. Результат помещается обратно в регистровую пару.
Пример:adiw R24,1 ;прибавить 1 к R25:R24
adiw R30,63 ;прибавить 63 к указателю Z(R31:R30)
3. Вычитание двух РОН- SUB Rd, Rr
Операция:Rd=Rd-Rr
Описание:Вычитает из регистра Rd содержимое регистра Rr. Результат помещается в регистр Rd
Пример:sub R13,R12 ;вычесть R12 из R13 (R13=R13-R12)
brne noteq ;перейти, в случае если R12 не равно R13
…
noteq:
…
4. Вычитание константы из регистра- SUBI Rd, K
Операция: Rd=Rd-K
Описание:Вычитает из регистраRd значение константы К. Результат помещается обратно в регистр.
Размещено на реф.рф
Данная команда применима только к старшей половине регистров общего назначения
Пример:subi R22,$11 ;вычесть $11 из R22
brne noteq ;перейти, в случае если R22 не равно $11
…
noteq:
…
5. Декремент РОН- DEC Rd
Операция:Rd=Rd-1
Описание:Уменьшает содержимое регистра Rd на единицу. Так как эта команда не влияет на флаг переноса С, она идеально подходит для организации счетчика числа интераций цикла при выполнении вычислений над многоразрядными числами. При работе с беззнаковыми числами для выполнения перехода в соответствии с результатом выполнения команды могут использоваться только команды условного перехода BREQ и BRNE. При работе с числами в дополнительном коде могут использоваться все команды условного перехода для знаковых проверок. Флаг V устанавливается в ʼʼ1ʼʼ только в том случае, в случае если до выполнения операции в регистре находилось значение $80
Пример:ldi R17,$10 ;записать число16 в регистр R17
loop:
add R1,R2 ;R1=R1+R2
dec R17 ;декрементировать R17
brne loop ;перейти, в случае если R17 не равно 0
…
6. Инкремент РОН- INC Rd
Операция:Rd=Rd+1
Описание:Увеличивает содержимое регистра Rd на единицу. Так как эта команда не влияет на флаг переноса С, она идеально подходит для организации счетчика числа интераций цикла при выполнении вычислений над многоразрядными числами. При работе с беззнаковыми числами для выполнения перехода в соответствии с результатом выполнения команды могут использоваться только команды условного перехода BREQ и BRNE. При работе с числами в дополнительном коде могут использоваться все команды условного перехода для знаковых проверок. Флаг V устанавливается в ʼʼ1ʼʼ только в том случае, в случае если до выполнения операции в регистре находилось значение $7F
Пример:clr R22 ;очистить регистр R22
loop:
inc R22 ;R22=R22+1
…
cpi R22,$4F ;
brne loop ;продолжать цикл, в случае если R22 не равно $4F
…
7. Арифметический сдвиг вправо- ASR Rd
Операция:Rd(n)=Rd(n+1),n=0…6
Описание:Сдвигает содержимое регистра Rd на 1 разряд вправо. Состояние 7-го разряда не изменяется. Значение 0-го разряда помещается в флаг С регистра SPEG. Часто используется для деления чисел со знаком на два
Пример:ldi R16,$10 ;записать число 16 в регистр R16
asr R16 ; R16=R16/2
ldi R17,$FC ;записать число -4 в регистр R17
asr R17 ; R17=R17/2
8. Логический сдвиг влево- LSL Rd
Операция: Rd(n+1)=Rd(n),Rd(0)=0
Описание:Сдвигает все разряды регистра Rd влево. Разряд b0 сбрасывается в ʼʼ0ʼʼ, а разряд b7 загружается в флаг С регистра SPEG. Эквивалентна команде ADD Rd,Rd
Пример:add R0,R4 ; R0=R0+R4
lsl R0 ;R0=R0*2
9. Логический сдвиг вправо- LSR Rd
Операция: Rd(n)=Rd(n+1) Rd(7)=0
Описание:Сдвигает все разряды регистра Rd вправо. Разряд b7 сбрасывается в ʼʼ0ʼʼ, а разряд b0 загружается в флаг С регистра SPEG.
Пример: add R0,R4 ; R0=R0+R4
lsr R0 ;R0=R0/2
Таблица 2.2. Команды арифметических операций и команд сдвига
Мнемоника | Описание | Операция | Циклы | Флаги |
ADD Rd,Rr | Сложение двух РОН | Rd=Rd+Rr | Z,C,N,V,H | |
ADC Rd,Rr | Сложение двух РОН с переносом | Rd=Rd+Rd+C | Z,C,N,V,H | |
ADIW Rd,K | Сложение регистровой пары с константой | Rdh:Rdl=Rdh:Rdl+K | Z,C,N,V,S | |
SUB Rd,Rr | Вычитание двух РОН | Rd=Rd-Rr | Z,C,N,V,H | |
SUBI Rd,K | Вычитание константы из РОН | Rd=Rd-K | Z,C,N,V,H | |
SBC Rd,Rr | Вычитание двух РОН с заемом | Rd=Rd-Rr-C | Z,C,N,V,H | |
SBCI Rd,K | Вычитание константы из РОН с заемом | Rd=Rd-K-C | Z,C,N,V,H | |
SBIW Rd,K | Вычитание константы из регистровой пары | Rdh:Rdl=Rdh:Rdl-K | Z,C,N,V,S | |
DEC Rd | Декремнт РОН | Rd=Rd-1 | Z,N,V, | |
INC Rd | Инкремент РОН | Rd=Rd+1 | Z,N,V | |
ASR Rd | Арифметический сдвиг вправо | Rd(n)=Rd(n+1), n=0…6 | Z,C,N,V | |
LSL Rd | Логический сдвиг влево | Rd(n+1)=Rd(n),Rd(0)=0 | Z,C,N,V | |
LSR Rd | Логический сдвиг вправо | Rd(n)=Rd(n+1) Rd(7)=0 | Z,C,N,V | |
ROL Rd | Сдвиг влево через перенос | Rd(0)=C,Rd(n+1)=Rd(n),C=Rd(7) | Z,C,N,V | |
ROR Rd | Сдвиг вправо через перенос | Rd(7)=C,Rd(n)=Rd(n+1), C=Rd(0) | Z,C,N,V |
2. 3.3. Команды операций с битами
К данной группе относятся команды, выполняющие установку или сброс заданного разряда РОН или РВВ. Причем для изменения состояния разрядов регистра состояния SREG имеются также дополнительные команды (точнее, эквивалентные мнемонические обозначения общих команд), т.к. проверка состояния разрядов именно этого регистра производится чаще всего. Условно к этой группе можно отнести также две команды передачи управления типа ʼʼпроверка/пропускʼʼ, которые пропускают следующую команду исходя из состояния разрядов РОН или РВВ.
Всем командам данной группы (Таблица 2.3) требуется один машинный цикл для выполнения, за исключением случаев, когда в результате проверки происходит пропуск команды. В этом случае команда выполняется за два или три машинных цикла исходя из пропускаемой команды.
Рассмотрим наиболее часто используемые команды операций с битами с примерами ( все команды операций с битами представлены в таблице 2.3):
1. Сбросить разряд- CBI A, b
Операция: A.b = 0
Описание:Сбрасывает разряд b регистра ввода/вывода, расположенного по адресу А пространства ввода/вывода. Эта команда применима только к младшим 32 регистрам (адреса 0…31)
Пример: cbi $12,7 ;cбросить 7-ой разряд порта D
2. Установить разряд РВВ- SBI A, b
Операция:A.b = 1
Описание:Устанавливает разряд b регистра ввода/вывода, расположенного по адресу А пространства ввода/вывода. Эта команда применима только к младшим 32 регистрам (адреса 0…31)
Пример:out $1E,R0 ;задать адрес ячейки EEPROM
sbi $1C,0 ;установить запрос на чтение
in R1,$1D ;считать данные из EEPROM
3. Пересылка флага Т в разряд РОН- BLD Rd,b
Операция:Rd.b = T
Описание:Копирует флаг Т регистраSPEG в разряд b регистра Rd
Пример:bst R1,2 ;сохранить 2-ой разряд регистра R1 в T
Bld R0,4 ;записать флаг Т в 4-й разряд регистра R0
4. Запись разряда РОН в флаг Т- BST Rd,b
Операция:T=Rd.b
Описание:Копирует разряд b регистра Rd в флаг Т регистра SPEG
Пример:bst R1,2 ;сохранить 2-й разряд регистра R1 вТ
bld R0,4 ;записать флаг Т в 4-й разряд регистра R0
5. Общее запрещение прерываний- CLI
Операция:I=0
Описание:Сбрасывает в ʼʼ0ʼʼ флаг общего разрешения прерываний I регистра SPEG. Эквивалентна команде BCLR 7
Пример:cli ;запретить прерывания
in R11,$16 ;прочитать состояние порта В
sei ;разрешить прерывания
6. Общее разрешение прерываний- SEI
Операция: I=1
Описание:Устанавливает в ʼʼ1ʼʼ флаг общего разрешения прерываний I регистра SPEG. Эквивалентна команде BSET 7
Пример:cli ;запретить прерывания
in R13,$16 ;прочитать состояние порта В
sei ;разрешить прерывания
7. Cброс флага Т- CLT
Операция: T=0
Описание:Сбрасывает в ʼʼ0ʼʼ флаг Т регистра SPEG. Эквивалентна команде BCLR 6
Пример:clt ;сбросить флаг Т
8. Установка флага Т- SET
Операция:T=1
Описание:Устанавливает в ʼʼ1ʼʼ флаг Т регистра SPEG.Эквивалентна команде BSET 6
Пример:set ;установить флаг Т
Таблица 2.3. Команды операций с разрядами
Мнемоника | Описание | Операция | Циклы | Флаги |
CBR Rd,K | Сброс разряда(ов) РОН | Rd=Rd*($FF-K) | Z,N,V | |
SBR Rd,K | Установка разряда(ов) РОН | Rd=RdÚK | Z,N,V | |
CBI A,b | Сброс разряда РВВ | A.![]() | - | |
SBI A,b | Установка разряда РВВ | A.b = 1 | - | |
BCLR s | Сброс флага | SREG.s = 0 | SREG.s | |
BSET s | Установка флага | SREG.s = 1 | SREG.s | |
BLD Rd,b | Загрузка разряда РОН из флага Т (SREG) | Rd.b = T | - | |
BST Rr,b | Запись разряда РОН в флаг Т (SREG) | T=Rd.b | T | |
CLC | Сброс флага переноса | C=0 | C | |
SEC | Установка флага переноса | C=1 | C | |
CLN | Сброс флага отр. Размещено на реф.рф числа | N=0 | N | |
SEN | Установка флага отр. Размещено на реф.рф числа | N=1 | N | |
CLZ | Сброс флага нуля | Z=0 | Z | |
SEZ | Установка флага отр. Размещено на реф.рф числа | Z=1 | Z | |
CLI | Общее запрещение прерываний | I=0 | I | |
SEI | Общее разрешение прерываний | I=1 | I | |
CLS | Сброс флага знака | S=0 | S | |
SES | Установка флага знака | S=1 | S | |
CLV | Сброс флага переполнения доп. Кода | V=0 | V | |
SEV | Установка флага переполнения доп. Кода | V=1 | V | |
CLT | Сброс флага Т | T=0 | T | |
SET | Установка флага Т | T=1 | T | |
CLH | Сброс флага половинного переноса | H=0 | H | |
SEH | Установка флага половинного переноса | H=1 | H |
2.3.4. Команды пересылки данных
Команды этой группы (Таблица 2.4) предназначены для пересылки содержимого ячеек, находящихся в адресном пространстве памяти данных. Разделение адресного пространства на три части (РОН, РВВ, ОЗУ) предопределило разнообразие команд данной группы. Пересылка данных, выполняемая командами группы, может производиться в следующих направлениях:
· РОНÛРОН;
· РОНÛРВВ;
· РОНÛ память данных.
Также к данной группе можно отнести стековые команды PUSH и POP (отсутствуют вAT90S1200), позволяющие сохранять в стеке и восстанавливать из стека содержимое РОН.
На выполнение команд данной группы требуется от одного до трех машинных циклов исходя из команды.
Рассмотрим наиболее часто используемые команды пересылки данных с примерами:
1. Пересылка между РОН- MOV Rd, Rr
Операция: Rd=Rr
Описание:Копирует содержимое регистра Rr в регистр Rd. Регистр-источник (Rr) не изменяется
Пример: mov R16, R0 ;переслать содержимое R0 в R16
call chec ;вызвать подпрограмму
…
chec:
cpi R16, $11 ;сравнить R16 с $11
…
ret ;вернуться из подпрограммы
2. Загрузка константы в РОН- LDI Rd, K
Операция:Rd=K
Описание:Загружает 8-разрядное число в регистр общего назначения Rd. Данная команда применима только к старшей половине РОН (адреса 16…31)
Пример: сlr R31 ;очистить ст. байт индексного регистра Z
ldi R30, $F0 ;загрузить адрес в регистр Z
lpm ;загрузить константу из памяти программ
;по адресу $00F0
3. Косвенное чтение памяти данных- LD Rd, Y
Операция:Rd=[Y]
Описание:Загружает один байт из адресного пространства памяти данных в регистр общего назначения Rd. Адрес ячейки памяти, к которой производится обращение, содержится в индексном регистре Y
Пример:clr R29 ;очистить ст. байт индексного регистра
ldi R28, $60 ;загрузить мл. байт адреса
ld R1, Y ;R1=[$0060]
4. Непосредственная загрузка из памяти данных- LDS Rd, K
Операция: Rd=[k]
Описание:Загружает один байт из адресного пространства памяти данных в регистр общего назначения Rd. Адрес ячейки памяти, к которой производится обращение, задается константой К.
Пример:lds R2, $FF00 ;R2=[$FF00]
add R2, R1 ;R2=R2+R1
sts $FF000, R2 ;записать результат по тому же адресу
5. Косвенная запись в память данных- ST X, Rd
Операция:[X]=Rr
Описание:Сохраняет содержимое регистра общего назначения Rr в памяти данных. Адрес ячейки памяти, к которой производится обращение, содержится в индексном регистре Х
Пример: clr R27 ;очистить ст. байт индексного регистра
ldi R26, $60 ;загрузить мл. байт адреса
st X, R1 ;загрузить R1 по адресу $0060
6. Непосредственная запись в память данных- STS K, Rd
Операция:[k]=Rd
Описание:Сохраняет содержимое регистра общего назначения Rd в памяти данных. Адрес ячейки памяти, к которой производится обращение, задается константой К
Пример:lds R2, $FF00 ;R2=[$FF00]
add R2, R1 ;R2=R2+R1
sts $FF000,R2 ;записать результат по тому же адресу
Таблица 2.4. Команды пересылки данных
Мнемоника | Описание | Операции | Циклы | Флаги |
MOV Rd,Rr | Пересылка между РОН | Rd=Rr | ||
LDI Rd,K | Загрузка константы в РОН | Rd=K | ||
LD Rd,X | Косвенное чтение | Rd=[X] | ||
LD Rd,X+ | Косвенное чтение с постинкрементом | Rd=[X],X=X+1 | ||
LD Rd,-X | Косвенное чтение с преддекрементом | X=X-1,Rd=[X] | ||
LD Rd,Y | Косвенное чтение | Rd=[Y] | ||
LD Rd,Y+ | Косвенное чтение с постинкрементом | Rd=[Y],Y=Y+1 | ||
LD Rd,-Y | Косвенное чтение с преддекрементом | Y=Y-1,Rd=[Y] | ||
LDD Rd,Y+q | Косвенное относительное чтение | Rd=[Y+q] | ||
LD Rd,Z | Косвенное чтение | Rd=[Z] | ||
LD Rd,Z+ | Косвенное чтение с постинкрементом | Rd=[Z],Z=Z+1 | ||
LD Rd,-Z | Косвенное чтение с преддекрементом | Z=Z-1,Rd=[Z] | ||
LDD Rd,Z+q | Косвенное относительное чтение | Rd=[Z+q] | ||
LDS Rd,k | Непосредственное чтение из ОЗУ | Rd=[k] | ||
ST X,Rr | Косвенная запись | [X]=Rr | ||
ST X+,Rr | Косвенная запись с постинкрементом | [X]=Rr,X=X+1 | ||
ST -X,Rr | Косвенная запись с постинкрементом | X=X-1,[X]=Rr | ||
ST Y,Rr | Косвенная запись | [Y]=Rr | ||
ST Y+,Rr | Косвенная запись с постинкрементом | [Y]=Rr,Y=Y+1 | ||
ST -Y,Rr | Косвенная запись с преддекрементом | Y=Y-1,[Y]=Rr | ||
STD Y+q,Rr | Косвенная относительная запись | [Y+q]=Rr | ||
ST Z,Rr | Косвенная запись | [Z]=Rr | ||
ST Z+,Rr | Косвенная запись с постинкрементом | [Z]=Rr,Z=Z+1 | ||
ST -Z,Rr | Косвенная запись с преддекрементом | Z=Z-1,[Z]=Rr | ||
STD Z+q,Rr | Косвенная относительная запись | [Z+q]=Rr | ||
STS k,Rr | Непосредственная запись в ОЗУ | [k]=Rr | ||
LPM | Загрузка данных из памяти программ | R0={Z} | ||
IN Rd,A | Пересылка из РВВ в РОН | Rd=A | ||
OUT A,Rr | Пересылка из РОН в РВВ | A=Rr | ||
PUSH Rr | Сохранение байта в стеке | STACK=Rr | ||
POP Rd | Извлечение байта из стека | Rd=STACK |
2.3.5. Команды передачи управления
В эту группу (Таблица 2.5) входят команды перехода, вызова подпрограмм и возврат из них и команд типа ʼʼпроверка/пропускʼʼ, пропускающие следующую за ними команду при выполнении некоторого условия. Также к этой группе относятся команды сравнения, формирующие флаги регистра SREG и предназначенные, как правило, для работы совместно с командами условного перехода.
Очевидно, что команды передачи управления нарушают нормальное (линейное) выполнение основной программы. Каждый раз, когда выполняется команда из этой группы (кроме команд сравнения), нормальное функционирование конвейера нарушается. Перед загрузкой в конвейер нового адреса производятся остановка и очистка выполняемой последовательности команд. Соответственно реинициализация конвейера приводит к крайне важно сти использования нескольких машинных циклов для выполнения таких команд.
Рассмотрим наиболее часто используемые команды передачи управления с примерами:
1. Относительный безусловный переход- RJMP k
Операция: PC=PC+k+1
Описание:Выполняет переход по адресу, равному сумме содержимого счетчика команд и константы k. На практике вместо числовых значений смещения используются метки
Пример: сpi R16, $42 ;сравнить R16 с числом $42
brne M1 ;перейти к M1, в случае если R16 не равно $42
rjmp ok ;безусловный переход
M1:
add R16, R17 ;прибавить R16 к R17
ink R16 ;R16=R16+1
ok:
…
2. Относительный вызов подпрограммы- RCALL k
Операция:PC=PC+k+1
Описание:Выполняет переход к подпрограмме, адрес которой получается сложением содержимого счетчика команд с константой k. Адрес следующей за RCALL командой (2 байта) сохраняется в стеке. На практике вместо числовых значений смещения указываются метки подпрограмм
Пример:rcall routine ;вызвать подпрограмму
…
routine:
push R14 ;сохранить содержимое R14
…
pop R14 ;восстановить содержимое R14
ret ;возврат из подпрограммы
3. Возврат из подпрограммы- RET
Операция:PC=STACK
Описание:Выполняет возврат в то место, откуда подпрограмма была вызвана
Пример: rcall routine ;вызвать подпрограмму
…
routine:
push R14 ;сохранить содержимое R14
…
pop R14 ;восстановить содержимое R14
ret ;возврат из подпрограммы
4. Пропуск команды при равенстве двух РОН- CPSE Rd, Rr
Операция:В случае если Rd=Rr, то PC=PC+2(3), иначе РС=РС+1
Описание:Сравнивает содержимое двух регистров общего назначения Rd и Rr и пропускает следующую команду, в случае если в регистрах записаны одинаковые значения
Пример:ink R4 ;увеличить R4 (R4=R4+1)
cpse R4, R0;сравнить содержимоеR4 и R0
neg R4 ;проинвертировать R4, в случае если R4 не равно R0
…
5. Сравнение РОН- CP Rd, Rr
Операция: Rd-Rr
Описание:Сравнивает содержимое двух РОН путем вычитания содержимого регистра Rr из содержимого регистра Rd. Данная команда влияет только на флаги регистра состояния SPEG, которые устанавливаются в соответствии с результатом вычитания. Содержимое регистров не изменяется. Как правило, данная команда используется совместно с одной из команд условного перехода
Пример:cp R4, R19 ;сравнить R4 с R19 (R=R4-R19)
brne noteq ;перейти, в случае если R4<>R19
…
noteq:
…
6. Сравнение содержимого РОН с константой- CPI Rd, K
Операция: Rd-K
Описание:Сравнивает содержимое РОН Rd с константой К путем вычитания константы из содержимого регистра Rd. Данная команда влияет только на флаги регистра состояния SPEG, которые устанавливаются в соответствии с результатом вычитания. Содержимое регистра Rd не изменяется. Как правило, данная команда используется совместно с одной из команд условного перехода
Пример:cpi R19, 3 ;сравнить R19 с числом 3 (R=R19-3)
brne noteq ;перейти, в случае если R19<>3
…
noteq:
…
7. Переход по условию ʼʼравноʼʼ- BREQ k
Операция: В случае если Z=1, то PC=PC+k+1, иначе РС=РС+1
Описание:Условный относительный переход. Проверяет флаг нуля (Z) и выполняет переход, в случае если данный разряд установлен. Величина смещения k представляется числом в дополнительном коде. При выполнении данной команды сразу же после команды CP, CPI, SUB или SUBI переход произойдет только в том случае, в случае если число (со знаком или без знака), находящееся в регистре Rd, будет равно числу (со знаком или без знака), находящемуся в регистре Rr. Эквивалентна команде BRBS 1, k
Пример:cp R1, R0 ;сравнить R1 с R0
breq equal ;перейти, в случае если R1=R2
…
equal:
…
8. Переход по условию ʼʼне равноʼʼ- BRNE k
Операция: В случае если Z=0, то PC=PC+k+1, иначе РС=РС+1
Описание:Условный относительный переход. Проверяет флаг нуля (Z) и выполняет переход, в случае если данный разряд сброшен. Величина смещения k представляется числом в дополнительном коде. При выполнении данной команды сразу же после команды CP, CPI, SUB или SUBI переход произойдет только в том случае, в случае если числа (со знаком или без знака), находящиеся в регистрах Rd и Rr, не будут равны. Эквивалентна команде BRBC 1,k
Пример: eor R27, R27 ;очистить R27
loop: inc R27 ;R27=R27+1
…
cpi R27, 5 ;сравнить R27 с 5
brne loop ;перейти, в случае если R27 не равно 5
…
9. Переход по условию ʼʼвыше или равноʼʼ (для беззнаковых данных)- BRSH k
Операция:В случае если C=0, то PC=PC+k+1, иначе РС=РС+1
Описание:Условный относительный переход. Проверяет флаг переноса (С) и выполняет переход, в случае если данный разряд сброшен. Величина смещения k представляется числом в дополнительном коде. При выполнении данной команды сразу же после команды CP, CPI, SUB или SUBI переход произойдет только в том случае, в случае если беззнаковое число, находящееся в регистре Rd, будет больше (или равно) беззнакового числа, находящегося в регистре Rr. Эквивалентна команде BRBC 0,k
Пример:subi R19,4 ;R19=R19-4
brsh hgsm ;перейти, в случае если R19 больше или равно 4
…
hgsm:
…
10. Переход по условию ʼʼменьшеʼʼ (для беззнаковых данных)- BRLO k
Операция:В случае если C=1, то PC=PC+k+1, иначе РС=РС+1
Описание:Условный относительный переход. Проверяет флаг переноса (С) и выполняет переход, в случае если данный разряд установлен. Величина смещения k представляется числом в дополнительном коде. При выполнении данной команды сразу же после команды CP, CPI, SUB или SUBI переход произойдет только в том случае, в случае если беззнаковое число, находящееся в регистре Rd, будет меньше беззнакового числа, находящееся в регистре Rr. Эквивалентна команде BRBS 0,k
Пример:eor R19, R19 ;очистить R19
loop: inc R19 ;R19=R19+1
…
cpi R19, $10 ;сравнить R19 с $10
brlo loop ;перейти, в случае если R19<$10
…
11. Переход по условию ʼʼотрицательное значениеʼʼ- BRMI k
Операция:В случае если N=1, то PC=PC+k+1, иначе РС=РС+1
Описание:Условный относительный переход. Проверяет флаг отрицательного значения (N) и выполняет переход, в случае если данный разряд установлен. Величина смещения k представляется числом в дополнительном коде. Эквивалентна команде BRBS 2, k
Пример:subi R18, 4 ;R18=R18-4
brmi minus ;перейти, в случае если результат отрицательный
…
minus:
…
12. Переход по условию ʼʼположительное значениеʼʼ- BRPL k
Операция:В случае если N=0, то PC=PC+k+1, иначе РС=РС+1
Описание:Условный относительный переход. Проверяет флаг отрицательного значения (N) и выполняет переход, в случае если данный разряд сброшен. Величина смещения k представляется числом в дополнительном коде. Эквивалентна команде BRBC 2, k
Пример:subi R26, $50 ;R26=R26-$50
brpl plus ;перейти, в случае если результат положительный
厠ਣ …
plus:
…
Таблица 2.5. Команды передачи управления
Мнемоника | Описание | Операция | Цикл | Флаги |
RJMP k | Относительный безусловный переход | PC=PC+k+1 | - | |
IJMP | Косвенный безусловный переход | PC=Z | - | |
RCALL | Относительный вызов подпрограммы | PC=PC+k+1 | - | |
ICALL | Косвенный вызов подпрограммы | PC=Z | - | |
RET | Возврат из подпрограммы | PC=STACK | - | |
RETI | Возврат из подпрограммы обработки прерывания | PC=STACK | I | |
CP Rd,Rr | Сравнение РОН | Rd-Rr | Z,N,V,C,H | |
CPC Rd,Rr | Сравнение РОН с учетом переноса | Rd-Rr-C | Z,N,V,C,H | |
CPI Rd,K | Сравнение РОН с константой | Rd-K | Z,N,V,C,H | |
CPSE Rd,Rr | Сравнение и пропуск след. команды при равенстве | В случае если Rd=Rr, то PC=PC+2(3) | 1/2/3 | - |
SBRC Rr,b | Пропуск след. команды, в случае если разряд РОН сброшен | В случае если Rr.b=0, то PC=PC+2(3) | 1/2/3 | - |
SBRS Rr,b | Пропуск след. команды, в случае если разряд РОН установлен | В случае если Rr.b=1, то PC=PC+2(3) | 1/2/3 | - |
SBIC A,b | Пропуск след. команды, в случае если разряд РВВ сброшен | В случае если A.b=0, то PC=PC+2(3) | 1/2/3 | - |
SBIS A,b | Пропуск след. команды, в случае если разряд РВВ установлен | В случае если A.b=1, то PC=PC+2(3) | 1/2/3 | - |
BRBC s,k | Переход, в случае если флаг s регистра SREG сброшен | В случае если SREG.s =0, то PC=PC+k+1 | 1/2 | - |
BRBS s,k | Переход, в случае если флаг s регистра SREG установлен | В случае если SREG.s =1, то PC=PC+k+1 | 1/2 | - |
BRCS k | Переход по переносу | В случае если C=1, то PC=PC+k+1 | 1/2 | - |
BRCC k | Переход, в случае если нет переноса | В случае если C=0, то PC=PC+k+1 | 1/2 | - |
BREQ k | Переход по условию ʼʼравноʼʼ | В случае если Z=1, то PC=PC+k+1 | 1/2 | - |
BRNE k | Переход по условию ʼʼне равноʼʼ | В случае если Z=0, то PC=PC+k+1 | 1/2 | - |
BRSH k | Переход по условию ʼʼвыше или равноʼʼ | В случае если C=0, то PC=PC+k+1 | 1/2 | - |
BRLO k | Переход по условию ʼʼменьшеʼʼ | В случае если C=1, то PC=PC+k+1 | 1/2 | - |
BRMI | Переход по условию ʼʼотрицательное значениеʼʼ | В случае если N=1, то PC=PC+k+1 | 1/2 | - |
BRPL | Переход по условию ʼʼположительное значениеʼʼ | В случае если N=0, то PC=PC+k+1 | 1/2 | - |
BRGE | Переход по условию ʼʼбольше или равноʼʼ (числа со знаком) | В случае если (NÅV)=0, то PC=PC+k+1 | 1/2 | - |
BRLT | Переход по условию ʼʼменьше нуляʼʼ (числа со знаком) | В случае если (NÅV)=1, то PC=PC+k+1 | 1/2 | - |
BRHS | Переход по половинному переносу | В случае если H=1, то PC=PC+k+1 | 1/2 | - |
BRHC | Переход, в случае если нет половинного переноса | В случае если H=0, то PC=PC+k+1 | 1/2 | - |
BRTS | Переход, в случае если флаг T установлен | В случае если T=1, то PC=PC+k+1 | 1/2 | - |
BRTC | Переход, в случае если флаг T сброшен | В случае если T=0, то PC=PC+k+1 | 1/2 | - |
BRVS | Переход по переполнению доп. кода | В случае если V=1, то PC=PC+k+1 | 1/2 | - |
BRVC | Переход, в случае если нет переполнения доп. кода | В случае если V=0, то PC=PC+k+1 | 1/2 | - |
BRID | Переход, в случае если прерывания запрещены | В случае если I=0, то PC=PC+k+1 | 1/2 | - |
BRIE | Переход, в случае если прерывания разрешены | В случае если I=1, то PC=PC+k+1 | 1/2 | - |
2.3.6. Команды управления системой
В эту группу входят всего 3 команды:
· NOP – пустая команда;
· SLEEP – перевод микроконтроллера в режим пониженного энергопотребления;
· WDR – сброс сторожевого таймера.
Команды NOP и WDR выполняются за один машинный цикл, а команда SLEEP – за четыре машинных цикла.
avr-objdump -d file_name.elf
Кому-то может больше понравиться дизассемблированный код с фрагментами исходного кода, где это возможно. Пример вызова для получения такого результата:avr-objdump -S file_name.elf
Полный список возможных параметров вы можете найти по ссылке: http://ccrma.stanford.edu/planetccrma/man/man1/avr-objdump.1.html
Хорошо, где взять avr-objdump и объектный файл? Первый входит в состав IDE Arduino и расположен в каталоге Arduino_dir\hardware\tools\avr\bin\, где Arduino_dir — это путь установки IDE Arduino.
Что касается объектных (elf) файлов, то они создаются средой разработки при компиляции проекта и находятся во временной папке скетча. Найти их можно следующим образом:
1. Если у вас еще не включен вывод сообщений при компиляции скетчей, то перейдите в окно настроек IDE Arduino и установите соответствующую опцию:
2. После компиляции скетча в последних строках, выводимых средой разработки, вы увидите примерно следующее:
Остается перейти в этот каталог и скопировать elf файл в Arduino_dir\hardware\tools\avr\bin\. Теперь, когда у нас avr-objdump и объектный файл находятся в одном каталоге (конечно, это необязательно, если в командах указывать полный путь к файлам), запускаем командную строку и вводим одну из приведенных ранее команд для дизассемблирования с поправкой на имя файла. В моем случае команда выглядит так:
avr-objdump.exe -S sketch_jun18a.ino.elf
В интерпретаторе командной строки необязательно вводить имя файла целиком: достаточно ввести несколько первых символов, затем нажимать TAB, пока интерпретатор не подставит нужное имя.
Итак, выполняем введенную команду и видим весьма объемный результат ее работы:
avr-objdump.exe -S sketch_jun18a.ino.elf > my_file.asm
При выполнении данной команды в каталоге с avr-objdump.exe будет создан файл my_file.asm, в который будет сохранен результат работы команды:
Я для генерации ассемблерного листинга использую команду меню Скетч->Экспорт бинарного файла среды разработки Ардуино. При таком способе результат дизассемблирования сохраняется в каталог скетча (вместе с hex файлом), не приходится лазить по временным папкам. Для настройки IDE Arduino на генерацию ассемблерного листинга выполним следующие действия:
1. Переходим в каталог Arduino_dir\hardware\arduino\avr\ и открываем файл platform.txt в текстовом редакторе.
2. Находим команды для сохранения hex файла и добавляем после них следующую строку:
recipe.hooks.savehex.postsavehex.1.pattern=»{compiler.path}/elf2asm.bat» «{compiler.path}avr-objdump» «{build.path}/{build.project_name}.elf» «{sketch_path}/{build.project_name}.asm»
%1 -S %2 > %3
Параметр -S определяет генерацию смешанного листинга. Использование bat файла обусловлено тем, что мы не можем использовать перенаправление вывода непосредственно в файле platform.txt.
На этом настройка IDE Arduino завершена. Можно переключиться в нее и проверить корректность работы, выбрав команду «Экспорт бинарного файла» в меню «Скетч». В каталоге со скетчем должен появиться файл с расширением asm. Приведенный способ дизассемблирования скетча пригодится нам в следующей публикации для анализа кода и продолжения темы обработки прерываний.
% PDF-1.6 % 8508 0 объект > эндобдж xref 8508 436 0000000016 00000 н. 0000010522 00000 п. 0000010657 00000 п. 0000010789 00000 п. 0000010826 00000 п. 0000011201 00000 п. 0000011350 00000 п. 0000011494 00000 п. 0000011864 00000 п. 0000012288 00000 п. 0000012933 00000 п. 0000013087 00000 п. 0000013394 00000 п. 0000013894 00000 п. 0000014358 00000 п. 0000014816 00000 п. 0000015488 00000 п. 0000016349 00000 п. 0000017295 00000 п. 0000018108 00000 п. 0000018210 00000 п. 0000046690 00000 н. 0000046922 00000 п. 0000047426 00000 п. 0000047537 00000 п. 0000086059 00000 п. 0000086286 00000 п. 0000086950 00000 п. 0000087039 00000 п. 0000102213 00000 н. 0000102452 00000 н. 0000102669 00000 н. 0000102745 00000 н. 0000102894 00000 п. 0000103112 00000 н. 0000103290 00000 н. 0000103472 00000 н. 0000103680 00000 п. 0000103843 00000 н. 0000104066 00000 н. 0000104284 00000 п. 0000104445 00000 н. 0000104676 00000 н. 0000104912 00000 н. 0000105072 00000 н. 0000105295 00000 н. 0000105511 00000 п. 0000105672 00000 н. 0000105895 00000 н. 0000106126 00000 п. 0000106287 00000 н. 0000106499 00000 н. 0000106735 00000 н. 0000106896 00000 н. 0000107107 00000 н. 0000107369 00000 н. 0000107531 00000 н. 0000107756 00000 н. 0000108001 00000 н. 0000108165 00000 н. 0000108390 00000 н. 0000108705 00000 н. 0000108865 00000 н. 0000109090 00000 н. 0000109407 00000 н. 0000109572 00000 н. 0000109796 00000 н. 0000110096 00000 н. 0000110259 00000 н. 0000110484 00000 н. 0000110722 00000 н. 0000110884 00000 н. 0000111108 00000 н. 0000111339 00000 н. 0000111499 00000 н. 0000111725 00000 н. 0000111914 00000 н. 0000112074 00000 н. 0000112301 00000 н. 0000112517 00000 н. 0000112677 00000 н. 0000112902 00000 н. 0000113127 00000 н. 0000113288 00000 н. 0000113512 00000 н. 0000113722 00000 н. 0000113884 00000 н. 0000114108 00000 н. 0000114332 00000 н. 0000114492 00000 н. 0000114718 00000 н. 0000114957 00000 н. 0000115117 00000 н. 0000115343 00000 н. 0000115570 00000 н. 0000115731 00000 н. 0000115956 00000 н. 0000116198 00000 н. 0000116358 00000 п. 0000116585 00000 н. 0000116814 00000 н. 0000116974 00000 н. 0000117202 00000 н. 0000117420 00000 н. 0000117582 00000 н. 0000117806 00000 н. 0000118058 00000 н. 0000118220 00000 н. 0000118444 00000 н. 0000118705 00000 н. 0000118866 00000 н. 0000119090 00000 н. 0000119321 00000 н. 0000119482 00000 н. 0000119706 00000 н. 0000119955 00000 н. 0000120115 00000 н. 0000120337 00000 н. 0000120597 00000 н. 0000120758 00000 н. 0000120986 00000 н. 0000121254 00000 н. 0000121414 00000 н. 0000121638 00000 н. 0000121874 00000 н. 0000122034 00000 н. 0000122255 00000 н. 0000122568 00000 н. 0000122727 00000 н. 0000122950 00000 н. 0000123178 00000 н. 0000123337 00000 н. 0000123557 00000 н. 0000123800 00000 н. 0000123960 00000 н. 0000124180 00000 н. 0000124416 00000 н. 0000124576 00000 н. 0000124797 00000 н. 0000125014 00000 н. 0000125172 00000 н. 0000125397 00000 н. 0000125629 00000 н. 0000125788 00000 н. 0000126012 00000 н. 0000126245 00000 н. 0000126404 00000 н. 0000126627 00000 н. 0000126872 00000 н. 0000127030 00000 н. 0000127253 00000 н. 0000127487 00000 н. 0000127648 00000 н. 0000127872 00000 н. 0000128108 00000 н. 0000128267 00000 н. 0000128488 00000 н. 0000128736 00000 н. 0000128896 00000 н. 0000129120 00000 н. 0000129359 00000 н. 0000129517 00000 н. 0000129738 00000 н. 0000129944 00000 н. 0000130104 00000 п. 0000130329 00000 н. 0000130541 00000 н. 0000130701 00000 п. 0000130923 00000 п. 0000131144 00000 н. 0000131302 00000 н. 0000131523 00000 н. 0000131775 00000 н. 0000131934 00000 н. 0000132160 00000 н. 0000132382 00000 н. 0000132541 00000 н. 0000132767 00000 н. 0000132989 00000 н. 0000133148 00000 н. 0000133368 00000 н. 0000133595 00000 н. 0000133756 00000 н. 0000133977 00000 н. 0000134191 00000 п. 0000134350 00000 н. 0000134573 00000 н. 0000134799 00000 н. 0000134958 00000 н. 0000135181 00000 н. 0000135408 00000 н. 0000135567 00000 н. 0000135788 00000 н. 0000136015 00000 н. 0000136173 00000 п. 0000136394 00000 н. 0000136653 00000 н. 0000136812 00000 н. 0000137034 00000 п. 0000137278 00000 н. 0000137436 00000 н. 0000137658 00000 н. 0000137876 00000 н. 0000138035 00000 н. 0000138257 00000 н. 0000138567 00000 н. 0000138725 00000 н. 0000138948 00000 н. 0000139255 00000 н. 0000139414 00000 н. 0000139635 00000 н. 0000139932 00000 н. 0000140091 00000 н. 0000140313 00000 н. 0000140534 00000 н. 0000140694 00000 п. 0000140914 00000 н. 0000141127 00000 н. 0000141285 00000 н. 0000141509 00000 н. 0000141724 00000 н. 0000141883 00000 н. 0000142109 00000 н. 0000142306 00000 н. 0000142464 00000 н. 0000142686 00000 н. 0000142892 00000 н. 0000143051 00000 н. 0000143262 00000 н. 0000143512 00000 н. 0000143671 00000 н. 0000143896 00000 н. 0000144113 00000 п. 0000144271 00000 н. 0000144493 00000 н. 0000144741 00000 н. 0000144902 00000 н. 0000145126 00000 н. 0000145401 00000 п. 0000145562 00000 н. 0000145784 00000 н. 0000146029 00000 н. 0000146190 00000 п. 0000146410 00000 н. 0000146659 00000 н. 0000146818 00000 н. 0000147042 00000 н. 0000147253 00000 н. 0000147412 00000 н. 0000147635 00000 н. 0000147884 00000 н. 0000148043 00000 н. 0000148264 00000 н. 0000148502 00000 н. 0000148663 00000 н. 0000148887 00000 н. 0000149154 00000 н. 0000149314 00000 н. 0000149534 00000 п. 0000149771 00000 п. 0000149916 00000 н. 0000150122 00000 н. 0000150281 00000 н. 0000150488 00000 н. 0000150721 00000 н. 0000150880 00000 н. 0000151102 00000 н. 0000151336 00000 н. 0000151498 00000 н. 0000151721 00000 н. 0000151946 00000 н. 0000152105 00000 н. 0000152325 00000 н. 0000152524 00000 н. 0000152682 00000 н. 0000152905 00000 н. 0000153126 00000 н. 0000153284 00000 н. 0000153505 00000 н. 0000153724 00000 н. 0000153883 00000 н. 0000154104 00000 н. 0000154331 00000 н. 0000154489 00000 н. 0000154713 00000 н. 0000154925 00000 н. 0000155084 00000 н. 0000155307 00000 н. 0000155532 00000 н. 0000155692 00000 н. 0000155915 00000 н. 0000156130 00000 н. 0000156288 00000 н. 0000156508 00000 н. 0000156733 00000 н. 0000156892 00000 н. 0000157116 00000 н. 0000157358 00000 н. 0000157519 00000 н. 0000157740 00000 н. 0000157970 00000 н. 0000158129 00000 н. 0000158353 00000 н. 0000158575 00000 н. 0000158734 00000 н. 0000158959 00000 н. 0000159191 00000 п. 0000159350 00000 н. 0000159573 00000 н. 0000159814 00000 н. 0000159973 00000 н. 0000160194 00000 п. 0000160436 00000 н. 0000160596 00000 н. 0000160820 00000 н. 0000161107 00000 н. 0000161267 00000 н. 0000161488 00000 н. 0000161707 00000 н. 0000161866 00000 н. 0000162088 00000 н. 0000162324 00000 н. 0000162486 00000 н. 0000162706 00000 н. 0000162949 00000 н. 0000163109 00000 н. 0000163336 00000 н. 0000163581 00000 н. 0000163739 00000 н. 0000163960 00000 н. 0000164211 00000 н. 0000164369 00000 н. 0000164589 00000 н. 0000164849 00000 н. 0000165007 00000 н. 0000165228 00000 н. 0000165446 00000 н. 0000165605 00000 н. 0000165826 00000 н. 0000166054 00000 н. 0000166212 00000 н. 0000166435 00000 н. 0000166657 00000 н. 0000166815 00000 н. 0000167040 00000 н. 0000167287 00000 н. 0000167447 00000 н. 0000167669 00000 н. 0000167910 00000 н. 0000168070 00000 н. 0000168290 00000 н. 0000168554 00000 н. 0000168716 00000 н. 0000168938 00000 н. 0000169205 00000 н. 0000169366 00000 н. 0000169587 00000 н. 0000169840 00000 н. 0000169999 00000 н. 0000170222 00000 п. 0000170487 00000 н. 0000170645 00000 н. 0000170869 00000 н. 0000171130 00000 н. 0000171291 00000 н. 0000171513 00000 н. 0000171732 00000 н. 0000171891 00000 н. 0000172113 00000 н. 0000172314 00000 н. 0000172473 00000 н. 0000172696 00000 н. 0000172924 00000 н. 0000173084 00000 н. 0000173306 00000 н. 0000173544 00000 н. 0000173702 00000 н. 0000173924 00000 н. 0000174169 00000 н. 0000174328 00000 н. 0000174551 00000 н. 0000174807 00000 н. 0000174965 00000 н. 0000175187 00000 н. 0000175484 00000 н. 0000175643 00000 н. 0000175866 00000 н. 0000176092 00000 н. {ч%.| ږ J WFGx | * # ̔? Ǡh H \ .t6CIࣿlkw.ujM {4Q 3B +? S»dlV% c.Ud (
AVR содержат 32 8-битных рабочих регистра общего назначения, которые напрямую подключены к Арифметико-логическому устройству (ALU). Почти все манипуляции с данными в вашей программе должны выполняться с помощью рабочих регистров общего назначения, поэтому важно понимать предлагаемые ими инструкции и способы их использования.
Ниже приведен обзор этих инструкций.Если вы впервые видите их, не волнуйтесь, если вы чувствуете себя подавленным. Важно понимать типы доступных инструкций и их общий синтаксис, а не запоминать их все.
Архитектура AVR имеет много инструкций для передачи данных, но пока мы рассмотрим только те, которые относятся к передаче данных между рабочими регистрами общего назначения. Они показаны в таблице ниже.
Мнемоника | Описание |
---|---|
ldi | немедленная загрузка |
мов | копировальный аппарат |
movw | копировать регистровую пару |
Одна из наиболее распространенных инструкций, которые вы будете использовать, — это ldi — загрузить немедленно . ldi позволяет загружать постоянное 8-битное значение непосредственно в указанный регистр. Например, константа 85 может быть загружена в регистр r16 следующим кодом:
ldi r16,85; загрузить значение 85 в регистр 16
Константу можно указать разными способами.Все нижеприведенные значения действительны и эквивалентны:
ldi r16,85; загрузить 85 в регистр 16 (десятичный)
ldi r16,0x55; загрузить 85 в регистр 16 (шестнадцатеричный)
ldi r16 - 55 долларов США; загрузить 85 в регистр 16 (также шестнадцатеричный)
ldi r16,0125; загрузить 85 в регистр 16 (восьмеричный)
ldi r16,0b01010101; загрузить 85 в регистр 16 (двоичный)
Обратите внимание на порядок инструкции и операндов. Сначала идет инструкция, за ней следует назначение, а затем , затем — вход. Сначала это может сбивать с толку, но полезно читать это как загружает r16 со значением 85 .
ldi является частью специального набора непосредственных инструкций . Непосредственные инструкции работают с регистром и требуют, чтобы в качестве операнда была указана константа. Непосредственные инструкции очень полезны, поскольку они позволяют вам работать с числом, которое вы указываете в самом коде. Однако не все из 32 регистров могут использоваться с непосредственными инструкциями — только регистры с 16 по 31.
Помните: Мгновенные инструкции работают только с регистрами с 16 по 31.Попытка использовать их с регистрами от 0 до 15 приведет к ошибке.
Содержимое одного регистра можно скопировать в другой регистр с помощью инструкции mov. Поскольку ldi работает только с регистрами с 16 по 31, инструкция mov — удобный способ загрузить константу в один из младших 16 регистров. Например, приведенный ниже код показывает, что константа загружается в r16, а mov используется для ее копирования в r0.
ldi r16,0x55; загрузить 0x55 в r16
mov r0, r16; скопировать содержимое r16 в r0
Как и в случае с ldi, порядок операндов в инструкции mov может сбивать с толку с указанием места назначения первым, а затем источника.Опять же, полезно читать как скопировать в r0 содержимое r16
Примечание: Хотя инструкция mov выглядит как переместить , она фактически копирует содержимое одного регистра в другой. После инструкции исходный регистр остается без изменений.
Хотя AVR имеет 8-битную архитектуру, есть несколько избранных инструкций, которые позволяют вам работать с 16-битными значениями (словами). Одна из них — инструкция movw, которая позволяет копировать содержимое одной пары регистров в другую пару регистров.Например, в приведенном ниже коде показано, как загрузить константу 0x1234 в r16 и r17 и скопировать ее в r0 и r1.
ldi r16,0x34; загрузить младший байт 0x1234 в r16
ldi r17,0x12; загрузить старший байт 0x1234 в r17
movw r1: r0, r17: r16; скопируйте содержимое r17: r16 в r1: r0
Вместо того, чтобы набирать пару регистров, которые вы хотите скопировать, вы можете просто указать нижний из двух, и будет подразумеваться верхний регистр, например
movw r0, r16; скопируйте содержимое r17: r16 в r1: r0
Обратите внимание, что эта инструкция только работает с парами регистров, в которых нижний регистр (как для источника, так и для пункта назначения) является четным значением.Например, следующее вызовет ошибку, поскольку нижний регистр места назначения имеет нечетное значение
. movw r2: r1, r19: r18; r2: r1 - недопустимый операнд для movw
Как и mov, movw выполняет копию , а не перемещение. Исходные регистры не меняются после этой инструкции.
ЦП поддерживает логические операции между двумя регистрами или регистром и константой. Основные логические инструкции показаны в таблице ниже.
Мнемоника | Описание |
---|---|
и | логическое И |
andi | логическое И с немедленным |
или | логическое ИЛИ |
ори | логическое ИЛИ с немедленным |
eor | эксклюзивно ИЛИ |
ком | одно дополнение |
нег | дополнение до двух |
Есть две логические непосредственные инструкции — andi и ori, которые вычисляют логические И или ИЛИ между регистром и константой.Они записываются так же, как ldi, сначала с мнемоникой инструкций, затем с регистром, а затем с константой. Некоторые примеры показаны ниже:
ldi r16,0x55; загрузить 0x55 в r16
andi r16,0x0F; маскировать верхние 4 бита r16 (результат = 0x05)
ldi r17,0x00; загрузить 0x00 в r17
ori r17,0x0F; установить младшие 4 бита r17 (результат = 0x0F)
Как и ldi, andi и ori работают только с регистрами с 16 по 31.
Другие логические инструкции не являются непосредственными — они работают с двумя регистрами, а не с регистром и константой.ЦП может вычислить логическое ИЛИ И , ИЛИ или Исключающее ИЛИ между двумя регистрами и перезаписать первый регистр результатом. Например, ниже показаны значения 0x55 и 0x0F, загружаемые в r16 и r17 соответственно. Затем между ними вычисляется логическое И , и результат сохраняется в r16
.
ldi r16,0x55; загрузить 0x55 в r16
ldi r17,0x0F; загрузить 0x0F в r17
и r16, r17; маскировать верхние 4 бита r16 (результат = 0x05)
Если бы порядок регистров был изменен, выход был бы сохранен в r17, i.е.
, и r17, r16; маскировать верхние 4 бита r17 (результат = 0x05)
Инструкции or и eor работают одинаково:
ldi r16,0x55; загрузить 0x55 в r16
ldi r17,0x0F; загрузить 0x0F в r17
или r16, r17; установить младшие 4 бита r16 (результат = 0x5F)
ldi r16,0x55; загрузить 0x55 в r16
ldi r17,0xFF; загрузить 0xFF в r17
eor r16, r17; переключить младшие 4 бита r16 (результат = 0xAA)
Один и тот же регистр может использоваться для обоих операндов
eor r16, r16; очистить r16 (результат = 0x00)
Инструкция com позволяет переключать все биты регистра.Например
ldi r16,0x55; загрузить 0x55 в r16
com r16; одно дополнение к r16 (результат = 0xAA)
Инструкция neg принимает два дополнения регистра (т. Е. Отменяет его).
ldi r16,5; загрузить 5 в r16
neg r16; отрицать r16 (результат = -5 = 0xFB)
AVR поддерживает множество арифметических инструкций для регистров общего назначения, показанных в таблице ниже.
Мнемоника | Описание |
---|---|
доб. | добавить без переноски |
АЦП | добавить с переноской |
adiw | добавить сразу к слову |
суб | вычесть без переноса |
subi | вычесть сразу |
сбк | вычесть с переносом |
SBCI | немедленно вычесть с переносом |
сбив | немедленно вычесть из слова |
инк. | шаг |
дек | Декрет|
мул | умножить без знака (1) |
муль | со множеством знаков (1) |
Мульсу | , умноженное на знак без знака (1) |
фмул | дробное умножение без знака (1) |
мулс | дробное умножение со знаком (1) |
фмулсу | дробное умножение со знаком и без знака (1) |
Регистры могут быть добавлены с помощью инструкции добавления.add принимает два регистра в качестве входных данных, вычисляет их сумму и сохраняет значение в первом входе. В приведенном ниже коде показано, как вычислить сумму 5 и 6 и сохранить результат в r16:
. ldi r16,5; загрузить 5 в регистр 16
ldi r17,6; загрузить 6 в регистр 17
добавить r16, r17; сложите r16 и r17 и сохраните результат в r16
Аналогично, команда adc — сложить с переносом , принимает два регистра в качестве входных данных и вычисляет их сумму, но добавит бит переноса , если предыдущая операция привела к переполнению.Это полезно при вычислении сумм 16-битных чисел. Как мы видели ранее, сложение 0x1234 и 0xABCD может быть вычислено как
ldi r16,0x34; поместите младший байт 0x1234 в r16
ldi r17,0x12; поместите старший байт 0x1234 в r17
ldi r18,0xCD; поместите младший байт 0xABCD в r18
ldi r19,0xAB; поместите старший байт 0xABCD в r19
добавить r16, r18; вычислить сумму младших байтов (результат = 0x01)
adc r17, r19; вычислить сумму старших байтов с переносом (результат = 0xBE)
Результатом выше является сумма 0xBE01, сохраненная в r16 и r17.
Как и в случае с сложением, есть пара инструкций вычитания, sub — вычитать и sbc — вычитать с переносом . sub и sbc используются так же, как add и adc. Например
ldi r16,0x01; поместите младший байт 0xBE01 в r16
ldi r17,0xBE; поместите старший байт 0xBE01 в r17
ldi r18,0x34; поместите младший байт 0x1234 в r18
ldi r19,0x12; поместите старший байт 0x1234 в r19
sub r16, r18; вычесть младшие байты (результат = 0xCD)
sbc r17, r19; вычесть старшие байты с переносом (результат = 0xAB)
Кроме того, существует команда немедленного вычитания — sbi, которая позволяет вычитать 8-битную константу из регистра.
ldi r16,0x05; загрузить 0x05 в регистр 16
sbi r16,0x05; вычесть 5 из r16 (результат = 0x00)
К сожалению, нет соответствующей инструкции добавить немедленно .
Пара inc и dec — это простые способы увеличения или уменьшения значения в регистре на единицу. Им требуется один регистр в качестве операнда, и они работают с любым из 32 регистров.
clr r16; очистить r16 (результат = 0)
inc r16; приращение r16 (результат = 1)
dec r16; уменьшение r16 (результат = 0)
Некоторые арифметические инструкции позволяют работать с 16-битными значениями, т.е.е. слова . Инструкция adiw — добавить сразу к слову , позволяет вам добавить постоянное значение из диапазона 0 — 63 в регистровую пару r24: r25 или X (r26: r27), Y (r28: r29) или Z (r30: r31) указатели. Если указан только один регистр, компилятор автоматически заполнит следующий (т.е. r24 → r25: r24). Ниже приведены способы использования adiw
ldi r24,0x00; загрузить 0x1000 в
ldi r25,0x10; регистры r24: r25
adiw r24,0x0A; добавить 0x0A к r24: r25 (результат = 0x100A)
ldi XL, 0x80; загрузить 0x8080 в
ldi XH, 0x80; Указатель X
adiw X, 1; увеличить указатель X (результат = 0x80801)
sbiw — немедленно вычесть из слова используется аналогично
ldi YL, 0xAA; загрузить 0x55AA в
ldi YL, 0x55; Указатель Y
sbiw Y, 0x10; вычесть 0x10 из Y (результат = 0x559A)
adiw и sbiw можно использовать только в регистрах с r24 по r31.Кроме того, они будут работать только с парами регистров, для которых нижнее число является четным, то есть r25: r24 — допустимые операнды, но r26: r25 — это , а не .
Если вы используете микрокроллер ATmega или XMega, у вас есть возможность выполнить аппаратное умножение на (ATtiny не реализует эти инструкции).
Инструкция mul позволяет вычислить произведение без знака на любого из 32 рабочих регистров общего назначения.Поскольку произведение двух 8-битных чисел может достигать 16-битных значений, для результата требуются два регистра. Вывод mul всегда будет храниться в r1 и r0, независимо от операндов.
ldi r16,0x18; загрузить r16 с помощью 0x18 (22)
ldi r17,0x37; загрузить r17 с помощью 0x37 (55)
mul r16, r17; умножить r16 и r17 (r1: r0 = 0x0528 = 1210)
Инструкция muls вычисляет произведение двух 8-битных значений со знаком . И снова результат будет сохранен в r1 и r0. Однако muls работает только с регистрами с r16 по r31.Компонент со знаком должен быть в формате дополнения до двух.
ldi r16,0xF2; загрузить r16 с помощью 0xF2 (-14)
ldi r17,0x37; загрузить r17 с помощью 0xDF (-33)
mul r16, r17; умножить r16 и r17 (r1: r0 = 0x01CE = 462)
Если , один из ваших операндов подписан, а другой — без знака, тогда есть инструкция только для вас: mulsu. Эта инструкция будет рассматривать первый операнд как подписанный, а второй как беззнаковый. Недостатком является то, что mulsu еще более ограничивает регистры — он будет работать только с r16 по r23.
ldi r16,0xF2; загрузить r16 с помощью 0xF2 (-14)
ldi r17,0x37; загрузить r17 с помощью 0x83 (131)
mul r16, r17; умножить r16 и r17 (r1: r0 = 0xF8D6 = -1834)
Чтобы преобразовать значение в формат с дополнением до двух, просто добавьте 256, если оно меньше нуля. Например, для представления -14:
-14 + 256 = 242 (0xF2)
Помимо умножения целых чисел, AVR поддерживает ограниченное умножение дробных чисел.Это тема, которая сама по себе заслуживает целого учебника и, честно говоря, немного сложнее, чем необходимо для новичка. Не стесняйтесь пока пропустить эту часть, но ниже будет дан краткий обзор очень .
Дробные числа между [0, 2) могут быть представлены 8-битным числом в формате 1,7, где старший бит представляет собой целочисленный компонент 0 или 1, а остальные биты представляют собой дробный компонент, как показано на таблица ниже.
Бит 7 | Бит 6 | Бит 5 | Бит 4 | Бит 3 | Бит 2 | Бит 1 | Бит 0 |
---|---|---|---|---|---|---|---|
2 0 | 2 -1 | 2 -2 | 2 -3 | 2 -4 | 2 -5 | 2 -6 | 2 -7 |
1 | 0.5 | 0,25 | 0,125 | 0,0625 | 0,03125 | 0,015625 | 0,0078125 |
Например, двоичное число 01001100 при использовании этого формата будет равно 0 * 1 + 1 * 0,5 + 0 * 0,25 + 0 * 0,125 + 1 * 0,0625 + 1 * 0,03125 + 0 * 0,015625 + 0 * 0,0078125 = 0,59375.
Инструкция fmul вычисляет сумму двух 8-битных чисел в формате 1,7 и выводит результат в r1 и r0 в формате 1,15. В 1.15 первый бит старшего байта представляет собой целочисленный компонент, а остальные биты представляют собой дробные компоненты.fmul будет работать только с r16 по r23.
ldi r16,0xC0; загрузить r16 с помощью 0xC0 (1.5)
ldi r17,0xA0; загрузить r17 с помощью 0xA0 (1.25)
fmul r16, r17; умножить r16 и r17 (r1: r0 = 0xF000 = 1.875)
Как и в случае целочисленного умножения, есть знаковая версия fmuls, которая может умножать дробные числа между [-1,1)
ldi r16,0xA0; загрузить r16 с помощью 0xA0 (-0,75)
ldi r17,0xF0; загрузить r17 с помощью 0xF0 (-0,125)
fmuls r16, r17; умножаем r16 и r17 (r1: r0 = 0x0C00 = 0.4375)
И подписанная неподписанная версия fmulsu
ldi r16,0xC0; загрузить r16 с помощью 0xC0 (-0,5)
ldi r17,0x60; загрузить r17 с помощью 0x60 (0,75)
fmulsu r16, r17; умножить r16 и r17 (r1: r0 = 0xD000 = -0,375)
Чтобы преобразовать дробное значение в формат с дополнением до двух, просто добавьте 2, если оно меньше нуля. Например, для представления -0,75:
-0,75 + 2 = 1,25 (0xA0)
Архитектура AVR имеет ряд инструкций для сдвига битов в рабочем регистре общего назначения, показанных в таблице ниже.
Мнемоника | Описание |
---|---|
LSL | логический сдвиг влево |
LSR | логический сдвиг вправо |
рол | повернуть влево через перенос |
рор | повернуть вправо через перенос |
asr | арифметический сдвиг вправо |
Команды lsl и lsr выполняют логических сдвигов на бит.Нулевой бит будет автоматически сдвинут в регистр в пустое пространство, оставшееся после сдвига, а сдвинутый бит будет сохранен как бит переноса в регистре состояния (подробнее об этом позже). Например
ldi r16,0b10101010; загрузить 1010101010 в r16
lsl r16; сдвиг r16 влево (результат = 01010100)
lsr r16; сдвиг r16 вправо (результат = 00101010)
Команды rol и ror работают очень похоже на lsl и lsr, за исключением того, что вместо автоматического смещения нуля в пустое пространство, смещается единица или ноль, если перенос произошел в предыдущей операции.
ldi r16,0b10101010; загрузить 1010101010 в r16
ldi r17,0b10101010; загрузить 1010101010 в r17
lsl r16; сдвиг r16 влево (результат = 01010100)
rol r17; сдвинуть r17 влево и принести перенос (результат = 01010101)
В приведенном выше примере старший бит r16 равен единице, поэтому, когда он сдвигается влево, он сохраняется в регистре состояния. Когда r17 повернут на влево, бит из предыдущей операции сдвигается в младший значащий бит.
Аналогично для ror
ldi r16,0b10101010; загрузить 1010101010 в r16
ldi r17,0b10101010; загрузить 1010101010 в r17
lsr r17; сдвинуть r17 вправо (результат = 01010101)
ror r16; сдвинуть r16 вправо и принести перенос (результат = 01010101)
Нуль смещается из r17, поэтому, когда r16 повернут на вправо на , ноль смещается в его старший значащий бит.
Наконец, есть несколько инструкций для управления битами регистра.
Мнемоника | Описание |
---|---|
сбр | установить бит (ы) в регистре |
CBR | очистить бит (ы) в регистре |
сер | установить регистр |
clr | очистить регистр |
своп | заменить полубайты |
Инструкции sbr и cbr позволяют установить или очистить бит в регистре.sbr и cbr требуют постоянной маски в качестве операнда, определяющего, какие биты устанавливаются или очищаются.
ldi r16,0x00; загрузить 0x00 в r16
sbr r16,0x0F; установить младшие 4 бита r16 (результат = 0x0F)
cbr r16,0x0F; очистить младшие 4 бита r16 (результат = 0x00)
Вы можете заметить, что sbr выглядит идентично инструкции ori. На самом деле это так. Ваш ассемблер сгенерирует один и тот же машинный код независимо от того, какую инструкцию вы используете — sbr на самом деле просто псевдоним для инструкции ori.
cbr — это , почти идентично andi, за исключением того, что постоянная маска должна быть инвертирована для получения того же результата. Когда используется инструкция cbr, ассемблер автоматически отменяет предоставленную константу и генерирует машинный код для andi. Следующие пары инструкций произведут такой же вывод ассемблера.
ori r16,0x0F; это тоже самое
sbr r16,0x0F; как это
andi r16,0xF0; это тоже самое
cbr r16,0x0F; как это
Ассемблеры предоставляют инструкцию cbr для удобства, поэтому отрицательная битовая маска не обязательна, а sbr предоставляется как псевдоним для ori для поддержания согласованности.
Несмотря на то, что в имени нет слова немедленный , sbr и cbr работают только с регистрами с 16 по 31.
ser — устанавливает регистр и clr — очищает регистр , позволяет вам устанавливать или очищать все биты в регистре. Они вызываются с одним регистром от 16 до 31.
ser r16; установить все биты в r16 (результат = 0xFF)
clr r16; очистить все биты в r16 (результат = 0x00)
Как и раньше, вы можете заметить, что ser — это то же самое, что и использование ldi с 0xFF, а clr — то же самое, что выполнение eor с тем же регистром, что и источник и место назначения (на самом деле, есть несколько разных способов достижения любого из этих результатов) .Сборщик заменит
С машинным кодом для
ser и clr предоставляются ассемблером, так как они быстрее набираются, чем альтернативы.
ser и clr не являются фактическими инструкциями, реализованными в архитектуре, но предоставляются ассемблером как псевдонимы для других инструкций.
Наконец, команда обмена позволяет вам поменять местами младшие 4 бита на 4 старших бита регистра (также называемые полубайтами ).
ldi r16,0x0F; загрузить 0x0F в r16
своп r16; поменять местами полубайты r16 (результат = 0xF0)
Команда перестановки наиболее полезна при работе с двоично-десятичным кодированием (BCD), где двоичное значение двух чисел 0-9 хранится в верхнем и нижнем полубайтах 8-разрядного числа.
Поздравляю, если вы прошли весь путь. Это был лот , который нужно было сдать за один присест. Возможно, вам придется перечитать это несколько раз, чтобы он полностью усвоился, но вы, по крайней мере, теперь должны быть знакомы с типами операций, которые могут выполняться с рабочими регистрами общего назначения.
Вероятно, пора взглянуть на другой практический пример, так что теперь мы рассмотрим настоящий «Hello World» встроенных систем: мигание светодиода.
Здесь вы найдете весь код (и многое другое!) Для книги Maker Media. Марка: Программирование AVR.
Сначала загрузите содержимое этого репозитория на свой жесткий диск.Самый простой способ находится с кнопкой «Загрузить ZIP» вверху и справа в этой самой сети страница. Распакуйте zip-файл в удобное место. (Не стесняйтесь клонировать репо, если вы удобны с Git.)
Большинство проектов имеют общий набор определений выводов и общий простой Последовательная библиотека USART в каталоге AVR-Programming-Library . В Файлы makefiles, которые я включил, по умолчанию зависят от структуры каталогов, поэтому не перемещайте папки, если вы также не измените путь к включенные файлы в make-файл.
Если вы используете Arduino IDE, вам нужно скопировать каталог AVR-Programming-Library в папку альбом / библиотеки . Если вы не знаете, где это, вы можете узнать в Диалог «Файл … Настройки» в Arduino. Теперь вы можете просто связать свой код, чтобы использовать эту библиотеку. используя «Эскиз … Импортировать библиотеку» и выбирая библиотеку из меню.
Теперь вы можете открыть код, отредактировать его и прошить в AVR, следуя инструкциям. в книге.
Весь код проекта организован по главам в книге. Так что если вы ищете пример кода SPI, см. папку «Chapter16_SPI» для Проекты, связанные с SPI. Это очевидно.
Но несколько проектов интересны помимо темы, рассмотренной в глава. Например, «Chapter05_Serial-IO» включает проект, который использует последовательная связь между вашим настольным компьютером и AVR, чтобы включить ваш компьютерную клавиатуру в музыкальную клавиатуру, которая воспроизводит ноты, созданные на AVR, превратив его в орган, управляемый последовательным портом.Вы бы не подумали загляните в главу «Последовательный ввод / вывод», если вы не читали книгу.
Итак, для обзора всех проектов файл allProjectsList перечисляет их все по именам.
Если вам нужен пустой шаблон для написания собственного кода AVR, загляните в каталог setupProject , который я здесь включил. Внутри вы найдете main.c и main.h файлы, которые практически пусты и готовы к работе. main.c использует мою простую библиотеку USART, которая также включена в файл сборки Makefile . Короче говоря, вы можете скопировать этот каталог, переименовать файлы и начать использовать его в своих проектах.
Но делать это вручную не обязательно. Запуск python setupProject.py myProjectName создаст для вас каталог с именем myProjectName , скопируйте пустые основные файлы, переименовав их соответствующим образом, и настройте Makefile соответственно. Все, что вам осталось сделать, это самая сложная часть — на самом деле кодирование.
Если вы часто используете эту настройку, вам нужно настроить Makefile и два основных файла в соответствии с вашими предпочтениями. Таким образом, всякий раз, когда вы запустить новый проект, он будет включать в себя настроенный файл Makefile с вашим программатор, тип микросхемы и любимая скорость передачи уже установлены.
Наконец, если вы хотите отобразить определения контактов в определениях макросов, запустите python createPinDefines.py . Программа спросит вас, как вы хотите позвонить макрос каждого вывода (например,грамм. «LED0»), а затем какой вывод на AVR вы хотите связать с ним (например, «PB1»). Когда вы закончите вводить свой PIN-код, он создаст файл «pinDefines.h» с макросами (я надеюсь) с красивыми именами. Двигаться этот файл в правильный каталог и включите его в свой код. Звонок LED0_SET_HIGH включит ваш светодиод.
Вы прочитали книгу, вы создали проекты, вы проработали код. Но вы все равно жаждете новых проектов, примеров, еще, еще, еще! Если я могу потрубить себе в рог, вам следует посетить LittleHacks.org где я веду блог о любых проектах микроконтроллеров, над которыми я сейчас работаю.
В частности, если вы читаете Марка: Программирование AVR и вас интересуют полностью проработанные версии проектов с дополнительными фотографии, видео и объяснения, которые не поместятся в книге, перейдите к Раздел AVR-программирования LittleHacks.org.
После того, как вы исчерпали все эти ресурсы, вам обязательно нужно в финал ECE 4760 Корнельского университета Проекты страница списка.Это впечатляющая коллекция приложений, которая обязательно вызовет некоторые творческие мысли. Все это хорошо задокументировано, и есть масса источников код в C. [раздел ссылок профессора Лэнда] (http://people.ece.cornell.edu/land/courses/ece4760/#links) тоже на высшем уровне, и его лекции на YouTube тоже стоит посмотреть, если вы серьезно обо всей этой сделке с AVR.
Концептуально Программирование на языке ассемблера очень просто, вы обычно перемещаете байт данных в регистр, вы что-то делаете с ним, а затем записываете.Практически все языки конвертируют свои программы в ассемблер потому что сборка близка к тому, что понимает оборудование, поэтому между инструкции по сборке и двоичный код оборудования (машинный язык).
Сборкаможет показаться новичку сложной из-за первоначальной крутой кривой обучения, однако из-за ограниченное количество инструкций, раз уж нахмуриться, это очень просто. Прежде чем вы сможете начать, вам нужно хорошее понимание архитектуры машины, набора команд, синтаксиса ассемблера и базового понимания принципы программирования, такие как циклы и подпрограммы, прежде чем вы сможете создать свою первую программу.
Одно из преимуществ изучения Assembly заключается в том, что все они очень похожи, поэтому, как только вы выучите одну, остальные могут быть подбирается легко, поскольку большинство процессоров делают то же самое на аппаратном уровне, в основном перемещая байты.
Assembly позволяет создавать минимально возможный и быстрый код, однако для больших проектов это может быть долгим и утомительным занятием.
В простейшей форме AVR состоят из регистров, портов и RAM:
[РЕГИСТРЫ] [ПОРТЫ] [БАРАН]
Имеется 32 регистра, которые можно считать столь же быстрой оперативной памятью, поэтому большая часть работы выполняется именно там.Как я упоминал ранее, большая часть ассемблера перемещает данные в эти регистры и что-то с ними делает.
RAM — это место, где мы храним наши программы и данные.
Порты — это то, как мы общаемся с внешним миром, но они появляются в MCU как дополнительные регистры.
Регистры имеют размер один байт (восемь бит) и являются внутренними для MCU, поэтому работают быстро. Мы можем думать о них как о рабочем пространстве, где нужно успеть кое-что сделать, прежде чем их куда-то отправят.Поэтому, прежде чем мы сможем писать программы сборки для AVR, важно хорошо разбираться в регистрах.
В AVR есть 32 внутренних регистра, обычно обозначаемых как R0-R31. Чаще всего используются R16-R31, потому что их проще использовать. Их можно загрузить напрямую с помощью константы. Например, если вы хотите загрузить 100 в регистр R16 со 100, вы должны использовать:
LDI R16,100; LDI = LoaD Immediate 100 в регистр 16
LDI означает LoaD Immediate, R16 — регистр, а 100 — наша константа.Все, что находится после точки с запятой «;» комментарий, игнорируемый Ассемблером.
Если вы хотите переместить значение 100 в один из регистров R0-R15, вы НЕ МОЖЕТЕ сделать это:
LDI R1,100; это ОШИБКА !!!
Чтобы переместить 100 в один из регистров R0-R15, вы должны сделать что-то вроде:
LDI R16,100; Загрузить 100 в регистр 16 MOV R1, R16; переместить содержимое регистра 16 в регистр 1
Это сначала перемещает 100 в регистр R16, затем мы перемещаем его из R16, а затем в R0.Обратите внимание, что операнды читаются справа налево. MOV — от R16 до R1, а не наоборот, даже если может показаться, что это так.
Итак, мы видим, что регистры R16-R31 проще в использовании, потому что их загрузка составляет половину работы. Из них регистры R26-R31 используются в качестве двухбайтовых указателей более сложными командами, поэтому мы должны придерживаться десяти регистров R16-R25 в качестве основного рабочего пространства для начала.
Мы можем использовать команду .DEF для присвоения нашим регистрам значимых имен.
.DEF A = R16 .DEF B = R18 .DEF N = R20 .DEF STUDENT_SCORE = R25
Теперь мы можем загрузить в регистр R16 значение 100, используя команду:
ЛДИ А, 100
Константы — это значения, которые не изменяют значение во время работы программы. Они определяются во время сборки вашей программы на машинном языке (двоичный код). и не меняются при выполнении вашей программы.
Константам можно присвоить имя с расширением.Команда SET (или .EQU). В нашем последнем примере мы загрузили регистр R16 со значением 100. Вместо использования константы 100 мы могли бы дать ей имя, например PERFECT_SCORE, с помощью операторов:
.SET PERFECT_SCORE = 100 .EQU PERFECT_SCORE = 100
Позже в программе мы можем загрузить R16 со 100, используя команду:
LDI R16, ИДЕАЛЬНЫЙ_ОСЧЕТ
Константы можно представить разными способами. Их можно определить как шестнадцатеричные, восьмеричные, двоичные и т. Д.Все следующие определения PERFECT_SCORE равны 100:
.SET PERFECT_SCORE = 100; Десятичное представление .SET PERFECT_SCORE = (2000 + 500) / 25; 2500 разделить на 25 = 100 .SET PERFECT_SCORE = 0x0064; Шестнадцатеричное представление .EQU PERFECT_SCORE = $ 64; Шестнадцатеричное представление .EQU PERFECT_SCORE = 0b0110_0100; Двоичная запись .EQU PERFECT_SCORE = 0144; Восьмеричное представление .EQU PERFECT_SCORE = 'd'; Нотация ASCII
Как мы видели ранее, константа может быть загружена непосредственно в регистры от R16 до 31.Все следующее загрузит R16 с 100:
LDI 16 100 рупий LDI R16, PERFECT_SCORE LDI R16, (2000 + 500) / 25 LDI R16, 64 доллара США LDI R16,0b0110_0100 LDI R16, 'd' LDI A, PERFECT_SCORE; если вы определили A = R16
AVR включают в себя большое семейство микросхем. Чтобы помочь нам создать код для различных процессоров, ATMEL предоставляет файл для каждого из них, который содержит серию стандартных определений .DEF и .EQU. адаптированный к этому конкретному чипу. Например, вот небольшой клип от M69DEF.INC файл для Процессор ATmega169, который используется в AVR Butterflys, который определяет регистры R26-R31 как двухбайтовые указатели, называемые X, Y и Z:
; ***** ОПРЕДЕЛЕНИЯ РЕГИСТРА ЦП ************* .def XH = r27 .def XL = r26 .def YH = r29 .def YL = r28 .def ZH = r31 .def ZL = r30
Директива .INCLUDE предписывает ассемблеру читать файл как часть нашей программы. Например, в верхней части программы для Butterfly вы обычно увидите:
. ВКЛЮЧИТЬ "M169DEF.INC "; БАБОЧКИ DEFS
Или для программы для ATtiny13:
. ВКЛЮЧИТЬ "TN13DEF.INC"
Вы даже можете создать свои собственные библиотеки часто используемых подпрограмм или констант и включать их самостоятельно.
. ВКЛЮЧИТЬ «MYFILE.ASM»
Лучший способ изучить Ассемблер — это сделать Ассемблер, поэтому, если вы еще этого не сделали, мы просто загружаем программное обеспечение Studio 4 с сайта Atmel.com и устанавливаем его в Windows. В прошлый раз, когда я его загрузил, мне пришлось сначала зарегистрироваться, что является простым и быстрым процессом.Если вы используете другую операционную систему, я полагаю, вы уже выяснили, как установить ассемблер и программист. Мы запускаем кабель от COM-порта на вашем ПК к программатору или микросхеме, и вы готовы к работе.
Мы собираемся ввести программу сборки под названием BEEP для бабочки. Если вы не используете Butterfly, продолжайте читать, мы узнаем, как адаптировать программу для другие фишки достаточно скоро.
Если вы запускаете программу впервые, выберите «Atmel AVR Assembler.»Выберите« Ассемблер 2 » если спросят. Введите имя файла BEEP или что-то подобное, тогда вам будет представлен список фишек. для чего собирать, при использовании бабочки выберите ATmega169.
; ------------------------------------------------ -; ; BEEP.ASM для AVR BUTTERFLY; ; СДЕЛАЙТЕ ЭТО ИЗВЕСТНЫЙ ЗВУК! ; ; АВТОР: ДЭНИЕЛ Дж. ДОРИ [email protected]; ; СОЗДАНО: 01 МАРТА 06 ОБНОВЛЕНО: 01 МАРТА 06; ; ПРИМЕЧАНИЕ. В СХЕМАТИЧЕСКОЙ СХЕМЕ ДИНАМИК ОЧИЩЕН НА PB5; ; ------------------------------------------------- -; .ВКЛЮЧАЙТЕ "M169DEF.INC"; (ОПРЕДЕЛЕНИЯ БАБОЧКИ) ; -----------------------------------------; ; Сначала мы определим НЕКОТОРЫЙ РЕГИСТР ДЛЯ ИСПОЛЬЗОВАНИЯ; ; -----------------------------------------; .DEF A = R16; АККУМУЛЯТОР ОБЩЕГО НАЗНАЧЕНИЯ .DEF I = R21; ИНДЕКСЫ ДЛЯ КОНТРОЛЬНОГО УПРАВЛЕНИЯ .DEF J = R22 .ORG $ 0000 ; -----------------------------------------; ; Сначала мы настраиваем область стека, затем устанавливаем; ; НАПРАВЛЯЮЩАЯ БИТКА НА ПОРТ-B ДЛЯ ВЫХОДА / SPKR; ; -----------------------------------------; НАЧАЛО: LDI A, LOW (RAMEND); НАСТРОЙКА УКАЗАТЕЛЯ СТЕКА OUT SPL, A; ТАК ВЫЗЫВАЕТ ПОДПРОГРАММЫ LDI A, ВЫСОКИЙ (RAMEND); РАБОТАЙТЕ ПРАВИЛЬНО ВЫХОД SPH, A; LDI A, 0b1111_1111; УСТАНОВИТЬ ВСЕ ПОРТЫ ДЛЯ ВЫХОДА OUT DDRB, A; ЗАПИСАТЬ 1s В DIRECTN REGS ; --------------; ; ГЛАВНЫЙ РЕЖИМ; ; --------------; BEEP: CLR I BLUPE: SER A; ВКЛЮЧИТЬ SPKR ВЫХОДНОЙ ПОРТB, A RCALL PAUSE; ПОДОЖДИТЕ CLR A; ВЫКЛЮЧИТЬ ВЫХОДНОЙ ПОРТB, A RCALL PAUSE; ПОДОЖДИТЕ СНОВА ДЕКАБРЬ I BRNE BLUPE LOOP: RJMP LOOP; ОСТАВАЙТЕСЬ ЗДЕСЬ, КОГДА СДЕЛАНО ; ----------------; ; ПРИОСТАНОВИТЬ ПРОГРАММУ; ; ----------------; ПАУЗА: CLR J ПЛЮП: NOP ДЕКАБРЬ J BRNE PLUPE RET
Если наше программирование прошло успешно, ваша бабочка должна издать небольшой звуковой сигнал и остановиться.Если нет, обязательно проверьте, что программный модуль отправляет правильный файл, ассемблер-редактор не обновляет программный модуль при переключении между программами.
Теперь рассмотрим программу более подробно. К настоящему времени вы должны понять первую часть программы. Он начинается с некоторых комментариев, начинающихся с символа «;» который Ассемблер проигнорирует. Затем мы используем директиву .INCLUDE, чтобы ассемблер читал файл M169DEF.INC. который содержит определения для микросхемы ATmega169.
; ------------------------------------------------ -; ; BEEP.ASM для AVR BUTTERFLY; ; СДЕЛАЙТЕ ЭТО ИЗВЕСТНЫЙ ЗВУК! ; ; АВТОР: ДЭНИЕЛ Дж. ДОРИ [email protected]; ; СОЗДАНО: 01 МАРТА 06 ОБНОВЛЕНО: 01 МАРТА 06; ; ПРИМЕЧАНИЕ. В СХЕМАТИЧЕСКОЙ СХЕМЕ ДИНАМИК ОЧИЩЕН НА PB5; ; ------------------------------------------------- -; .ВКЛЮЧАЙТЕ "M169DEF.INC"; (ОПРЕДЕЛЕНИЯ БАБОЧКИ)
Если вы используете другой чип, например ATtiny13, вы должны использовать AT13DEF.INC. Если вы выполнили стандартную установку в Windows XP, эти файлы можно найти в C: \ Program Files \ Atmel \ AVR Tools \ AvrAssembler2 \ Appnotes, если вам нужно найти имя файла для используемого чипа.
Далее, если вы используете 3 вольта для питания вашего чипа, вам необходимо подключить небольшой динамик к порту B, контакт 5. Проверьте распиновку в верхней части паспорта вашего чипа. которую вы можете бесплатно скачать с сайта Atmel.com. Вы можете использовать маленький компьютерный динамик от старого компьютера и подключите порт B, контакт 5 к +3 В или к земле.(+3 Вольта должно быть чуть громче). Если вы используете 5 вольт для питания вашего чипа, установите небольшой резистор в диапазоне от 100 до 220 Ом. последовательно с динамиком для ограничения тока.
Если порт B, контакт 5 недоступен, например, на микросхемах ATtiny он используется как линия сброса. Затем подключитесь к другому контакту порта B, например, к нулевому контакту, и мы изменим программу позже.
Вернемся к нашей программе. Затем мы даем наши собственные имена (A, I, J) регистрам, которые мы будем использовать. Обратите внимание, что все они находятся в диапазоне от R16 до R25:
; -----------------------------------------; ; Сначала мы определим НЕКОТОРЫЙ РЕГИСТР ДЛЯ ИСПОЛЬЗОВАНИЯ; ; -----------------------------------------; .DEF A = R16; АККУМУЛЯТОР ОБЩЕГО НАЗНАЧЕНИЯ .DEF I = R21; ИНДЕКСЫ ДЛЯ КОНТРОЛЬНОГО УПРАВЛЕНИЯ .DEF J = R22
Прежде чем мы действительно сможем создать наши первые строки кода, мы должны сообщить Ассемблеру, куда положить в программную память. Это делается с помощью Директивы происхождения «.ORG» и, как правило, программ AVR. начинать с нижней части памяти в нулевом месте, потому что именно туда смотрит микросхема AVR при включении или сбросить.
.ORG $ 0000
$ 0000 — это просто еще одна константа, подобная тем, которые мы обсуждали ранее, поэтому мы могли бы использовать .ORG 0 или даже что-то сумасшедшее, например .ORG 100-100, но традиционно это делается с шестнадцатеричная запись $.
Чтобы выполнять переходы, циклы и подпрограммы, нам нужен способ указать Ассемблеру, куда идти. Для этого используются ярлыки. Они также используются, чтобы помочь нам понять, что делает код. Например, поскольку мы находимся в начале нашей программы, давайте обозначим ее «СТАРТ:».
Ярлыки обычно начинаются в крайнем левом углу экрана, а остальная часть программы имеет отступ.Этикетки состоят из цифр и букв, но должны начинаться с буквы, и когда мы их определяем, они заканчиваться двоеточием »:« Чтобы использовать метку, мы не включаем «:». Например, чтобы перейти к СТАРТ: мы должны написать код:
RJMP START
Прежде чем мы сможем вызывать какие-либо подпрограммы, нам нужно настроить структуру памяти, называемую стеком в памяти. Обычно наша программа находится внизу памяти, а стек — вверху. Обычно программисты устанавливают стек в начале своих программ.
Внутри включаемых файлов (в нашем случае M169DEF.INC) константа (RAMEND) определена в верхней части память для нас. Проблема в том, что если у нас будет более 256 байт ОЗУ, будет больше чем мы можем вместить в один байт. Выражения LOW () и HIGH () разбивают шестнадцатиразрядный число в два байта для нас. LOW (RAMEND) даст нам младший байт, а HIGH (RAMEND) даст нам старший байт. Обратите внимание, что эти два байта являются константами, и если мы хотим записать их в Указатель стека, мы должны сначала загрузить их в регистр в диапазоне R16-R32, как и любую другую константу.
Указатель стека — это специальная двухбайтовая ячейка памяти, которая всегда указывает на верхний элемент стека. Две ячейки памяти определены как SPL (младший байт) и SPH (старший байт). На AVR они лечатся как порт, поэтому команда MOV не будет работать, и мы должны использовать команду OUT. OUT обычно отправляет содержимое регистра в расположение порта.
НАЧАЛО: LDI A, LOW (RAMEND); НАСТРОЙКА УКАЗАТЕЛЯ СТЕКА OUT SPL, A; ТАК ВЫЗЫВАЕТ ПОДПРОГРАММЫ LDI A, ВЫСОКИЙ (RAMEND); РАБОТАЙТЕ ПРАВИЛЬНО ВЫХОД SPH, A;
Каждый порт ввода / вывода AVR имеет связанный регистр направления данных, который определяется в нашем включаемом файле (M169DEF.INC). Для порта B это DDRB, для порта C — DDRC и т. Д. Если мы установим бит в этом регистре на единицу, то этот вывод станет выходным выводом, с другой стороны. если мы напишем ноль, он станет и входным контактом.
Поскольку нам нужен порт B, контакт 5 должен быть выходным контактом, и мы не используем никаких других контактов на порту B, мы можем просто сделать их всеми выходными контактами, записав все по одному.
Мы снова перемещаем константу, на этот раз значение 0b1111_1111 в регистр R16. Поскольку регистры направления данных являются регистрами портов, мы не можем использовать команду MOV, мы должны снова использовать команду OUT:
LDI A, 0b1111_1111; УСТАНОВИТЬ ВСЕ ПОРТЫ ДЛЯ ВЫХОДА OUT DDRB, A; ЗАПИСАТЬ 1s В DIRECTN REGS
Теперь, когда все наши инициализации выполнены, мы переходим к основной части нашей программы.Поскольку наша программа издает звуковой сигнал на динамике, я назвал его BEEP, но мы могли бы пометил его MAIN или любое другое имя, которое вы хотите использовать.
Мы не хотим, чтобы наш AVR издавал непрерывный звуковой сигнал и сильно раздражал, поэтому мы собираемся настроить счетчик для ограничения продолжительности звукового сигнала. Мы собираемся использовать регистр R21, который у нас был ранее. дано имя «I», и мы собираемся начать его с нулевого значения. Обычно мы могли бы это сделать загрузив его напрямую с помощью команды LDI I, 0, но мы собираемся использовать новую команду CLR, которая будет установить все биты в регистре в ноль.
Преимущество команды CLR заключается в том, что ее можно использовать для ВСЕХ регистров от R0 до R31. Противоположностью команде CLR является SER (регистр SEt), который устанавливает все биты в регистре в единицу. Фактически, ранее мы использовали LDI A, 0b1111_1111, чтобы установить все биты в регистре A в единицу, но мы могли вместо этого использовали SER A.
; --------------; ; ГЛАВНЫЙ РЕЖИМ; ; --------------; BEEP: CLR I
Чтобы создать звук, исходящий из динамика, нужно сначала активировать его, а затем деактивировать. это путем отправки серии единиц и нулей, например 10101010101… и т. д. Это заставит динамик входите и выходите, создавая таким образом тон. Чтобы сделать это эффективно, мы будем использовать цикл, а цикл будет требуется метка, поэтому мы называем эту часть кода BLUPE (Beep Loop):
BLUPE:
Чтобы активировать динамик, нам нужно отправить единицу на PortB, контакт 5. Это перебор, но поскольку мы не используют какие-либо другие контакты на порту B, мы можем использовать команду SER A, чтобы установить ВСЕ биты на затем отправить их в порт B.
SER A; ВКЛЮЧИТЬ SPKR ВЫХОДНОЙ ПОРТB, A
Так как процессор работает очень быстро, в Butterfly это 2 МГц и по умолчанию включена многие другие чипы имеют частоту 1 МГц, если мы не замедлим звук, излучаемый нашим динамиком будет слишком высоко, чтобы услышать.Итак, мы используем подпрограмму, которую мы называем PAUSE, к которой мы переходим с команда RCALL, которая просто тратит на нас время.
Команда RCALL сохраняет наше место в стеке, затем переходит к подпрограмме и продолжает для выполнения расположенных там команд. Противоположностью команде RCALL является команда RET (RETurn), который извлекает из стека наше предыдущее местоположение, чтобы программа могла вернуться туда, откуда оно пришло. Таким образом, каждая подпрограмма должна заканчиваться командой RET. Мы делаем отступ для команды RCALL PAUSE, чтобы напомнить нам, что программа переходит в другое место.
RCALL PAUSE; ПОДОЖДИТЕ
Раньше мы активировали динамик, записывая все в порт B, теперь мы хотим сделать наоборот поэтому мы используем команду CLR, чтобы установить все биты в регистре A в ноль, а затем отправить их в порт B.
CLR A; ВЫКЛЮЧИТЕ ВЫХОДНОЙ ПОРТB, A RCALL PAUSE; ПОДОЖДИТЕ СНОВА
Ранее мы настроили регистр I в качестве счетчика, чтобы ограничить время, в течение которого динамик будет издавать звуковой сигнал.Для этого мы вычтем единицу из нашего счетчика и остановимся после активации и деактивации. динамик 256 раз.
Чтобы вычесть единицу из регистра I, мы могли бы сначала LDI A, 1, затем SUB I, A, который вычитает значение в A от значения в I. Точно так же мы могли бы также ДОБАВИТЬ I, A, если бы мы хотели добавить содержимое A к I.
Лучшим способом вычесть единицу из регистра I является команда SUBtract Immediate SUBI I, 1, которая позволяет нам, чтобы вычесть константу из Регистра.К сожалению, нет команды немедленного добавления.
Еще лучший способ вычесть единицу из I — использовать команду DECrement DEC I. Добавление и вычитание единицы из регистров настолько распространено, что существуют отдельные команды, которые это делают. DECrement (DEC I) вычтет единицу из I, а INCrement (INC I) добавит единицу в регистр I.
DEC I
Обычно, когда мы хотим перейти к местоположению и не обязательно возвращаться, мы используем команду RJMP, но если мы хотим прыгнуть только на основании определенных критериев, это обычно называется ВЕТВЬЮ, как ветка дерева.
Есть много инструкций веток, если вы еще не сделали этого, вы должны проверить всю Набор инструкций можно найти в технических данных используемого вами чипа, где вы найдете полный список.
Мы собираемся уменьшать регистр I каждый раз в основном цикле и останавливаться, когда он достигнет нуля. Для этого мы собираемся использовать инструкцию BRanch if Not Equal to zero (BRNE). Так что если я зарегистрируюсь еще не достиг нуля, затем мы возвращаемся к началу нашего основного цикла (BLUPE).Когда регистр I достигает нуля, программа НЕ разветвляется, а переходит к следующей инструкции. и выйдите из цикла.
BRNE BLUPE
Если мы хотим безоговорочно перейти к другой части программы, мы обычно используем Relative JuMP. команда (RJMP). Инструкция RJMP быстро перенесет нас в другую часть программы, но расстояние, на которое мы можем пройти, ограничено. Если мы столкнемся с ошибкой, потому что метка, которую мы хотим перепрыгнуть to слишком далеко, то мы можем использовать более медленную команду JMP без каких-либо ограничений по расстоянию.
Некоторые из вас могут спросить, как получается, что мы начинаем с нуля, а затем каждый раз вычитаем единицу через цикл и по-прежнему останавливаться на нуле? Ответ таков: когда мы вычитаем единицу из нуля, восьмибитный register «перевернется» на 255, затем мы продолжаем вычитать единицу из 255, пока снова не дойдем до нуля.
После того, как мы издаем звуковой сигнал, нам больше не понадобится процессор, поэтому мы отправляем его в бесконечный цикл, заставляя его перескакивать на себя:
LOOP: RJMP LOOP; ОСТАВАЙТЕСЬ ЗДЕСЬ, КОГДА СДЕЛАНО
Чтобы замедлить нашу программу, чтобы мы могли слышать звуковой сигнал, мы используем подпрограмму, которая ничего не делает. но выполните цикл 255 раз между активацией динамика и его деактивацией.Мы создаем этот цикл, используя другой регистр, который мы определили как J, и уменьшая его. пока он не достигнет нуля, как мы это делали в нашем основном цикле, с помощью инструкции BRanch if Not Equal to zero (BRNE).
Чтобы еще больше замедлить нашу программу, мы можем вставить команду, которая ничего не делает, кроме как ждать один тактовый цикл называется «Нет операции» (NOP). На самом деле мы можем изменить частоту нашего тона добавив еще больше NOP.
; ----------------; ; ПРИОСТАНОВИТЬ ПРОГРАММУ; ; ----------------; ПАУЗА: CLR J ПЛЮП: NOP ДЕКАБРЬ J BRNE PLUPE RET
Как упоминалось ранее, все подпрограммы должны заканчиваться инструкцией RETurn (RET).
Работа «И» может быть продемонстрирована на следующей схеме из двух последовательно соединенных переключателей и лампочки:
Switch_1 Светодиод Switch_2 ---- / --------- / -------- D
Ясно видно, что светодиод будет гореть только тогда, когда оба переключателя замкнуты, чтобы произвести c полная схема. Переключите один И второй переключатель, оба должны быть замкнуты, прежде чем светодиод заработает. Этот результат может быть отображен в таблице истинности, где ноль означает выключение, а единица — включение:
SW1 SW2 Светодиод 0 0 = 0 0 1 = 0 1 0 = 0 1 1 = 1
Операция «И» может использоваться для сброса бита в ноль.Из приведенной выше таблицы истинности вы можете видеть, что все, что объединено операцией AND с нулем, равно нулю. Допустим, вы хотели очистить высокий бит реестра, следующий код будет именно так:
ЛДИ А, 0b1111_1111; А = 11111111 ANDI A, 0b0111_1111; A = 01111111
Операции «И» также могут использоваться с «битовой маской» для удаления интересующих нас битов. Например, если нас интересуют только четыре старших бита байта. Мы можем использовать двоичное число 0b1111_0000, чтобы убрать старший младший бит этого регистра и игнорировать остаток:
ЛДИ А, 0b1010_1111; А = 1111_1111 ANDI A, 0b1111_0000; A = 1010_0000
Работа «ИЛИ» может быть продемонстрирована следующей схемой с двумя переключателями, включенными параллельно. подключен к свету:
Switch_1 ------------- / --------- + | ВЕЛ + -------- D Switch_2 | ------------ / ---------- +
Ясно видно, что светодиод загорается, когда один «ИЛИ» другой переключатель замкнут, и даже если оба закрыты.Это можно представить в виде таблицы истинности:
SW1 SW2 Светодиод 0 0 = 0 0 1 = 1 1 0 = 1 1 1 = 1
Операция «ИЛИ» может использоваться для установки бита в единицу. Из приведенной выше таблицы истинности вы можете видеть, что все, что связано с единицей ИЛИ, является единицей. Допустим, вам нужно установить высокий бит реестра, это сделает следующий код:
ЛДИ А, 0b0101_0101; А = 0101_0101 ORI A, 0b1000_0000; A = 1101_0101
Операция «EOR» такая же, как операция «ИЛИ», за исключением того, что она выключена, когда оба переключателя закрыты.Это означает, что светодиод горит, если горит одно «ИЛИ», но не горит оба. Это можно продемонстрировать с помощью следующей таблицы истинности:
SW1 SW2 Светодиод 0 0 = 0 0 1 = 1 1 0 = 1 1 1 = 0
Если вы посмотрите на приведенную выше таблицу истинности, вы увидите, что единичный EOR с нулем дает единицу, а единичный EOR, совмещенный с единицей, дает нам ноль. Использование EOR с одним дает нам противоположное или обратное. Это дает нам свойство немного переворачиваться. Если вам нужно «мигать» старшим битом регистра, следующий код сделает это, не нарушая другие биты регистра «A»:
ЛДИ В, 0b1000_0000 LDI A, 0b0101_0101; A = 0101_0101 EOR A, B; A = 1101_0101 EOR A, B; A = 0101_0101 EOR A, B; A = 1101_0101
Операция «НЕ» или обратная операция означает, что вы хотите обратного, единицы становятся нулем, а нули становятся единицей.Таблица истинности для этого:
A NOT_A 0 1 1 0
Если мы вспомним команду EOR, мы поймем, что когда мы EOR что-то с помощью единицы, мы переворачиваем этот бит. Итак, чтобы получить обратное или НЕ целое значение, мы можем выполнить его EOR со всеми единицами:
LDI B, 0b1111_1111; ВСЕ LDI A, 0b1010_1010; A = 1010_1010 EOR A, B; A = 0101_0101
Ранее в основном цикле нашей программы, чтобы заставить динамик генерировать тональный сигнал, мы написали единичный к порту динамика, подождал короткое время, затем записал ноль, подождал короткое время, затем повторил цикл 256 раз.
Еще один способ добиться аналогичного результата — это прочитать значение на динамике, и если это единица, инвертируйте его в ноль, и если это ноль, инвертируйте его в единицу. Здесь можно использовать инструкцию Exclusive OR (EOR), потому что теперь мы знаем, что все EOR с единицами даст нам обратное или противоположное.
В нашей новой версии основной части нашей программы мы можем использовать регистр J для хранения значения всех и используйте его, чтобы инвертировать то, что находится на Порте B:
; --------------; ; ГЛАВНЫЙ РЕЖИМ; ; --------------; BEEP: CLR I BLUPE: LDI J, 0b1111_1111; ЗАГРУЗИТЬ БИТМАСКУ В А, ПОРТB; ЧИТАТЬ В ПОРТУ B EOR A, J; ИНВЕРТИРОВАТЬ / ПЕРЕКЛЮЧИТЬ ВЫХОДНОЙ ПОРТB, A; ЗАПИСАТЬ В ПОРТ B RCALL PAUSE; ПОДОЖДИТЕ ДЕКАБРЬ I BRNE BLUPE LOOP: RJMP LOOP; ОСТАВАЙТЕСЬ ЗДЕСЬ, КОГДА СДЕЛАНО
Мы могли бы даже улучшить нашу программу, только инвертировав вывод, на котором находится динамик.Возможно, мы могли бы захотеть использовать другие контакты порта B для чего-то другого, кроме создания тона.
На Butterfly динамик подключен к контакту 5 порта B, поэтому мы можем загрузить регистр J. взамен со значением 0b0001_0000.
LDI J, 0b0001_0000; ЗАГРУЗИТЬ БИТМАСКУ В А, ПОРТB; ЧИТАТЬ В ПОРТУ B
Инструкции Set Bit in I / O Port (SBI) и Clear Bit in I / O Port (CBI) могут использоваться для установки или сбросить биты в порте ввода-вывода, который отправит единицу или ноль на соответствующий вывод.Например, мы могли бы использовать их в основном цикле нашей программы BEEP для активации динамика:
; --------------; ; ГЛАВНЫЙ РЕЖИМ; ; --------------; BEEP: CLR I BLUPE: SBI PORTB, 5; АКТИВИРОВАТЬ ДИНАМИК RCALL PAUSE; ПОДОЖДИТЕ CBI PORTB, 5; ВЫКЛЮЧИТЬ ДИНАМИК RCALL PAUSE; ПОДОЖДИТЕ СНОВА ДЕКАБРЬ I BRNE BLUPE LOOP: RJMP LOOP; ОСТАВАЙТЕСЬ ЗДЕСЬ, КОГДА СДЕЛАНО
Таблица данных сообщает нам, что мы можем переключить выходной контакт, записав единицу на входной контакт (PINx).Каждый выходной порт имеет связанный входной регистр, для PORTB это будет PINB. Мы можем упростить переключение динамика:
; --------------; ; ГЛАВНЫЙ РЕЖИМ; ; --------------; BEEP: CLR I BLUPE: SBI PINB, 5; ПЕРЕКЛЮЧЕНИЕ ДИНАМИКА RCALL PAUSE; ПОДОЖДИТЕ ДЕКАБРЬ I BRNE BLUPE LOOP: RJMP LOOP; ОСТАВАЙТЕСЬ ЗДЕСЬ, КОГДА СДЕЛАНО
Стек — это структура памяти, похожая на стопку пластин. Вы можете размещать только новые тарелки наверху стопки.Например, если в стеке было три элемента, и вы добавили четвертый:
[TOP] [TOP] [3] [4] <- НОВИНКА [2] ==> [3] [1] [2] [СТЕК] [1] [КУЧА]
Теперь, если мы удалим [4] из вершины стека, тогда [3] снова станет вершиной стека. Затем, если мы удалим [3], то [2] окажется наверху:
[TOP] [TOP] [TOP] [4] [3] [2] [3] ==> [2] ==> [1] [2] [1] [СТЕК] [1] [СТЕК] [КУЧА]
Инструкция PUSH копирует регистр в верхнюю часть стека, а POP удаляет значение из вверху стека и копирует его в регистр.
Обычно используется для сохранения значения регистра. Например, если бы мы хотели, чтобы наша программа BEEP использовать только регистр A как в основном цикле нашей программы, так и в подпрограмме PAUSE. Мы могли бы поместить A в стек перед вызовом подпрограммы PAUSE, а затем вытащить ее из стека после того, как вернемся:
; --------------; ; ГЛАВНЫЙ РЕЖИМ; ; --------------; BEEP: CLR A; ИСПОЛЬЗОВАТЬ КАК СЧЕТЧИК BLUPE: SBI PINB, 5; ПЕРЕКЛЮЧЕНИЕ ДИНАМИКА НАЖАТЬ A; СОХРАНИТЬ СОДЕРЖАНИЕ A RCALL PAUSE; ПОДОЖДИТЕ POP A; ВОССТАНОВИТЬ A ДЕКАБРЬ А BRNE BLUPE LOOP: RJMP LOOP; ОСТАВАЙТЕСЬ ЗДЕСЬ, КОГДА СДЕЛАНО ПАУЗА: CLR A ПЛЮП: НЕТ ДЕКАБРЬ А BRNE PLUPE RET
Еще лучший способ сделать это — поместить инструкции PUSH & POP в подпрограмму PAUSE.Первое, что мы делаем, это сохраняем A в стеке до того, как его значение изменится, а затем просто восстанавливаем его. прежде чем мы вернемся. Это делает процедуру PAUSE многоразовой и переносимой, поскольку она сохраняет и восстанавливает значение регистра, которое он использует:
ПАУЗА: НАЖАТЬ А; СОХРАНИТЬ СОДЕРЖАНИЕ А CLR A ПЛЮП: НЕТ ДЕКАБРЬ А BRNE PLUPE POP A; ВОССТАНОВИТЬ A RET
Теперь, когда у нас есть базовое понимание языка ассемблера AVR, вам будет проще следуйте более продвинутым руководствам.Еще один отличный способ научиться — смотреть на другой рабочий код и наблюдать методы, которые они используют. Я рекомендую вам взять рабочий код и поэкспериментировать с ним, потому что лучший способ изучить Ассемблер — это писать код на Ассемблере, учиться на практике.
Какой машинный код соответствует инструкции языка ассемблера AVR и r31, rO Введите свой ответ в виде 16-битного двоичного числа. Не должно быть пробелов или знаков препинания — просто введите шестнадцать единиц или нулей.) Какой машинный код соответствует инструкции языка ассемблера AVR и r31, rO Введите свой ответ в виде 16-битного двоичного числа. Не должно быть пробелов или знаков препинания — просто введите шестнадцать единиц или нулей.)
Пожалуйста, помогите, показывая шаги. Вопрос 4. (продолжение) (b) Рассмотрим 16-битное двоичное число, хранящееся в регистрах AVR r15: r14, которое программист считает значением с дополнением до двух. (r15 содержит старший байт, r14 — наименее значимый.) Запишите последовательность инструкций языка ассемблера AVR, которые выполняют каждую из следующих операций. Результат должен оказаться в r15: r14.При необходимости можно свободно использовать другие регистры i) Устанавливает r15: r14 на постоянное значение -1 (2 отметки) …
Разберите следующую инструкцию (замените машинный код на ассемблер): 0x22b8f5cb Номера регистров указаны в таблице ниже. Коды операций и коды функций можно найти в Интернете. Номер регистра $ t0 $ ti $ t2 $ t3 $ t4 $ t5 $ t6 $ t7 $ 50 $ 51 $ s2 $ s3 $ 54 $ 55 $ 56 $ s7 $ t8 $ t9 Правила: • Все ответы должны быть отформатированы как допустимая сборка MIPS.• Немедленное следует записывать в десятичном формате. • Имена регистров должны начинаться с символа $. На регистры могут ссылаться …
Вопрос 2 Язык ассемблера ARM (25 баллов) Краткое изложение набора команд ARM приведено в конце этого документа. (5 баллов) Объясните разницу между инструкциями eor и eors. Используйте пример, чтобы показать, почему обе формы полезны. а. б. (5 баллов) На примере объясните, что делает инструкция «Idr r3, [r7, # 4]».c. (10 баллов) Ниже приводится язык ассемблера, созданный с помощью типа компиляции mystery,% function mystery: args 0, pretend = 0, frame = …
Напишите код подпрограммы в ASSEMBLY ЯЗЫК (формат Мано), чтобы разделить два положительных числа на метод повторного вычитания. Например, чтобы разделите 100 на 8, вычтя 8 из 100, пока не появится напоминание. меньше 8, затем подсчитайте время вычитания, чтобы получить результат.Первое число, второе число, а затем результат адреса должны соответствовать телефонному коду. ПОЖАЛУЙСТА, НАПИШИТЕ ОТВЕТ НА ЯЗЫКЕ СБОРКИ ТОЛЬКО.
1. Напишите программу на языке ассемблера, используя набор инструкций MIPS, который считывает два целых числа от пользователя, названного начальным и конечным числом, и определяет все простые числа между началом и концом (включая начало и конец).Ваша программа должна выполнить проверку обоих чисел следующим образом: i. начальный номер должен быть меньше или равен конечному номеру. II. Оба числа должны быть положительными. iii. Максимальное значение для конечного числа — 10000 …
Ch04.2. [3 балла] Рассмотрим следующий код языка ассемблера: I0: ADD R4 R1RO I1: SUB R9R3 R4; I2: ДОБАВИТЬ R4 — R5 + R6 I3: LDW R2MEMIR3100]; 14: LDW R2 = MEM [R2 + 0]; 15: STW MEM [R4 + 100] = R3; I6: И R2R2 & R1; 17: BEQ R9R1, цель; I8: AND R9 R9 & R1 Рассмотрим конвейер с пересылкой, обнаружением опасностей и 1 слотом задержки для ответвлений.Конвейер представляет собой типичный 5-ступенчатый IF, ID, EX, MEM, WB MIPS …
Инструкция на языке ассемблера Intel x86 должна быть использовал. 3. Что делает этот псевдокод? И преобразовать его в сборку. prime (1): # 2: prime [2]: # 3: {первое простое число} {второе простое число) кандидат-5 (первый кандидат на новое простое число) в то время как primeCount <100 цикл ndex: 1 while (index < primeCount) и (prime [index] не делят кандидата равномерно) добавляют 1 к концу индекса, тогда как if (index> primeCount) then (нет существующего простого числа равномерно делит кандидата, поэтому это новое простое число) добавить 1 к primeCount ;…
Мне нужен только язык программы ADA этого задания, если кто можно было бы ввести его в компилятор, и это было бы здорово. Только Версия ADA, а не Python или Visual Basic Вычисление двоичных данных В этом задании вам предлагается решить задачу, используя 3 разных языка программирования: Python (33,33%), Ada (33,33%) и VB (33,33%). Вам необходимо найти компилятор для каждого из упомянутых языков и протестировать их на своих машинах. Проблема, заданная…
Пожалуйста, ответьте на все вопросы! Спасибо. Каким будет соответствующий ассемблерный код MIPS для следующего оператора C? Предположим, что переменные a, b, c и d заданы и были объявлены как 32-битные целые числа a — b — (c + 7) + d; 1. 2. Покажите, как значение 0xB47CA034 будет расположено в машине с прямым порядком байтов и прямым порядком байтов. Предположим, что данные сохраняются, начиная с адреса 0 3.Преобразуйте следующие числа с основанием 16 в основание 2 a ….
Итак, у вас есть этот чип и его программируемый. Но как разместить свою программу на этом чипе?
Комментариев? Предложения? Напишите на форум!
Когда код компилируется в программу Windows, он превращается в «двоичный», что означает, что он не читается человеком (что трудно понять машинам), вместо этого он машиночитаемый.В Windows эти программы (часто называемые приложениями или исполняемыми файлами ) часто называются, оканчиваясь на .exe (например, notepad.exe или winword.exe ), на компьютерах Mac они часто называются, оканчиваясь на .App (хотя Finder это скрывает).
Для микроконтроллеров двоичные файлы заканчиваются на .hex (сокращение от Intel Hex Format ). Возможны и другие форматы, но это в значительной степени стандартно.
Ваш компилятор сгенерирует файл .hex из кода, а затем все, что вам нужно сделать, это передать эту программу .hex на чип!
Как обсуждалось в разделе «Что это такое?», Микросхема AVR имеет небольшой объем флэш-памяти. В этой памяти хранится программа. Когда чип запускается (вы даете ему питание), он запускает любую программу во флеш-памяти. Итак, все, что нам нужно сделать, это выяснить, как записать на флэш-память
.Хороший способ подумать о вспышке — это изучить ее так, как вы, вероятно, знакомы с ней.Флэш-память также используется в картах MMC / SD, которые обычно используются в MP3-плеерах или цифровых камерах. Если вы посмотрите на нижнюю часть карточки, вы увидите маленькие золотые пальчики. Это контактные площадки.
Задняя сторона мультимедийной карты. Анджей Барабаш.
Если вы посмотрите в спецификации карты MMC, вы обнаружите, что это то, для чего предназначены 7 контактов. (SD / высокоскоростные карты могут иметь дополнительные прокладки, но этот стандарт был добавлен позже, поскольку существующий стандарт считался слишком медленным для больших мегапиксельных камер)
Штифт № | Имя контакта | Функция контактов |
---|---|---|
1 | #CS | Выбор микросхемы (активировать) |
2 | SI | серийный номер |
3 | ЗЕМЛЯ | Земля |
4 | VCC | Мощность |
5 | SCK | Часы данных |
6 | Не подключен | Не подключен |
7 | SO | Последовательный выход |
Контакты питания и заземления используются для подключения питания к микросхеме.Вывод Chip Select используется, чтобы сообщить микросхеме, что он должен проснуться, вывод Serial In pin используется для отправки данных на карту, Serial Out — как данные отправляются с карты, а Data Clock — как мы сообщаем карте, насколько быстро мы хотим, чтобы данные были сохранены или получены. Данные выталкиваются из чипа по одному биту за раз, поэтому, если тактовая частота составляет 100 кГц, это означает, что в секунду передается 100 000 бит.
Если вы хотите восстановить или сохранить данные на флэш-карте, все, что вам нужно сделать, это вставить карту в устройство чтения флэш-карт.Затем у ридера есть драйвер на вашем компьютере, который позволяет вам просто перетаскивать файлы на изображение на экране, и он выполняет всю тяжелую работу по отправке данных на карту.
Ладно, при чем тут программист? Что ж, микроконтроллер — это как флеш-карта, а картридер — как программист. Когда вы вставляете чип в программатор (или, что более вероятно, кабель программатора в печатную плату, которая содержит чипы), это позволяет программному обеспечению на вашем компьютере взаимодействовать с чипом через программатор.Обычно вы можете просто дать простую команду, например «отправить эту программу», и программист сделает всю тяжелую работу по программированию и проверке данных.
Каждый AVR имеет набор контактов, которые являются контактами программирования, вам просто нужно подключить программатор к этим контактам в правильном порядке, и вы готовы его программировать. К сожалению, почти каждый чип имеет разную распиновку, поэтому обязательно посмотрите в таблице данные для правильных контактов.
Например, я большой поклонник микросхемы ATtiny2313, поэтому давайте откроем «сводную» таблицу (всего лишь дюжина страниц вместо стандартных 250.На первой странице есть всякая информация о том, что находится в чипе.
Quick Quiz: Сколько EEPROM имеет чип? Как насчет флеш-памяти? БАРАН?
На второй странице распиновка. Мы будем иметь дело с пакетом PDIP (пластиковый двухрядный пакет), который выглядит следующим образом:
Распиновка воспроизводится здесь
Микросхема имеет 20 контактов. Посмотрите внимательно, и вы заметите, что 6 из этих контактов эквивалентны контактам карты SD / MMC.Есть вывод питания ( VCC ) и вывод заземления ( GND ), также есть вывод SCK (почему-то он называется UCSK ) вывод Serial Out ( MISO — Master In Serial Out ) и Serial In pin ( MOSI — Master Out Serial In) . Единственное, чего у него нет, так это вывода / ChipSelect. Вместо этого он имеет вывод / RESET , который действует аналогичным образом. Когда вывод / RESET находится под положительным напряжением (такое же напряжение, как VCC ), микросхема запускает программу, когда вывод / RESET находится на земле (такое же напряжение, как GND ), тогда микросхема останавливается. и слушает инструкции по программированию на выводах программирования.
У всех SD-карт одинаковые пальцы в одном месте, поэтому, когда вы вставляете карту в кардридер, подключаются правильные контактные площадки. Программирование AVR работает аналогичным образом. Вместо контактных площадок микросхему часто помещают на печатную плату, которая имеет контакты заголовка, в которые вставляется программатор, распиновка заголовка стандартизирована, так что любой программист может использоваться после правильного подключения заголовка.
Существует два стандарта для внутрисистемного программирования AVR:
Слева — стандартный 6-контактный разъем, справа — стандартный 10-контактный разъем, заголовки выглядят следующим образом:
Вот пример фотографии, показывающей, как это выглядит, когда микросхема подключена с 6-контактным разъемом
Фото любезно предоставлено EvilMadScientistLabs
Черная метка на печатной плате и заголовок обозначают контакт №1.Вы можете проследить провода, чтобы сопоставить контакты микросхемы с контактами заголовка и убедиться, что эта макетная плата правильная.
Упражнение: проверьте распиновку для этой целевой платы
Чтобы узнать, как сделать «целевую плату», которая позволит вам программировать AVR (в частности, как сказал ATtiny2313), посетите учебник EvilMadScientistLab. Это довольно просто и будет покрывать часть того же материала.
Теперь, когда вы знаете, как работает программирование, пришло время выбрать программатор weapon .
В этом посте я описываю, как я создал рабочую программу для AVR непосредственно в шестнадцатеричном формате без использования компилятора C или ассемблера. Непосредственно это не полезно, но помогает понять, как представлены инструкции по сборке. и переведен в двоичный.
Это простой пример программы, которая устанавливает PB5 (светодиод на Arduino Pro Mini) на вывод и переключает его в бесконечном цикле.
ldi r16, 0x20
out DDRB, r16 // установить PB5 на вывод
петля:
out PINB, r16 // переключить PB5
ldi r17, 0xff
ждать:
декабрь r17
брне, подожди
rjmp петля
Кодировка инструкций определена в Руководстве по набору инструкций AVR. и адреса регистров, которые вы найдете в сводке регистров в таблице данных AVR.
В качестве примера давайте посмотрим на строку из DDRB, r16
. Инструкция определяется следующим образом: Просто возьмите 16-битный код операции 1011 1AAr rrrr AAAA
и вставьте r = 0x10
(r16) и A = 0x04
(DDRB), что дает 1011 1001 0000 0100
или 0xb904
в шестнадцатеричном формате.
Вся программа переведена в двоичный код:
asm bin шестигранник
ldi r16, 0x20 1110 0010 0000 0000 e200
выход 0x04, r16 1011 1001 0000 0100 b904
выход 0x03, r16 1011 1001 0000 0011 b903
ldi r17, 0xff 1110 1111 0001 1111 ef1f
дек r17 1001 0101 0001 1010 951a
brne -2 1111 0011 1111 0001 f7f1
rjmp -5 1100 1111 1111 1011 cffb
Для прошивки с avrdude программа должна быть в intel HEX формат.