Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Ассемблер AVR для начинающих

.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
847.87 Кб
Скачать

Ассемблер AVR для начинающих (первый шаг)

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

В качестве базового контроллера для экспериментов я выбрал наиболее доступный и дешевый на данный момент ATtiny13. Он имеет всего восемь выводов и 1 кБайт Flash-памяти, но для изучения базовых операций его будет более чем достаточно. Схема для экспериментов представлена на рисунке 1. 

Рисунок 1 - Схема платы для экспериментов

Центральным элементом ее является, как уже было сказано, контроллер DD1 типа ATtiny13. Питание схемы и программирование контроллера осуществляется при помощи разъема XS1. Распиновка его зависит от применяемого программатора. В качестве такого программатора рекомендую использовать USBasp, описанный мною в предыдущих статьях. Конденсаторы С1 и С2 - для фильтрации помех по питанию. Кнопки SB1 и SB2 - любые без фиксации. Резисторы R2-R5 - токоограничительные. Могут быть выбраны из диапазона 220-390 Ом. Резистор R1 - переменный сопротивлением 1-20 кОм. Он служит для изменения напряжения, подаваемого на вход АЦП контроллера. Светодиоды LED1 и LED2 любого цвета и диаметра. Все элементы подключены таким образом, чтобы задействовать альтернативные функции контроллера, поэтому нежелательно вносить изменения в схему. Теперь, что касается программной части. Многие рекомендуют для программирования на ассемблере использовать пакет "AVR Studio", выпускаемый самой фирмой Atmel, и распространяемый бесплатно. Рекомендации эти обоснованы: подсветка ключевых слов, справка, отладочный механизм - все эти приятные мелочи имеются в наличии. В качестве альтернативы я использую максимально облегченный вариант, который предлагает Ю. Ревич в своей книге "Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера" (СПб.: БХВ-Петербург, 2008 - 384 с.). Не вдаваясь в подробности, что откуда взялось, выкладываю сюда архив с необходимым ПО. Скачать его можно здесь. Объем его крайне невелик, хотя в век широкополосных каналов интернета это практически не имеет значения. Архив можно распаковать в любую папку.  Итак, что же найдет любопытный пользователь, заглянув в полученную папку "asm"? Рассмотрим все по-порядку. В папке "Appnotes" располагаются файлы двух типов: с расширениями "inc" и "asm". Файлы "inc" содержат в себе информацию о контроллерах Atmel, с которыми может работать используемый здесь транслятор языка ассемблер. Имя файла совпадает с названием контроллера. Например, для нашей платы понадобится "tn13def.inc", но об этом позже. Файлы "asm" содержат примеры кодов, написанных инженерами фирмы Atmel, и выполняющих различные типовые операции. Для разнообразия их можно просмотреть, но использовать будем только самостоятельно разработанные алгоритмы (иначе, какие ж мы программисты?). В папке "asmedit" находится редактор для набора программ на ассемблере. В принципе, для этих целей можно использовать произвольный текстовый редактор, даже обычный блокнот, но предлагаемый здесь удобнее, поскольку имеет подсветку некоторых команд, а также кнопки, позволяющие производить ассемблирование прямо из окна программы. Файл avrasm32.exe является транслятором языка ассемблера. Именно он преобразует написанную нами программу в hex-файл, который может быть зашит непосредственно в микроконтроллер. Файл build.bat представляет собой пакетный файл, который указывает опции ассемблирования для программы avrasm32.exe. Рассмотрим его структуру подробнее. В приведенном примере, скачанном читателем с сайта, в нем написан следующий текст: "F:\Prog\AVR\asm\avrasm32 -fI %F:\Prog\AVR\asm\Nokia3410\main.asm pause" В принципе, структура его понятна интуитивно, но на всякий случай поведаю поподробней. Итак, сначала указывается путь, по которому находится файл avrasm32. У меня он расположен по пути "F:\Prog\AVR\asm\". Вы же указывайте ту папку, куда распаковали архив. Далее идут опции ассемблирования "-fI". Их менять не стоит. В конце после знака "%" указывается исходный файл с расширением asm и полный путь к нему. Вторая строка представляет собой команду pause, которая дает возможность просмотреть результат создания hex-файла, и закрыть окно командной строки по нажатию любой клавиши. О том, как это все выглядит на практике, будет рассказано и показано чуть позже. А пока необходимо сделать единственную настройку в программе asm_ed. Заходим в папку asmedit, запускаем файл Asm_Ed.exe. Появляется окно следующего вида: Как видно на скриншоте, большую часть окна занимает поле для ввода текста, здесь мы и будем набирать свою программу. Сейчас же нам нужен пункт главного меню Service. В выпадающем списке выбираем "Properties...". Открывается окно следующего вида: Переходим к вкладке "Project" и меняем во второй сверху строке путь к файлу build.bat, не трогая остального: Все! Можем нажимать кнопочку "ОК" и радоваться - программа готова к работе. Теперь для запуска процесса асемблирования необходимо будет нажать всего лишь кнопку "II" на панели инструментов. Итак, соберите приведенную на рисунке 1 схему, скачайте и настройте ПО, сделайте программатор, если у вас еще нет столь полезной вещи, и приступим к дальнейшей работе. Но это будет уже совсем другая история...

