Как сделать треугольник в ассемблере

Обновлено: 07.07.2024

Часто бывает так, что программа, успешно пройдя этапы трансляции и компоновки, все же работает не так, как ожидалось или вообще не работает. Это значит, что формально, с точки зрения правил языка программирования, программа написана правильно (в ней нет синтаксических ошибок), однако алгоритм ее в чем-то неверен. Для отладки такой программы следует воспользоваться услугами интерактивного отладчика. Интерактивным он называется потому, что вся работа с ним осуществляется в непрерывном диалоге с пользователем.

Познакомимся с отладчиком TD.EXE из пакета TASM, воспользовавшись программой из первого примера.

Как уже отмечалось, для полного использования возможностей отладчика следует при трансляции программы указать в числе других ключ /zi, а при компоновке - ключ /v:

Кроме того, следует убедиться, что в вашем рабочем каталоге имеется и загрузочный (Р.ЕХЕ) и исходный (P.ASM) файл, поскольку отладчик в своей работе использует оба этих файла. Вызовем отладчик командой

На экране появится кадр отладчика, в котором видны два окна - окно Module с исходным текстом отлаживаемой программы и окно Watches для наблюдения за ходом изменения заданных переменных в процессе выполнения программы (см.рис.).


Окно Watches нам не понадобится, и его можно убрать, щелкнув мышью по маленькому квадратику в левом верхнем углу окна или введя команду Alt+F3, предварительно сделав это окно активным. Переключение (по кругу) между окнами осуществляется клавишей F6.

Верхняя строка кадра отладчика представляет собой главное меню. Для перехода в меню необходимо нажать клавишу Alt и первую (выделенную цветом) букву требуемого пункта. Для выбора затем конкретного действия надо с помощью клавиш со стрелками вверх и вниз- выделить нужный пункт и нажать клавишу Enter.

В нижней строке отладчика приведены его основные команды, вызов которых осуществляется нажатием на функциональные клавиши F1. F10. В действительности команд гораздо больше; некоторые из них можно реализовать только с помощью пунктов главного меню, другие вызываются комбинациями функциональных и управляющих (Alt, Ctrl или Shift) клавиш.

Рассмотрим типичные действия, к которым приходится прибегать по ходу отладки программы.

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

Нажав одну из клавиш F8 или F7, мы выполним одно предложение программы. Различие этих команд весьма существенно: команда F7 (trace, трассировка) позволяет войти внутрь вызываемых подпрограмм, а также выполнять циклы шаг за шагом. Команда F8 (step, шаг), наоборот, выполняет подпрограммы и циклы как одно неразрывное действие, что заметно ускоряет пошаговую отладку программы, если мы, например, уверены, что вызываемая подпрограмма выполняется правильно.

Можно выполнить сразу и целый фрагмент программы, т. е. несколько предложений. Для этого надо поместить курсор _ перед тем предложением, на котором требуется сделать остановку (или на любой символ внутри него), и нажать клавишу F4 (here, сюда). Выполнятся все строки программы до той, на которой установлен курсор; значок сплошной треугольник переместится на эту строку. Далее можно опять выполнять программу построчно, нажимая на клавишу F8, или, установив в требуемом месте курсор, выполнить следующий фрагмент, нажав F4.

Для повторного выполнения программы ее следует "рестартовать". Для этого надо выбрать в главном меню пункт Run-Program reset или просто нажать Ctrl+F2.

Важнейшим элементом отладки программы является наблюдение значений тех или иных полей данных, особенно тех, которые заполняются программой динамически, т. е. по ходу ее выполнения. Для того чтобы вывести на экран содержимое поля данных, надо поместить курсор на имя этого поля (например, mesg в нашем примере) и выбрать пункт меню Data-Inspect. В появившемся окне ввода переменной (см.рис.) можно скорректировать имя интересующего нас поля данных или ввести новое; если имя правильное, достаточно нажать клавишу Enter.


