Вы находитесь на странице: 1из 64

Предисловие

Приветствую всех любителей, специалистов и просто людей интересующихся


микропроцессорной техникой!

За последние годы у меня скопилось большое количество самых разных


наработок по программированию микроконтроллеров AVR. Немного сгруппировав
уже готовый материал, я решил оформить его в виде одной цельной книги. В
самом начале я не знал, что у меня получится, но теперь, последний раз
просматривая материал, решил, что ее формат - это учебник/справочник по
программированию на языке ассемблера в соотношении примерно 40/60 %. В
книге освещено множество различных аспектов программирования и, так или
иначе, затронуты все без исключения внутренние модули AVR. Но самое
главное это то, что подавляющее большинство программ позаимствованы из
реально работающих и проверенных временем программ.

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


легко. Странно, но только теперь, когда пришло время написать
предисловие, я не знаю что сказать… В доступной литературе про AVR
написано очень много и мне не хотелось бы повторятся. Вопрос прямого
сравнения микроконтроллеров различных типов я (не без усилий воли) также
хочу обойти стороной. Тогда что же остается? Думаю объяснить, а почему же
именно ассемблер?

Ведь всем понятно, что основным инструментом современного разработчика


является, конечно, “Си”. Тем более, когда имеется в виду большой объем
памяти и, тем более, если речь идет об AVR, где поддержка компиляторов
языков высокого уровня была положена в основу архитектуры. При сравнении
ассемблера и “Си”, лично у меня всегда возникают образы лопаты и
экскаватора. Экскаватор выполняет огромные объемы однотипной работы, он
быстро выроет любой котлован. Но есть места, куда экскаватор элементарно
не может заехать, не говоря уже о небольших траншеях сложной формы. Вот
здесь то и нужна лопата. Не стоит забывать также, что экскаватор не
всегда имеется в наличии, а вот лопата всегда под рукой и она ничего не
стоит.

Сейчас я хочу обратиться в первую очередь к тем, кто только начинает


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

И буквально несколько слов, о соглашениях принятых в этой книге. Все


примеры, если это отдельно не оговорено в тексте, для единообразия
привязаны к модели AVR ATmega8-16PU. Числовая система, принятая по
умолчанию, – десятичная. Заголовки всех подпрограмм имеют однотипное
оформление, где находится описание используемых регистров и других
параметров. Подпрограммы имеют подробные комментарии, начинающиеся с
символа “;”. Метки, пользовательские имена регистров и ячеек памяти, а
также мнемоники команд, директивы ассемблера и встроенные функции состоят
из прописных букв. Числовые константы, имена регистров общего назначения,
регистров ввода-вывода и их битов написаны заглавными буквами. Например:
; Имена из прописных букв:
; initial - метка
; temp – пользовательское имя регистра
; ldi, out, cbi, sbi – мнемоники команд
; .equ, .def, .cseg, .org – директивы ассемблера
; low(), high() – встроенные функции
;
; Имена из заглавных букв:
; LED, RAMEND – числовые константа
; R16 – имя регистра общего назначения
; SPH, SPL, PORTB, DDRB – регистры ввода-вывода
; PB2 – бит регистра ввода-вывода

.equ LED = PB2


.def temp = R16

.cseg
.org 0
rjmp initial
.......

.org 0x20
initial: ldi temp,low(RAMEND)
out SPL,
ldi temp,high(RAMEND)
out SPH,
cbi PORTB,LED
sbi DDRB,LED
.......

Все программы на ассемблере могут быть оттранслированы в среде IDE AVR


Studio любых версии, вплоть до самых ранних. Для разработки программного
обеспечения высокого уровня был использован компилятор Delphi 2007 for
Win32, но исходные тексты должны быть распознаны и более ранними версиями
начиная с Delphi 6.

Тексты всех ассемблерных программ был тщательно проверены, но исключать


наличие опечаток все-таки нельзя. Поэтому любые сообщения о найденных
неточностях, а также общих замечаниях связанных с книгой вы можете
присылать на мой электронный адрес kotigyr@mail.ru

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


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

Искренне желаю всем счастья и успехов во всех ваших начинаниях!

-------------------------------------------------------------------------
------------------------------------

Устройство микроконтроллеров AVR


Первые микроконтроллеры с ядром AVR (аббревиатура производная от имен
двух главных разработчиков Alf-Egil Bogen + Vegard Wollen + RISC) увидели
свет в 1997г и начали стремительно завоевывать нишу на рынке 8-разрядных
микроконтроллеров для встраиваемых систем. А уже через несколько лет
своего становления AVR превратились в основное направление развития
компании Atmel.

Первопроходцем стало семейство Classic, которое сейчас уже полностью


снято с производства. Первые модели Classic были немного “сыроватыми”
(что, впрочем, характерно и для многих других сложных изделий) и вызывали
различные нарекания по поводу стабильности работы, надежности
энергонезависимой памяти и т.д. Однако со временем недостатки были
устранены, а основные концепции Atmel увидели свое продолжение в двух
семействах нового поколения ATtiny (младшее семейство) и ATmega (старшее
семейство). В данный момент микроконтроллеры ATtiny и ATmega составляют
основную массу изделий AVR (характеристики микроконтроллеров ATtiny и
ATmega приведены в приложении A). Но сказать, что на этом их развитие
завершилось, конечно, нельзя. На сегодняшний день ядро AVR лежит в основе
целого ряда микроконтроллеров для специализированных приложений. К ним
относятся модели, содержащие на своем борту CAN (AT90CAN), USB (AT90USB),
модули для генерации ШИМ (AT90PWM), радиочастотный модуль (AT86RF), а
также конфигурируемые микроконтроллеры, которые совмещают на одном
кристалле процессор и программируемые массивы FPGA.

Со второй половины 2008 г начался серийный выпуск моделей


микроконтроллеров семейства Xmega. Архитектура AVR перетерпела
значительную переработку. Были устранены многие слабые места. В составе
Xmega появились контроллеры прерываний и прямого доступа к памяти, 12-
разрядные АЦП и ЦАП, дополнительные модули таймеров-счетчиков и мн. др.
Память EEPROM стала проецироваться на адресное пространство SRAM.
Напряжение питания снизилось до 3.3 В, а максимальная тактовая частота
процессора Xmega возросла до 32 МГц.

Каждый год объем продаж AVR-микроконтроллеров увеличивается примерно в


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

Как и подавляющее большинство современных 8-разрядных микроконтроллеров,


AVR является типичным представителем архитектуры Гарвардского типа.
Память программ и память данных в нем отделены друг от друга и находятся
в различных адресных пространствах (см. рис.1). ЦПУ имеет две независимые
шины: 16-разрядную для обращения к ПЗУ и 8-разрядную для взаимодействия с
ОЗУ. Длина слова команды у AVR кратна 16-ти битам и может составлять 2
или 4 байта.

Разделение памяти программ и данных в Гарвардской архитектуре


Рис.1 Разделение памяти программ и данных в Гарвардской архитектуре

Гарвардская архитектура дает возможность одновременно осуществлять


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

Работа конвейера команд


Рис.2 Работа конвейера команд
В микроконтроллерах AVR реализован двухступенчатый конвейер команд (см.
рис.2). Во время выполнения текущей команды, происходит выборка и
декодирование следующей инструкции. Функционирование конвейера нарушается
только в те моменты, когда результат выполнения команды не определен. Это
относится к командам типа Test & Skip (Проверка и пропуск) и аппаратным
прерываниям. В первом случае происходит ветвление по условию, которое
заранее неизвестно, а во втором – программный переход в неопределенный
момент времени.

AVR имеют систему команд RISC (Reduced Instruct Set Computers –


компьютеры с сокращённым набором команд). Такая система подразумевает
наличие небольшого, хорошо продуманного набора команд, большая часть из
которых выполняется за одинаковый промежуток времени (машинный цикл).
Машинный цикл ядра AVR – 1 период тактовой частоты системного генератора.
Это означает, что производительность микроконтроллера составляет 1 MIPS
(Millions Instruction Per Second) на 1 МГц (!) или 20 MIPS при наибольшей
частоте 20 МГц.

Внутреннее устройство микроконтроллеров семейства ATtiny


Рис.3 Внутреннее устройство микроконтроллеров семейства ATtiny

Внутренняя структура микроконтроллеров семейства ATtiny приведена на


рис.3, а семейства ATmega на рис.4. Аппаратные модули, закрашенные серым
цветом, имеются не во всех моделях AVR.

Внутреннее устройство микроконтроллеров семейства ATmega


Рис.4 Внутреннее устройство микроконтроллеров семейства ATmega

-------------------------------------------------------------------------
------------------------------------

Память

Примечания.
1. Не во всех моделях ATtiny и только из области загрузчика.
2. Доступны не все FUSE-биты.
3. Только в режиме отладки.

Микроконтроллеры AVR имеют 7 типов памяти, каждый из которых расположен в


собственном адресном пространстве. Это энергонезависимая FLASH-память
программ (ПЗУ), статическая память

данных SRAM (ОЗУ), энергонезависимая EEPROM-память данных, а также


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

Табл.1. Возможность доступа к различным ресурсам памяти.

Тип памяти
Прикладная программа

Последовательное
программирование при низком напряжении

Параллельное и последовательное
программирование при высоком напряжении

Интерфейс JTAG

Интерфейс debugWire

Чтение

Запись

Чтение

Запись

Чтение

Запись

Чтение

Запись

Чтение

Запись

FLASH

+ (1)

+
+

SRAM

+(3)

+(3)

EEPROM

+
+

Конфигурационные ячейки

+(1)

+(2)

+(2)

Ячейки защиты

+(1)

+
+

+(2)

+(2)

Калибровочные
ячейки и ячейки идентификатора

-
Память программ FLASH

Коды программ микроконтроллера размещаются в энергонезависимом ПЗУ,


выполненной по технологии FLASH. При нормальных условиях эксплуатации,
FLASH-память позволяет сохранять свое содержимое в неизменном виде в
течение 40 лет и допускает как минимум 10000 циклов стирания/записи.
Организация FLASH памяти программ ATmega8
Рис.5 Организация FLASH памяти программ ATmega8

Организация памяти программ, на примере ATmega8, приведена на рис.5.


Размер FLASH-памяти этой модели составляет 8192 байт. Но, поскольку
каждая команда занимает 2 или 4 байта, то по отношению к AVR точнее будет
говорить об объеме в 4096 слов. Такая размерность определяет максимально
возможное число слов команд (кодов операций), доступное при написании
программы.

Для адресации памяти программ используется программный счетчик PC


(Program Counter) (другое название: регистр или счетчик команд). Он
представляет собой регистр, в котором находится текущей адрес команды во
FLASH-памяти. Таким образом, счетчик команд адресует 16-разрядные слова,
а не байты, и имеет переменную разрядность, которая зависит от размера
FLASH. Так у ATmega8 (4096 слов), PC имеет разрядность 12 бит, у ATmega16
(8192 слова) - 13 бит и т.д. В архитектуре AVR, PC является недоступным
для программиста регистром.

Помимо своего основного назначения (хранение команд), FLASH-память AVR-


микроконтроллеров также позволяет хранить пользовательские данные
произвольного типа: константы, таблицы, постоянные коэффициенты и т.д.
Минимальный адресуемый элемент в этом случае 1 байт. Доступ к таким
данным из прикладной программы производится различными модификациями
инструкции lpm.

Как правило, алгоритмы работы микроконтроллера, записанные в памяти


программ, не должны изменяться во времени. Однако у AVR все-таки имеется
возможность модифицировать содержимое FLASH посредством собственного
программного обеспечения. Обсуждению этого вопроса посвящена глава
«Самопрограммирование микроконтроллеров AVR». Там же показан и другой
способ разделения FLASH на секцию прикладной программы (Application
Section) и секцию загрузчика (Boot Loader Section). Изменять содержимое
памяти программ можно только из области загрузчика.

В процессе программирования запись во FLASH-память происходит


постранично. Размер страницы зависит от объема памяти конкретной модели и
может быть равен 32, 64 или 128 слов.

-------------------------------------------------------------------------
------------------

Память данных SRAM

Микроконтроллеры AVR имеют статическую память данных. Ее содержимое


остается неизменным даже при полной остановке источника тактовой частоты.
После отключения напряжения питания вся информация, записанная в ячейках
SRAM, теряется. В любой микропроцессорной системе память данных является
самой быстрой.
При частоте 20 МГц и однотактовом машинном цикле время модификации SRAM
составляет 50 нс.

Организация памяти данных SRAM ATmega8


Рис.6 Организация памяти данных SRAM ATmega8

Линейная память данных AVR разбита на три отдельные части, как показано
на рис.6. Это регистры общего назначения (РОН) или GPR (General Purpose
Registers), регистры ввода-вывода (РВВ) или IO (Input-Output Registers) и
ОЗУ микроконтроллера. РОН и РВВ, кроме этого имеют индивидуальный
диапазон адресов (собственное адресное пространство), который могут
использовать только команды определенного типа.

AVR содержат 32 регистра общего назначения с символьными именами R0…R31.


Эти регистры подключены непосредственно к арифметико-логическому
устройству (АЛУ) и, главным образом, служат для обработки данных и
вычислительных операций. Большая часть команд в качестве своих параметров
использует РОНы. Поэтому и любая программа наиболее интенсивно использует
именно эти ячейки памяти. В индивидуальном и абсолютном адресных
пространствах РОНы занимают адреса 0…31 (0…0x1F).

Доступ ко всем аппаратным ресурсам микроконтроллера производится через


РВВ. Управление линиями ввода-вывода, прерываниями и любыми другими
интерфейсами сводится к обыкновенным процедурам чтения/записи этих
регистров. AVR содержат, как минимум 64 РВВ, которые имеют индивидуальный
диапазон адресов 0…63 (0…0x3F). В абсолютном адресном пространстве РВВ
занимают адреса 32…95 (0x20…0x5F) сразу за регистрами общего назначения.
Для обращения к РВВ в индивидуальном диапазоне адресов разработаны
команды in Rd,P (Записать содержимое РВВ в РОН), out P,Rd (Вывести
содержимое РОН в РВВ). Кроме этого у первой половины РВВ разрешен доступ
к их битовым полям. Установить или сбросить любой бит в регистрах 0…31
(0…0x1F) можно командами sbi P,b (Установить бит b в РВВ) и
сbi P,b (Сбросить бит b в РВВ) соответственно. Символьные имена регистров
и месторасположения в пространстве РВВ зависят от конкретной модели
микроконтроллера и оговорены в технической документации.

После РВВ, собственно, начинается адресное пространство внутреннего ОЗУ,


основным предназначением которого является хранение переменных прикладной
программы (имеется не во всех моделях ATtiny). Некоторые микроконтроллеры
старшего семейства допускают подключение внешнего ОЗУ или XRAM (External
Random Access Memory). Такая возможность реализована у них на аппаратном
уровне (отдельные порты начинают работать как шины адресов/данных). В
этом случае адресное пространство внешней памяти данных размещается сразу
за адресным пространством памяти SRAM. Обращаться к абсолютным адресам
памяти данных можно, например, с помощью инструкций lds Rd,k (Переместить
содержимое ячейки по адресу k SRAM в РОН), sts Rd,k (Переместить
содержимое РОН в ячейку SRAM по адресу k) и т.д.

Таким образом, существуют два независимых способа доступа к РОН и РВВ.


Первый заключается в использовании группы команд, которые обращаются к
индивидуальным адресам регистров (0…0x1F для РОН и 0…0x3F для РВВ)
напрямую. Второй – в применении инструкций, которые обращаются к ячейкам
абсолютного адресного пространства SRAM, отдельной частью которого
являются РОН и РВВ. Так пересылка из регистра общего назначения R16 в РВВ
SPH с помощью команд out SPH,R16 и sts SPH+0x20,R16 приведет к одному и
тому же результату. Во второй команде к имени SPH, которое представляет
собой адрес 0x3E в пределах РВВ, добавлено смещение 0x20 для задания
абсолютного адреса 0x5E в SRAM.

По мере развития линейки ATmega, новым моделям с наиболее развитой


периферией стало уже не достаточно 64 РВВ (в ATmega2560 их порядка 200).
Atmel очень просто решила этот вопрос, разместив ряд управляющих
регистров в так называемом дополнительном адресном пространстве ввода-
вывода. Под дополнительное пространство отводится диапазон адресов SRAM
0x0060…0x00FF (160 добавочных регистров) либо 0x0060…0x01FF (416
добавочных регистров). Естественно, что к этим РВВ можно обращаться
только как к ячейкам памяти из абсолютного адресного пространства SRAM.
После подачи напряжения питания, все РОНы микроконтроллера обнуляются.
Начальное содержимое РВВ (начальные настройки аппаратной части) зависит
от конкретного предназначения регистра. Ячейки же ОЗУ не имеют
собственных цепей сброса, и после запуска в них находится “мусор”. Эти
произвольные данные можно использовать как, входные параметры при
генерации случайных чисел.