Ассемблер AVR для начинающих (второй шаг)

Итак, надеюсь добросовестный читатель уже собрал программатор, экспериментальную плату, а также установил и настроил требуемое программное обеспечение. Сейчас, написав первую статью из цикла, я понимаю, что несколько погорячился и сделал ту же ошибку, что и мои предшественники, поставив словосочетание "для начинающих". Вернее было бы сформулировать тему "Для начинающих программировать на ассемблере", то есть я предполагаю, что читатель уже имеет хотя бы поверхностное представление о том, что такое микроконтроллер, иначе только на знакомство с этой темой у нас уйдет уйма времени. Для тех же, кто совсем не знаком с ними, могу порекомендовать совершенно замечательный на мой взгляд цикл статей С. Рюмика "Микроконтроллеры AVR", опубликованный в журнале Радиоаматор (№№ 1-11 за 2005 год). В этом цикле в качестве базового контроллера выбран ATmega8, однако общие функциональные узлы у вышеназванного контроллера и ATtiny13 практически не отличаются.  Для знакомства же непосредственно с микроконтроллером ATtiny13 я рекомендую книгу А.В. Евстифеева "Микроконтроллеры AVR семейства tiny. Руководство пользователя" (М.: Издательский дом "Додэка-XXI", 2007. - 432 с.). Она содержит переведенные и систематизированные даташиты на весь номенклатурный ряд контроллеров семейства tiny, и, на мой взгляд, должна являться настольной для тех, кто занимается программированием микроконтроллеров. Впрочем, я по мере повествования буду давать кое-какие сведения относительно тех узлов и модулей контроллера, которые будут применяться в написанных программах. Но все это лирическое отступление. Вернемся непосредственно к повествованию.  Контроллер ATtiny13 несмотря на свой малый размер, имеет весьма неплохие функциональные характеристики. А небольшое количество выводов с лихвой компенсируется количеством функций, которые каждый из них выполняет. Цоколевка и описание выводов представлено ниже: Таблица взята из вышеназванной книги А.В. Евстифеева. Как можно видеть, каждый вывод может выполнять не менее трех функций, а то и намного больше. Поначалу мы не будем рассматривать альтернативные функции, а лишь базовую - цифровой вход/выход. Как видно из рисунка и таблицы, все выводы, за исключением выводов питания, имеет название РВ с последующим порядковым номером. Что же это означает? Все выводы контроллера объединены по 8 штук для удобства работы с ними, а на каждую группу из 8 выводов выделено по три специальных регистра ввода-вывода. Вообще понятие регистров является ключевым при работе в контроллерами, особенно на ассемблере. Рассмотрим более подробно каждый из трех вышеупомянутых регистров. Все они являются однобайтовыми ячейкам в памяти контроллера. Каждый бит их отвечает один из выводов контроллера, причем номер бита в регистре совпадает с номером вывода (например, 0-й бит отвечает за вывод РВ0, 1-й - за РВ1 и т.д.). Все регистры имеют свое имя, по которому к ним обращаются при написании программ. Что же это за имена? 1. Регистр DDRB отвечает за направление передачи информации каждого вывода контроллера. Если какой-либо бит этого регистра равен "0", то соответствующий ему вывод будет входом, а если "1" - то выходом. Причем каждый вывод конфигурируется индивидуально и в любом месте программы. Это значит, что при разных условиях или в разное время один и тот же вывод может быть сконфигурирован как вход либо как выход, причем независимо от остальных выводов. 2. Регистр PINB содержит в себе текущее состояние всех выводов: если на вывод подано напряжение, то в соответствующий бит записывается логическая "1", если напряжение отсутствует - логический "0". В основном этот регистр используется для считывания состояния вывода, находящегося в режиме входа. 3. Регистр PORTB выполняет двоякую функцию в зависимости направления передачи информации. Если вывод работает как цифровой выход, то запись "1" в какой-либо бит регистра PORTB приводит к появлению напряжения на соответствующем выводе, а запись "0" - к исчезновению напряжения. Таким образом, в режиме выхода именно этот регистр определяет состояние каждого вывода. В режиме цифрового входа запись логической "1" в какой-либо бит приводит к подключению встроенного подтягивающего резистора на соответствующем выводе, а запись "0" - к его отключению. Что же это за такая штука - "подтягивающий резистор", и для чего она предназначена? Если вывод работает как цифровой вход, то сопротивление входного буфера достаточно велико, а входной ток - весьма мал. Поэтому любые электрические наводки могут привести к самопроизвольному переключению вывода в произвольное состояние. Чтобы этого не происходило, между входом и источником питания включается резистор сопротивлением несколько десятков килоом, "подтягивающий" потенциал входа к напряжению питания (отсюда и название). Ток, протекающий через этот резистор достаточно мал, чтобы не мешать работе остальной схемы, но достаточно велик, чтобы воспрепятствовать случайным переключениям вывода. Мы часто будем использовать подтягивающие резисторы при работе с кнопками, поскольку когда они не нажаты, выводы, к которым они подключены, фактически "висят" в воздухе и подвержены наводкам. Следует упомянуть, что при включении питания все регистры сброшены в 0, и каждый вывод выполняет функцию цифрового входа без подтягивающего резистора. Теперь, когда мы имеем хоть какое-то представление, ЧТО нужно для работы с вводами контроллера, пришла пора узнать, КАК с ними работать. Напишем нашу первую рабочую программу на ассемблере. Вначале я дам полный алгоритм создания нового проекта, в дальнейшем же буду его опускать, останавливаясь только на самом тексте программы. 1. Заходим в папку asm, создаем в ней новую папку. Переименовываем в удобное для нас имя. Для определенности я буду называть их по номеру нашего шага. В данном случае "step2". 2. Правой кнопкой щелкаем на файле build.bat и изменяем путь к исходному файлу, указывая вновь созданную папку (step2). У меня после этого содержимое выглядит так: "F:\Prog\AVR\asm\avrasm32 -fI %F:\Prog\AVR\asm\step2\main.asm pause" У вас оно может отличаться в зависимости от того, куда вы распаковали архив. 3. Заходим в папку Asmedit и запускаем программу ASM_Ed.exe 4. В открывшемся окне пишем текст программы. На этом пункте остановлюсь более подробно, поскольку он является основным в нашем сегодняшнем занятии, равно как и в последующих. Что же собой представляет текст ассемблерной программы? Он может включать в себя несколько элементов, записываемых по определенным правилам: - команды ассемблера с операндами или без них в зависимости от  синтаксиса команды. Между именем команды и первым операндом должен быть либо пробел, либо знак табуляции, либо и то и другое в любом количестве. Операнды разделяются между собой запятой, до и после которой может стоять также произвольное количество пробелов либо знаков табуляции; - директивы, каждая из которых начинается с символа "."; - метки, представляющие собой произвольно названные пользователем места программы, к которым может потребоваться переход. Каждая метка оканчивается символом ":"; - комментарии, начинающиеся с символа ";". Весь текст от начала комментария до конца строки игнорируется при создании hex-файла и может быть совершенно произвольным; - пустые строки для лучшей структурированности и читабельности программы. В каждой строке может быть не более одной команды. Однако одновременное присутствие в строке метки с последующей командой и комментарием допускается. Используя эти правила, напишем программу, которая будет включать светодиод LED2, пока удерживается нажатой кнопка SB1, и выключать его, если кнопка отпущена. Текст программы представлен ниже: .include "F:\Prog\AVR\asm\Appnotes\tn13def.inc" sbi DDRB,  4     ;РВ4 - выход (светодиод LED2) sbi PORTB, 2    ;Включение подтягивающего резистора на РВ2 (кнопка SB1) sbic PINB,  2    ;Если РВ2=0 (кнопка нажата), пропустить след. строку sbi PORTB, 4    ;Установка РВ4 в 1 (выключение светодиода) sbis PINB,  2    ;Если РВ2=1 (кнопка отпущена), пропустить след. строку cbi PORTB, 4    ;Установка РВ4 в 0 (включение светодиода) Разберем его поподробнее. Первая строка содержит директиву "include", написанную по указанным выше правилам с точкой в начале. Назначение ее - включать в текст программы указанный за ней файл. Как я говорил еще в первом шаге, нам потребуется файл "tn13def.inc". В этой строке вам необходимо будет изменить путь, указав расположение папки Appnotes в своем компьютере. Зачем же нам нужно подключать этот файл? Любопытный читатель может заглянуть в него и почитать его содержимое, но, скорее всего, поначалу он мало что поймет там. Пока же скажу, что в нем содержится соответствие имен регистров, которые по умолчанию ассемблер не знает, с их физическими адресами в контроллере. Следующие строки представляют собой команды ассемблера. Внимательный читатель заметит, что всего используется четыре различные команды. рассмотрим назначение каждой. Команда sbi имеет два операнда: первый - имя регистра, второй - номер бита. В результате ее выполнения указанный бит в указанном регистре устанавливается в "1". Команда cbi по синтаксису аналогична вышеприведенной и выполняет прямо противоположную функцию - сбрасывает указанный бит в указанном регистре в "0". Команда sbis также аналогична по синтаксису вышеприведенным. Однако в отличие от них она не выполняет никаких операций с регистрами, а лишь проверяет состояние указанного бита в указанном регистре, и если тот равен "1", пропускает следующую за командой строку. В противном же случае следующая за ней строка выполняется, равно как и все остальные за ней. Команда sbiс является противоположностью команды sbis. Она пропускает следующую строку, если указанный бит регистра равен "0". Теперь, суммируя все вышеизложенное, попробуем разобраться в алгоритме работы программы. Для начала я сделаю это буквально построчно. 1 строка. Директивой include подключается файл tn13def.inc, содержащий определения регистров. 2 строка. Командой sbi устанавливается "1" в бит 4 регистра DDRB, тем самым вывод РВ4 переключается на выход. Если посмотреть схему платы (рис. 1 предыдущего шага), можно видеть, что к этому выводу подключен светодиод LED2. После команды и знака ";" написан комментарий, кратко поясняющий смысл выполняемых в строке действий. 3 строка. Той же командой sbi устанавливается "1" в бит 2 регистра PORTB, подключая внутренний подтягивающий резистор к выводу РВ2, к которому подключена кнопка SB1. Поскольку мы не изменяли состояние бита 2 регистра DDRB, этот вывод так и останется входом, что нам, собственно, и нужно.  4 строка. Командой sbic проверяется наличие логического "0" на входе PB2, используя регистр PINB. Если внимательно посмотреть на схему, можно увидеть, что кнопки при нажатии, замыкают соответствующий вывод с общим проводом. Это стандартный прием, поскольку при отпущенной кнопке на выводе присутствует логическая "1" за счет подтягивающего резистора, а при нажатой появляется логический "0" благодаря подключению вывода к общему проводу. Итак, если на выводе РВ2 присутствует логический "0", то есть кнопка нажата, мы пропускаем следующую строку, а если кнопка отпущена, то выполняем ее. 5 строка. В ней командой sbi устанавливается логическая "1" в бит 4 регистра PORTB, тем самым выключая светодиод LED2. Въедливый читатель может поинтересоваться, почему же светодиод выключается, если мы подаем напряжение на выход. Ответ кроется в схеме. Светодиод анодом подключен к проводу питания, а катодом к выводу контроллера. Поэтому если подать на вывод напряжение, то потенциалы анода и катода сравняются, и светодиод погаснет. Если же на вывод выдать логический "0", то к светодиоду будет приложено напряжение, и он зажжется. Таким образом пара строк 4 и 5 выключает светодиод LED2 при отпущенной кнопке. 6 строка. Противоположна по смыслу 4-й. Командой sbis проверяется наличие логической "1" на входе РВ2, то есть проверяется, отпущена ли кнопка. Если кнопка отпущена, то следующая строка пропускается, и происходит переход к следующей за ней. Но поскольку 7-я строка последняя, то происходит переход ко 2-й строке. Если же кнопка нажата, то выполняется строка 7. 7 строка. Противоположна 5-й. Командой cbi бит 4 регистра PORTB сбрасывается в "0", тем самым включая светодиод LED2. Таким образом, пара строк 6 и 7 включает светодиод LED2 при нажатой кнопке SB1. Как видите, ничего особо сложного мы не совершили. Используя знание всего 3-х регистров и 4-х команд, мы написали нашу первую программу. Что же делать с ней дальше. Если вы еще не забыли, мы продолжаем писать алгоритм создания программы. 5. Написав текст программы в окне редактора, выбираем пункт меню "File", и в открывшемся списке нажимаем "Save As...". В окне сохранения файла выбираем созданную нами папку step2 и указываем имя файла "main", поскольку именно это имя было задано нами в файле "build.bat" После сохранения окно программы должно иметь следующий вид: 6. Создаем hex-файл. Для этого нажимаем кнопку "II" на панели инструментов. Должно появится окно следующего вида: Оно извещает нас о том, что ассемблирование прошло без ошибок и создан файл прошивки "main.hex" объемом 6 слов, то есть 12 байт. Замечу, что аналогичная программа на языке Си имела бы как минимум в 5 раз больший объем. 7. Зайдя в папку step2,  обнаруживаем в ней пополнение в виде вновь созданного файла main.hex, который теперь может быть зашит в контроллер любым программатором, что и необходимо выполнить, дабы увидеть результаты работы написанной нами программы. После прошивки контроллера, если схема собрана правильно, все должно работать по разработанному нами алгоритму: при отпущенных кнопках светодиод LED2 должен быть погашен, а во время удержания нажатой кнопки SB1 - зажжен. На этом второй шаг можно считать сделанным: мы написали первую программу на ассемблере и, зашив ее в контроллер, убедились в ее работоспособности. Настало время осмыслить все написанное и выполнить самостоятельную работу.  До следующего шага предлагаю сделать такие задания: 1. Добавить к программе обработку нажатия кнопки SB2 с противоположным алгоритмом: при отпущенной кнопке SB2 светодиод LED1 должен быть зажжен, а при нажатой - погашен. 2. Написать программу управления светодиодом LED2 при помощи обеих кнопок. При нажатии на кнопку SB1 светодиод должен зажигаться и оставаться включенным до тех пор, пока не будет нажата кнопка SB2, которая выключает его до следующего нажатия SB1.