В кадр отладчика будет выведено окно с характеристиками и содержимым указанной переменной (см.рис.). Отладчик сообщает, что переменная mesg хранится в памяти по адресу 5D82:000, т. е. имеет сегментный адрес 5D82h и смещение OOOOh, и описана как последовательность из 12 байт. Тут же приводятся значения всех байтов нашей строки, включая их начертание на экране, а также десятичное и 16-ричное представление.


В окне Inspecting можно изменить значение отображаемого поля данных. Для этого надо, сделав это окно активным и поместив курсор на отображение конкретного элемента нашего символьного массива, например элемента с индексом 12 (знак "!"), ввести команду Alt+F10. Эта команда для любого активного окна открывает его внутреннее меню с дополнительными возможностями. В данном случае внутреннее меню будет иметь вид, показанный на нижнем рисунке.


Нас будет интересовать пункт Change (изменение). Выбрав этот пункт, мы получим окно, в котором можно ввести требуемое значение изменяемого данного. На следующем рисунке показано это окно с введенным символом '>', которым будет заменен восклицательный знак. Можно было вместо символа в одинарных кавычках ввести его 16-ричный код ASCII, если он известен (число ЗЕ для знака >). Допустим ввод и десятичного кода, если завершить его буквой d F2d).


Если теперь, не выходя из отладчика, выполнить программу до конца, на экран вместо ! будет выведен >.

Для того чтобы, находясь в отладчике, увидеть результат работы программы, надо ввести команду Alt+F5 (или выбрать пункт Window-User screen главного меню). Возврат в кадр отладчика осуществляется нажатием любой клавиши.

Необходимо отдавать себе отчет в том, что любые изменения, вносимые в текст программы в процессе работы с отладчиком, будут действовать только до конца данного сеанса (даже, точнее говоря, до рестарта программы). Отладчик изменяет не файл Р.ЕХЕ, хранящийся на диске, а только его копию в памяти. После завершения сеанса работы с отладчиком все, что было загружено в память, исчезает вместе с внесенными нами изменениями.

Начальное окно отладчика дает недостаточно информации для серьезной работы с программой. При отладке программы на уровне языка ассемблера необходимо контролировать все регистры процессора, включая регистр флагов, а также во многих случаях поля данных вне программы (например, векторы прерываний или системные таблицы). Для этого надо командой Alt+V, С (пункт главного меню View-CPU) открыть "окно процессора" (см.рис.).


Окно процессора состоит, в свою очередь, из пяти внутренних окон для наблюдения текста программы на языке ассемблера и в машинных кодах, регистров процессора, флагов, стека и содержимого памяти. С помощью этих окон можно полностью контролировать работу процессора при выполнении отлаживаемой программы. Для того чтобы можно было работать с конкретным окном, надо сделать его активным, щелкнув по нему мышью. Переходить из окна в окно можно также (по кругу), нажимая клавишу Tab. Для управления ходом программы используются функциональные клавиши, перечисленные в нижней строке кадра (F7 или F8 для пошагового выполнения, F4 для выполнения до курсора и т. д.). Курсор во всех внутренних окнах окна процессора выглядит в виде синей ленточки. Добавим еще, что, щелкнув мышью по значку стрелки вверх в правом верхнем углу окна процессора, можно увеличить это окно до размеров кадра отладчика.

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

Выполните программу до первой команды int 21h (предложение 7) и просмотрите содержимое регистров процессора. Вы увидите, что в старшей половине регистра АХ находится число 09h - номер вызываемой функции DOS. Младшая половина регистра АХ заполнена "мусором" - остатком от выполнения последней операции с регистром АХ. В регистре DX будет OOOOh - относительный адрес первого байта строки mesg в сегменте команд. Изменим этот относительный адрес. Для этого надо перейти в окно регистров, поместить курсор на строку, отображающую содержимое регистра DX, и ввести команду Alt+F10, открывающую внутреннее меню окна регистров (см.рис.).


Как видно из рисунка, меню окна регистров предоставляет возможность выполнить увеличение содержимого регистра на 1 (Increment), уменьшить его на 1 (Decrement), обнулить (Zero) и заменить на любое заданное значение (Change). Выбрав пункт Change, занесем в регистр DX число 5 (см.рис.).