-------------------------------------------------------------------------
-----

Память данных SRAM

[Нашли ошибку? Нажмите ctrl+enter для уведомления]


Металлоискатель MD3010II
Металлоискатель MD3010II
Купить 98 $
Набор 4WD Kit Bluetooth
Набор 4WD Kit Bluetooth
Купить 100 $
Охранный PIR сенсор с функцией отправки MMS по GSM-каналу
Охранный PIR сенсор с функцией отправки MMS по GSM-каналу
Купить 35 $

[Поддержите авторов статей сайта Паяльник!]


Память данных EEPROM

EEPROM предназначена для сохранения данных, которые не должны изменятся


после отключения напряжения питания. Эта память отсутствует в некоторых
моделях младшего семейства. В целом устройство EEPROM подобно FLASH.
Однако EEPROM имеет значительно больший ресурс (до 100000 циклов
стирания/записи), и допускает побайтовую адресацию, вместо страничной у
FLASH. Время записи в EEPROM для разных моделей находится в пределах 2…9
мс.

Организация памяти данных EEPROM ATmega8


Рис.7 Организация памяти данных EEPROM ATmega8

EEPROM-память данных находится в отдельном адресном пространстве,


независимом от FLASH и SRAM (см. рис.7). Взаимодействие прикладной
программы с EEPROM происходит через 4 РВВ: EEARH, EEARL, EEDR и EECR.

Адрес ячейки памяти в операциях чтения/записи находится в регистрах EEARH


(старший байт адреса) и EEARL (младший байт адреса). В моделях с объемом
EEPROM до 256 б старший регистр адреса, по понятным причинам отсутствует.
Число перед записью в EEPROM помещается в регистр EEDR. B этом же
регистре нужно искать байт данных после операции чтения.

Для непосредственного управления процессами чтения/записи служат биты


регистра EECR. Этим процедурам должен предшествовать строго определенный
порядок действий, как показано в подпрограммах.
; Подпрограмма записи байта данных в EEPROM
; R16 – регистр с байтом данных для записи при входе
; R17 – регистр для передачи младшего байта адреса
; R18 – регистр для передачи старшего байта адреса

eeprom_write:
sbic EECR,EEWE ;ожидаем пока запись не будет закончена
rjmp eeprom_write;предыдущая операция записи в EEPROM
out EEARH,R18 ;задаем адрес ячейки EEPROM, в которую
out EEARL,R17 ;необходимо записать байт данных
out EEDR,R16 ;заносим в регистр данных байт для записи
sbi EECR,EEMWE ;разрешаем запись
sbi EECR,EEWE ;начинаем запись байта в EEPROM
ret

; Подпрограмма чтения байта данных из EEPROM


; R16 – регистр с прочитанным байтом данных на входе
; R17 – регистр для передачи младшего байта адреса
; R18 – регистр для передачи старшего байта адреса

eeprom_read:
sbic EECR,EEWE ;ожидаем пока запись не будет закончена
rjmp eeprom_write;предыдущая операция записи в EEPROM
out EEARH,R18 ;задаем адрес ячейки EEPROM, из которую
out EEARL,R17 ;необходимо прочитать байт данных
sbi EECR,EERE ;начинаем чтение байта из EEPROM
in R16,EEDR ;извлекаем байт из регистра данных
ret

-------------------------------------------------------------------------
---------------------------

Конфигурационные ячейки

Конфигурационные ячейки (Fuse Bits) определяют различные параметры


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

В разных моделях микроконтроллеров может содержаться от 5 до 20


конфигурационных ячеек. Все они находятся в отдельном адресном
пространстве, доступном только на этапе программирования. Наибольшее что
может сделать прикладная программа – это считать содержимое FUSE-битов.
Однако такой код должен выполняться только в области загрузчика
(подробнее см. «Самопрограммирование микроконтроллеров AVR»).

FUSE-биты сведены в байты конфигурации. В зависимости от количества FUSE-


битов AVR-микроконтроллеры могут иметь до трех таких байтов. Это младший,
старший и дополнительный байты конфигурации. Описание FUSE-битов
микроконтроллера ATmega8 находится в табл.2.

Табл.2. Конфигурационные ячейки микроконтроллера ATmega8:

Номер
бита
Название

Заводское значение

Назначение

Младший байт конфигурации

CKSEL0

Определяют режим работы тактового генератора. Заводское значение


CKSEL3:CKSEL0=1000 активизирует внутренний RC-генератор с частотой 1 МГц.

CKSEL1

CKSEL2

CKSEL3

SUT0

Определяют длительность задержки включения после сброса. Заводское


значение SUT1:SUT0=10 устанавливает задержку времени 64 мс.

5
SUT1

BODEN

Разрешает/запрещает функционирование схемы BOD. Заводское значение


BODEN=1 запрещает схему сброса.

BODLEVEL

Задает порог срабатывания схемы BOD. При BODEN=1 значения этого бита
игнорируются. Заводское значение BODLEVEL=1 устанавливает порог
срабатывания 4.0 В.

Старший байт конфигурации

BOOTRST

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


адресу в памяти программ либо в начале секции загрузчика. Заводское
значение BOOTRST=1 устанавливает вектор сброса по адресу PC=0.

BOOTSZ0

Определяет размер секции загрузчика. Заводское значение BOOTSZ1:


BOOTSZ0=00 устанавливает размер секции 1024 слова.

2
BOOTSZ1

EESAVE

Определяет влияние команды Chip Erase (Стирание кристалла) на EEPROM-


память данных. Заводское значение EESAVE=1 разрешает стирание EEPROM
вместе с памятью программ FLASH.

CKOPT

Определяет функционирование тактового генератора. Действие бита зависит


от установок CKSEL3:CKSEL0

SPIEN

Разрешает/запрещает программирование через интерфейс SPI (внутрисхемное


программирование). Этот бит доступен только во время высоковольтного
программирования. Заводское значение SPIEN=0 разрешает SPI.

WDTON

Определяет способ запуска WDR. Заводское значение WDR=1 запрещает работу


сторожевого таймера сразу после запуска микроконтроллера.

RSTDISBL
1

Разрешает/запрещает функционирование линии RESET. Заводское значение


RSTDISBL=1 подключает линию RESET к выводу 6 порта C.

Конфигурационные ячейки AVR имеют одну важную особенность.


Запрограммированным FUSE-битам соответствует значение лог.0 вместо
привычного в подобных случаях состояния лог.1. Так, например, EESAVE=0
означает активизацию бита защиты EEPROM во время стирания кристалла,
WDR=0 – запуск сторожевого таймера сразу после запуска устройства и т.д.

Существуют некоторые FUSE-биты, которые недоступны при программировании


через SPI, JTAG или debugWire. Полный же доступ к ячейкам конфигурации
может быть получен только во время высоковольтного программирования.

-------------------------------------------------------------------------
------------------------

Ячейки защиты

Ячейки защиты (Lock Bits) помогают защитить программное обеспечение от


корыстного использования третьими лицами.

Ячейки бывают двух типов. К первому из них относятся биты LB1, LB2,
которые имеются во всех без исключения AVR- микроконтроллерах. Их
комбинация определяет степень доступа к памяти FLAS и EEPROM в
соответствии с табл.3.

Табл.3. Ячейки защиты FLASH-памяти программ и EEPROM-памяти данных:

Номер режима защиты

Ячейки защиты

Описание

LB1

LB2

Защита FLASH и EEPROM

Защита FLASH и EEPROM отключена.


2

Запись FLASH и EEPROM запрещена.

Чтение и запись FLASH и EEPROM запрещены.

Ячейки второго типа содержаться только в тех моделях AVR, которые имеют
возможность самопрограммирования. Одна пара этих ячеек BLB02, BLB01
определяет режим защиты прикладной программы, а вторая пара BLB12, BLB11
– режим защиты секции загрузчика. Эти биты накладывают определенные
ограничения на функционирование инструкций lpm/spm и не имеют слишком
большого практического значения.

Все ячейки располагаются в одном байте и доступны на этапе


программирования либо из Boot Loader Section. Подобно FUSE-битам,
запрограммированному значению ячеек защиты соответствует уровень лог.0.

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


осторожности. Механизм защиты активизируется сразу после изменения их
содержимого! Например, установка комбинации

LB1:LB2=00 полностью заблокирует доступ к FLAS и EEPROM. При чтении этих


областей, вместо реально записанных данных, в дампе памяти можно будет
увидеть пустую последовательность байтов типа 0x00, 0x01…0xFE,0xFF, 0x00
и.д. Перепрограммирование ячеек защиты возможно только после полного
стирания кристалла.

-------------------------------------------------------------------------
------------------------

Калибровочные ячейки и ячейки идентификаторов

Любую модель AVR на этапе программирования можно опознать по содержимому


трех 8-разрядных ячеек идентификаторов. Первый байт представляет собой
код производителя и, естественно, одинаковый для вех микроконтроллеров.
Его значение 0x1E. Во втором байте находится код объема FLASH-памяти
программ. Он может иметь одно из ряда значений 0x90…0x98. Наименьшему
числу соответствует объем FLASH 1 кб, наибольшему – объем 256 кб.
Последняя из ячеек идентификаторов содержит код модели в диапазоне
0x01…0x0C.
Второй и третий байты у различных моделей могут быть одинаковыми, но их
комбинация в пределах семейства уникальна. Микроконтроллеры ATmega8
содержат идентификаторы 0x1E, 0x93, 0x07.

Все без исключения AVR-микроконтроллеры могут использовать в качестве


источника тактовой частоты внутренний RC-генератор (за выбор источника
отвечают конфигурационные ячейки CKSEL3:CKSEL0). Причем в большинстве
моделей этот генератор способен работать на нескольких частотах. Так, у
ATmega8 это 1,2,4 и 8 МГц. Однако действующая технология изготовления
кристаллов дает значительный разброс по этому параметру.

Для управления частотой RC-генератора, используется РВВ OSCCAL. Изменение


содержимого OSCCAL от 0 до 0xFF позволяет перестраивать генератор в
пределах -100…+200% от номинальной частоты. Точные же значения, при
которых погрешность хода не превышает 1% при температуре 25 C (или 3% во
всем рабочем диапазоне температур), определены на заводе изготовителе и
записаны в калибровочные ячейки микроконтроллера. Естественно, что
количество калибровочных ячеек равно числу возможных частот RC-
генератора.

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


ячейки загружается при старте в регистр OSCCAL аппаратно. Тем самым
производится автоматическая настройка RC-генератора. То же самое
относится и ко всем остальным микроконтроллерам, но только по отношению к
наименьшей из ряда опорных частот. Например, у ATmega8 при старте в
OSCCAL копируется лишь содержимое ячейки с калибровочной константой на
частоту 1 МГц. Во всех остальных случаях на этапе программирования
необходимо считать значение соответствующей калибровочной ячейки и
поместить его по известному адресу в энергонезависимую память FLAS или
EEPROM, а прикладная программа в ходе инициализации должна считать это
число и скопировать его в регистр настройки генератора.

-------------------------------------------------------------------------
-------------------

Арифметико-логическое устройство

Арифметико-логическое устройство (АЛУ) выполняет все вычислительные


операции в микропроцессорной системе. Непосредственно к АЛУ подключены 32
РОНа (регистровый файл), как показано на рис.8.

Арифметико-логическое устройство
Рис.8 Арифметико-логическое устройство

В большинстве операций любой из этих регистров может использоваться в


качестве источника входных данных либо приемника результата. При этом все
арифметические и логические команды над РОНми имеют вид подобный add
Rd,Rs. За названием мнемоники, которая описывает действие (addition –
сложение), следуют два параметра (операнда). В данном случае это регистры
Rd (destination – приемник) и Rs (source – источник), содержимое которых
необходимо сложить между собою. Сумма будет размещена по адресу регистра
Rd.

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

Что касается, арифметических и логических действий над регистром и


константой, то в целом они подобны действиями между РОНми. Однако в этих
целях могут использоваться только старшая половина регистрового файла.
Например, в команде вычитания константы из регистра subi Rd,K в качестве
Rd могут выступать только регистры R16…R31.

В состав АЛУ микроконтроллеров семейства ATmega входит также 2-тактный


аппаратный умножитель 8x8 бит, который способен работать как с
беззнаковыми так и со знаковыми числами представленными в дополнительном
коде. Во всех разновидностях операции умножения, 16-разрядное
произведение помещается в регистры R1 (старший байт) и R0 (младший байт).

Регистры R26…R31 у AVR имеют двойное предназначение. Они могут быть


использованы как три 16-разрядных указателя, которые имеют символьные
имена X (XH:XL = R27:R26), Y (YH: YL = R27:R26), Z (ZH: ZL = R27:R26) и
применяются для задания адресов ячеек SRAM в различных командах пересылки
данных (ld Rd,X/Y/Z, st X/Y/Z,Rd и т.д.). Указатель Z, кроме этого,
необходим инструкциям lpm/spm при чтении/записи FLASH-память программ.
Регистры X и Y отсутствуют в некоторых устаревших моделях ATtiny.

АЛУ любого микропроцессора неразрывно связано с регистром флагов


программы (регистр состояния программы), который у AVR имеет название
SREG (Status Register) и расположен в пространстве РВВ. Описание битов
SREG приведено в табл.4. Все флаги SREG доступны как для чтения, так и
для записи.

Табл.4. Флаги регистра состояния программы SREG:

Номер бита
в регистре

Название флага

Описание

Флаг переноса. Устанавливается в 1 если в результате операции произошел


выход за границы байта.

Флаг нуля. Устанавливается в 1 если результат операции равен нулю.

N
Флаг отрицательного результата. В этот флаг копируется содержимое 7-мого
MSB результата операции.

Флаг переполнения в дополнительном коде. Устанавливается в 1 при


переполнении разрядной сетки знакового результата.

Флаг знака. Содержимое флага определяется как N XOR V.

Флаг половинного переноса. Устанавливается в 1 если в результате операции


сложения/вычитания произошел перенос/заем из 3-тего бита в 4-тый.

Флаг хранения копируемого бита. Флаг используется в качестве источника и


приемника командами копирования битов bld и bst соответственно.

Флаг глобального разрешения прерываний. При установке в 1 этого флага


происходит разрешение всех немаскируемых прерываний.

Флаг Z является показателем нулевого результата во всех вычислительных


операциях. Флаги С и H используются преимущественно для связи байтов в
процессе сложения/вычитания (флаг С также в сдвиговых операциях). Биты
N,S и V необходимы для знаковых вычислений. Флаг T – источник и приемник
в командах копирования битов bld/bst, но может быть использован и как
дополнительная ячейка для хранения любой пользовательской информации
(флаг пользователя). Последний флаг I, в отличие от всех остальных, не
связан с результатами вычислительных операций либо операций пересылок и
предназначен для глобального разрешения/запрета прерываний.
-------------------------------------------------------------------------
--------------

Стек

Стек представляет собой область памяти, которую ЦПУ использует для


сохранения и восстановления адресов возврата из подпрограмм.
Практически у всех микроконтроллеров AVR стек размещается в SRAM. Для
адресации текущего элемента (вершины стека) используется указатель стека
SP (Stack Pointer). Это однобайтовый РВВ SPL у моделей с объемом памяти
данных до 256 б, или двухбайтовый SPH:SPL (SPH – старший байт, SPL –
младший байт).

Когда микропроцессор встречает одну из инструкций вызовов


rcall/call/ecall/icall/eicall, то адрес следующего за ними слова в памяти
программ аппаратно копируется в стек. В момент выхода из подпрограммы по
команде ret адрес возврата восстанавливается из стека в программный
счетчик. В моделях с объемом памяти программ 128 и 256 к/слов для
сохранения PC в стеке потребуется 3 байта, для всех остальных – 2 байта.
При сохранении каждого байта содержимое SP уменьшается не единицу, а при
восстановлении, соответственно увеличивается.

Расположение стека в памяти данных


Рис.9 Расположение стека в памяти данных

Программист должен самостоятельно определить местоположение стека в самом


начале программы. С точки зрения максимальной его глубины, вершину стека
нужно поместить в самом конце SRAM, как это показано на рис.9:

.include "m8def.inc"

ldi temp,low(RAMEND) ;устанавливаем SP = RAMEND


out SPL,temp ;для ATmega8 SP = 0x045F
ldi temp,high(RAMEND)
out SPH,temp

Константа RAMEND из стандартного заголовочного файла m8def.inc имеет


значение адреса последней ячейки SRAM.

В диапазоне адресов SRAM между РВВ и текущим положением SP размещаются


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

Стек AVR, помимо сохранения адресов возврата, имеет еще одно очень важное
предназначение. Он позволяет сохранять любые данные специально
предназначенными для этого командами push Rr (загрузка в стек) и pop Rd
(выгрузка из стека). Каждый раз при выполнении push Rr содержимое Rr
копируется в стек, после чего SP уменьшается на единицу. При выполнении
pop Rr содержимое ячейки стека, на которую указывает SP,
восстанавливается в Rr, а само значение SP инкрементируется. Стек
подобного рода имеет организацию Last In First Out (Первый Вошел
Последний Вышел): регистр, сохраненный последней командой push, будет
восстановлен первой командой pop:
; SP Уровень стека
после команды

