Мы научились включать и выключать светодиоды, а как сделать, чтобы они сами заморгали? Понятно, что после того, как их включили, нужно сделать паузу, затем выключить, снова выждать паузу и снова включить. А как сделать паузу?
Есть конечно, команда NOP, которая сделает паузу равную одному такту, но мы ее никак не увидим.
Такт – это один импульс тактового генератора. Современные контроллеры работают с тактовыми частотами от 1 до 60 мегагерц (от 1 до 60 миллионов импульсов в секунду). Большинство команд AVR выполняется за один такт, где то около 10 команд выполняется за 2 такта.
Поэтому паузу сделанную одной командой NOP, увидеть невозможно, а если нам нужна пауза в полсекунды - тогда нужно минимум написать 500 тысяч команд NOP. Но, это не реально!.
Может, остановить программу? А остановить мы ее не можем, иначе будет сбой программы. Есть другое решение – можно программу зациклить, чтобы отсчитать какое-то количество тактов, а затем продолжить.
Самый простой вариант зацикливания:
Cycle: rjmp Cycle
Строка начинается метки Cycle:
Метка обязательно заканчивается двоеточием – в этом ее отличие. Метка может иметь любое сочетание букв и цифр написанное в английской транскрипции (как в предыдущем примере я написал Cicle: и ничего, программа без проблем распознала эту метку).
Кто изучал программы для PIC, видимо заметил, что там после метки, двоеточие не ставится.
Далее стоит команда rjmp Cycle – относительный переход к метке Cycle т.е. на самого себя. Все “бесконечный цикл”.
Из этой программы можно выйти, только если были заданы условия прерывания, или включен сторожевой таймер WDR, иначе никак. Есть программы, основное тело которой состоит только из такой строки, вся остальная часть построена на подпрограммах, вызываемых прерываниями. Но эти случаи мы рассмотрим чуть позже.
Вообще – любая программа - это цикл, имеющий начало и конец
Самый короткий цикл, это тот который мы только, что рассмотрели, состоящий всего из одной команды. Нередко применяется тогда, когда от контроллера не требуется никаких действий, только ожидание.
Программа может представлять из себя огромный цикл, включающий в свою структуру множество мелких циклов – подпрограмм. Пишутся они с помощью команд ветвления и команд вызова подпрограмм.
Рассмотрим некоторые из них:
breq – Перейти (на определенную строку) если результат предыдущей строки =0
Если значение регистров указанных в предыдущей строке не равно 0, то команда игнорируется (как будто ее нет)
brne - Перейти (на определенную строку) если результат предыдущей строки не 0
Если значение регистров указанных в предыдущей строке равно 0, то команда игнорируется (как будто ее нет)
rcall - Относительный вызов подпрограммы
ret – Выход из подпрограммы
Здесь функции команд понятны без комментприев.
Еще рассмотрим пару команд, которые очень часто испоьзуются:
inc – Инкремент, прибавить к регистру единицу
dec – Декремент, вычесть из регистра единицу
Попробуем дописать предыдущую программу:
.def temp=r16; директива .def назначает регистру r16 имя temp
.def temp1=r17; директива .def назначает регистру r17 имя temp1
.def temp2=r18; директива .def назначает регистру r18 имя temp2
.def temp3=r19; директива .def назначает регистру r19 имя temp3
;====================================================
; Начало программы
.cseg; директива .cseg определяет начало сегмента, где будет расположен
; основной код программы. В AVR Studio 5 это директива не
; обязательна
.org 0; начало первой строки программы
rjmp Start; относительный переход к метке Start (в PIC соответствует
; команде goto)
; ====================================================
Start:
ser temp; устанавливает все биты регистра temp в 1
out DDRB,temp; переводит все биты
out DDRD,temp; порта B и D на вывод
clr temp; обнуляет регистр temp (устанавливает все биты регистра temp в 0)
out PortB,temp; отключает подтягивающие резисторы
out PortD,temp; портов B и D
Cicle:
ldi temp,0b11001100; включает светодиоды
out PortB, temp; порта B
rcall Pause; вызов подпрограммы задержки
clr temp; выключает светодиоды
out PortB, temp; порта B
rcall Pause; вызов подпрограммы задержки
rjmp Cicle; Возвращаемся к метке Cicle, зацикливаемся
; ====================================================
; Подпрограмма задержки
; ====================================================
Pause:
ldi Temp1,0; записать в регистр temp1 знчение 0
ldi Temp2,0; записать в регистр temp2 знчение 0
ldi Temp3,2; записать в регистр temp3 знчение 2
Pause1:
dec Temp1; вычесть из значения регистра temp1 единицу
brne Pause1; если значение temp1 не равно 0 перейти к метке Pause1
dec Temp2; вычесть из значения регистра temp2 единицу
brne Pause1; если значение temp2 не равно 0 перейти к метке Pause1
dec Temp3; вычесть из значения регистра temp3 единицу
brne Pause1; если значение temp1 не равно 0 перейти к метке Pause1
ret; выйти из подпрограммы
Здесь проект для Proteus:
🎁attiny2313_led_1.rar 27.14 Kb ⇣ 136
компилируем, загружаем проект, распаковываем и запускаем:
Смотрим как работает программа в Proteus (как загрузить .hex файл подробно описано во 2-ой части). Что видим? Заморгали светодиоды?
🎁morganie.wmv 491.56 Kb ⇣ 118
Разберем, что мы тут понаписали:
Строка:
clr temp – обнуляет регистр temp
Строка:
rcall Pause
; вызов подпрограммы задержкикоманда rcall вызывает подпрограмму с меткой Pause, которая реализует нужную нам задержку. Выход из подпрограммы происходит по команде - ret
По команде ret происходит возврат в прежнюю точку, откуда была вызвана подпрограмма. Подпрограммы удобно создавать, когда в программе требуется многократно выполнять одно и тоже действие. Достаточно написать одну подпрограмму и обращаться к ней в любой момент, по мере необходимости.
Как работает наша подпрограмма Pause?
Нам нужно правильно рассчитать время задержки. Возьмем во внимание, что внутренняя (без внешнего кварцевого генератора) тактовая частота нашего микроконтроллера равна 8 МГц. По умолчанию, происходит деление этой частоты на 8 т.е. реальная тактовая частота равна 1 МГц, и задержка одного такта составит 1 миллисекунду. Для того, чтобы организовать задержку в 0,5 секунды, нужно пропустить 500 тысяч тактов. Как это сделать?
А для этого мы организуем 3-х байтный счетчик из регистров Temp1, Temp2, Temp3
Почему трехбайтный? Давайте посмотрим, как выглядит число 500000 в двочной и шестнадцатиричной системах.
[b0] 00000111 10100001 00100000;
07 A1 20 [h]
Наглядно видно, что число состоит из трех байт.
Вроде все просто, но если мы запишем эти числа в регистры, то на самом деле задержка окажется значительно больше, чем мы ожидали. Почему?
А ведь кроме регистров есть в подпрограмме команды, которые выполняются за определенное количество тактов, они то и забирают дополнительное время. Чем больше команд, тем большее время будет выполняться задержка. Если грубо прикинуть, то время задержки будет примерно в четыре раза больше ожидаемого! Поэтому я записал в младшие регистры нули, а в старший 2.
Pause:
ldi Temp1,0; записать в регистр temp1 знчение 0
ldi Temp2,0; записать в регистр temp2 знчение 0
Не будет ошибкой, если мы пропишем clr temp1 и clr temp2
ldi Temp3,2; записать в регистр temp3 знчение 2
После того, как прописали значения регистров переходим к самой задержке:
Строки:
Pause1:
dec Temp1
brne Pause1
команда dec Temp1 – вычитает из регистра temp1, (в который сейчас прописан нуль) единицу, результатом будет число FFh или 255 (чтобы проверить - сделайте обратное действие в восьми битном регистре, произойдет переполнение и обнуление регистра)
команда brne Pause1 – сравнивает значение пред идущего регистра (регистра temp1) с нулем. Если результат не равен нулю, то пересылает к метке Pause1. Цикл повторяется до тех пор, пока значение temp1 не станет равным нулю.
Если значение регистра temp =0, то команда brne игнорируется и выполняется пустой такт (как Nop), а прогамма переходит к строке ниже.
Аналогично происходит и с регистрами temp2 и temp3, пока не будет выполнена команда – ret.
А что делать, если нам надо вычислить задержку с точностью до нескольких микросекунд?
Для этого используем AVR Studio 5
В редакторе пишем текст (можно скопировать и перенести отсюда).
После того, как ввели текст, компилируем программу (F7).
Кликаем (View)
В меню (View) кликаем (Toolbars)
Ставим галочку в подменю (AVR Dbugger)
Выходим из подменю на страницу редактора, устанавливаем курсор на строке в начале подпрограммы задержки и кликаем (F9)
Возникает красный кружек с выделенной строкой (Breakpoint) точка останова.
Точно также выделяем вторую строку (Breakpoint)
1. Кликаем на зеленый треугольник (Start), тем самым запускаем эмуляцию-отладку (Debuger).
2. Кликаем на символ микросхемы, если справа не появилось окно (Processor).
3. Устанавливаем желтую стрелку на верхнем Breakpoint-е, кликая по зеленому треугольнику (Start).
4. Выделяем строку (Cycle Counter) и прописываем 0.
5. Устанавливаем тактовую частоту процессора
Кликаем на зеленый треугольник (Start) и ждем появления желтой стрелки в нижнем красном кружке Breakpoint-а.
Смотрим строку Stop Watch (секундомер).
Это время от начала подпрограммы задержки, до его завершения.
Чтобы убрать Breakpoint-ы, нужно подвести курсор на красный кружек и кликнуть (F9).
Таким образом можно определить длительность выполнения любого отрезка программы, и очень точно выставить временные интервалы.
В нашей программе время отчитывается с момента включения светодиода до момента его отключения. Частота процессора соответствует 1 МГц. Установим Breakpoint на команде включения светодиода, а второй на команде выключения.
Запустим Debug-ер, кликнув зеленый треугольник. Смотрим показания секундомера Stop Watch.
Это реальное время включения светодиодов (394 255,00 микросекунд). Снова обнуляем, и запускем Debug-ер,
теперь в показаниях секундомера смотрим время паузы (394 257,00 микросекунд). Получили разницу в 2 микросекунды. Попробуйте самостоятельно, с помощью команд Nop, сделать одинаковыми время включения и выключения светодиодов.
Изменим программу таким образом,, чтобы получить возможность управлять включением светодиодов с помощью кнопок.
.def temp=r16; дирекива .def назначает регистру r16 имя temp
.def temp1=r17; директива .def назначает регистру r17 имя temp1
.def temp2=r18; директива .def назначает регистру r18 имя temp2
.def temp3=r19; директива .def назначает регистру r19 имя temp3
;====================================================
; Начало программы
.cseg; директива .cseg определяет начало сегмента, где будет расположен
; основной код программы. В AVR Studio 5 это директива не
; обязательна
.org 0; начало первой строки программы
rjmp Start; относительный переход к метке Start (в PIC соответствует
; команде goto)
; ====================================================
Start:
ser temp; устанавливает все биты регистра temp в 1
out DDRB,temp; переводит все биты порта B на вывод
clr temp1; обнуляет регистр temp1
out DDRD,temp1; переводит все биты порта D на ввод
out PortB,temp1; отключает подтягивающие резисторы портов B
out PortD,temp; включает подтягивающие резисторы портов D
Cicle:
in temp,PinD; считывает значение порта D и
out PortB, temp; переводит результат в порт B
rcall Pause; вызов подпрограммы задержки
clr temp; выключает светодиоды
out PortB, temp; порта B
rcall Pause; вызов подпрограммы задержки
rjmp Cicle; Возвращаемся к метке Cicle, зацикливаемся
; Подпрограмма задержки
; ====================================================
Pause:
clr temp1; обнулить регистр temp1
clr temp2; обнулить регистр temp2
ldi temp3,2; записать в регистр temp3 число 2
Pause1:
dec temp1; вычесть из значения регистра temp1 единицу
brne Pause1; если значение temp1 не равно 0 перейти к метке Pause1
dec temp2; вычесть из значения регистра temp2 единицу
brne Pause1; если значение temp2 не равно 0 перейти к метке Pause1
dec temp3; вычесть из значения регистра temp3 единицу
brne Pause1; если значение temp1 не равно 0 перейти к метке Pause1
ret; выйти из подпрограммы
Строка:
in temp,PinD
Команда in считывает значение регистра порта D, и записывает в регистр temp.
Небольшое отступление: По поводу правильного введения цифровых значений в программу. Программа AVR Studio 5, как и предыдущие версии не понимает интеловской формы введения шестнадцатеричных чисел, где после значения ставиться маленькая буква h, например A5h. В AVR Studio 5 правильно писать 0хА5. В обычном тексте удобно писать и пояснять именно в интеловской форме, но в тексте программ такая форма недопустима.
Компилируем программу и перейдем в программу Proteus.
Теперь у нас появилась возможность подключить кнопки и понаблюдать, как они влияют на светодиоды.
Если подвести курсор на кнопку и кликнуть клавишей break на компьютерной клавиатуре, то нажатие кнопки будет зафиксировано, таким же образом можно отжать кнопку. Поиграйте с кнопками, понаблюдайте за процессом, попробуйте самостоятельно изменить время задержки в программе.
Вот небольшой видеоролик, того, что должно получиться.
🎁knopki.wmv 1.73 Mb ⇣ 120
Далее мы напишем программу бегущих огней, выясним, что такое стек.
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.