Теперь, если выполнить очередную команду (int 21h), DOS выведет на экран строку, начало которой расположено в байте 5 сегмента данных. В нашей фразе "Hello, world!" байт 5 приходится на запятую (нумерация байтов в строке, естественно, начинается с нуля). В результате на экран будет выведена строка

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

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

Окружение представляет собой область памяти, в которой в виде символьных строк записаны значения переменных, называемых переменными окружения, например

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

Пользователь может включить в окружение строки определения дополнительных переменных с помощью команды SET. Часто в качестве значений таких переменных указываются пути к каталогам со вспомогательными файлами или ключи, задающие режим работы различных программ.

Введение в C / C ++ и программирование на ассемблере

1. Введение

Когда требуется смешанное программирование на C / C ++ и ассемблере, доступны две следующие стратегии обработки:

  • Если код сборки короткий, язык ассемблера может быть непосредственно встроен в исходный файл C / C ++ для достижения смешанного программирования.
  • Если код сборки длиннее, его можно записать как файл сборки отдельно и, наконец, добавить в проект в виде файла сборки.ATPCSОбязательно звонить и навещать друг друга с программами C.

2. Инструкции на встроенном ассемблере.

Встраивание программ C / C ++ в программы на ассемблере позволяет реализовать некоторые функции, недоступные в языках высокого уровня, и повысить эффективность выполнения программ. Встроенный ассемблер компилятора armcc поддерживает набор инструкций ARM, а встроенный ассемблер компилятора tcc поддерживает набор инструкций Thumb.

2.1 Формат синтаксиса встроенных инструкций по сборке

Вы можете использовать ключевое слово __asm ​​для добавления программы на ассемблере в программу ARM на языке C. Формат следующий:


Среди них инструкции в <> - это все инструкции сборки.Множественные операторы инструкций сборки могут быть записаны в одной строке, и операторы инструкций должны быть разделены точкой с запятой. В разделе инструкций по сборке оператор комментария принимает формат комментария языка C. В программе ARM C ++, помимо ключевого слова __asm ​​для идентификации раздела встроенной программы инструкции сборки, ключевое слово asm также может использоваться для представления раздела встроенной инструкции сборки, формат следующий:
asm ("Команда");
Среди них круглые скобки после asm должны быть оператором инструкции сборки и не могут содержать операторы комментариев.

2.2 Примеры включения / отключения прерываний IRQ

2.3 Меры предосторожности при встроенной сборке

Суффикс. Инструкции по сборке в файле S: ассемблер armasm Для сборки используются встроенные инструкции сборки в программе на языке C. Встроенный ассемблер Скомпилировано. Между этими двумя ассемблерами есть определенные различия, поэтому при встроенной сборке обратите внимание на следующие моменты:

2.3.1 Осторожно используйте физические регистры

Вы должны осторожно использовать физические регистры, такие как R0 ~ R3, IP (R12), LR (R14) и флаги N, Z, C и V в CPSR. Поскольку при вычислении выражений C в ассемблерном коде эти физические регистры могут использоваться, а биты флагов N, Z, C и V будут изменены.
При вычислении y = x + x / y;

2.3.2 Переменные C разрешены во встроенном ассемблере

R0 будет изменен при вычислении x / y, что повлияет на результат R0 + x / y. Переменные C разрешены во встроенном ассемблере, и вышеупомянутые проблемы могут быть решены путем замены регистра R0 переменными C. В это время встроенный ассемблер выделит подходящую единицу хранения для переменной var, чтобы избежать конфликтов. Если встроенный ассемблер не может выделить подходящую единицу хранения, он сообщит об ошибке.

2.3.3 Нет необходимости сохранять и восстанавливать использованные регистры

Для регистров, используемых во встроенной программе на ассемблере, Компилятор автоматически сохранит и восстановит эти регистры при компиляции. , Пользователю не нужно сохранять и восстанавливать эти регистры. За исключением регистров CPSR и SPSR, другим физическим регистрам должны быть присвоены значения перед чтением, иначе компилятор сообщит об ошибке.

3. Взаимный доступ к переменным ассемблерных и C / C ++ программ.