push R16 ;сохраняем R16 0x045F R16 ?


?
push R17 ;сохраняем R17 0x045E R16
R17 ?
push R18 ;сохраняем R18 0x045D R16
R17 R18
????????
pop R18 ;восстанавливаем R18 0x045D R16
R17 ?
pop R17 ;восстанавливаем R17 0x045E R16 ?
?
pop R16 ;восстанавливаем R16 0x045F ? ?
?

Через стек очень просто можно обменять содержимое регистров местами:

; Обмен R16 <-> R17 SP Уровень стека


после команды

push R16 ;сохраняем R16 0x045F R16 ?


push R17 ;сохраняем R17 0x045E R16
R17
pop R16 ;восстанавливаем R16 0x045E R16 ?
pop R17 ;восстанавливаем R17 0x045F ? ?

Пример работы стека


Рис.10 Пример работы стека

На рис.10 приведен небольшой фрагмент кода, в котором пошагово рассмотрен


процесс изменения стека при входе и выходе из подпрограммы toggle и
сохранении и восстановлении регистра R17. Это типичный пример, где могут
понадобиться инструкции push/pop. Подпрограмма toggle использует РОН R17
в своих нуждах, но этот- же регистр может использоваться и в ходе
основной программы. Поэтому, во избежание повреждения данных, R17 перед
модификацией загружается в стек и восстанавливается из него перед
командой ret.

У некоторых устаревших моделей ATtiny отсутствует SRAM. Поэтому стек


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

-------------------------------------------------------------------------
---------------------------

Прерывания

Прерывания – это специальный механизм, позволяющий остановить выполнение


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

В большинстве случаев прерывания позволяют очень сильно разгрузить ЦПУ от


той работы, которую самостоятельно могут выполнять отдельные его модули.
Например, любой таймер-счетчик, отсчитав необходимое число тактов
генератора, может сгенерировать прерывание и вызвать подпрограмму, в
которой будет находиться код для отчета различных временных интервалов.
Точно таким же образом при наступлении определенного события (отсылка или
прием слова данных, изменение состояния на линии и мн. др.) способны
оповещать процессор модули USART, SPI, TWI, ADC и т.д. Различные модели
AVR могут содержать от 4 до 56 таких источников прерываний.

Табл.5. Таблица векторов прерываний:

Номер

Адрес в памяти программ

Бит разрешение прерывания

Флаг прерывания

Описание события

0x0001

INT0 из GICR

INTF0 из GIFR

Внешнее прерывание 0

0x0002

INT1 из GICR

INTF1 из GIFR

Внешнее прерывание 1

3
0x0003

OCIE2 из TIMSK

OCF2 из TIFR

Cовпадение TCNT2 и OCR2

0x0004

TOIE2 из TIMSK

TOV2 из TIFR

Переполнение TCNT2

0x0005

TICIE1 из TIMSK

ICF1 из TIFR

Захват в ICP1

0x0006

OCIE1A из TIMSK

OCF1A из TIFR

Совпадение TCNT1 и OCR1A

0x0007

OCIE1B из TIMSK
OCF1B из TIFR

Совпадение TCNT1 и OCR1B

0x0008

TOIE1 из TIMSK

TOV1 из TIFR

Переполнение TCNT1

0x0009

TOIE2 из TIMSK

TOV2 из TIFR

Переполнение TCNT0

10

0x000A

SPIE из SPCR

SPIF из SPSR

Прерывание от модуля SPI

11

0x000B

RXCIE из UCSRB

RXC из UCSRA

Получение байта по USART


12

0x000C

UDRIE из UCSRB

UDRE из UCSRA

Опустошение UDR в USART

13

0x000D

TXCIE из UCSRB

TXC из UCSRA

Передача байта по USART

14

0x000E

ADIE из ADCSRA

ADIF из ADCSRA

Прерывание от АЦП

15

0x000F

EERIE из EECR

EEMWE из EECR

Завершение записи в EEPROM

16

0x0010
ACIE из ACSR

ACI из ACSR

Прерывание от компаратора

17

0x0011

TWIE из TWCR

TWINT из TWCR

Прерывание от модуля TWI

18

0x0012

SPMIE из SPMCR

SPMIE из SPMEN

Завершение выполнения spm

За каждым обработчиком прерывания жестко закреплен определенный адрес


(вектор прерывания) в начале памяти программ. Положения векторов
прерываний для ATmega8 сведены табл.5. Обычно по адресу вектора
прерывания находится инструкция перехода, которая передает управление
подпрограмме обработчика:

.cseg
.org 0 ;начало с нулевого адреса памяти программ

rjmp initial ;переход на начало основной программы


rjmp service_INT0 ;переход на метку service_INT0
rjmp service_INT1 ;переход на метку service_INT1
.
rjmp service_TWI ;переход на метку service_TWI
rjmp service_SPMR ;переход на метку service_SPMR

В моделях AVR с объемом FLASH ?8 кбайт, для векторов отводится по 1 слову


памяти программ, как раз для инструкций rjmp. Во всех остальных
микроконтроллерах каждый вектор прерывания занимает уже 2 слова, а в
качестве инструкции перехода используются jmp.
Управление прерываниями производится индивидуально. За разрешение каждого
из них отвечают специальные разряды соответствующих РВВ, а о наступлении
события микроконтроллер может судить по состоянию флагов прерывания (см.
табл.5). Например, если прерывание по переполнению таймера-счетчика 2
разрешено (установлен бит TOIE2 из TIMSK), то при изменении содержимого
счетного регистра TCNT2 c 0xFF на 0x00 в регистре TIFR аппаратно будет
установлен флаг прерывания TOV2 и микроконтроллер вызовет подпрограмму по
адресу 0x0004.

За общее управление прерываний у AVR отвечает флаг I из регистра SREG.


При I=0 все прерывания, независимо от состояния битов разрешения,
запрещены.

При вызове обработчика прерывания адрес текущей команды в основной


программе копируется в стек, как и при обычном вызове подпрограммы. Но
вместе с этим микроконтроллер аппаратно сбрасывает на нуль флаг I и флаг,
который явился источником прерывания. Обработчик должен заканчиваться
командой возврата из прерывания reti. После ее выполнения адрес возврата
восстанавливается в PC и при этом одновременно устанавливается флаг I.

Ниже рассмотрен пример использования обработчика внешнего прерывания


INT0.

.include "m8def.inc" ;подключение стандартного заголовочного файла

.cseg ;начало секции кода с нулевого адреса


.org 0

rjmp main ;вектор сброса


rjmp service_INT0 ;вектор внешнего прерывания 0
.

main:
ldi R16,low(RAMEND) ;инициализируем указатель стека
out SPL,R16
ldi R16,high(RAMEND)
out SPH,R16
sbi PORTD,PD2 ;настраиваем на ввод линию 2 порта D
(альтернативная
sbi DDRD,PD2 ;функция INT0) и подключаем к ней внутренний
резистор
ldi R16,1«ISC01 ;задаем условием прерывания изменения
out MCUCR,R16 ;состояния вывода INT0 с лог.1 на лог.0
ldi R16,1«INT0 ;разрешаем прерывание INT0
out GICR,R16
sei ;разрешаем прерывания глобально (I=1)
.

service_INT0:
push R16 ;сохраняем в стеке R16
in R16,SREG
push R16 ;сохраняем в стеке SREG
.
pop R16 ;восстанавливаем из стека SREG
out SREG,R16
pop R16 ;восстанавливаем из стека R16
reti

Инструкция перехода, размещенная по нулевому адресу (вектор сброса у всех


моделей AVR), передает управление на начало основной программы main, где
и происходит инициализация микроконтроллера. В качестве условия
возникновения прерывания выбрано изменение состояния вывода INT0 с уровня
лог.1 на лог.0 (момент нажатия кнопки). При возникновении указанного
события произойдет вызов подпрограммы по адресу 0x0001 (вектор прерывания
INT0) и, далее, обработчика service_INT0. В обработчике нужно сохранить
содержимое SREG и, если это необходимо, остальных регистров, которые
используются в контексте основной программы.

Возможна такая ситуация, что в процессе работы одновременно возникнут


сразу несколько запросов на прерывания (одновременно будут установлены
несколько флагов прерывания). В этом случае первым будет вызван тот
обработчик, чей адрес в таблице векторов прерывания находится выше.
Например, при возникновении запросов от АЦП (адрес 0x000E) и компаратора
(адрес 0x0010), первым будет обработан запрос от АЦП. Таким образом,
каждое прерывание у AVR имеет свой собственный неизменный приоритет,
который зависит от его местоположением в таблице векторов.

Здесь возможны две проблемы. Во-первых, обработка отдельных прерываний


может быть достаточно длительной процедурой и другие запросы окажутся
отложенными на недопустимо большой срок. А во-вторых, прерывание с низким
приоритетом может иметь намного большее значение для данного устройства.
В обоих случаях можно выйти из положения, если допустить в программе
вложенные прерывания. Для этого после сохранения контекста в обработчике
прерывания нужно вручную установить флаг I командой sei:

service_INT0:
push R16 ;сохраняем в стеке R16
in R16,SREG
push R16 ;сохраняем в стеке SREG
sei ;разрешаем прерывания во время обработчика
.
pop R16 ;восстанавливаем из стека SREG
out SREG,R16
pop R16 ;восстанавливаем из стека R16
reti

Вложенные прерывания могут оказаться недопустимыми при малом размере


стека.

-------------------------------------------------------------------------
-----

Порты ввода-вывода

Систему ввода-вывода микроконтроллеров AVR можно считать одной из самых


сильных сторон архитектуры. Каждая линия допускает независимое
управление, имеет симметричные нагрузочные характеристики и номинальные
втекающий и вытекающий токи до 20…25 мА (максимальный до 40 мА) с
просадкой напряжения не более 0.7 В.

Линии объединяются в 8-разрядные порты ввода-вывода, которые у AVR имеют


названия A,B,C…K,L. Обозначение каждой линии на схеме определяет ее
принадлежность к порту и порядковый номер. Например, выводы порта B имеют
названия PB0,PB1…PB7, порта D - PD0,PD1…PD7 и т.д. В различных моделях
отдельные порты могут содержать меньше 8 линий ввода-вывода.

Расположение выводов ATmega8


Рис.11 Расположение выводов ATmega8

Большинство выводов AVR-микроконтроллеров имеют также альтернативные


назначения. Это аналоговые линии компаратора и АЦП, цифровые модулей SPI,
TWI, USART и т.д. Описание выводов микроконтроллера ATmega8 находится в
табл.6, а схема их расположения на рис.11.

Внутрення схема линии порта ввода-вывода


Рис.12 Внутрення схема линии порта ввода-вывода

Упрощенная внутренняя схема линии порта ввода-вывода приведена на рис.12.


Каждым портом микроконтроллера управляют 3 РВВ. Это DDRx, PORTx и PINx (x
– название порта: A,B,C и т.д.). Регистры портов A,B,C,D всех моделей
находятся в первой половине адресного пространства ввода-вывода и поэтому
допускают прямые операции над отдельными их битами. В цифровых портах AVR
реализована истинная функциональность “чтение-модификация-запись”.

Регистр DDRx отвечает за направление передачи данных. Запись лог.1 в


разряды DDRx настраивает линии на вывод, а запись лог.0, соответственно,
на ввод. Например:

ldi R16,0b11100011 ;настроить на ввод линии 2,3,4 и


out DDRB,R16 ;на вывод линии 0,1,5,6,7 порта B

sbi DDRB,0 ;настроить на вывод линию 0 порта B


cbi DDRB,1 ;настроить на ввод линию 1 порта B

Регистр PORTx имеет двойное предназначение. Если линии порта настроены на


вывод, то содержимое PORTx определяет логическое состояние выводов порта.
Для линий настроенных на ввод значение PORTx определяет состояние
внутреннего подтягивающего (pull-up) резистора к шине питания. В этом
случае при уровне лог.1 в разрядах PORTx резистор подключен, а при уровне
лог.0 – отключен и линии переведены в высокоимпедансное состояние (z-
состояние):

ldi R16,0b11110000 ;настроить на вывод линии 4…7


out DDRC,R16 ;и на ввод линии 0…3 порта С
ldi R16,0b11111111 ;выставить на линиях 4…7 порта С уровень лог.1
out PORTC,R16 ;и подключить к линиям 0…3 порта С pull-up резисторы

sbi DDRC,0 ;настроить на вывод линию 0 порта C


cbi PORTC,0 ;выставить на линии 0 порта С уровень лог.0

сbi DDRC,1 ;настроить на ввод линию 1 порта C


сbi PORTC,1 ;перевести линию 1 порта С в z-состояние

Регистр PINx предназначен для считывания уровней сигнала с выводов


микроконтроллера. Естественно, что необходимость в этом существует только
для линий настроенных на ввод (для линий настроенных на вывод содержимое
PINx повторяет состояние выходного регистра PORTx). Разряды PINx не
оказывают ни какого влияния на состояние выводов, а сам регистр доступен
только для чтения. Логический уровень входной линии фиксируется в
триггере-защелке в каждом цикле тактовой частоты. Таким образом, реальное
значение сигнала при считывании может иметь отставание порядка 0.5…1.5
машинных циклов. Пример использования:

cbi PORTD,0 ;настроить на ввод линию 0 порта D


sbi PORTD,0 ;подключить к линии 0 порта D pull-up резистор
nop ;задержка 1 цикл для установки режима
sbis PIND,0 ;считать состояние линии 0 порта D
rjmp ulo ;если лог.0, то перейти на метку ulo
rjmp uhi ;если лог.1, то перейти на метку uhi

У микроконтроллеров AVR имеется возможность управлять pull-up резисторами


сразу на всех его выводах. За это свойство отвечает бит PUD из РВВ SFIOR
либо MCUCR. При установке разряда PUD запрещается подключение всех
резисторов. При PUD=0 (значение после сброса) состояние внутренних
резисторов определяется состоянием регистра PORTx.

Различные возможности конфигурации линий портов ввода-вывода AVR


приведены в табл.7. На время спящего режима (после выполнения команды
sleep) входной буфер порта отключается.

При использовании выводов микроконтроллера по их альтернативному


назначению, может понадобится вручную сконфигурировать линии. Так,
например, в случае применения компаратора, АЦП, внешних прерываний
соответствующие линии должны быть переведены в z-состояние, а для ШИМ-
выводов таймеров счетчиков и выходных сигналов других модулей линии
должны быть настроены на вывод.

Табл.6. Описание выводов модели ATmega8(L):

Табл.7. Конфигурация выводов порта:

-------------------------------------------------------------------------
-------

Система команд

Микроконтроллеры AVR имеют систему сокращенного набора команд RISC, хотя


целиком и не полностью попадают под это определение.
Система RISC подразумевает полную симметрию между ресурсами памяти
разного типа. Это, в частности, позволяет обращаться к регистрам, портам
и памяти данных одними и теми же командами, что и обуславливает их
небольшое количество. Однако, не смотря на то, что адресное пространство
памяти AVR действительно непрерывно, всё же три разных его области
используются только для своих специфических целей. РОН – преимущественно
для математических операций, РВВ – для управления процессором, ОЗУ –
только как хранилище информации. В связи с этим существуют группы команд
как для работы с каждым видом памяти в отдельности, так и для пересылки
данных из одной области памяти в другую. Поэтому и количество команд AVR
достаточно велико. В фирменной документации, где много говорится про
ортогональность ядра, в первую очередь имеется в виду полная
равноправность именно РОН.

Система команд линейки ATtiny является подмножеством системы команд


старшего семейства ATmega. В ряде старых моделях ATtiny могут
отсутствовать некоторые аппаратные узлы (индексные регистры X,Y,
программный стек, память ОЗУ и др.) и, соответственно, отсутствовать
связанные с ними команды. Система же команд ATtiny более позднего времени
выпуска практически аналогична семейству ATmega. Главное отличие обеих
семейств – отсутствие встроенного умножителя у ATtiny (отсутствие группы
команд умножения).
Разные модели ATtiny могут иметь (90…120) команд. ATmega поддерживают
(130…135) инструкций. Так заявлено в спецификациях Atmel. Но фактическое
число, на самом деле, значительно меньше.

Это связано с тем, что в ассемблере AVR встроен ряд макроопределений,


эквивалентных реальным командам, но имеющих иной символический вид. Так,
например, у команды ori Rd,K существует команда двойник sbr Rd,K, которая
выполняет тоже действие (Rd = Rd OR K) и имеет такой же код операции.
Аналогичные псевдокоманды существуют и для разных случаев применения bset
s, bclr s, brbs s,k, brbc s,k, и мн. др.
Способы адресации

Большинство команд ассемблера используют различные ячейки памяти


