Как написать программу на ассемблере для AVR микроконтроллера, чтобы реализовать мигание светодиода. Какие инструкции ассемблера используются для управления портами ввода-вывода AVR. Как настроить среду разработки для программирования AVR на ассемблере.
Основы программирования AVR микроконтроллеров на ассемблере
Программирование микроконтроллеров AVR на языке ассемблера позволяет создавать эффективный и компактный код для управления периферией. Рассмотрим базовые принципы на примере простой программы мигания светодиодом.
Структура программы на ассемблере для AVR
Типичная структура программы на ассемблере для AVR включает следующие элементы:
- Директивы препроцессора (.include, .def и т.д.)
- Объявление констант и переменных
- Вектор прерываний
- Основной код программы
- Подпрограммы
Ключевые инструкции для работы с портами ввода-вывода
Для управления светодиодом нам понадобятся следующие инструкции:
- SBI — установка бита в регистре ввода-вывода
- CBI
- LDI — загрузка константы в регистр
- OUT — запись в регистр ввода-вывода
Схема подключения светодиода к AVR микроконтроллеру
Для реализации мигающего светодиода потребуется следующая схема:
- Светодиод подключается анодом к выводу порта B микроконтроллера (например, PB0)
- Катод светодиода подключается через резистор 220-470 Ом к общему проводу (GND)
- Питание микроконтроллера подается на выводы VCC и GND
Пример кода на ассемблере для мигания светодиодом
Рассмотрим простой пример программы для мигания светодиодом на AVR ассемблере:
«`assembly .include «m328pdef.inc» .def temp = r16 .cseg .org 0x0000 rjmp start start: ldi temp, (1<- Директива
.include
подключает файл с определениями для конкретного МК - Команда
ldi
загружает константу в регистр для настройки порта на выход - Инструкции
sbi
иcbi
включают и выключают светодиод - Подпрограмма
delay
реализует задержку
Настройка среды разработки для программирования AVR на ассемблере
Для программирования AVR микроконтроллеров на ассемблере потребуется следующее ПО:
- Atmel Studio или AVR Studio — интегрированная среда разработки
- AVRA или AVRASM2 — ассемблер для AVR
- AVRDUDE — утилита для прошивки микроконтроллеров
Основные этапы разработки:
- Написание кода в текстовом редакторе или IDE
- Компиляция ассемблером в HEX-файл
- Прошивка HEX-файла в микроконтроллер
Отладка программ для AVR на ассемблере
При разработке программ на ассемблере для AVR полезны следующие методы отладки:
- Пошаговое выполнение кода в симуляторе
- Установка точек останова
- Просмотр значений регистров и памяти
- Использование внутрисхемных отладчиков
Оптимизация кода на ассемблере для AVR
Ассемблер позволяет создавать очень эффективный код. Некоторые приемы оптимизации:
- Использование битовых операций вместо арифметических
- Замена циклов на развернутый код
- Применение специальных инструкций AVR (например, SBRC)
- Оптимизация доступа к памяти
Расширенные возможности программирования AVR на ассемблере
Помимо базовых операций, ассемблер AVR позволяет использовать:
- Прерывания для обработки асинхронных событий
- Таймеры/счетчики для точного отсчета времени
- АЦП для измерения аналоговых сигналов
- Последовательные интерфейсы (UART, SPI, I2C)
Преимущества и недостатки программирования AVR на ассемблере
Преимущества:
- Максимальный контроль над аппаратными ресурсами
- Наиболее компактный и быстрый код
- Возможность оптимизации критичных участков
Недостатки:
- Сложность разработки больших программ
- Трудности с переносимостью кода
- Больше времени на разработку по сравнению с C
Часто задаваемые вопросы о программировании AVR на ассемблере
Какие знания нужны для программирования AVR на ассемблере?
Для эффективного программирования AVR на ассемблере необходимо:
- Понимание архитектуры AVR микроконтроллеров
- Знание системы команд AVR
- Умение работать с регистрами и памятью МК
- Навыки низкоуровневой оптимизации кода
Чем отличается программирование AVR на ассемблере от программирования на C?
Основные отличия:
- Ассемблер требует явного управления регистрами и памятью
- Код на ассемблере более verbose, но и более эффективен
- C предоставляет более высокий уровень абстракции
- На ассемблере сложнее реализовывать алгоритмически сложные задачи
Какие инструменты нужны для программирования AVR на ассемблере?
Минимальный набор инструментов включает:
- Текстовый редактор для написания кода
- Ассемблер (например, AVRA) для компиляции
- Программатор и ПО для прошивки МК
Дополнительно могут использоваться:
- IDE (например, Atmel Studio)
- Отладчик
- Симулятор
Первая программа для AVR микроконтроллера на Ассемблере
Приведен и подробно разобран пример простой программы для AVR микроконтроллера на языке Ассемблер (Assembler). Собираем простую схему на микроконтроллере для мигания светодиодами, компилируем программу и прошиваем ее в микроконтроллер под ОС GNU Linux.
Содержание:
- Подготовка
- Принципиальная схема и макет
- Исходный код программы на Ассемблере
- Документация по Ассемблеру
- Работа с числами в Hex, Bin и Dec
- Компиляция и прошивка программы в МК
- Заключение
Подготовка
Итак, у нас уже есть настроенный и подключенный к микроконтроллеру программатор, также мы разобрались с программой avrdude, изучили ее настройки и примеры использования. Пришло время разработать свою первую программу, которая будет выполнять какие-то реальные действия с AVR микроконтроллером (МК).
Писать программу мы будем на языке программирования Ассемблер (Assembler, Asm). Основной ее задачей будет заставить поочередно и с установленной задержкой мигать два разноцветных светодиода (красный и синий), имитируя таким образом полицейскую мигалку.
В результате у вас получится простая электронная схема, которую можно вмонтировать в какой-то пластмассовый макет полицейского автомобиля и подарить ребенку для забавы.
Понятное дело что подобную мигалку можно реализовать на основе простого мультивибратора на двух транзисторах с конденсаторами. Микроконтроллер же вам предоставляет намного больше возможностей.
Используя один чип можно оживить полицейскую мигалку + заставить раз в несколько секунд мигать модель авто фарами, добавить различные звуковые эффекты, научить модельку ездить реагируя на препятствия и многое другое.
Первый инструмент, который нам понадобится — редактор исходного кода, здесь можно использовать любой текстовый редактор. В одной из прошлых статей мы рассматривали настройку среды разработки программ Geany для программирования AVR микроконтроллеров с использованием языков Ассемблера и Си.
В принципе там уже все готово, останется написать код программы и поочередным нажатием двух кнопок (Compile-Flash) скомпилировать и прошить программу в микроконтроллер.
Несмотря на то что у вас уже может быть настроена среда Geany, я приведу все консольные команды которые необходимы для компиляции и прошивки нашей программы в МК.
Принципиальная схема и макет
Для понимания того что из себя представляет наша конструкция, для которой мы будем писать программу, приведу ниже принципиальную схему устройства.
Рис. 1. Принципиальная схема мигалки на светодиодах и микроконтроллере ATmega8.
Примечание: принципиальная схема нарисована за несколько минут в программе Eeschema, которая входит в комплекс программ EDA(Electronic Design Automation) KiCAD (для Linux, FreeBSD, Solaris, Windows). Очень мощный профессиональный инструмент, и что не мало важно — свободный!
Схема устройства состоит из микроконтроллера ATmega8 и двух светодиодов, которые подключены через гасящие резисторы. К микроконтроллеру подключен ISP-коннектор для осуществления программирования через программатор. Также предусмотрены клеммы для подключения внешнего источника питания напряжением 5В.
То как выглядит данная схема в сборе на макетной баспаечной панели (BreadBoard) можно посмотреть на рисунке ниже:
Рис. 2. Конструкция светодиодной мигалки на микроконтроллере ATmega8.
К микроконтроллеру подключен программатор USBAsp, используя ISP интерфейс, от него же и будет питаться наша экспериментальная конструкция. Если нужно запитать конструкцию от внешнего источника питания напряжением 5В то достаточно его подключить к + и — линиям питания панели.
Исходный код программы на Ассемблере
Разработанная нами программа будет попеременно зажигать и гасить два светодиода. Светодиоды подключены к двум пинам микроконтроллера, которые соответствуют каналам с названиями PD0 и PD1 (порт PORTD, D).
Ниже приведен исходный код программы на Ассебмлере(Assembler, Asm) для микроконтроллера ATmega8. Сохраните этот код в файл под названием leds_blinking.asm для последующей работы.
; Светодиодная мигалка на микроконтроллере ATmega8 ; https://ph0en1x.net .INCLUDEPATH "/usr/share/avra/" ; путь для подгрузки INC файлов .INCLUDE "m8def.inc" ; загрузка предопределений для ATmega8 .LIST ; включить генерацию листинга .CSEG ; начало сегмента кода .ORG 0x0000 ; начальное значение для адресации ; -- инициализация стека -- LDI R16, Low(RAMEND) ; младший байт конечного адреса ОЗУ в R16 OUT SPL, R16 ; установка младшего байта указателя стека LDI R16, High(RAMEND) ; старший байт конечного адреса ОЗУ в R16 OUT SPH, R16 ; установка старшего байта указателя стека .equ Delay = 5 ; установка константы времени задержки ; -- устанавливаем каналы PD0 и PD1 порта PORTD (PD) на вывод -- LDI R16, 0b00000011 ; поместим в регистр R16 число 3 (0x3) OUT DDRD, R16 ; загрузим значение из регистра R16 в порт DDRD ; -- основной цикл программы -- Start: SBI PORTD, PORTD0 ; подача на пин с каналом PD0 высокого уровня CBI PORTD, PORTD1 ; подача на пин сканалом PD1 низкого уровня RCALL Wait ; вызываем подпрограмму задержки по времени SBI PORTD, PORTD1 ; подача на пин с каналом PD1 высокого уровня CBI PORTD, PORTD0 RCALL Wait RJMP Start ; возврат к метке Start, повторяем все в цикле ; -- подпрограмма задержки по времени -- Wait: LDI R17, Delay ; загрузка константы для задержки в регистр R17 WLoop0: LDI R18, 50 ; загружаем число 50 (0x32) в регистр R18 WLoop1: LDI R19, 0xC8 ; загружаем число 200 (0xC8, $C8) в регистр R19 WLoop2: DEC R19 ; уменьшаем значение в регистре R19 на 1 BRNE WLoop2 ; возврат к WLoop2 если значение в R19 не равно 0 DEC R18 ; уменьшаем значение в регистре R18 на 1 BRNE WLoop1 ; возврат к WLoop1 если значение в R18 не равно 0 DEC R17 ; уменьшаем значение в регистре R17 на 1 BRNE WLoop0 ; возврат к WLoop0 если значение в R17 не равно 0 RET ; возврат из подпрограммы Wait Program_name: .DB "Simple LEDs blinking program"
Кратко рассмотрим приведенный выше код и построчно разберем его структуру. Выполнение программы происходит по порядку — с верху кода и к низу, учитывая при этом метки, переходы с возвратами и условия.
Все строки и части строк, которые начинаются с символа «;» — это комментарии. При компиляции и выполнении программы такие строчки игнорируются, они служат для документирования и примечаний.
При помощи директивы «.INCLUDEPATH» мы указываем путь «/usr/share/avra/», по которому компилятору нужно искать файлы для включения их в текущий файл с использованием директив « .INCLUDE«. В нашем примере подключается файл, полный путь к которому будет выглядеть вот так: «/usr/share/avra/m8def.inc».
Директива «.LIST» указывает компилятору о необходимости генерирования листинга с текущего места в коде, отключить генерирование можно директивой «.NOLIST». Листинг представляет собой файл в котором содержится комбинация ассемблерного кода, адресов и кодов операций. Используется для отладки и других полезных нужд.
Директива «.CSEG» (CodeSEGment) определяет начало программного сегмента (код программы что записан во флешь-память) — сегмента кода. Соответственно все что размещено ниже этой директивы относится к программному коду.
Для определения сегмента данных (RAM, оперативная память) или памяти EEPROM используются директивы «.DSEG» и «.ESEG» соответственно. Таким образом выполняется распределение памяти по сегментам.
Каждый из сегментов может использоваться в программном коде только раз, по умолчанию если не указана ни одна из директив используется сегмент кода (CSEG).
При помощи директивы «.ORG» компилятору указывается начальный адрес «0x0000» сегмента, в данном случае мы указали начальный адрес сегмента кода. В данной программе эту директиву можно было бы и не использовать, поскольку по умолчанию адрес программного кода всегда 0x0000.
Дальше в коде происходит инициализация стека. Стек (Stack) — это область памяти (как правило у всех AVR чипов размещается в SRAM), которая используется микропроцессором для хранения и последующего считывания адресов возврата из подпрограмм, а также для других пользовательских нужд.
При вызове подпрограммы flhtc nt записывается в стек и начинается выполнение кода подпрограммы. По завершению подпрограммы (директива RET)
Стек работает по принципу LIFO (Last In — First Out, последним пришёл — первым вышел). Для адресации вершины стека используется указатель стека — SP (Stack Pointer), это может быть однобайтовое или двухбайтовое значение в зависимости от доступного количества SRAM памяти в МК.
При помощи инструкции «LDI» мы загружаем в регистр R16 значение младшего байта конечного адреса ОЗУ «Low(RAMEND)» (предопределенная константа в файле m8def.inc что содержит адрес последней ячейки SRAM), а потом при помощи инструкции OUT выполняем загрузку данного значения из регистра R16 в порт SPL (Stack Pointer Low). Таким же образом производится инициализация старшего байта адреса в указателе стека SPH.
Инструкция LDI используется для загрузки старшего и младшего значений из константы в регистр общего назначения. А инструкция OUT позволяет выполнить операцию загрузки с немного иной спецификой — из регистра общего назначения в регистр периферийного устройства МК, порт ввода-вывода и т.п.
Если не произвести инициализацию стека то возврат из подпрограмм станет невозможным, к примеру в приведенном коде после выполнения инструкции перехода к подпрограмме «RCALL Wait» возврат не будет выполнен и программа не будет работать как нужно.
Директива «.equ» выполняет присвоение указанному символьному имени «Delay» числового значения «5», по сути мы объявили константу. Имя константы должно быть уникальным, а присвоенное значение не может быть изменено в процессе работы программы.
Дальше мы устанавливает каналы PD0 и PD1 порта DDRD (PortD) на вывод, делается это загрузкой двоичного значения 0b00000011 (0x3, число 3) в регистр R16 с последующим выводом этого значения из него в порт DDRD при помощи команды OUT.
По умолчанию все каналы (и соответствующие им пины) порта настроены на ввод (input). При помощи двоичного числа 0b00000011, где последние биты установлены в 1, мы переводим каналы PD0 и PD1 в режим вывода.
Начиная с метки «Start:» начинается основной рабочий цикл нашей программы, эта метка послужит нам для обозначения начального адреса основного цикла и позже будет использована для возврата.
При помощи инструкции «SBI» (Set Bit in I/O register) выполняем установку бита PORTD0 (предопределен в файле m8def.inc) в порте PORTD чем установим на пине с каналом PD0 высокий уровень напряжения.
Используя инструкцию «CBI» (Clear Bit in I/o register) выполняется очистка бита PORTD1 в байте регистра для порта PORTD и тем самым устанавливается низкий уровень напряжения на пине с каналом PD1.
Дальше с помощью инструкции RCALL выполняем относительный вызов подпрограммы которая начинается с метки «Wait:«. Здесь для запоминания адреса возврата уже используется стек, который мы инициализировали в начале программы.
После завершения подпрограммы (в нашем случае ее функция — задержка по времени) программа вернется к позиции где был выполнен вызов подпрограммы (адрес возврата будет получен из стека) и с этого места продолжится выполнение последующих операторов.
После вызова подпрограммы задержки «Wait» следуют вызовы инструкций SBI и CBI в которых выполняется установка битов в байте регистра для порта PORTD таким образом, что теперь на пине с каналом PD0 у нас будет низкий уровень, а на пине с каналом PD1 — высокий.
По завершению этих инструкций следует еще один вызов подпрограммы задержки «Wait», а дальше следует инструкция «RJMP» которая выполнит относительный переход к указанной метке — «Start», после чего программа снова начнет установку битов в порте с задержками по времени.
Таким образом выполняется реализация бесконечного цикла в котором будут изменяться состояния битов в байте регистра для порта PORTD микроконтроллера и поочередно зажигаться/гаснуть светодиоды которые подключены к пинам что соответствуют каналам данного порта (PD0, PD1).
После основного цикла программы следует наша подпрограмма задержки по времени. Принцип ее работы заключается в выполнении трех вложенных циклов, в каждом из которых происходит вычитание (DEC, decrement) единички из числа которое хранится в отдельном регистре, и так до тех пор пока значение не достигнет нуля. Инструкция «DEC» декрементирует значение указанного регистра и требует для этого 1 рабочий такт процессора.
При помощи инструкций «BRNE» (условный переход) выполняется анализ нулевого бита статусных флагов процессора (Zero Flag, ZF). Переход на указанную в инструкции метку будет выполнен если после выполнения предыдущей команды нулевой флаг был установлен.
В данном случае проверяется значение нулевого флага после выполнения команд «DEC» над значениями которые хранится в регистрах общего назначения (R17, R18, R19). Инструкция «BRNE» требует 1/2 такта процессора.
Таким образом, использовав несколько вложенных циклов, ми заберем у ЦПУ некоторое количество тактов и реализуем нужную задержку по времени, которая будет зависеть от количества итераций в каждом цикле и от установленной частоты микропроцессора.
По умолчанию, без установки фьюзов что задают источник и частоту тактового генератора, в микроконтроллере ATmega8 используется откалиброванный внутренний RC-генератор с частотой 1МГц. Если же мы изменим частоту МК на 4Мгц то наши светодиоды начнут мигать в 4 раза быстрее, поскольку на каждую операцию вычитания и сравнения будет тратиться в 4 раза меньше времени.
Завершается подпрограмма инструкцией «RET«, которая выполняет возврат из подпрограммы и продолжение выполнения инструкций с того места, с которого эта подпрограмма была вызвана (на основе сохраненного адреса возвращения, который сохранился в стеке при вызове инструкции «RCALL»).
При помощи директивы «.DB» в памяти программ (флешь) резервируется цепочка из байтов под строчку данных «Simple LEDs blinking program», эти данные являются статичными и их нельзя изменять в ходе работы программы. Для резервирования слов (Double Word) нужно использовать директиву «.DW».
В данном случае, у нас во FLASH-память вместе с программным кодом будет записана строка «Simple LEDs blinking program«, которая содержит название программы. Данные из этой строчки нигде в программе не используются и приведены в качестве примера.
При каждом резервировании данных с использованием директивы «.DB» или «.DW» должна предшествовать уникальная метка, которая пригодится нам когда нужно будет получить адрес размещаемых данных в памяти для дальнейшего их использования, в нашем случае это «Program_name:«.
При построении программы важно чтобы счетчик выполняемых команд не добрался до адреса с зарезервированными данными, иначе процессор начнет выполнять эти строчки как программный код (поскольку они размещены в сегменте кода). В примере моей программы байты под название программы зарезервированы в конце сегмента кода и за пределами рабочих циклов программы, так что все ОК.
Эти данные можно разместить и в начале кода, использовав операторы перехода для изоляции этих байтов от выполнения:
RJMP DataEnd Program_name: .DB "Simple LEDs blinking program" DataEnd:
Документация по Ассемблеру
Разобраться с основами языка программирования Ассемблер в пределах одной статьи достаточно сложно, без практики здесь никак, но тем не менее на начальном этапе и для нашего эксперимента приведенных знаний вполне достаточно. У вас уже будет базовое представление что такое программа на Ассемблере и как используются директивы и инструкции.
Процесс дальнейшего изучения Ассемблера для AVR микроконтроллеров полностью в ваших руках. Есть достаточно много полезных ресурсов в интернете, книг и материалов с примерами и пояснениями.
Приведу несколько полезных документов, которые вы можете скачать и использовать для справки при разработке программ на AVR ASM.
Справка по Ассемблеру для Atmel AVR (перевод Руслана Шимкевича): atmel-avr-assembler-quick-doc-ru.zip (16Кб, HTML, RU).
Справка по инструкциям Atmel Assembler: atmel-avr-instruction-set-manual-en.pdf.zip (700Кб, PDF, EN, 2015).
Работа с числами в Hex, Bin и Dec
В коде программы для загрузки значений в регистры используются числа и в скобках приведены их значения в шестнадцатеричной системе счисления, например: «50 (0x32, )». В двоичной системе счисления числа указываются в формате «0b00000011».
Для удобной переконвертации чисел из шестнадцатеричной системы счисления в десятичную, двоичную и наоборот отлично подходит программный калькулятор из среды рабочего окружения KDE — KCalc.
Рис. 3. KCalc — простое и эффективное решение для пересчета между разными системами счисления.
В настройках (Settings) нужно выбрать режим (Numeral System Mode), после чего программа приобретет вид что на рисунке выше. Переключаться между системами счисления можно устанавливая флажки в полях «Dec», «Hex», «Bin».
Для примера: переключаемся в Hex и набираем «FF», потом переключаемся в Dec и видим число в десятичной системе счисления — 255, просто и удобно.
В операционной системе GNU Linux с рабочей средой GNOME (например Ubuntu) также есть подобный калькулятор, это программа — galculator.
Компиляция и прошивка программы в МК
Итак, у нас уже есть полный код программы, который мы сохранили в файл с именем «leds_blinking.asm». Теперь самое время скомпилировать его, делается это нажатием кнопки «Compile» в предварительно настроенной среде Geany или же отдельной командой в консоли:
avra --includepath /usr/share/avra/ leds_blinking.asm
Если результат выполнения будет без ошибок то мы получим файл прошивки в формате Intel HEX — «leds_blinking.hex», который уже можно прошивать во флешь-память микроконтроллера.
Примечание: опцию «—includepath /usr/share/avra/» можно и не указывать, поскольку в файле с исходным кодом уже была указана директива «.INCLUDEPATH» для поиска файлов с предопределениями для разных моделей МК.
Осталось прошить микроконтроллер используя полученный файл «leds_blinking.hex». В примере я использую программатор USBAsp и микроконтроллер ATmega8, вот так выглядит команда для записи получившегося файла во флешь-память МК:
avrdude -p m8 -c usbasp -P usb -U flash:w:leds_blinking.hex
Примечание: в команде используется относительный путь к файлу leds_blinking.hex, поэтому для успешного выполнения команды нужно перейти в терминале(консоли) в директорию где находится данный файл.
Сразу же после прошивки флешь-памяти на микроконтроллер поступит команда сброса (RESET) и программа начнет выполняться, об єтом будут свидетельствовать два попеременно мелькающих светодиода.
Если же светодиоды не подают признаков жизни, значит что-то пошло не так. Посмотрите внимательно вывод команды для компиляции и прошивки МК, возможно что там увидите сообщения об ошибках которые нужно исправить.
Заключение
Увеличив значение константы «Delay» можно уменьшить частоту мерцания светодиодов, а уменьшив — увеличить частоту. Также можете попробовать добавить несколько светодиодов к свободным каналам порта (PD2-PD7) и модифицировать программу таким образом чтобы получить бегущий огонь из светодиодов.
В заключение приведу краткое видео работы рассмотренной схемы на двух светодиодах:
В следующей статье мы разберем программу с похожим функционалом, используя тот-же макет, только выполним ее на языке программирования Си.
Начало цикла статей: Программирование AVR микроконтроллеров в Linux на языках Asembler и C.
Урок 1. Запуск среды Proteus и AVR Studio, создаем первые файлы, инициализируем МК. Пишем программу «Мигалку»
В данном уроке подробно разобран пример простой программы под AVR микроконтроллер Atmega328 на языке Ассемблер (Assembler). Проектируем простую схему для мигания светодиодом и эмулируем ее в среде Proteus.
Содержание:
- Подготовка
- Принципиальная схема
- Документация по применяемым директивам и инструкциям Ассемблера
- Исходный код программы на Ассемблере
- Заключение
Прежде всего нужно установить среду Atmel Studio 7. 0 для написания прошивки и Proteus от 7 версии в выше, для эмуляции работы микроконтроллера.
Программа будет написана на языке Ассемблер, главной ее задачей будет поочередно устанавливать логический «0» и «1» на выбранном порту ввода-вывода МК Atmega328. По окончании урока получим готовое, хоть и примитивное устройство — мигающий светодиод.
Первый инструмент, который нам понадобится — редактор исходного кода Atmel Studio 7.0. Скачать можно с официального сайта, бесплатно.
Открываем Atmel Studio и создаем новый проект, как показано ниже.
Для понимания того что из себя представляет наша конструкция, для которой мы будем писать программу, приведу ниже принципиальную схему устройства.
Как видно на схеме всего 3 элемента, МК, резистор и светодиод. (При симуляции в Proteus линии питания можно не задействовать, они включены по умолчанию). Настройки фьюз-битов оставим по умолчанию. В данный момент нам понадобится только параметр частоты тактирования, который по умлчанию 8 МГц, и активный делить на 8, битом CKDIV8.
Обозначение | Функция |
ADC | Сложить с переносом |
ADD | Сложить без переноса |
ADIW | Сложить непосредственное значение со словом |
AND | Выполнить логическое AND |
ANDI | Выполнить логическое AND c непосредственным значением |
ASR | Арифметически сдвинуть вправо |
BCLR | Очистить флаг |
BLD | Загрузить T флаг в бит регистра |
BRBC | Перейти если бит в регистре статуса очищен |
BRBS | Перейти если бит в регистре статуса установлен |
BRCC | Перейти если флаг переноса очищен |
BRCS | Перейти если флаг переноса установлен |
BREQ | Перейти если равно |
BRGE | Перейти если больше или равно (с учетом знака) |
BRHC | Перейти если флаг полупереноса очищен |
BRHS | Перейти если флаг полупереноса установлен |
BRID | Перейти если глобальное прерывание запрещено |
BRIE | Перейти если глобальное прерывание разрешено |
BRLO | Перейти если меньше (без знака) |
BRLT | Перейти если меньше чем (со знаком) |
BRMI | Перейти если минус |
BRNE | Перейти если не равно |
BRPL | Перейти если плюс |
BRSH | Перейти если равно или больше (без знака) |
BRTC | Перейти если флаг T очищен |
BRTS | Перейти если флаг T установлен |
BRVC | Перейти если переполнение очищено |
BRVS | Перейти если переполнение установлено |
BSET | Установить флаг |
BST | Переписать бит из регистра во флаг T |
CALL | Выполнить длинный вызов подпрограммы |
CBI | — Очистить бит в регистре I/O |
CBR | Очистить биты в регистре |
CLC | Очистить флаг переноса |
CLH | Очистить флаг полупереноса |
CLI | Очистить флаг глобального прерывания |
CLN | Очистить флаг отрицательного значения |
CLR | Очистить регистр |
CLS | Очистить флаг знака |
CLT | Очистить флаг T |
CLV | Очистить флаг переполнения |
CLZ | Очистить флаг нулевого значения |
COM | Выполнить дополнение до единицы |
CP | Сравнить |
CPC | Сравнить с учетом переноса |
CPI | Сравнить c константой |
CPSE | Сравнить и пропустить если равно |
DEC | Декрементировать |
EOR | Выполнить исключающее OR |
ICALL | Вызвать подпрограмму косвенно |
IJMP | Перейти косвенно |
IN | Загрузить данные из порта I/O в регистр |
INC | Инкрементировать |
FMUL | Дробное незнаковое умножение |
FMULS | Дробное умножение со знаком |
FMULSU | Дробное умножение знакового с незнаковым |
JMP | Перейти |
LD Rd,X | Загрузить косвенно |
LD Rd,X+ | Загрузить косвенно инкрементировав впоследствии |
LD Rd,-X | Загрузить косвенно декрементировав предварительно |
LDI | Загрузить непосредственное значение |
LDS | Загрузить непосредственно из СОЗУ |
LPM | Загрузить байт памяти программ |
LSL | Логически сдвинуть влево |
LSR | Логически сдвинуть вправо |
MOV | Копировать регистр |
MUL | Перемножить |
NEG | Выполнить дополнение до двух |
NOP | Выполнить холостую команду |
OR | Выполнить логическое OR |
ORI | Выполнить логическое OR с непосредственным значением |
OUT | Записать данные из регистра в порт I/O |
POP | Загрузить регистр из стека |
PUSH | Поместить регистр в стек |
RCALL | Вызвать подпрограмму относительно |
RET | Вернуться из подпрограммы |
RETI | Вернуться из прерывания |
RJMP | Перейти относительно |
ROL | Сдвинуть влево через перенос |
ROR | Сдвинуть вправо через перенос |
SBC | Вычесть с переносом |
SBCI | Вычесть непосредственное значение с переносом |
SBI | Установить бит в регистр I/O |
SBIC | Пропустить если бит в регистре I/O очищен |
SBIS | Пропустить если бит в регистре I/O установлен |
SBIW | Вычесть непосредственное значение из слова |
SBR | Установить биты в регистре |
SBRC | Пропустить если бит в регистре очищен |
SBRS | Пропустить если бит в регистре установлен |
SEC | Установить флаг переноса |
SEH | Установить флаг полупереноса |
SEI | Установить флаг глобального прерывания |
SEN | Установить флаг отрицательного значения |
SER | Установить все биты регистра |
SES | Установить флаг знака |
SET | Установить флаг T |
SEV | Установить флаг переполнения |
SEZ | Установить флаг нулевого значения |
SLEEP | Установить режим SLEEP |
ST X,Rr | Записать косвенно |
ST Y,Rr | Записать косвенно из регистра в СОЗУ с использованием индекса Y |
ST Z,Rr | Записать косвенно из регистра в СОЗУ с использованием индекса Z |
STS | Загрузить непосредственно в СОЗУ |
SUB | Вычесть без переноса |
SUBI | Вычесть непосредственное значение |
SWAP | Поменять нибблы местами |
TST | Проверить на ноль или минус |
WDR | Сбросить сторожевой таймер |
. def temp = r16 ; задаем имя нашему регистру общего назначения .org 0x0000 ; начинаем программу с reset rjmp reset reset: ldi temp, LOW(RAMEND) ;Указатель стека указывает out SPL, temp ;на последний адрес ОЗУ ldi temp, HIGH(RAMEND) out SPH, temp ldi temp, 0b00000001 ; порт В на выход out DDRB, temp ; //Основное тело программы main: sbi PORTB,0 rcall delay cbi PORTB,0 rcall delay rjmp main //Подпрограмма задержки delay: clr r20 clr r21 m_1: inc r20 brne m_1 inc r21 brne m_1 ret
Заключение
Как видно, программа состояит из инициализации переферии МК, основного цикла и подпрограммы задержки.
Результат кода показан на снимке осциллографа
Скачать исходный код
AVR Tutorials — Пример программы №3: Мигание
Здесь мы рассмотрим программу, которая будет мигать светодиодом с частотой 1 Гц. Программа написана для микроконтроллера ATmega328P, работающего на частоте 16 МГц, со светодиодом, подключенным к PINB0
. Если ваши настройки отличаются, вам придется внести коррективы.
В этой программе мы увидим, как работает условное ветвление , и используем его для настройки цикла задержки 0,5 секунды. Мы также рассмотрим время выполнения отдельных инструкций и посмотрим, как точно рассчитать, сколько времени потребуется для выполнения сегмента кода. Программа показана ниже.
.include "m328pdef.inc" маска .def = r16 ; регистр маски .def ledR = r17 ; светодиодный регистр .def oLoopR = r18 ; регистр внешнего цикла .def iLoopRl = r24 ; низкий регистр внутреннего цикла .def iLoopRh = r25 ; высокий регистр внутреннего цикла .equ oVal = 71 ; значение внешнего цикла .equ iVal = 28168 ; значение внутреннего цикла .cseg .org 0x00 клр светодиодR ; очистить светодиодный регистр ldi маска,(1<Расшифровка кода
В этой программе мы начинаем, как обычно, с директивы . include для определений наших выводов и регистров.
.include "m328pdef.inc"После этого мы используем директиву .def, чтобы дать некоторым нашим регистрам осмысленные имена.
.def маска = r16 ; регистр маски .def ledR = r17 ; светодиодный регистр .def oLoopR = r18 ; регистр внешнего цикла .def iLoopRl = r24 ; низкий регистр внутреннего цикла .def iLoopRh = r25 ; верхний регистр внутреннего циклаДля нашей программы мы определяем регистр маски для управления переключением светодиодов, регистр для хранения значения, которое мы собираемся записать в наш порт светодиода, регистр для действия в качестве счетчика для внешнего цикла и пару регистров для действовать как счетчик для внутреннего цикла. Мы могли бы просто использовать имена регистров, но их определение делает нашу программу более легкой для чтения и более легкой для изменения регистра позже, если нам нужно.
Теперь мы подошли к новой директиве . equ - равно . Директива .equ позволяет нам определять константы в нашей программе так же, как в C. Как и в случае с директивой .def, использование .equ дает осмысленные имена константам в нашей программе и позволяет легко изменить значение позже, если нам нужно.
.equ oVal = 71 ; значение внешнего цикла .equ iVal = 28168 ; значение внутреннего циклаВыше мы определяем две константы oVal и iVal для хранения начальных значений циклов задержки, которые мы будем использовать позже.
Используйте директиву .equ для присвоения осмысленных имен константам.
Как и всегда, мы указываем сегмент кода в начале флэш-памяти, чтобы наша программа выполнялась немедленно при запуске микроконтроллера.
.cseg .org 0x00Для нашей первой инструкции мы очищаем ранее определенный регистр ledR, чтобы убедиться, что его содержимое равно 0. Затем мы загружаем маску со значением (1<
клр светодиодR ; очистить светодиодный регистр ldi маска,(1<Переключение светодиодов
Далее у нас есть начало метки, которое будет обозначать начало цикла задержки переключения светодиодов. Начнем с выполнения эксклюзив ИЛИ между ledR и маской. Помните, что ledR имеет значение 00000000, когда мы впервые входим в цикл, а маска имеет значение 00000001, поэтому после завершения исключающего ИЛИ ledR будет содержать значение 00000001.
start: eor ledR,mask ; переключить PINB0 в ledR выход PORTB,LEDR ; записать ledR в PORTBПри следующем выполнении цикла ledR будет содержать значение 00000001, а значение маски по-прежнему будет 00000001. Таким образом, при повторном выполнении исключающего ИЛИ ledR будет содержать значение 00000000. Это наш способ переключения бит. Мы выводим это значение в PORTB, чтобы включить или выключить PINB0.
У нас есть способ переключать
PINB0
, теперь нам просто нужен способ потратить некоторое время на создание задержки. Мы сделаем это с помощью двух циклов, внутреннего и внешнего, каждый из которых будет уменьшать значение определенного регистра, пока оно не достигнет нуля.Цикл задержки
Значение счетчика внешнего цикла будет 8-битным и будет загружено в регистр oLoopR. Мы загрузим константу oVal (определенную как 71) в регистр счетчика внешнего цикла.
ldi oLoopR,OVal ; инициализировать счетчик внешнего циклаДва 8-битных счетчика просто не дадут нам достаточно времени для заметной задержки на 16 МГц, поэтому нам понадобится 16-битное значение для нашего внутреннего цикла. Для этого мы будем использовать регистры r24 и r25 (определенные как iLoopL и iLoopH соответственно), так как они поддерживают некоторые специальные 16-битные инструкции.
На метке oLoop (внешний цикл) мы инициализируем oLoopRl и oLoopH с помощью двух отдельных инструкций ldi, поскольку нет возможности напрямую загрузить 16-битное значение в два регистра с помощью одной инструкции.
Чтобы облегчить нашу жизнь, ассемблер предлагает несколько функций для разбиения числа на составляющие его байты —
LOW()
иHIGH()
. Ниже показано, как использоватьLOW()
иHIGH()
, чтобы разбить iVal на младшие и старшие байты и сохранить их в регистрах подсчета внутреннего цикла.oLoop: ldi iLoopRl,LOW(iVal) ; инициализировать счетчик внутреннего цикла во внутреннем ldi iLoopRh,HIGH(iVal) ; Цикл верхнего и нижнего регистровПримечание: Функции LOW и HIGH разбивают 16-битные числа на младшие и старшие байты соответственно. Это делает ассемблер, а не микроконтроллер.
Мы инициализировали наш 16-битный счетчик перед входом во внутренний цикл, теперь пришло время написать код для внутреннего цикла. Начнем с того, что пометим его меткой iLoop.
Затем мы используем 16-битную инструкцию sbiw для уменьшения регистров
iLoopRl
иiLoopRh
. sbiw вызывается с операндом iLoopRl (подразумевается iLoopRh) и константой 1. В каждом цикле цикла мы будем вычитать 1 из iLoopRl и iLoopRh, и микроконтроллер автоматически будет обрабатывать биты переноса между двумя регистрами.iLoop: sbiw iLoopRl,1 ; decrement iLoopRh:iLoopRlТеперь у нас есть способ уменьшить наш 16-битный счетчик, но ничего из того, что мы видели до сих пор, не позволяет нам зацикливаться до тех пор, пока он не достигнет нуля, и продолжить, когда это произойдет. Сейчас мы вводим условную ветвь .
Инструкция brne - ветвь , если не равно - проверяет, был ли результат предыдущей операции нулевым . Если бы было а не ,
brne
будет филиал на метку, указанную в качестве операнда. Если это было ноль, то brne перейдет к следующей инструкции.iLoop: sbiw iLoopRl,1 ; уменьшить iLoopRh: iLoopRl включить iLoop ; перейти к iLoop, если iLoopRh:iLoopRl != 0Непосредственно перед инструкцией brne мы уменьшили iLoopRl:iLoopRh. Если счетчик не достиг нуля, нам нужно продолжать уменьшение, поэтому мы указываем метку iLoop в качестве цели для brne.
Код будет продолжать уменьшать iLoopRl:iLoopRh и переходить обратно к iLoop, пока он не равен нулю. Когда iLoopRl:iLoopRh, наконец, достигнет нуля, brne перейдет к следующей инструкции.
Этот поток показан на следующей диаграмме.
Теперь, когда у нас есть внутренний цикл, который будет уменьшать значение нашего 16-битного счетчика, нам просто нужно сделать то же самое для счетчика внешнего цикла. Поскольку он всего 8-битный, мы можем использовать инструкцию dec для уменьшения его счетчика. Затем мы можем использовать ту же инструкцию условного перехода brne, чтобы проверить, является ли она нулевой или нет.
dec oLoopR ; уменьшить oLoopR brne oLoop ; переход к oLoop, если oLoopR != 0На этот раз мы не хотим просто возвращаться к инструкции dec — если oLoopR не равно нулю, мы хотим повторно инициализировать наш 16-битный счетчик и снова пройти этот цикл , поэтому мы предоставляем метку oLoop. Наш код теперь будет проходить через наш внутренний цикл каждый раз, когда он проходит через внешний цикл, давая нам полный 24-битный счетчик — и достаточно потерянного времени, чтобы иметь заметную задержку.
Когда счетчик внешнего цикла oLoopR, наконец, достигнет нуля, мы перейдем к нашей следующей инструкции — переходу к началу, где мы переключим наш выходной контакт и начнем весь процесс заново. Весь «раздел задержки» нашего кода показан ниже.
ldi oLoopR,OVal ; инициализировать счетчик внешнего цикла oLoop: ldi iLoopRl,LOW(iVal) ; инициализировать счетчик внутреннего цикла во внутреннем ldi iLoopRh,HIGH(iVal) ; зациклить верхний и нижний регистры iLoop: sbiw iLoopRl,1 ; уменьшить iLoopRh: iLoopRl включить iLoop ; переход к iLoop, если iLoopRh:iLoopRl != 0 dec oLoopR ; уменьшить oLoopR brne oLoop ; перейти к oLoop, если oLoopR != 0 запуск rjmp; вернуться к началуРасчет времени задержки
Теперь, когда вы увидели программу, вам может быть интересно, где значения цикла
oVal
иiVal
произошли от.Если вы обратитесь к набору инструкций ATmel, вы увидите, что каждая инструкция выполняется за определенное количество тактов. Большинство инструкций требуют только одного или двух, но некоторые могут занять три или четыре. Поскольку мы знаем, сколько времени занимает каждая инструкция, и частоту ЦП нашего микроконтроллера, мы можем рассчитать ровно , сколько времени потребуется для выполнения нашего кода — то, что невозможно в C.
0007 внутренний цикл . У нас есть две инструкции - sbiw и brne.
iLoop: sbiw iLoopRl,1 brne iLoopЕсли вы посмотрите на набор инструкций ATmel, то увидите, что для выполнения sbiw требуется 2 такта. Однако, если вы посмотрите на циклы для brne, вы получите несколько более сложный ответ: 1, если условие ложно, 2, если условие истинно.
Если вы посмотрите на цикл, то заметите, что условие brne оценивается как истинное всякий раз, когда iLoopRl не равен нулю. Таким образом, brne будет занимать 2 цикла каждый раз, когда мы проходим цикл, за исключением 9.0007 последние раз.
Время для этого цикла на самом деле легко вычислить. Мы пройдем цикл iVal раз. Каждая инструкция sbiw будет занимать 2 такта, а инструкция brne будет занимать 2 такта каждый раз, за исключением последней итерации, которая займет всего 1. Таким образом, она займет iVal*(2+2)-1 = 28168*(2+2). )-1 = 112671 цикл, чтобы полностью завершить внутренний цикл.
iLoop: sbiw iLoopRl,1 ; 2 цикла включить iLoop ; 2 или 1 циклВнутренний цикл встроен во внешний цикл, поэтому мы должны учитывать количество его циклов при расчете времени для нашего внешнего цикла. Давайте посмотрим на циклы для инструкций во внешнем цикле.
oLoop: ldi iLoopRl,LOW(iVal) ; 1 цикл ldi iLoopRh,HIGH(iVal) ; 1 цикл ; iLoop: sbiw iLoopRl,1 ; 112671 цикл включить iLoop ; ; dec oLoopR ; 1 цикл brne oLoop ; 2 или 1 циклСложите все это, и вы увидите, что каждая итерация внешнего цикла занимает либо 112676, либо 112675 циклов, в зависимости от того, оценивает ли последняя инструкция brne значение true или false.
Мы используем oLoopR в качестве счетчика для нашего внешнего цикла, который инициализируется константой oVal. Мы можем рассчитать общее количество циклов для нашего внешнего цикла так же, как и для внутреннего цикла: oVal*112676-1 = 71*112676-1 = 7999995 циклов.
У нас есть еще несколько инструкций в нашем общем цикле для завершения программы:
start: eor ledR,mask ; 1 цикл выход PORTB,LEDR ; 1 цикл ldi oLoopR,oVal ; 1 цикл oLoop: ldi iLoopRl,LOW(iVal) ; ldi iLoopRh,HIGH(iVal) ; ; iLoop: sbiw iLoopRl,1 ; 799995 циклов включить iLoop ; ; dec oLoopR ; brne oLoop ; запуск rjmp; 2 циклаДобавление остальных инструкций к общему количеству циклов дает 8000000 циклов. На частоте 16 МГц это ровно 0,5 секунды!
Хорошо, мы узнали, как рассчитать время выполнения при заданных значениях счетчика, но как нам получить значения счетчика для желаемого количества циклов? Нам просто нужно выполнить тот же анализ, что и раньше, но сохранить значения счетчика в качестве переменных в уравнении и решить для них в конце.
Для внутреннего цикла время выполнения равно
innerLoopCount = iVal*(2+2)-1 = 4*iVal-1Для внешнего цикла
externalLoopCount = oVal*(1+1+innerLoopCount+1+2)-1 = oVal*(5+innerLoopCount)-1 = oVal*(5+(4*iVal-1))-1 = oVal*(4+4*iVal)-1 = 4*oVal*(1+iVal)-1Складывая остальные инструкции, получаем
итого = 1+1+1+outerLoopCount+2 = 5+outerLoopCount = 5+4*oVal*(1+iVal)-1 = 4+4*oVal*(1+iVal)Теперь у нас есть небольшая проблема. У нас есть две переменные, oVal и iVal, но только одно уравнение, поэтому мы не можем решить для обеих.
Быстрый и простой способ обойти это — просто выбрать значение для одного из счетчиков и найти необходимое значение для другого. Мы могли бы выбрать значение 255 для oVal, затем переставить и найти значение iVal, которое даст 8000000 циклов.
8000000 = 4+4*255*(1+iVal) 8000000-4 = 1020*(1+iVal) 7999996/1020 = 1+iVal iVal = 7842,13Это здорово, но мы можем работать только с целыми числами. iVal необходимо округлить до ближайшего целого числа, что приведет к небольшой ошибке. Подставив 7842 обратно в наше уравнение для тактов, мы получим 79.99864 с ошибкой 0,0017%. Ужасное приближение, правда?
Если вам действительно нужно точное количество тактов, вот что я предлагаю вам сделать. Откройте электронную таблицу и поместите все возможные значения для одного из ваших значений счетчика на один столбец ниже. В другом столбце вычислите соответствующее значение для другого счетчика и найдите минимальное значение. Надеюсь, что это ноль, но если нет, вам придется жить с ближайшим приближением или добавить несколько фиктивных инструкций в свой цикл, пока математика не сработает до целого числа. Пример такой электронной таблицы показан ниже.
Вы можете заметить, что счетчик oVal начинается с 256. 8-битные числа доходят только до 255, зачем включать это значение?
Подумайте на секунду, что произойдет, если мы инициализируем oVal числом 0? Поскольку мы уменьшаем его до того, как проверим его с помощью brne, на самом деле потеряет значение в первой итерации цикла (т. е. вернется к 255). Следовательно, инициализация счетчика в 0 даст 256 отсчетов.
Этот изящный маленький трюк позволит вам купить дополнительное значение цикла, если оно вам понадобится. Имейте в виду, что это работает только в том случае, если вы уменьшаете свой счет до того, как проверите его значение.
Заключение
Вот она, программа, которая мигает светодиодом с частотой 1 Гц. В этом руководстве мы увидели много полезных новых функций, в первую очередь условных ветвей . В следующих руководствах мы рассмотрим все возможности, предлагаемые условным переходом, и движущую силу, стоящую за ними, регистр состояния .
<< Предыдущая
Следующая >>
Meeting Assembly — Hello World Arduino Мигающий код | от J3 | Джунглитроника
Симуляция в Atmel Studio 7 #arduSerie — 27
На этой странице я расскажу как работает программа blink для Arduino на ассемблере,
как создать проект в Atmel Studio 7 и немного подробностей о AVR.Теперь пришло время взглянуть на то, что мы хотим сделать и как это можно сделать.
1 — Цели руководства:
После изучения этого руководства по микроконтроллерам AVR читатели должны уметь:
. Напишите ассемблерный код для инициализации AVR ATmega 328P и запуска кода на нем
. Поймите, как это работает построчно
. Пробуем это в симуляторе Atmel Studio 7
. Загрузите код в Arduino Uno непосредственно из Atmel Studio 7 IDE2 — Десять (10) Asm Используемые инструкции:
Набор инструкций AVR — Руководство
Инструкция: Цикл: Описания
лди : 1 : Немедленная загрузка в ; Загружает 8-битную константу непосредственно в регистры с 16 по 31.
cbi : 1 : Clear Bit In I/O Register — очищает указанный бит в регистре ввода/вывода.
sbi : 1 : Установить бит в регистре ввода-вывода — Устанавливает указанный бит в регистре ввода-вывода.
out : 1 : Сохранить регистр в ячейке ввода-вывода — Сохраняет данные из регистра Rr в файле регистров в пространство ввода-вывода (порты, таймеры, регистры конфигурации и т. д.).
dec : 1 : Декремент — Вычитает единицу из содержимого регистра Rd и помещает результат в целевой регистр Rd.
adiw : 2 : Add Immediate to Word — Добавляет непосредственное значение (0–63) к паре регистров и помещает результат в пару регистров.
brne : 2 : Переход, если не равно — Условный относительный переход. Проверяет нулевой флаг (Z) и выполняет переход относительно ПК, если Z сброшен.
rcall : 1 : Относительный вызов подпрограммы — Относительный вызов на адрес внутри ПК
ret : 1 : Возврат из подпрограммы — Возврат из подпрограммы.
rjmp : 1 : Относительный переход — Относительный переход к адресу.
3 — Индекс решения :Точка 00 — МОТИВАЦИЯ
Точка 01 — Открыть техническое описание — Подготовить решение
Точка 02 — Открыть проект моделирования
Точка 03 — Открыть целевой проект
Точка 04 — Запустить свой код
Точка 05 — Выполните математические действия
Пункт 06 — Запустите симуляцию на Atmega 238P
Пункт 07 — Запустите на реальной плате — Arduino UNO
Пункт 08 — Возьмите ваш проект моделирования в качестве шаблона
Пункт 09 — Возьмите ваш целевой проект в качестве шаблона
Пункт 10 — Подготовьте следующий проектСимулятор очень важен, потому что он позволяет нам шаг за шагом выполнять программу и код программного обеспечения и проанализируйте и поймите, что делает ваш код, прежде чем вы действительно загрузите его на свою доску и запустите.
Хотя аппаратное обеспечение так же важно, как и программное обеспечение, оно только сообщает вам, работает ваш код или нет.
Я хотел бы, чтобы Arduino Internals (под капотом) и Atmel Studio 7 сделали его открытым исходным кодом. Спасибо команде Атмел!
Это видео для меня как воспоминание, чтобы через какое-то время сохранить самое важное, чтобы не забыть все.
И делиться ими можно благодаря страсти, которую я испытываю к сообществу DIY. Итак, поехали! Давайте начнем!Пункт 01 — Открыть техническое описание — Подготовить решение
Для дальнейшего развития любого проекта необходимо прочитать техническое описание (ATmega328P). Откроем документ и составим первые Близких контактов третьего рода 🙂
Если вы нажмете на этот флажок (вверху справа) и направитесь к Сводка регистров , вы найдете все записи и объекты наших исследований. Здесь вы найдете три основных регистра портов ввода/вывода Atmel AVR:
PORTx, DDRx и PINx., источник: http://www.avrbeginners.net/architecture/ioports/ioports.html 2 Вот рисунок их базовая функциональность: как видите, для каждого штифта есть внутреннее подтягивание (на PIC, 18-K's Families вперед). Его можно активировать, установив бит DDR на выводе в 0, а бит порта в 1. Очищенный бит DDR означает, что вывод является входным. Таким образом, вывод отключен от регистра порта (см. драйвер на рисунке?), и вывод является плавающим. В этом случае бит порта управляет подтягиванием.
Мы выделяем конфигурацию, которую мы возьмем в нашем проекте: мы установим DDRx в 1 и будем переключать PORTx между высоким и низким состояниями, чтобы светодиод мигал.
Назад к техническому описанию из сводки набора инструкций , мы будем использовать в нашем коде только 10 из 131 (ага! всего сто тридцать один! тьфу…) Мощные инструкции, которые cbi , ldi , из , дек , adwi , brne , rcall , ret , rjmp и sbi (подробности выше).
Теперь откройте Atmel Studio 7 :Файл > Создать > Проект/РешениеЗакройте стартовую страницу , выберите Atmel Studio Solution > Blank Solution из установленных 9 0255 таб. Назовите его, как хотите… Я дам вам имя Solution_AVR1 , настройте каталог вашего проекта, чтобы вы знали, где его разместить.
Сейчас на Solution Explorer, щелкните правой кнопкой мыши и выберите Add > New Project , выберите Assembler > AVR Assembler Project, и на Device Selection , введите 328p и выберите ATmega328P и назовите его как вы желаете. Как только вы откроете проект, вы должны поместить заголовок блочного комментария (BCH), чтобы все знали, для чего предназначен этот код (что является хорошей практикой программирования). см. ссылки на потрясающий веб-сайт BCH с примерами: D
;
; _27_arduserie_Simulae_328P.asm
; ***вот идет BCH****
;Мы будем следовать академическим стандартам открытия двух проектов. Один для моделирования, а другой для загрузки в оборудование.
Точка 02 — Откройте проект моделирования
_27_arduserie_Simulae_328P для целей моделирования. Прохладный!
Point 03 — Open a Target Project
_27_arduserie_Target_328P для наведения на цель, которая является аппаратным кодом, Arduino Uno, конечно…
Пункт 04 — Инициируйте свой код
Давайте рассмотрим, пожалуй, самую простую программу:
.ORG 0x000 ; следующая инструкция должна быть написана для добавления 0x0000 ; бесконечный цикл
НАЧАЛО: ; это метка «START»
rjmp START ; R elative J u MP to STARTПри выполнении команды CPU ( ALU — Арифметико-логическое устройство ) перейдет к START . Прыжок будет повторяться снова и снова, что приведет к бесконечному циклу. Ладно, ничего страшного…
Пункт 05 — посчитаем
Продолжим разработку кода.
Если предположить, что наш Arduino AVR работает на частоте 16 МГц (16 миллионов тактов в секунду), сколько времени все это займет?T = 1/ F, тогда T = 1/16E6
T = 0,0000000625 секунд, а не 0,06 мксДовольно быстро! Мы не можем видеть на этой частоте мигание светодиода. Итак, подходя к ответу, экспериментально было замечено, что человеческий глаз не может разглядеть разницу в кадре изображения, если она появляется менее чем за 16 мс до 13 мс (0,016-0,013 с). Следовательно, чисто исходя из этого, мы можем сказать, что частота дискретизации составляет от 60 Гц до 80 Гц (около 0,06 и 0,08 МГц).
Итак, нам нужно знать, сколько раз мы будем повторять цикл, скажем, полсекунды выключено и полсекунды включено, чтобы светодиод мигал каждые полсекунды с частотой 16 МГц, нам нужно x циклов. Вот как:если 1 цикл займет -----------0,0000000625 секунд
x циклов займет -----------0,5 секунды
это составит 0,5 / 0,0000000625 с = 8 000 0000 цикловКак добиться всех этих циклов, если я умею считать только до 255 (помните, у нас 8-битный чип)… магия? Волшебная палочка?
Расслабьтесь, с техникой, которую я представлю, все станет легко.Для этого нам придется реализовать два цикла : внутренний и внешний цикл . Посмотрите, как:
Расчеты — внутренний цикл обрабатывается как одна БОЛЬШАЯ инструкция, требующая 262145 тактовых циклов. См.: Сначала внутренний цикл — как вы знаете, регистры можно использовать парами, что позволяет работать со значениями от 0 до 65 535. Это слово .
Следующий фрагмент кода очищает регистры 24 и 25 и увеличивает их в цикле до тех пор, пока они снова не переполнятся нулем.
: Когда возникает это условие, петля больше не повторяется. Вперед!клр r24 ; clr требуется 1 цикл
clr r25 ; clr требуется 1 цикл DELAY_05: ; нам нужна задержка .5s
adiw r24, 1 ; adiw нужно 2 цикла и
brne DELAY_05 ; brne требуется 2 цикла, если переход выполнен
; и 1 в противном случаеКаждый раз, когда регистры не переполняются, цикл занимает adiw(2) + brne(2) = 4 такта.
Это делается 0xFFFF (65 535) раз, прежде чем произойдет переполнение. В следующий раз циклу потребуется всего 3 цикла, потому что переход не выполняется.
В сумме это дает 4 * 65 535 (зацикливание) + 3 (переполнение) + 2 (очищение) = 262 145 циклов. Этого все равно мало: 8 000 000/262 145 ~ 30,51.«Внешний» цикл будет выполнять обратный счет от 31 до нуля с помощью R16.
ldi r16, 31 OUTER_LOOP: ; метка внешнего контура
ldi r24, 0 ; очистить регистр 24
лди р25, 0 ; очистить регистр 25 DELAY_05 : ; метка петли
adiw r24, 1 ; «добавить непосредственно в слово»: r24:r25 равны
; увеличено
brne DELAY_05
dec r16 ; декремент r16
brne OUTER_LOOP ; нагрузка р16 с 8Всего на петлю нужно: 262 145 (внутренняя петля) + 1 (уб) + 2 (уб) = 262 148 * 31 = 8 126 588 тактов.
Это больше похоже на то, что нам нужно, но 126 588 циклов слишком долго.Здесь вступает в действие тонкая настройка — нам нужно изменить начальное значение r24:r25.
Внешний цикл выполняется 31 раз и включает в себя «инструкцию большого внутреннего цикла».
Нам нужно вычесть из внутреннего цикла с: 126 588 / 31 = 4 083 цикла на внутренний цикл.
Это то, что внутренний цикл должен быть короче. Каждая итерация внутреннего цикла занимает 4 такта (последняя занимает 3, но это не так важно), так что давайте разделим эти 4 083 на 4.
Это на 1 020,8 или на 1 021 меньше итераций.Это новое значение инициализации для r24:r25!
Теперь, если хотите, проведите все эти вычисления еще раз: Результат — 8 000 000 тактов!Теперь просто поместите это в отдельную процедуру и вызовите ее из основного цикла мигания светодиода. Вот полная программа:
.ORG 0x0000 ; следующая инструкция должна быть записана в ; адрес 0x0000 rjmp START ; вектор сброса: перейти к «главному» НАЧАЛО: ldi r16, низкий (RAMEND) ; настроить стек out SPL, r16 ldi r16, high(RAMEND) out SPH, r16 ldi r16, 0xFF ; загрузить регистр 16 с 0xFF (все биты 1) out DDRB, r16 ; записать значение в r16 (0xFF) в Data ; Регистр направления B LOOP: sbi PortB, 5 ; выключить светодиод rcall delay_05 ; подождите полсекунды cbi PortB, 5 ; включить rcall delay_05 ; подождите полсекунды rjmp LOOP ; перейти к 9 петле0252
DELAY_05: ; подпрограмма: ldi r16, 31 ; загрузить r16 с 31 OUTER_LOOP: ; метка внешнего цикла ldi r24, low(1021) ; загрузить регистры r24:r25 с 1021, наш новый ; начальное значение ldi r25, high(1021) ; метка цикла DELAY_LOOP: ; "добавить непосредственно в слово": r24:r25 are ; увеличенный adiw r24, 1 ; если нет переполнения ("ветвь, если не равно"), перейти ; вернуться к "delay_loop" brne DELAY_LOOP dec r16 ; декремент r16 brne OUTER_LOOP ; и цикл, если внешний цикл не завершен ret ; возврат из подпрограммыПункт 06 — Запустите симуляцию на Atmega 238P
Итак, давайте начнем сеанс отладки или симуляции.
Вам просто нужно щелкнуть Build > Build Solution и продолжить, нажав кнопку Start Debugging and Break (Alt + F5) в меню Debug .
Давайте просто сделаем это и посмотрим, что произойдет...Ой, ошибка? Пожалуйста, выберите подключенные инструменты и интерфейс и повторите попытку... для этого нажмите Продолжить и в Инструмент > Выбранный отладчик/программист выбрать Симулятор.
Воспользуйтесь преимуществом и перейдите к Toolchain и под General отключите Generate HEX file . Это функция безопасности. Поскольку мы здесь моделируем, мы не хотим помещать этот файл на чип, верно?
Ctrl + S и закрыть окно свойств и снова Alt + F5 .
Итак, отладчик запустился, а прерывание означает паузу в первой строке (или что хотите...). Выберите в Окно ввода/вывода ПОРТB .
Обратите внимание, что желтая стрелка останавливается прямо на нулевом адресе. Он указывает на инструкцию, которая будет выполнена следующей (нажатие Reset приводит сюда), но еще не выполнена.
Если вы хотите выполнить код построчно, вам нужно знать две основные команды: Перейти через (F10) и Перейти к (F11).
Посмотрим, как это работает.
Если я нажму F11 , произойдет переход к следующей инструкции. Программа счетчика начинает считать. Пройден машинный цикл.
В тактовой частоте Arduino это занимает всего 0,06 микросекунды.
На следующем шаге мы заметим, что регистр R16 изменит свое значение. Это будет 0xFF значение первого полубайта из указателя стека.Чтобы разобраться, лучше перейти по этой ссылке.
Эти операции позволяют нам использовать 16 бит (одно слово) для необходимого подсчета.
Примечание:
8-битный указатель стека микроконтроллера AVR может состоять либо из одного регистра ввода-вывода SPL (низкий указатель стека), либо из двух (2) регистров ввода-вывода SPL и SPH (высокий указатель стека). Размер указателя стека зависит от объема памяти данных, которую содержит микротроллер. Если вся память данных может быть адресована с помощью 8-битной адресации, то указатель стека имеет ширину 8 бит, то есть только SPL, в противном случае указатель стека состоит из SPL и SPH.
Хорошо. Теперь нам нужно настроить наш DDRB . С 0xFF все биты равны 1, как вывод.
Теперь необходимо только поочередно устанавливать PORTB вверх и вниз, используя sb i для установки бита и cbi для его очистки.Теперь мы используем rcall для перехода к циклу delay_05 , который ждет полсекунды. Для этого нажмите сейчас F10 — Step Over .
Через короткую секунду часы остановятся в цикле числа 8 миллионов согласно нашим расчетам выше.
Теперь если посчитать Секундомер будет забивать ровно полсекунды. Смотрите:
500000000 us = 0,5 секундыПотрясающе!!!
Когда вы закончите, остановите отладку, нажав кнопку Остановить отладку ( Ctrl+Shift+F5 ).
Наконец-то мы смогли превратить язык ассемблера в Привет, мир !!!.
Пункт 07 — Запуск на реальной плате — Arduino UNO
Скопируйте и вставьте этот код в целевой проект .
В нашем предыдущем видео у нас уже есть необходимые инструменты для загрузки этого кода для Arduino непосредственно из Atmel Studio 7 IDE. Просто перейдите в меню Инструменты , нажмите:Отправить в ArduinoUNOи готово! Вот видео для этой установки.
Пункт 08. Возьмите ваш проект Simulation в качестве шаблона.
Теперь давайте превратим весь проект в шаблон. Для этого используйте код симуляции.
Мы будем использовать это как заголовок блока комментариев (скопируйте и вставьте в файл main. asm моделирования):
/*====================== ===========================================
| Проект: ПРИСВОЕННЫЙ НОМЕР И НАЗВАНИЕ
|
| Автор: ИМЯ СТУДЕНТА ЗДЕСЬ
| Язык: НАЗВАНИЕ ЯЗЫКА, НА КОТОРОМ НАПИСАНА ПРОГРАММА И
| НАЗВАНИЕ КОМПИЛЯТОРА, ИСПОЛЬЗУЕМОГО ДЛЯ КОМПИЛЯЦИИ
| БЫЛ ИСПЫТАН
| Решение: ВАШЕ РЕШЕНИЕ ATMEL STUDIO 7 NAME
|
| Проекты: _##_####_Simulation_ATMEGA328P и
| _##_####_Target_ATMEGA328P
|
| Для компиляции: ОБЪЯСНИТЕ, КАК СОСТАВЛЯТЬ ЭТУ ПРОГРАММУ
|
| Программное обеспечение: НАЗВАНИЕ И НАЗВАНИЕ КЛАССА, ДЛЯ КОТОРОГО БЫЛА ЭТА ПРОГРАММА
| НАПИСАНО
| Версия ОС: НОМЕР ВАШЕЙ КОМПЬЮТЕРНОЙ ПРОГРАММЫ
|
| Платформа: ВАША КОМПЬЮТЕРНАЯ ПРОГРАММА
|
| Инструктор: ИМЯ ПРЕПОДАВАТЕЛЯ ВАШЕГО КУРСА
|
| Дата выполнения: ДАТА И ВРЕМЯ ЭТОЙ ПРОГРАММЫ 9.0234 | ОТПРАВЛЕНО
|
+ ------------------------------------------------ -----------------
|
| Описание: ОПИСАТЬ ПРОБЛЕМУ, ЧТО ЭТА ПРОГРАММА НАПИСАЛА
| РЕШАТЬ.
|
| Ввод: ОПИШИТЕ ВВОД, КОТОРЫЙ ТРЕБУЕТ ПРОГРАММА.
|
| Вывод: ОПИШИТЕ ВЫВОД, КОТОРЫЙ ПРОИЗВОДИТ ПРОГРАММА.
|
| Алгоритм: ОПИСАНИЕ ПОДХОДА, ИСПОЛЬЗУЕМОГО ПРОГРАММОЙ ДЛЯ РЕШЕНИЯ ЗАДАЧИ
| ПРОБЛЕМА.
|
| Необходимые функции не включены: ОПИШИТЕ ЗДЕСЬ ЛЮБЫЕ ТРЕБОВАНИЯ ИЗ
| ЗАДАНИЕ, КОТОРОЕ ПРОГРАММА НЕ ПЫТАЕТСЯ РЕШИТЬ.
|
| Известные ошибки: ЕСЛИ ПРОГРАММА НЕ РАБОТАЕТ КОРРЕКТНО В НЕКОТОРЫХ
| СИТУАЦИИ, ОПИСАТЬ СИТУАЦИИ И ПРОБЛЕМЫ ЗДЕСЬ.
|
*=============================================== ================*/ .INCLUDE "m328pdef.inc" ; это автоматически включается
.ORG 0x0000 ; начальная инструкция
rjmp START ; вектор сброса: перейти к «главному» ;***поместите сюда свои данные*** 9. ВЫХОД ; сообщает ассемблеру, что все кончено!Ctrl+s . Теперь нажмите File > Export Template…
Выберите Template Type , нажмите Project Template .
Нажмите Далее .
Выберите имя шаблона (simulae_template_328P ) , Описание шаблона, t введите небольшой текст, чтобы напомнить вам позже (шаблон моделирования/целевого проекта для Arduino Uno) и Выходное местоположение (новый каталог). Оставьте все в настройках по умолчанию.
В итоге вы получите файл в формате .zip, в моем случае C:\Users\giljr\Documents\Visual Studio 2015\My Exported Templates.Повторите ту же процедуру для целевого проекта. Назовите его target_template_328P.
Круто!!! перезагрузите Atmel Studio 7 IDE и перейдите File> New> Project n бум!!! Ну вот!!!
Пункт 09. Возьмите целевой проект в качестве шаблона.
Повторите ту же процедуру для целевого проекта.
Пункт 10 — Подготовка следующего проекта
Файл > Импорт > Шаблон проекта и выберите ZIP-файл интересующего вас проекта.
Сохранить как ‘ Solution_AVR2 ’ и ‘ _28_arduserie_Simulation_ATMEGA328P » и « _28_arduserie_Target_ATMEGA328P »
Для следующего проекта давайте поработаем с « Таймеры и счетчики ».
Погружаемся все глубже и глубже внутрь платы Arduino!!!
Но это вопросы для следующего видео…спасибо за ваше время!!! пока!!!
Кстати, некоторые важные понятия:
Что такое сборка?
Ассемблер — это язык низкого уровня. Язык ассемблера (или ассемблера), часто обозначаемый аббревиатурой asm , представляет собой язык программирования низкого уровня для компьютера или другого программируемого устройства, в котором существует очень сильное (как правило, взаимно однозначное) соответствие между языком и инструкции машинного кода архитектуры. Язык ассемблера преобразуется в исполняемый машинный код служебной программой, называемой ассемблером (Википедия).Что такое стек?
Стек — это последовательный блок памяти данных, выделенный программистом.
Этот блок памяти может использоваться как внутренним управлением микроконтроллера, так и программатором для временного хранения данных. Стек работает по принципу «последним пришел — первым вышел» ( LIFO ), т. е. то, что хранится в стеке последним, извлекается из стека первым.Что такое указатель стека?
Указатель стека — это, по сути, регистр или регистры, которые содержат либо «адрес памяти последнего места в этом стеке, где были сохранены данные», либо «адрес памяти следующего доступного места в стеке для хранения данных». Определение указателя стека зависит от конструкции микроконтроллера. В микроконтроллерах AVR, таких как ATMega8515, ATMega16, ATTiny13 и т. д., указатель стека содержит адрес следующего доступного места в стеке, доступного для хранения данных.
8-битный указатель стека микроконтроллера AVR может состоять из одного регистра ввода-вывода SPL (указатель стека, низкий) или двух (2) регистров ввода-вывода SPL и SPH (указатель стека, высокий). Размер указателя стека зависит от объема памяти данных, которую содержит микроконтроллер. Если вся память данных может быть адресована с помощью 8-битной адресации, то указатель стека имеет ширину 8 бит, то есть только SPL, в противном случае указатель стека состоит из SPL и SPH.Что, черт возьми, означает РАМЕНД?
RAM END — это метка, представляющая адрес последней ячейки памяти в SRAM. Чтобы использовать эту метку, вы ДОЛЖНЫ убедиться, что вы включили файл заголовка определения для конкретного микроконтроллера. Функции low() и high() используются ассемблером для возврата соответственно младшего и старшего байтов 16-битного слова. Помните, что мы имеем дело с 8-битным микроконтроллером, который может одновременно обрабатывать только 8 бит. RAMEND — это 16-битное слово, поэтому мы используем функции для его разделения.Загрузить все архивы проектов
Ссылки:
Лекция 1: Использование, настройка и моделирование с помощью Atmel Studio 7 (Спасибо мистеру Сантосу за отличный урок!!! — Хотел бы я посмотреть ваши занятия вживую!!!)
Стек микроконтроллера AVR и указатель стека
Введение в ассемблер и встроенное «Hello W world»!!!!
Шаблоны комментариев к блокам с примерами
Порты ввода-вывода
Организация памяти AVR
Раздел Mikrchip 8.