3.1 Ассемблер обращается к программным переменным C / C ++

Ассемблер может получить доступ к глобальным переменным, объявленным в программе C / C ++, через адрес . Конкретный метод / шаги доступа следующие:
1) Объявите глобальные переменные в программах на C / C ++.
2 ) Используйте директивы IMPORT / EXTERN в ассемблере для объявления и ссылки на глобальную переменную.
3 ) Используйте псевдо-инструкцию LDR, чтобы прочитать адрес памяти глобальной переменной.
4 ) В зависимости от типа данных используйте соответствующую инструкцию LDR для чтения глобальной переменной; используйте соответствующую инструкцию STR для сохранения значения глобальной переменной. Для разных типов переменных требуются инструкции LDR и STR с разными параметрами, как показано в следующей таблице.

Типы переменных на языке C / C ++
Суффиксные инструкции LDR и STR
описание
unsigned char
LDRB/STRB
Беззнаковый символ
unsigned short
LDRH/STRH
Беззнаковый короткий
unsigned int
LDR/STR
Беззнаковое целое
char
LDRSB/STRSB
Тип символа (8 бит)
short
LDRSH/STRSH
Короткое целое число (16 бит)

Что касается структуры, если вы знаете смещение каждого элемента данных, вы можете получить к нему доступ с помощью инструкций сохранения / загрузки. Если пространство, занимаемое структурой, меньше 8 слов, LDM и STM могут использоваться для одновременного чтения и записи.

Прочтите глобальную переменную C, измените ее, а затем сохраните новое значение в глобальной переменной:

3.2 Программа на C / C ++ обращается к данным ассемблера

К данным, объявленным в ассемблере, могут обращаться программы C / C ++. Конкретный метод / шаги доступа следующие:
1) Используйте псевдо-инструкцию EXPORT / GLOBAL в ассемблере, чтобы объявить, что символ является глобальной меткой, которая может использоваться другими файлами.
2) Определите переменные-указатели соответствующих типов данных в программах на C / C ++.
3) Назначьте переменную указателя глобальной метке в ассемблере и используйте указатель для доступа к данным в ассемблере.

Предполагая, что область памяти определена в ассемблере и хранится строка символов, ассемблерный код выглядит следующим образом:

4. Взаимные вызовы функций ассемблера и программ на C / C ++.

Взаимный вызов между программой C / C ++ и ассемблером ARM должен соответствовать правилам ATPCS (стандарт вызова процедур ARM / Thumb). Подпрограмма языка C, скомпилированная с помощью компилятора языка C ADS, автоматически соответствует типу ATPCS, указанному пользователем. Для языка ассемблера это полностью зависит от пользователя, чтобы гарантировать, что каждая подпрограмма соответствует выбранному типу ATPCS. В частности, ассемблер должен удовлетворять следующим трем условиям, чтобы реализовать взаимный вызов с языком C.
1) Соблюдайте соответствующие правила ATPCS при написании подпрограмм.
2) Использование стека должно соответствовать соответствующим правилам ATPCS.
3) Используйте параметр -atpcs в компиляторе сборки.

4.1 Основные правила ATPCS

Ознакомьтесь с основными правилами ATPCSATPCS。

4.2 C программа вызывает ассемблер

Настройка ассемблера должна соответствовать правилам ATPCS, чтобы гарантировать правильную передачу параметров при вызове программы.В этом случае программа на C может вызывать подфункцию сборки. Метод вызова ассемблера программы C следующий:
1) Псевдо-инструкция EXPORT используется в ассемблере, чтобы объявить, что эта подпрограмма может использоваться извне, чтобы другие программы могли вызывать подпрограмму.
2) Используйте ключевое слово extern, чтобы объявить внешнюю функцию (объявить вызываемую подпрограмму сборки) в программе на языке C, после чего эту подпрограмму сборки можно будет вызвать.


Код реализации strcopy выглядит следующим образом:

4.3 Ассемблер вызывает программу на языке C