микроконтроллера и, соответственно, содержат кроме кода операции (КОП)
также их адреса. В зависимости от того в каком виде в команде хранится
адрес различают два способа адресации: прямую и косвенную. В первом
случае адрес ячейки задан явно, а во-втором он находится в одном из
регистров-указателей (у AVR это 16-разрядные регистры X,Y,Z). У
микропроцессоров различного типа, оба способа адресации могут иметь
множество вариаций. Ниже приведены характерные только для
микроконтроллеров AVR.
Разновидности прямой адресации

Прямая адресация регистра общего назначения

В команде присутствует адрес регистра приемника либо источника


результата. Примерами команд могут служить inc Rd, dec Rd, lsl Rd, lsr Rd
и т.д. Адресацию, где в команде кроме адреса регистра находится еще и
константа (ldi Rd,K, ori Rd,K и т.д.), называют также непосредственной,
а в случае сохранения/восстановления данных в стеке (push Rr и pop Rd) –
стековой. Адрес РОН находится в пределах 0…31 (в командах с
непосредственной адресацией 0…15).

Прямая адресация двух регистров общего назначения

Команды данного типа содержат адреса двух РОН, один из которых является
источником, а другой приемником результата в арифметических операциях, а
также операциях пересылки. Примеры команд: mov Rd, Rr, add Rd, Rr, sub
Rd, Rr, and Rd,Rr и т.д. Адреса обоих регистров лежат в пределах 0…31, но
в некоторых командах умножения могут использоваться только РОНы 16…31
(muls Rd,Rr и fmuls Rd,Rr) или 16…23 (mulsu Rd,Rr и fmulsu Rd,Rr).

Прямая адресация регистра ввода-вывода

Прямую адресацию регистра ввода-вывода у AVR используют команды двух


типов. Это копирование РВВ в РОН in Rd,P и пересылка в противоположном
направлении out P,Rr. В обоих случаях могут быть использованы любые
регистры общего назначения 0…31 и регистры ввода-вывода 0…63.

Прямая адресация ОЗУ

Прямая адресация ОЗУ встречается в командах lds Rd,k и sts k,Rr. Первая
инструкция пересылает байта из SRAM микроконтроллера в один из РОНов,
вторая копирует содержимое РОНа в ячейку SRAM. В обеих командах под поле
адреса ячейки памяти отводится 16 битов, а значит, имеется возможность
напрямую обращаться к любому адресу SRAM из диапазона 0…65535. Инструкции
работают со всеми РОНами 0…31 и имеют размер в 2 слова (4 байта).
Разновидности косвенной адресации

Простая косвенная адресация


Простая косвенная адресация применяется для копирования данных из SRAM в
РОН одной из команд ld Rd,X/Y/Z, а также для пересылки в обратном
направлении st X/Y/Z,Rr. В 2-байтовых регистрах-указателях X,Y,Z
содержится адрес ячейки приемника либо источника в диапазоне 0…65535.

Косвенная адресация с преддекрементом

Этот вид адресации подобен простой косвенной адресации за исключением


одного отличая. Перед выполнением операций пересылки значения индексных
регистров X,Y,Z аппаратно уменьшается на единицу, что и символизирует
знак “-” в командах ld Rd,-X/-Y/-Z и st -X/-Y/-Z,Rr.

Косвенная адресация с постинкрементом

При косвенная адресации с постинкрементом значения указателей X,Y,Z


аппаратно увеличивается на единицу (знак “+” перед указателями) после
пересылки байта командами ld Rd,X+/Y+/Z+ и st X+/Y+/Z+,Rr.

Относительная косвенная адресация

Относительная косвенная адресация также используется для пересылки данных


между РОН и SRAM. Однако адрес ячейки памяти определяется здесь как сумма
содержимого указателей Y,Z и фиксированного смещения q. Для пересылки
байта из SRAM в РОН применяются команды ldd Rd,Y+q/Z+q, а для пересылки в
обратном направлении st Y+q/Z+q,Rr. Величина q может лежать в пределах
0…63.

-------------------------------------------------------------------------
---------------------

Арифметические и логические команды

Табл.1. Арифметические и логические команды

Как видно из табл.1, AVR имеют всего 3 разновидности команды сложения.


Инструкции add Rd,Rr (Сложение двух регистров), adc Rd,Rr (Сложение двух
регистров с учётом переноса), позволяют складывать как однобайтовые
числа, так числа произвольной разрядности. В последнем случае для связи
байтов используется флаг переноса C в регистре SREG, который
устанавливается всякий раз, когда разрядность суммы превысит 8 бит. Этот
перенос должен быть добавлен к сумме старших байтов командой adc Rd,Rr:

add R18,R16 ; сложение двухбайтовых чисел


adc R19,R17 ; R19:R18 = R19:R18 + R17:R16

Сложение регистра с константой, к сожалению, отсутствует. И это приносит


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

ldi R17,0x30 ; заносим в R17 константу 0x30


add R16,R17 ; R16 = R17 + 0x30

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


adiw Rdl,K (Сложение константы со словом). Она позволяет прибавить 6-
разрядную константу, лежащую в диапазоне 0…63, к одной из 4х регистровых
пар Rdh:Rdl (R25:R24, R27:R26, R29:R28, R31:R30). С её помощью также
удобно реализовать 16-разрядный счётчик событий:

key_press:
adiw R24,1 ; инкрементируем счётчик R25:R24
sbis PINB,0 ; пока на линии 0 порта PORTB низкий
rjmp key_press ; логический уровень

Из всех арифметических операций, вычитание в AVR - наиболее разнообразно


по способам адресации.

Имеется две команды с прямой адресацией РОН: sub Rd,Rr (Вычитание двух
регистров), sbc Rd,Rr (Вычитание двух регистров с учётом заема). При
совместном использовании они позволяют получить разность чисел любой
разрядности. Для связи байт, при этом, и здесь служит флаг C. Но в
отличие от сложения он устанавливается, когда разность оказалась
отрицательной(Rd < Rr) и имеет в этом случае смысл заема, который должен
быть вычтен из разности старших байт командой sbc Rd,Rr:

sub R18,R16 ; вычитание двухбайтовых чисел


sbc R19,R17 ; R19:R18 = R19:R18 - R17:R16

Ещё две команды с непосредственной адресацией используются для тех же


целей: subi Rd,K (Вычитание константы из регистра) и sbсi Rd,K
(Вычитание константы из регистра c учётом заёма). Эти инструкции очень
универсальны. Их с одинаковым успехом можно применять как в целях
вычитания, так и сложения. В последнем случае необходимо использовать
представление числа K в дополнительном коде (т.е. изменить знак числа –K
= 0xFF+1-K):

subi R16,-0x30 ; R16 = R16 + 0x30 = R16 – (-0x30)

Существует также инструкция, работающая в двухбайтовом формате, sbiw


Rdl,K (Вычитание константы из слова). Подобно сложению adiw Rdl,K, с её
помощью можно вычесть 6-разрядную K (0…63) из тех же самых регистровых
пар (R25:R24, R27:R26, R29:R28, R31:R30). Главная область применения
команды – реализация счётчика циклов:

delay:
sbiw R24,1 ; декрементируем счётчик R25:R24 пока
brne delay ; его содержимое не станет 0, формируя
. ; задержку времени 4*R25:R24 циклов

Группа логических инструкций представлена 6-ю командами.


Операция “НЕ” производится по команде com Rd (Дополнение до одного). При
этом фактически производится действие Rd<0xFF-Rd, при котором
инвертируются все биты регистра. Команды and Rd,Rr (“Логическое И”
регистров), andi Rd,K (“Логическое И” регистра и константы) и or Rd,Rr
(“Логическое ИЛИ” регистров), ori Rd,K (“Логическое ИЛИ” регистра и
константы) производят соответствующие логические операции как с
регистрами, так и регистра с константой. Операция же “Исключающее ИЛИ”
возможна только между регистрами. Для этих целей служит команда eor Rd,Rr
(“Исключающее ИЛИ” регистров). Если провести операцию “Исключающее ИЛИ”
регистра с самим собой (eor Rd,Rd), то будет получен нулевой результат
(Rd<Rd EOR Rd = 0). Это свойство часто применяется, когда необходимо
отчистить регистр, а команда eor Rd,Rd может иметь при этом
альтернативную форму записи clr Rd (Очистить регистр). Кроме того, с
помощью последовательности всего трех команд eor Rd,Rr можно обменять
содержимое двух регистров (команда обмена РОНов у AVR отсутствует)
местами не используя ни одной дополнительной ячейки памяти:

eor R16,R17 ; R16 = R16 XOR R17


eor R17,R16 ; R17 = R17 XOR (R16 XOR R17) = R16
eor R16,R17 ; R16 = (R16 XOR R17) XOR R16 = R17

Все описанные логические операции могут использоваться, например, в


следующем контексте:

in R16,PORTB ;заносим для изменения РВВ PORTB в РОН R16


ori R16,(1«PB0)|(1«PB5) ;устанавливаем биты 0,5 в R16
andi R16,~((1«PB1)|(1«PB2));сбрасываем биты 1,2 в R16
out PORTB,R16 ;выводим изменённое значение R16 в PORTB
eor R16,R16 ;обнуляем R16
out PORTC,R16 ;заносим 0 в PORTC

Как во многих других случаях, у инструкции ori Rd,K существует другая


форма написания sbr Rd,K (Установка бита(ов) в регистре), числящаяся,
однако, как самостоятельная команда. Применяя её необходимо помнить, что,
несмотря на аббревиатуру, на самом деле производится именно операция
“Логическое ИЛИ” регистра и битовой маски, а не установка битов в
регистре. Так, например, для установки бита n в регистре Rd правильно
писать sbr Rd,1<

in R16,PORTB ; заносим для изменения РВВ PORTB в РОН R16


sbr R16,(1«PB0)|(1«PB5) ; устанавливаем биты 0,5 в R16
cbr R16,(1«PB1)|(1«PB2) ; сбрасываем биты 1,2 в R16
out PORTB,R16 ; выводим изменённое значение R16 в PORTB
clr R16 ; обнуляем R16
out PORTC,R16 ; заносим 0 в PORTC

Когда надо установить все биты регистра, то можно воспользоваться


инструкцией ser Rd (Установка регистра), которая заносит константу 0xFF в
регистр Rd. Она является частной формой написания команды ldi Rd,K
(Загрузка константы в регистр), относящейся к группе команд пересылки.
Подобное действие можно осуществить и другими способами. Например, в
результате команды andi R16,0xFF все биты регистра R16 также будут
установленными.

Но в отличие от ser R16 или, что тоже самое, ldi R16,0xFF - будут
изменены значения флагов N и Z, что может повлиять на правильный ход
выполнение программы. По той же причине в ряде случаев для обнуления
регистра лучше использовать ldi R16,0 вместо clr R16 (eor R16,R16); все
флаги в регистре SREG останутся неизменными. Единственное, что мешает
повсеместному применению команды ldi Rd,K, это ограниченный набор РОН с
которыми она работает. Только регистры R16…R31 могут использоваться с
этой инструкцией. Это существенное ограничение наложено и на все
арифметические и логические команды, которые используют непосредственную
адресацию (действие над регистром и константой).

На практике часто необходимо осуществить проверку регистра на нуль. Проще


всего это сделать если произвести операцию “Логическое И” регистра самим
с собой (and Rd,Rd). При этом содержимое Rd останется неизменным, а в
регистре SREG будет установлен флаг нулевого результата Z если Rd = 0.
Параллельно с ним будет переопределён и флаг N, который в случае
использования знаковых чисел будет свидетельствовать об отрицательном
содержимом регистра. Для инструкции and Rd,Rn где Rd = Rn, может быть
использована другая форма записи в виде псевдокоманды tst Rd (Проверка
на нуль и минус).

В тех случаях, когда необходимо изменить знак однобайтового числа


используют команду neg Rd (Дополнение до двух). Она осуществляет действие
Rd<0x00-Rd и используется только для чисел представленных дополнительном
коде.

У AVR имеются также команды прямого и обратного счёта. Первая из них inc
Rd (Инкремент) увеличивает на единицу содержимое регистра, а вторая dec
Rd (Декремент), соответственно, уменьшает. Конечно, действия
инкрементирования и декрементирования могут быть заменены, например, на
прибавление 1 и вычитание 1 из регистра Rd (subi R16,-1 эквивалентно inc
R16, а subi R16,1 тоже, что и dec Rd). Но, несмотря на это обе команды
имеют большое самостоятельное значение. Главная их особенность в том, что
они не влияют на флаг переноса C и тем самым оптимизированы для
формирования циклов, в которых он может использоваться:

ldi R16,SIZE ; производим сдвиг числа,


clc ; находящегося в ОЗУ и состоящего из
rleft: ld R17,X ; SIZE байт на один разряд влево
rol R17
st X+,R18
dec R16
brne rleft
.

Дополняет группу арифметических команд умножение однобайтовых чисел. В


зависимости от формата представления множителя и множимого, существует 6
вариаций данной операции. Произведение, полученное по команде умножения
(2-байтовая величина) заносится в регистровую пару R1:R0. Эта интересная
аппаратная особенность AVR позволяет очень быстро обработать результат,
так как он размещается в РОН и может быть перемещён в любое место всего
одной командой пересылки. Следует, однако, предварительно позаботится о
сохранении содержимого регистров R1, R0 если они используются в
программе.

Для умножения целых чисел разработаны три команды: mul Rd,Rr (Умножение
беззнаковых чисел), muls Rd,Rr (Умножение знаковых чисел), mulsu Rd,Rr
(Умножение знакового числа на беззнаковое).
Знаковые числами должны быть представлены в дополнительном коде. При этом
для первых двух команд безразлично на месте каких операндов будут
находиться множитель и множимое. К одному и тому же результату приведут,
например, mul R16,R17 и mul R17,R16 (muls R16,R17 и muls R17,R16). В
случае mulsu Rd,Rr на месте Rd обязательно должно стоять знаковое число.
Помимо флага Z (один из операндов 0), умножение оказывает влияние и на
флаг переноса C. В него заносится старший бит регистра R1. Таким образом,
в командах muls Rd,Rr, mulsu Rd,Rr он фактически является флагом знака и
устанавливается когда произведение отрицательное. Необходимо также
помнить, что инструкция muls Rd, Rr может использоваться только с
регистрами R16…R31. Ещё меньше поддерживает команда mulsu Rd,Rr, только
R16…R23.

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


умножения дробных чисел с фиксированной запятой. Главная задача, где это
может быть необходимо, - цифровая обработка сигналов. Для дробного
умножения имеются команды: fmul Rd,Rr (Умножение дробных беззнаковых
чисел), fmuls Rd,Rr (Умножение дробных знаковых чисел), fmulsu Rd,Rr
(Умножение дробного знакового числа на беззнаковое). Все команды работают
с числами в формате (1.7). Старший бит регистра является целой частью
числа, а младшие 7 битов отведены под дробную. Произведение приводится к
формату (2.14). Для этого производится сдвиг результата на один разряд
влево так, что старший разряд оказывается во флаге C. Все инструкции
дробного умножения работают только с регистрами R16…R23.

-------------------------------------------------------------------------
------------

Команды пересылки данных

Табл 2. Команды пересылки данных:

Группа команд пересылки данных сведена в табл.2. Под определением


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

В пределах РОН пересылка производится с помощью команды mov Rd,Rr


(Пересылка между регистрами). Кроме этого, имеется возможность
перемещения двухбайтовых чисел (регистровых пар R1:R0, R3:R2,…, R29:R28,
R31:R30) с помощью команды movw Rd,Rr (Пересылка между регистровыми
парами), где на месте операндов Rd и Rr должны стоять младшие регистры в
обозначениях регистровых парах приёмника и источника соответственно
(например, movw R0, R30 для пересылки R1:R0 < R31:R30). Обе эти команды
выполняются за один машинный цикл:

mov R17,R16 ; возведение в квадрат R16


mul R17,R16 ; с помещением двухбайтового результата
movw R16,R0 ; в регистровую пару R17:R16

Для загрузки константы в регистр служит команда с непосредственной


адресацией ldi Rd,K (Загрузка константы в регистр). Она работает только
со старшими РОНами (R16…R31).

Команды для работы с SRAM микроконтроллера используют, в основном,


косвенную адресацию. Для этих целей применяются три 16-разрядных
индексных регистра X,Y,Z, которые находятся в адресном пространстве РОН и
по совместительству также являются регистровыми парами R27:R26, R29:R28,
R31:R30.

Копировать байт из памяти ОЗУ в РОН можно любой из команд: ld Rd,X, ld