Ассемблер AVR для начинающих (третий шаг)

Итак, мои добросовестные читатели, надеюсь, вы благополучно освоили изложенный ранее материал и справились с предложенными мною заданиями. Настало время обогатить себя новыми знаниями. В прошлый раз я поведал о том, что все действия, которые выполняет контроллер, определяются так называемыми регистрами, и даже рассказал о трех из них: PORTB, DDRB, PINB. На самом деле этих регистров намного больше. Кроме того, существует две их основные разновидности. Рассмотренные ранее относятся к так называемым регистрам ввода-вывода (РВВ). Этот тип регистров определяет функционирование различных узлов и модулей контроллера: портов ввода/вывода, таймеров, прерываний, энергонезависимой памяти, АЦП, компаратора, цифровых интерфейсов и др. Для каждого из этих модулей определено по одному, а чаще по несколько РВВ. По мере изложения материала я буду рассказывать о них подробнее. Сегодня же нам понадобятся только уже известные нам по прошлому разу РВВ. Кроме РВВ, количество и состав которых отличается у разных микроконтроллеров, имеются еще так называемые регистры общего назначения (РОН). Все микроконтроллеры AVR имеют по 32 РОН, которые называются очень просто: r0, r1, r2, ... r31. Забегая вперед, скажу, что не все эти регистры равнозначны, но большей части команд нет разницы, какие из них задействовать. Следует уяснить и запомнить, что большинство операций в контроллере выполняется именно над РОН. Фактически, рассмотренные нами в прошлый раз команды составляют большую часть доступных команд для работы непосредственно с РВВ. Все РОН представляют собой однобайтовые регистры, к которым в ассемблере можно обращаться, указывая их имя.  Но это все слова. Перейдем теперь к практике. Напишем программу, которая будет самостоятельно мигать светодиодом LED2 с определенной частотой. Создаем новую папку с именем step3 и изменяем файл build.bat для ассемблирования из этой папки. Открываем ASM_Ed и пишем в нем следующий текст: .include "F:\Prog\AVR\asm\Appnotes\tn13def.inc" sbi DDRB, 4     ;Линия РВ4 - выход main:              ;Основной цикл программы sbic PINB, 4     ;Если РВ4=0 (светодиод зажжен), то пропустить след. строку cbi PORTB,4     ;Установка РВ4 в 0 (включение светодиода) sbis PINB, 4     ;Если РВ4=1 (светодиод погашен), то пропустить след. строку sbi PORTB,4     ;Установка РВ4 в 1 (выключение светодиода) ldi r16, 255      ;Загрузка значения в регистр r16 ldi r17, 255      ;Загрузка значения в регистр r17 delay:             ;Цикл задержки subi r16, 1       ;Вычитание 1 из регистра r16 sbci r17, 0       ;Вычитание с переносом из регистра r17 brcc delay       ;Если не было переноса вернуться к метке delay rjmp main       ;Вернуться к метке main Эта программа бесспорно уже побольше предыдущей. Внимательный читатель может увидеть в ней как знакомые ранее команды, так и совершенно новые. Вначале по традиции расскажу о нововведениях в программе, а затем об алгоритме работы программы. Можно увидеть, что помимо команд и комментариев, в программе появились метки. Это main в 3 строке и delay в 11 строке. Их можно узнать по двоеточию после них. Метки не занимают памяти контроллера, а лишь служат некоторым другим командам для навигации по программе.  Теперь о новых командах. Команда ldi имеет два операнда: первый - РОН, а второй - любая численная константа в диапазоне от 0 до 255 (поскольку все РОН однобайтовые, то в них можно записывать только числа в указанном диапазоне, при попытке записать большее число, транслятор выдаст предупреждение). В результате выполнения этой команды в указанный РОН загружается указанная константа. То есть если провести аналогию с языками высокого уровня, эта команда сходна с операцией присвоения (РОН = k). Команда subi по синтаксису аналогична вышеприведенной. В результате ее выполнения из указанного РОН вычитается указанная константа, а результат заносится в тот же регистр (РОН = РОН - k).  Команда sbci также аналогична по синтаксису, и сходна по функциональному назначению с предыдущей. Она также вычитает из указанного РОН указанную константу, но с учетом переноса. Что это означает? По сути практически любая логическая либо математическая операция устанавливает либо сбрасывает те или иные биты в так называемом статусном регистре SREG. Доступ к нему имеется только с использованием специальных ассемблерных команд. Обо всех битах я напишу в последующих статьях, пока же скажу, что есть в нем так называемый бит переноса С. Этот бит устанавливается в "1", если в результате предыдущей операции произошло переполнение в старший разряд при сложении либо заем из старшего разряда при вычитании (то есть при попытке прибавить число к 255 либо вычесть число из 0). Итак, команда sbic вычитает из РОН указанную константу и бит переноса и записывает результат в тот же РОН (РОН = РОН - k - C). Команда brcc относится к командам условного перехода. Единственным ее аргументом является метка, на которую перемещается программный счетчик, если в результате выполнения предыдущей операции не произошло переноса, то есть, если был сброшен бит С статусного регистра. Если же перенос произошел, выполняется следующая за ней команда (Если С=0, то перейти к метке, иначе перейти к следующей команде). Команда rjmp является командой безусловного перехода. Ее единственным аргументом также является метка, на который перемещается программный счетчик, однако перемещение выполняется всегда, без каких-либо условий. Таким образом, следующая за rjmp команда никогда не выполнится, если там не будет установлена другая метка, на которую будет совершен переход при помощи какой-то иной команды перехода. Ну что же, наш багаж знаний обогатился еще на 5 команд. Теперь в нашем распоряжении их целых 9 штук.  Рассмотрим алгоритм работы программы. Те строки, которые содержат уже известные нам команды, я буду описывать кратко, считая, что читатель с ними знаком. 1 строка. Подключение файла tn13def.inc к нашему ассемблерному файлу. 2 строка. Запись "1" в 4-й бит регистра DDRB для перевода линии РВ4 на выход 3 строка. Метка "main". В принципе, эту метку можно было бы и не ставить, но ее использование является правилом хорошего тона, потому что незачем каждый раз выполнять инициализацию портов ввода-вывода. Пока у нас она состоит всего из одной команды - это не так страшно, но ведь в реальных программах это десятки команд, и некоторые из них сбрасывают используемые модули к исходному состоянию. А зачем нам оно надо? Поэтому возьмите себе за правило. Сначала - инициализация, затем - зацикливание программы за пределами раздела инициализации. 4 и 5 строки. Если бит 4 в регистре PINB сброшен (то есть светодиод погашен, команда sbic), то переходим к строке 6, а иначе сбрасываем этот бит в регистре PORTB (команда cbi). 6 и 7 строки. Если бит 4 в регистре PINB установлен (то есть светодиод зажжен, команда sbis), то переходим к строке 8, а иначе устанавливаем этот бит в регистре PORTB (команда sbi). Таким образом, строки 4 - 7 выполняют самостоятельное переключение светодиода (проверяется состояние светодиода и меняется на противоположное). Если так и оставить программу, то окажется, что это переключение будет происходить с очень высокой скоростью, и глаз его не увидит, разве что заметит, что яркость светодиода стала меньше. Чтобы заставить светодиод мигать с приемлемой частотой, необходимо осуществить задержку выполнения программы. Обычно это делается путем многократного вычитания либо сложения. Поскольку каждая операция требует определенного времени, то выполнение однотипных команд несколько тысяч раз подряд как раз и обеспечит нам требуемую задержку. Рассмотрим теперь, как это реализовано у нас. Строка 8. При помощи команды ldi в РОН r16 загружается максимально возможное число 255. Почему именно r16 а не r0? Дело в том, что команды, имеющие вторым операндом константу, не работают с регистрами r0-r15. При попытке указать в качестве первого операнда такой регистр транслятор выдаст ошибку и откажется работать далее. Строка 9. Аналогична строке 8, только теперь загружается 255 в регистр r17. От значений, загруженных в эти регистры, будет зависеть наша задержка.  Строка 10. Метка "delay". Это начало цикла вычитания. Мы загрузили в два регистра числа 255. Теперь наша задача вычитать из них единцы, пока оба не станут равными 0. Когда это произойдет, мы сможем покинуть этот цикл. Строка 11. Командой subi мы вычитаем единицу из регистра r16. Вычитание производится просто, без всяких условий и переносов. Строка 12. На первый взгляд она кажется бессмысленной. Мы вычитаем из регистра r17 нуль. Однако, не стоит забывать, что команда sbci вычитает число с учетом переноса. Как это все работает в комплексе, я расскажу далее. Строка 13. Проверка бита переноса С. Поскольку эта команда идет следом за командой вычитания из регистра r17, то проверяется перенос именно из этого регистра. Пока содержимое регистра r17 будет больше либо равно 0, команда brcc будет отправлять нас к метке delay. Как только произойдет попытка вычитания из нуля в r17, мы сразу же переходим к строке 14. Теперь рассмотрим работу строк 10-13 в комплексе. Сначала мы вычитаем 1 из регистра r16. В следующей строке вычитаем 0 с учетом переноса из регистра r17. Пока содержимое регистра r16 больше или равно 0, содержимое регистра r17 не изменится. При вычитании единицы из нуля в регистре r16 происходят следующие события: в самом r16 устанавливается значение 255, кроме того, устанавливается флаг переноса С. Теперь при выполнении команды sbci из r17 вычтется 1. Таким образом, вычитание единицы из r17 происходит только тогда, когда в регистре r16 происходит попытка вычесть единицу из нуля. Когда же в регистре r17 произойдет вычитание единицы из нуля, также установится флаг переноса, который, будучи отловленным командой brcc, завершит цикл. За счет такой нехитрой конструкции получается, что весь цикл будет выполняться 256 х 256 = 65536 раз. Если учесть, что каждая команда выполняется один такт контроллера, и на переход к началу цикла тратится еще один такт, то каждый проход цикла выполняется за четыре такта. Таким образом имеем задержку в 4 х 65536 = 262144 тактов. Если учесть, что частота контроллера по умолчанию составляет 1 Мгц, то задержка будет равна 262144/1000000 = 0,26 с. Таким образом, светодиод будет менять свое состояние каждую четверть секунды, что даст нам частоту мигания приблизительно равную 2 Гц. Ну и наконец строка 14, о которой мы уже было позабыли. В ней происходит безусловный переход к метке main, таким образом мы исключаем повторную инициализацию вывода РВ4. Вот таким образом работает написанная нами программа. Замечу, что задавая различные значения, записываемые в регистры r16 - r17 можно получать разные задержки, меньшие 0,26, используя вышеизложенные расчеты. А как же быть, если необходимо сделать большую задержку? Тогда нужно добавить еще один регистр, например r18. И реализация задержки станет выглядеть так: ldi r16, 255      ;Загрузка значения в регистр r16 ldi r17, 255      ;Загрузка значения в регистр r17 ldi r18, 2         ;Загрузка значения в регистр r18 delay:             ;Цикл задержки subi r16, 1       ;Вычитание 1 из регистра r16 sbci r17, 0       ;Вычитание с переносом из регистра r17 sbci r18, 0       ;Вычитание с переносом из регистра r18 brcc delay       ;Если не было переноса вернуться к метке delay Общее число проходов цикла теперь станет равно 256х256х3 = 196608, а с учетом того, что в цикле появилась еще одна команда, теперь он будет выполняться за 5 тактов. Общее число тактов задержки составит 196608х5=983040, а период задержки: 983040/1000000 = 0,98 с. Возможно, внимательный читатель спросит, а почему же мы загружаем 255 и 2, а умножаем 256 и 3. Тут все дело в том, что счет введется не с 1, как мы привыкли, а с 0, и за счет этого происходит один лишний проход цикла. Вот так просто это все объясняется. Надеюсь, все мои рассуждения и пояснения понятны читателю. На этом я считаю, что шаг 3 можно считать сделанным. Мы узнали новые команды, ознакомились с разными видами регистров контроллера. В качестве самостоятельно работы предлагаю следующую задачу: организовать поочередное переключение светодиодов LED1 и LED2 с частотой, максимально близкой к 0,5 Гц, то есть с периодом в 2 с.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]