Настройки ассемблера должны соответствовать правилам APTCS, чтобы обеспечить правильную передачу параметров при вызове программы. Метод вызова программы на языке C на ассемблере следующий:
1) Используйте директиву IMPORT в программе сборки, чтобы объявить вызываемую функцию программы C.
2) При вызове программы C установите параметры входа правильно, а затем используйте инструкцию BL для вызова.

Вышеупомянутая программа использует 5 параметров, каждый из которых использует регистр R0 для хранения первого параметра, R1 для хранения второго параметра, R2 для хранения третьего параметра, R3 для хранения четвертого параметра и пятый параметр для передачи стеком. Из-за использования стека для передачи параметров указатель стека должен быть скорректирован после завершения вызова программы. Подфункция суммы программы на языке C вызывается в ассемблере для реализации 1 + 2 + 3 + 4 + 5, а окончательный результат сложения сохраняется в регистре R0.

В прошлый раз я поведал о том, что все действия, которые выполняет контроллер, определяются так называемыми регистрами, и даже рассказал о трех из них: PORTB, DDRB, PINB.

На самом деле этих регистров намного больше. Кроме того, существует две их основные разновидности. Рассмотренные ранее относятся к так называемым регистрам ввода-вывода (РВВ). Этот тип регистров определяет функционирование различных узлов и модулей контроллера: портов ввода/вывода, таймеров, прерываний, энергонезависимой памяти, АЦП, компаратора, цифровых интерфейсов и др. Для каждого из этих модулей определено по одному, а чаще по несколько РВВ. По мере изложения материала я буду рассказывать о них подробнее. Сегодня же нам понадобятся только уже известные нам по прошлому разу РВВ.

Кроме РВВ, количество и состав которых отличается у разных микроконтроллеров, имеются еще так называемые регистры общего назначения (РОН). Все микроконтроллеры AVR имеют по 32 РОН, которые называются очень просто: r0, r1, r2, . r31. Забегая вперед, скажу, что не все эти регистры равнозначны, но большей части команд нет разницы, какие из них задействовать. Следует уяснить и запомнить, что большинство операций в контроллере выполняется именно над РОН. Фактически, рассмотренные нами в прошлый раз команды составляют большую часть доступных команд для работы непосредственно с РВВ. Все РОН представляют собой однобайтовые регистры, к которым в ассемблере можно обращаться, указывая их имя.

Но это все слова. Перейдем теперь к практике. Напишем программу, которая будет самостоятельно мигать светодиодом LED2 с определенной частотой. Создаем новую папку с именем step3 и изменяем файл build.bat для ассемблирования из этой папки. Открываем ASM_Ed и пишем в нем следующий текст:

.include "F:\Prog\AVR\asm\Appnotes\tn13def.inc"
sbi DDRB, 4 ;Линия РВ4 - выход
main: ;Основной цикл программы
sbic PINB, 4 ;Если РВ4=0 (светодиод зажжен), то пропустить след. строку
cbi PORTB,4 ;Установка РВ4 в 0 (включение светодиода)
sbis PINB, 4 ;Если РВ4=1 (светодиод погашен), то пропустить след. строку
sbi PORTB,4 ;Установка РВ4 в 1 (выключение светодиода)
ldi r16, 255 ;Загрузка значения в регистр r16
ldi r17, 255 ;Загрузка значения в регистр r17
delay: ;Цикл задержки
subi r16, 1 ;Вычитание 1 из регистра r16
sbci r17, 0 ;Вычитание с переносом из регистра r17
brcc delay ;Если не было переноса вернуться к метке delay
rjmp main ;Вернуться к метке main

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

Можно увидеть, что помимо команд и комментариев, в программе появились метки. Это main в 3 строке и delay в 11 строке. Их можно узнать по двоеточию после них. Метки не занимают памяти контроллера, а лишь служат некоторым другим командам для навигации по программе.

Теперь о новых командах.

Команда ldi имеет два операнда: первый - РОН, а второй - любая численная константа в диапазоне от 0 до 255 (поскольку все РОН однобайтовые, то в них можно записывать только числа в указанном диапазоне, при попытке записать большее число, транслятор выдаст предупреждение). В результате выполнения этой команды в указанный РОН загружается указанная константа. То есть если провести аналогию с языками высокого уровня, эта команда сходна с операцией присвоения (РОН = k).