Rd,Y, ld Rd,Z (Косвенное чтение из памяти данных). В этом случае в
регистре приёмнике окажется содержимое ячейки памяти данных, адрес
которой находится в одном из индексных регистров. Кроме этого, существует
ещё две вариации данного действия. В одной из них после копирования
производится инкрементирование регистров X,Y,Z, на что указывает знак “+”
в командах: ld Rd,X+, ld Rd,Y+, ld Rd,Z+ (Косвенное чтение из памяти
данных с постинкрементом). Во втором случае, перед пересылкой, содержимое
индексного регистра сначала уменьшается на 1 (знак “-” в команде): ld
Rd,-X, ld Rd,-Y, ld Rd,-Z (Косвенное чтение из памяти данных с
преддекрементом).
Команды косвенного чтения с постинкрементом/преддекрементом очень
эффективны при работе с массивами однотипных данных. Для обращения же к
элементам структуры (набору данных разного типа) удобно использовать ld
Rd,Y+q, ld Rd,Z+q (Косвенное относительное чтение из памяти данных), в
которых в качестве указателей используется Y,Z со смещением q (адрес
ячейки памяти определяет сумма (Y)+q или (Z)+q). Смещение q в командах -
фиксированная величина, лежащая в пределах 0…63.

Пересылка данных из РОН в ОЗУ посредством косвенной адресации реализуется


с помощью команд: st X,Rr, st Y,Rr, st Z,Rr (Косвенная запись в память
данных). Содержимое регистра в них копируется в ячейку памяти, адрес
которой определяется указателями X,Y и Z соответственно. Точно также
существуют команды загрузки с постинкрементом st X+,Rr, st Y+,Rr, st
Z+,Rr (Косвенная запись в память данных с постинкрементом) и с
преддекрементом индексного регистра st -X,Rr, st -Y,Rr, st -Z, Rr
(Косвенная запись в память данных с преддекрементом). Ещё две команды
этой группы используют косвенную адресацию со смещением: st Y+q,Rr, st
Z+q,Rr (Косвенная относительная запись в память данных). Здесь смещение q
может находится в пределах 0…63.

Как видно между командами выгрузки из памяти и загрузки в память


существует полная симметрия. У каждой операции пересылки существует
аналогичная, для пересылки в обратном направлении, причём с
использованием трёх равноправных индексных регистров. Кроме того все они
выполняются за один промежуток времени в 2 машинных цикла. Типичный
пример их совместного использования может выглядеть следующим образом:

ldi YL, low(buffer1) ; копирование SIZE байт из


ldi YH, high(buffer1) ; буфера buffer1 в буфер buffer2
ldi ZL, low(buffer2) ; перед копированием в индексные
ldi ZH, high(buffer2) ; регистры Y и Z заносятся адреса
ldi R16,SIZE ; буферов buffer1 и buffer2
copy: ld R17,Y+
st Z+,R17
dec R16
brne copy
.

Копирование данных из ОЗУ в РОН и обратно может быть произведено и с


использованием прямой адресации по командам lds Rd,k (Прямое чтение из
памяти данных) и sts k,Rr (Прямая запись в память данных) соответственно.
В этом случае двухбайтовый адрес k ячейки памяти находится в коде
операции, а сами команды занимают 2 слова (4 байта) памяти программ.

Все вышеуказанные команды пересылок работают в едином адресном


пространстве где под РОН отведены адреса 0x00…0x1F, под РВВ от 0x20…0x5F,
остальное под ячейки ОЗУ. Поэтому, например, копирование R16<R17 может
быть произведено разными путями: mov R16,R17, lds R16,0x0011, ld R16,X (в
регистре X адрес 0x0011) и т.д. Первый способ здесь, конечно, более
предпочтительный (использует пересылку типа регистр-регистр и выполняется
быстрее), но остальные команды позволяют получить доступ к произвольному
элементу любой области памяти и поэтому очень универсальны.

Для обращения к РВВ служат команды: in Rd,P (Ввод из порта), out P,Rr
(Вывод в порт). Первая считывает значение РВВ в один из РОНов, а вторая
производит пересылку РОН в РВВ.

Поскольку напрямую модифицировать РВВ невозможно, приходится сначала


копировать их содержимое в рабочие РОН, производить необходимые
изменения, а потом заносить обратно:
in R16,PORTC ; запись и модификация РВВ,
andi R16,0b00001111 ; отвечающего за содержимое порта
out PORTC,R16

Тоже самое относится и к данным, расположенным в ОЗУ:

lds R16,0x0200 ; запись и модификация байта из ОЗУ,


inc R16 ; находящегося по абсолютному адресу 0x0200
sts 0x0200,R16

В группе команд пересылки данных имеются две, специально разработанные


для работы со стеком. Это push Rr (Сохранение в стеке) и pop Rd
(Извлечение из стека). Не смотря на то, что основное назначение стека это
сохранение адресов возврата при вызове подпрограмм, эти инструкции
позволяют использовать его и для оперативного сохранения данных,
находящихся в РОН. Командой push Rr содержимое регистра копируется в
ячейку памяти (вершину стека), адрес которой содержится в указателе стека
SP, после чего значение SP уменьшается на 1. По команде pop Rd указатель
стека возвращается на предыдущий элемент (SP+1), а значение, байта
записанное по этому адресу копируется в регистр.

Таким образом реализуется память магазинного типа, где вообще не надо


заботиться о задании адресов сохраняемых и восстанавливаемых из в памяти
ОЗУ данных. Единственное, про что всегда необходимо помнить, - это
порядок доступа к элементам стека Last In First Out (Первый Вошел
Последний Вышел). Очередность, в которой регистры восстанавливаются из
стека, должна быть обратной по отношению к очередности сохранения
регистров:

ldi R16, low(RAMEND) ;установка вершины стека


out SPL,R16
ldi R16, high(RAMEND)
out SPH,R16
.
push R16 ;сохранение регистров в стеке
push R17
push R18
.
pop R18 ;восстановление регистров из стека
pop R17
pop R16

Имеется и другая возможность программно реализовать стек данных. Команды


ld Rd,X+, ld Rd,Y+, ld Rd,Z+ по сути являются иной реализацией действия
push Rr, а st -X,Rd, st -Y,Rd, st -Z, Rd подобны pop Rd. Разница состоит
лишь в том, что в качестве индексного регистра выступает не SP, а один из
регистров–указателей (X,Y,Z) и вершина стека перемещается в сторону
увеличения адресов ОЗУ:

ldi ZL, low(stack) ;установка вершины стека


ldi ZH, high(stack)
.
ld R16, Z+ ;сохранение регистров в стеке
ld R17, Z+
ld R18, Z+
.
st -Z, R18 ;восстановление регистров из стека
st -Z, R17
st -Z, R16

Учитывая то, что SP расположен в пространстве РВВ, а X,Y,Z в РОН,


использование эмуляции стека может быть даже более предпочтительной.
Регистры X,Y,Z могут быть быстрей модифицированы и кроме того отсутствует
опасность повредить их содержимое при вызове подпрограмм и возникновении
прерываний.

Подобно большинству микроконтроллеров, у AVR имеется возможность хранения


таблиц констант в памяти программ. Для их чтения разработаны команды lpm
(Загрузка памяти программ), lpm Rd,Z (Загрузка памяти программ), lpm
Rd,Z+ (Загрузка памяти программ с постинкрементом). Все они переписывают
содержимое байта из FLASH памяти программ (адрес байта находится в Z) в
один из РОН. С помощью первой инструкции, не имеющей параметров,
копирование производится в R0. Вторая и третья модификации команды
используют в качестве приёмника любой РОН. Команда lpm Rd,Z+ при этом
осуществляет ещё и инкрементирование индексного регистра Z после
считывания. Типичный пример копирования строки из памяти программ в ОЗУ
может выглядеть следующим образом:

ldi ZL,low(2*string) ;копирование строки "Hello World"


ldi ZH,high(2*string);длиной 11 байт из памяти программ
ldi YL,low(buffer) ;в ОЗУ микроконтроллера по
ldi YH,high(buffer) ;адресу buffer
ldi R16,11
cycle: lpm R17,Z+
st Y+,R17
dec R16
brne cycle
.

.org 0x1000
string: .db "Hello World", 0

Здесь последовательно осуществляется считывание строки "Hello World !"


(13 байт в кодировке ASCII) расположенной в памяти программ начиная с
адреса 0x1000 (задан директивой .org 0x1000). Для резервирования FLASH
памяти используется директива ассемблера .db, после которой
непосредственно следуют данные. Строка переписывается в ОЗУ по адресу
buffer.

Есть два важных момента при использовании операций такого типа. В


приведённом выше фрагменте программы в качестве указателя на строку в
индексный регистр Z заносится удвоенный адрес метки 2*string и это не
случайность. Дело в том, что метке string соответствует адрес слова
программ 0x1000. Но слово программ у AVR имеет блину 2 байта, а в
указатель Z необходимо занести именно адрес байта т.е. 2*0x1000 = 0x2000.
По той же причине в памяти программ необходимо выделять только чётное
(кратное двум) количество байтов и если это не так, то надо добавлять
незначащий байт 0 в самом конце, как это и случилось в нашем примере
(строка "Hello World !" содержит 13 байт). Попутно заметим, что строки
символов ASCII, как правило, и хранят в таком формате (добавляют в конце
0). Это позволяет автоматически распознавать конец строки и считывать, не
зная заранее её длины.

Использование 2-байтового регистра Z позволяет адресовать только 64 кб


при считывании памяти программ. Этого явно недостаточно для тех моделей
AVR, которые имеют 128 и 256 кб FLASH-памяти. Для того чтобы работать во
всём диапазоне адресов к указателю Z в этом случае добавляется регистр
RAMPZ из пространства РВВ, в котором используются 1 или 2 младших бита.
Этот 3-байтовый индексный регистр RAMPZ:ZH:ZL используют инструкции elpm
(Расширенная загрузка памяти программ), elpm Rd, Z (Расширенная загрузка
памяти программ), elpm Rd,Z+ (Расширенная загрузка памяти программ с
постинкрементом).

Последняя инструкция в группы команд пересылки spm (Запись памяти


программ). Она реализует возможность самопрограммирования
микроконтроллеров AVR. Конкретное действие, которое выполняет эта
команда, зависит от установок в управляющем РВВ SPMCSR. Это может быть
стирание страницы памяти программ, занесение данных для записи во
временный буфер или копирование буфера в память программ, а также чтение
ячеек идентификаторов и защиты. В любом случае для задания адреса области
используется указатель Z, а данные, если они необходимы, передаются в
регистровой паре R1:R0. Так как действие данной инструкции связано с
модификацией программного кода, для ее корректного применения предпринят
ряд мер предосторожности, о чём подробно будет сказано в разделе
“Самопрограммирование микроконтроллеров AVR”.

-------------------------------------------------------------------------
----------------------

Команды передачи управления

Табл 3. Команды передачи управления:

Все команды группы передачи управления приведены в табл.3.


Микроконтроллеры AVR могут содержать до 4-х разновидностей команды
безусловного перехода. Команды такого типа модифицируют
содержимое программного счётчика, после чего программа продолжает
выполняться с нового места (адреса перехода). Безусловный переход в
памяти программ происходит не зависимо от каких либо условий, флагов
программы и т.д.

Инструкция rjmp k (Относительный безусловный переход) позволяет


осуществить переход в диапазоне +2047…-2047 слов в памяти программ от
места где она расположена. Адрес перехода при этом зависит от текущего
значения программного счётчика и вычисляется как смещение PC+1+k.

Команда ijmp (Косвенный безусловный переход) производит переход в памяти


программ в пределах 0…65535 слов по адресу, находящемуся в индексном
регистре Z. В этом случае адрес перехода является переменной величиной
доступной из программы. Это даёт возможность легко реализовать процедуру
ветвления, когда в зависимости от условий должен быть выполнен тот или
иной фрагмент кода.

В моделях с объёмом памяти программ 256 кб доступна также команда eijmp


(Расширенный безусловный косвенный переход). Она использует 3-байтовый
указатель адреса EIND:ZH:ZL. В регистре EIND из пространства РВВ
находится 17-тый бит адреса, а переход осуществляется в пределах 0…131071
слов.

Команда jmp k (Абсолютный безусловный переход) - переход по адресу k в


любую точку из любого места программы. Абсолютный адрес перехода
находится в коде операции, а команда занимает 2 слова (4 байта) и
выполняется в течении 3-х машинных циклов.

При использовании команд передачи управления в качестве параметров обычно


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

ldi ZL,low(label) ;косвенный переход на метку label


ldi ZH,high(label)
ijmp
.
jmp label ;абсолютный переход на метку label
.
rjmp label ;относительный переход на метку label
.

.org 0x0200
label:. ;метка в программе по адресу 0x0200

Другим типом команд являются вызовы подпрограмм. Существуют следующие их


разновидности: rcall k (Относительный вызов подпрограммы), icall
(Косвенный вызов подпрограммы), eicall (Расширенный косвенный вызов
подпрограммы) и jmp k (Абсолютный вызов подпрограммы). Функционируют
такие инструкции аналогично командам безусловного перехода, но с одним
существенным отличием. Перед переходом в памяти программ, адрес следующей
команды предварительно сохраняется в специально отведённой для этих целей
области памяти (стеке). Таким образом, находясь в любой точке программы,
сохраняется возможность вернутся в то место, из которого был осуществлен
вызов. Это действие осуществляет команда ret (Возврат из подпрограммы).
Она загружает в PC сохранённый в стеке адрес возврата, после чего
программа продолжает выполняться со следующей команды после rcall k,
icall, eicall, call k:

ldi ZL,low(func1);косвенный вызов подпрограммы func1


ldi ZH,high(func1)
icall
.
call func1 ;абсолютный вызов подпрограммы func1
.
rcall func1 ;относительный вызов подпрограммы func1
.

func1:. ;подпрограмма func1


call func2 ;абсолютный вызов подпрограммы func2
.
ret ;возврат из подпрограммы func1

func2:. ;подпрограмма func2


ret ;возврат из подпрограммы func2

В приведенном примере подпрограмма func1 может вызываться много раз из


различных мест программы. Кроме того, в теле самой подпрограммы func1
могут содержаться вызовы различных подпрограмм (вызов func2 и т.д.).
Такая организация программы позволяет максимально эффективно использовать
ресурсы процессора, делает код более наглядным и позволяет многократно
использовать разработанные подпрограммы в дальнейших проектах.

Еще одним скрытым механизмом вызова подпрограмм являются аппаратные


прерывания. При их возникновении происходит вызов подпрограммы
(обработчика прерывания) по фиксированному адресу (вектору прерывания),
при этом адрес возврата также запоминается в стеке. Но в отличии от
вызова по командам rcall k, icall, eicall, call k ещё и автоматически
сбрасывается флаг глобального разрешения прерываний I в регистре SREG,
т.е. запрещаются прерывания во время прерывания. Для выхода из прерывания
используется команда reti (Возврат из прерывания) которая, загружает в PC
адрес возврата и восстанавливает флаг I (разрешает прерывания при выходе
из прерывания):

.org 0x0002
rjmp service_INT0 ;вектор внешнего прерывания INT0

.org 0x0100
service_ INT0: ???????? ;обработчик прерывания INT0
reti ;возврат в основную программу

Иногда командой reti удобно завершить и обычную подпрограмму. Так бывает,


когда, например, при выходе необходимо разрешить прерывания. В этом
случае она фактически заменяет собой две инструкции sei (Разрешение
прерываний) и ret:

func: cli ;глобальный запрет прерываний (I=0)


.
reti ;выход из подпрограммы и разрешение прерываний

Время выполнения rcall k, icall, call k, ret, reti зависит от величины


программного счётчика конкретной модели. Если его разрядность более 16
бит (? 128 кб FLASH памяти), то для сохранения 3-байтового адреса
возврата требуется на один машинный цикл больше.

В данную группу команд включены также 4 инструкции сравнения: cp Rd,Rr


(Сравнить регистры), cpс Rd,Rr (Сравнить регистры с учётом переноса), cpi
Rd,K (Сравнить регистр c константой), cpse Rd,Rr (Сравнить регистры и
пропустить команду если они равны). Строго говоря первые три из них не
являются командами передачи управления, так как не могут оказать влияние
на счётчик программ. Единственным их предназначением является сравнение
числовых величин, находящихся в РОН. Как видно из описания, команда cp
Rd,Rr аналогична sub Rd,Rr, вместо cpc Rd,Rr фактически выполняется sbc
Rd,Rr, а вместо cpi Rd,K - cubi Rd,K. Разница заключается только лишь в
том, что содержимое регистра Rd остаётся неизменным. При этом
переопределяется значение флагов Z,C,S, N,V,H регистра SREG по которым и
можно судить о соотношении между числовыми величинами. В частности,
сравнение двухбайтовых чисел находящихся в регистровых парах R19:R18
R17:R16 можно выполнить следующим образом:

cp R18, R16 ; сравнение 2-байтовых чисел


cpc R19, R17 ; R19:R18 - R17:R16

В этом примере флага C окажется установленным, когда R19:R18< R17:R16


(сброшенным, когда R19:R18>R17:R16). Что касается флага Z, то в данном
случае он не может быть критерием проверки на нуль (Z=1 будет
свидетельствовать только о равенстве R19=R17+C).