Команда subi по синтаксису аналогична вышеприведенной. В результате ее выполнения из указанного РОН вычитается указанная константа, а результат заносится в тот же регистр (РОН = РОН - k).

Команда sbci также аналогична по синтаксису, и сходна по функциональному назначению с предыдущей. Она также вычитает из указанного РОН указанную константу, но с учетом переноса. Что это означает? По сути практически любая логическая либо математическая операция устанавливает либо сбрасывает те или иные биты в так называемом статусном регистре SREG. Доступ к нему имеется только с использованием специальных ассемблерных команд. Обо всех битах я напишу в последующих статьях, пока же скажу, что есть в нем так называемый бит переноса С. Этот бит устанавливается в "1", если в результате предыдущей операции произошло переполнение в старший разряд при сложении либо заем из старшего разряда при вычитании (то есть при попытке прибавить число к 255 либо вычесть число из 0). Итак, команда sbic вычитает из РОН указанную константу и бит переноса и записывает результат в тот же РОН (РОН = РОН - k - C).

Команда brcc относится к командам условного перехода. Единственным ее аргументом является метка, на которую перемещается программный счетчик, если в результате выполнения предыдущей операции не произошло переноса, то есть, если был сброшен бит С статусного регистра. Если же перенос произошел, выполняется следующая за ней команда (Если С=0, то перейти к метке, иначе перейти к следующей команде).

Команда rjmp является командой безусловного перехода. Ее единственным аргументом также является метка, на который перемещается программный счетчик, однако перемещение выполняется всегда, без каких-либо условий. Таким образом, следующая за rjmp команда никогда не выполнится, если там не будет установлена другая метка, на которую будет совершен переход при помощи какой-то иной команды перехода.

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

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

1 строка. Подключение файла tn13def.inc к нашему ассемблерному файлу.

2 строка. Запись "1" в 4-й бит регистра DDRB для перевода линии РВ4 на выход

3 строка. Метка "main". В принципе, эту метку можно было бы и не ставить, но ее использование является правилом хорошего тона, потому что незачем каждый раз выполнять инициализацию портов ввода-вывода. Пока у нас она состоит всего из одной команды - это не так страшно, но ведь в реальных программах это десятки команд, и некоторые из них сбрасывают используемые модули к исходному состоянию. А зачем нам оно надо? Поэтому возьмите себе за правило. Сначала - инициализация, затем - зацикливание программы за пределами раздела инициализации.

4 и 5 строки. Если бит 4 в регистре PINB сброшен (то есть светодиод погашен, команда sbic), то переходим к строке 6, а иначе сбрасываем этот бит в регистре PORTB (команда cbi).

6 и 7 строки. Если бит 4 в регистре PINB установлен (то есть светодиод зажжен, команда sbis), то переходим к строке 8, а иначе устанавливаем этот бит в регистре PORTB (команда sbi).

Таким образом, строки 4 - 7 выполняют самостоятельное переключение светодиода (проверяется состояние светодиода и меняется на противоположное). Если так и оставить программу, то окажется, что это переключение будет происходить с очень высокой скоростью, и глаз его не увидит, разве что заметит, что яркость светодиода стала меньше. Чтобы заставить светодиод мигать с приемлемой частотой, необходимо осуществить задержку выполнения программы. Обычно это делается путем многократного вычитания либо сложения. Поскольку каждая операция требует определенного времени, то выполнение однотипных команд несколько тысяч раз подряд как раз и обеспечит нам требуемую задержку. Рассмотрим теперь, как это реализовано у нас.

Строка 8. При помощи команды ldi в РОН r16 загружается максимально возможное число 255. Почему именно r16 а не r0? Дело в том, что команды, имеющие вторым операндом константу, не работают с регистрами r0-r15. При попытке указать в качестве первого операнда такой регистр транслятор выдаст ошибку и откажется работать далее.

Строка 9. Аналогична строке 8, только теперь загружается 255 в регистр r17. От значений, загруженных в эти регистры, будет зависеть наша задержка.