Стоит заметить также, что, подобно всем остальным командам, использующим


непосредственную адресацию, cpi Rd,K может работать только с регистрами
R16…R31.

Инструкция cpse Rd,Rr выполняется иначе. Она относится к типу Test and
Skip (Проверка и пропуск). По логике работы команды такого рода выполняют
проверку определённого условия и если оно истинно (в данном случае если
Rd=Rr), то следующая в тексте команда пропускается. При этом все флаги
программы остаются неизменными.

Помимо этого существуют ещё 4 команды Test and Skip ориентированных на


проверку состояния определённого бита в регистрах. Две из них работают с
РОН: sbrc Rr,b (Пропуск команды, если бит регистра сброшен), sbrs Rr,b
(Пропуск команды, если бит регистра установлен). Ещё две с РВВ: sbic P,b
(Пропуск команды, если бит регистра ввода-вывода сброшен), sbis P,b
(Пропуск команды, если бит регистра ввода-вывода установлен). Если бит
b(0…7) установлен при выполнении команд sbrs Rr,b и sbis P,b или сброшен
для sbrc Rr,b, sbic P,b, то производится пропуск следующей команды.
Нижеприведённый код демонстрирует программную задержку до тех пор пока не
будет нажата кнопка подключённая к линии 1 порта B (пока бит 1 РВВ PINB
не будет сброшен):

button_press:
sbic PINB,1 ; задержка пока на выводе 1 порта B
rjmp button_press ; не появится низкий уровень
.

В отличии от команд sbrs Rr,b, sbrc Rr,b, которые работают со всеми без
исключения РОН, инструкции sbis P,b, sbic P,b могут использовать только
первые 32 РВВ. Если учесть, что число управляющих РВВ на много больше то,
это довольно существенное ограничение. Конечно те РВВ, доступ к битам
которых наиболее важен, разработчики постарались разместить именно в этой
области. К ним относятся все регистры управления портами A,B,C,D,E,F, а
также ряд других, отвечающих за работу EEPROM, USART, SPI т.д. Во всех
остальных случаях необходимо cкопировать содержимое РВВ в один из РОН и
уже дальше анализировать состояние соответствующего бита. Программное
ожидание сброса бита 2 порта J должно выглядеть следующим образом:

button_press:
lds R16,PINJ ; задержка пока на выводе 2 порта J
sbrc R16,2 ; не появится низкий уровень
rjmp button_press
.

Время выполнения инструкций Test and Skip может быть различным в


зависимости от того пропускается следующая команда или нет. В первом
случае необходимо 2 машинных цикла, во втором 1. Если же производится
пропуск “длинной” команды состоящей из двух слов (lds Rd,k, jmp k и т.д.)
на выполнение операции уйдёт 3 цикла.

Флаги программы регистра состояния также не доступны инструкциям sbis


P,b, sbic P,b, но из-за важности их значения предусмотрены две команды
условного перехода разработанных специально для работы с SREG: brbs s,k
(Переход, если бит регистра SREG установлен), brbc s,k (Переход, если бит
регистра SREG сброшен). Когда соответствующий бит s в SREG установлен для
brbs s,k или сброшен для brbс s,k производится относительный переход в
пределах +63…-63 слов (PC+k+1).

Ассемблер AVR поддерживает по 9 различных форм написания инструкций brbs


s,k, brbc s,k для разных значений флагов программы (табл.4).

Табл 4. Команды условных переходов по состоянию флагов SREG:

В ходе программы, как правило, наиболее часто производится проверка


флагов Z и C. Если установка флага Z однозначно свидетельствует о нулевом
результате операции, то установка флага переноса C может иметь разный
смысл в зависимости от того, какая команда оказывает на него влияние.

Следующая подпрограмма осуществляет инкрементирование 4-байтового


счётчика R27:R26:R25:R24, а проверка флага C производится с целью
установить, происходит ли переполнение младшего слова R25:R24, и если да,
то к старшей регистровой паре R27:R26 добавляется 1:

inc_cnt:
adiw R24,1 ; добавление 1 к регистровой паре R25:R24
brcc PC+2 ; и если возникает перенос, то
adiw R26,1 ; увеличение на 1 регистровой пары R27:R26
ret

Другим примером, где необходимо знать состояние флага C, может служить


сравнение чисел:

cpi R16,0x40 ; сравнить содержимое R16 с числом 0x40


brlo ulo ; перейти наметку ulo если R16 < 0x40
.
ulo: .

Вместо brcc k, здесь используется более подходящая по смыслу форма


написания инструкции brlo k (переход если R16 меньше 0x40).

Подобный понятный символический вид имеют и остальные команды условных


относительных переходов: breq k(Переход если равно 0), brne k(Переход
если равно 0), brie k (Переход если прерывания разрешены), brid k
(Переход если прерывания запрещены) и т.д.

-------------------------------------------------------------------------
-----------------

Группа команд операций с битами

Табл 5. Команд битовых операций

Табл 5. Команд битовых операций

Команды данной группы доступны всем без исключения моделям AVR и сведены
в табл.5.

Управлять отдельными битами первых 32 РВВ возможно благодаря командам sbi


P,b (Установить бит в регистре ввода-вывода) и cbi P,b (Сбросить бит в
регистре ввода-вывода). Обе команды выполняются за 2 машинных цикла.

При управлении разрядами портов микроконтроллера необходимо помнить о


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

sbi PORTC,PC0 ; установка высокого уровня на линии 0 порта С


cbi DDRC,DDC0 ; настройка линии 0 порта С на ввод
rjmp PC+1 ; задержка 2 машинных цикла
sbic PINC,PINC0 ; чтение значения линии 0 порта С и программный
rjmp bit_set ; переход на метку bit_set если высокий уровень
rjmp bit_clear ; или на метку bit_set если низкий уровень
.
bit_set:
.
bit_clear:
.

Следующим типом команд являются сдвиговые операции, которых у AVR 5


разновидностей. Команды lsl Rd (Логический сдвиг влево), lsr Rd
(Логический сдвиг вправо) осуществляют сдвиг содержимого регистра Rd на
один разряд вправо и влево соответственно. При этом в младший бит после
команды lsl Rd (в старший после lsr Rd) заносится 0. Например,
результатом сдвига числа 0b11010111 = 0xD7 влево будет 0b10101110 = 0xAE,
а вправо 0b01101011 = 0x6B.

Так или иначе старший бит результата при lsl Rd и младший при lsr Rd
теряется. В тех случаях, когда это недопустимо, необходимо использовать
циклический сдвиг rol Rd (Вращение влево через флаг переноса С) и ror Rd
(Вращение вправо через флаг переноса C). Обе команды функционируют
подобно lsl Rd, lsr Rd, но с одним важным отличием: старший разряд Rd
после команды rol Rd (младший после ror Rd) заносятся во флаг переноса С
из регистра флагов SREG. При этом содержимое самого бита С предварительно
копируется в младший разряд регистра при rol Rd (старший после rol Rd).
Так при циклическом сдвиге того же числа 0b11010111 = 0xD7 влево получим
0b1010111c, а вправо 0bc1101011, где на месте c будет значение C до
выполнения команды. После операции флаг C окажется установленным (у числа
0b11010111 = 0xD7 MSB, MLB = 1). Обратим внимание на то, что вместо lsl
Rd на самом деле выполняется add Rd,Rd, а вместо rol Rd - adс Rd,Rd.

Ещё одна с виду необычная сдвиговая команда asr Rd. (Арифметический сдвиг
вправо) разработана для деления на два знаковых чисел, представленных в
дополнительном коде. В ходе операции все биты регистра сдвигаются на один
разряд вправо (как при lsr Rd), кроме старшего 7-го, который остается
неизменным (MSB является знаком результата и не может изменится при
делении на положительное число 2). Если применить asr Rd к регистру в
котором находится, например, число 0b11010010 = -46, то получим
0b11101001 = -23. Разумеется такое деление будет безошибочным, только для
знаковых 1-байтовых чисел из диапазона -128…127.

Следующая команда swap Rd (Переставить полубайты в регистре) меняет


местами старшую и младшую тетрады любого РОН и при этом не оказывает
влияние ни на один из флагов регистра SREG. Эта инструкция очень удобна,
например, для работы с двоично-кодированными десятичными числами. С ее
помощью также легко организовать, например, умножение на 16:

swap R16 ; умножение на 16 1-байтового числа


mov R17,R16 ; R17:R16 = 16*R16
andi R16,0xF0
andi R17,0x0F
В AVR пересылку бита из одного регистра в другой можно легко осуществить,
используя команды bst Rr,b (Запись бита во флаг T), bld Rd,b (Чтение
бита из флага T). Первая команда копирует бит b из регистра Rd во флаг T
из РВВ SREG. Вторая производит обратное действие – перемещает содержимое
флага T в соответствующий бит b регистра Rd. Поскольку флаг T является
надежным хранилищем бита информации (не одна арифметическая и логическая
команды на него не влияют), то между командами сохранения и
восстановления может быть выполнен такой объем кода, какой требует логика
программы.

В группе битовых операций отдельно существуют две инструкции специально


разработанные для установки и сброса битов РВВ SREG (флагов программы).
Это bset s (Установить флаг), bclr s (Сбросить флаг) соответственно.
Других способов напрямую влиять на флаги нет, ведь sbi P,b и cbi P,b, как
уже говорилось не будут работать с SREG, адрес в пространстве ввода-
вывода у которого 0x3F. Под s в операнде понимается номер флага. Таким
образом для установки флага C необходимо записать bset 0, для сброса Z,
например, bclr 1 и т.д. что, конечно, неудобно. Поэтому на практике
пользуются более наглядными псевдокомандами. Для bset s это sec
(Установить С), sez (Установить Z), sen (Установить N), sev (Установить
V), ses (Установить S), seh(Установить H), set (Установить T), sei
(Установить I). Для bclr s это clc (Сбросить С), clz (Сбросить Z), cln
(Сбросить N), clv (Сбросить V), cls (Сбросить S), seh (Сбросить H), clt
(Сбросить T), cli (Сбросить I).

-------------------------------------------------------------------------
--------------------

Группа команд управления процессором

Табл 6. Команды управления процессором

К группе команд управления процессором (табл.6) относятся 4 инструкции,


которые не связаны с работой ЦПУ но, так или иначе, оказывают влияние на
его аппаратные ресурсы.

Команда nop (Отсутствие операций) необходима для формирования точных


временных интервалов. Она заставляет микроконтроллер затратить один
холостой цикл машинного времени в течении которого процессор
бездействует. Работа конвейера команд при этом не нарушается. По логике
работы “пустая операция” nop может быть заменена на mov Rd,Rd или movw
Rd,Rd, которые тоже выполнятся за 1 цикл и не изменят ни содержимое Rd ни
SREG.

Команда sleep (Переход в спящий режим) переводит процессор в спящий режим


работы. В этом состоянии происходит полная остановка АЛУ и еще ряда
периферийных устройств. Какие именно узлы будут отключены зависит от вида
спящего режима (имеется до 6-ти режимов). Сам же выбор спящего режима
задается соответствующими установками в управляющем РВВ MCUCR (для ряда
моделей также через MCUCSR,EMCUCR,SMCR). Дальнейшее выполнение программы
(выход из спящего режима) возможно лишь в результате прерывания от
работающего модуля или сброса микроконтроллера.

Обязательным узлом в составе современных микроконтроллеров является


сторожевой таймер WDT (Watchdog Timer). Это независимый от процессора
внутренний RC-генератор, который у AVR имеет частоту 128 кГц, и счетчик
импульсов с программируемым коэффициентом счета. После поступления
определенного числа импульсов счетчик переполняется и выдает сигнал
сброса. Каждый раз когда в тексте программы встречается команда wdr
(Сброс сторожевого таймера) происходит обнуление счетчика и сброс
микроконтроллера откладывается на интервал времени необходимый до
следующего переполнения. Таким образом при нормальном ходе программы и
включенном сторожевом таймере перезагрузка не настанет никогда. Но если
вдруг возник какой-либо непредвиденный сбой в результате которого
произошло “зависание” программа, то инструкция wdr не будет выполнена и
произойдет сброс. Хотя использование WDT значительно повысит стабильность
работы устройства, его нужно использовать с осторожностью. Необходимо
ставить инструкции wdr в тот участок кода который, гарантировано
выполняется с такой периодичностью, чтобы счетчик таймера не
переполнился. За управление сторожевым таймером отвечает РВВ WDTCR
(WDTCSR) и ячейка конфигурации WDTON.

Последняя команда данной группы break (Остановка) доступна только


моделям, имеющим на борту отладочный интерфейс JTAG (Joint Test Action
Group) или dW (debugWire) и может быть необходима только в процессе
отладки приложения. Инструкция является обыкновенной программной точкой
остановки. Встречая её в ходе программы процессор останавливает свою
работу и передаёт управление встроенному отладчику, через который можно
проанализировать текущее состояние регистров, памяти и т.д. Пропуская
через дешифратор команд инструкцию break, а также любой неизвестный код
операции, микроконтроллер интерпретирует ее как пустую команду nop.

-------------------------------------------------------------------------
---------------

Разработка программ в IDE AVR Studio

В поддержку своей архитектуры в 1997 г. Atmel выпустила AVR Studio -


программный продукт для разработки приложений на основе AVR-
микроконтроллеров. AVR Studio представляет собой интегрированную среду
разработки IDE (Integrate Development Environment), объединяя в себе
большое количество различных инструментов для написания и отладки
программ. Продвигая на рынке новую продукцию, Atmel с самого начала
попыталась сделать ее максимально открытой для потребителя. AVR Studio не
является исключением. Последняя версия IDE, равно как и любая информация
по AVR-микроконтроллерам, всегда свободно доступна на сайтах
производителя.

Минимальный набор разработчика в IDE представлен фирменным ассемблером и


симулятором. Однако AVR Studio легко интегрируется со многими
программными средствами сторонних производителей. И на сегодняшний день,
в частности, в нее включена поддержка постоянно развивающегося
компилятора языка Си WinAVR основанного на принципах GNU. Это очень
мощный и к тому же бесплатный инструмент, что очень редко встречается у
микроконтроллеров уровня AVR.
Принцип работы ассемблера

Ассемблер относится к языкам программирования низкого уровня. Его основой


является множество команд, уникальное для каждого микропроцессора.
Поэтому ассемблер является также и аппаратно-зависимым. Он может
использоваться только совместно с архитектурой определенного типа. Каждое
семейство микропроцессоров имеет свой собственный вариант этого языка.
Каждая инструкция ассемблера представляет собой символическое изображение
соответствующей машинной команды со своим кодом операции (КОП). Команды
ассемблера имеют удобочитаемый вид и названия, ассоциирующиеся с их
действием. Так команда пересылки между двумя РОНами mov Rd,Rr является
прототипом 16-разрядного кода операции 0010 11rd dddd rrrr. Битовые поля
ddddd и rrrrr в нем определяют адреса регистров приемника и источника
соответственно. Например, для пересылки регистра R5 в R22 необходимо
записать на ассемблере mov R22,R5 или 0010 1101 0110 0101 на машинном
языке. Разница в восприятии очевидна.

Каждую команду ассемблера можно логически разделить на две части:


мнемонику и операнды.
Мнемоника Операнд 1 Операнд 2
mov Rd, Rr

Мнемоника является обязательной частью команды и определяет ее


функциональное назначение. Операнды представляют собой параметры команды.
В качестве операндов могут выступать числовые значения, адреса и смещения
относительно адресов. Команды AVR, в зависимости от назначения, могут
иметь до двух параметров.

Главная задача ассемблера – преобразование исходного текста


пользовательской программы в машинный код, пригодный для записи в память
программ микроконтроллера. Сам процесс преобразования называют
компиляцией, а программу ассемблер – компилятором.

Конечным результатом работы компилятора является файл с исполняемым


кодом. Если программа использует инициализированные данные, размещенные в
энергонезависимой памяти, то в дополнении к этому, будет сгенерирован
также файл для EEPROM. Информация, размещенная в этих файлах,
используется программатором при программировании FLASH-память программ
и/или EEPROM-память данных.

Существует большое количество различных форматов выходных файлов, но


самый распространенный из них 16-тиричный Intel Hex Format. Файлы такого
типа, как правило, имеют два различных расширения: .hex у файлов
содержащих коды программ, и .epp у файлов, содержащих данные для записи
EEPROM-памяти.

В соответствии со своей технологией работы, компилятор создает также


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

После сборки проекта могут быть сгенерированы также файлы, имеющие


расширение .lst (файл листинга), .map и некоторые другие. В первом из них
находится полный отчет о проделанной компилятором работе. Во втором
приводится перечень всех символьных имен, встретившихся в программе, и их
числовых значений.

-------------------------------------------------------------------------
-----------------

Синтаксис ассемблера
Исходный текст программы на языке ассемблера может быть создан в любом
текстовом редакторе и должен быть размещен в файлах с расширениями .asm,
.inc или .txt. Удобнее всего для этих целей использовать встроенный в AVR
Studio редактор.

Алфавит ассемблера состоит из набора символов ACSII. Однако в


комментариях к программам допускается использовать любой удобный язык,
основанный на наборе символов в кодировке UNICODE. Изначально ассемблер
не чувствителен к регистру символов. Так, например, между тремя именами
переменной TEMP, temp, Temp нет ни какой разницы.

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


системах исчисления:
десятичной (примеры чисел: 10, 255),
шеснадцатиричной (примеры чисел: 0x0A, $FF),
двоичной (примеры чисел: 0b00001010, 0b11111111),
восмиричной (примеры чисел: 012, 0377).

По умолчанию применяется десятичная система, и любое число без префикса


будет воспринято именно как десятичное. Необходимо обратить внимание
также на то, что ассемблер AVR не поддерживает распространенную форму
записи 16-тиричных чисел c постфиксом “H” (1AH, 0FFH).

В табл.1 приведен список операторов, доступных для использования в


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

Табл 1. Арифметические и логические операторы, использующие в числовых


выражениях:

Ассемблер содержит также ряд встроенных функций. Некоторые из них,


имеющие наибольшее практическое значение, приведены в табл.2.

Табл 2. Некоторые встроенные в ассемблер функции:

Операторы арифметических и логических операций, а также символы “;”, “.”,


“(”, “)”, “[”, “]”, “{”, “}”, “?” - зарезервированы компилятором. Они не
должны встречаться в обозначениях констант, инструкций и меток. Кроме
этого, пользовательские имена, не могут начинаться с цифр и не должны
совпадать с названиями регистров (R0…R31,X,Y,Z) и встроенными в ассемблер
функциями.

------------------------------------------------------------------

Директивы ассемблера

Директивы представляют собой команды управления компилятором. Объявление


каждой из них должно начинается с точки. Практика показывает, что в любом
ассемблере наиболее интенсивно используется только порядка 10…20
директив. Все остальные либо не являются обязательными, либо отвечают за
управление, лишь незначительными свойствами компилятора. К “основным”,
характерным и для ассемблеров других процессоров, относятся директивы
.equ, .org, .def, .сseg, .dseg и т.д. Ну, а такие директивы, как .dq,
.exit, .listmac в реальных программах встречаются действительно очень
редко. Ниже приведен перечень, описание и примеры использования директив
фирменного ассемблера микроконтроллеров AVR.
Директива .include подставляет текстовый файл в то место программы, где
происходит ее употребление. В дополнении к этому сам файл подстановки
также может содержать директиву .include. Если файл расположен в
директории проекта или в одной из служебных папок, то вместо полного
пути, допускается указывать, лишь ссылку на его имя.

Директива .include
Синтаксис написания:
.include "{путь к файлу}"
Пример использования:

.include "m8def.inc" ;вставка стандартного заголовочного файла

Директива .exit указывает ассемблеру место окончания файла исходного


текста. Все операторы, находящиеся после директивы, становятся невидимыми
для компилятора. Если .exit встречается в подключаемом файле, то сборка
проекта заканчивается строкой, где расположена директива .include. В
случае отсутствия директивы .exit, конечной точкой сборки считается
последняя строка исходного текста.

Директива .exit
Синтаксис написания:
.exit
Пример использования:

.exit ;конец файла

Директивы .nolist и .list служат для управления файлом листинга, который


обычно генерируется после сборки проекта. Первая из них запрещает, а
другая, соответственно, разрешает вывод информации в файл. Директива
.list отменяет действие .nolist и наоборот.

Директивы .nolist, .list


Синтаксис написания:
.nolist, .list
Пример использования:

.nolist ;запретить вывод текста файла “m8def.inc”


.include "m8def.inc" ;в файл листинга программы
.list ;продолжить вывод информации

Директива .equ присваивает символьному имени некоторое числовое значение.


Символьное имя должно быть уникальным и не может быть изменено в процессе
написания программы. Директива не может применяться для назначения
символьных имен регистрам общего назначения.

Директива .equ
Синтаксис написания:
.equ {символьное имя} = {выражение}
Пример использования:

.equ DDRB = 0x17 ;присвоение имени DDRB значения 0x17


.equ PORTB = DDRB + 1 ;присвоение имени PORTB значения 0x18
Директива .set производит то же самое действие, что и .equ. Но в отличии
от последней, символьное имя может быть переопределено в любом месте
программы.

Директива .set
Синтаксис написания:
.set {символьное имя} = {выражение}
Пример использования:

.set OFFSET = 0x100 ;присвоение имени OFFSET значения 0x100


.
.set OFFSET = OFFSET + 1 ;переопределение значения OFFSET

Директива .def присваивает символьное имя одному из регистров общего


назначения. В дальнейшем ходе программы данное имя может быть отменено
директивой .undef.

Директивы .def, .undef


Синтаксис написания:
.def {символьное имя} = {регистр}
.undef {символьное имя}
Пример использования:

.def temp = R16 ;присвоение регистру R16 имя temp


.undef temp ;отмена дальнейшего использования имени temp

Директивы .db, .dw, .dd, .dq предназначены для резервирования памяти


микроконтроллера под инициализированные данные. Все они могут применяться
только в сегментах кода и EEPROM-памяти. Разница между этими директивами
заключается в разрядности, представляемых данных. Директива .db
резервирует байты, .dw – слова, .dd – двойные слова. В редких случаях
может так же оказаться удобным использование директивы .dq, резервирующей
64-разрядные данные.

Директивы .db, .dw, .dd, .dq


Синтаксис написания:
{метка}: .db {8-разрядные данные}
{метка}: .dw {16-разрядные данные}
{метка}: .dd {32-разрядные данные}
{метка}: .dq {64-разрядные данные}
Пример использования:

label:
.db 0xFA, 250, -6, 0b11111010
.dw 0xFADE, 64222, -1314, 0b1111101011011110
.dd 0xFADEEFCA, 4208914378, -86052918
.dq 0xFADEEFCAEFBACDEF, 18077149609196178927, -521103510453211

Директива .byte резервирует память под неинициализированные данные в


сегментах SRAM и EEPROM.

Директива .byte
Синтаксис написания:
{метка}: .byte {количество резервируемых данных}
Пример использования:

.equ PAGESIZE = 0x20


buffer: . byte 2*PAGESIZE ;резервирование 64 байт в SRAM

Директивы .dseg, .eseg, .cseg определяют начало сегментов кода, данных и


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

Директивы .dseg, .eseg, .cseg


Синтаксис написания:
.dseg
.eseg
.cseg
Пример использования:

.dseg ;начало сегмента данных


buffer: . byte 32 ;резервирование 32 байт под буфер в SRAM

.cseg ;начало сегмента кода


rjmp initial
.
string: .db "ATmega8",0 ;строка, хранящаяся во FLASH-памяти

.eseg ;начало сегмента EEPROM-памяти


_var: .byte 2 ;резервирование 2-ух байт под переменную _var
_cnst: .db 0xAA ;резервирование байта под переменную _cnst = 0xAA

Директива .org позволяет задать компилятору начальный адрес в пределах


сегментов кода, данных и EEPROM-памяти. В случае применения в сегменте
кода, директива определяет адрес размещения 16-разрядного слова программ.

Директива .org
Синтаксис написания:
.org {начальный адрес}
Пример использования:

.equ SRAM_START = 0x60


.equ RAMEND = 0x045F

.dseg ;начало сегмента данных


.org SRAM_START ;резервирование 32 байт в SRAM под буфер,
buffer: . byte 32 ;начиная с адреса 0x60

.cseg ;начало сегмента кода


.org 0 ;вектор сброса по адресу 0
rjmp initial
.
.org 0x50 ;начало основной программы с адреса 0x50
initial:
ldi temp,high(RAMEND) ;инициализация стека
out SPH,temp
ldi temp,low(RAMEND)
out SPL,temp
.

Директивы .macro, .endmacro (.endm), определяющие начало и конец макроса


соответственно.

Директивы .macro, .endmacro (.endm)


Синтаксис написания:
.macro {имя макроса}
Пример использования:

.macro set_bit ;объявление макроса установки бита порта


sbi @0,@1 ;установить бит @1 регистра порта @0
sbi @0-1,@1 ;настроить на вывод линию @1 регистра DDRx
.endm
.
set_bit PORTB,0 ;установить на линии 0 порта B лог.1

Директива .listmac разрешает расширенный вывод текста макросов в файле


листинга. В этом случае содержимое каждого макроопределения,
встретившегося в программе, отображается целиком. Если директива не
используется, то код в нутрии макроса не приводится.

Директива .listmac
Синтаксис написания:
.listmac
Пример использования:

.listmac ;разрешить разворачивать текст макросов в файле листинга

Директивы .message, .warning, .error предназначены для вывода в окно


сборки проекта дополнительной информации о ходе компиляции программы.
Директива .message генерирует сообщение для строки, в которой был
встречен ее вызов. Применение .warning приводит к выдачи предупреждения,
а .error – к сообщению об ошибки. В последнем случае сборка проекта
прекращается.

Директивы .message, .warning, .error


Синтаксис написания:
.message "{текст сообщение}"
.warning "{текст предупреждения}"
.error "{текст соодщения об ошибки}"
Пример использования:

.message "Macros has been called here."


.warning "Too high frequency!"
.error "Wrong macro argument!"

Группа директив условной компиляции .ifdef, .ifndef, .if, .else, elif,


.endif используются для вставок программного кода в зависимости от
различных условий. Директива .ifdef проверяют наличие объявления
некоторого символьного имени. За директивой может следовать набор команд,
которые будут подставлены в текст, если условие проверки “истина” (имя
было объявлено). Директива .ifndef противоположна .ifdef проверяет
отсутствие объявления символьного имени. Директива .if производит
подстановку кода, когда выполняется условие сравнения, указанное в
качестве ее параметра. Команды, которые должны выполняться, в случае если
условие директивы .if “ложно” – располагаются после директивы .else.
Ветвление типа “если” - “то” может иметь несколько уровней вложения
благодаря директиве .elif. Каждый блок проверки, начинающийся с .ifdef,
.ifndef, .if, должен быть закрыт директивой .endif.

Директивы if, .ifdef, .ifndef, .else, elif, .endif


Синтаксис написания:
.ifdef {символ} (или .ifndef {символ})
.if {условие}
.else {выражение} (или .elif { условие})
.endif
Пример использования:

.macro del_ms ;макрос, формирующий задержку времени в мс


.ifndef FREQ ;если не объявлена константа FREQ (частота в Гц),
.warning "Undefined FREQ constan!" ;выдаем предупреждение и
.equ FREQ = 1000000 ;присваиваем по умолчание значение 1 МГц
.endif
.equ DELAY = (@0*FREQ)/4000 ;величина задания задержки времени
.if DELAY >; 65535 ;если DELAY размером больше 2 байт, то
.error “Integer overflow in DELAY!” ;реализация макроса не возможна
.else
push XL ;сохраняем в стеке рабочие регистры XL, XH
push XH
ldi XH,high(DELAY) ;цикл задержки времени
ldi XL,low(DELAY)
sbiw XH:XL,1
brne PC-1
pop XH
pop XL ;восстанавливаем из стека рабочие регистры XH, XL
.endif
.endm
.
.equ FREQ = 2000000 ;объявление тактовой частоты 2 МГц
.
del_ms 25 ;формирование задержки времени в 25 мс

-------------------------------------------------------------------------
----

Содержимое заголовочного файла

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


заголовочных файлов, поставляемых вместе с пакетом программ AVR Studio.

Заголовочные файлы существуют для всех моделей AVR. По умолчанию исходной


директорией их размещения является “С:\Program
Files\Atmel\AVRTools\AvrAssembler2\Appnotes”. Каждый файл имеет имя
подобное "m8def.inc", в котором первая часть указывает на модель
микроконтроллера.

Заголовочный файл содержит объявление всех символьных имен, которые могут


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

; ***** I/O REGISTER DEFINITIONS **************************


; NOTE:
; Definitions marked "MEMORY MAPPED" are extended I/O ports
; and cannot be used with IN/OUT instructions

.equ SREG = 0x3F


.equ SPL = 0x3D
.equ SPH = 0x3E
.
.equ TWAR = 0x02
.equ TWSR = 0x01
.equ TWBR = 0x00

; ***** BIT DEFINITIONS ***********************************


.
; ***** CPU **************************
; SREG - Status Register
.equ SREG_C = 0 ; Carry Flag
.equ SREG_Z = 1 ; Zero Flag
.equ SREG_N = 2 ; Negative Flag
.equ SREG_V = 3 ; Two's Complement Overflow Flag
.equ SREG_S = 4 ; Sign Bit
.equ SREG_H = 5 ; Half Carry Flag
.equ SREG_T = 6 ; Bit Copy Storage
.equ SREG_I = 7 ; Global Interrupt Enable
.

Директива .equ связывает название каждого из РВВ с его фактическим


адресом в адресном пространстве ввода-вывода. Эти адреса могут
использоваться только совместно с инструкциями in, out, sbi, cbi, sbis,
sbic. Если обращаться к управляющему регистру как к ячейке памяти в
абсолютном адресном пространстве, то к его имени необходимо будет
добавлять смещение 0x20:
lds SPL+0x20,R16 ;копировать R16 в ячейку 0x5D SRAM (0x3D I/O).

Обозначения РВВ и их битов в точности совпадают с теми, которые


приводятся в технической документации на каждую конкретную модель.
Поэтому не рекомендуется изменять эти имена в заголовочном файле по
своему усмотрению. Стандартные названия помогают облегчить переносимость
программ в пределах одного семейства. Если, например, понадобиться
перенести рабочий код с модели ATmega8 на ATmeg8535, то все что надо
будет сделать – это заменить в исходном тексте файле "m8def.inc" на
"m8535def.inc" и заново откомпилировать проект.

В каждом заголовочном файле РОНам R27…R31 директивой .def присвоены


дополнительные служебные имена:

; ***** CPU REGISTER DEFINITIONS **************************


.def XH = R27
.def XL = R26
.def YH = R29
.def YL = R28
.def ZH = R31
.def ZL = R30

Файл содержит также объявление ряда констант, которые часто встречаются в


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

; ***** DATA MEMORY DECLARATIONS **************************


.equ FLASHEND = 0x0FFF ; Note: Word address
.equ IOEND = 0x003F
.equ SRAM_START = 0x0060
.equ SRAM_SIZE = 1024
.equ RAMEND = 0x045F
.equ XRAMEND = 0x0000
.equ E2END = 0x01FF
.equ EEPROMEND = 0x01FF
.equ EEADRBITS = 9
????????
; ***** BOOTLOADER DECLARATIONS ***************************
.equ NRWW_START_ADDR = 0xC00
.equ NRWW_STOP_ADDR = 0xFFF
.equ RWW_START_ADDR = 0x0
.equ RWW_STOP_ADDR = 0xBFF
.equ PAGESIZE = 32
.equ FIRSTBOOTSTART = 0xF80
.equ SECONDBOOTSTART = 0xF00
.equ THIRDBOOTSTART = 0xE00
.equ FOURTHBOOTSTART = 0xC00
.equ SMALLBOOTSTART = FIRSTBOOTSTART
.equ LARGEBOOTSTART = FOURTHBOOTSTART

В конце файла приведена таблица векторов прерывания:

; ***** INTERRUPT VECTORS *********************************


.equ INT0addr = 0x0001 ; External Interrupt Request 0
.equ INT1addr = 0x0002 ; External Interrupt Request 1
.equ OC2addr = 0x0003 ; Timer/Counter2 Compare Match
????????
.equ ACIaddr = 0x0010 ; Analog Comparator
.equ TWIaddr = 0x0011 ; 2-wire Serial Interface
.equ SPMRaddr = 0x0012 ; Store Program Memory Ready

.equ INT_VECTORS_SIZE = 19 ; size in words

-------------------------------------------------------------------------
--------------

Структура программы

Рассмотрим синтаксические особенности ассемблера AVR на примере небольшой


тестовой программы для микроконтроллера ATmega8, приведенной ниже.
Программа формирует на выводе PB2 импульсы с частотой следования ? 2.5 Гц
при частоте внутреннего RC-генератора 1 МГц.
?
1. ; Тестовая программа для ATmega8.
2. // Светодиод подключен к выводу PB0 микроконтроллера.
3. /* Биты конфигурации:
Low Fuse High Fuse
BODLEVEL = 1 RSTDISBL = 1
BODEN = 1 WDTON = 1
SUT1 = 1 SPIEN = 1
SUT0 = 0 CKOPT = 1
CKSEL3 = 0 | EESAVE = 1
CKSEL2 = 0 |_ RC-генератор BOOTSZ1 = 1
CKSEL1 = 0 | 1 МГц BOOTSZ0 = 1
CKSEL0 = 1 | BOOTRST = 1 */

4. .nolist ;подключение стандартного заголовочного файла


5. .include "m8def.inc"
6. .list