Строка 10. Метка "delay". Это начало цикла вычитания. Мы загрузили в два регистра числа 255. Теперь наша задача вычитать из них единцы, пока оба не станут равными 0. Когда это произойдет, мы сможем покинуть этот цикл.

Строка 11. Командой subi мы вычитаем единицу из регистра r16. Вычитание производится просто, без всяких условий и переносов.

Строка 12. На первый взгляд она кажется бессмысленной. Мы вычитаем из регистра r17 нуль. Однако, не стоит забывать, что команда sbci вычитает число с учетом переноса. Как это все работает в комплексе, я расскажу далее.

Строка 13. Проверка бита переноса С. Поскольку эта команда идет следом за командой вычитания из регистра r17, то проверяется перенос именно из этого регистра. Пока содержимое регистра r17 будет больше либо равно 0, команда brcc будет отправлять нас к метке delay. Как только произойдет попытка вычитания из нуля в r17, мы сразу же переходим к строке 14.

Теперь рассмотрим работу строк 10-13 в комплексе. Сначала мы вычитаем 1 из регистра r16. В следующей строке вычитаем 0 с учетом переноса из регистра r17. Пока содержимое регистра r16 больше или равно 0, содержимое регистра r17 не изменится. При вычитании единицы из нуля в регистре r16 происходят следующие события: в самом r16 устанавливается значение 255, кроме того, устанавливается флаг переноса С. Теперь при выполнении команды sbci из r17 вычтется 1. Таким образом, вычитание единицы из r17 происходит только тогда, когда в регистре r16 происходит попытка вычесть единицу из нуля. Когда же в регистре r17 произойдет вычитание единицы из нуля, также установится флаг переноса, который, будучи отловленным командой brcc, завершит цикл.

За счет такой нехитрой конструкции получается, что весь цикл будет выполняться 256 х 256 = 65536 раз. Если учесть, что каждая команда выполняется один такт контроллера, и на переход к началу цикла тратится еще один такт, то каждый проход цикла выполняется за четыре такта. Таким образом имеем задержку в 4 х 65536 = 262144 тактов. Если учесть, что частота контроллера по умолчанию составляет 1 Мгц, то задержка будет равна 262144/1000000 = 0,26 с. Таким образом, светодиод будет менять свое состояние каждую четверть секунды, что даст нам частоту мигания приблизительно равную 2 Гц.

Ну и наконец строка 14, о которой мы уже было позабыли. В ней происходит безусловный переход к метке main, таким образом мы исключаем повторную инициализацию вывода РВ4.

Вот таким образом работает написанная нами программа. Замечу, что задавая различные значения, записываемые в регистры r16 - r17 можно получать разные задержки, меньшие 0,26, используя вышеизложенные расчеты. А как же быть, если необходимо сделать большую задержку? Тогда нужно добавить еще один регистр, например r18. И реализация задержки станет выглядеть так:

ldi r16, 255 ;Загрузка значения в регистр r16
ldi r17, 255 ;Загрузка значения в регистр r17
ldi r18, 2 ;Загрузка значения в регистр r18
delay: ;Цикл задержки
subi r16, 1 ;Вычитание 1 из регистра r16
sbci r17, 0 ;Вычитание с переносом из регистра r17
sbci r18, 0 ;Вычитание с переносом из регистра r18
brcc delay ;Если не было переноса вернуться к метке delay

Общее число проходов цикла теперь станет равно 256х256х3 = 196608, а с учетом того, что в цикле появилась еще одна команда, теперь он будет выполняться за 5 тактов. Общее число тактов задержки составит 196608х5=983040, а период задержки: 983040/1000000 = 0,98 с.

Возможно, внимательный читатель спросит, а почему же мы загружаем 255 и 2, а умножаем 256 и 3. Тут все дело в том, что счет введется не с 1, как мы привыкли, а с 0, и за счет этого происходит один лишний проход цикла. Вот так просто это все объясняется.

Надеюсь, все мои рассуждения и пояснения понятны читателю. На этом я считаю, что шаг 3 можно считать сделанным. Мы узнали новые команды, ознакомились с разными видами регистров контроллера.