7. .equ PAUSE = 50000 ;задержка времени


8. .equ LED = PB2 ;вывод для подключения светодиода

9. .def temp = R16 ;регистр для промежуточных операций


10. .def buffer = R17 ;регистр для чтения порта

11. .cseg
12. .org 0
13. rjmp initial
;0xC01F
14. rjmp 0 ;rjmp service_INT0 ;внешнее прерывание 0
0xCFFE
15. rjmp 0 ;rjmp service_INT1 ;внешнее прерывание 1 0xCFFD
16. rjmp 0 ;rjmp service_OC2 ;совпадение TCNT2 и OCR2
0xCFFC
17. rjmp 0 ;rjmp service_OVF2 ;переполнение TCNT2
0xCFFB
18. rjmp 0 ;rjmp service_ICP1 ;захват в ICP1 0xCFFA
19. rjmp 0 ;rjmp service_OC1A ;совпадение TCNT1 и OCR1A
0xCFF9
20. rjmp 0 ;rjmp service_OC1B ;совпадение TCNT1 и OCR1B
0xCFF8
21. rjmp 0 ;rjmp service_OVF1 ;переполнение TCNT1 0xCFF7
22. rjmp 0 ;rjmp service_OVF0 ;переполнение TCNT0
0xCFF6
23. rjmp 0 ;rjmp service_SPI ;прерывание от модуля SPI
0xCFF5
24. rjmp 0 ;rjmp service_URXC ;получение байта по USART
0xCFF4
25. rjmp 0 ;rjmp service_UDRE ;опустошение UDR в USART
0xCFF3
26. rjmp 0 ;rjmp service_UTXC ;передача байта по USART
0xCFF2
27. rjmp 0 ;rjmp service_ADCC ;прерывание от АЦП
0xCFF1
28. rjmp 0 ;rjmp service_ERDY ;завершение записи в EEPROM
0xCFF0
29. rjmp 0 ;rjmp service_ACI ;прерывание от компаратора
0xCFEF
30. rjmp 0 ;rjmp service_TWI ;прерывание от модуля TWI
0xCFEE
31. rjmp 0 ;rjmp service_SPMR ;завершение выполнения spm
0xCFED

32. .org 0x20


33. initial: ldi temp,low(RAMEND)
;0xE50F
34. out SPL,temp
;0xBF0D
35. ldi temp,high(RAMEND)
;0xE004
36. out SPH,temp
;0xBF0E
37. cbi PORTB,LED
;0x98C2
38. sbi DDRB,LED
;0x9ABA
39. ldi temp,1«LED
;0xE004
40. main: in buffer,PORTB:
;0xB318
41. eor buffer,temp
;0x2710
42. out PORTB,buffer
;0xBB18
43. rcall delay
;0xD001
44. rjmp main
;0xCFFB

45. delay: ldi XH,high(PAUSE)


;0xECB3
46. ldi XL,low(PAUSE)
;0xE5A0
47. sbiw XH:XL,1
;0x9711
48. brne PC-1
;0xF7F1
49. ret

В строках 1…3 приведено 3 возможных варианта оформления комментариев.


Комментарий в строке 1, начинающийся со знака “;”, распознается любым
ассемблером и поэтому является наиболее предпочтительным. Комментарии в
строках 2 и 3 подобны тем, которые используются в нотациях языков
высокого уровня. Последний из них дает возможность выделить сразу
несколько строк (маркерами начала и конца фрагмента текста является “/*”
и “*/” соответственно).

В строке 5 директивой .include к программе подключается стандартный


заголовочный файл "m8def.inc". Необязательные директивы .nolist и .list
(строки 4 и 6 соответственно) запрещают вывод содержимого подключаемого
файла в файл листинга.

Все директивы допускается размещать в одной строке. Так, например, можно


было бы записать
.nolist .include "m8def.inc" .list

Несмотря на это, желательно придерживаться правильного стиля


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

Объявление констант находится в строках 7 и 8. Константе LED


присваивается номер линии ввода-вывода PB2 = 2, описание которой
находится в заголовочном файле. В строках 9 и 10 двум рабочим РОНам
назначаются пользовательские имена.

Секция рабочего кода открывается директивой .cseg в строке 11. Директива


.org 0 (строка 12) устанавливает начальный адрес в памяти программ. В
строке 13 должна находиться инструкция перехода на метку начала основной
программы initial.

Метка представляет собой адрес в пределах секции кода или данных. В


ассемблере AVR она должна быть записана в начале строки и завершаться в
конце двоеточием.

Конечно, адрес в команде может быть указан и явно. В данном примере его
можно задать безошибочно (rjmp 0x20 вместо rjmp initial). Но, это
возможно только потому, что директива .org 0x20 (строка 32) заставляет
компилятор поместить команду в строке 33 по адресу 32-го слова FLASH-
памяти программ. Однако, в большинстве случаев, адрес размещения той или
иной команды заранее неизвестен и, кроме того, он может изменяться по
мере того, как в программу будут вноситься изменения. Именно поэтому
предпочтительней использовать метки. Назначение их адресов производится
автоматически на этапе компиляции. Да и символьные имена меток,
предоставляют об объектах намного больше информации, чем просо какие-то
числа.

На месте каждого неиспользуемого в программе прерывания желательно


поставить “заглушки” в виде команды возврата rjmp 0 или reti, как это
сделано в строках 14…31.

В строках 33…39 производится инициализация ресурсов микроконтроллера.


Любая программа обязательно должна начинаться с установки начального
значения указателя стека (если он имеется). В строках 33…36 в SPH:SPL
заносится значение RAMEND = 0x045F (вершина стека перемещается в самый
верх SRAM). Для выделения младшего и старшего полубайтов константы RAMEND
используются встроенные функции low(RAMEND)=0x5F и high(RAMEND)=0x04
соответственно.

Далее разряд LED в регистре данных порта B сбрасывается на 0 (стока 37),


а сама линия управления светодиодом настраивается на вывод (стока 38). В
строке 39 в регистр temp заносится константа 1<

ldi temp,(1«PB0)|(1«PB5) или ldi temp,(1«PB0)+(1«PB5) ;temp <-


0b00100001
out DDRB,temp ;DDRB <- temp

Код в строках 40…44 представляет собой тело основной программы. В регистр


buffer считывается текущее состояние порта данных PORTB (строка 40).
Далее между содержимым buffer и temp производится операция “Исключающее
ИЛИ” (строка 41), после чего модифицированное содержимое buffer снова
выводится в порт (строках 42). В результате такого действия логический
уровень на линии PB2 изменится на противоположный. В строке 43 происходит
вызов подпрограммы задержки времени delay (?200 мс), а в строке 44
расположена инструкция перехода на метку main. Так образуется основной
цикл программы, в котором происходит постоянное повторение операторов в
строках 40…44: инвертирование уровня на выводе LED, задержка, затем снова
инвертирование и т.д.

Внутри подпрограммы delay в регистровую пару XH:XL заносится число PAUSE


= 50000, определяющее длительность задержки времени (строки 45, 46). В
строке 47 из содержимого XH:XL вычитается 1. Команда условного перехода в
строке 48 проверяет, значение флага Z из SREG. Если Z = 1 (результат
предыдущей операции не равен нулю), то управление передается на строку
47. Так будет повторяться до тех пор, пока не выполнится условие XH:XL =
0 (т.е. пока не пройдет 50000 циклов вычитания). Команда выхода ret в
строке 49 возвращает управление в то место основной программы, с которого
произошел вызов delay.

Отдельно стоит обратить внимание на встроенную в ассемблер переменную PC


в строке 48. Она представляет собой текущее содержимое программного
счетчика. Адрес PC-1 будет указывать на предыдущее слово в памяти
программ микроконтроллера. В данном случае в этом слове находится команда
sbiw XH:XL,1. Использовать PC очень удобно для программных переходов в
небольших пределах. Однако здесь всегда необходимо помнить, что у
микроконтроллеров AVR имеются команды, которые имеют размер в 2 16-
разрядных слова программ (lds Rd,k, jmp k и др.), из-за чего при
вычислении смещения необходимо будет добавлять\отнимать к PC значение 2
вместо 1 на каждую такую инструкцию.
Справа, в листинге приведен машинный код, который будет сгенерирован
после компиляции программы.

-------------------------------------------------------------------------
------------------------------

Макросы

По своей сути макрос является обыкновенным шаблоном для подстановки


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

Каждый макрос нужно предварительно объявить в разделе описания


переменных. Объявление макроса имеет вид типа:

.macro {имя макроса}


.
текст для подстановки
.
.endmacro или .endm

В дальнейшем макрос можно будет использовать неограниченное число раз,


простой подстановкой его имени в текст программы. За именем макроса через
запятую могут последовать несколько параметров (до 10). В самом теле
макроса ссылка на параметры производится через символ “@”, после которого
идет порядковый номер (@0…@9). Так, например, может выглядеть макрос,
реализующий сложение содержимого регистровой пары с 16-разрядным числом

.macro add16 ;объявление макроса add16


subi @1,low(-@2) ;вычитаем из регистровой пары @0:@1 16-разрядное
sbci @0,high(-@2) ;число @2 в дополнительном коде
.endm

На месте употребления в программе макроса

.
add16 XH,XL,0x1234 ;сложение XH:XL с 16-разрядным числом 0x1234
.

будет сгенерирован следующий код подстановки:

subi R26,0xCC ;XH:XL = XH:XL - 0xEDCC


sbci R27,0xED

В макросах часто применяются директивы условной компиляции, такие как


.else, elif, .endif и т.д., а также .message, .warning и .error. С их
помощью можно производить подстановку различных фрагментов кода в
зависимости от начальных значений входных параметров, а также выявлять
ошибки, выдавать сообщения и предупреждения в ходе компиляции программы.
На листинге ниже показан макрос pulse_low, формирующий на одном из
выводов импульс низкого уровня.
.macro pulse_low ;объявление макроса pulse_low
.if @0 == 0x18 ;если параметр @0 порта C, то выводим сообщение
.message "Порт C содержит только 7 линий ввода-вывода."
.elif @1 == 7 ;если параметр @1 линия 7, то выводим ошибку
.error "Линия 7 порта C отсутствует в модели ATmega8!"
.endif
.if @2 >; 65535 ;если параметр @2 больше 2 б, то выводим
предупреждение
.warning "Слишком большая задержка времени!"
.endif
cbi @0,@1 ;устанавливаем лог.0 на линии @1 порта @0
ldi R24,low(@2);заносим в счетчик циклов R25:R24 значение @2
ldi R25,high(@2)
loop: sbiw R24,1 ;производим цикл вычитания
brne loop
sbi @0,@1 ;устанавливаем лог.1 на линии @1 порта @0
.endm
????????
pulse_low PORTB,PB0,10000 ;импульс на линии 0 порта B около 40000
циклов
.

Макрос принимает 3 значения: адрес регистра данных порта (PORTB, PORTC


или PORTD), номер линии ввода-вывода (0…7) и двухбайтовую константу,
определяющую длительность задержки (0…65535). В нутрии макроса
производится проверка адреса порта. Если в качестве порта был задан PORTC
(адрес регистра PORTС = 0x18), то будет выдано сообщение о том, что в
модели ATmega8 этот порт содержит только 7 линий ввода-вывода. И если,
кроме этого, будет выбрана линия 7 (произойдет попытка обратиться к
несуществующему биту) – из-за ошибки сборка проекта будет приостановлена.
Дополнительную проверку на переполнение проходит, и параметр задержки
времени. В случае если его значение превышает 0xFFFF, будет выдано
предупреждение.

Все метки в теле макроса, как, например, метка loop в макросе pulse_low,
являются локальными. Область их видимости ограничена директивами .macro и
.endm. В ассемблере AVR допускается использование макросов в макросе (до
8 уровней вложения).

-------------------------------------------------------------------------
----------

Создание и отладка программ

Разработка программ в AVR Studio начинается с создания проекта. После


установки программы это легче всего сделать, через менеджер создания
проектов во вкладке Project/Project Wizard.

Project Wizard AVR Studio


Рис.1 Project Wizard AVR Studio

После нажатия экранной кнопки New Project появится окно на рис.1, в


котором надо задать название и директорию размещения проекта, например
FirstProject. В качестве типа проекта необходимо выбрать Atmel AVR
Assembler (проекты AVR GCC используют Си-компилятор WinAVR). Сейчас и в
дальнейшем очень важно размещать все файлы проекта в одной папке, что
избавит от многих проблем при редактировании и переносе программ.

Project Wizard AVR Studio


Рис.2 Project Wizard AVR Studio

На следующем этапе необходимо выбрать модель микроконтроллера и тип


отладочного средства, как показано на рис.2 (в нашем случае это ATmega8 и
AVR Simulator соответственно). После чего откроется окно текстового
редактора, где и будет непосредственно происходить создание программы (см
рис.3).

Текстовый редактор AVR Studio


Рис.3 Текстовый редактор AVR Studio

В окне проекта Project можно видеть все компоненты программы: файл с


исходным текстом FirstProject.asm, заголовочный файл m8def.inc, а также
выходные файлы .lst, .map, .hex и .obj с одноименными названиями. В
разделе Labels находятся символьные имена меток, встречающиеся в
программе.

Компиляция проекта осуществляется после нажатия на иконку Assemble либо


Assemble and Run. В последнем случае сразу же запускается и программа
отладчика. Если в исходным тексте были допущены ошибки, то .hex,
естественно, создан не будет, а в окне Build, появится описание всех
ошибок и строки где они находятся. После внесения необходимых исправлений
и успешной сборки, в окне Build отобразится статистика о проекте в виде
диапазонов адресов и размеров секций FLASH, SRAM и EEPROM.

Работа в симуляторе AVR Studio


Рис.4 Работа в симуляторе AVR Studio

Проверить работоспособность программы можно в симуляторе, либо с помощью


любого другого отладчика. Его запуск происходит после нажатия иконки
Start Debagging. На рис.4 показана работа в симуляторе и вид основных
отладочных окон, в которых можно наблюдать за состоянием содержимого
различных областей памяти и символьными именами, объявленных в тексте
программы. Все эти данные доступны для редактирования на ходу.

Отладку можно вести как в пошаговом (кнопки Step Into, Step Over, Step
Out), так в автоматическом (Auto Step) или ускоренном (Run) режимах.
Имеется возможность использовать также точки останова. Симулятор,
встречая строку, в которой находится точка останова, принудительно
останавливает свое выполнение, после чего можно детально изучить
содержимое отладочных окон. Управление точками останова производится
кнопками Toggle Brekpoint и Remove all Program Brekpoints. В окне
Disassembler можно видеть соответствующие машинных кодов командам
ассемблера AVR.

AVR Studio предоставляет программисту интуитивно-понятный


пользовательский интерфейс и при ее освоении никаких проблем, как
правило, не возникает. Управление свойствами встроенного текстового
редактора и среды в целом ничем не отличается от подобных действий в
различных офисных приложениях. Детальное описание всех компонентов AVR
Studio можно найти во встроенной справочной системе.

-------------------------------------------------------------------------
-------
Структура HEX-файла

После успешной сборки проекта будет сгенерирован 16-тиричный файл


FirstProject.hex, содержащий в символьном виде машинный код программы:

:020000020000FC
:100000001FC0FECFFDCFFCCFFBCFFACFF9CFF8CF8B
:10001000F7CFF6CFF5CFF4CFF3CFF2CFF1CFF0CFCC
:06002000EFCFEECFEDCFA3
:100040000FE50DBF04E00EBFC298BA9A04E018B3E2
:10005000102718BB01D0FBCFB3ECA0E51197F1F747
:02006000089501
:00000001FF

Рассмотрим формат данных hex-файл на примере второй строки текста


(маркером начала строки всегда является символ “:”):

NN AAAA CC DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD SS
: 10 0000 00 1FC0FECFFDCFFCCFFBCFFACFF9CFF8CF 8B

Поле NN определяет количество байтов данных в строке (в нашем случае 16


байтов). Поле AAAA – это начальный адрес, с которого данные будут
записаны в память микроконтроллера.

За адресом следует поле команды CC. Программатор, ориентируясь на поле


CC, распознает функциональное назначения строки. Ассемблер и другие
компиляторы языков высокого уровня для AVR могут установить следующие
значения данного параметра:
00 – в строке находятся данные для записи в память,
01 – последняя строка в файле,
02 – строка содержит начальный адрес сегмента памяти,
04 – строка содержит адрес в пределах сегмента памяти.

В данной строке CC=00 (т.е. строка предназначена для записи данных). За


полем CC (кроме команды 01) идут непосредственно данные в количестве,
определяемом параметром NN. Последнее поле SS – контрольная сумма. Сумма
всех байтов в неповрежденной строке без учета переполнения всегда
нулевая:

10 + 00 + 00 + 00 + 1F + … + CF + F8 + CF + 8B = 0.

Каждый hex-файл должен завершаться строкой :00000001FF.

------------------------------------

Вам также может понравиться