В качестве самостоятельно работы предлагаю следующую задачу: организовать поочередное переключение светодиодов LED1 и LED2 с частотой, максимально близкой к 0,5 Гц, то есть с периодом в 2 с.

Если у вас возникнут вопросы, задавайте их на форуме или здесь в виде комментариев к статье.

За время программирования чипов AVR, нарыл я разных математических подпрограмм для этих чипов. Может кому пригодятся. Что мне жалко этого добра? Пущай народ чесной пользуется. Если у кого то есть что то еще, то можно добавить это в статью.

Комментарии ( 45 )

Да, это, конечно, круто, но лично я вряд ли возьмусь за ассемблер АВР, хотя если бы были комментарии, кое-что, возможно, использовал бы в пиковском ассемблере, например bin2bcd. Хотя можно и в этом разобраться при желании.

Логическое ударение на ассемблер или на АВР? Конечно лучше бы платформо-независимые алгоритмы, чем конкретная реализация.

Я когда начинал изучать эти чипы, начинал с асма. На самом деле там многие команды дублирующие и вообще никогда ненужны.

Всего 35 команд? Это же ужас как неудобно. Все приходится через задницу делать. Чем больше команд тем проще!

Команд или мнемоник? Тут есть нюанс… У Z80 было под 700 команд. Включая такие которые одной мнемоникой могли целый блок памяти скопировать. Это было круто! У пика же все очень и очень куцо. Даже сложения вроде бы нету.

По моему, таки команд. Я про x86. Если про MSP430 — у него 27 команд и 53 (или около того) мнемоники.

В обоих вариантов есть плюсы и минусы. Для программирования удобнее всего трехадресные системы команд с ортогональной адресацией (когда любой из операндов и результат можно достать/положить любым доступным способом адресации). Эти же наборы команд сложнее всего реализовать в железе и параллельно исполнять. И так же трудно добиться малого потребления. Малое число простых команд, отсутствие заумных методов адресации + (относительно) большой регистровый блок — другая альтернатива. Скорость и малое потребление достигаются относительно легко, малыми затратами железа, но писать становится, мягко говоря, совсем не просто. Неудобство писания на асме решается переходом на более высокоуровневые языки, а вот скорость и потребление так в лоб не решаются. Потому, вобщем, и ушли от сложных систем команд. Впрочем, можно сломать сразу все, в чем легко убедиться посмотрев на х86 — горбатая и при этом сложная система команд, неудобная ни для писания на асме ни для оптимизации ЯВУ. При этом сложная чисто аппаратно и плохо поддающаяся оптимизации под малое потребление.

Это если не знать ничего другого. За сегментную адресацию и привязку некоторых команд к регистрам (которых мало) всегда хотелось стукнуть разработчиков чем-нибудь тяжелым.

Ну почему же ничего другого. Я штук шесть архитектур относительно свободно знаю.

А по х86… не любишь сегменты? Протект мод и флат режим к твоим услугам :) Хотя можно и без ПМ в флэт режиме работать. У Зубкова пример был. Опять же до 64к можно было в COM прекрасно уложиться и в одном сегменте. Вот узкое регистровое горло это да, порой вымораживает. Зато самих комбинаций регистров навалом. Хоть целый, хоть побайтно, в любой комбинации. Куча разных косвенных индексаций, возможность делать паровозы из адресов и смещений. Вообще удобно было с памятью работать, в отличии от той же load-Store. А еще сопроцессор и mmx добавляют лулзов. Хотя я их особо глубоко не копал. Так пару раз для вычисления одной шняги делал, когда писал на асме курсач по МПС (там надо было сэмулировать систему управления из движка, нагрузки и двух обратных связей по моменту и оборотам с ПИД регулятором). Лохов, наш препод по МПС, жог напалмом в заданиях.

Эти все режимы в подметки не годились возможностям PDP-11 или, скажем, Motorola 68000. Замечу, М68К и i86 — одногодки, объяснить кривость x86 тем, что они, типа, первопроходцы, не получается.

Вообще есть расширенный набор команд, используется в пик18 и в новых сериях пик16

Читайте также: