Text
                    Микроконтроллеры AVR
Вводный курс


John Morton AVR An Introductory Course Newnes
Серия «МИРОВАЯ ЭЛЕКТРОНИКА» Джон Мортон Микроконтроллеры AVR Вводный курс Перевод с английского Москва Издательский дом «Додэка-ХХ1» 2006
УДК 621.316.544. lAtmel ББК 31.264 М80 Мортон Дж. М 80 Микроконтроллеры AVR. Вводный курс. /Пер. с англ. — М.: Из- Издательский дом «Додэка-ХХ1», 2006. — 272 с: ил. (Серия «Мировая электроника»). ISBN 5-94120-096-Х Данное издание представляет собой практическое руководство, с помощью которого вы сможете изучить, а впоследствии и использовать микроконтролле- микроконтроллеры AVR компании Atmel. Неважно, студент ли вы, собирающийся использовать микроконтроллер AVR в своем проекте или же опытный разработчик встраиваемых систем, впер- впервые столкнувшийся с AVR, — если вам нужно быстро разобраться в этих попу- популярных микроконтроллерах, то эта книга для вас. Для демонстрации различных возможностей AVR Джон Мортон использует простые устройства и программы. В отличие от книг, в которых излагается голая теория либо просто воспроизводится фирменная техническая документация, та- такой подход (обучение в процессе использования) предлагает быстрое и интуи- интуитивное изучение возможностей микроконтроллеров AVR. В общей сложности, в книге рассмотрены 16 проектов, охватывающих все наиболее популярные микроконтроллеры AVR, включая модели семейства Tiny. Предназначена для разработчиков радиоэлектронной аппаратуры, инжене- инженеров, студентов технических вузов и радиолюбителей. УДК 621.316.544. lAtmel ББК 31.264 Все права защищены. Никакая часть этого издания не может быть воспроизведена в любой форме или любыми средствами, электронными или механическими, включая фо- фотографирование, ксерокопирование или иные средства копирования или сохранения информации, без письменного разрешения издательства. This edition of AVR: An Introductory Course by John Morton is published by arrangement with Elsevier Ltd, The Boulevard, Langford Lane, Kidlington, 0X5 1GB, England ISBN 0 7506 56352 (англ.) © John Morton, 2002 ISBN 5-94120-096-Х (рус.) © Издательский дом «Додэка-XXI», 2006 ® Серия «Мировая электроника»
ОГЛАВЛЕНИЕ Благодарности 9 Предисловие 10 Глава 1. Введение 11 Краткое замечание для пользователей PIC 13 Системы счисления 14 Сложение в двоичной системе 17 Отрицательные числа 17 8-битный RISC FLASH-микроконтроллер? 19 Первые шаги 20 Выбор модели 20 Блок-схема алгоритма 23 Написание программы 24 Ассемблирование 25 Регистры 25 Команды 29 Шаблон программы 30 5
Оглавление Глава 2. Основные операции в AT90S1200 и TINY12 Программа А. Светодиод (LEDon) AVR Studio — трансляция с языка ассемблера Проверка AVR Studio — симуляция Эмуляция Аппаратное обеспечение AVR Studio — программирование Конфигурационные ячейки Программы В и С. Кнопка Семисегментные индикаторы и косвенная адресация Программы D и Е. Счетчик Формирование временных интервалов Программа F. Бегущий огонек Формирование временных интервалов без таймера? Счетчик команд и подпрограммы Программа G. Счетчик (версия 3.0) Программа Н. Светофор Логические элементы Программа I. Симулятор логических элементов SREG — регистр состояния Сторожевой таймер Спящий режим Остальные команды Программа J. Частотомер Глава 3. Знакомство с остальными моделями семейства Глава 4. Дополнительные возможности Прерывания Программа К. Измеритель скорости реакции Случайное распределение Аналоговый компаратор Программа L. 4-битный аналого-цифровой преобразователь Аналого-цифровой преобразователь (АЦП) Программа М. Инвертор напряжения EEPROM Таймер/счетчик 1 A6-битный) Функция захвата Функция сравнения Главная программа N. Музыкальный автомат Глава 5. Продвинутые возможности ШИМ — широтно-импульсная модуляция UART Программа О. Конвертер клавиатуры -6-
Оглавление Последовательный интерфейс SPI 163 Нестандартный Таймер 1 модели Tinyl5 167 Сокращение объема кода 170 Обзор семейства Mega 171 Заключительная программа Р. Робот, управляемый компьютером 172 Заключение 178 Приложение Л. Основные параметры некоторых моделей AVR 180 Приложение В. Цоколевка некоторых моделей AVR 181 Приложение С. Обзор системы команд 182 Приложение D. Справочник команд 186 Приложение Е. Таблица векторов прерываний 195 Приложение К Преобразование шестнадцатеричных чисел 197 Приложение G. Таблица кодов символов ASCII 198 Приложение Н. Если ничего не получается, прочтите это 199 Приложение I. Контактная информация и дополнительная литература 200 Приложение J. Полные тексты учебных программ 201 Ответы к упражнениям 244 Предметный указатель 265 Предметный указатель 1001 -7-
Посвящается Таре
БЛАГОДАРНОСТИ Когда Роберт Жарнек познакомил меня с микроконтрол- микроконтроллерами AVR, я очень быстро осознал их преимущества перед прочими микроконтроллерами. Единственным недостатком, впрочем, весьма относительным, была их неизвестность по сравнению, например, с микроконтроллерами PIC фирмы Microchip. Я прекрасно понимал, что быстрое распростране- распространение микроконтроллеров AVR всего лишь вопрос времени, и поэтому написал книгу, которую можно рассматривать как базовое руководство по их использованию. Эта книга пред- предназначена для тех, кто совершенно незнаком с микроконт- микроконтроллерами или имеет о них только смутное представление. Я хотел бы воспользоваться возможностью и поблагода- поблагодарить всех, кто помогал мне в создании этой книги. Английс- Английское отделение компании Atmel любезно предоставило мне образцы своего оборудования, однако я вас уверяю — при на- написании книги я оставался беспристрастным и объективным! Я очень хочу поблагодарить Мэта Вебба за его квалифициро- квалифицированную и тщательную вычитку, в результате которой на стра- страницах появлялась целая куча надписей «Что это?». Несмотря на то что у него было множество других, более полезных дел, вроде сдачи выпускных экзаменов, он умудрялся найти время для тщательного просмотра моей рукописи. Также я хочу вы- выразить свою благодарность Ричарду Джорджу за предложен- предложенные им примеры программ и общие советы. Я благодарю Мэ- Мэта Гаррисона за помощь в подготовке иллюстраций — впос- впоследствии он начал учиться по этому направлению в Королевском художественном колледже. В заключение я должен поблагодарить Макса Хоси за его огромное великоду- великодушие, поддержку и консультации, а также руководство кафед- кафедры электронной техники колледжа Рэдли, Абингдон за пре- предоставленную возможность работать в их великолепно обо- оборудованной лаборатории. Джон Мортон -9-
ПРЕДИСЛОВИЕ Примите мои поздравления! Раз вы читаете эту книгу, значит, вас заинтересовало одно из наиболее производитель- производительных и универсальных семейств 8-битных микроконтроллеров в мире — семейство AVR. Прочитав книгу, вы получите общее представление обо всех микроконтроллерах семейства и уз- узнаете, каким образом с их помощью можно упростить разра- разработку своих устройств, а также создавать более сложные из- изделия. Микроконтроллеры AVR, как и все другие, позволяют со- создавать нестандартные и вместе с тем достаточно гибкие ре- решения. Однако микроконтроллеры AVR являются при этом эффективными, быстродействующими и простыми в исполь- использовании, благодаря чему идеально подходят для разработчи- разработчиков электронных устройств. Сначала мы познакомимся с ос- основными принципами программирования микроконтролле- микроконтроллеров (в частности, с различными системами счисления) и подробно рассмотрим основные этапы создания программ. После этого вы приступите к изучению собственно микро- микроконтроллеров AVR, причем все рассматриваемые вопросы будут сопровождаться примерами в виде реально работаю- работающих программ. Среди этих программ, в частности, имитатор светофора, музыкальный автомат, частотомер и даже робот, управляемый персональным компьютером. На первых порах мы в основном будем рассматривать го- готовые учебные программы. Однако по мере прочтения книги объем кода, самостоятельно написанного вами при выполне- выполнении упражнений, будет постоянно увеличиваться. Эти уп- упражнения встречаются на протяжении всей книги, а ответы к ним приведены в самом конце. В приложениях собраны ос- основные данные, относящиеся к наиболее популярным мик- микроконтроллерам AVR, что позволяет быстро найти нужную информацию, не перерывая кучу документации. Короче говоря, в этой книге используется активная мето- методика обучения программированию микроконтроллеров AVR. Кроме того, книга будет полезным источником информации для всех программистов, работающих с этими микроконт- микроконтроллерами. Джон Мортон -10-
Глава 1. ВВЕДЕНИЕ Микроконтроллеры AVR — это одни из самых быстродействующих микроконтроллеров в мире. Лично я представляю себе микроконтроллер в виде бесполезного куска кремния, обладающего тем не менее огромным потенциалом. Пока в нем нет программы, он ничего не будет делать, одна- однако при ее наличии он сможет выполнять практически любые функции. Достаточно большая принципиальная схема в ваших руках может превра- превратиться в обычную программу, уменьшив таким образом целое устройство до одной единственной микросхемы. Микроконтроллеры ликвидируют разрыв между аппаратным и программным обеспечением — они выполня- выполняют программу как обычный компьютер, являясь в то же время дискретны- дискретными элементами, которые могут взаимодействовать с другими компонента- компонентами схемы. За несколько лет микроконтроллеры стали неотъемлемой час- частью инструментария радиоинженеров и огромного числа радиолюбителей, поскольку они великолепно подходят для экспериментирования, мелкосе- мелкосерийного производства и реализации проектов, требующих определенной гибкости выполняемых функций. Этапы разработки программного обеспечения микроконтроллеров AVR приведены на Рис. 1.1. Среди микроконтроллеров AVR имеется огромное количество различ- различных моделей, начиная от небольших устройств в 8-выводных корпусах (се- (семейство Tiny) и заканчивая микросхемами в 40-выводных корпусах (се- (семейство Mega)!). Однако самое потрясающее заключается в том, что мож- можно спокойно писать программу для одной модели, а затем передумать и переделать эту программу под другую модель микроконтроллера, внеся всего лишь незначительные изменения. Более того, изучая один из микро- микроконтроллеров AVR, вы научитесь работать со всеми моделями семейства. Разумеется, каждый из микроконтроллеров имеет свои особенности, од- однако в основе всех моделей лежит общее ядро. !) В настоящее время наиболее развитые микроконтроллеры AVR выпускают- выпускаются в 64-выводных корпусах. — Примеч. пер. -11-
Глава 1. Введение 1. Чистый AVR ничего не делает 2. Пишем программу на компьютере 3. Программируем виртуальный AVR в компьютере 6. Проверяем программу в реальном устройстве 5. Программируем реальный AVR 4. Проверяем программу на компьютере Рис. 1.1. Этапы разработки программного обеспечения микроконтроллеров AVR Вообще говоря, программирование микроконтроллеров AVR заключа- заключается в различных манипуляциях числами. Соответственно, задача про- программирования состоит в том, чтобы заставить микроконтроллер выпол- выполнять поставленную задачу путем простых перемещений чисел и осуществ- осуществления операций над ними. Существует ограниченный набор операций, которые можно выполнять над числами, — эти операции называются ко- командами. В программах используются как простые команды (общего на- назначения), так и более сложные, выполняющие различные специфические функции. Микроконтроллер будет последовательно перебирать эти ко- команды, выполняя миллионы их каждую секунду (это зависит от частоты подключенного к нему генератора), и, таким образом, выполнять постав- поставленную задачу. В микроконтроллерах AVR числа можно: 1. Принимать с входов (например, используя входной «порт»). 2. Сохранять в определенных ячейках микросхемы. 3. Обрабатывать (например, складывать, вычитать, умножать и т.п.). 4. Передавать через выходы (например, используя выходной «порт»). Вот, в принципе, и все, что касается программирования — вы, навер- наверное, уже думаете: «Класс!» К счастью, в микроконтроллерах AVR имеется много других полезных функций, которые сильно облегчают нам жизнь. Сюда относятся различные модули, такие как встроенные таймеры, после- последовательные интерфейсы, аналоговые компараторы, а также куча так на- -12-
Краткое замечание для пользователей PIC зываемых «флагов», благодаря которым мы можем определить, произошло ли какое-либо определенное событие или нет. Мы начнем обучение с рассмотрения основных концепций, общих для всех микроконтроллеров, и сразу же после этого приступим к изучению нескольких учебных проектов на микроконтроллерах AT90S1200 (которые будем для краткости называть 1200) и Tiny. Затем мы познакомимся с более сложными операциями, воспользовавшись для этого более развитыми мо- моделями (такими, как AT90S2313). В заключение мы изучим наиболее про- продвинутые возможности микроконтроллеров AVR и выполним заключи- заключительный проект на базе микроконтроллера 2313. Большинство рассматри- рассматриваемых нами проектов можно легко адаптировать под любую модель AVR, поэтому вам совершенно не требуется бежать в магазин и скупать все име- имеющиеся там микроконтроллеры. Краткое замечание для пользователей PIC Я полагаю, что многие читатели уже знакомы с популярными микро- микроконтроллерами PIC фирмы Microchip. Поэтому я вкратце упомяну о пре- преимуществах микроконтроллеров AVR по сравнению с PIC. Тем, кто не имеет о микроконтроллерах PIC никакого понятия, не стоит особо беспо- беспокоиться, если что-то окажется непонятным, — чуть позже вы все поймете! Прежде всего, микроконтроллеры AVR имеют более совершенную ар- архитектуру и могут выполнять команды в каждом такте (в отличие от PIC, которым для выполнения команды требуется четыре такта). Поэтому при той же тактовой частоте микроконтроллеры AVR работают в 4 раза быст- быстрее. Кроме того, они имеют 32 рабочих регистра (в отличие от одного единственного, имеющегося в PIC) и почти в 3 раза больше команд. Бла- Благодаря этому программы для AVR практически всегда будут короче анало- аналогичных программ для PIC. Однако, несмотря на то что в документации указывается от 90 до 120 команд (в зависимости от модели), многие из них дублируют друг друга, и, по моим подсчетам, из всех команд действитель- действительно уникальными является не более 50. А вот к так называемым регистрам специальных функций микроконт- микроконтроллеров PIC (которые в AVR называются регистрами ввода/вывода) разре- разрешен прямой доступ (можно писать непосредственно в порты), что в микро- микроконтроллерах AVR не допускается. Однако это не такой уж большой недо- недостаток, и в целом программы для AVR являются более эффективными. Все микроконтроллеры AVR имеют FLASH-память программ, что позволяет осуществлять их многократное перепрограммирование. И наконец, в связи с тем, что различные модели микроконтроллеров PIC разрабатывались на протяжении многих лет, у них имеется ряд досадных проблем с совмести- совместимостью, которых в микроконтроллерах AVR до сих пор удавалось избегать. -13-
Глава 1. Введение Системы счисления Теперь пришло время познакомиться с различными системами счисле- счисления, используемыми при программировании микроконтроллеров AVR: двоичной, десятичной и шестнадцатеричной. Двоичные числа являются числами с основанием 2 (т.е. каждая цифра может принимать только два значения: 0 или 1) в отличие от десятичных чисел, имеющих основание 10, с десятью различными цифрами (от 0 до 9). Соответственно, числа в шест- шестнадцатеричной системе имеют основание 16 и представлены 16 различны- различными цифрами @, 1, 2, 3,4, 5, 6, 7, 8, 9, А, В, С, D, Е и F). В Табл. 1.1 приведен пример счета в различных системах счисления. Таблица 1.1. Пример счета в различных системах счисления Двоичная (8 разрядов) 00000000 00000001 00000010 00000011 00000100 00000101 00000110 00000111 00001000 00001001 00001010 00001011 00001100 00001101 00001110 00001111 00010000 00010001 Десятичная C разряда) 000 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 Шестнадцатеричная B разряда) 00 01 02 03 04 05 06 07 08 09 0А ОВ ОС 0D 0Е 0F 10 11 и т.д. Двоичный разряд (или бит), расположенный справа, называется млад- младшим значащим разрядом (МЗР) или младшим значащим битом (Least Significant Bit — LSB), а также битом 0 (почему нумерация начинается с 0, а не с 1, станет ясно немного позже). Нулевой бит показывает количество -14-
Системы счисления «единиц» в числе. Единица равна 2°. Бит, расположенный левее (бит 1), показывает количество «двоек», следующий бит (бит 2) показывает коли- количество «четверок» и т.д. Отметим, что 2 = 21, а 4 = 22, т.е. номер бита соот- соответствует степени двойки, представляемой этим битом. Помните, что ну- нумерация битов ведется справа налево (об этом очень часто забывают!). Со- Совокупность 8 битов называется байтом. Самый старший бит двоичного слова (например, 7-й бит байта) называется старшим значащим разрядом (СЗР) или старшим значащим битом (Most Significant Bit — MSB). Таким образом, чтобы преобразовать десятичное число в двоичное, не- необходимо найти наибольшую степень двойки, которая будет меньше этого числа, вычесть и многократно повторить указанные вычисления. гтг ПРИМЕР 1.1. Найдем двоичный эквивалент десятичного числа 83. Наибольшая степень двойки, меньше 83, равна 64 = 26. Бит 6 = 1. Разность 83 - 64 = 19. 32 > 19, поэтому бит 5 = 0, 16 < 19, поэтому бит 4=1. Разность 19-16 = 3. 8 > 3, поэтому бит 3 = 0, 4 > 3, поэтому бит 2 = 0, 2 < 3, поэтому бит 1 = 1. Разность 3 — 2=1. 1 = 1, поэтому бит 0=1. Таким образом, двоичный эквивалент равен 1010011. В то же время существует другой (и более изящный) метод, который, вероятно, покажется вам более легким. Возьмите число, которое вы хотите преобразовать, и разделите его на два. Если остаток равен единице (т.е. число было нечетным), запишите единицу. Затем снова разделите ре- результат на два и так далее, записывая остаток слева от предыдущего значе- значения, до тех пор, пока делимое (и остаток) не станет равным единице. Г~7[ ПРИМЕР 1.2. Найдем двоичный эквивалент десятичного числа 83. Делим 83 на 2. Частное 41, остаток 1. Делим 41 на 2. Частное 20, остаток 1. Делим 20 на 2. Частное 10, остаток 0. Делим 10 на 2. Частное 5, остаток 0. Делим 5 на 2. Частное 2, остаток 1. Делим 2 на 2. Частное 1, остаток 0. Делим 1 на 2. Частное 0, остаток 1. Таким образом, двоичный эквивалент равен 1010011. -15-
Глава 1. Введение /gS УПРАЖНЕНИЕ 1.1. Найдите двоичный эквивалент десятичного -^ числа 199. /gS УПРАЖНЕНИЕ 1.2. Найдите двоичный эквивалент десятичного ^^ числа 170. Аналогично двоичным числам разряд 0 шестнадцатеричного числа по- показывает количество единиц A6° = 1), разряд 1 — количество чисел 16 A61 = 16) и т.д. Чтобы преобразовать десятичное число в шестнадцатерич- ное (вместо этого слова часто используют сокращение «hex»), следует оп- определить, сколько в числе содержится единиц и сколько чисел 16. 0 ПРИМЕР 1.3. Преобразуем десятичное число 59 в шестнадцатерич- ное. В числе 59 содержится три числа 16, поэтому 1-й разряд равен 3. Разность 59 - 48 = 11; число 11 соответствует шестнадцате- ричному В, поэтому 0-й разряд равен В. Следовательно, искомое число равно ЗВ. tgS УПРАЖНЕНИЕ 1.3. Найдите шестнадцатеричный эквивалент де- ^^ сятичного числа 199. tgS УПРАЖНЕНИЕ 1.4. Найдите шестнадцатеричный эквивалент де- ^~* сятичного числа 170. Одной из полезных особенностей шестнадцатеричной системы, кото- которую вы могли заметить при выполнении Упражнения 1.4, является очень простое преобразование двоичных чисел в шестнадцатеричные. Если раз- разбить двоичное число на 4-битные группы (называемые полубайтами, или тетрадами), то каждая такая группа будет соответствовать одному шест- надцатеричному разряду. 0 ПРИМЕР 1.4. Преобразуем число 01101001 в шестнадцатеричную систему счисления. Делим число на полубайты: ОНО и 1001. Не- Нетрудно заметить, что ОНО соответствует 4 + 2 = 6, а 1001 соответ- соответствует 8+1 = 9. Таким образом, указанное 8-битное число равно 69 в шестнадцатеричной системе. Очевидно, что это преобразование выполнить гораздо проще, чем в случае десятичных чисел, поэтому при программировании шестнадцатеричные числа используются намного чаще. jgS УПРАЖНЕНИЕ 1.5. Преобразуйте 11100111 в шестнадцатеричное ^^ число. -16-
Сложение в двоичной системе Сложение в двоичной системе Сложение двоичных чисел выполняется абсолютно по тем же прави- правилам, что и десятичных. Посмотрим различные комбинации битов. 0 + 0 = 0 нет переноса 1+0=1 нет переноса 1 + 1 = 0 перенос 1 1+0 + 0=1 нет переноса 1 + 1+0 = 0 перенос 1 1 + 1 + 1 = 1 перенос 1 ГТТ ПРИМЕР 1.5.4 +7 =11 7 1100 +0111 1011 = 11 в десятичной системе &< УПРАЖНЕНИЕ 1.6. Вычислите 01011010 + 00001111, используя ^^ двоичное сложение. Отрицательные числа Мы разобрались с вами, как преобразовывать положительные десятич- десятичные числа в двоичные, но как сделать то же самое с отрицательными? Пре- Прежде всего, необходимо выделить один бит для хранения знака, в результате чего 4-битное число сможет принимать значения от -7 до +8. Вообще го- говоря, имеется несколько способов представления отрицательных чисел, однако наиболее распространенным является представление отрицатель- отрицательных чисел в дополнительном коде (two's complement). Чтобы из положи- положительного числа получить отрицательное число в дополнительном коде, не- необходимо инвертировать все биты исходного числа, а затем прибавить к получившемуся числу единицу. Г7Г ПРИМЕР 1.6. 0111 =7 Инвертируем все биты: 1000 Прибавляем единицу: 1001 1001 = —7 Г7\ ПРИМЕР 1.7. 1000 = 8 Инвертируем: 0111 Прибавляем единицу: 1000 1000 = -8 = +8 НЕОДНОЗНАЧНОСТЬ! -17-
Глава 1. Введение Из Примера 1.7 видно, что мы не можем использовать число -8, пос- поскольку его двоичное представление совпадает с представлением числа +8. Эта асимметрия является прискорбным недостатком представления чисел в дополнительном коде, однако с ним приходится мириться, поскольку этот недостаток является наименьшим по сравнению с недостатками дру- других способов представления двоичных чисел. Давайте попробуем сложить -2 и + 7. г-Я ПРИМЕР 1.8. 2 = 0010, соответственно -2=1110 LJ 1110 = «2 +0111 = 7 0101 = 5 Что и ожидалось! /gS УПРАЖНЕНИЕ 1.7. Представьте число -40 в 8-битном дополни- ^^ тельном коде и докажите, что результат операции —40 + 50 соответ- соответствует ожидаемому (-10). Благодаря такому представлению чисел нам достаточно просто прове- проверить старший значащий бит (MSB), чтобы определить, отрицательное пе- перед нами число или положительное. Единица в старшем бите соответству- соответствует отрицательному числу, нуль — положительному. Однако применительно к результатам сложения или вычитания больших положительных или от- отрицательных чисел это утверждение может быть неверным. Г^Г ПРИМЕР 1.9.69 + 120 = ... 1 11000101 = +69 01111000 = +120 10111101 = +189 или-67 Другими словами, при использовании чисел в дополнительном коде мы должны интерпретировать результат как отрицательный (имеющий 1 в старшем бите). Поэтому существует проверка на переполнение дополни- дополнительного кода, которую мы можем использовать для определения действи- действительного знака результата. Переполнение дополнительного кода происхо- происходит, когда: • MSB обоих слагаемых равны 0, a MSB результата равен 1. • MSB обоих слагаемых равны 1, a MSB результата равен 0. Соответственно, действительный знак числа определяется результатом проверки на переполнение дополнительного кода и значением MSB ре- результата операции (см. Табл. 1.2). -18-
8-битный RISC FLASH-микроконтроллер? Таблица 1.2. Определение действительного знака результата Переполнение дополнительного кода Нет Нет Есть Есть MSB результата 0 1 0 I Знак + - + - При сложении чисел в Примере 1.10 произошло переполнение допол- дополнительного кода, a MSB результата равен 1, поэтому результат положи- положительный (+189), как и ожидалось. Думаю, вы будете рады узнать, что боль- большинство описанных действий поддерживается микроконтроллерами AVR автоматически. Другим способом представления отрицательных чисел является обрат- обратный код числа (one's complement), который получается в результате просто- простого инвертирования всех его битов и занесения единицы в знаковый бит. 8-битный RISC FLASH-микроконтроллер? Мы называем AVR 8-битным микроконтроллером. Это означает, что он оперирует 8-битными числами. Двоичное число 11111111 является наи- наибольшим 8-битным числом и равно десятичному 255 и шестнадцатерично- му FF (проверьте!). Для указания конкретной системы счисления в про- программах используются различные способы записи (ведь десятичное число 11111111 очень сильно отличается от двоичного числа 11111111!). Двоич- Двоичные числа записываются в виде ОЬООЮЮОО (т.е. 0Ь...). Десятичная система счисления используется по умолчанию, а шестнадцатеричные числа начи- начинаются с символов Ох или знака доллара (ОхЗА, или $ЗА). Следовательно, ОЬОО101011 равно 43, которое равно 0x2 В. При работе с входами и выходами микроконтроллеров AVR обычно ис- используют двоичную систему счисления, при этом каждый входной или вы- выходной контакт соответствует конкретному биту. Бит, установленный в 1, соответствует состоянию, называемому логическая единица. Это означает, что напряжение на выводе микроконтроллера равно напряжению питания (например, +5 В). Бит, сброшенный в 0, соответствует состоянию логичес- логического нуля, или 0 В. Для входных сигналов порогом между состояниями логического 0 и логической 1 является половина напряжения питания (на- (например, +2.5 В). Также вы не раз услышите, что микроконтроллеры AVR называют RISC-микроконтроллерами. Это означает, что они принадлежат к классу -19-
Глава 1. Введение микроконтроллеров с сокращенным набором команд (Reduced Instruction Set Computer). Такая архитектура немного усложняет жизнь программис- программистам (нам с вами), однако микросхема при этом получается более простой и более быстродействующей. Иногда микроконтроллеры AVR называют FLASH-микроконтроллерами (читается как «флэш»). Это название отражает тот факт, что написанная ва- вами программа хранится в FLASH-памяти — памяти, которую можно пере- перезаписывать снова и снова. Соответственно, вы сможете многократно про- программировать один и тот же кристалл AVR — для радиолюбителей это озна- означает возможность длительного использования одной и той же микросхемы. Первые шаги Процесс разработки программ состоит из пяти основных этапов: 1. Выбор конкретного микроконтроллера и составление блок-схемы программы. 2. Написание программы (с помощью Блокнота, AVR Studio или любой другой подходящей программы). 3. Ассемблирование программы (преобразует написанный вами текст в форму, понятную микроконтроллеру). 4. Симуляция или Эмуляция программы, чтобы убедиться в ее работоспособности (или неработоспособности). 5. Программирование AVR. На этом этапе написанное вами заносится в реальный микроконтроллер. А теперь рассмотрим некоторые из этих этапов более подробно. Выбор модели Поскольку в семейство AVR входит большое число различных моделей микроконтроллеров, необходимо хорошенько подумать о том, какая из них лучше всего подойдет для вашего устройства. Некоторую информа- информацию о микроконтроллере можно получить из его обозначения: AT90S1200 Код объема ОЗУ; О -ОЗУ отсутствует № модели ЦПУ — О Код объема EEPROM; 2-64 байта 1 Кбайт FLASH-памяти программ Коды объема памяти: Коды 0123456789АВ Объем [байт] 0 32 64 128 256 512 1К 2К 4К 8К 16К 32К -20-
Выбор модели Значения использованных терминов могут быть вам незнакомы, одна- однако не волнуйтесь — мы скоро их рассмотрим. Следует заметить, что мик- микроконтроллеры семейств Tiny и Mega имеют немного другую систему обоз- обозначений. Краткие сведения о характеристиках некоторых микроконтрол- микроконтроллеров AVR приведены в Приложении А. Off УПРАЖНЕНИЕ 1.8. Определите объем различных областей памяти ^~* микроконтроллера AT90S8515. Одним из наиболее важных параметров микроконтроллеров, не на- нашедший, к сожалению, отражения в обозначении модели, является число входов и выходов. Модель 1200 имеет 15 контактов ввода/вывода (т.е. 15 выводов, которые могут использоваться как входы или выходы), а модель 8515 — целых 32 контакта ввода/вывода! 0 ПРИМЕР 1.10. Необходимо разработать устройство, которое будет считать количество нажатий на кнопку и отображать это число на одном семисегментном индикаторе (при достижении значения 9 он будет сбрасываться). 1. Для управления семисегментным индикатором требуется семь выходов. 2. Для кнопки требуется один вход. Таким образом, для такого устройства потребуется в общей сложности 8 контактов ввода/вывода. В данном случае вполне можно использовать 1200, поскольку это одна из самых простых моделей, имеющая достаточ- достаточное количество выводов. При работе с большим числом входов и выходов часто используется полезный прием, называемый апробированием. Он особенно удобен при управлении несколькими семисегментными индикаторами или при необ- необходимости контролировать большое количество кнопок. Лучше всего про- продемонстрировать этот прием на примере. 0 ПРИМЕР 1.11. Необходимо разработать счетчик, который прибав- прибавляет число от 1 до 9 к текущему двухзначному значению. Соответ- Соответственно, в устройстве будет 9 кнопок и 2 семисегментных индикато- индикатора. На первый взгляд, для решения поставленной задачи потребует- потребуется достаточно много входов и выходов: 1. Для каждого семисегментного индикатора требуется семь выхо- выходов, итого 14. 2. Для каждой кнопки требуется один вход, итого 9. -21-
Глава 1. Введение Таким образом, в общей сложности требуется 23 вывода, что влечет за собой необходимость использования «большого» микроконтроллера, та- такого как 8515 (имеющего 32 контакта ввода/вывода); однако на самом деле использовать такой «большой» микроконтроллер нет никакой необходи- необходимости, поскольку требуемое число выводов можно значительно умень- уменьшить. При использовании стробирования состояния всех кнопок можно бу- будет прочитать с помощью шести выводов, а для управления двумя семисег- ментными индикаторами потребуется всего девять выводов. Итого полу- получается 15 контактов ввода/вывода, имеющихся в микроконтроллере 1200. Соответствующая схема приведена на Рис. 1.2. Рис. 1.2. Схема стробирования При подаче на вывод РВО лог. 1 (+5 В), а на выводы РВ1 и РВ2 — лог. 1 @ В) разрешается обработка кнопок 1, 4 и 7. После этого состояние каж- каждой из них можно узнать, проверив напряжение на одном из выводов РВЗ...РВ5. Таким образом, подавая последовательно на выводы РВ0...РВ2 лог. 1, можно проверить состояние всех кнопок. Чтобы определить, какое количество выводов потребуется для обслуживания массива из X кнопок, найдите пару сомножителей числа X, имеющих наименьшую сумму (на- (например, для числа 24 сомножителями с наименьшей суммой являются числа 6 и 4, поэтому для контроля 24 кнопок потребуется 6 + 4 = 10 кон- контактов ввода/вывода). Лучше сделать меньшее число выводов (конечно, если эти числа не равны) выходами, а большее число — входами. В этом случае опрос всех строк матрицы кнопок займет меньше времени. -22-
Блок-схема алгоритма Стробирование семисегментных индикаторов заключается в кратко- кратковременном отображении числа на одном индикаторе и последующем вы- выключении этого индикатора на время отображения другого числа на дру- другом индикаторе. На выводы PD0...PD6 выдается код числа для обоих ин- индикаторов, а подавая лог. 1 на вывод РВ6 или РВ7, вы можете включать соответствующий индикатор. Хотя в действительности индикаторы мер- мерцают с большой частотой, кажется, что они светятся непрерывно. Требова- Требования к программированию подобных узлов мы рассмотрим позже. faS УПРАЖНЕНИЕ 1.9. С помощью Приложения А определите, какую ^^ модель микроконтроллера AVR можно использовать для реализа- реализации 4-разрядного калькулятора с кнопками для цифр от 0 до 9 и пя- пяти операций: +, —, х, ч- и =. Блок-схема алгоритма После того как вы определили требуемое количество контактов вво- ввода/вывода и, таким образом, выбрали конкретный микроконтроллер, можно приступать к следующему этапу, который заключается в создании блок-схемы программы. В принципе, на этом этапе формируется основа программы, а написать программу, имея перед собой блок-схему, гораздо легче, чем с нуля. Блок-схема должна отображать основные этапы функционирования микроконтроллера, а также прояснять структуру программы. Представьте, что ваша программа является растительным лабиринтом. В этом случае блок-схема будет представлять собой грубую карту, обозначающую основ- основные участки лабиринта. При создании блок-схемы вы должны иметь в ви- виду, что лабиринт не может выходить к обрыву (т.е. программа не может просто взять и закончиться), так как в противном случае AVR перешагнет через край и разобьется. Вместо этого AVR вынужден постоянно бродить по лабиринту (хотя вы можете усыпить его!). Простой пример блок-схемы программы приведен на Рис. 1.3. 0 ПРИМЕР 1.12. Блок-схема программы, которая включает светоди- од (СИД), если нажата кнопка. Блок инициализации представляет некоторые действия, которые необ- необходимо выполнять в начале каждой программы для настройки различных функций. Эти действия мы рассмотрим чуть позже. Прямоугольники со скругленными углами используются для обозначе- обозначения начального и завершающего блоков программы, а ромбы используют- используются для обозначения условий. Условные переходы (ромбы на блок-схеме) означают: «если что-то произошло, то переходим туда-то». -23-
Глава 1. Введение Рис. 1.3. Блок-схема программы, включающей и выключающей СИД Объем кода, соответствующий каждому конкретному элементу блок-схемы, может быть самым разным и, вообще говоря, не важен. Идея блок-схемы заключается в выделении основных этапов выполнения про- программы, а также в создании диаграммы, которую мог бы понять любой че- человек, даже совершенно незнакомый с программированием. В дальней- дальнейшем вы поймете, что гораздо легче писать программу на основе блок-схе- блок-схемы, поскольку в этом случае можно заниматься каждым блоком по отдельности, совершенно не задумываясь об общей структуре программы. /pS УПРАЖНЕНИЕ 1.10. Повышенной сложности!Нарисуйте блок-схе- ^^ му программы устройства сигнализации с тремя кнопками. При об- обнаружении устройством сигнала от датчика необходимо в течение 10 с нажать в правильном порядке три кнопки, иначе сработает сиг- сигнализация. Если кнопки нажаты вовремя, устройство возвращается в состояние, в котором находилось до получения сигнала от датчи- датчика. Если набран неправильный код, включается сирена. (Сложность ответов может быть различной, однако для ориентировки намекну, что мой вариант ответа состоит из 13 блоков.) Написание программы Следующим этапом после разработки блок-схемы является загрузка шаблона (подобного предложенному на стр. 31) и написание на его основе своей программы. Это можно сделать с помощью любого простейшего текстового редактора, например Блокнота (эта программа входит в состав Windows®), или специализированной среды разработки, такой как AVR Studio. -24-
Ассемблирование Ассемблирование Чтобы написанную вами программу можно было записать в микросхе- микросхему, ее необходимо ассемблировать. Данная операция преобразует текст программы в последовательность чисел, которая может быть помещена в FLASH-память программ микроконтроллера. Эта последовательность чи- чисел называется шестнадцатеричным кодом, или hex-файлом — и файл будет иметь расширение .hex. Ассемблер проверяет вашу программу строку за строкой и пытается преобразовать каждую строчку в соответствующий шестнадцатеричный код. Если он не может распознать, что написано в ка- какой-либо строке, он регистрирует в этой строке ошибку (error). Ошибкой является то место в программе, которое ассемблер однозначно считает неправильным, т.е. он не может понять, что там написано. Также ассемб- ассемблер может сгенерировать предупреждение (warning) — если встретилось что-то, что возможно неверно, т.е. написанное выглядит необычно, но не обязательно неправильно. Все сказанное станет гораздо понятнее, когда мы приступим к ассемблированию нашей первой программы. Регистры Одним из наиболее важных аспектов программирования AVR и микро- микроконтроллеров вообще являются регистры. Чтобы было понятнее, пред- представьте себе, что в микроконтроллере AVR имеется шкаф с большим коли- количеством ящиков, в каждом из которых хранится 8-битное число (один байт). Эти ящики и являются регистрами — точнее, мы называем их регистрами ввода/вывода (РВВ). Кроме этих регистров ввода/вывода, у нас есть 32 «рабочих» регистра. Они отличаются от регистров ввода/вывода, поскольку не являются частью шкафа. Представьте себе, что рабочие ре- регистры являются служащими, а вы — их начальником. Если вы хотите по- положить что-нибудь в шкаф, вы отдаете это служащему и приказываете ему положить это в шкаф. Точно так же программист не может поместить чис- число непосредственно в регистр ввода/вывода. Вместо этого он должен запи- записать число в рабочий регистр, а затем скопировать рабочий регистр в ре- регистр ввода/вывода. Вы можете также попросить служащих выполнить ка- какую-либо операцию над имеющимися у них числами, т.е. вы можете складывать числа, находящиеся в рабочих регистрах. На Рис. 1.4 показаны регистры модели 1200. Из рисунка видно, что каждому регистру соответствует уникальный номер. Рабочие регистры обозначаются как R0, R1, ..., R31. Заметим, од- однако, что регистры R30 и R31 немного отличаются от остальных. Они об- образуют сдвоенный регистр Z — регистр, который может содержать 16-бит- 16-битное значение (называемое словом). К этим регистрам можно обращаться -25-
Глава 1. Введение $08 $10 $11 $12 $16 $17 $18 $1С $1D $1Е $21 $32 $33 $35 $38 $39 $ЗВ $3F ACSR PIND DDRD PORTD PINB DDRB PORTB EECR EEDR EAR WDTCR TCNTO TCCRO MCUCR TIFR TIMSK GIMSK SREG Г Рис. 1.4. Регистры модели 1200 по отдельности, как к регистрам ZL и ZH, но можно в принципе и объеди- объединить таким образом, что ZL (lower Z — младший) будет содержать биты 0...7 16-битного числа, a ZH (higher Z — старший) — биты 8... 15. ПРИМЕР 1.13 ZH ZL 00000000 11111111 ПРИМЕР 1.14 ZH ZL 11111111 11111111 -> прибавить 1 к ZL -> ZH ZL 00000001 0000000 -> прибавить 1 к ZL -> ZH ZL 00000000 00000000 Заметим, что такое объединение используется только в некоторых ко- командах. Будем считать, что команда не может использовать сдвоенный регистр, пока это не указано явно. -26-
Регистры Как вы понимаете, удобнее давать рабочим регистрам названия (по той же причине, по которой вы не называете своих служащих по их учетным номерам), и такая возможность у вас имеется. Целесообразно давать ре- регистрам имена, соответствующие характеру хранящихся в них чисел. На- Например, если регистр R5 используется для хранения числа прошедших ми- минут, его можно назвать Minutes. Каким образом можно назначать регист- регистрам имена, вы узнаете, когда мы будем рассматривать шаблон программ. Чуть позже мы с вами также увидим, что рабочие регистры R16...R31 име- имеют больше возможностей, чем остальные. Регистрам ввода/вывода тоже присвоены номера @...63 десятичные, или $0...$3F шестнадцатеричные). Каждый из этих регистров выполняет специфические функции (например, считает ход времени, управляет пос- последовательным портом и т.п.), и в течение курса мы рассмотрим функции всех этих регистров. Однако я отдельно выделю регистры PortB, PortD, PinB и PinD. Эти регистры ввода/вывода представляют собой порты (В и D соответственно) — основное средство связи микроконтроллеров AVR с ок- окружающим миром. И не удивляйтесь, пожалуйста, отсутствию портов А и С. Все четыре порта (А, В, С и D) имеются в более развитых моделях (на- (например, 8515); более простые микроконтроллеры AVR (например, 1200) имеют только два порта. Эти порты соответствуют портам В и D более раз- развитых микроконтроллеров, поэтому так и называются. На Рис. 1.5 приведено расположение выводов (цоколевка) микроконт- микроконтроллера 1200. Обратите внимание на выводы, обозначенные РВО, РВ1, ..., РВ7, — это выводы порта В. Соответственно, выводы PD0...PD6 являются выводами порта D. Состояние этих выводов можно считать (как входов) или изменить (как выходов). Если порт функционирует как вход, то двоич- двоичное число, содержащееся в регистрах PinB или PinD, покажет нам состоя- состояние выводов, при этом вывод РВО будет соответствовать биту 0 регистра PinB и т.д. Если на выводе присутствует напряжение ВЫСОКОГО уровня, то соответствующий бит установлен в 1, и наоборот. Обратите внимание, что порт D содержит всего семь битов, а не восемь. Рис. 1.5. Цоколевка микро- микроконтроллера 1200 -27-
Глава 1. Введение 0 ПРИМЕР 1.15. Все выводы РВ0...РВ7 используются в качестве вхо- входов. К ним подключены кнопки, которые другими выводами под- подключены к шине питания +5 В. Когда все кнопки нажаты, в регист- регистре PinB находится число ОЬ11111111, или 255 в десятичной системе. Когда нажаты все кнопки, кроме кнопки, подключенной к выводу РВ7, в регистре PinB находится число ObOlllllll, или 127 в деся- десятичной системе. Аналогичным образом, если вывод является выходом, его состояние контролируется соответствующим битом регистра Portx. Вывод может обеспечивать втекающий или вытекающий ток до 20 мА и, таким образом, способен напрямую управлять светодиодными индикаторами (СИД). 0 ПРИМЕР 1.16. Все выводы РВ0...РВ7 являются выходами и под- подключены к СИД. Другие выводы СИД подключены через резисторы к общему проводу. Для включения всех СИД в регистр PortB запи- записывается число ОЬ 11111111. Для выключения двух центральных СИД в регистр PortB записывается число 0Ы1100111. /gS УПРАЖНЕНИЕ 1.11. Воспользуемся примером, приведенным вы- ^^ ше, в котором все выводы РВ0...РВ7 подключены к СИД. Мы хотим получить «бегущую дорожку» из восьми светодиодов (как показано на Рис. 1.6) и собираемся для создания этого эффекта поочередно записывать в регистр PortB соответствующие числа. Что это должны быть за числа (в двоичной, десятичной и шестнадцатеричной систе- системе счисления)? Рис. 1.6. «Бегущая дорожка» -28-
J& Команды УПРАЖНЕНИЕ 1.12. К выводам PDO, PD1 и PD2 подключены кнопки, которые другими выводами подключены к шине питания +5 В. Эти кнопки используются в пульте для телевикторины. Какие числа в регистре PinD указывают на то, что одновременно нажато более одной кнопки (в двоичной, десятичной и шестнадцатеричной системе счисления)? Команды С этого момента мы начнем изучать команды. Полностью все команды микроконтроллеров AVR приведены в Приложении С в конце книги. В об- общей сложности микроконтроллеры AVR поддерживают около сотни ко- команд. На первый взгляд это звучит довольно устрашающе, но не беспокойтесь — большинство из них просто дублируют друг друга. На са- самом деле имеется всего около 40 команд, которые действительно нужно запомнить. Причем большинство этих команд очень легко запомнить, поскольку они имеют знакомые названия, как, например, add или jmp. К тому же имеется несколько общих правил, которые могут помочь рас- расшифровать неизвестную команду. Если в названии команды встречается буква i, это означает, как правило, наличие непосредственного операнда (immediate), т.е. числа, которое указывается прямо в команде, или в ре- регистре ввода/вывода (I/O). Буква b часто означает бит (bit) или переход (branch). Давайте взглянем на формат строки с командой. Г-7Г ПРИМЕР 1.17 (Метка:) sbi portb,0 ; Включить СИД Первым элементом строки (необязательным) является метка. Она позволяет перейти на эту строку из другого места программы. Замечу, что метка не может начинаться с числа и не должна совпадать с названием ко- команды или регистрового файла. Метка всегда заканчивается символом двоеточия (при написании программы двоеточие легко пропустить, и, ес- если вы будете невнимательны, метки могут стать частым источником оши- ошибок). Причем метка необязательно должна находиться на той же строке, что и команда. Например, следующий фрагмент совершенно корректен: Метка: sbi portb,0 ; Включить СИД После метки располагается собственно команда: sbi, т.е. указание, что именно мы делаем, а затем — над нем мы это делаем: portb,0 (эти симво- символы называются операндами). Последним и тоже немаловажным элемен- элементом строки является точка с запятой, за которой располагается -29-
Глава 1. Введение комментарий, объясняющий обычным языком, что делает эта строка. Вы можете писать в программе все что угодно, если это находится после точ- точки с запятой. В противном случае, ассемблер попытается транслировать написанное (т.е. слова «включить СИД»), что, естественно, приведет к ге- генерации сообщения об ошибке. Поскольку ассемблер просматривает про- программу построчно, то при обнаружении точки с запятой он переходит к разбору следующей строки. Подчеркну, что очень важно объяснять каждую написанную вами строчку, как это сделано в предыдущих примерах. И для этого есть мно- множество причин. Во-первых, то, что вы написали, может быть понятно вам сейчас, но после некоторого перерыва, через неделю или через ме- месяц, вы будете смотреть на строку и думать: «Черт возьми, для чего я это написал!» Во-вторых, у вас может возникнуть необходимость показать программу другим людям для консультации. Мне часто присылают про- программы, в которых, к сожалению, очень мало комментариев либо их во- вообще нет. В этом случае мало чем можно помочь, так как, глядя на голый код, практически невозможно определить, что же программа должна делать. Написание хороших комментариев — нелегкая задача, поскольку они должны быть ясными, но не слишком длинными. В частности, следует из- избегать простого копирования смысла строки. Г^Г ПРИМЕР 1.18 sbi PortB,0 ; Установить бит 0 регистра PortB Комментарий, подобный приведенному выше, абсолютно лишен смысла, поскольку он не объясняет, почему вы устанавливаете бит О регистра PortB, а просто констатирует этот факт. Если вы хотите получить общее представление обо всех имеющихся командах, внимательно изучите Приложение С. Это поможет вам понять, каким образом группируются различные команды. Со всеми этими командами мы будем постепенно знакомиться при написании учебных программ. Шаблон программы Большинство программ имеют одинаковую структуру, кроме того, во всех программах присутствуют некоторые общие элементы, необходимые для их нормальной работы. Поэтому, чтобы облегчить себе жизнь, мы мо- можем написать некоторый шаблон, сохранить его и загружать всякий раз, когда начинаем писать новую программу. Шаблон, которым я обычно пользуюсь, приведен на Рис. 1.7. -30-
Шаблон программы Автор: Дата: Версия: Имя файла: Для AVR: Тактовая частота: Выполняемые функции: .devi с е хххххххх .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\xxxxxx.inc" .list ; Объявления: .def temp =rl6 ; Начало программы rjmp Init Первая выполняемая команда Init: ldi temp,Obxxxxxxxx out DDRB,temp ldi t emp,0 bxxxxxxxx out DDRD,temp ldi t emp,0 bxxxxxxxx out PortB,temp ldi t emp,Obxxxxxxxx out PortD,temp Определяем входы и выходы порта В Определяем входы и выходы порта D Включаем подтяжку для входов порта В и задаем начальные состояния выходов Включаем подтяжку для входов порта D и задаем начальные состояния выходов ; Основное тело программы Start: <Разместите здесь текст вашей программы> rjmp Start ; Возвращаемся к метке Start Рис. L 7. Шаблон программы Рамка из звездочек, располагающаяся в самом начале шаблона, пред- представляет собой заголовок программы (звездочки здесь набраны исключи- исключительно для красоты). Заголовок заполняется таким образом, чтобы можно -31-
Глава 1. Введение было легко понять, что это за программа, не просматривая ее целиком. Также благодаря заголовку можно убедиться, что вы работаете с последней версией программы. Заметьте, что содержимое этого блока совершенно не влияет на реальную работу программы, поскольку в начале каждой строки стоит точка с запятой. В строке «Тактовая частота:» указывается частота источника тактовых сигналов (например, кварцевого резонатора), под- подключенного к микроконтроллеру. Микроконтроллеру AVR необходим ста- стабильный сигнал, указывающий, когда следует переходить к выполнению следующей команды; таким образом, микроконтроллер выполняет коман- команды в каждом периоде тактовых импульсов (или такте). Соответственно, если к микроконтроллеру подключен резонатор с частотой 4 МГц, то мик- микроконтроллер будет выполнять около 4 миллионов команд в секунду. За- Заметьте, что я говорю около 4 миллионов, поскольку некоторые команды (как правило, используемые для переходов внутри программы) выполня- выполняются за два такта. В строке «Для AVR:» указывается, для какой конкрет- конкретной модели микроконтроллера предназначена программа. После заголовка начинаются строки, действительно выполняющие ка- какие-либо функции. Слово .device является директивой (командой для ас- ассемблера), которая сообщает ассемблеру, для какой модели микроконт- микроконтроллера должна транслироваться программа. Например, если вы пишете программу для модели 1200, строка с этой директивой должна иметь вид: .device at90sl200 Другой важной директивой является директива .include, которая позво- позволяет ассемблеру использовать так называемые включаемые файлы. Они вы- выполняют для ассемблера роль словаря. Ассемблер поймет большинство на- написанных вами выражений, а для остальных ему может потребоваться найти перевод. Например, все имена регистров ввода/вывода и их адреса хранятся во включаемых файлах, поэтому, вместо того чтобы писать адрес $3F, вы можете указать символическое имя регистра SREG. При установке программы ассемблера на компьютер включаемые файлы для различных моделей микроконтроллеров помещаются в определенную папку. В про- программах я буду указывать путь, имеющийся на моем компьютере, однако на вашем компьютере этот путь может быть другим. Итак, если предпола- предполагается использовать модель 1200, полная строка будет иметь вид: .include "C:\Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" В заключение я хотел бы сказать несколько слов о директивах .nolist и .list. Когда ассемблер обрабатывает написанный вами код, он генерирует файл листинга, который содержит копию вашей программы с комментари- комментариями ассемблера. Вообще говоря, вам совсем не нужно, чтобы в файле лис- листинга оказался довольно объемный текст включаемого файла. Для этого -32-
Шаблон программы достаточно поставить перед директивой .include директиву .nolist, которая указывает ассемблеру прекратить копирование считываемых данных в файл листинга. После строки с директивой .include поставьте директиву .list, чтобы вновь разрешить ассемблеру вывод данных в файл листинга. Таким образом, строки с директивами .list и .nolist совершенно не влияют на функционирование программы, однако благодаря им можно значи- значительно уменьшить размер файла листинга. Мы ознакомимся с файлом листинга более подробно, когда будем писать нашу первую программу. После заголовка обычно размещаются различные объявления (declarations). Они являются вашими собственными дополнениями к сло- словарю ассемблера — вы можете присвоить используемым регистрам осмыс- осмысленные названия. Я, например, всегда использую рабочий регистр, назы- называемый temp, для временного хранения данных, и назначаю это имя регистру R16. Имена рабочих регистров задаются с помощью директивы .def, как это показано в шаблоне. Другим типом объявления, которое мо- может использоваться для присваивания числового значения идентификато- идентификатору, является директива .equ. Она, в частности, может использоваться для задания своих имен регистрам ввода/вывода. Например, я собираюсь под- подключить семисегментный индикатор к порту В и хочу при обращении к регистру PortB писать DisplayPort. Регистр PortB является регистром вво- ввода/вывода с номером 0x18, так что после объявления я смогу писать в про- программе DisplayPort и это слово будет интерпретироваться ассемблером как PortB: .equ DisplayPort = PortB ИЛИ .equ DisplayPort = 0x18 Эта директива полезна также в том случае, если в различных местах программы используется какое-либо число, значение которого вы, вероят- вероятно, будете изменять в процессе отладки программы. Можно воспользо- воспользоваться директивой .equ для задания имени этого числа, а в тексте програм- программы просто ссылаться на это имя. Теперь, если вам потребуется изменить число, достаточно будет изменить его только в строке с директивой .equ, a не в тех местах, где это число используется. Однако пока мы не будем ис- использовать эту директиву. В следующей после объявлений строке располагается первая команда, выполняемая микроконтроллером при включении питания. В этой строке я советую поместить команду перехода к секции, помеченной меткой Init, в которой выполняются все начальные настройки AVR. Для этого исполь- используется команда rjmp: rjmp Init 33
Глава 1. Введение Это команда относительного перехода (relative jump). Другими слова- словами, она указывает микроконтроллеру перейти на участок программы, ко- который вы пометили меткой Init. Причина, по которой переход называется относительным, связана с тем, каким образом ассемблер интерпретирует эту команду, и, вообще говоря, не особо важна для понимания. Пусть, на- например, секция Init располагается через 40 команд от команды rjmp Init. В этом случае ассемблер интерпретирует эту команду как «перепрыгнуть вперед через 40 команд», т.е. перейти относительно текущей команды. Однако гораздо проще считать, что микроконтроллер просто переходит к метке Ink. В первой части секции Init задается, какие из выводов будут работать как входы, а какие — как выходы. Это осуществляется при помощи регист- регистров ввода/вывода DDRB и DDRD (регистры направления передачи дан- данных). Каждый бит этих регистров соответствует одному из выводов микро- микроконтроллера. Например, бит 4 регистра DDRB соответствует выводу РВ4, а бит 2 регистра DDRD — выводу PD2. Установка соответствующего бита регистра DDRx в 1 делает вывод выходом, а сброс бита в 0 делает вывод входом. Если мы сконфигурируем вывод как вход, мы сможем задать, будет ли к этому выводу подключен внутренний подтягивающий резистор или нет. Это может избавить нас от необходимости использовать внешние резисто- резисторы. Чтобы включить подтяжку входа, необходимо установить в 1 соответ- соответствующий бит регистра Portx; однако, если вам этого не требуется, убеди- убедитесь, что вы ее отключили, сбросив соответствующий бит регистра Portx в 0. Что же касается выходов, то при включении микроконтроллера они должны находиться в определенном начальном состоянии (например, все выключены). Поэтому следует установить или сбросить соответствующие биты регистров Portx в зависимости от того, в какое состояние мы хотим установить выходы при старте. Поясним сказанное на примере. 0 ПРИМЕР 1.19. Используется микроконтроллер 1200, выводы PB0, РВ4 и РВ7 подключены к кнопкам. Мы хотим, чтобы подтяжка бы- была только на входах РВ4 и РВ7. Выводы PD0...PD6 подключены к семисегментному индикатору; остальные выводы оставлены непод- неподключенными. При включении питания все выходы должны быть выключены. Какие значения необходимо записать в регистры DDRB, DDRD, PortB и PortD, чтобы выводы микроконтроллера функционировали описанным образом? Прежде всего, разберемся с входами и выходами. Выводы PB0, PB4 и РВ7 — входы, остальные не задействованы (поэтому сделаем их выхода- выходами). Соответственно, в регистр DDRB необходимо записать число лл
Шаблон программы ОЬОПОШО. Все выводы порта D являются выходами или не используют- используются, поэтому в регистре DDRD должно быть число Ob 11111111. Для включения подтяжки на выводах РВ4 и РВ7, установим биты PortB,4 и PortB,7 в 1. Все выходы при включении питания должны быть выключены, поэтому в регистр PortB необходимо записать число 0Ы0010000. Все выходы порта D должны быть выключены, поэтому в ре- регистр PortD необходимо записать число ОЬОООООООО. Мы не можем записать эти значения непосредственно в регистры вво- ввода/вывода, вместо этого мы должны сначала записать их в рабочий регистр (например, temp), а затем переслать содержимое рабочего регистра в ре- регистр ввода/вывода. Эту операцию можно выполнить несколькими спосо- способами: ldi register number Эта команда загружает непосредственное значение (load immediate) в регистр. Необходимо отметить, что эта команда может работать только с регистрами R16...R31 (именно поэтому мы можем использовать temp, поскольку он соответствует регистру R16). Если же число, которое мы со- собираемся записать в регистр, равно 0 или 255/0xFF/0b 11111111, можно воспользоваться другими командами: clr register ; Эта команда сбрасывает содержимое регистра (clear register), т.е. запи- записывает в него 0. Заметим, что в отличие от команды ldi эта команда мо- может использоваться со всеми рабочими регистрами. И наконец, ser register ; Эта команда заполняет содержимое регистра (set register), т.е. записы- записывает в него число 255/OxFF/Obl 1111111. Как и команда ldi, эта команда мо- может работать тол ь к о с регистрами R16...R3L Теперь нам необходимо переслать temp в регистр ввода/вывода, ис- используя следующую команду: out ioreg,reg ; Эта команда выводит (out) содержимое регистра reg общего назначе- назначения в регистр ввода/вывода ioreg. Обратите внимание на порядок операн- операндов команды — сначала адрес регистра ввода/вывода, потом рабочий ре- регистр — их очень легко перепутать! Теперь вам должно быть понятно, что в восьми строках секции Ink осуществляется загрузка регистров DDRB, DDRD, PortB и PortD посредством регистра temp. -35-
Глава 1. Введение >g^ УПРАЖНЕНИЕ 1.13. Используется микроконтроллер 1200, вывод ^^ РВО подключен к датчику давления, а выводы РВ1, РВ2 и РВЗ уп- управляют красным, желтым и зеленым светодиодами соответствен- соответственно. На выводы PD0...PD3 выдается сигнал для ИК передатчика, а с выводов PD4...PD6 принимается сигнал с ИК приемника. Осталь- Остальные выводы не подключены. При включении питания все выходы должны быть выключены, а на входе РВО должна быть разрешена подтяжка. Напишите восемь строк, которые будут составлять сек- секцию Init этой программы. После секции Ink начинается основное тело программы, обозначенное меткой Start. Начиная с этого места, будет располагаться основная часть кода. Обратите внимание, что программа заканчивается строкой rjmp Start. Необязательно возвращаться именно к метке Start, однако переход куда-нибудь должен быть, поэтому вам может потребоваться из- изменить эту последнюю строку программы соответствующим образом. В конце программы можно поместить директиву .exit, которая приказыва- приказывает ассемблеру прекратить трансляцию файла, однако это делать не обяза- обязательно, поскольку при достижении конца файла трансляция прекратится в любом случае. -36-
Глава 2. ОСНОВНЫЕ ОПЕРАЦИИ В AT90S1200 И TINY12 Лучшим способом обучения является внимательное изучение приме- примеров и самостоятельное написание программ. На протяжении остальной части книги мы рассмотрим ряд учебных проектов, причем большую часть кода для многих из них вы будете писать самостоятельно. Чтобы обучение было наиболее эффективным, желательно на практике исследовать работу этих программ, полностью набирая их по мере рассмотрения в Блокноте или какой-либо другой программе. Если же в настоящий момент вы не рас- располагаете специализированным программным обеспечением для AVR, можно пока просто набирать программы в Блокноте, а проверить их позже. Прежде чем приступить к изучению, наберите шаблон, описанный в предыдущей главе, внесите в него необходимые изменения и сохраните в файле template.asm. Расширение .asm означает, что в файле содержится ис- исходный текст на а с с ем бл ере, т.е. именно то, что потом будет ассемб- ассемблировано. Если вы пользуетесь Блокнотом, убедитесь, что при сохранении в поле «Тип файла» (File Type) вы выбрали значение «Все файлы» (All Files). Программа А. Светодиод (LEDon) • Управление выходами Первые несколько программ, которые мы рассмотрим, предназначены для модели 1200. Загрузите шаблон и, выбрав в меню Файл (File) пункт Со- Сохранить как... (Save as...), сохраните его в файле ledon.asm (исходный файл шаблона при этом останется неизменным). Внесите в текст необходимые изменения, относящиеся к модели микроконтроллера (заголовок, дирек- директивы .device и .include). Наша первая программа будет просто включать СИД и удерживать его в этом состоянии. Прежде всего, необходимо опре- определить входы и выходы. В этом проекте нам потребуется только один вы- выход, в качестве которого будем использовать вывод RB0. Вторым этапом при разработке программы является создание блок-схемы алгоритма (см. Рис. 2.1). После этого мы можем приступить к написанию собственно программы. Первый прямоугольник (Инициализация) соответствует сек- -37-
Глава 2. Основные операции в AT90S1200 и TINY 12 ции Init. Эту часть программы вы вполне можете написать самостоятельно (помните, если вывод не используется в схеме, его необходимо делать вы- выходом). Рис. 2.1. Блок-схема программы А Во втором блоке включается СИД. Чтобы его включить, нужно на вывод RB0 подать напряжение ВЫСОКОГО логического уровня, для чего 0-й бит регистра PortB устанавливается в 1. Для выполнения этой операции мы могли бы загрузить число в temp, а затем переслать его в регистр PortB; однако это можно сделать гораздо проще. Мы можем использовать следующую команду: sbi ioreg,bit ; Эта команда устанавливает бит с номером bit в регистре ввода/вывода ioreg (set bit in an I/O register). Хотя мы и не можем загружать числа непос- непосредственно в регистры ввода/вывода, мы можем устанавливать и сбрасы- сбрасывать индивидуальные биты в некоторых из них. Мы не можем устанав- устанавливать и сбрасывать отдельные биты в регистрах ввода/вывода 32...63 ($20...$3F). К счастью, регистр PortB ($18), а также все регистры Portx и Pinx могут управляться описанным образом. Аналогичная команда для сброса бита имеет следующий формат: cbi ioreg,bit ; Эта команда сбрасывает бит в регистре ввода/вывода (clear bit in an I/O register), однако не забывайте, что она применима только к регистрам вво- ввода/вывода 0...31. В нашем конкретном случае мы хотим установить 0-й бит регистра PortB в 1. Воспользуемся для этого следующей командой, поме- пометив ее меткой Start: Start: sbi PortB,0 ; Включаем СИД В следующей строке напишем: rjmp Start ; Возвращаемся к метке Start Таким образом, микроконтроллер будет выполнять бесконечный цикл, постоянно включая СИД. Теперь программа готова для ассемблирования. -38- Инициализация Включить СИД
Программа А. Светодиод (LEDon) В том, что вы все написали правильно, можно убедиться, сравнив напи- написанное с текстом программы, приведенным в Приложении J {Программа А). Тексты всех остальных программ, которые мы с вами будем писать, то- тоже приведены в этом приложении. Теперь приступим к ассемблированию программы, а если у вас нет соответствующего программного обеспече- обеспечения, просто прочитайте следующий параграф. Среду разработки AVR Studioх) можно совершенно бесплатно скачать с сайта компании Atmel (www.atmel.com). Она позволяет транслировать программы с языка ассем- ассемблера и отлаживать их, а при наличии соответствующей аппаратуры и про- программировать микроконтроллеры AVR. AVR Studio — трансляция с языка ассемблера После запуска AVR Studio создайте новый проект, выбрав в меню Project команду New Project. В появившемся окне в поле Project Name вве- введите название проекта (например, LEDon), в поле Location укажите подхо- подходящее расположение, а в списке Project Type выберите тип проекта «Atmel AVR Assembler». Здесь же можно указать на необходимость создания ос- основного (входного) файла для проекта (флажок Create initial File), а также на необходимость создания отдельной папки для проекта (флажок Create Folder). В проект могут входить ассемблерные и другие файлы. Написанная вами программа является ассемблерным файлом (.asm), и его необходимо добавить в проект2). Для этого в окне Workspace (вкладка Project) щелкни- щелкните правой кнопкой мыши на группе Assembler и выберите пункт Add existing file. Найдите созданный вами файл LEDon.asm и выберите его двойным щелчком мыши. Название файла должно появиться в дереве проекта. Те- Теперь нажмите клавишу F7 или выберите пункт Build в меню Project, в ре- результате чего начнется трансляция программы. Будем надеяться, что трансляция вашего файла пройдет без ошибок. В противном случае будет полезно просмотреть файл листинга (*.Ist). Откройте его в Блокноте или каком-либо другом текстовом редакторе и поищите сообщения об ошиб- ошибках3). Поскольку наша программа чрезвычайно проста, эти сообщения бу- будут вызваны скорее всего орфографическими ошибками. Устраните все за- замечания и переходите к проверке программы. !) В книге рассматривается работа со средой AVR Studio 4. — Примеч. пер. 2) Если при создании проекта указывается имя существующего файла, он до- добавляется в проект автоматически. — Примеч. пер. 3) Сообщения об ошибках выводятся также в окне Output (вкладка Build). Для локализации ошибок достаточно дважды щелкнуть левой кнопкой на сообщении об ошибке. При этом курсор в окне редактора будет установлен на строку, вызвав- вызвавшую сообщение об ошибке. — Примеч. пер. -39-
Глава 2. Основные операции в AT90S1200 и TINY12 Проверка Существует три основных метода, позволяющих проверить работоспо- работоспособность программы: 1. Симуляция. 2. Эмуляция. 3. Программирование микроконтроллера и проверка его в реальной схеме. Первый из этих методов, симуляция, является полностью програм- программным. Используется специальное программное обеспечение, которое си- симулирует деятельность микроконтроллера и показывает вам, что происхо- происходит внутри него во время выполнения программы, в частности как изме- изменяется состояние его регистров. Вы можете также симулировать изменение входных сигналов, вручную изменяя, например, содержимое регистра PinB. С помощью этого метода можно легко убедиться в работо- работоспособности (или, напротив, в неработоспособности) ключевых идей, ле- лежащих в основе программы. С другой стороны, вы не сможете проверить реакцию программы на некоторые реальные воздействия, такие как дре- дребезг контактов. Симулятор микроконтроллеров AVR входит в состав среды разработки AVR Studio. AVR Studio — симуляция Теперь мы приступим к симуляции программы LEDon. После ассемб- ассемблирования программы (файла с расширением .asm) необходимо переклю- переключить AVR Studio в режим отладки, для чего следует нажать кнопку У . Через некоторое время откроется окно с текстом программы (если оно не было открыто), первая строка которой (rjmp Ink) будет отмечена желтой стрелкой. Одновременно станут доступными некоторые кнопки на пане- панелях инструментов, находящихся в верхней части экрана. Для пошагового выполнения программы используются три из них. Наиболее полезная из них, { ), называется Step Into. При нажатии на эту кнопку выполняется текущая строка программы. При помощи этой кнопки (или соответствую- соответствующей «горячей» клавиши F11) можно по шагам выполнить программу. На- Назначение остальных кнопок пошаговой отладки мы рассмотрим немного позже, при изучении подпрограмм. Чтобы получить от симуляции хоть ка- какую-нибудь пользу, нам нужно посмотреть, каким образом изменяется со- состояние регистров ввода/вывода (в частности, бита 0 регистра PortB). Для этого перейдите к вкладке I/O окна Workspace и раскройте группу I/O AT90S1200. Вы увидите, что регистры ввода/вывода сгруппированы по функциональным блокам микроконтроллера. Раскрыв блок PortB, вы уви- увидите три регистра: PortB, DDRB и PinB. Также вы можете просмотреть со- -40-
Программа А. Светодиод (LEDon) держимое рабочих регистров, выбрав в меню View пункт Register. В данном случае мы будем наблюдать за регистром R16 (temp). Еще одной полезной кнопкой является кнопка сброса у (Shift+F5). Продолжайте пошаговое выполнение программы. Обратите внимание, как в регистре temp появляется число OxFF (Obi 1111111), которое затем за- записывается в регистры DDRB и DDRD. После этого регистр temp, а вслед за ним и регистры PortB и PortD сбрасываются в 00. Затем 0-й бит регистра PortB устанавливается в 1, что индицируется закрашиванием соответству- соответствующего квадратика в окне Workspace. Заметьте, что эта операция приведет также к автоматической установке на следующем шаге 0-го бита регистра PinB. He забывайте, в чем отличие между этими двумя регистрами, — ре- регистр PortB представляет данные, которые вы собираетесь передать через порт, а регистр PinB представляет реальные значения напряжений на вы- выводах порта. Например, если вы попытаетесь установить вход в состояние ВЫСОКОГО уровня в то время, когда он случайно замкнут на общий про- провод, то в регистре PortB соответствующий бит установится в 1, а в регистре PinB этот бит будет сброшен в 0, поскольку вывод подключен к шине 0 В. Эмуляция Эмуляция позволяет получить гораздо больше информации о реаль- реальном функционировании программы и может быть намного полезнее при отыскании ошибок в программе. При эмуляции к компьютеру подключа- подключается зонд (probe) с разъемом, соответствующим конкретной модели AVR. Под управлением программы эмулятора зонд начинает функционировать точно так же, как и реальный микроконтроллер, выполняющий вашу про- программу. Работа устройства под управлением эмулятора ничем не отличает- отличается от работы под управлением реального микроконтроллера, однако, ис- используя эмулятор, вы можете замедлить выполнение программы, а также просмотреть состояние внутренних узлов микроконтроллера (регистров и т.п.). При использовании этого метода проверяется работоспособность программы, корректность разводки печатной платы, а также их совмест- совместная работа. К сожалению, эмулятор является довольно дорогим удоволь- удовольствием. В качестве примера упомяну фирменный внутрисхемный эмуля- эмулятор ICE (ln-Circuit Emulator). Если у вас нет эмулятора (либо после завершения эмуляции), вам нуж- нужно будет запрограммировать реальный микроконтроллер AVR и устано- установить его в устройство или на макетную плату. Одним из важнейших досто- достоинств микроконтроллеров AVR является наличие у них FLASH-памяти программ, что позволяет многократно программировать одну и туже мик- микросхему. Так что вы можете спокойно запрограммировать микроконтрол- _41-
Глава 2. Основные операции в AT90S1200 и TINY12 лер, посмотреть, работает ли он, внести в программу необходимые исправ- исправления и запрограммировать его снова. Для того чтобы воспользоваться этими двумя последними методами тестирования, вам, очевидно, понадобится какая-либо схема или отладоч- отладочная плата. Если вы разрабатываете собственное устройство, позаботьтесь о правильной разводке определенных выводов микроконтроллера. Этот вопрос мы рассмотрим в следующем разделе. Аппаратное обеспечение На Рис. 2.2 приведена цоколевка микросхемы 1200. Вы уже знакомы с выводами РВх и PDx, однако в микроконтроллере есть и другие выводы, имеющие специальное назначение. Вывод Vcc — это вывод положительно- положительного полюса источника питания; для модели 1200 напряжение питания может быть от 2.7 до 6.0 В. Допустимый диапазон напряжения питания зависит от модели, тем не менее для любого микроконтроллера напряжение 4...5 В бу- будет безопасным. Вывод GND — это общий вывод @ В). Также в микроконт- микроконтроллере имеется вывод аппаратного сброса RESET. Черта над названием вывода означает, что он является выводом с активным НИЗКИМ уров- уровнем. Другими словами, чтобы сбросить микроконтроллер, необходимо по- подать на этот вывод напряжение НИЗКОГО уровня (на время не менее 50 не). Соответственно, если нам требуется кнопка сброса, мы можем под- подключить ее согласно схеме, подобной приведенной на Рис. 2.3. Между включением и появлением на вы- выходе источника питания стабильного напря- напряжения, очевидно, должно пройти какое-то время. Аналогично кварцевый генератор сможет сформировать стабильный тактовый сигнал только по истечении некоторого вре- времени после включения. Поэтому необходимо сделать так, чтобы между подачей напряже- напряжение. 2.2. Цоколевка ния питания на AVR и началом выполнения микросхемы 1200 программы прошло определенное время. К счастью, в микроконтроллерах AVR уже имеется узел, формирующий эту задержку (длительностью около И мс); однако, если вашему источнику питания или генератору требуется задержка большей длительности, можно использовать схему, подобную приведенной на Рис. 2.4. Увеличение задержки достигается увеличением емкости конденсатора С1. И наконец, выводы XTAL1 и XTAL2, как видно из их названия, пред- предназначены для подключения кварцевого или керамического резонатора, формирующего импульсы стабильной частоты, которые необходимы для -42-
Программа А. Светодиод (LEDon) Рис. 2.3. Схема подключения кнопки сброса Рис. 2.4. Схема для увеличения длительности задержки нормальной работы AVR. Чем выше частота резонатора, тем быстрее мик- микроконтроллер будет выполнять программу, однако различные модели име- имеют разные значения максимальной частоты тактового сигнала. Обычно максимальная частота находится в диапазоне от 4 до 8 МГц, а используе- используемая в этой главе модель 1200 может работать при частотах до 12 МГц! Заме- Замечу, что в некоторых микроконтроллерах (в частности, в моделях Tiny и 1200) имеется встроенный генератор частоты 1 МГц, при использовании которого внешний резонатор не требуется. Встроенный генератор постро- построен на базе RC-цепочки и поэтому менее точен, более чувствителен к изме- изменению температуры и т.д. Однако, если вам не требуется высокая точность, -43-
Глава 2. Основные операции в AT90S1200 и TINY12 имеет смысл его использовать, освободив тем самым место на печатной плате. На Рис. 2.5 показано, как кварцевый или керамический резонатор подключается к выводам XTAL1 и XTAL2. Рис. 2.5. Схема подключения резонатора Если требуется синхронизировать микроконтроллер с другим устрой- устройством или в схеме уже имеется линия синхронизации с сигналом высокой частоты, то в качестве тактового сигнала можно воспользоваться внешним сигналом. Для этого нужно подключить выход генератора к выводу XTAL1, а вывод XTAL2 оставить неподключенным. На Рис. 2.6 показано, каким образом можно синхронизировать два микроконтроллера AVR, ис- используя буфер типа НС (быстродействующая КМОП-технология). Рис. 2.6. Схема синхронизации двух микроконтроллеров AVR 44-
Программа А. Светодиод (LEDon) AVR Studio — программирование Чтобы проверить работоспособность запрограммированного микро- микроконтроллера, нужна печатная плата устройства или же макетная плата. Са- Самым очевидным решением является изготовление печатных плат по мере необходимости, однако гораздо проще и удобнее изготовить собственную макетную плату, пригодную для реализации всех проектов, рассматривае- рассматриваемых в книге. Схема, соответствующая программе LEDon, приведена на Рис. 2.7. Рис. 2.7. Схема для проверки программы LEDon Если у вас уже есть макетная плата, проверьте, как на ней распаяны светодиоды. В нашей схеме предполагается, что выводы будут играть роль источников тока для светодиодов (т.е. чтобы включить СИД, надо уста- установить на выходе ВЫСОКИЙ уровень). Если же имеющаяся у вас плата разведена таким образом, что выводы микроконтроллера являются пот- потребителями тока светодиодов, необходимо внести изменения в програм- программу микроконтроллера. При этом сигнал лог. О будет включать СИД, а сигнал лог. 1 — выключать его. Поэтому вместо обнуления регистра PortB в начале секции Init потребуется записать в него число ОЫ1111111 (чтобы выключить все светодиоды). Кроме того, для включения СИД нуж- нужно будет не устанавливать 0-й бит регистра PortB, а сбрасывать его. Для этого достаточно вместо команды sbi использовать команду cbi. Замечу также, что, хотя эта программа была написана в расчете на мо- модель 1200 (как самую простейшую), она совместима со всеми другими мо- моделями микроконтроллеров AVR. Поэтому, если в вашем распоряжении имеется, например, микроконтроллер модели 8515 (используемый в неко- некоторых из имеющихся в продаже комплектов разработки), просто измените -45-
Глава 2. Основные операции в AT90S1200 и TINY12 в программе аргументы директив .device и .include, и она должна будет за- заработать. Теперь приступим к программированию микроконтроллера, восполь- воспользовавшись для этого стартовым набором разработчика STK500 (действия, которые необходимо выполнять при использовании других программато- программаторов, не должны очень сильно отличаться от указанных). Для программи- программирования микросхемы вставьте ее в соответствующую панельку на плате. Кроме того, может потребоваться изменить положение перемычек для вы- выбора требуемой модели кристалла. В среде AVR Studio выберите в меню Tools -> Program AVR -> Connect...; в открывшемся окне выберите исполь- используемый программатор (STK500 или AVRISP), порт компьютера, к которому он подключен, и нажмите кнопку Connect. В открывшемся окне диалога выберите соответствующую модель микроконтроллера (AT90S1200). Нам требуется запрограммировать FLASH-память программ. Если перед этим вы отлаживали программу в симуляторе, т.е. она еще на- находится в его памяти, достаточно будет выделить пункт Use current Simulator/Emulator Flash Memory, а затем нажать кнопку Program. Если же программа отсутствует в памяти симулятора, просто загрузите ее в ком- компьютер, ассемблируйте и запустите симуляцию. В результате этих дейс- действий программа будет загружена в память симулятора. Конфигурационные ячейки Вы, очевидно, заметили, что в окне программирования имеется не- несколько вкладок. В частности, на вкладке Fuses вы можете задать некото- некоторые параметры аппаратной конфигурации программируемого микроконт- микроконтроллера. Набор этих конфигурационных ячеек изменяется от модели к мо- модели. В модели 1200, например, имеется всего две таких ячейки — RCEN и SPIEN. Ячейка RCEN должна быть установлена, если в качестве источни- источника тактовых сигналов вы используете внутренний RC-генератор микро- микроконтроллера. Если же вы используете внешний источник тактовых сигна- сигналов, например кварцевый резонатор (как в данном проекте), эта ячейка должна быть сброшена. Вторая конфигурационная ячейка — SPIEN раз- разрешает считывание кода программы из кристалла. Если вы хотите сохра- сохранить код своей программы в секрете и не хотите, чтобы другие люди могли считать его из микросхемы, убедитесь, что эта ячейка сброшена. Конечно, довольно глупо разбираться во всех этих тонкостях только для того, чтобы посмотреть, как включается СИД, однако впереди нас ждут более сложные задачи! -46-
Программы В и С. Кнопка Программы В и С. Кнопка • Контроль входов • Управление выходами Теперь мы с вами разберемся, каким образом можно контролировать состояние входов микроконтроллера и как использовать эту информацию для управления выходами. Это устройство тоже будет довольно простым — кнопка без фиксации и СИД, который включается при нажатой кнопке и выключается при отпущенной. Состояние входа можно проверить двумя способами: 1. Проверить конкретный бит в регистре Pinx, используя команды sbic или sbis. 2. Считать значение из регистра Pinx в рабочий регистр с помощью команды in. Кнопку мы подключим к выводу PD0 и шине О В, а СИД подключим к выводу РВО. Блок-схема соответствующей программы приведена на Рис. 1.3, а принципиальная схема устройства — на Рис. 2.8. Рис. 2.8. Принципиальная схема устройства для контроля состояния входов (программы В и С) Вы уже знаете достаточно, чтобы самостоятельно написать секцию Init программы. Обратите внимание: поскольку на схеме отсутствует внешний подтягивающий регистр, необходимо включить внутреннюю подтяжку на входе PD0. В самом начале программы необходимо проверить, не нажата ли кнопка. Для этого в нашем распоряжении имеется две команды: sbic ioreg,bit -47-
Глава 2. Основные операции в AT90S1200 и TINY12 Эта команда проверяет бит в регистре ввода/вывода и пропускает сле- следующую команду, если бит сброшен (skip if bit in I/O is clear). Аналогично команда sbis ioreg,bit проверяет бит в регистре ввода/вывода и пропускает следующую команду, если бит установлен (skip if bit in I/O is set). Обратите внимание, что эти две команды, как и команды sbi и cbi, работают только с регистрами ввода/вы- ввода/вывода 0...31 ($O...$1F). К счастью, адрес регистра, который мы будем прове- проверять, PinD, находится как раз в этом диапазоне (номер $10). Таким обра- образом, чтобы проверить состояние кнопки (при ее нажатии на выводе PD0 появляется напряжение НИЗКОГО уровня), мы должны написать: sbis PinD,0 ; Проверим кнопку Эта команда заставит AVR пропустить следующую команду, если на выводе PD0 присутствует напряжение НИЗКОГО уровня. Соответствен- Соответственно, команда в следующей строке будет выполнена только в том случае, ес- если кнопка не нажата. В этой строке необходимо выключить СИД, поэ- поэтому прикажем микроконтроллеру п ерей ты к секции LEDoff: rjmp LEDoff ; Переходим к секции LEDoff После этой строки находится команда, которая выполняется только в том случае, если кнопка нажата. Соответственно, этой командой мы долж- должны включить СИД, для чего можно использовать ту же команду, что и в предыдущей программе. faS УПРАЖНЕНИЕ 2.1. Напишите две строки, в первой из которых '^ включается СИД, а во второй осуществляется возврат к метке Start для повторной проверки состояния кнопки. Теперь нам осталось разобраться с секцией LEDoff. rgS УПРАЖНЕНИЕ 2.2. Напишите две строки, в первой из которых вы- ^^ ключается СИД, а во второй осуществляется возврат к метке Start для повторной проверки состояния кнопки. Вот и вся программа. Удостовериться в том, что все написано правиль- правильно, можно, посмотрев на текст, приведенный в Приложении J {Программа В). Теперь можно протестировать программу и записать ее в микроконт- микроконтроллер точно так же, как мы это делали при разработке предыдущей про- программы. Во время симуляции вы можете симулировать нажатие кнопки простым выделением квадратика, соответствующего биту 0 регистра PinD на вкладке I/O окна Workspace. -48-
Программы В и С. Кнопка Иногда имеет смысл снова вернуться к задаче и посмотреть на нее с другой стороны. Вместо того чтобы обрабатывать кнопку и СИД как от- отдельные биты двух портов, давайте посмотрим, как связаны их состояния и числа, находящиеся в регистрах портов. Когда кнопка нажата, в регистре PinD находится число ОЬОООООООО, а светодиод должен быть включен (т.е. в регистр PortB необходимо записать число ОЬОООООООО). Когда кнопка не нажата, в регистре PinD находится число ObOOOOOOOl, и соответственно в регистр PortB необходимо записать число ObOOOOOOOl. Поэтому вместо то- того, чтобы проверять отдельные биты, мы будем использовать содержимое порта, сохраняя его в регистровом файле. Таким образом, единственной задачей программы будет пересылка числа из регистра PinD в регистр PortD. Мы не можем напрямую перемещать числа между регистрами вво- ввода/вывода, поэтому сначала мы прочитаем содержимое регистра PinD с помощью следующей команды: in register,ioreg ; Эта команда копирует содержимое регистра ввода/вывода в рабочий регистр. Чтобы переслать число из рабочего регистра обратно в регистр ввода/вывода, мы воспользуемся командой out. Таким образом, основное тело программы будет выглядеть следующим образом: Start: in temp,PinD ; Считываем состояние кнопки out PortB,temp ; Вкл/выкл СИД rjmp Start ; Возвращаемся к началу Полный текст этой программы приведен в Приложении J {Программа С). Семисегментные индикаторы и косвенная адресация Используя для управления семисегментными индикаторами микро- микроконтроллер AVR, а не отдельную микросхему дешифратора, вы сможете отображать на них все, что вам угодно. Очевидно, что на индикаторе мож- можно отображать не только любые числа, но и многие латинские буквы: А, Ь, с, С, d, Е, F, G, h, Н, i, I, J, I, L, n, о, О, Р, г, S, t, u, U, у и Z. Все выводы семисегментного индикатора желательно подключить к одному порту в любом порядке (это может облегчить разводку печатной платы). Незадействованный вывод порта можно использовать для управ- управления десятичной точкой индикатора. Запомните, какому биту порта со- соответствует каждый из сегментов (а, Ь, с и т.д.). Общепринятые обозначе- обозначения сегментов семисегментного индикатора показаны на Рис. 2.9. -49-
Глава 2. Основные операции в AT90S1200 и TINY 12 Рис. 2.9. Обозначения выводов семисегментного индикатора 0 ПРИМЕР 2.1. Порт В используется следующим образом: бит 7 — d, бит 6 — а, бит 5 — с, бит 4 — g, бит 3 — Ь, бит 2 — f, бит 1 — е. Я назначил номера битов сегментам совершенно произвольно, чтобы показать, что порядок подключения выводов индикатора к порту не имеет никакого значения. В дальнейшем вы не раз увидите, что из-за физичес- физических ограничений печатной платы разводку одних конфигураций осущест- осуществить легче или она получается более компактной, чем другие. Программу изменить легко — намного легче, чем аппаратную часть. Если индикатор подключен так, как указано в Примере 2.1, то число, помещаемое в регистр PortB, должно иметь вид dacgbfe- (состояние 0-го бита не имеет значения, поскольку этот вывод порта не подключен к ин- индикатору). Значение каждого бита этого числа соответствует требуемому состоянию вывода, соединенного с конкретным сегментом. Итак, если вы используете дисплей с общим катодом (т.е. для включения сегмента надо подать на него напряжение ВЫСОКОГО уровня, см. Рис. 2.10) и хотите отобразить, например, букву А, то вам пот- потребуется включить сегменты а, Ь, с, е, f и g. Вернемся к Примеру 2.1, в котором сегменты подключены к порту В в порядке dacgbfe-. В этом случае для отображения буквы А в регистр PortB следует записать число ОЬОПППО. Бит 0 сброшен, поскольку соответству- соответствующий вывод порта не подключен к дисплею. 0 ПРИМЕР 2.2. Если сегменты дисплея с общим катодом подключе- подключены к порту В в порядке dacgbfe-, какие числа следует записать в ре- регистр PortB для отображения букв С и Е? Буква С состоит из сегментов a, d, e и f, поэтому число, записываемое в регистр PortB, должно быть равно Obi 1000110. Буква Е включает сегменты a, d, e, f и g, поэтому число должно быть равно ОЬПОЮПО. -50-
Программы В и С. Кнопка Общий катод Общий анод Рис. 2.10. Дисплеи с общим катодом и общим анодом УПРАЖНЕНИЕ 2.3. Если сегменты индикатора подключены к пор- порту В в порядке abcdefg-, то какие числа необходимо загрузить в ре- регистр PortB для отображения символов 0, 1,2, 3, 4, 5, 6, 7, 8, 9, А, Ь, с, d, E и F? Преобразование числа в код для семисегментного индикатора можно выполнить различными способами, однако наиболее простой из них за- заключается в использовании таблицы перекодировки (look-up table). Основ- Основной идеей, лежащей в основе таблиц перекодировки, является косвенная адресация. До сих пор мы использовали только прямую адресацию, т.е., если нам необходимо было прочитать содержимое регистра с номером 4, мы просто считывали содержимое этого регистра. Косвенная же адресация за- заключается в таком способе чтения числа из регистра с номером X, при ко- котором значение X определяется содержимым другого регистра, называемо- называемого Z B-байтный регистр, образованный объединением регистров R30 и R31). Эта операция по своей сути напоминает отсылку письма, причем письмо соответствует содержимому рабочего регистра (R0...R31), а адрес задается числом, находящимся в регистре Z. 0 ПРИМЕР 2.3. Необходимо загрузить число 00 в рабочие регистры с R0 по R29. -51-
Глава 2. Основные операции в AT90S1200 и TINY12 -52- Вместо того, чтобы писать: clr R0 ; Очищаем R0 clr R1 ; Очищаем R1 clr R2 ; Очищаем R2 clr R29 ; Очищаем R29 мы можем воспользоваться косвенной адресацией и выполнить эту задачу меньшим числом команд. Первым регистром, в который мы хотим запи- записать число, является R0 (адрес 0), поэтому следует записать в регистр Z число 00 (задавая адрес письма равным 0). Как вы помните, регистр Z со- состоит из двух регистров — ZL и ZH (старший и младший байты), поэтому необходимо очистить их оба: clr ZL ; Очищаем ZL clr ZH ; Очищаем ZH После этого нам необходимо инициализировать какой-либо регистр нулем, чтобы мы могли послать его содержимое «по почте» другому регистру. У нас уже есть регистр, содержащий 0 (ZH), его мы и будем ис- использовать. st register, Z Эта команда косвенно пересылает (store) содержимое рабочего регист- регистра register по адресу, хранящемуся в регистре Z. Соответственно, команда st ZH,Z пошлет число, находящееся в регистре ZH @), по адресу, задаваемому ре- регистром Z (тоже 0), и, таким образом, очистит регистр R0. Теперь мы хо- хотим очистить регистр R1, поэтому инкрементируем регистр Z, чтобы он указывал на адрес 01 (т.е. R1). После этого программа должна вернуться обратно, образуя цикл, в котором постепенно будут сброшены все регист- регистры. Причем для этого потребуется гораздо меньше команд, чем в случае использования прямой адресации. Все, что нам нужно, — это контролиро- контролировать регистр ZL для определения момента, когда его содержимое станет равным 30, так как это значение превышает адрес последнего регистра, ко- который мы собираемся очистить. А как узнать, что регистр ZL стал равен 30? Мы просто вычтем из ре- регистра 30 и проверим, равен результат нулю или нет. Если ZLравен 30, то результат вычитания будет равен 0. Однако мы не собираемся действи- действительно вычитать 30 из ZL! Вместо этого мы воспользуемся одной из ко- команд сравнения: ср register,register
Программы В и С. Кнопка Эта команда сравнивает (compares) содержимое двух регистров общего назначения (на самом деле микроконтроллер вычитает один регистр из другого, оставляя неизменным их содержимое). После этой команды мы должны узнать, равен ли результат нулю. Это можно сделать, посмотрев состояние флага нуля. Регистр ввода/вывода SREG ($3F) содержит набор флагов, которые автоматически устанавливаются и сбрасываются в зави- зависимости от результата выполнения ряда операций. Флаг нуля устанавлива- устанавливается, если результат операции равен нулю. Проверить состояние этого флага можно двумя способами. Первый из них: brbs label,bit По этой команде микроконтроллер переходит в другое место програм- программы, если заданный бит в регистре SREG установлен (branches if a bit is set). Флаг нуля является 1-м битом, поэтому операнд bit должен быть равен 1. Обратите внимание, что между командой и меткой label не может быть больше 63 команд. Аналогично brbc label,bit По этой команде микроконтроллер переходит в другое место програм- программы, если бит в регистре SREG сброшен (branches if a bit is clear). И здесь мы впервые встречаемся с избыточностью системы команд, которая заключа- заключается в том, что наряду с общими командами проверки состояния битов ре- регистра SREG в микроконтроллерах AVR имеются команды, соответ- соответствующие каждому конкретному биту. В частности, для флага нуля: breq label Эта команда означает «переход, если равно» (branch if equal) (а конкрет- конкретно — переход, если флаг нуля установлен). Обратная по смыслу команда: brne label означает «переход, если не равно» (branch if not equal) (а конкретно — пе- переход, если флаг нуля сброшен). Полный набор избыточных/второстепен- избыточных/второстепенных команд приведен в Приложении С вместе с их эквивалентными ко- командами. Для сравнения содержимого регистра с константой, а не с содер- содержимым другого регистра используется команда: cpi register,number Пожалуйста, обратите внимание на то, что эта команда работает только с регистрами R16...R31, однако, поскольку ZL со- соответствует R30, мы в данном случае можем воспользоваться этой коман- командой. Таким образом, фрагмент программы, выполняющий очистку регист- регистров R0...R29, выглядит следующим образом: -53-
Глава 2. Основные операции в AT90S1200 и TINY12 clr ZL ; Очищаем ZL clr ZH ; Очищаем ZH ClearLoop: st ZH,Z ; Косвенно очищаем регистр inc ZL ; Переходим к следующему адресу cpi ZL,30 ; Сравниваем ZL с 30. brne ClearLoop ; Переходим к ClearLoop, если ZL <> 30 Имеет смысл разместить эти шесть команд в секции Init для сброса большого числа рабочих регистров при старте программы. Вы можете за- задавать начальный и конечный адреса, изменяя начальное значение регист- регистра ZL и конечное значение, с которым он сравнивается. Однако заметьте, что вы не можете в этом же цикле сбрасывать регистр ZL (т.е. адрес не мо- может быть больше 30), поскольку в противном случае программа войдет в бесконечный цикл (поразмыслите над этим). rgS УПРАЖНЕНИЕ 2.4. Повышенной сложности! Напишите фраг- ^^ мент из шести команд, при выполнении которого в регистры R0, R1,..., R15 будут занесены числа 0, 1,..., 15 соответственно. Косвенным может быть не только запись, но и чтение: Id register,Z Эта команда косвенно загружает (load) в рабочий регистр register зна- значение, которое находится по адресу, указываемому регистром Z. Таким об- образом, если сформировать таблицу значений, занимающих последователь- последовательные ячейки памяти, то, изменяя значение Z, можно считывать различные значения. Допустим, к примеру, мы храним коды для отображения на се- мисегментном индикаторе цифр от 0 до 9 в регистрах R20...R29. Тогда за- запишем в регистр Z число 20 («обнулим» его, чтобы он указывал на начало таблицы) и прибавим к регистру то число, которое хотим преобразовать. Используя после этого операцию косвенного чтения, мы получим семи- сегментный код, соответствующий этому числу: ldi ZL,20 ; Устанавливаем ZL на R20 add ZL,digit ; Прибавляем цифру к ZL Id temp,Z ; Читаем Rx в temp out PortB,temp ; Выводим temp в порт В Приведенный выше фрагмент преобразует число, хранящееся в регистре digit, в семисегментный код, который затем выставляется на вы- выводы порта В. Не забудьте, что перед этим необходимо занести все коды в соответствующие регистры: ldi R20,0Ы1111100 ; Код для 0 ldi R21/0b01100000 ; Код для 1 ldi R29,0Ы1110110 ; Код для 9 -54-
Программы D и Е. Счетчик В заключение замечу, что рабочие регистры очень редко используются для хранения таблиц. Более того, такое использование регистров слишком расточительно, но, поскольку в модели 1200 нет других областей памяти данных, у нас не остается выбора. В моделях, имеющих ОЗУ, можно его использовать для хранения таблиц перекодировки. Кроме того, в других моделях микроконтроллеров имеется команда lpm, которая позволяет ис- использовать для хранения таблиц память программ. Подробнее об этой воз- возможности мы поговорим при разработке симулятора логических элемен- элементов (см. стр. 85). Программы D и Е. Счетчик • Контроль входов • Управление семисегментными индикаторами Нашим следующим проектом будет счетчик, который подсчитывает количество нажатий на кнопку, от 0 до 9. После 10 нажатий (когда значе- значение счетчика превысит 9) он должен сброситься в 0. Семисегментный дис- дисплей будет подключен к выводам РВ0...РВ6, а кнопка — к выводу PD0. Принципиальная схема устройства приведена на Рис. 2.11; обратите вни- внимание на то, в каком порядке выводы индикатора подключены к порту. Блок-схема программы приведена на Рис. 2.12. Рис. 2.11. Принципиальная схема счетчика Секцию Init вы вполне можете написать самостоятельно, не забудьте только включить подтяжку на входе, к которому подключена кнопка. На- Начальное состояние выводов порта В должно соответствовать 1 на индика- индикаторе. Для хранения числа нажатий мы будем использовать регистр, назы- -55-
Глава 2. Основные операции в AT90S1200 и TINY12 Инициализация Инкрементировать счетчик ваемый Counter, поэтому вам необходимо в секции объявлений присвоить это имя рабочему регистру R17. Мы задействуем именно R17 по той при- причине, что регистры R16...R31 являются «исполнительными ассистента- ассистентами» — более мощными регистрами, над которыми можно выполнять боль- большее число операций. Поэтому сначала мы «забиваем» регистры R16...R31 и только потом, если их не хватило, используем R0...R15. В регистры с R20 по R29 в секции Init загрузите коды семисегментного индикатора для цифр от 0 до 9. (ПОДСКАЗКА: Если это сделать до настройки порта, то для его инициализации можно будет просто скопировать R20 в регистр PortB. Кроме того, не забудьте сбросить в секции Ink регистр Counter.) 56- Кнопка нажата? Счетчик больше 9? Изменить число на индикаторе Обнулить счетчик Рис. 2.12. Блок-схема программы «Счетчик»
Программы D и Е. Счетчик /gS УПРАЖНЕНИЕ 2.5. С помощью каких трех команд можно прове- *~~* рить состояние кнопки и вернуться обратно для повторной провер- проверки? Если кнопка нажата, программа должна выходить из цикла и инкрементировать Counter. После этого мы должны определить, не стал ли Counter больше 9. Вос- Воспользуемся командой cpi для сравнения и командой brne для перехода, ес- если Counter не равен 10. В противном случае регистр Counter должен быть сброшен в 0. При использовании команд brne и аналогичных может ока- оказаться полезным следующий прием. Часто встречаются ситуации, когда по результату сравнения вместо перехода «куда-то» мы хотим просто пропус- пропустить следующую команду (как в случае команд sbis и sbic). Чтобы осущест- осуществить пропуск при использовании команд перехода, напишите в качестве операнда вместо метки слово PC + 2, тогда при переходе будет пропущена одна команда (т.е. программа перейдет на две команды вперед). Буквы PC означают «счетчик команд» (Program Counter), который более подробно будет рассматриваться на стр. 71. /gS УПРАЖНЕНИЕ 2.6. С помощью каких трех строк можно прове- ^^ рить, не равен ли Counter десяти, и сбросить его, если это так? Воз- Возможно, вам пригодится прием с использованием счетчика команд (PC + 2). Теперь нам необходимо отобразить на индикаторе содержимое регист- регистра Counter. Для этого мы, как и прежде, загрузим в ZL адрес регистра R20, после чего прибавим к нему Counter. /gS УПРАЖНЕНИЕ 2.7. С помощью каких пяти строк можно вывести "^ содержимое регистра Counter на индикатор, подключенный к порту В, и вернуться к метке Start? Полный текст получившейся программы приведен в Приложении J {Программа D). Я рекомендую вам собрать это устройство. Поработав с ним, вы заметите, что наша программа имеет ряд недостатков. Основной проблемой является то, что мы не ждем отпускания кнопки, в результате чего инкрементирование регистра Counter происходит много- многократно, в течение всего времени, пока кнопка нажата. Если предположить, что длительность нажатия на кнопку равна 0.1 с, а частота резонатора рав- равна 4 МГц, то, учитывая время выполнения фонового цикла программы (приблизительно 14 тактов), при каждом нажатии на кнопку инкременти- инкрементирование регистра Counter будет выполнено примерно 4 000 000/A4 х 10) = = 28 600 раз! То есть мы получили довольно неплохой генератор случайных чисел (отступая от темы, замечу, что создание генератора случайных чисел -57-
Глава 2. Основные операции в AT90S1200 и TINY12 без использования событий, связанных с какими-либо действиями чело- человека, является довольно сложной задачей — компьютерам не слишком хо- хорошо удаются случайные операции). Обнаруженный эффект можно ис- использовать при создании электронных игральных кубиков, однако давайте вернемся к нашей исходной задаче создания надежного счетчика. Новая блок-схема приведена на Рис. 2.13. В конец программы следует внести изменения, позволяющие перед возвратом к началу основного цикла дождаться отпускания кнопки. rgS УПРАЖНЕНИЕ 2.8. Напишите две новые строки, необходимые J^^ для устранения описанной проблемы, и укажите место, куда их не- необходимо вставить. (ПОДСКАЗКА: этому циклу нужно будет при- присвоить имя.) Проверьте работу новой программы (полный ее текст приведен в При- Приложении J — Программа Е). Наверняка вы заметите еще один недостаток, степень проявления которого зависит от качества использованной кноп- кнопки. Вы увидите, что при нажатии на кнопку состояние счетчика изменяет- изменяется скачками (например, с 1 на 4). Это происходит из-за явления, которое называется дребезгом контактов. Оно заключается в многократном некон- неконтролируемом размыкании и замыкании контактов при нажатии или отпус- отпускании кнопки, как показано на Рис. 2.14. Чтобы счетчик не воспринял единственное нажатие на кнопку как не- несколько разных нажатий, нужно будет ввести небольшую задержку между моментом отпускания кнопки и повторной проверкой ее состояния. Это накладывает определенные ограничения на минимальное время между на- нажатиями, однако следует найти компромиссное решение. 0 ПРИМЕР 2.4. Чтобы избавиться от дребезга контактов, мы могли бы подождать после отпускания кнопки 5 с, прежде чем повторно проверять ее состояние. Но в таком случае, если нажать на кнопку через 3 с после предыдущего нажатия, то этот сигнал не будет заре- зарегистрирован. Такая задержка наверняка предотвратит влияние дре- дребезга, однако приведет к тому, что минимальный интервал между нажатиями станет слишком большим. 0 ПРИМЕР 2.5. В качестве альтернативы для подавления дребезга контактов мы могли бы подождать 0.1 с после отпускания кнопки перед повторной проверкой ее состояния. Однако длительность дребезга может быть больше 0.1 с, так что такая задержка будет неэффективной. -58-
Программы D и Е. Счетчик Рис. 2.13. Скорректированная блок-схема программы «Счетчик» -59- Инициализация Кнопка нажата? Инкрементировать счетчик Счетчик больше 9? Изменить число на индикаторе Кнопка нажата? Обнулить счетчик
Глава 2. Основные операции в AT90S1200 и TINY12 г Кнопка ^> + 5В Кнопка нажата Кнопка отпущена Рис. 2.14. Дребезг контактов О* ЮОк -о ОВ Подходящим значением задержки могла бы быть величина порядка нескольких десятых секунды, однако для разных кнопок это значение бу- будет различным, так что вам придется немного поэкспериментировать. Чтобы реализовать требуемую задержку, нам необходимо познакомиться с различными способами формирования временных интервалов, чем мы и займемся в следующем разделе. Формирование временных интервалов Если вы вспомните список регистров ввода/вывода (не помешает лиш- лишний раз взглянуть на стр. 26), то обнаружите регистр TCNT0 ($32) — счет- счетный регистр таймера/счетчика 0 (Timer/Counter 0). Это встроенный таймер микроконтроллера, который может автоматически считать в прямом на- направлении с заданной скоростью, сбрасываясь в 0 после достижения значе- значения 255. Мы можем использовать этот таймер для выполнения различных операций, связанных со временем (например, для формирования секунд- секундной задержки). В более развитых микроконтроллерах имеется несколько таймеров, часть из которых являются 16-битными. Причина, по которой в названии таймера присутствует слово «счетчик», заключается в том, что он может также использоваться для подсчета числа импульсов на определен- определенном выводе микроконтроллера (в случае модели 1200 это вывод 8 — PD4). В данном проекте мы будем использовать таймер/счетчик 0 в качестве тайме- таймера, поэтому я буду называть его Таймер 0 или, для краткости, Т/СО. Прежде чем использовать Таймер 0, его необходимо соответствующим образом сконфигурировать (в частности, задать работу в режиме таймера, а не счетчика). Для конфигурирования таймера используется регистр TCCR0 ($33) — регистр управления Т/СО (Т/СО Control Register). Каждый бит этого регистра отвечает за определенный аспект функционирования Т/СО. В модели 1200 используются только биты 2...0 (Рис. 2.15). Биты 3...7 не используются, однако, изменяя состояние битов 0...2 оп- определенным образом, можно задать требуемое нам поведение Т/СО. Если мы вообще не собираемся использовать Т/СО, то все три бита должны быть -60-
Программы D и Е. Счетчик TCCRO — регистр управления Т/СО ($33) Бит 7 6 5 4 3 2 1 О Название ----- CS02 CSO1 CSOO I—' I 000 001 010 011 100 101 110 111 Т/СО остановлен Частота Т/СО равна тактовой частоте (СК) Частота Т/СО равна СК/8 Частота Т/СО равна СК/64 Частота Т/СО равна СК/256 Частота Т/СО равна СК/1024 Т/СО изменяется по спадающему фронту на выводе ТО Т/СО изменяется по нарастающему фронту на выводе ТО Рис. 2.15. Регистр управления Т/СО сброшены. Если мы собираемся использовать его в качестве таймера, необ- необходимо выбрать одну из пяти возможных в этом режиме конфигураций. И наконец, если мы хотим использовать его для счета внешних импульсов (на выводе PD4), мы можем воспользоваться двумя последними конфигу- конфигурациями. Различные конфигурации, доступные нам при использовании Т/СО в качестве таймера, определяют скорость его счета. Понятно, что зна- значение тактовой частоты (СК) слишком велико (несколько МГц) — это час- частота кварцевого резонатора, подключенного к микроконтроллеру, — поэ- поэтому для отсчета интервалов порядка секунд нам придется существенно уменьшить это значение. Уменьшить скорость счета Таймера 0 можно не более чем в 1024 раза. Соответственно, если подключить к микроконтрол- микроконтроллеру резонатор с частотой 2.4576 МГц (это действительно очень распро- распространенное значение частоты резонатора), то частота счета Таймера 0 будет равна 2 457 600/1024 = 2400 Гц. Таким образом, даже при максимально воз- возможном замедлении таймера его состояние будет изменяться 2400 раз в се- секунду. 0 ПРИМЕР 2.6. Какое число следует загрузить в регистр TCCR0, что- чтобы с наибольшей эффективностью использовать Т/СО для подсчета числа прошедших секунд? Биты с 3-го по 7-й всегда равны нулю. Счет осуществляется по внутреннему тактовому сигналу с наи- наименьшей частотой, равной СК/1024. -61-
Глава 2. Основные операции в AT90S1200 и TINY 12 Соответственно число, записываемое в регистр TCCR0, равно ObOOOOOlOl. rgf УПРАЖНЕНИЕ 2.9. Какое число должно быть записано в регистр ^^ TCCR0, если Т/СО используется для подсчета нажатий на кнопку, подключенную между выводом PD4 и +5 В? Чтобы записать число в регистр TCCR0, мы должны сначала загрузить его в регистр temp, а затем воспользоваться командой out, как и в случае других регистров ввода/вывода. Поскольку в дальнейшем вы вряд ли буде- будете изменять конфигурацию таймера, имеет смысл задавать ее в секции Init и больше не обращать на это внимание. Для получения секундных и минутных интервалов дальнейшее деление частоты вам необходимо осуществлять самостоятельно. Воспользуемся для этого так называемым маркером и некоторым количеством счетных ре- регистров. Это рабочие регистры, которые мы будем использовать для фор- формирования требуемых временных интервалов. Основная идея заключается в том, чтобы подсчитать, сколько раз Таймер 0 достигает определенного значения. Например, для формирования секундной задержки мы должны подождать, пока Таймер 0 отсчитает 2400 раз. Это аналогично тому, как ес- если бы Таймер 0 достиг значения 80 в общей сложности 30 раз, поскольку 30 х 80 = 2400. Эту задачу можно было бы выполнить, используя и другие сомножители числа 2400, меньшие 256. Чтобы проверить, не равно ли содержимое Таймера 0 числу 80, напи- напишем следующие строки: out TCNT0,temp ; Копируем TCNTO в temp cpi temp,80 ; Сравниваем temp с 80 breq Equal ; Переходим к Equal, если temp = 80 В этом фрагменте проверяется равенство Таймера 0 числу 80 и осу- осуществляется переход к метке Equal, если это так. Проблема заключается в том, что нам требуется сравнивать состояние Таймера 0 с различными зна- значениями, а не только с числом 80. Сначала мы возьмем это число, однако в следующем цикле мы будем сравнивать Таймер 0 с числом 160, затем с числом 240 и т.д. Поэтому мы будем использовать регистр (я называю его маркером), в который первоначально записывается 80, а при каждом до- достижении Таймером 0 значения маркера последний увеличивается на 80. В микроконтроллерах AVR отсутствует команда сложения константы с ре- регистром, однако имеется команда вычитания константы из регистра. Оче- Очевидно, что вычитание отрицательного числа эквивалентно сложению. subi register,number -62-
Программа F. Бегущий огонек Эта команда вычитает константу (subtracts immediate) из регистра об- общего назначения. Обратите внимание: команда работает только с регистрами R16...R31. Итак, нам удалось определить момент достижения Таймером 0 задан- заданного значения (80). Для формирования секундного интервала нам нужно, чтобы это событие наступило 30 раз. Возьмем регистр, запишем в него число 30 и будем уменьшать содержимое этого регистра каждый раз, когда значение Таймера 0 будет становиться равным 80. dec register Эта команда декрементирует, т.е. уменьшает на единицу (decrements) содержимое регистра. Равенство регистра нулю означает, что описанное событие произошло 30 раз. Собрав все описанное вместе, получим фраг- фрагмент программы, необходимый для формирования секундной задержки. ldi Count30,30 ; Начальное значение счетчика 30 ldi Mark80,80 ; Начальное значение маркера 80 TimeLoop: out TCNT0,temp ; Сохраняем состояние Таймера 0 в temp ср temp,Mark80 ; Сравниваем temp с Mark80 brne TimeLoop ; Если не равны, возвращаемся ; к началу цикла subi Mark80,-80 ; Прибавляем 80 к Mark80 dec Count30 ; Уменьшаем Count30 на единицу brne TimeLoop ; Если Count30 <> 0, возвращаемся ; к началу цикла Первые две команды загружают в регистры счетчика и маркера требуе- требуемые значения. Затем содержимое регистра TCNT0 копируется в регистр temp, который в свою очередь сравнивается с маркером. Если они не рав- равны, программа переходит к началу цикла (TimeLoop). В противном случае значение маркера увеличивается на 80, счетчик уменьшается на единицу, и, если последний не равен нулю, программа переходит к метке TimeLoop. Не забудьте определить регистры Mark80 и Count30 в секции объявлений (это должны быть регистры из диапазона R16...R31). Программа F. Бегущий огонек • Формирование временных интервалов • Считывание входных сигналов • Управление выходами Нашим следующим проектом будет устройство, формирующее огонек, «бегущий» по ряду светодиодов. Этот эффект достигается поочередным -63-
Глава 2. Основные операции в AT90S1200 и TINY12 включением светодиодов. Скоростью движения огонька мы будем управ- управлять с помощью двух кнопок: при нажатии на одну из них скорость будет увеличиваться, а при нажатии на другую — уменьшаться. По умолчанию каждый светодиод будет включаться на 0.5 с; это значение можно будет из- изменять от 0.1 до 1 с. Светодиоды мы подключим к порту В, а кнопки — к выводам PD0 и PD1. Принципиальная схема устройства и блок-схема алгоритма приведе- приведены на Рис. 2.16 и Рис. 2.17 соответственно. Рис. 2.16. Принципиальная схема устройства «Бегущий огонек» Секция, соответствующая блоку инициализации, должна быть доста- достаточно простой; не забудьте только о том, что в этой секции имеет смысл сконфигурировать и Таймер 0 (поскольку мы будем оперировать интерва- интервалами порядка секунды, придется использовать Т/СО в режиме таймера, ра- работающего на минимально возможной частоте). Обратите также внима- внимание, что на входах PD0 и PD1 потребуется включить подтяжку, а у порта В при старте программы должен быть включен только один выход (напри- (например, РВО). Теперь подумаем о том, как мы будем формировать задержку, длитель- длительность которой может изменяться от 0.1 до 1с. Минимальная задержка @.1 с) может быть сформирована при значении маркера, равном 240 B400/240 = 10 Гц), — предполагается, что в схеме используется кварцевый резонатор с частотой 2.4576 МГц, а период тактового сигнала Таймера 0 ра- равен СК/1024. В этом случае, чтобы изменять длительность общей задерж- задержки от 0.1 до 1 с, достаточно будет изменять начальное значение счетчика от 1 до 10. Таким образом, нам понадобится регистр маркера Mark240 и ре- регистр счетчика Counter. По умолчанию Counter равен 5 @.5 с), но может -64-
Программа F. Бегущий огонек Инициализация Увеличить длительность свечения Уменьшить длительность свечения Рис. 2.17. Блок-схема программы «Бегущий огонек» быть установлен и в другое значение, задаваемое регистром Speed. He за- забудьте определить эти регистры в секции объявлений в начале программы. В следующем после инициализации блоке проверяется состояние кнопки уменьшения скорости. Назначим эту функцию кнопке, подклю- подключенной к выводу PD0. Ее состояние мы будем проверять с помощью ко- команды sbic. Если кнопка не нажата (т.е. на выводе порта ВЫСОКИЙ уро- уровень), то будет выполняться следующая после sbic команда — команда -65» Кнопка увеличения скорости нажата? Кнопка уменьшения скорости нажата? Заданное время прошло? Сменить СИД
Глава 2. Основные операции в AT90S1200 и TINY12 перехода к секции проверки состояния кнопки увеличения скорости (на- (назовем эту секцию UpTest). Если же кнопка нажата, нам нужно увеличить на единицу значение регистра Speed (замедлить движение огонька). Эту операцию можно вы- выполнить с помощью следующей команды: inc register ; Эта команда инкрементирует (increment) содержимое регистра, т.е. уве- увеличивает его на 1. Нам не требуются задержки больше 1 с, поэтому мы должны следить за тем, чтобы значение Speed не стало больше 10 (таким образом, число 11 означает, что задержка больше допустимой). Мы можем легко выполнить такую проверку, используя уже рассмотренную команду сравнения регистра с константой, cpi. Если регистр Speed не равен 11, мы спокойно можем перейти к метке ReleaseDown и ждать отпускания кноп- кнопки. Если же содержимое регистра равно 11, мы должны уменьшить его на единицу (используя команду dec). Соответственно, первые строки программы будут следующими: Start: sbic PinD,0 ; Проверяем кнопку уменьшения скорости rjmp UpTest ; He нажата, переходим inc Speed ; Уменьшаем длительность cpi Speed,11 ; Speed стало равно 11? brne ReleaseDown ; Если нет, перейдем к ReleaseDown dec Speed ; Уменьшаем Speed на 1 ReleaseDown: sbis PinD,0 ; Ждем отпускания кнопки rjmp ReleaseDown В блоке UpTest мы выполняем те же самые операции с кнопкой увели- увеличения скорости, подключенной к PD1, только вместо перехода к метке UpTest мы будем переходить к следующей секции, которую назовем Timer. Если кнопка увеличения скорости нажата, то мы должны декрементиро- вать регистр Speed, а вместо проверки его на равенство 11 должны прове- проверить, не равен ли он нулю (и увеличиваем его, если это так). Конечно, можно было бы написать команду cpi Speed,0, однако это лишнее, так как флаг нуля изменяется в результате выполнения команды dec. Таким обра- образом, если в результате декрементирования регистр станет равным нулю, то мы сможем, как и раньше, воспользоваться командой brne. /gS УПРАЖНЕНИЕ 2.10. Напишите семь строк программы, выполня- ^^ ющих описанные выше действия. -66-
Программа F. Бегущий огонек Следующая секция, которую мы назвали Timer, должна проверять, не закончилось ли заданное время, и возвращаться на начало основной программы, если время не закончилось. То есть вместо формирования собственного цикла блок обработки временных интервалов должен воз- возвращаться к метке Start. Помимо всего прочего, мы должны разместить в секции Init строки, в которых задаются начальные значения маркера и счетчика. В регистр Mark240 необходимо загрузить 240, а в регистры Speed и Counter — 5. Имея инициализированные таким образом регистры, мы можем перейти непос- непосредственно к циклу отсчета времени. Timer: in temp,TCNT0 ; Загружаем содержимое Таймера 0 в temp ср temp,Mark240 ; Сравниваем с маркером brne Start ; Если не равны, возвращаемся к Start subi Mark240,-240 ; Прибавляем 240 к Mark240 dec Counter ; Декрементируем счетчик brne Start ; Если не ноль, возвращаемся к Start Обратите внимание на то, что вместо возврата к началу секции Timer программа переходит к метке Start. Однако, если вместо метки Start пере- переходить в конце цикла длительностью 0.1 с к метке Timer, можно уменьшить влияние дребезга контактов кнопки. В последнем случае состояние кно- кнопок будет проверяться через каждые 0.1 с, т.е. длительность нажатия кноп- кнопки должна быть не менее 0.1 с. После истечения общего времени мы долж- должны «сдвинуть» включенный СИД и снова загрузить в регистр Counter со- содержимое регистра Speed. Для этого мы воспользуемся командой: mov regl,reg2 ; Эта команда пересылает (move) число из регистра regl в регистр reg2. /gS УПРАЖНЕНИЕ 2.11. Какой одной командой можно загрузить в ^^ Counter содержимое регистра Speed? Для изменения узора светодиодов в нашем распоряжении имеется не- несколько команд сдвига: asr register ; Арифметический сдвиг вправо lsr register ; Логический сдвиг вправо lsl register ; Логический сдвиг влево ror register ; Циклический сдвиг вправо rol register ; Циклический сдвиг влево При арифметическом сдвиге все биты регистра сдвигаются вправо, при этом бит 7 не изменяется, а бит 0 загружается в флаг переноса (С). Как и -67-
Глава 2. Основные операции в AT90S1200 и TINY12 флаг нуля, этот флаг расположен в регистре SREG. При логическом сдвиге вправо все биты регистра сдвигаются вправо, а 7-й бит сбрасывается в 0. Циклический сдвиг право осуществляется через флаг переноса, т.е. значе- значение флага переноса заносится в 7-й бит, а 0-й бит исходного содержимого регистра загружается в флаг переноса. Все описанные операции проил- проиллюстрированы на Рис. 2.18. Рис. 2.18. Операции сдвига При изменении узора нам необходимо исключить появление на краях строки битов, установленных в 1, так как это приведет к несвоевременно- несвоевременному включению краевых СИД, и требуемый узор будет нарушен. Из этого следует, что нам подойдут только команды lsl или lsr. Для определенности воспользуемся командой lsl, сдвигающей узор влево. Мы не можем приме- применять описанные команды сдвига непосредственно к регистру PortB, поэто- поэтому нам потребуется считать текущее состояние светодиодов в регистр temp, сдвинуть temp, а затем записать его обратно в PortB. Причем перед пере- пересылкой регистра temp в PortB мы должны проверить, не вышли ли мы за границу строки (не был ли это уже восьмой сдвиг), и, если это так, сбро- сбросить PortB в исходное состояние (все выходы, кроме РВО, выключены). Возникновение этой ситуации можно контролировать, отслеживая состо- состояние флага переноса, который установится в 1 при сдвиге единичного бита за пределы строки (см. Рис. 2.18). Для этого используется команда: brcc label -68-
Программа F. Бегущий огонек Эта команда передает управление на метку label, если флаг переноса сброшен (branches if the carry flag is clear). Соответственно, фрагмент про- программы, выполняющий описанные действия, выглядит следующим образом: in temp,PortB ; Считываем текущее состояние lsl temp ; Сдвигаем влево brcc РС+2 ; Проверяем флаг С и пропускаем команду, ; если он сброшен ldi temp,0b00000001 ; Сбрасываем в исходное состояние out PortB,temp ; Изменяем состояние выводов порта В rjmp Start ; Возвращаемся к метке Start Заметьте, что для пропуска команды при сброшенном флаге переноса мы использовали выражение, связанное с текущим значением счетчика команд (PC + 2). Полный текст программы приведен в Приложении J {Программа F). Теперь вы можете внимательно изучить всю программу, скомпилиро- скомпилировать ее и выполнить симуляцию. Вы заметите, что при пошаговом выпол- выполнении программы во время симуляции ожидание конца счета Таймера О занимает слишком много времени. Для таких случаев в AVR Studio предус- предусмотрен способ быстрого прохода через отдельные участки программы. Ес- Если вы щелкните правой кнопкой мыши на строке программы (в режиме симуляции), то в контекстном меню вы обнаружите пункт Run to Cursor (Ctrl + F10). Выбор этого пункта приведет к тому, что программа будет вы- выполняться с большой скоростью до того места, где был расположен курсор (не так быстро, как в реальном устройстве, но все же близко к этому). К настоящему моменту мы рассмотрели довольно много команд; очень важно помнить их все, чтобы применять по мере необходимости. Даже ес- если вы не можете вспомнить точное название команды (которое можно найти в Приложении С), вы должны хотя бы знать, что такая команда су- существует. Ф ВОПРОС ДЛЯ ПОВТОРЕНИЯМ™ делают следующие команды: sbi, cbi, sbic, sbis, rjmp, ldi, st, Id, clr, ser, in,out, cp, cpi, brbs, brbc, breq, brne, brcc, subi, dec, inc, mov, asr, lsr, lsl, ror и rol? (Ответы в Приложении D.) Формирование временных интервалов без таймера? Иногда Таймер О (TCNT0) может нам потребоваться для других целей (например, для подсчета импульсов на выводе T0/PD4), поэтому давайте посмотрим, как можно формировать временные интервалы без использо- использования таймера. Каждая команда выполняется определенное время, поэто- поэтому, используя написанные соответствующим образом циклы, мы можем формировать задержки с такой же точностью, что и при использовании -69-
Глава 2. Основные операции в AT90S1200 и TINY12 Таймера 0. Единственным недостатком такого подхода является то, что в отличие от Таймера 0 цикл нельзя прерывать (скажем, по нажатию кноп- кнопки), иначе длительность получаемой задержки будет больше требуемой. В основе этого способа лежит определение количества тактов, которое нам требуется пропустить, и постепенное уменьшение этого значения до нуля. Если это число больше 255 (что бывает практически всегда), то воз- возникают определенные проблемы. В этом случае мы должны будем распре- распределить это число по нескольким регистрам и последовательно их обраба- обрабатывать. Младший байт при этом декрементируется до тех пор, пока его значение не изменится с 00 на FF (при этом устанавливается флаг перено- переноса), затем декрементируется следующий по порядку старший байт и т.д. Первым делом необходимо определить, сколько тактов потребуется для формирования заданной задержки. Например, для секундной задерж- задержки при использовании резонатора на 4 МГц нам потребуется «убить» 4 миллиона тактов. Цикл, который мы напишем, будет выполняться за «х» тактов. Значениях приведены в Табл. 2.1. Таблица 2.1 X 3 4 5 6 7 Период при тактовой частоте 4 МГЦ 0...63 мкс 64мкс...16мс 16 мс. .4.1 с 4.1 с...17 мин 17 мин...74 ч Период при тактовой частоте 2.4576 МЩ 0... 102 мкс 102мкс...26мс 26мс...6.7с 6.7с...27мин 27 мин.. 120 ч Мы формируем задержку длительностью 1 с, соответственно х = 5. За- Затем делим 4 000 000 на 5, получаем 800 000. Переводим это число в шест- надцатеричную систему, получаем 0хС3500. Записываем это число, ис- -70- ПРИМЕР 2.7 Старший Младший Перенос? байт байт 0x1 А 0x04 нет 0х1А 0x03 нет 0х1А 0x02 нет 0х1А 0x01 нет 0х1А 0x00 нет С 0x1 А ^* OxFF ДА (поэтому декрементируем старший байт) 0x19 S*.0xFF нет 0x19 OxFE и т.д.
Программа F. Бегущий огонек пользуя четное количество разрядов (добавляем нулевой старший разряд, если количество разрядов нечетное), и затем делим его на группы по два разряда. В данном случае будет 0x00, 0x35 и ОхОС. В начале задержки в программе мы загружаем эти числа в рабочие ре- регистры. ldi Delay1,0x00 ldi Delay2,0x35 ldi Delay3,OxOC Собственно задержка формируется командами обработки регистров (по одной команде на каждый регистр) плюс еще одной в конце (т.е. в на- нашем случае с помощью четырех команд). Чтобы написать такой короткий цикл, воспользуемся новой командой: sbci reg,number ; Эта команда вычитает константу (subtract the immediate) из регистра, а также вычитает единицу, если установлен флаг переноса (сапу). Напри- Например, команда: sbci Delay2,0 вычитает 1 из регистра Delay2, если флаг переноса установлен, и 0 — в про- противном случае. Таким образом, наш цикл задержки будет выглядеть следу- следующим образом: Loop: subi Delayl,l ; Вычитаем 1 из Delayl sbci Delay2,0 ; Вычитаем 1 из Delay2, если флаг С = 1 sbci Delay3,0 ; Вычитаем 1 из Delay3, если флаг С = 1 brcc Loop ; Возвращаемся к началу, если флаг С сброшен К моменту выхода из цикла пройдет ровно одна секунда. Обратите вни- внимание, что каждая итерация цикла равна пяти тактам (команда перехода вы- выполняется за два такта). Теперь вам должно быть понятно, откуда взялись цифры, приведенные в Табл. 2.1, — для каждого дополнительного регистра задержки длительность итерации цикла увеличивается на один такт. Может возникнуть вопрос: почему для вычитания единицы (декрементирования) мы используем команду subi, а не dec? Дело в том, что команда dec, в отличие от команды subi, не влияет на флаг переноса. А он нам необходим для опре- определения моментов декрементирования старших байтов и выхода из цикла. Счетчик команд и подпрограммы В микроконтроллерах AVR имеется специальный встроенный счетчик, называемый счетчиком команд (Program Counter — PC), в котором хранит- хранится адрес следующей выполняемой команды. При выполнении обычных -71-
Глава 2. Основные операции в AT90S1200 и TINY 12 команд (арифметических, логических и т.п.) содержимое счетчика просто инкрементируется, в результате чего он указывает на следующую команду программы. При выполнении же команд перехода, таких как rjmp или brne, содержимое счетчика увеличивается на определенную величину, в резуль- результате чего микроконтроллер переходит к другому месту программы. В левом столбце приведенного выше примера указаны адреса (в шест- надцатеричной системе), по которым команды располагаются в памяти программ. Обратите внимание, что пустые строки не имеют адреса, также как и метки, которые в действительности обозначают адрес следующей ко- команды. Посмотрим, как ведет себя счетчик команд при выполнении этого фрагмента программы. Первоначально PC равен 039, а после выполнения команды sbi он становится равным ОЗА. После этого проверяется бит 0 ре- регистра PinD. Если он установлен в 1, PC просто увеличивается до 03В, а если сброшен в 0, то PC увеличивается на 2 и становится равным ОЗС. При вы- выполнении команды rjmp Start в PC загружается число 039, в результате чего программа переходит к команде, обозначенной меткой Start. Приведенный пример объясняет трюк с выражением «PC + 2», который мы использовали при написании нашей предыдущей программы: если результат выражения получается «не равно» (т.е. флаг нуля сброшен), к счетчику команд прибав- прибавляется не 1, как обычно, а 2, в результате чего пропускается одна команда. /gS УПРАЖНЕНИЕ 2.12. Как влияет на состояние PC команда rjmp ^^ Loop, использованная в предыдущем примере? Ну, вот мы дошли и до подпрограмм. Подпрограммой называется некото- некоторая совокупность команд программы, доступ к которым можно получить из любого места данной программы. После завершения подпрограммы про- программа возвращается обратно и продолжает выполняться с того места, где ее выполнение было прервано. Основная загвоздка заключается в том, что микроконтроллер должен помнить,в каком месте программы он находил- находился в момент вызова подпрограммы, чтобы иметь возможность вернуться ту- туда после выполнения последней. Область памяти, в которой сохраняется эта информация, называется стеком. Его можно представить в виде пачки бу- -72- ПРИМЕР2.8 Start: 039 sbi PortВ,0 ; Включаем СИД ОЗА sbic PinD,0 ; Проверяем кнопку 03В cbi PortB,0 ; Выключаем СИД Loop: ОЗС dec Counter ; 03D breq PC+2 ; Пропускаем команду, если 0 ОЗЕ rjmp Start ; 03F rjmp Loop ;
Программа F. Бегущий огонек маги. При вызове подпрограммы содержимое счетчика команд помещается на вершину стека. Когда выполняется команда возврата из подпрограммы, число, находящееся на вершине стека, загружается обратно в счетчик ко- команд, благодаря чему AVR возвращается к выполнению команды, следую- следующей за командой вызова подпрограммы. В модели 1200 имеется трехуров- трехуровневый аппаратный стек. Когда подпрограмма вызывается из другой под- подпрограммы, содержимое PC помещается на вершину стека, проталкивая (push) предыдущее число на более низкий уровень. Последующая команда возврата, как обычно, загрузит число с вершины стека в счетчик команд. Наличие в стеке всего трех уровней означает, что вы можете сделать не более трех вложенных друг в друга вызовов подпрограмм. Это ограничение связа- связано с тем, что при четвертом вызове подпрограммы и соответственно при за- занесении в стек нового значения первое число из трех, ранее занесенных в стек, теряется. Эта ситуация показана на Рис. 2.19. Команда вызова подпрограмм имеет вид: rcall label Эта команда является командой относительного вызова (relative call), поэтому подпрограмма должна находиться в пределах 2048 команд от ко- команды rcall. Для возврата (return) из подпрограммы используется соответ- соответствующая команда: ret Разумеется, вы можете сколько угодно вызывать подпрограммы из другой подпрограммы, например: Замечу, что до настоящего момента все наши программы были совмес- совместимы «снизу вверх», т.е. они могли работать и в более развитых моделях AVR. При использовании же подпрограмм эта совместимость нарушается, и, если вы разрабатываете такие программы для моделей, отличных от 1200 или Tiny, в секцию Init необходимо добавить следующие четыре стро- строки (почему это надо сделать, будет объяснено в главе 3): ldi temp,LOW(RAMEND) ; Указатель стека установлен out SPL,temp ; на последний адрес ОЗУ ldi temp,HIGH(RAMEND) out SPH;temp ; -73- Subl: rcall Sub2 rcall Sub3 rcall Sub4 ret Start: rcall Subl ;
Глава 2. Основные операции в AT90S1200 и TINY12 Ш ПОСЛЕ 035 Sub1: rcall Sub2 036 035 Sub2: ret rcall Sub3 038 039 Sub3: ret rcall Sub4 03A 03B 03C Sub4: Start: ret ret rcall Sub1 Рис. 2.19. Принцип работы трехуровневого стека -74-
Программа G. Счетчик (версия 3.0) Кнопка симулятора Step Over О (F10) используется для выполнения программы без захода в подпрограмму, т.е. сама подпрограмма выполняет- выполняется с высокой скоростью, после чего программа останавливается на следу- следующей строке. Кнопка Step Out Ш (Shift + F11) используется при нахожде- нахождении в теле подпрограммы. После нажатия на эту кнопку программа, вы- выполнив команду возврата, останавливается. Программа G. Счетчик (версия 3.0) • Подавление дребезга контактов на входах • Управление семисегментными индикаторами Поскольку теперь мы знаем, как реализуется таймер, мы можем вер- вернуться к проекту счетчика и усовершенствовать его, введя функцию анти- антидребезга, противодействующую влиянию дребезга контактов. Новая блок-схема показана на Рис. 2.20. Как видно из блок-схемы, нужно просто добавить две идентичные за- задержки до и после секции ReleaseWait программы. Вместо того чтобы дуб- дублировать команды, формирующие задержку, мы можем написать подпро- подпрограмму задержки, которую вызовем 2 раза. Например, если мы назовем эту подпрограмму Debounce, то последние строки нашей новой программы будут выглядеть следующим образом: rcall Debounce ; Вставляем требуемую задержку ReleaseWait: sbis PinD,0 ; Кнопка отпущена? rjmp ReleaseWait ; Нет, остаемся в цикле rcall Debounce ; Вставляем требуемую задержку rjmp Start ; Да, возвращаемся к началу программы Теперь мы можем приступить к написанию подпрограммы Debounce. Лично я предпочитаю размещать тексты подпрограмм в самом начале про- программы после строки rjmp Init, но перед секцией Init. В данном случае мы будем формировать задержку без использования таймера. /pS УПРАЖНЕНИЕ 2.13. Сколько тактов потребуется для формирова- "^^ ния задержки длительностью 0.1 с при использовании резонатора на 4 МГц? Преобразуйте полученное число в шестнадцатеричную систему и разбейте его на байты. Какими должны быть начальные значения рабочих регистров задержки? /pS УПРАЖНЕНИЕ 2.14. Повышенной сложности! Напишите во- ^^ семь строчек, составляющих подпрограмму Debounce. -75-
Глава 2. Основные операции в AT90S1200 и TINY12 Рис. 2.20. Блок-схема, реализующая функцию антидребезга -76- Инициализация Кнопка нажата? Инкрементировать счетчик Счетчик больше 9? Изменить число на индикаторе Подождать 0.1с Кнопка нажата? Подождать 0.1с Обнулить счетчик
Программа Н. Светофор Не забудьте определить три новых регистра, которые вы будете исполь- использовать в подпрограмме. Поскольку регистры R20...R29 используются для хранения кодов семисегментных индикаторов, a R30 и R31 соответствуют регистрам ZH и ZL, вы можете решить, что свободные старшие регистры кончились и необходимо использовать менее универсальные регистры R0...R15. Однако обратите внимание, что в подпрограмме Debounce не ис- используется регистр temp. Поэтому его вполне можно задействовать вместо регистра Delayl. Так что либо определите Delayl как R16 (назначение од- одному и тому же регистру двух различных имен не является ошибкой), ли- либо, чтобы не запутаться, забудьте об имени Delayl и используйте в подпро- подпрограмме вместо него имя temp. Запустите эту программу и убедитесь в от- отсутствии эффекта дребезга контактов. Можете ли вы уменьшить величину задержки? Какой наименьшей задержки будет достаточно для надежной работы устройства? Программа Н. Светофор • Формирование временных интервалов без таймера • Переключение выходов Следующее устройство, которое мы с вами сделаем, — это контроллер светофора. У него будут сигналы для автомобилей (зеленый, желтый и красный), а также сигналы для пешеходов (зеленый и красный) с желтым индикатором «ЖДИТЕ». Кроме того, в контроллере будет кнопка, кото- которую должны будут нажимать пешеходы, намереваясь перейти через дорогу. Операции, связанные с формированием и измерением временных интер- интервалов, потребуются нам в двух случаях. Во-первых, мы будем отслеживать время между нажатиями на кнопку, чтобы выдерживать заданный мини- минимальный интервал между остановками автотранспорта (как и на настоя- настоящем пешеходном переходе). Помимо этого, нам нужно будет контролиро- контролировать длительность включенного состояния сигналов светофора, а также их мигания. Для контроля минимального времени между нажатиями на кнопку (примем его равным 25 с) мы будем использовать Таймер 0, а для других целей мы будем использовать недавно рассмотренный «бестаймер- ный» способ. Принципиальная схема контроллера приведена на Рис. 2.21, а блок-схема — на Рис. 2.22. Секцию Init вы можете написать самостоятельно, не забудьте только включить подтяжку на выводе PD0. Таймер 0 должен работать в режиме внутреннего счета с периодом СК/1024. Первые две команды устанавливают все светодиоды в начальное состо- состояние (включен красный сигнал для пешеходов и зеленый — для автомоби- автомобилей). -77-
Глава 2. Основные операции в AT90S1200 и TINY12 Рис. 2.21. Принципиальная схема контроллера светофора /gS УПРАЖНЕНИЕ 2.15. С помощью каких двух команд можно выпол- ^^ нить эту операцию? Помимо ожидания нажатия на кнопку в начале программы, мы долж- должны дождаться окончания заданного 25-секундного интервала. За это будет отвечать подпрограмма с именем Timer, которую мы напишем чуть позже. Итак, в третьей строке будет располагаться команда: rcall Timer ; Обработаем таймер В этой подпрограмме мы задействуем бит Т регистра SREG — специ- специальный временный бит, который программист может использовать для своих целей. Мы будем использовать его в качестве флага, показывающе- показывающего, прошли или нет требуемые 25 с. При старте программы этот бит сбро- сброшен, а при остановке движения транспорта и соответственно при начале движения пешеходов он устанавливается в 1. Если вызов подпрограммы Timer происходит при установленном бите Т, работа таймера не прерывает- прерывается, однако, вместо того, чтобы оставаться в цикле до окончания заданного интервала времени, микроконтроллер просто выходит из подпрограммы (с использованием команды ret), если заданное время не вышло. После окончания заданного интервала бит Т снова сбрасывается, сигнализируя о том, что можно снова останавливать движение транспорта. После коман- команды вызова подпрограммы мы проверяем состояние кнопки. /gS УПРАЖНЕНИЕ 2.16. С помощью каких двух команд можно про- ^^ верить состояние кнопки и вернуться к метке Start, если кнопка не нажата? -78-
Программа Н. Светофор Инициализация Автомобили: Зеленый Пешеходы: Красный Автомобили: Желтый Пешеходы: Красный Ждать 4 с Автомобили: Красный Пешеходы: Зеленый Ждать 8 с Автомобили: Желтый мигает Пешеходы: Зеленый мигает Ждать 4 с Рис. 2.22. Блок-схема программы контроллера светофора -79- Кнопка нажата? ^ После ^"^ прошлого нажатия w прошло 25 с? ^
Глава 2. Основные операции в AT90S1200 и TINY12 /gS УПРАЖНЕНИЕ 2.17. При нажатии на кнопку должен включиться "^"^ сигнал «ЖДИТЕ» пешеходного светофора. С помощью какой од- одной команды можно это сделать? Для проверки состояния бита Т вы можете использовать одну из следу- следующих команд: brts label ; Переход, если бит Т установлен brts label ; Переход, если бит Т сброшен /gS УПРАЖНЕНИЕ 2.18. Какие две строки образуют новый цикл (на- ^^^ зовем его Loop) и проверяют состояние бита Т в регистре SREG, ос- оставаясь в цикле, пока бит Т сброшен? По истечении требуемого времени мы можем начинать останавливать движение транспорта. Выключим зеленый сигнал светофора и включим желтый. Состояние остальных сигналов при этом не меняется. /gS УПРАЖНЕНИЕ 2.19. С помощью каких двух команд можно вы- ^^ полнить эту операцию? Из блок-схемы видно, что нам необходимо сформировать достаточно много задержек. Поэтому вместо многократного копирования одного и того же фрагмента кода имеет смысл написать подпрограмму задержки. Причем мы можем написать подпрограмму, которая будет формировать наименьшую из требуемых нам задержек @.5 с, используется при мига- мигании), а для формирования задержек большей длительности просто вызы- вызывать ее несколько раз. Назовем эту подпрограмму HalfSecond. Соответ- Соответственно, для формирования 4-секундной задержки эту подпрограмму надо будет вызвать 8 раз. Причем можно просто написать команду rcall HalfSecond 8 раз, а можно сделать цикл: ldi temp,8 ; FourSeconds: rcall HalfSecond dec temp brne FourSeconds В этом фрагменте в регистр temp загружается число 8 и при каждом де- крементировании этого регистра вызывается подпрограмма HalfSecond. После восьми проходов цикл завершается. После 4-секундной задержки необходимо включить красный сигнал автомобильного светофора, а желтый выключить. При этом в пешеходном светофоре должен погаснуть красный свет и загореться зеленый. Сигнал «ЖДИТЕ» тоже должен погаснуть. -80-
Программа Н. Светофор /gS УПРАЖНЕНИЕ 2.20. С помощью каких двух команд можно изме- ^^^ нить состояние выходов указанным образом? /gS УПРАЖНЕНИЕ 2.21. С помощью каких четырех команд можно ^^ сформировать 8-секундную задержку? По истечении восьми секунд красный сигнал автомобильного свето- светофора выключается, а желтые сигналы обоих светофоров должны начать мигать. Сначала просто включим эти сигналы, а затем посмотрим, как же заставить их мигать. /g^ УПРАЖНЕНИЕ 2.22. С помощью каких двух команд можно изме- ^^ нить состояние выходов указанным образом? Для изменения состояния обоих требуемых сигналов мы должны ин- инвертировать биты порта. Это можно сделать двумя способами. Во-первых, мы можем вычислить обратный код числа, находящегося в регистре, с по- помощью команды: com register ; Эта команда инвертирует все биты регистра @ становится 1, а 1 стано- становится 0). @S УПРАЖНЕНИЕ 2.23. Если число в регистре temp равно Ь10110011, ^~* какое число будет в нем после выполнения команды com temp? Однако мы хотим инвертировать биты избирательно. Это можно сде- сделать с помощью логической команды Исключающее ИЛИ (EOR, или XOR). Логические команды берут один или несколько битов (входы) и в зависи- зависимости от их состояния формируют выходной бит (результат логической операции). Таблица, показывающая результат применения более распро- распространенной операции Включающее ИЛИ(ЮК) к двум битам (обычно назы- называемая таблицей истинности), приведена ниже: Входы 0 0 1 1 0 1 0 1 Результат 0 1 1 1 -81-
Глава 2. Основные операции в AT90S1200 и TINY 12 Результат операции равен 1, если первый или второй входной бит ра- равен 1 (или оба бита равны 1). Операция «Исключающее ИЛИ» отличается тем, что в случае, если оба входных бита равны 1, результат равен 0: Входы 0 0 1 1 0 1 0 1 Результат 0 1 1 0 Одним из полезных свойств этой функции является то, что, если вто- второй бит равен 1, первый бит изменяет свое состояние, а если равен 0 — не изменяет (см. таблицу истинности). Благодаря этому можно выборочно инвертировать требуемые биты. Если, например, мы хотим инвертировать 0-й бит в рабочем регистре, мы должны будем выполнить операцию «Ис- «Исключающее ИЛИ» между этим регистром и числом 0ЫЮ000001. Команда, выполняющая операцию «Исключающее ИЛИ», имеет вид: eor regl,reg2 ; Эта команда выполняет операцию «Исключающее ИЛИ» (exclusive OR) между содержимым регистров regl и reg2, сохраняя результат в регист- регистре regl. УПРАЖНЕНИЕ 2.24. С помощью каких четырех команд можно считать состояние сигналов светофора в регистр temp, инвертировать биты 1 и 3, а затем записать temp обратно в регистр PortB (ПОДСКАЗ- (ПОДСКАЗКА: вам понадобится еще один рабочий регистр, назовем его tog)? УПРАЖНЕНИЕ 2.25. Повышенной сложности/ Вставьте коман- команды, написанные вами при выполнении предыдущего упражнения, в цикл, в котором выполняется полусекундная задержка, после чего переключается состояние требуемых сигналов. Цикл выполняется 8 раз. Вам понадобится новый регистр для счета итераций цикла; назовите его Counter, а цикл обозначьте меткой FlashLoop. Длина фрагмента программы, выполняющего описанные действия, долж- должна быть равна восьми строкам. Теперь сигналы светофора можно переключить в исходное состояние, только, прежде чем перейти к метке Start, не забудьте установить бит Т. Для этого используется команда: set ; Установить бит Т -82-
Программа Н. Светофор /gS УПРАЖНЕНИЕ 2.26. Напишите две последние строки про- ^^ граммы. Теперь нам осталось написать две подпрограммы: HalfSecond и Timer. Разберемся сначала с HalfSecond, так как она должна быть гораз- гораздо проще. /gS УПРАЖНЕНИЕ 2.27. Сформируйте полусекундную задержку без >^ использования Таймера 0 и на основе этого фрагмента напишите подпрограмму HalfSecond, длина которой должна быть равна вось- восьми командам. Используется резонатор с частотой 2.4576 МГц. В подпрограмме Timer мы прежде всего должны проверить состояние бита Т. Если он сброшен, то мы просто выходим из подпрограммы, ничего не делая. /gS УПРАЖНЕНИЕ 2.28. Напишите первые две строки подпрограммы ^ Timer. Далее мы можем использовать тот же метод, что и раньше при работе с таймером. Однако вместо возврата к началу секции мы просто будем выхо- выходить из подпрограммы. Требуемое время равно 25 с, что при использова- использовании резонатора 2.4576 МГц и тактовой частоте Таймера 0, равной СК/1024, соответствует значению маркера 240 и счетчика 250 (проверьте!). rgS УПРАЖНЕНИЕ 2.29. Повышенной сложности! Напишите ос- у^ тавшиеся десять строк подпрограммы. Считайте, что регистры счетчика и маркера были инициализированы в секции Init (это действительно надо сделать!). В конце подпрограммы надо будет загружать в регистр счетчика его начальное значение. Не забудьте сбросить бит Т перед выходом из подпрограммы (воспользуйтесь командой clt). Поздравляю! Эту программу вы написали практически самостоятель- самостоятельно. Чтобы проверить себя, сравните написанное с текстом Программы И (Приложение!). Логические элементы Мы уже познакомились с логическими элементами «Включающее ИЛИ» и «Исключающее ИЛИ», а теперь пришла пора познакомиться с элементами остальных типов: AND, NAND, NOR, ENOR, BUFFER, NOT. -83-
Глава 2. Основные операции в AT90S1200 и TINY12 Этим элементам соответствуют следующие таблицы истинности: AND («И») Входы 0 0 1 1 0 1 0 1 Результат 0 0 0 1 Эта операция часто используется для маскирования (игнорирования определенных битов). Если второй бит равен 0, то первый бит маскируется (становится равным 0). Если первый бит равен 1, второй бит остается не- неизменным. Таким образом, выполнение операции AND между регистром и числом ОЬООООПП маскирует биты 4...7 содержимого регистра и остав- оставляет биты 0...3 неизменными. NAND(«H-HE») Входы 0 0 1 1 0 1 0 1 Результат 1 1 1 0 ЖЖ(«ИЛИ-НЕ») Входы 0 0 1 1 0 1 0 1 Результат 1 0 0 0 ENOR («Исключающее ИЛИ-НЕ») Входы 0 0 1 1 0 1 0 1 Результат 1 0 0 1 Результат противоположен результату операции AND Результат противоположен результату операции OR Результат противоположен результату операции EOR -84-
Программа I. Симулятор логических элементов NOT («НЕ») Вход 0 1 Выход 1 0 BUFFER («буфер») Вход 0 1 Выход 0 1 Только один вход, результат на выходе обратен входу Только один вход, выход дублирует вход В системе команд отсутствуют специальные команды для выражения всех этих логических элементов, однако соответствующие операции мож- можно реализовать, комбинируя имеющиеся команды. Программа I. Симулятор логических элементов • Логические выражения • Микроконтроллер TinyAVR Нашей следующей задачей будет создание симулятора логических эле- элементов, который может быть запрограммирован таким образом, чтобы вы- выполнять функции любого из элементов, перечисленных выше. Соответ- Соответственно, нам требуется два входа, один выход, а также три управляющих вывода, посредством которых будет задаваться тип симулируемого эле- элемента. Итого нам потребуется шесть контактов ввода/вывода. Именно та- такое количество выводов имеется у микроконтроллеров AVR семейства Tiny. Конкретно, мы будем писать нашу программу для модели Tiny 12, од- однако ее легко можно будет адаптировать под другие модели микроконт- микроконтроллеров, включая модель 1200, с которой мы работали до сих пор. На Рис. 2.23 приведена цоколевка некоторых моделей семейства Tiny. Рис. 2.23. Цоколевка моделей семейства Tiny -85-
Глава 2. Основные операции в AT90S1200 и TINY12 Отличительной особенностью этого семейства является наличие 6-битного порта В (РВ0...РВ5), причем все шесть контактов ввода/вывода доступны только при определенных условиях. В частности, выводы РВЗ и РВ4 одновременно являются входами тактового генератора, поэтому для использования этих выводов в качестве контактов ввода/вывода необхо- необходимо выбрать встроенный тактовый генератор. При использовании отдельного генератора, для подключения которого требуется только вывод XTAL1, будет доступен вывод РВ4, но не РВЗ. Использование вывода RESET в качестве вывода аппаратного сброса исключает использование линии РВ5. Более того, в моделях TinylO и Tinyll вывод РВ5 может рабо- работать только как вход. А в модели Tiny 12 вывод РВ5 является либо входом, либо выходом с открытым стоком (это означает, что вы можете пере- перевести его в режим выхода, но только с НИЗКИМ уровнем на выходе, т.е. этот вывод может только потреблять ток, а отдавать не может). Поэто- Поэтому, хотя размер регистров PinB и DDRB равен 6 бит, размер регистра PortB составляет всего 5 бит. Соответственно, вывод РВ5 не имеет внутренней подтяжки и требует использования внешнего подтягивающего резистора. Основным преимуществом микроконтроллеров семейства Tiny перед ис- используемой до сих пор моделью 1200 является наличие команды: 1рш Эта команда загружает из памяти программ (load program memory) в ра- рабочий регистр R0 один байт, адрес которого определяется регистром Z. Это означает, что для хранения различных таблиц мы сможем использо- использовать память программ, а не рабочие регистры. Кроме того, при этом полу- получается более эффективный код: поскольку каждая команда занимает в па- памяти программ 16бит, то соответственно вместо одной команды мы мо- можем хранить 2 байта данных. Указанная команда потребуется нам при написании программы для разрабатываемого устройства. Рис. 2.24. Принципиальная схема симулятора -86-
Программа I. Симулятор логических элементов Принципиальная схема симулятора приведена на Рис. 2.24. Обратите внимание, что элементы NOT и BUFFER имеют только один вход, в ка- качестве которого будет использоваться вывод РВ1. Соответственно, ис- используемые таблицы истинности элементов NOT и BUFFER для двух входов будут следующими: NOT Входы 0 0 1 1 BUFFER 0 1 0 1 Входы 0 0 1 1 0 1 0 1 Результат 1 1 0 0 Результат 0 0 1 1 УПРАЖНЕНИЕ 2.30. Попытайтесь самостоятельно нарисовать блок-схему, а потом сравните ее с моей, приведенной в ответах. Для построения этой блок-схемы достаточно будет трех блоков, так как мы пока не затрагиваем вопрос о том, как управлять имитацией эле- элементов конкретных типов. В секции Init выход эмулятора (РВ2) первоначально должен быть вы- выключен. Для выбора типа имитируемого элемента мы воспользуемся дво- двоичным переключателем, выходы которого, в зависимости от положения ключа, могут изменять свое состояние от 000 до 111. Соответственно, в про- программе это значение будет использоваться для определения адреса описа- описания элемента. Хотя на выходе ключа могут присутствовать значения от 000 до 111, числа в регистре PinB будут находиться в диапазоне от ххОООх до xxl 11х (значения битов 0,4 и 5 следует игнорировать). Поэтому мы считаем содержимое регистра PinB и маскируем биты 0, 4 и 5 с помощью команды: andi reg, number ; -87-
Глава 2. Основные операции в AT90S1200 и TINY 12 Эта команда выполняет операцию «И» (AND) между содержимым ра- рабочего регистра reg и константой number (и опять-таки эта команда работа- работает только с регистрами R16...R31). Чтобы маскировать биты 0, 4, 5 и оста- оставить неизменными биты 1...3, мы выполним операцию «И» между регист- регистром и числом ОЬООШО. Затем мы сдвинем результат на один бит вправо, так, чтобы после сдвига в 5-м бите гарантированно был бы 0. qS УПРАЖНЕНИЕ 2.31. Какую из команд сдвига следует использовать ^^ в данном случае? Результат представляет собой число от 0 до 7, которое мы будем ис- использовать для обращения к определенной ячейке памяти программ. Поэ- Поэтому нам нужно будет загрузить содержимое регистра PinB в рабочий ре- регистр ZL, поскольку именно он используется в качестве указателя на вы- выбранный адрес. /gS УПРАЖНЕНИЕ 2.32. Напишите три строки программы, в которых ^^ производится чтение PinB в ZL, маскировка битов 0, 4 и 5, а затем сдвиг результата на один бит вправо. Наша таблица будет располагаться сразу же после команды rjmp Init. Эта команда размещается по адресу 000 памяти программ (поэтому она и выполняется первой). Длина слова команды равна 16 бит, т.е. занимает 2 байта (или одно слово). Соответственно, адресация памяти программ осуществляется по словам, а при побайтной адресации адрес равен удво- удвоенному адресу слова. Это иллюстрируется на Рис. 2.25. Рис. 2.25. Побайтная и пословная адресация Таким образом, наша таблица будет начинаться с адреса 001, что соот- соответствует адресу байта 002. В регистре ZL хранится адрес байта, поэтому мы должны прибавить 2 к ZL, чтобы он указывал на начало таблицы. -88-
Программа I. Симулятор логических элементов rgS УПРАЖНЕНИЕ 2.33. С помощью каких двух команд можно при- ^^ бавить 2 к регистру ZL, а затем считать содержимое памяти про- программ в рабочий регистр R0? Теперь осталось решить, что же должно содержаться в таблице, кото- которая определяет работу устройства в качестве конкретного логического эле- элемента. Немного поразмыслив, я пришел к выводу, что проще всего будет использовать раздельную форму таблицы истинности для каждого симу- симулируемого элемента. То, о чем мы сейчас будем говорить, может показать- показаться далеко не очевидным, однако, разобравшись, вы увидите, что выбран- выбранный способ не так уж и плох. Для описания каждого элемента мы выделим один байт. Возьмем таб- таблицу истинности каждого элемента и посмотрим на набор выходных со- состояний (например, 0001 для элемента AND и 0111 для элемента IOR). За- Затем сформируем новое число, 4-й и 5-й биты которого будут равны двум старшим битам тетрады, а 0-й и 1-й — двум младшим. То есть в случае эле- элемента AND число 0001 разбивается на 00 и 01, итоговое значение равно 00000001. А в случае элемента IOR число 0111 разбивается на 01 и 11, ито- итоговое значение равно 00010011. /gS УПРАЖНЕНИЕ 2.34. Чему равны соответствующие однобайтные JS^ числа для элементов NAND, NOR, ENOR, EOR, NOT и BUFFER? Теперь мы занесем полученные значения в таблицу в произвольном порядке (обратите внимание, что положение этих значений в таблице оп- определяет взаимосвязь между числом на входах РВ1, РВ2 и РВЗ и типом си- симулируемого элемента). В языке ассемблера предусмотрены директивы (команды ассемблеру), позволяющие поместить заданное слово или байт в память программ. Это директивы .dw (define word — определить слово) и .db (define byte — определить байт). При использовании директивы .dw по- полученные ранее однобайтные значения необходимо объединять попарно, например: .dw ObOOOOOOOlOOOlOOll ; Код для AND и I0R ИЛИ .db ObOOOOOOOl,ОЪОООЮОИ ; Код для AND, код для IOR Между двумя строками, написанными выше, есть одно существенное отличие. При использовании директивы .dw младший байт слова распола- располагается по меньшему адресу. К примеру, если бы обе эти строки располага- располагались по адресу слова 00, то при использовании директивы .dw код для эле- элемента IOR хранился бы в байте с адресом 00, а при использовании директи- -89-
Глава 2. Основные операции в AT90S1200 и TINY12 вы .db — с адресом 01. До тех пор, пока вы следите за корректной адресацией байтов, нет никакой разницы, какую из директив использовать. /gS УПРАЖНЕНИЕ 2.35. Заполните три строки таблицы, используя "^ директиву .dw или .db. Таким образом, используя команду 1pm, мы загружаем в регистр R0 представление таблицы истинности выбранного элемента. Затем мы про- проверяем сигнал на входе А элемента (РВ4). Если на выводе НИЗКИЙ уро- уровень, мы переставляем тетрады регистра R0 (т.е. 00000001 преобразуется в 00010000). Этим мы определяем требуемую нам половину таблицы истин- истинности (вспомните: мы ведь изначально разделили каждую таблицу попо- пополам). Перестановка тетрад производится командой: swap reg Затем мы проверяем сигнал на входе В (РВ5). Если на выводе НИЗ- НИЗКИЙ уровень, мы сдвигаем содержимое регистра R0 на один бит вправо. В результате этой операции мы определяем, какое из двух выбранных зна- значений таблицы истинности соответствует текущему состоянию входов. Вот как выглядят эти четыре строки: sbis PinB,4 ; Проверяем вход А swap R0 ; Переставляем тетрады, если О sbis PinB,5 ; Проверяем вход В ror R0 ; Сдвигаем, если 0 Таким образом, бит 0 регистра R0 содержит значение выхода, которое нам необходимо передать на вывод PB0. Однако нам не хотелось бы изме- изменять конфигурацию подтяжки на входах порта, поэтому мы должны запи- записать в регистр PortB число, 0-й бит которого равен этому же биту регистра R0, а остальные биты (РВ1...РВ4) равны 1. Аналогично тому, как операция «И» используется для сброса заданных битов в 0 (маскирования), операция «ИЛИ» используется для установки заданных битов в 1. Например, в на- нашем случае в результате выполнения операции «ИЛИ» между содержимым R0 и константой 0Ь11110 мы получим число, все биты которого, кроме ну- нулевого, будут установлены в 1 (состояние 0-го бита не изменится). После этого мы можем спокойно загружать полученное значение в регистр PortB. Команда, выполняющая операцию «ИЛИ», имеет формат: ori reg,number Эта команда выполняет операцию «ИЛИ» между содержимым регист- регистра reg и константой number, правда, применима она только к регистрам R16...R31. Из-за этого нам необходимо переслать содержимое R0 в регистр temp, используя команду mov. -90-
Программа I. Симулятор логических элементов /gS УПРАЖНЕНИЕ 2.36. С помощью каких четырех команд можно ^^^ скопировать содержимое регистра R0 в регистр temp, установить би- биты 1...4 регистра temp в 1 и переслать его в регистр PortB, после чего вернуться к метке Start? На этом написание программы завершено, полный ее текст вы найдете в Приложении J. SREG — регистр состояния Мы уже знакомы с некоторыми битами регистра SREG (флаг нуля, флаг переноса и бит Т), а теперь пришла пора познакомиться и с осталь- остальными. Каждый из битов можно индивидуально проверить, сбросить или установить командами общего назначения — уже знакомыми нам brbc и brbs, а также командами: bset bit ; Установить бит регистра SREG bclr bit ; Сбросить бит регистра SREG Для каждого бита также предусмотрены индивидуальные команды (на- (наподобие breq и Ьгсс), которые приведены в Приложении С. Назначение битов регистра SREG показано на Рис. 2.26. Если вы хотите узнать, воздействует ли какая-либо команда на опреде- определенный флаг, обратитесь к описанию системы команд (Приложение D). Назначение флагов отрицательного значения, знака и переполнения до- дополнительного кода станет ясным, если вы еще раз обратитесь к разделу книги, посвященному отрицательным двоичным числам. Флаг половин- половинного переноса ведет себя так же, как и флаг обычного переноса, однако связан с младшей тетрадой. Например: НИ 01011010 = 90 + 00001111 = 15 01101001 = 105 Эта операция установит флаг половинного переноса, поскольку про- произошел перенос из 3-го бита. Флаг общего разрешения прерываний будет рассмотрен в главе 4, в разделе, посвященном прерываниям. Сторожевой таймер К полезным особенностям микроконтроллеров AVR можно отнести наличие в них сторожевого таймера (watchdog timer) — встроенного тай- таймера, работающего от независимого генератора с частотой 1 МГц. Этот таймер позволяет периодически, через заданные интервалы времени, -91-
Глава 2. Основные операции в AT90S1200 и TINY12 SREG — регистр состояния ($3F) Бит 7 6 5 4 3 2 10 Название I T H S V N Z С Флаг переноса: Отражает возникновение переноса при выполнении арифметических операций и команд гог и rol Флаг нуля: 0 — результат предыдущей операции не равен нулю 1 — результат предыдущей операции равен нулю Флаг отрицательного значения: 0 — MSB результата равен О 1 — MSB результата равен 1 Флаг переполнения дополнительного кода: 0 — не было переполнения дополнительного кода 1 — было переполнение дополнительного кода Флаг знака (V xor N): 0 — результат положительный 1 — результат отрицательный Флаг половинного переноса: Аналогичен флагу переноса, только для младшей тетрады (т.е. для 4 младших битов) БитТ: Временный бит Флаг глобального разрешения прерываний: Общее разрешение/запрещение прерываний (сбрасывается при возникновении прерывания) Рис. 2.26. Регистр SREG сбрасывать микроконтроллер. Чтобы избежать непреднамеренного сброса микроконтроллера, сторожевой таймер необходимо периодически сбра- сбрасывать (разумеется, раньше, чем он сбросит микроконтроллер). Главным образом, сторожевой таймер используется для повышения надежности программ, так как если программа начнет вести себя непредусмотренным образом, то сторожевой таймер сработает и сбросит микроконтроллер, в результате чего восстановится нормальное функционирование програм- программы. Для сброса сторожевого таймера предназначена команда: wdr ; Эта команда сбрасывает сторожевой таймер (reset the watchdog timer). На языке программистов эта операция называется «бросить кость собаке». Управление сторожевым таймером (или сокращенно WDT) осуществ- осуществляется с помощью регистра ввода/вывода WDTCR (Рис. 2.27). -92-
Программа I. Симулятор логических элементов WDTCR — регистр управления сторожевым таймером ($21) Бит 7 6 5 4 3 2 1 О Название - - - - WDE WDP2 WDP1 WDP0 I I 000 001 010 011 100 101 110 111 15мс 30 мс 60 мс 0.12с 0.24 с 0.49 с 0.97 с 1.9с Разрешение WDT: 0 — сторожевой таймер выключен 1 — сторожевой таймер включен Рис. 2.27. Регистр WDTCR Бит WDE управляет состоянием WDT (включен или выключен), а биты WDP0...2 определяют период между сбросами микроконтроллера (период сторожевого таймера). Имейте в виду, что длительность этого периода за- зависит от температуры и напряжения питания. Значения, приведенные вы- выше, справедливы для напряжения питания +5 В и температуры +25°С. При напряжении питания +3 В эти значения будут примерно в три раза больше. Спящий режим Часто встречаются такие приложения, в которых микроконтроллер на- находится в ждущем режиме до наступления некоторого события. В таком слу- случае имеет смысл перевести микроконтроллер в режим пониженного энерго- энергопотребления, или спящий режим. Выход микроконтроллера из спящего ре- режима может произойти в результате аппаратного сброса, сброса WDT или прерывания (последняя ситуация обсуждается в главе 4). Команда перевода микроконтроллера AVR в спящий режим имеет простой формат: sleep ; -93-
Глава 2. Основные операции в AT90S1200 и TINY12 Разработчики AVR предусмотрели две разновидности спящего режима: «легкая дремота» и «глубокий сон». В первом случае (режим Idle) выполне- выполнение программы приостанавливается, однако таймеры (такие, как Таймер 0) продолжают работать. Во втором случае (режим Power Down) ос- останавливается работа практически всех узлов микроконтроллера, за ис- исключением сторожевого таймера, узла сброса и узла обработки внешнего прерывания INTO. Как использовать спящий режим? Пусть нам необходимо разработать устройство, включающееся при перемещении. Для этого мы будем прове- проверять датчик вибраций и, если он выключен, переходить в спящий режим. Через некоторое время WDT «разбудит» AVR и сбросит его. Проверка дат- датчика вибрации занимает несколько микросекунд, поэтому период WDT можно задать равным 60 мс, в результате чего микроконтроллер будет на- находиться в активном режиме только около одной тысячной всего времени. Когда же сработает датчик вибрации, микроконтроллер пропустит коман- команду sleep и продолжит нормальную работу. После этого WDT необходимо выключить или начать сбрасывать через регулярные интервалы времени командой wdr. Для управления спящим режимом микроконтроллеров AVR использу- используется регистр ввода/вывода MCUCR ($35). Бит 5 регистра разрешает пере- перевод микроконтроллера в спящий режим (этот бит необходимо установить в 1 перед выполнением команды sleep). Бит 4 определяет режим @ — Idle, 1 — Power Down). Остальные команды При разработке учебных проектов мы с вами познакомились с боль- большинством команд моделей 1200 и Tiny. Теперь рассмотрим остальные: neg reg ; Эта команда делает содержимое регистра reg отрицательным (т.е. вы- вычисляет дополнительный код числа). пор ; Это пустая команда, означающая «нет операции», другими словами, это команда, которая ничего не делает. На нее просто расходуется один такт. А еще мы не рассмотрели несколько команд, выполняющих логичес- логические и арифметические операции между двумя регистрами: and regl,reg2 ; regl = regl AND reg2 or regl,reg2 ; regl = regl OR reg2 add regl,reg2 ; regl = regl + reg2 adc regl,reg2 ; regl = regl + reg2 + С sub regl,reg2 ; regl = regl - reg2 sbc regl,reg2 ; regl = regl - reg2 - С -94-
Программа J. Частотомер Имеются также специальные команды для работы с битом Т регистра SREG: bst reg,bit ; Сохранить бит bit регистра reg в бит Т bid reg,bit ; Загрузить бит Т в бит bit регистра reg Кроме того, в системе команд имеются две дополнительные команды сравнения. Первая из них: cpse regl;reg2 ; Эта команда сравнивает содержимое двух регистров общего назначе- назначения и пропускает следующую команду, если они равны. Вторая команда (срс) работает аналогично команде ср, которая, как вы должны помнить, в действительности выполняет команду sub между регистрами, не меняя их содержимого. А команда срс выполняет команду sbc между регистрами, не меняя их содержимого. Изменение флагов регистра SREG (флаг нуля, пе- переноса и др.) происходит точно так же, как и в случае команд sub и sbc: срс regl,reg2 ; Сравнить regl с reg2, учитывая флаг С И наконец, две команды, предназначенные для проверки состояния отдельных битов рабочего регистра: srbc reg,bit ; Проверить бит в регистре и пропустить ; следующую команду, если он сброшен srbs reg,bit ; Проверить бит в регистре и пропустить ; следующую команду, если он установлен Программа J. Частотомер • Управление несколькими семисегментными индикаторами • Формирование временных интервалов + счет • Сторожевой таймер Изучение главы мы завершим разработкой довольно сложного уст- устройства. Этот проект охватывает основные вопросы, рассмотренные в данной главе. Мы разработаем частотомер, предназначенный для диапазо- диапазона 1 Гц...999 кГц. Частота будет отображаться на трех семисегментных ин- индикаторах, показывающих значение в герцах, если частота менее 1 кГц, и в килогерцах — в противном случае. Для индикации единиц измерения бу- будут использоваться отдельные светодиоды. Кроме того, устройство будет находиться в рабочем режиме только в том случае, если частота входного сигнала превышает 1 Гц; при пропадании сигнала устройство будет пере- переходить в спящий режим. Принципиальная схема частотомера приведена на Рис. 2.28. -95-
Глава 2. Основные операции в AT90S1200 и TINY 12 Рис. 2.28. Принципиальная схема частотомера Поскольку мы будем управлять семисегментными индикаторами дина- динамически (т.е. используя стробирование), каждый индикатор будет включен только в течение 1/3 всего времени. Чтобы средний ток через каждый сег- сегмент индикатора был равен току при постоянном включении, мы должны уменьшить сопротивление последовательных резисторов в 3 раза. При на- напряжении питания, равном +5 В, и падении напряжения на СИД, равном 2 В, падение напряжения на резисторе будет составлять 3 В. Чтобы при ста- статическом управлении индикаторами ток через сегмент был равен 10 мА, со- сопротивление каждого резистора должно быть равно 300 Ом. Соответствен- Соответственно, для этого проекта я выбрал сопротивление 100 Ом. Существует два основных способа измерения частоты. Для сигналов с высокой частотой лучше всего выбрать фиксированный интервал времени и подсчитать число колебаний входного сигнала в течение этого интерва- интервала. Для получения численного значения частоты это число колебаний можно будет масштабировать. В случае сигналов низкой частоты данный метод становится слишком неточным, поэтому вместо описанного метода -96-
Программа J. Частотомер используется измерение интервала времени между нарастающими фрон- фронтами входного сигнала. Программа должна будет определять, в каком диа- диапазоне лежит входной сигнал и соответственно какой метод измерения ис- использовать. В нашем распоряжении имеется всего один таймер/счетчик, что до- довольно неудобно, но не смертельно. Для высокочастотных сигналов необ- необходимо использовать Т/СО в режиме счета внешних импульсов, так как вручную будет трудно обеспечить надежную проверку состояния входа. Для низкочастотных сигналов будет проще проверять состояние входа не- непосредственно, а временной интервал измерять с помощью таймера. Это будет довольно длинная программа, однако мы уже рассмотрели все ос- основные вопросы, которые необходимы для построения блок-схемы, пока- показанной на Рис. 2.29. Проверка на наличие сигнала высокой частоты занимает меньше всего времени F4 мс), поэтому она производится в первую очередь. Если изме- измеренная частота меньше I кГц, программа перейдет к секции проверки на- наличия сигнала низкой частоты. Проверка на наличие сигнала высокой частоты состоит в формировании задержки, равной 64 мс (без использова- использования Т/СО), и подсчете количества импульсов, поступивших за это время на вход таймера Т/СО. Единственная проблема заключается в том, что сигна- сигналу с максимальной частотой A МГц) будет соответствовать 64 000 тактов, что намного больше 256. Поэтому нам необходимо следить за состоянием Т/СО и в случае его переполнения инкрементировать собственный регистр счетчика, который будет работать как старший байт Т/СО. Теперь вам должно быть понятно, почему я выбрал интервал, равный 64 мс. Наибольшее число, которое можно сохранить в двух регистрах, равно OxFFFF, или 65 535, так что число 64 000 довольно близко к максимально- максимальному значению. К тому же для последующего преобразования количества от- отсчетов в значение частоты, выраженное в кГц, нам нужно будет разделить это число на 64. Деление на числа вида 2п выполняется очень легко — мы просто сдвигаем исходное число на п битов вправо (можете проверить это на бумаге). Поэтому значение в 64 мс является идеальным выбором. Для проверки наличия сигнала низкой частоты мы переводим Т/СО в режим таймера. Затем мы ждем изменения сигнала на входе, после чего за- запускаем отсчет времени. Отсчет прекращается после последующего дву- двукратного изменения сигнала на входе (полученное значение соответствует периоду входного сигнала). И опять же при частоте сигнала, равной 1 Гц, и тактовой частоте Т/СО, равной 4 МГц, количество отсчетов таймера/счет- таймера/счетчика будет равно четырем миллионам, из чего следует, что для хранения этого числа нам потребуется уже три регистра. Если получившееся число окажется больше, чем могут вместить в себя три регистра, значит, частота входного сигнала меньше 1 Гц, и мы должны перевести AVR в спящий -97-
Глава 2. Основные операции в AT90S1200 и TINY12 ИЗМЕРЕНИЕ ВЫСОКОЙ ЧАСТОТЫ Инициализация Инициализация дляВЧ Разделить количество отсчетов на 64, чтобы получить частоту в кГц НЕТ Преобразовать число в 3 цифры ИЗМЕРЕНИЕ НИЗКОЙ ЧАСТОТЫ Инициализация дляНЧ Ждать изменения состояния PD4 Обновить дисплей Инкрементировать старший байт(ы) Выключить дисплей и перейти в спящий режим Преобразовать число в 3 цифры Рис. 2.29. Блок-схема программы «Частотомер» -98- Прошло 64 мс? Переполнение \Т/С0? .> Инкрементировать старший байт Частота > 1 МГц? Загрузить в регистры дисплея «-Hi» Частота < 1 кГц? Прошло 0.5 с? Обновить дисплей -^Состояние^Х PD4 изменилось ^дважды? .^ Переполнение «/Переполнение^^ ч^амого старшего^ ^^байта?/^ Частота > 1 кГц?
Программа J. Частотомер режим. Сторожевой таймер надо будет сконфигурировать таким образом, чтобы он «будил» микроконтроллер каждые 1024 мс (т.е. примерно один раз в секунду); заметьте, однако, что при нормальном режиме работы WDT необходимо будет периодически сбрасывать. В секции Init следует сконфигурировать порты, отключив подтяжку на входе. Также необходимо установить период сторожевого таймера, равный 1024 мс, и сконфигурировать MCUCR для разрешения режима Power Down. Теперь нам нужно аккуратно написать основной цикл, в котором будет осуществляться измерение времени, — это наиболее важная часть про- программы. Мы можем предположить, что длительность итерации цикла бу- будет находиться в диапазоне от 4 до 10 тактов, так что для формирования интервала длительностью 64 мс B56 000 тактов) начальное значение счет- счетчика будет составлять от 64 000 до 25 600. Соответственно можно предпо- предположить, что для отсчета времени хватит двух счетных регистров (Delayl и Delay2), однако, чтобы убедиться в этом воочию, надо полностью написать код цикла. Перед входом в цикл нужно будет инициализировать регистры задержки (пока мы не знаем чисел, которые надо будет записать в эти ре- регистры, так как они зависят от длительности цикла), задать режим работы Т/СО и сбросить его в 0. Также нужно будет записать в порт В число 0Ь10000000, чтобы включить СИД «кГц» и очистить дисплей. Кроме того, необходимо предусмотреть строку, где будет сбрасываться регистр upperbyte, назначение которого мы рассмотрим чуть позже. ldi Delayl,?? ldi Delay2,?? ldi temp,0b00000011 ; Сконфигурируем Т/СО для счета по out TCCRO,temp ; нарастающему фронту на ТО (PD4) ldi temp,0bl0000000 ; Выключаем все индикаторы и out PortB,temp ; включаем СИД «кГц» clr upperbyte ; Сбрасываем программный счетчик clr temp ; Сбрасываем Таймер 0 out TCNTO,temp Собственно цикл начинается с привычной операции декрементирова- ния 2-байтного числа, хранящегося в регистрах задержки, и выхода из цикла по истечении заданного времени: Highspeed: subi Delayl, 1 ; Декрементируем Delayl sbci Delay2,0 ; Декрементируем Delay2 при заеме brcs DoneHi ; Если время вышло, выходим из цикла После этого нам нужно будет проверить, не произошло ли переполне- переполнения Т/СО. Это можно сделать двумя способами. Проще всего проверить флаг переполнения таймера, который, в отличие от других флагов, встре- -99-
Глава 2. Основные операции в AT90S1200 и TINY12 чавшихся нам до сих пор, находится в регистре ввода/вывода TIFR. К со- сожалению, мы не можем непосредственно проверить состояние этого фла- флага, используя команды sbic или sbis, поскольку регистр TIFR находится по адресу 0x38, что больше 0x1 Е Соответственно, чтобы проверить бит, нам придется переписать содержимое TIFR в рабочий регистр. Еще больше раздражает тот факт, что надо будет сбрасывать этот флаг, записывая в него 1. И опять-таки, мы не сможем использовать команду sbi и вынужде- вынуждены будем выполнять эту операцию с использованием рабочего регистра. В целом на это потребуется пять команд, однако существует и другой спо- способ, при котором потребуется всего четыре команды. Этот способ основан на сохранении текущего значения Т/СО и сравнении его со значением Т/СО, которое было считано в предыдущем проходе цикла. Мы с полным правом можем ожидать, что текущее значение всегда будет больше преды- предыдущего, если только не было переполнения. Таким образом, мы можем обнаружить факт переполнения, просто сравнивая старое и новое значе- значение, причем нам не потребуется сбрасывать какие-либо флаги. В следую- следующем фрагменте для сохранения нового значения используется регистр temp, а для хранения старого значения — регистр temp2: mov temp2,temp ; Копируем temp в temp2 (старое значение) in temp,TCNT0 ; Сохраняем новое значение в temp ср temp/temp2 ; Сравниваем старое и новое значение brsh Highspeed ; Возвращаемся к началу цикла, ; если новое «больше или равно» Таким образом, цикл Highspeed состоит из семи команд, при этом каж- каждая итерация цикла выполняется за восемь тактов при отсутствии пере- переполнения Т/СО (не забывайте, что команда перехода выполняется за два такта). Теперь нам нужно всего-навсего написать аналогичный фрагмент, в котором мы будем инкрементировать старший байт, проверяя его при этом на максимальное значение, декрементировать наши счетные регист- регистры, выходя из цикла, если они станут равными 0, и возвращаться к метке Highspeed, и все это должно выполняться за то же число тактов. Вы- Выполнение последнего условия очень важно для достижения наибольшей точности измерения. К счастью, мы можем выполнить это условие, у нас даже есть один такт в запасе! Соответственно, для использования этого такта мы применим команду пор. Максимально допустимое число импуль- импульсов входного сигнала, поступающих в течение 64 мс, равно 63 999 (т.е. 1 МГц F4 000) уже слишком высокая частота; 64 000 равно OxFAOO, что очень удобно, так как мы можем просто проверять, не равен ли старший байт OxFA). Если это так, мы выходим из цикла: inc upperbyte ; Инкрементируем старший байт cpi upperbyte,OxFA ; Слишком высокая частота? breq TooHigh ; Выходим из цикла, если так - 100 -
Программа J. Частотомер subi Delayl,l ; Декрементируем счетные регистры sbci Delay2,0 brcs DoneHi ; Выходим из цикла, если время вышло пор ; Выравниваем на 1 такт rjmp Highspeed ; Возвращаемся на начало цикла Сейчас вы можете воскликнуть: «Погодите-ка, приведенный фрагмент выполняется за девять тактов, а не за восемь!» Разумеется, вы правы, од- однако обратите внимание на фрагмент предыдущей секции, который вы- выполняется, если программа не возвращается к началу цикла Highspeed. Если программа не возвращается к началу цикла, переход не выполня- выполняется , поэтому фрагмент выполняется на один такт быстрее. А в приведен- приведенном выше цикле мы компенсируем «недостачу» этого такта. Таким обра- образом, при выполнении всей секции счетные регистры будут декрементиро- ваться однократно каждые 8 тактов или дважды каждые 16 тактов. Было бы неплохо, если бы вы написали этот цикл и внимательно исследовали его работу, чтобы убедиться в этом. Поскольку теперь мы знаем, что декремен- тирование регистров задержки выполняется каждые 8 тактов, мы можем вычислить значения, загружаемые в эти регистры при инициализации, для формирования задержки длительностью 64 мс. qS УПРАЖНЕНИЕ 2.37. Какие значения должны загружаться в ""^ регистры Delayl и Delay2 при инициализации? На этом наиболее сложная часть секции заканчивается. Теперь нам нужно просто сохранить текущее значение Т/СО. Единственное, в чем сле- следует убедиться, — не было ли переполнения с момента последней провер- проверки. Для этого воспользуемся такой же проверкой, как и раньше. Off УПРАЖНЕНИЕ 2.38. Напишите шесть строк, составляющих сек- ^^ цию DoneHi, в которой значение Т/СО сохраняется в регистре lowerbyte и сравнивается с регистром temp (где хранится старое зна- значение Т/СО). Если lowerbyte больше или равен temp, программа пе- переходит к секции Divide64. В противном случае инкрементируется регистр upperbyte, который затем проверяется на равенство числу OxFA. Если результат проверки положительный, то выполняется пе- переход к секции TooHigh. В следующей секции производится деление 2-байтного числа, храня- хранящегося в регистрах lowerbyte и upperbyte, на число 64 = 26. Мы выполним эту операцию, сдвинув исходное число вправо на шесть битов. Чтобы стар- старший байт переходил в младший, мы будем сначала сдвигать старший байт, заполняя 7-й бит нулем, а затем сдвигать младший байт, занося в 7-й бит флаг переноса. -101-
Глава 2. Основные операции в AT90S1200 и TINY12 УПРАЖНЕНИЕ 2.39. Какие две команды выполняют деление 2-байтного числа на 2? В цикле Divide64 эта операция выполняется 6 раз. Сначала мы загружа- загружаем в temp число 6, а затем делим исходное значение на 2, как описано вы- выше. После этого мы декрементируем temp и возвращаемся к началу цикла, если содержимое регистра не равно 0. В последующих итерациях нам со- совершенно не нужно заново инициализировать temp, поэтому в действи- действительности хотелось бы не просто перейти к метке Divide64, а еще и пропус- пропустить одну команду. Это можно сделать с помощью простого приема: rjmp Divide64+1 ; Перейти к Divide64 и пропустить ; одну команду Этот прием работает со всеми командами ветвления и с любым числом пропускаемых команд. Замечу, что большое число пропускаемых команд (например, 8) ведет к появлению неуклюжего кода, который тяжело со- сопровождать и в котором легко сделать ошибку. УПРАЖНЕНИЕ 2.40. Какие пять команд составляют секцию Divide64? Затем мы проверяем, не слишком ли мало полученное значение. В двух байтах хранится значение частоты в кГц, поэтому, если это число меньше 1 (т.е. 0), мы переходим к секции измерения низкочастотных сиг- сигналов. /gS УПРАЖНЕНИЕ 2.41. С помощью каких четырех команд можно ^^ проверить оба байта на равенство нулю и перейти к метке LowSpeed, если это условие выполняется? Теперь нам необходимо преобразовать 2-байтное число в трехзначное десятичное число, т.е. получить три цифры (сотни, десятки и единицы), которые можно будет легко отобразить. Для выполнения преобразования напишем подпрограмму, поскольку эту же операцию нам придется выпол- выполнять в секции LowSpeed. Назовем эту подпрограмму преобразования DigitConvert. Поскольку индикаторы управляются динамически, необхо- необходимо вызывать подпрограмму вывода на дисплей через определенные про- промежутки времени. К сожалению, разработанный нами цикл формирова- формирования задержек нельзя приспособить для периодического вызова этой под- подпрограммы, поскольку в таком случае потребуется большое количество тактов и нарушатся временные соотношения. Процесс измерения частоты занимает всего 64 мс, поэтому сразу же возникает мысль включать индика- индикаторы на полсекунды после окончания этого процесса. -102-
Программа J. Частотомер Мы будем формировать полусекундную задержку самым простым спо- способом (используя для счета рабочие регистры) и одновременно вызывать в этом цикле задержки подпрограмму Display. faS УПРАЖНЕНИЕ 2.42. Напишите фрагмент из девяти команд, в ко- ^^ тором сначала инициализируются три регистра задержки, а затем выполняется цикл полусекундной задержки, во время которого вы- вызывается подпрограмма Display. По истечении заданного интервала времени программа должна переходить к метке Start. При выполне- выполнении расчетов вы должны учесть время выполнения подпрограммы Display. Команда rcall выполняется за три такта, а команда ret — за четыре. В среднем в подпрограмме будет выполняться две команды, поэтому можно считать, что выполнение подпрограммы увеличит длительность итерации цикла на девять тактов. Обозначьте цикл за- задержки меткой HalfSecond. Все, что нам осталось сделать для завершения блока измерения высо- высокочастотных сигналов, — это разобраться с секцией TooHigh, которая просто выводит на дисплей буквы «-HI». Числа, соответствующие симво- символам, которые отображаются на дисплее, будут сохранены в регистрах Hundreds, Tens и Ones. Для их отображения будет использоваться такая же таблица, как и раньше, за исключением того, что в новой таблице числу 10 будет соответствовать символ «Н», а числу 11 — символ «-». Числу 12 будет соответствовать символ пробела (все сегменты индикатора выключены). Соответственно, в секции Init надо будет записать это число во все регист- регистры дисплея. Таким образом, мы должны просто загрузить 11 в Hundreds, 10 в Tens и 1 в Ones (так как цифра 1 похожа на символ «I»), а остальное сдела- сделает подпрограмма Display. После вызова подпрограммы мы выполним пере- переход к строке, расположенной на три команды раньше метки HalfSecond (в этих трех строках инициализируются счетные регистры). qS УПРАЖНЕНИЕ 2.43. Из каких четырех команд состоит секция ^ TooHigh? Вот мы и закончили написание блока, предназначенного для измере- измерения сигналов высокой частоты. Таким образом, наша программа уже на- наполовину готова. Теперь давайте взглянем на подпрограмму DigitConvert. Эта подпрограм- подпрограмма берет 2-байтное число, хранящееся в регистрах upperbyte и lowerbyte, и определяет в нем количество сотен, десятков и единиц. Это преобразование осуществляется следующим образом. Берется исходное 2-байтное число и из него многократно вычитается число 100, до тех пор, пока не произойдет перенос. После этого остаток увеличивается на 100, и над ним выполняются -103-
Глава 2. Основные операции в AT90S1200 и TINY12 те же операции, только с числом 10. Значение, оставшееся после всех этих манипуляций в младшем байте, равно числу единиц, и его достаточно прос- просто сохранить. Более того, после определения сотен у нас нет необходимости использовать в вычислениях старший байт исходного числа, так как мы зна- знаем, что после этой операции число полностью размещается в младшем байте (если число меньше 100, оно занимает один байт). DigitConvert: clr Hundreds ; Очищаем регистры clr Tens ; clr Ones ; FindHundreds: subi lowerbyte,100 ; Вычитаем 100 из младшего байта sbci upperbyte,0 ; Вычитаем 1 при переносе brcs FindTens ; При переносе переходим к ; вычислению десятков inc Hundreds ; Инкрементируем число сотен rjmp FindHundreds ; Повторяем FindTens: subi lowerbyte,-100 ; Прибавляем последние 100 subi lowerbyte,10 ; Вычитаем 10 из младшего байта brcs FindOnes ; При переносе переходим к ; вычислению единиц inc Tens ; Инкрементируем число десятков rjmp FindTens+1 ; Повторяем FindOnes: subi lowerbyte,-10 ; Прибавляем последние 10 mov ones,lowerbyte ; Сохраняем число единиц ret ; Выходим из подпрограммы Вы можете протестировать эту подпрограмму, обработав какое-нибудь контрольное число (например, убедитесь, что для числа 329 получится три сотни, два десятка и девять единиц). Другой подпрограммой, которую мы используем, является подпро- подпрограмма Display. Она определяет, какой из трех индикаторов следует вклю- включить, считывает соответствующее число из регистра Hundred, Tens или Ones и отображает его. При формировании полусекундной задержки подпро- подпрограмма вызывается приблизительно через каждые 4 мс. Мы не можем из- изменять состояние индикаторов так часто, так как этого времени недоста- недостаточно для полного включения СИД и при столь частом переключении изображение на дисплее будет тусклым и «смазанным» (символы с одного индикатора будут отображаться на соседних). Поэтому увеличим этот ин- интервал в 50 раз — т.е. в течение 49 вызовов подпрограмма возвращается, ничего не делая, а при 50-м вызове подпрограмма выполняется полностью. В этом случае обновление дисплея будет происходить каждые 0.2 мс, что — 104 —
Программа J. Частотомер гораздо лучше. Однако, если вы заметите какие-либо из указанных ранее эффектов, масштабный коэффициент E0) надо будет увеличить. Для решения описанной задачи мы будем использовать регистр DisplayCounter. В секции Init в него будет загружаться число 50. Соответ- Соответственно, в самом начале подпрограммы Display регистр DisplayCounter де- крементируется и, если результат не равен нулю, выполняется возврат из подпрограммы. В противном случае в DisplayCounter необходимо снова загрузить число 50. Кроме того, в этот момент мы можем сбросить сторо- сторожевой таймер. Это необходимо делать периодически, а подпрограмма Display вызывается регулярно, независимо от того, какая ветвь программы выполняется (говоря «регулярно», я имею в виду, что вызов производится не реже одного раза в секунду). Поэтому само собой напрашивается ре- решение сбрасывать сторожевой таймер при выполнении подпрограммы Display. sgS УПРАЖНЕНИЕ 2.44. Напишите пять срок, с которых начинается ^^ подпрограмма Display. Мы должны каким-то образом определять, с каким из индикаторов следует работать при конкретном вызове подпрограммы, для чего будем хранить в регистре DisplayNumber номер индикатора (от 0 до 2). Соответ- Соответственно, первым делом мы инкрементируем DisplayNumber и сбрасываем его в нуль, если он становится равным 3 (также его надо будет сбрасывать и в секции Init). /g^ УПРАЖНЕНИЕ 2.45. Напишите следующие четыре строки под- ^^^ программы, выполняющие описанные действия. В следующем фрагменте мы будем активно пользоваться косвенной адресацией. Сначала мы считаем цифру, которую необходимо отобразить, из одного из регистров Hundreds, Tens или Ones (эти регистры вы должны объявить в самом начале программы). Лично я присвоил эти имена регист- регистрам R26, R27 и R28 соответственно. Инициализируем ZL таким образом, чтобы он указывал на R26 (запишем в ZL число 26), а затем прибавим к не- нему число, находящееся в регистре DisplayNumber. В результате мы укажем на одно из трех чисел, которые нам нужно отобразить. Используя команду Id, мы загрузим это число в регистр temp. Поскольку коды семисегментно- го индикатора хранятся в регистрах R0...R12, нам необходимо обнулить ZL, чтобы он стал указывать на R0. Прибавляя к регистру ZL число, нахо- находящееся в регистре temp, мы установим указатель на код семисегментного индикатора, соответствующего отображаемой цифре. После этого загру- загрузим код в регистр temp. Мы не должны сбрасывать 7-й бит регистра PortB, если он установлен (включен СИД «кГц»). В этом случае мы выполним - 105 -
Глава 2. Основные операции в AT90S1200 и TINY12 операцию «ИЛИ» содержимого temp с числом 0Ы0000000. В любом случае содержимое temp необходимо будет выдать в порт В. rgS УПРАЖНЕНИЕ 2.46. Напишите девять строк, в результате вы- ^^ пол нения которых требуемый код семисегментного индикатора вы- выводится в порт В. Прежде чем выйти из подпрограммы, мы должны включить требуемый индикатор. Вспомним основной принцип стробирования: число, выдавае- выдаваемое в порт В, поступает на все индикаторы, но за счет того, что включает- включается только один из них, появляется именно на нем. В данном случае нам нужно будет поочередно устанавливать биты 0, 1, 2 и опять 0 регистра PortD. Для выполнения этой операции проще всего считать содержимое PortD в регистр temp, сдвинуть его влево командой lsl, проверить, не уста- установлен ли бит 3 (т.е. зашли слишком далеко) и, если это так, загрузить в него число ObOOOOOOOl. rgS УПРАЖНЕНИЕ 2.47. С помощью каких шести команд можно ^^ включить требуемый индикатор, а затем выйти из подпрограммы? Теперь осталось разобраться с блоком программы, в котором произво- производится измерение сигналов низкой частоты. Прежде всего нам нужно скон- сконфигурировать Т/СО так, чтобы счет выполнялся по каждому такту (это обеспечит нам максимальное разрешение). Кроме того, нам нужно очис- очистить регистры задержки Delay2 и Delay3, а также сбросить РВ7 для включе- включения СИД «Гц». rgS УПРАЖНЕНИЕ 2.48. Напишите первые пять строк секции ^^ LowSpeed. Нам нужно будет каким-то образом определить момент изменения со- состояния входа PD4 (поскольку таймер Т/СО работает в режиме внутренне- внутреннего счета, мы должны проверять состояние входа самостоятельно). В нашем распоряжении имеется несколько способов, один из которых состоит в следующем. Запомним начальное значение регистра PinD, а затем войдем в цикл, в котором будем считывать текущее значение PinD и выполнять операцию «Исключающее ИЛИ» между ним и начальным значением. Эта логическая операция позволит нам выделить несовпадающие биты. г-т[ ПРИМЕР 2.9 1—' ObOOOllOOl EORObl0001001 OblOOlOOOO <- 7-й и 4-й биты операндов различаются —106 —
Программа J. Частотомер Нас интересует только 4-й бит (PD4), соответствующий входу частото- частотомера, поэтому после выполнения операции «Исключающее ИЛИ» мы мо- можем проверить 4-й бит результата и вернуться к началу цикла, если этот бит не равен 1. Находясь в любом цикле большой длительности (а время выполнения данного цикла может быть довольно большим), мы должны также вызывать подпрограмму Display. in store,PinD ; Запоминаем начальное значение FirstChange: rcall Display ; Обработаем дисплей in store2,PinD ; Считываем текущее значение eor store2,store ; Сравниваем начальное и текущее значения sbrs store2,4 ; Выходим из цикла, если PD4 изменился rjmp FirstChange ; Остаемся в цикле, пока PD4 не изменится В основном цикле блока измерения низкочастотного сигнала произво- производится двойная проверка изменения входного сигнала (т.е. мы ждем полного периода колебаний входного сигнала) и инкрементирование старших бай- байтов при переполнении Т/СО. Обрабатывать переполнение Т/СО мы будем так же, как и раньше, но с одним существенным отличием: мы не можем использовать регистр temp для хранения старого значения, поскольку этот регистр постоянно используется в подпрограмме Display. Очень важно от- отслеживать появление подобных ситуаций, поскольку они могут стать ис- источником многих проблем — старайтесь по возможности использовать ра- рабочие регистры локально (т.е. не ждите, что значения будут храниться в них очень долго). При таком подходе вы сможете использовать регистры, подобные temp, в любом месте программы. В данном случае вместо регист- регистра temp мы можем воспользоваться регистром Delay 1; таким образом, к концу цикла в регистре Delayl будет находиться текущее состояние тайме- таймера Т/СО. Перед входом в основной цикл нам необходимо сбросить Delayl и Т/СО. Также нам потребуется счетчик, чтобы сосчитать число изменений входного сигнала. Нам нужно отследить, когда сигнал изменится дважды, поэтому определим регистр Counter и занесем в него число 2. УПРАЖНЕНИЕ 2.49. Напишите три команды, предшествующие циклу. Далее в цикле проверяется состояние входа (точно так же, как мы дела- делали это раньше), и при его изменении выполняется переход к секции Change. gpS УПРАЖНЕНИЕ 2.50. Напишите пять строк, выполняющих эту "^^ проверку (ПОДСКАЗКА: первая строка должна находиться вне цикла; пометьте цикл меткой LowLoop). -107-
Глава 2. Основные операции в AT90S1200 и TINY12 После этого мы вызываем подпрограмму Display (так как это необходимо делать регулярно), а затем проверяем, не было ли переполнения Т/СО. Если не было, возвращаемся к началу цикла LowLoop. Если же переполнение про- произошло, то мы инкрементируем регистр Delayi, а если и он переполнился — то Delay3. Минимальная измеряемая частота равна 1 Гц, поэтому наиболь- наибольшая длительность периода составляет около 4 000 000 отсчетов, что соот- соответствует числу 0x3D0900 в шестнадцатеричной системе. Следовательно, ес- если содержимое регистра Delay3 станет равным 0x3 Е, мы будем знать, что частота входного сигнала слишком мала, и перейдем к секции TooSlow. ggf УПРАЖНЕНИЕ 2.51. Повышенной сложности! Какие 7/строк ^^ являются последними в блоке измерения сигналов низкой частоты? В секции Change следует декрементировать Counter и вернуться к метке LowLoop, если результат декрементирования не равен нулю. После второго изменения сигнала возвращаться не требуется. Вместо этого мы должны убедиться, что полученное значение слишком мало для того, чтобы потре- потребовался переход к блоку измерения сигналов высокой частоты. Макси- Максимальная частота сигнала, которую можно измерить используемым в дан- данный момент способом, равна 999 Гц, что соответствует 4004 тактам. Поэто- Поэтому если результат равен 4000 (OxFA) или меньше, то мы должны перейти к метке Start и приступить к измерению частоты входного сигнала другим способом. Наверное, вам не очень понятно, как можно убедиться в том, что число, разбитое на три регистра, меньше 0x000FA0. Начнем с того, что мы не можем использовать вычитание, так как это изменит содержимое регистров задержки. Вместо этого мы воспользуемся командами сравне- сравнения, применявшимися нами для сравнения однобайтных значений, сов- совместно с командой срс, которая сравнивает содержимое двух регистров, учитывая при этом состояние флага переноса. Эта операция аналогична вычитанию с переносом (sbci), но без изменения содержимого регистров. Единственным неудобством является то, что команда срс работает с двумя регистрами, а не с регистром и константой, так что нам придется загрузить числа во временные регистры. Таким образом, текст секции Change будет следующим: Change: in store,PortB ; Считываем новое значение порта В dec Counter ; Ждем второго изменения brne LowLoop ; Это не второе изменение, остаемся в цикле ldi temp,0x0F ; Загружаем временные регистры ldi temp2,0x00 cpi Delayi,OxAO ; Сравниваем 3-байтное число срс Delay2,temp ; с 0x000FA0 срс Delay3,temp2 -108-
Программа J. Частотомер brcc PC+2 ; Меньше OxFAO, переходим к измерению rjmp Start ; сигналов высокой частоты Из текста видно, что вместо ожидаемой строки (brcs Start) — перехода к метке Start при установленном флаге переноса — мы решили пропустить строку rjmp Start при сброшенном флаге переноса. Оба эти способа приво- приводят к одному и тому же результату, так зачем же мы написали лишнюю строчку? Причина заключается в том, что команда brcs может переходить только в пределах 64 команд. Однако метка Start находится намного даль- дальше, поэтому для перехода к ней мы вынуждены использовать команду rjmp. Подобные места в программе будут обнаружены при ее ассемблиро- ассемблировании, и обычно эти моменты пропускаются во время написания програм- программы, так что вам не нужно отсчитывать 60 дополнительных строчек при вставке в текст команды brcs или ей подобной. Теперь мы преобразуем период, выраженный в тактах, в частоту. Для этого нужно взять число 4 000 000 и разделить его на длительность периода (в тактах), которую мы только что измерили. Если мы намерили 40 000 тактов в периоде, значит, частота сигнала равна 100 Гц. Существует мно- множество способов выполнения двоичного деления больших чисел, однако самый простой способ деления числа х на число у — определить, сколько раз можно вычесть у из х. Для реализации этого способа потребуется мень- меньше команд, но сами вычисления займут больше времени. Мы разместим число 4 000 000 = 0x3D0900 в трех регистрах — temp, temp2 и temp3. При каждом успешном вычитании числа, находящегося в регистрах Delay 1, Delay2 и Delay3, мы будем инкрементировать младший байт ответа. При его переполнении мы будем инкрементировать старший байт. Результат будет находиться в диапазоне от 1 до 1000, так что для его хранения нам понадобится всего два байта. В следующем фрагменте производится ини- инициализация процесса деления: ldi temp,0x00 ; Загружаем 4 000 000 ldi temp2,0x09 ; в три временных регистра ldi temp3,0x3D clr lowerbyte ; Очищаем регистры результата clr upperbyte rgf УПРАЖНЕНИЕ 2.52. Напишите восемь строк цикла (назовем его ^^ Divide), в котором производится деление константы 4 000 000 на число, находящееся в регистрах задержки. (СОВЕТ: назовите следу- следующую секцию DoneDividing и переходите к ней при неудачном вы- вычитании, т.е. в случае установки флага переноса.) Как и при измерении сигналов высокой частоты, теперь нам нужно вычислить количество сотен, десятков и единиц числа, части которого на- -109-
Глава 2. Основные операции в AT90S1200 и TINY12 ходятся в регистрах lowerbyte и upperbyte. Для этого мы спокойно можем воспользоваться уже написанной подпрограммой DigitConvert. Затем про- программа возвращается к метке LowSpeed. /gS УПРАЖНЕНИЕ 2.53. Какие две команды завершают цикл измере- ^^ ния сигналов низкой частоты? Теперь нам осталось написать только секцию TooSlow, которая выпол- выполняется, если период колебаний входного сигнала больше одной секунды. Нам хотелось, чтобы в этом случае выключался дисплей и микроконтрол- микроконтроллер переходил в спящий режим. @S УПРАЖНЕНИЕ 2.54. Напишите три первые строки секции ^^ TooSlow. Не забудьте в секции Init проинициализировать регистры R0...R11 кор- корректными значениями кодов для семисегментных индикаторов. Посколь- Поскольку команду непосредственной загрузки ldi можно использовать только с регистрами R16...R31, вам нужно будет сначала загружать числа в регистр temp, а затем командой mov пересылать их в соответствующие регистры R0...R11. Кроме того, инициализируйте регистр PortD таким образом, что- чтобы один из индикаторов был включен (например, ObOOOOOOOl). Определи- Определите все рабочие регистры в самом начале программы. Теперь программа го- готова для тестирования в симуляторе. Может быть, стоит воплотить это уст- устройство «в железе», так как оно все-таки выполняет полезные функции; однако вскоре вы заметите, что у нашего частотомера не очень хорошее разрешение, поскольку в каждом из диапазонов отображаются только три разряда значений частоты A00...999 Гц и 100...999 кГц). Если хотите, може- можете подумать о том, как доработать программу, чтобы обеспечить разреше- разрешение, равное трем разрядам во всем диапазоне измеряемых частот. В следу- следующих главах вы познакомитесь с различными решениями, которые позво- позволят вам значительно упростить программу. Поэтому после прочтения книги стоит вернуться к данному проекту и переписать программу заново. Во время написания этой большой программы вы должны были по- понять, насколько важны перерывы в работе. Даже если работа «горит», ни- никогда не повредит отвлечься на несколько минут и немного отдохнуть. После перерыва вы по-новому взглянете на проблему и сможете найти место, где допустили ошибку. Хорошее планирование и использование блок-схем поможет уменьшить количество таких упущений. Могу вам дать еще один совет: поговорите с людьми о решениях, которые вам необходи- необходимо принять, или о проблемах в случае затруднений. Даже если эти люди совершенно не разбираются в микроконтроллерах, само формулирование вопроса достаточно часто помогает найти ответ. -ПО-
Глава 3. ЗНАКОМСТВО С ОСТАЛЬНЫМИ МОДЕЛЯМИ СЕМЕЙСТВА До сих пор мы рассматривали наиболее типичные модели микроконт- микроконтроллеров AVR — 1200 и Tiny. В настоящей главе я расскажу вам об основ- основных различиях между этими моделями и остальными микроконтроллера- микроконтроллерами AVR, чтобы при последующем чтении книги вы имели о них хотя бы общее представление. Одним из преимуществ других моделей семейства является наличие у них дополнительной памяти данных, называемой ОЗУ. Распределение памяти в каждой модели разное, однако общая организа- организация памяти соответствует приведенной на Рис. 3.1. Первые 32 адреса занимают рабочие регистры, а следующие 64 адреса — регистры ввода/вывода. Так что основным отличием между моделями с ОЗУ и мо- моделями без ОЗУ является наличие у первых дополни- дополнительной области памяти, начинающейся с адреса $60. Доступ к этой области может осуществляться при по- помощи как уже рассмотренных команд Id и st, так и других, имеющихся в этих более развитых моделях. Кроме того, в составе регистрового файла моделей с ОЗУ появляются еще две 2-байтные пары регистров. Наряду с регистром Z (R30 и R31) имеются регистры Y (R28 и R29) и X (R26 и R27). Они могут использо- использоваться в любых командах, где в качестве операнда бе- берется регистр Z (например, Id, st, lpm и т.п.). В то время как рассмотренные нами микроконт- микроконтроллеры 1200 и Tiny имеют трехуровневый аппарат- аппаратный стек, остальным моделям требуется указать, в каком месте ОЗУ следует поместить с тек. То есть теоретически глубина стека может быть равна объему ОЗУ, однако очевидно, что часть ОЗУ может использоваться более эффектным способом. Все, что нам нуж- нужно сделать, — это указать в качестве вершины стека адрес верхней границы ОЗУ. При этом получится что-то вроде перевернутого стека (см. Рис. 3.2), который работает точно так же, как и любой другой стек. Рабочие регистры R0...R31 Регистры ввода/вывода $00...$3F ОЗУ 00 1F 20 5F 60 Рис. 3.1. Организа- Организация памяти микро- микроконтроллеров AYR 111-
Глава 3. Знакомство с остальными моделями семейства ОЗУ Адрес 000 Последний адрес Вершина стека Рис. 3.2. «Перевернутый» стек Для работы со стеком предусмотрено два регистра ввода/вывода — SPL и SPH, которые являются регистрами указателя стека (stack pointer) (младший и старший байты соответственно). Именно в них необходимо загружать адрес вершины стека, а конкретно — адрес верхней границы ОЗУ. Это значение хранится в константе RAMEND, определенной во включаемом файле, который указывается в самом начале любой програм- программы. Следовательно, младший байт константы RAMEND мы загружаем в регистр SPL, а старший байт — в регистр SPH и таким образом устанавли- устанавливаем указатель стека на конец ОЗУ. Для выполнения описанных действий используется следующий код: ldi temp,LOW(RAMEND) ; Указатель стека указывает на out SPL,temp ; последний адрес ОЗУ ldi temp,HIGH(RAMEND) out SPH,temp Эти строки должны располагаться в секции Init до первого вызова ка- каких-либо подпрограмм. Для моделей с объемом ОЗУ 128 байт размер конс- константы RAMEND равен всего 1 байт, поэтому две последние строки приве- приведенного фрагмента следует убрать. Еще одним важным отличием более развитых моделей является рас- расширенный набор команд. Во-первых, команды Id и sd обладают большей гибкостью. Вы сможете использовать «расширенные» регистры X, Y или Z в качестве указателя адреса, который будет автоматически инкрементиро- ваться или декрементироваться при каждой операции чтения/записи: Id reg,longreg+ ; Эта команда загружает (load) содержимое ячейки памяти, указываемой сдвоенным регистром longreg (т.е. X, Y или Z), в регистр общего назначе- назначения reg, а затем увеличивает longreg на единицу. -112-
Глава 3. Знакомство с остальными моделями семейства Id reg,-longreg ; Эта команда вычитает единицу из сдвоенного регистра longreg (т.е. X, Y или Z), а затем загружает содержимое ячейки памяти, указываемой этим сдвоенным регистром, в регистр общего назначения reg. Аналогичные ко- команды имеются и для команд сохранения st. Используя одну из этих команд, можно, в частности, написать более короткую подпрограмму очистки последовательности регистров. В данном случае я решил использовать в качестве указателя для косвенной адреса- адресации регистр X, поэтому подпрограмма сбрасывает регистры с R0 по R25: clr XL ; Очищаем XL clr XH ; Очищаем ХН ClearLoop: st XH,X+ ; Очищаем указанный регистр и ; инкрементируем X cpi XL,26 ; Сравниваем XL с 26 brne ClearLoop ; Переходим к метке ClearLoop, если ZL о 26 Другим нововведением в области чтения/записи данных является ко- команда: ldd reg,longreg+number ; Эта команда загружает (load) содержимое ячейки памяти, указываемой регистром Y или Z, в регистр общего назначения reg, а затем прибавляет константу number @...63) к Y или к Z {обратите внимание, что эта команда не применима к регистру X). Разумеется, предусмотрена и аналогичная команда сохранения std, работающая таким же образом. По- Помимо этого имеется возможность непосредственной адресации ячейки памяти ОЗУ: Ids reg,number ; Эта команда загружает (load) содержимое ячейки памяти с адресом number в регистр общего назначения reg. Константа может принимать зна- значения 0...65535 (т.е. до 64 Кбайт). Аналогично команда sts записывает со- содержимое регистра по адресу, задаваемому константой. Очень полезны также операции косвенного перехода и косвенного вы- вызова подпрограмм, в которых адрес определяется содержимым регистра Z: icall ; Вызывает подпрограмму по адресу, задаваемому регистром Z ijmp ; Переходит по адресу, задаваемому регистром Z Е ПРИМЕР 3.1. Имеется программа, которая должна выполнять одну из пяти различных функций в зависимости от числа в регистре Function. Суммируя содержимое Function с текущим значением счет- счетчика команд и переходя по получившемуся адресу, мы можем орга- организовать ветвление: -ИЗ-
Глава 3. Знакомство с остальными моделями семейства clr ZH ; Обнуляем старший байт ldi ZL,JumpTable ; Устанавливаем Z на начало таблицы add ZL,Function ; Прибавляем Function к ZL ijmp ; Косвенно переходим JumpTable: rjmp Addition ; Переходим к секции Addition rjmp Subtraction ; Переходим к секции Subtraction rjmp Multiplication ; Переходим к секции Multiplication rjmp Division ; Переходим к секции Division rjm Power ; Переходим к секции Power Обратите внимание, что, хотя в тексте программы в регистр Z загружа- загружается имя метки JumpTable, ассемблер вместо этого имени подставляет зна- значение адреса памяти программ, в котором размещается команда, помечен- помеченная меткой. Эта операция необходима для установки регистра Z на начало таблицы переходов (rjmp Addition). Заметим, что загрузка значения JumpTable эквивалентна загрузке PC + 3. После этого к регистру Z прибав- прибавляется содержимое регистра Function, для того чтобы выполнить переход к одной из пяти секций, в зависимости от значения Function @...4). Вы наверняка помните, какое количество операций сложения и вычи- вычитания нам приходилось делать, оперируя с 2-байтными числами в про- программе частотомера. В рассматриваемых моделях имеются две команды, предназначенные как раз для этих целей: adiw longreg,number sbiw longreg,number Эти команды прибавляют (add) или вычитают (subtract) число number @...63) к/из одного из 16-битных регистров longreg (X, Y или Z). Символ «w» означает 16-битное слово (word). При возникновении переполнения или переноса в младшем байте, автоматически изменяется старший байт. Так что: И наконец, последние две команды, имеющиеся в более развитых мик- микроконтроллерах AVR: push register pop register ; До сих пор мы использовали стек только для автоматического сохране- сохранения счетчика команд при вызове подпрограмм. Используя приведенные выше команды, вы можете сохранять (push) содержимое любого рабочего регистра в стеке или же восстанавливать (pop) его из стека. —114 — subi XL,50 sbci XH,0 sbiw XL,50
Глава 3. Знакомство с остальными моделями семейства 0 ПРИМЕР 3.2. Мы можем использовать команды push и pop для со- создания детектора палиндромов. Вообще говоря, палиндромом назы- называется любая симметричная последовательность символов (напри- (например, «радар», «а роза упала на лапу Азора» и пр.). Если предполо- предположить, что длина входной последовательности известна, то задача намного упростится. Это значение нам потребуется для определе- определения середины последовательности. Предположим, что входная пос- последовательность поступает (в виде символов ASCII) в регистр, на- называемый Input. Код ASCII является одним из способов трансляции цифр и букв в числовое значение, при котором каждому символу соответствует уникальный однобайтный номер. Итак, нам необхо- необходимо определить, является ли последовательность символов, посту- поступающих в регистр Input, палиндромической (симметричной). На- Начнем проверку с заталкивания (pushing) содержимого Input в стек. Мы будем выполнять эту операцию над каждым новым симво- символом до тех пор, пока не достигнем середины последовательности. После этого мы начнем выталкивать (popping) числа из стека и сравнивать их со значением входных символов. Пока каждый вход- входной символ совпадает с вытолкнутым из стека, последовательность можно считать палиндромической. Если же очередной входной символ не равен вытолкнутому из стека, то входная последователь- последовательность «бракуется». На вывод PD0 будет подаваться положительный импульс длительностью 1 мкс, указывающий на приход нового сим- символа (мы не можем определять приход нового символа по измене- изменению входа, поскольку в этом случае мы не будем реагировать на повторяющиеся символы). Предположим, что длина входной последовательности хранится в ре- регистре Length. Необходимо разделить это значение на два, чтобы опреде- определить середину последовательности. Кроме того, необходимо обратить вни- внимание на то, является значение длины четным или нет. Для этого проверя- проверяем флаг переноса: если он установлен, то значение Length нечетное, и нужно установить бит Т. Start: mov HalfLength,Length ; Делим Length на 2 для получения lsr HalfLength ; Halflength in temp,SREG ; Копируем флаг переноса в бит Т, bst temp,0 ; т.е. устанавливаем бит Т, ; если Length нечетное Полагая, что первый байт последовательности уже находится в регист- регистре Input, мы затолкнем его в стек и дождемся импульса на выводе PD0. Длительность импульса равна 1 мкс, поэтому при тактовой частоте 4 МГц -115-
Глава 3. Знакомство с остальными моделями семейства состояние этого входа необходимо проверять, по крайней мере, один раз за четыре такта. В следующем фрагменте проверка осуществляется один раз в три такта: FirstHalf: push Input ; Заносим Input в стек Pulse: sbis PinD,0 ; Проверяем импульс rjmp Pulse ; Возвращаемся назад При обнаружении импульса (что означает готовность нового входного символа) инкрементируется регистр Counter, содержащий номер входного символа. Этот регистр сравнивается с HalfLength, и, если Counter меньше HalfLength, программа переходит к началу цикла. inc Counter ; Считаем входные числа ср Counter,HalfLength ; Сравниваем с половинным значением brio FirstHalf ; Возвращаемся к началу цикла Когда Counter становится равным HalfLength, мы проверяем значение бита Т, чтобы определить, является ли последовательность четной или нет. Если она нечетная, центральную букву следует игнорировать. В этом слу- случае мы сбрасываем бит Т и возвращаемся к метке Pulse, где будем ждать прихода следующего символа. Если же последовательность четная, мы мо- можем сразу перейти к проверке второй половины входной последователь- последовательности. brtc SecondHalf ; Проверяем бит Т clt ; Сбрасываем бит Т rjmp Pulse К этому моменту мы прошли середину последовательности, и все но- новые входные символы должны соответствовать ранее сохраненным значе- значениям. Число с вершины стека выталкивается и сравнивается с текущим входным символом. Если они не равны, последовательность «бракуется». SecondHalf: pop Input2 ; Загружаем из стека в Input2 ср Input,Input2 ; Сравниваем Input и Input2 brne Reject ; Если не равны, бракуем последовательность После удачного сравнения мы, как и раньше, инкрементируем Counter и сравниваем его с регистром Length. Если эти регистры равны, значит, проверка завершена, и мы можем приступить к приему новой последова- последовательности. Если же нет, ждем прихода нового символа, после чего возвра- возвращаемся к метке SecondHalf. inc Counter ; Считаем число символов ср Counter.Length ; Сравниваем с длиной последовательности -116-
Глава 3. Знакомство с остальными моделями семейства breq Accept ; Конец последовательности - принимаем Pulse2: sbis PinD,0 ; Ждем импульса rjmp Pulse2 ; rjmp SecondHalf ; Возвращаемся обратно после ; прихода нового символа Если есть желание, можете поиграться с этим примером в симуляторе, не забудьте только инициализировать стек, как было рассказано в начале главы. Также можно подумать над тем, как избежать необходимости опре- определения длины входной последовательности. Если вы хотите узнать боль- больше о подобных вещах, поищите книги, посвященные формальным языкам и синтаксическому анализу. -117-
Глава 4. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ Прерывания До сих пор мы должны были самостоятельно проверять возникновение различных событий (например, нажата ли кнопка, не переполнился ли таймер/счетчик и т.д.). К счастью, существуют определенные события, о наступлении которых мы можем узнавать автоматически. Возникновение любого из этих событий (при соответствующей конфигурации програм- программы) приведет к прекращению нормального выполнения программы и пе- переходу по определенному адресу. Эти события называются прерываниями. В модели 1200 имеются следующие прерывания: • Прерывание по НИЗКОМУ уровню на выводе INTO (PD2). • Прерывание по нарастающему фронту сигнала на выводе INTO. • Прерывание по спадающему фронту сигнала на выводе INTO. • Прерывание по переполнению таймера/счетчика Т/СО. • Прерывание по выходному сигналу аналогового компаратора. Первые три представляют собой внешние прерывания на выводе INTO и являются взаимоисключающими (т.е. в любой момент времени вы можете разрешить только одно из этих прерываний). Об аналоговом компараторе мы поговорим чуть позже. При возникновении прерывания программа пе- переходит по одному из адресов, расположенных в начале памяти программ. В совокупности эти адреса образуют таблицу векторов прерываний. Табли- Таблица векторов прерываний модели 1200 приведена в Табл. 4.1, для остальных моделей таблицы векторов прерываний приведены в Приложении Е. Таблица 4.1. Таблица векторов прерываний модели 1200 Тип прерывания Включение питания/Сброс Внешнее прерывание на INTO Прерывание по переполнению Т/СО Прерывание от аналогового компаратора Адрес перехода программы 0x000 0x001 0x002 0x003 -118-
Прерывания К примеру, если разрешено прерывание по переполнению Т/СО, то при возникновении переполнения программа прекращает свою работу и переходит по адресу 0x002 памяти программ. Если используются все три прерывания, то первые строки программы должны выглядеть следующим образом: rjmp Init ; Первая выполняемая команда rjmp Extlnt ; К обработчику внешнего прерывания rjmp Overflowlnt ; К обработчику прерывания от Т/СО rjmp ACInt ; К обработчику прерывания от компаратора Использование такого кода гарантирует, что программа перейдет к соответствующей секции при возникновении любого из прерываний (эти секции мы будем называть подпрограммами обработки прерываний). У нас есть возможность разрешать отдельные прерывания, используя различные регистры. Бит разрешения внешнего прерывания является 6-м битом регистра ввода/вывода GIMSK (General Interrupt Mask — об- общий регистр маски прерываний). Установка этого бита разрешает пре- прерывание, сброс — запрещает. Бит разрешения прерывания по перепол- переполнению Т/СО является 1-м битом регистра ввода/вывода TIMSK (Timer Interrupt Mask — регистр маски прерываний от таймеров/счетчиков). Однако наивысший приоритет имеет «глобальное разрешение» прерыва- прерываний. Это специальный переключатель, который запрещает все прерыва- прерывания, когда выключен, и разрешает индивидуально разрешенные пре- прерывания, когда включен. В роли этого переключателя выступает бит I регистра SREG (если хотите, можете снова взглянуть на формат этого ре- регистра, приведенный на стр. 92). Внешнее прерывание INTO может быть сконфигурировано таким об- образом, чтобы оно происходило при наступлении одного из трех условий, в зависимости от состояния битов 0 и 1 регистра ввода/вывода MCUCR (в этом регистре хранятся также установки спящего режима). Зависи- Зависимость типа прерывания от состояния указанных битов приведена в Табл. 4.2. Таблица 4.2, Типы прерываний в зависимости от состояния битов регистра MCUCR MCUCR БитО 0 0 1 1 Бит1 0 1 0 1 Условие возникновения прерывания На выводе INTO напряжение НИЗКОГО уровня Неверная конфигурация На выводе INTO спадающий фронт На выводе INTO нарастающий фронт -119-
Глава 4. Дополнительные возможности При возникновении прерывания значение счетчика команд, как и при вызове подпрограмм, сохраняется в стеке, поэтому после завершения об- обработки прерывания программа может вернуться к тому месту, где ее вы- выполнение было прервано. Более того, при возникновении прерывания бит глобального разрешения прерываний автоматически сбрасывается. Благодаря этому во время выполнения подпрограммы обработки прерыва- прерывания исключается возникновение других прерываний, что могло бы при- привести к огромному числу рекурсивных вызовов. При возврате из обработ- обработчика нам, как правило, нужно повторно разрешать прерывания. К счас- счастью, для этого имеется специальная команда: Эта команда осуществляет возврат (return) из прерывания, устанавли- устанавливая при этом бит общего разрешения прерываний (interrupt). Каждое прерывание имеет свой флаг прерывания. Этот флаг (бит) уста- устанавливается в 1 в тот момент, когда прерывание должно произойти, даже если прерывания глобально запрещены и соответствующая программа об- обработки прерываний не вызывается. Если прерывания запрещены (напри- (например, мы уже находимся в другой подпрограмме обработки прерываний), можно проверить состояние этого флага, чтобы определить, не было ли прерывания. Заметьте, что этот флаг остается установленным до тех пор, пока его не сбросить, а подпрограмма обработки прерываний вызывается, если этот флаг установлен и прерывания глобально разрешены. В связи с этим вы должны на всякий случай сбросить все флаги прерываний перед установкой бита глобального разрешения прерываний, поскольку некото- некоторые флаги могут быть установленными из-за ранее возникших событий. Флаги прерываний сбрасываются записью в них 1 — звучит нелогично, однако это так! Флаг прерывания по переполнению Т/СО является 1-м би- битом регистра ввода/вывода TIFR (Timer Interrupt Flag Register — регистр флагов прерываний таймеров), расположенного по адресу $38, а флаг внешнего прерывания — 6-м битом регистра ввода/вывода GIFR (General Interrupt Flag Register — регистр флагов прерываний общего назначения), расположенного по адресу $ЗА. Программа К. Измеритель скорости реакции • Прерывания • Генерация случайных чисел • Управление семисегментными индикаторами Следующей нашей программой будет программа для прибора, измеря- измеряющего скорость реакции. Это устройство работает следующим образом. -120-
Программа К. Измеритель скорости реакции Через произвольный промежуток времени D... 12 с) после нажатия кнопки готовности включается СИД. При его включении пользователь должен на- нажать на кнопку. Программа определит время реакции пользователя и отоб- отобразит это время в миллисекундах на дисплее, состоящем из трех семисег- ментных индикаторов. Если пользователь нажмет кнопку до включения светодиода, нажатие не будет засчитано. Принципиальная схема этого уст- устройства приведена на Рис. 4.1, а блок-схема программы — на Рис. 4.2. Мы будем использовать внешнее прерывание и прерывание по пере- переполнению Т/СО, так что вам придется соответствующим образом изменить начало программы. Заметьте, что поскольку мы не будем использовать прерывание от аналогового компаратора, то нет необходимости помещать специальную команду по адресу 0x003. /gS УПРАЖНЕНИЕ 4.1. Какими будут первые три команды про- ^^ граммы? Напишите текст секции Init, устанавливая Т/СО в режим внутреннего счета с периодом, равным СК/1024. Также вам нужно будет разрешить внешнее прерывание и прерывание по переполнению Т/СО, только не ус- устанавливайте пока флаг глобального разрешения прерываний. Сконфигу- Сконфигурируйте внешнее прерывание таким образом, чтобы оно происходило при НИЗКОМ уровне на входе (т.е. при нажатой кнопке). r^S УПРАЖНЕНИЕ 4.2. С помощью каких шести команд можно ин- >^^ дивидуально разрешить прерывания? В месте, обозначенном меткой Start, мы сначала вызываем подпро- подпрограмму Display, а затем проверяем состояние кнопки «Готов» (PinD, 1). Продолжаем выполнять эту проверку, оставаясь в цикле до тех пор, пока кнопка «Готов» не будет нажата. zg^ УПРАЖНЕНИЕ 4.3. Какие три строки выполняют указанные *^~* действия? Подпрограмма Display очень похожа на аналогичную подпрограмму, написанную нами для частотомера. Единственное отличие заключается в выборе требуемого индикатора. Вместо того чтобы сдвигать единичный бит от 0-го ко 2-му биту порта D, в этой подпрограмме сдвиг будет осу- осуществляться от 4-го к 6-му биту, проверяя состояние 7-го бита порта для определения перебора. Внесите в подпрограмму необходимые изменения и скопируйте ее в свой проект. Теперь нам необходимо написать подпро- подпрограмму, формирующую случайную задержку. -121-
Глава 4. Дополнительные возможности -122 Рис. 4.1. Принципиальная схема измерителя скорости реакции
Программа К. Измеритель скорости реакции Рис. 4.2. Блок-схема программы для измерителя скорости реакции Случайное распределение Одним из наиболее интересных моментов в данной программе будет процедура генерации случайных чисел для формирования временной за- задержки случайной длительности. Наиболее очевидным методом генера- генерации случайных чисел является использование каких-либо действий пользователя и преобразования полученных данных в числовое значение. 123- Инициализация Обновить дисплей •^Кнопка^ «Готов» ч^нажата? Сгенерировать случайное число Разрешить прерывания ""прерывания4 ^разрешены? Инкрементировать старший байт СИД включен? Случайный интервал уистек?у> Включить СИД Вернуться, разрешая прерывания Вернуться, не разрешая прерываний Преобразовать в 3 цифры разрядов Записать в регистры дисплея«bAd» | СИД включен? Запомнить значение TCNT0 ОБРАБОТЧИК ВНЕШНЕГО ПРЕРЫВАНИЯ
Глава 4. Дополнительные возможности Например, мы могли бы считывать содержимое TCNT0 при нажатии на кнопку «Готов». Таймер Т/СО, работающий в режиме внутреннего счета, постоянно изменяет свое состояние и переполняется, поэтому его значе- значение в момент нажатия кнопки можно считать в какой-то мере случайным. Однако часто мы не можем позволить себе такую роскошь, как использо- использование действий человека, а генерировать последовательность случайных чисел нужно. Как же быть в этом случае? К настоящему времени разрабо- разработано огромное число алгоритмов различной сложности для генерации слу- случайных чисел. Однако сложность функций, которые мы можем непосред- непосредственно реализовать с использованием ассемблера AVR, ограничена. К счастью, один из наиболее простых алгоритмов базируется исключи- исключительно на операциях сложения и умножения. Линейно-конгруэнтный ме- метод, предложенный Лемером в 1948 году, описывается следующей фор- формулой: In+1 = modm(aln + с). При использовании этого метода новое число последовательности об- образуется путем умножения предыдущего числа на а, прибавления с и при- применения к результату операции по модулю т. Операция modm(x) эквива- эквивалентна нахождению остатка от деления х на т. Мы можем воспользоваться тем, что результат выполнения любой операции в микроконтроллерах AVR можно легко представить в виде результата деления по модулю 256. Сло- Сложим, например, числа 20 и 250. Правильный ответ будет 270, однако ре- результат будет равен 14. Число 14 является результатом операции деления числа 270 по модулю 256, или mod256B70). На значения констант а и с на- накладываются определенные ограничения с целью увеличения степени слу- случайности последовательности. Учитывая, что наибольшая скорость алго- алгоритма достигается при наименьшем множителе (а), мы выберем а = 5 и с = 1. Кроме того, вы должны указать «затравку» — первое число в после- последовательности (/о). Можете проверить работу этого алгоритма вручную и убедиться в псевдослучайном характере вычисляемых значений. Первое, на что вы должны обратить внимание, — произвольность чисел не зависит от величины начального значения, поэтому не требуется подбирать ка- какое-то особое число. Вы также увидите, что последовательность повторя- повторяется через каждые 256 чисел, — это основной недостаток алгоритма. Боль- Большее значение модуля соответствующим образом увеличит период повторе- повторения. Мы могли бы получить модуль, равный 65 536, используя 2-байтные регистры (X, Y или Z) и команду adiw. В этом случае последовательность повторялась бы только через каждые 65 536 значений! Однако для измери- измерителя скорости реакции нам будет вполне достаточно периода, равного 256. Чтобы на базе случайного значения сформировать временной интер- интервал случайной длительности, поступим следующим образом. Максималь- -124-
Программа К. Измеритель скорости реакции ное время равно 10 с, а переполнение Т/СО происходит каждые 256 тактов, или каждые 256/4000 = 0.064 с. Соответственно нам понадобится счетчик, максимальное значение которого будет находиться в диапазоне от 61 до 183. Видно, что разность этих чисел не очень сильно отличается от 128 (во- (вообще говоря, разность равна 122). Нам же будет гораздо удобнее, если раз- разность этих значений будет равна 128. А поскольку диапазон изменения ве- величины задержки выбран нами в достаточной степени произвольно, мы с таким же успехом можем использовать счетчик с максимальным значени- значением от 60 до 188. Чтобы получить такие значения из случайных чисел, лежа- лежащих в диапазоне 0...255, мы разделим сгенерированное число на два и при- прибавим 60. Вернемся к нашей программе. Для хранения случайного числа мы бу- будем использовать регистр Random. Его надо будет умножить на 5 (сложить самого с собой 4 раза) и затем прибавить к результату единицу. sgS УПРАЖНЕНИЕ 4.4. Напишите шесть строк, формирующих сле- ^^ дующее случайное число. /gS УПРАЖНЕНИЕ 4.5. Какими тремя командами можно скопиро- у^ вать Random в регистр CountX, разделить CountX на два и прибавить к нему 60? После этого мы должны обнулить старший байт таймера (TimeH), вы- выключить дисплей (очистить регистр PortB), сбросить все флаги прерыва- прерываний и установить флаг глобального разрешения прерываний. /gp^ УПРАЖНЕНИЕ 4.6. Напишите шесть строк, выполняющих сброс ^^ регистров TimeH, PortB и флагов прерываний. В системе команд AVR предусмотрена специальная команда для гло- глобального разрешения прерываний: sei ; Установить бит разрешения прерываний Оставшаяся часть программы представляет собой цикл, в котором просто проверяется флаг глобального разрешения прерываний и выполня- выполняется возврат к метке Start в случае, если этот флаг сброшен. Это связано с тем, что после обработки внешнего прерывания бит глобального прерыва- прерывания не устанавливается, т.е. прерывания не разрешаются, и после вы- выхода из обработчика прерывания программа вернется к метке Start. На- Напротив, после обработки прерывания от Т/СО прерывания будут разреше- разрешены повторно, так что программа останется в цикле. -125-
Глава 4. Дополнительные возможности /g^ УПРАЖНЕНИЕ 4.7. Какие три строки завершают основное ^~* программы? тело Взглянув на блок-схему подпрограммы обработки прерывания по переполнению таймера Т/СО (Tint), мы увидим, что первым делом прове- проверяется, не включен ли СИД (PinD, 0). Если он выключен, мы должны сформировать задержку случайной длительности, определяющую момент следующего включения СИД. Если же СИД уже включен, мы должны ин- крементировать старший байт нашего таймера (TimeH). Если итоговое время превышает максимальное значение, которое можно отобразить на дисплее, то мы должны записать в регистры дисплея символы «-HI» и вер- вернуться в основную программу, не разрешая прерываний. За секунду таймер Т/СО накапливает 2400 отсчетов (вместе с регист- регистром, хранящим старший байт). Мы должны преобразовать это значение в миллисекунды, т.е. каким-либо образом подсчитать тысячные доли секун- секунды. Для этого мы можем умножить 2-байтное число на 5, а затем разделить результат на 12. Подвергнув обратному преобразованию число 999 (наи- (наибольшее время реакции), получим значение 2397 = 95D. Нам будет гораздо проще контролировать только достижение старшим байтом некоторого значения (например, ОхОА). Это легко реализовать, загружая в TCNT0 при включении СИД число 0хА2, а затем вычитая это число из итогового зна- значения: Tint: sbic PinD,0 ; Проверяем СИД rjmp TInt_LEDon ; Переходим к другой секции, если ; СИД включен dec CountX ; Декрементируем счетчик случайных чисел breq РС+2 ; Пропускаем команду, если счетчик равен 0, reti ; иначе выходим sbi PortD,0 ; Включаем СИД по окончании интервала ldi temp,0xA2 ; Инициализируем TCNT0 out TCNTO,temp ; для упрощения проверки на максимум reti TInt_LEDon: inc TimeH ; Инкрементируем старший байт cpi TimeH,OxOA ; Проверяем на максимальное время breq РС+2 ; Пропускаем, если реакция очень медленная reti ; ldi Hundreds,13 ; «-» ldi Tens,14 ; «H» ldi Ones,l ; «I» ret ; Выходим, не устанавливая бит I -126-
Программа К. Измеритель скорости реакции Логика работы подпрограммы обработки внешнего прерывания INTO (назовем ее Extlnt) гораздо проще. В этой подпрограмме мы первым де- делом проверяем состояние СИД. Если он выключен, значит, пользователь смошенничал, нажав кнопку до включения СИД. В этом случае мы загру- загружаем в регистры Hundreds, Tens и Ones числа 10, 11 и 12 соответственно, чтобы отобразить на дисплее слово «bAd». После этого мы выходим из об- обработки прерывания, не устанавливая бит глобального разрешения прерываний. Если же СИД уже включен, то нажатие является коррект- корректным. В этом случае мы должны остановить Т/СО и запомнить текущее значение счетчика, скопировав содержимое TCNT0 в регистр TimeL. Од- Однако вполне возможна такая ситуация, при которой сразу же после пре- прерывания INTO произойдет переполнение Т/СО. Поэтому мы должны про- проверить флаг прерывания по переполнению Т/СО и, если он установлен, инкрементировать регистр TimeH. Затем из полученного времени реак- реакции, хранящегося в регистрах TimeL и TimeH, необходимо вычесть число 0хА2 (которое мы сами добавили). Полученную разность необходимо ум- умножить на 5 и разделить на 12. ??S УПРАЖНЕНИЕ 4.8. Напишите первые двенадцать строк подпро- ^^ граммы Extlnt, которые проверяют состояние СИД, выполняют пе- переход к секции Cheat, если он выключен, а также останавливают Т/СО, запоминая его текущее значение и инкрементируя при необ- необходимости регистр TimeH. Из полученного значения вычитается число 0хА2, после чего Т/СО перезапускается в режиме внутреннего счета с периодом СК/1024. /gS УПРАЖНЕНИЕ 4.9. Из каких четырех команд состоит секция ^ Cheat? После вычитания числа 0хА2 мы должны увеличить время в 5 раз. Пос- Поскольку исходное значение хранится в двух регистрах, нам нужно будет ис- использовать команду adc для добавления к старшему байту переноса (при возникновении последнего): ldi Count,4 ; Загружаем в счетчик 4 mov temp,TimeL .; Запоминаем ьремя в TimeH и TimeL mov tempH,TimeH Times5: add temp,TimeL ; Прибавляем TimeL к самому себе adc tempH,TimeH ; Прибавляем TimeH к самому себе, ; учитывая перенос dec Count4 ; Повторяем 4 раза brne Times5 ; -127-
Глава 4. Дополнительные возможности Произведение, полученное в результате выполнения указанных ко- команд, хранится в регистрах temp и tempH. Нам нужно разделить его на 12. Это можно сделать разными способами, но самый простой заключается в том, чтобы определить, сколько раз мы можем вычесть число 12 из исход- исходного значения. pS УПРАЖНЕНИЕ4.10. Повышенной слояснос/им/Напишитефраг- ^^ мент из девяти команд, где сначала сбрасываются регистры TimeH и TimeL, а затем образуется цикл, в котором выполняется деление 2-байтного числа, находящегося в регистрах temp и tempH, на 12. Результат деления сохраняется в регистрах TimeL и TimeH. Выход из цикла осуществляется переходом к секции DigitConvert. В секции DigitConvert выполняется преобразование 2-байтного числа в три одноразрядных числа (эта секция идентична одноименной секции из программы частотомера, за исключением имен рабочих регистров). Кроме того, секция должна завершаться не командой ret, а командой rjmp Start. Регистры R0...R14, хранящие коды для семисегментного индикатора, должны быть инициализированы в секции Init. В регистрах RIO, Rl I, R12, R13 и R14 хранятся коды для символов «b», «A», «d», «-» и «Н» соответ- соответственно. Полный текст данной программы приведен в Приложении J {Программа К). Думаю, будет интересно поэкспериментировать с этим уст- устройством. Разумеется, проще всего измерить скорость реакции с помощью AVR следующим образом: пусть ваш друг зажмет микроконтроллер в руке, а затем отпустит его. Скорость реакции будет определяться тем, насколько низко упадет микросхема, прежде чем вы ее поймаете! Аналоговый компаратор Еще одной полезной функцией большинства микроконтроллеров AVR является наличие аналогового компаратора (АК), который срав- сравнивает напряжения на двух выводах — AIN0 и AIN1 (в модели 1200 это выводы РВО и РВ1) и изменяет состояние определенного бита в зависи- зависимости от того, какое из напряжений больше. Управление компарато- компаратором осуществляется с помощью регистра ввода/вывода ACSR, назначе- назначение битов которого приведено на Рис. 4.3. Бит 7 предназначен для включения/выключения АК. Перед выключе- выключением компаратора необходимо запретить возникающие от него прерыва- прерывания (сбросить 3-й бит регистра), так как в противном случае при выключе- выключении может произойти прерывание. Биты 0 и 1 определяют условие возник- возникновения прерывания от АК (прерывание может возникать при любом -128-
Программа L. 4-битный аналого-цифровой преобразователь изменении бита результата, при его установке или при его сбросе). Назна- Назначение остальных битов понятно из Рис. 4.3. ACSR — регистр управления и состояния аналогового компаратора ($08) Бит Название 7 б ADC - 5 4 АСО ACI 3 2 ACIE - 1 О ACIS1 ACIS0 00 01 10 11 Прерывание по изменению - Прерывание по спадающему фронту Прерывание по нарастающему фронту Разрешение прерывания от компаратора: 0 — прерывание запрещено 1 — прерывание разрешено Флаг прерывания от компаратора: 0 — прерывания не было 1 — прерывание было Результат сравнения (выход компаратора): 0 — напряжение на AIN0 больше напряжения на AIN1 1 — напряжение на AIN0 меньше напряжения на AIN1 Запрещение аналогового компаратора: 0 — аналоговый компаратор включен 1 — аналоговый компаратор выключен (снижение потребляемой мощности) Рис. 4.3. Регистр ACSR Программа L. 4-битный аналого-цифровой преобразователь • Аналоговый компаратор Наша следующая программа демонстрирует часто встречающуюся ситуацию, при которой требуется выполнить некоторую задачу, ис- используя только имеющиеся в распоряжении средства. Некоторые наи- наиболее развитые микроконтроллеры AVR имеют в своем составе функ- функционально законченный 10-битный АЦП, поэтому для реализации 4-битного АЦП достаточно будет простого ограничения результатов преобразования. Однако во многих моделях AVR такая роскошь недо- недоступна, и единственное, чем они обладают, — это аналоговый компара- -129-
Глава 4. Дополнительные возможности тор. В этом случае разрабатываемая нами программа может быть весьма полезна. В основе нашей схемы будет суммирующий усилитель, вырабатывающий одно из 16 возможных опорных напряжений. Пере- Перебирая все эти значения и сравнивая их поочередно с напряжением входного сигнала, мы можем за четыре прохода цикла определить вели- величину входного напряжения с разрешением 4 бита. Принципиальная схема всего устройства приведена на Рис. 4.4; обратите особое внима- внимание на то, как работает суммирующий усилитель. Более полную ин- информацию о суммирующих усилителях можно найти в литературе j). Соответствующая блок-схема приведена на Рис. 4.5. Сигналы на выводах PD0...PD3 определяют величину опорного на- напряжения, подаваемого на вход компаратора согласно Табл. 4.3. Рис. 4.4. Принципиальная схема АЦП Напишите секцию Init, при этом не забудьте включить аналоговый компаратор путем установки 7-го бита регистра ACSR. Прерывание от АК разрешать не надо. В строке, отмеченной меткой Start, инициализируем порт D значением ОЬООООЮОО. При этом старший бит (MSB) селектора ус- установится в 1, в результате чего на входе AINO появится напряжение 2.5 В, !) См., в частности, Brimicombe M.W. Introducing Electronic Systems, Nelson Thornes, 1987. -130-
Программа L. 4-битный аналого-цифровой преобразователь Сбросить текущий бит и установить следующий Установить следующий бит Рис. 4.5. Блок-схема программы для АЦП Таблица 4.3. Опорное напряжение, подаваемое на вход компаратора PD0...PD3 0000 0001 0010 ООП 0100 0101 оно 0111 Опорное напряжение [В] 0 0.312 0.625 0.937 1.25 1.562 1.875 2.187 PD0...PD3 1000 1001 1010 1011 1100 1101 1110 1111 Опорное напряжение [В] 2.5 2.218 3.125 3.437 3.75 4.062 4.375 4.687 -131- Инициализация Начальное значение 1000 Опорное напряжение больше?
Глава 4. Дополнительные возможности которое затем сравнивается с напряжением на входе AIN1. Если входное напряжение превышает опорное, то 5-й бит регистра ACSR будет установ- установлен, а если наоборот — сброшен. Если входное напряжение больше опор- опорного, значит, результирующий код больше 1000, поэтому установим 2-й бит порта (не изменяя состояния 3-го бита). Если же входное напряжение меньше опорного, значит, результирующий код меньше 1000, поэтому сбросим 3-й бит и установим 2-й. /gS УПРАЖНЕНИЕ 4.11. Напишите пять строк, в которых выполня- '^ ется инициализация регистра PortD, после чего проверяется выход компаратора. Если на выходе компаратора НИЗКИЙ уровень, то сбрасывается 3-й бит регистра PortD. Кроме того, независимо от со- состояния компаратора устанавливается 2-й бит регистра. /gS УПРАЖНЕНИЕ 4.12. Повторите описанные операции для остав- ^^ шихся битов (еще восемь строк). /gS УПРАЖНЕНИЕ 4.13. Повышенной сложности! Напишите че- '^ тире строки, в которых итоговое состояние выводов PD0...PD3 пе- передается на выход схемы (РВ4...РВ7), после чего выполняется воз- возврат к метке Start. Аналого-цифровой преобразователь (АЦП) В других моделях, таких как Tiny 15, 4433 и 8535, имеется встроенный 10-битный АЦП. Он работает практически так же, как и рассмотренная нами схема, за исключением того, что все действия выполняются автома- автоматически, а составные части блока находятся внутри кристалла. Напряже- Напряжение на одном из аналоговых входов измеряется (относительно напряже- напряжения на выводе опорного напряжения AREF), преобразуется в 10-битное число и сохраняется в двух регистрах — ADCL и ADCH (младший и стар- старший байт результата соответственно). Предусмотрено два основных режи- режима работы: непрерывное преобразование и однократное преобразование. В ре- режиме непрерывного преобразования АЦП периодически измеряет напря- напряжение входного сигнала и по окончании каждого преобразования обновляет содержимое регистров ADCL и ADCH. В режиме однократного преобразования пользователь должен инициировать каждое преобразова- преобразование самостоятельно. В моделях 4433 и 8535 номер вывода, подключенного к входу АЦП, за- задается с помощью регистра ADMUX ($07). Для этого используются биты 0...2, назначение которых приведено в Табл. 4.4; все остальные биты не ис- используются. -132-
Программа L. 4-битный аналого-цифровой преобразователь Таблица 4.4. Назначение битов регистра ADMUX Биты 2...0 000 001 010 011 100 101 по 111 Аналоговый вход Канал 0 (РАО) Канал 1 (PAI) Канал 2 (РА2) Канал 3 (РАЗ) Канал4(РА4) Канал 5 (РА5) Каналб(РАб) Канал 7 (РА7) Если вы хотите проверить несколько каналов, то можно изменить со- содержимое регистра ADMUX, и рабочий канал сменится сразу же либо, ес- если преобразование еще не завершено, по его завершении. Это означает, что в режиме непрерывного преобразования можно очень легко выпол- выполнять сканирование каналов, поскольку при изменении номера канала во время цикла преобразования следующий цикл преобразования начнется уже на новом канале. Остальные установки АЦП находятся в регистре ввода/вывода ADCSR (регистр состояния АЦП), расположенного по адресу $06. Назначение би- битов этого регистра приведено на Рис. 4.6. Биты 0...2 определяют частоту тактового сигнала АЦП. Тем самым за- задается как длительность каждого преобразования, так и его точность. Для достижения полной 10-битной точности рекомендуется использовать частоты от 50 до 200 кГц. Частоты выше 200 кГц можно использовать толь- только в том случае, если скорость преобразования важнее, чем точность. В частности, при частоте 1 МГц мы получим 8-битное разрешение, а при 2 МГц — 6-битное. Прерывание от АЦП генерируется (если разрешено) при завершении преобразования. Остальные биты регистра объяснений не требуют. АЦП в модели Tiny 15 обладает несколько большими возможностями: у него есть внутренний источник опорного напряжения и он может осу- осуществлять дифференциальное преобразование (т.е. измерять разность на- напряжений на двух входах). Кроме того, в моделях 4433 и 8535 младший байт 10-битного результата преобразования всегда сохраняется в регистре ADCL, а два старших бита — в регистре ADCH. В случае же микроконтрол- микроконтроллера Tinyl5 можно задавать выравнивание результата — описанным выше способом или же наоборот (старший байт полностью сохраняется в ADCH, -133-
Глава 4. Дополнительные возможности ADCSR — регистр состояния АЦП ($06) Бит 7 Название ADEN 6 5 ADSC ADFR 4 ADIF ADIE ADPS2 ADPS1 J О ADPSO Тактовая частота АЦП 000 001 010 011 100 101 110 111 СК/2 СК/2 СК/4 СК/8 СК/16 СК/32 СК/64 СК/128 Разрешение прерывания от АЦП: 0 — прерывание запрещено 1 — прерывание разрешено Флаг прерывания от АЦП: 0 — прерывание было 1 — прерывания не было Выбор режима работы АЦП: 0 — режим однократного преобразования 1 — режим непрерывного преобразования Запуск преобразования (в режиме однократного преобразования): 0 — преобразование завершено 1 — начать преобразование Разрешение АЦП: 0 — АЦП выключен (наименьшее потребление) 1 — АЦП включен Рис. 4.6. Регистр ADCSR а два младших бита — в ADCL). Управление этими функциями осуществ- осуществляется при помощи регистра ADMUX (см. Рис. 4.7). Если вы внимательно посмотрите на описание битов 0...2 регистра, то заметите, что мы можем измерять разность напряжений между выводами, называемыми ADC2 (РВЗ) и ADC1 (РВ4). Эти выводы подключаются к дифференциальному усилителю, выходной сигнал которого и измеряется с помощью АЦП. Дифференциальный усилитель может иметь усиление xl или х20. Вы наверняка обратили внимание на то, что при определенных -134-
Программа L. 4-битный аналого-цифровой преобразователь ADMUX— регистр мультиплексора АЦП ($07) Бит 7 6 5 4 Название REFS1 REFSO ADLAR - 3 2 1 - MUX2 MUX1 | О михо Выбор канала АЦП 000 001 010 011 100 101 110 111 ADC0 (PB5) ADC1 (РВ2) ADC2 (РВЗ) ADC3 (РВ4) ADC2-ADC2 х1 ADC2-ADC2 х20 ADC2-ADC3 х1 ADC2-ADC3 х20 Выравнивание результата АЦП: 0 — младший байт в ADCL, 2 MSB в ADCH 1 — старший байт в ADCH, 2 LSB в ADCL Опорное напряжение — Vcc Внешний ИОН, подключенный к выводу AREF (РВО) Внутренний ИОН B.56 В) Внутренний ИОН B.56 В) со сглаживающим конденсатором на выводе РВО Рис. 4.7. Регистр ADMUX значениях битов измеряется разница напряжений между выводом ADC2 и им же! Эта возможность используется для калибровки АЦП, так как диф- дифференциальный усилитель, применяющийся при измерении дифференци- дифференциальных сигналов, обязательно имеет небольшое напряжение смещения. Измерив величину данного смещения и вычитая впоследствии это значе- значение из результатов измерений, вы повысите точность аналого-цифрового преобразования. Другой удобной функцией, весьма полезной при необходимости вы- выполнения высокоточных преобразований, является возможность пере- переключения микроконтроллера в спящий режим и осуществления анало- аналого-цифрового преобразования в то время, пока микроконтроллер находит- находится в этом режиме. Это позволяет избавиться от электромагнитных наводок -135-
Глава 4. Дополнительные возможности со стороны ЦПУ (центрального процессора) микроконтроллера. Для вы- выхода из спящего режима можно использовать прерывание от АЦП. Этот метод демонстрируется в Примере 4.1. ldi temp,0bl0001011 ; Включаем АЦП, однократное преобразование out ADCSR,temp ; Разрешаем прерывание от АЦП ldi temp,0b00101000 ; Разрешаем спящий режим out MCUCR ; (режим «Снижение шумов АЦП») sleep ; Переходим в спящий режим - ; преобразование начнется автоматически После завершения цикла преобразования будет вызвана подпрограмма обработки прерывания от АЦП (адрес $008 в Tiny 15 и $00Е в 4433 или 8535). По окончании обработки прерывания выполнение программы про- продолжится с команды, следующей за командой sleep. Программа М. Инвертор напряжения • Аналого-цифровое преобразование • Цифро-аналоговое преобразование Мы можем использовать АЦП для цифро-аналогового преобразова- преобразования. Этот метод основан на использовании цифрового выхода микроконт- микроконтроллера для заряда конденсатора до требуемого значения выходного на- напряжения. Затем выход микроконтроллера отключается от конденсатора (начинает работать как вход). После этого конденсатор начинает медленно разряжаться через высокое сопротивление нагрузки, при этом уменьшает- уменьшается и напряжение на аналоговом выходе. Одновременно напряжение на аналоговом выходе контролируется с помощью одного из аналоговых вхо- входов микроконтроллера. Если выходное напряжение снизится ниже опре- определенного уровня, выход микроконтроллера снова подключится к конден- конденсатору для подзарядки последнего. Чтобы снизить выходное напряжение, выход AVR сбрасывается в 0 для быстрого разряда конденсатора. Этот про- процесс иллюстрируется на Рис. 4.8 (пульсации показаны с преувеличением). Сопротивление резистора R должно быть достаточно малым для обес- обеспечения быстрой реакции, а емкость конденсатора С должна быть, наобо- наоборот, достаточно большой для сглаживания пульсаций выходного сигнала. Чтобы продемонстрировать описанный метод, сделаем инвертор напряже- напряжения — устройство, которое преобразует входное напряжение Vm (от 0 до 5 В) в выходное, — величиной E - Vm). К примеру, если на входе 2 В, то на выходе будет 3 В. Принципиальная схема такого устройства приведена на Рис. 4.9, а блок-схема алгоритма — на Рис. 4.10. — 136 — ПРИМЕР 4.1
Программа М. Инвертор напряжения Выход Рис. 4.8. Использование АЦП для цифро-аналогового преобразования (схема и временная диаграмма) Рис. 4.9. Принципиальная схема инвертора напряжения В секции Ink нам надо будет включить АЦП и выбрать в качестве вход- входного вывод ADC0. Мы хотим достичь максимальной точности, поэтому частота преобразования должна быть не более 200 кГц. В схеме будет ис- использоваться внутренний тактовый генератор микроконтроллера, имею- имеющий частоту 1.6 МГц, поэтому тактовую частоту АЦП можно задать равной СК/8 B00 кГц). АЦП должен работать в режиме однократного преобразо- преобразования, а результат должен быть выровнен влево (старшие восемь битов ре- результата размещаются в регистре ADCH, а два младших бита — в регистре ADCL). Наконец, зададим в качестве опорного напряжение питания Ксс и запустим преобразование. -137-
Глава 4. Дополнительные возможности Рис. 4.10. Блок-схема программы для инвертора напряжения sgS УПРАЖНЕНИЕ 4.14. Какие значения следует загрузить в регистры ^^ ADCSR и ADMUX в секции Ink? Напишите полностью секцию Init. При старте программы вывод РВО должен быть выходом, а РВ5 и РВ2 — входами. При достижении програм- программой метки Start необходимо выбрать канал ADC0 (сбросить 0-й бит регист- регистра ADMUX) и начать преобразование, установив 6-й бит регистра ADCSR. По окончании преобразования этот бит будет автоматически сброшен. /gS УПРАЖНЕНИЕ 4.15. Какие четыре команды запускают преобра- ^~* зование по каналу ADC0 и ждут его окончания? После завершения преобразования значение, соответствующее входно- входному напряжению, будет находиться в регистрах ADCL и ADCH. Нам не нужна полная 10-битная точность, поэтому мы будем использовать только 8 бит. — 138 — Инициализация Измерить напряжение на входе Требуемое выходное напряжение = 5 - 1/L Измерить напряжение на выходе у ьых. х^ напряжение слишком ^ мало? у Выдать 5 В на РВО •'Вых. \ напряжение слишком . велико? Выдать 0 В на РВО Сделать РВО входом
Программа М. Инвертор напряжения Поскольку мы задали выравнивание влево, то для получения 8-битного зна- значения нам достаточно прочитать только регистр ADCH. Для вычисления функции E - Kin) мы просто инвертируем результат (единицы становятся нулями и наоборот). Инвертирование выполняется командой com, а резуль- результат инвертирования сохраняется в рабочем регистре Desired (это значение соответствует напряжению, которое мы хотим получить на выходе). /gS УПРАЖНЕНИЕ 4.16. Напишите фрагмент из шести команд, в ко- ^^ тором выполняется сохранение и инвертирование результата изме- измерения входного напряжения, осуществляется смена входного кана- канала АЦП на ADC1 и запускается новое преобразование. Кроме того, в конце фрагмента должен находиться цикл ожидания завершения преобразования. Теперь следует считать напряжение с выхода и сравнить его с требуе- требуемым. Скопируйте измеренное значение напряжения из регистра ADCH в регистр Actual {действительное напряжение на выходе). Затем восполь- воспользуйтесь командами сравнения (ср) и условного перехода (brio) для перехо- перехода к секциям TooHigh (действительное напряжение больше заданного) или TooLow (действительное напряжение меньше заданного). УПРАЖНЕНИЕ 4.17. С помощью каких семи команд можно реа- реализовать указанные проверки и переходы? Если действительное на- напряжение равно желаемому, РВО должен становиться входом (сбро- (сбросив 0-й бит регистра DDRB), после чего выполняется переход к мет- метке Start. В секции TooHigh необходимо понизить выходное напряжение, поэто- поэтому РВО переключается в режим выхода (установкой 0-го бита регистра DDRB), после чего на него выдается сигнал НИЗКОГО уровня (О В). В сек- секции же TooLow необходимо повысить выходное напряжение, поэтому пос- после переключения РВО в режим выхода на него выдается сигнал ВЫСОКО- ВЫСОКОГО уровня E В). rgS УПРАЖНЕНИЕ 4.18. Напишите шесть строк, составляющие сек- >^ ции TooHigh и TooLow. В конце каждой из секций должен выпол- выполняться переход к метке Start. На этом разработка программы М закончена. Если есть желание, мо- можете немного поэкспериментировать и попытаться реализовать более сложные зависимости выходного сигнала от входного (а может быть, и от двух входных сигналов). Возможно, вам удастся создать что-то наподобие звукового микшера, суммируя сигналы двух входных каналов или же вы- -139-
Глава 4. Дополнительные возможности читая сигналы левого и правого канала друг из друга для создания «псев- до-объемного» звука. Видите, как много интересных устройств можно сде- сделать даже на таком крошечном микроконтроллере, какТту15! EEPROM Наряду с ОЗУ и памятью программ, о которых вы уже знаете, во мно- многих микроконтроллерах AVR имеется дополнительная область памяти, со- сочетающая гибкость ОЗУ и постоянство памяти программ. В отличие от ОЗУ, EEPROM сохраняет свое содержимое при отсутствии питания; при этом, в отличие от памяти программ, содержимое EEPROM можно считы- считывать и изменять во время работы программы. Аббревиатура EEPROM рас- расшифровывается как Electrically Erasable Programmable Read-Only Memory (электрически стираемое программируемое постоянное запоминающее устройство — ЭСППЗУ). Для работы с EEPROM используются три регист- регистра ввода/вывода: • EEAR — регистр, хранящий адрес, по которому будет осуществляться чтение/запись EEPROM. • EEDR — регистр, хранящий данные, которые будут записаны в EEPROM или считаны оттуда. • EECR — регистр управления EEPROM: ¦ установка бита 0 инициирует чтение из EEPROM; ¦ установка бита 1 инициирует запись в EEPROM. В модели 1200 имеется всего 64 байта EEPROM, однако в других моде- моделях ее объем может быть намного больше (вплоть до 512 байт). Учтите, что операция записи в EEPROM занимает некоторое время. Чтобы определить момент окончания записи, необходимо проверять состояние 1-го бита ре- регистра EECR (который устанавливается для инициирования записи) — по завершении процесса записи этот регистр автоматически сбрасывается. ПРИМЕР 4.2. Чтобы записать в EEPROM число 45 по адресу 0x30, необходимо выполнить следующие команды: ldi temp,0x30 ; Задаем адрес out EAR,temp ; ldi temp,45 ; Загружаем данные out EEDR,temp ; sbi EECR,1 ; Начинаем запись EEWait: sbic EECR,1 ; Ждем окончания записи rjmp EEWait ; Выходим из цикла при EECR.1 = 0 ПРИМЕР 4.3. Чтобы считать содержимое EEPROM по адресу 0x14, не- необходимо написать приведенные ниже строки. После выполнения дан- данного фрагмента считанный байт будет находиться в регистре EEDR.
Программа М. Инвертор напряжения ldi temp,0x14 ; Задаем адрес out EAR,temp ; sbi EECR,0 ; Читаем ; Данные - в EEDR УПРАЖНЕНИЕ 4.19. Повышенной сложности/ Напишите под- подпрограмму, которая формирует в EEPROM по адресам 0x00...OxOF таблицу перекодировки ASCII. Иначе говоря, по адресу п в EEPROM должен находиться код ASCII символа п (т.е. коды чисел 0...9 и сим- символов А, В, С, D, Е и F). Коды ASCII указанных символов приведены в Приложении G. Подпрограмма должна состоять из 14 строк. Занести данные в EEPROM при программировании микроконтролле- микроконтроллера можно двумя способами. В программе AVR Studio перейдите к меню View -> New Memory View (Alt+4) и выберите EEPROM. При этом на экране появится окно, в котором будет отображено содержимое EEPROM. Прос- Просто введите значения, которые вы хотите записать в EEPROM, а при выборе программатора (например, STK500) в блоке «EEPROM» выберите пункт Current Simulator/Emulator Memory. В результате содержимое окна EEPROM будет записано в EEPROM-память микроконтроллера. Еще про- проще указать, какие данные вы хотите записать в EEPROM, непосредственно в программе. Для определения EEPROM используется директива .eseg (сегмент EEPROM). Все, что будет расположено после этой директивы, будет записано в EEPROM. Если вы хотите написать обычный код, необ- необходимо вставить директиву .cseg (сегмент кода). ПРИМЕР 4.4 .eseg ; Данные будут записаны в EEPROM .db 0x04,0x06,0x07 .db 0x50 .cseg ; Последующие данные ; будут записаны в память программ ldi temp,45 Директива .db заносит следующие за ней байт(ы) в память. В приведен- приведенном примере значения 0x04, 0x06, 0x07 и 0x50 записываются в EEPROM по адресам 00...03. Заметьте, эти строки не предназначены для изменения содержимого EEPROM — они только указывают программатору, какие данные следует записать в EEPROM во время программирования мик- микроконтроллера. Для указания конкретных адресов EEPROM можно ис- использовать директиву .org. В модели 1200, которая не поддерживает ко- команду lpm, для хранения таблицы кодов семисегментного индикатора го- гораздо выгоднее использовать EEPROM, чем регистры R0...R10, как мы делали это раньше. -141-
Глава 4. Дополнительные возможности Таймер/счетчик 1 A6-битный) В ряде моделей AVR, таких как 2313, наряду с 8-битным таймером/счет- таймером/счетчиком 0 имеется дополнительный 16-битный таймер/счетчик, называемый таймер/счетчик 1. Этот таймер очень полезен, так как позволяет значитель- значительно уменьшить количество маркеров и программных счетчиков при форми- формировании временных интервалов большой длительности. Состояние тайме- таймера/счетчика 1 (Т/С1) хранится в двух регистрах ввода/вывода TCNT1H (старший байт) и TCNT1L (младший байт). Тактирование таймера Т/С1 осуществляется независимо от Т/СО, т.е. он может работать на другой час- частоте. Кроме того, он может работать в режиме счета импульсов, поступаю- поступающих на его вход Т1. При работе Т/С1 на частоте 2400 Гц 16-битный счетный регистр позволит нам отсчитывать до 27 с, не используя дополнительных программных счетчиков. Здесь необходимо сделать важное замечание: при чтении состояния таймера Т/С1 оба регистра должны быть считаны одно- одновременно, так как в противном случае может случиться так, что между за- запоминанием младшего и старшего байта произойдет переполнение млад- младшего байта и старший байт инкрементируется, что приведет к сохранению совершенно неверного значения. Чтобы этого избежать, вы должны счи- считывать младший байт первым. Когда вы читаете регистр TCNT1L, со- содержимое регистра TCNT1H запоминается в специальном встроенном ре- регистре TEMP микроконтроллера. При последующем чтении из регистра TCNT1H возвращается не его содержимое, а содержимое регистра TEMP. Не путайте: внутренний регистр TEMP не имеет никакого отношения к ра- рабочему регистру R16, который мы тоже часто называем temp. 0 ПРИМЕР 4.5. Считать состояние Т/С1 в два рабочих регистра — TimeL и TimeH. Состояние Т/С 1 0x28FF in TimeL,TCNTlL ; FF сохраняется в TimeL, a 0x28 - ; во внутреннем регистре TEMP 0x2900 in TimeH,TCNTlH ; Содержимое TEMP копируется ; в TimeH Соответственно, даже если между операциями чтения состояние Т/С1 изменится с 0x28FF на 0x2900, числа в регистрах TimeL и TimeH будут рав- равны 0x28 и OxFF, а не 0x28 и 0x00. Аналогичным образом при записи младшего и старшего байта в ре- регистры вы должны записать старший байт первым. При записи в регистр TCNT1H микроконтроллер сохраняет записываемое значение во внутреннем регистре TEMP, а затем, при записи младшего байта, заносит оба байта в регистры Т/С1 одновременно. -142-
Программа М. Инвертор напряжения ПРИМЕР 4.6. Запишите 0x28F7 в таймер/счетчик 1. ldi TimeL,0x28 ldi TimeH,0xF7 out TCNT.lH,TimeH ; 0x2 8 запоминается во внутр. регистре TEMP out TCNTlL,TimeL ; 0xF7 в TCNT1L и 0x28 в ТСТ1Н ; записываются одновременно Таймер/счетчик Т/С1 имеет еще несколько 2-байтных регистров, таких как ICR1H/L и OCR1AH/L, чтение и запись которых должны осуществ- осуществляться так же, как и чтение/запись регистров TCNT1H/L. Назначение этих регистров обсуждается в следующих двух разделах. Функция захвата Представьте себе, что вам необходимо измерить время наступления ка- какого-либо события на определенном выводе (что мы делали в частотоме- частотомере). Разумеется, мы можем просто проверять состояние этого вывода, а за- затем считывать состояние Т/С1, как делали прежде, однако для упрощения программы и высвобождения ресурсов микроконтроллера имеет смысл воспользоваться возможностью автоматического сохранения состояния Т/С1. Функция захвата (input capture) обеспечивает автоматическое со- сохранение состояния Т/С1 в двух регистрах ввода/вывода: ICR1H (старший байт) и ICR1L (младший байт). Сохранение осуществляется при наступле- наступлении определенного события на выводе ICP (PD6 в модели 2313). Таким со- событием может быть появление нарастающего или спадающего фронта сиг- сигнала. Работа модуля захвата управляется одним из регистров управления Т/С1 — регистром ввода/вывода TCCR1B (Рис. 4.11); другой управляю- управляющий регистр называется TCCR1A и будет рассмотрен в следующем разделе. Бит 7 может использоваться для повышения помехоустойчивости функции захвата. Если подавление помех включено, то для выполнения захвата напряжение должно измениться, к примеру, с лог. О до лог. 1 и оста- оставаться в этом состоянии в течение 4 тактов. Если напряжение снизится до лог. О раньше, сигнал будет воспринят как импульсная помеха, и операции захвата не произойдет. Если вы хотите реагировать на сигналы длитель- длительностью меньше 4 машинных циклов, придется отключить функцию подав- подавления помех (сбросить 7-й бит регистра в 0). Бит 3 относится к модулю сравнения, который мы рассмотрим в следующем разделе. Предусмотрено также прерывание по факту захвата, позволяющее нам отследить момент наступления данного события. Адрес вектора этого прерывания — $003 (в модели 2313). Разрешение данного прерывания осуществляется установ- установкой 3-го бита регистра TIMSK. -143-
Глава 4. Дополнительные возможности TCCR1B — регистр управления В таймера/счетчика 1 ($2Е) Бит Название 7 ICNC1 б 5 ICES1 - СТС1 J 2 CS12 1 CS11 О CS1O Выбор источника тактового сигнала Т/С1 000 001 010 011 100 101 110 111 Т/С1 остановлен Частота Т/С1 равна тактовой частоте (СК) Частота Т/С1 равна СК/8 Частота Т/С 1 равна СК/64 Частота Т/С1 равна СК/256 Частота Т/С1 равна СК/1024 Т/С1 изменяется по спадающему фронту на выводе Т1 Т/С1 изменяется по нарастающему фронту на выводе Т1 Сброс таймера/счетчика при совпадении: 0 — не сбрасывать Т/С 1 при событии «совпадение» 1 — сбрасывать Т/С 1 в $0000 при событии «совпадение» Выбор активного фронта: 0 — захват осуществляется по спадающему фронту на выводе ICP 1 — захват осуществляется по нарастающему фронту на выводе ICP Подавление помех на входе модуля захвата: 0 — подавление помех отключено 1 — длительность изменения напряжения на ICP должна быть не менее 4 тактов Рис. 4.11. Регистр TCCR1B 2 ПРИМЕР 4.7. Функцию захвата можно использовать при создании велосипедного спидометра, в котором магнит проходит над поверх- поверхностью датчика при каждом обороте колеса. Скорость велосипеда может быть вычислена как функция времени между оборотами. Датчик магнитного поля должен быть подключен к входу ICP и формировать сигнал ВЫСОКОГО уровня при прохождении магни- магнита над датчиком. Мы будем измерять интервалы длительностью не более одной секунды, поэтому лучше использовать тактовый сигнал с периодом СК/256. Если вы забыли таблицу векторов прерываний для модели 2313, обратитесь к Приложению Е. Скелет программы спидометра приведен ниже: -144-
Программа М. Инвертор напряжения Тексты подпрограмм Display и DigConvert не приведены, так как пред- предполагается, что вы можете написать их самостоятельно, взяв за основу аналогичные подпрограммы из предыдущего проекта. Напоминаю вам, что подпрограмма DigConvert должна преобразовывать число, хранящееся в регистрах temp и tempH (т.е. разницу между двумя событиями), в разряды отображаемого числа. Также вам надо закончить секцию Init — настроить входы и выходы. Заметьте, что, хотя мы не используем прерывания, векто- векторы которых находятся по адресам $001 и $002, мы все равно должны раз- разместить команды по этим адресам. Мы могли бы просто написать пор (по operation — нет операции), но reti все же будет безопаснее. Смысл здесь в том, что если в результате какой-либо непредвиденной ошибки будет сге- сгенерировано прерывание INTO, то программа просто выйдет из обработчи- обработчика и ничего страшного не произойдет. Это один из методов так называемо- называемого «безопасного программирования» — ожидать неожидаемое. — 145 — rjmp Init ; Адрес $000 reti ; $001 - прерывание INTO не используем reti ; $002 - прерывание INT1 не используем IC_Int: ; $003 - прерывание по захвату in temp,ICRL ; Сохраняем захваченное значение in tempH,ICRH ; в рабочих регистрах sub temp,PrevL ; Вычисляем разность между старым и sbc tempH,PrevH ; новым значением mov PrevL,ICRL ; Запоминаем новое значение mov PrevH,ICRH rcall DigConvert ; Выделяем разряды 2-байтного числа reti Display: ; Напишите сами ret DigConvert: ; Напишите сами ret Init: ldi temp,Obll000100 ; Включаем подавление помех out TCCRlB,temp ; T/Cl работает при СК/256 ldi temp,0b00001000 ; Разрешаем прерывание от Т/С1 out TIMSK,temp sei ; Разрешаем прерывания ... ; Напишите сами Start: rcall ; Обновляем изображение на дисплее rjmp Start ; Снова измеряем
Глава 4. Дополнительные возможности Функция сравнения Почти во всех случаях использования таймера/счетчика вам требуется проверять его на предмет достижения определенного значения. К счастью, все микроконтроллеры, содержащие таймер/счетчик 1, имеют возмож- возможность выполнять такую проверку автоматически. Мы можем потребовать, чтобы микроконтроллер непрерывно сравнивал состояние Т/С1 с задан- заданным 16-битным числом. Когда Т/С1 достигает этого значения, генериру- генерируется прерывание. При этом мы можем изменить состояние одного из вы- выходов микроконтроллеров, а также сбросить Т/С1 (см. описание 3-го бита регистра TCCR1B на стр. 144). В модели 2313, например, число, с которым сравнивается Т/С1, хранится в двух регистрах ввода/вывода: 0CR1AH (старший байт) и 0CR1AL (младший байт). Буква «А» означает, что это ре- регистры первого блока сравнения, а не второго, который обозначается бук- буквой «В» и имеется, в частности, в модели 8515. Благодаря наличию двух блоков сравнения этот микроконтроллер может постоянно сравнивать состояние Т/С1 с двумя различными значениями. Очевидно, что, если мы собираемся использовать функцию сравнения, мы должны будем разрешить прерывание по сравнению, которое происходит при TCNT1H = 0CR1AH и TCNT1L = 0CR1AL. Разрешение данного прерыва- прерывания осуществляется установкой 6-го бита регистра TIMSK. Адрес вектора прерываний зависит от конкретной модели микроконтроллера, однако в модели 2313 этот адрес равен $004. Функция сравнения пригодится нам в следующем проекте, а в следующей главе вы узнаете, как с помощью дан- данной функции можно формировать сигналы с ШИМ (широтно-импуль- сной модуляцией). /gS УПРАЖНЕНИЕ 4.20. Повышенной сложности! Мы хотим, что- "^^ бы прерывание происходило каждую секунду (используется резона- резонатор 4 МГц). Определите числа, которые должны быть загружены в регистры TCCR1B, TIMSK, OCR1AH и OCR1AL. Главная программа N. Музыкальный автомат • EEPROM • Функция сравнения (output compare) • Генерация звука Управляя динамиком на определенной частоте, мы можем использо- использовать AVR для формирования тональных сигналов. В действительности при использовании прямоугольных сигналов формируется более натуральный звук, чем при использовании синусоидальных сигналов. Рассматриваемое устройство (последнее в этой главе) позволит пользователю записать в -146-
Главная программа N. Музыкальный автомат EEPROM микроконтроллера короткую мелодию, а затем воспроизвести ее через динамик. Частоты, соответствующие музыкальным нотам одной ок- октавы, приведены в Табл. 4.5. Таблица 4.5. Частоты, соответствующие некоторым музыкальным нотам Нота С С# D D# Частота, Гц 128 136 144 152 Нота Е F F# G Частота, Гц 161 171 181 192 Нота G# А А# В Частота, Гц 203 215 228 242 Значения частот для нот следующей октавы можно получить, удваивая частоту соответствующей ноты. Полагая, что используется 4 октавы, мы можем закодировать каждую ноту как совокупность буквы D бита) и номе- номера октавы B бита). Длительность звучания будет закодирована в оставших- оставшихся двух битах. Таким образом, для хранения каждой ноты мелодии потре- потребуется 1 байт в EEPROM. В модели 2313 имеется 128 байт EEPROM, соот- соответственно мы можем запомнить мелодию из 128 нот. Если требуется более длинная мелодия, можно использовать микросхему с EEPROM большего объема, например 8515. Формат кодирования нот приведен на Рис. 4.12. № бита 7 6 5 4 3 2 1 0 Длительность Октава Нота (напр.,С#) Рис. 4.12. Формат кодирования нот Такой музыкальный автомат состоит всего лишь из динамика, подклю- подключенного к выводу РВО, и стандартного кварцевого резонатора, подключен- подключенного к выводам XTAL1 и XTAL2. Микроконтроллер может непосредствен- непосредственно управлять только динамиками с относительно высоким сопротивлени- сопротивлением катушки (более 64 0м). Если используется низкоомный динамик (например, 8 Ом), его лучше подключить через транзисторный ключ. Блок-схема соответствующей программы приведена на Рис. 4.13; обратите внимание, что в программе активно используются прерывания, поэтому основное тело программы является пустым бесконечным циклом. Цифровое обозначение символов нот от 0x00 до ОхОВ будет соответ- соответствовать нотам от «С» до «В». Число ОхОС в этом поле будет признаком конца мелодии, при обнаружении которого микроконтроллер должен на- -147-
Глава 4. Дополнительные возможности Рис. 4.13. Блок-схема программы для музыкального автомата чать воспроизведение мелодии с начала. Разумеется, вы можете добавить еще одно значение, скажем, OxOD, которое будет означать конец мелодии, но не возврат к ее началу (т.е. автомат будет ждать ручного сброса). Однако в моей версии программы такой возможности не предусмотрено. В секции Init следует сконфигурировать входы и выходы, времязадающие регистры и регистр указателя стека (SPL). Разрешите прерывание по пере- переполнению Т/СО и прерывание по событию «совпадение» Т/С1. Таймер Т/С1 будет использоваться для формирования на входе динамика сигнала заданной частоты, а таймер Т/СО — для управления длительностью звуча- звучания ноты. Соответственно, сконфигурируем Т/СО для работы при СК/1024, а Т/С1 — при СК. В секции Init вы также должны инициализи- инициализировать 1-ю ноту мелодии, вызвав подпрограмму Read_EEPROM (текст подпрограммы мы напишем позже). Что касается метки Start, то по этому адресу вам нужно поместить единственную команду, передающую управление на эту же метку. При воз- возникновении прерывания по совпадению Т/С1 будет изменяться состояние -148- ОСНОВНОЕТЕЛО ПРОГРАММЫ Инициализация ПРЕРЫВАНИЕ ПО ПЕРЕПОЛНЕНИЮ Т/СО Декрементировать регистр длительности Переключить вывод РВО Длительность Возврат Перейти к следующей ноте Сбросить адрес EEPROM в О След. нота = 0х0С? Выделить частоту и длительность Возврат
Главная программа N. Музыкальный автомат выхода микроконтроллера. Для этого регистр PortB считывается в регистр temp, инвертируется и полученное значение записывается обратно в PortB. /gS УПРАЖНЕНИЕ 4.21. Напишите четыре строки, составляющиете- >^ ло подпрограммы обработки прерывания по совпадению Т/С 1. Раз- Разместите по адресу $004 команду перехода к этой подпрограмме. Все остальные действия производятся в прерывании по переполнению Т/СО. Рабочий регистр Length будет использоваться для контроля длитель- длительности звучания ноты. В начале подпрограммы Length декрементируется. Если результат не равен 0, обработка прерывания завершается, в против- противном случае команда возврата пропускается и продолжается выполнение подпрограммы. Если прошло достаточно времени, мы должны поменять ноту, однако перед этим необходимо сделать небольшую паузу. Эта пауза позволит нам проиграть одну и ту же ноту 2 раза, не создавая при этом ощущения проигрывания ноты удвоенной длительности. Самый простой способ сформировать задержку — дождаться повторной установки флага прерывания по переполнению Т/СО. Если это произошло, надо выйти из цикла, сбросить флаг и перейти к секции, в которой осуществляется чте- чтение следующей ноты из EEPROM (назовем эту секцию Read_EEPROM). sgS УПРАЖНЕНИЕ 4.22. Напишите восемь строк, с которых начина- ^^ ется подпрограмма обработки прерывания по переполнению Т/СО. Разместите по адресу $006 команду перехода к этой подпрограмме. В секции Read_EEPROM осуществляется копирование адреса, храня- хранящегося в рабочем регистре address, в регистр EEAR. Считываем содержи- содержимое ячейки EEPROM в регистр ZL и маскируем биты 4...7, выделяя код но- ноты. Затем сравниваем это значение с ОхОС; если равно, переходим к секции Reset. Если же не равно, убеждаемся, что код ноты меньше 12 (brio). Если он больше, то код ноты неверен, поэтому ZL должен быть сброшен в 0. Ес- Если код ноты меньше 12, эта команда пропускается. tgS УПРАЖНЕНИЕ 4.23. Напишите первые восемь строк секции J^} Read_EEPROM. Для считывания значений из таблицы, расположенной в памяти про- программ, мы будем использовать регистр ZL (при выполнении команды lpm). Как вы помните, lpm использует побайтную, а не пословную адресацию па- памяти программ, поэтому исходное значение в регистре ZL следует умно- умножить на два (с помощью команды lsl). Таблица перекодировки будет распо- располагаться в EEPROM, начиная с адреса 013 {адрес слова). Это достигается указанием в тексте программы директивы .org. Данная директива означает -149-
Глава 4. Дополнительные возможности «поместить следующую команду по адресу...». Начальные строки нашей таблицы приведены ниже (символы .dw являются директивой, размещаю- размещающей в памяти программ указанные после нее 2-байтные числа). .org 13 .dw 0x7A12 ; Частота для С (слово по адресу 013) .dw 0x7338 ; Частота для С# (слово по адресу 014) и т.д. Таким образом, для корректной адресации таблицы мы должны приба- прибавить к ZL число 26. Используйте команду lpm для считывания младшего байта и поместите результат из регистра R0 в рабочий регистр NoteL. Затем инкрементируйте ZL и повторите операцию, сохранив результат в регист- регистре NoteH. УПРАЖНЕНИЕ 4.24. Какие с ем ь команд выполняют данные дей- действия? Для вычисления значений таблицы нам придется заняться математи- математикой. Взяв частоты нот самой низкой из используемых октав (см. Табл. 4.5) и поделив на них число 4 000 000 (частота генератора), мы получим набор значений, с которыми собираемся сравнивать состояние Т/С1. Для более высоких октав мы просто будем уменьшать эти значения в 2 раза. Полу- Полученные мной значения приведены в полной версии программы (см. При- Приложение J); вы можете их вычислить самостоятельно или просто скопиро- скопировать оттуда. Для получения кода октавы мы снова копируем EEDR в temp, пере- переставляем тетрады, а затем маскируем биты 2...7, в которых находится ин- информация об октаве. Пометим следующую строку меткой GetOctave. Прежде всего результат маскирования проверяется на равенство нулю; если это так, мы можем просто перейти к следующей секции (GetLength). Если результат не равен 0, мы делим число, хранящееся в регистрах NoteL и NoteH, на два, декрементируем temp, а затем возвращаемся к метке GetOctave. /pS УПРАЖНЕНИЕ 4.25. Напишите восемь команд, использующих ^^ биты 4...5 содержимого EEPROM для подстройки частоты в соответствии с заданной октавой. Теперь регистры NoteH и NoteL можно пересылать в регистры вво- ввода/вывода OCR1AH и OCR1AL. Помните только, что первым записыва- записывается старший байт. Затем мы определяем длительность ноты, приме- применяя методику, аналогичную только что использованной. Снова копируем EEDR в temp, маскируем биты 5...0, переставляем тетрады и сдвигаем на -150-
Главная программа N. Музыкальный автомат один бит вправо. В результате требуемые биты окажутся в 1-м и во 2-м би- битах регистра temp, т.е. в нем будет число 0, 2, 4 или 6 — почти то, что нам нужно. Прибавив к temp число 2, получим значение 2, 4, 6 или 8. Это зна- значение следует сохранить в регистре Length. gpS УПРАЖНЕНИЕ 4.26. Из каких девяти команд состоит секция ^^ GetLength (в конце секции выполняется возврат из подпрограммы с разрешением прерываний)? Вот мы и закончили разработку нашей программы. Записывая в EEPROM различные значения при программировании кристалла, можно настроить автомат на воспроизведение любой мелодии. Для преобразова- преобразования нот, октав и длительностей в соответствующие шестнадцатеричные значения вам могут пригодиться электронные таблицы. Если хотите, мо- можете также поразмышлять на тему реализации более удобного способа за- загрузки значений в EEPROM. К примеру, для ввода мелодии можно сделать клавиатуру в виде матрицы кнопок, стробируя их для уменьшения требуе- требуемого количества входов. С другой стороны, в устройство можно добавить семисегментный индикатор, отображающий обозначение ноты, и набор кнопок для изменения как самой ноты, так и ее позиции. При таком под- подходе для ввода мелодии практически не потребуются навыки пианиста! -151-
Глава 5. ПРОДВИНУТЫЕ ВОЗМОЖНОСТИ ШИМ — широтно-импульсная модуляция В этом разделе мы увидим, как при помощи функции сравнения можно формировать аналоговый сигнал, — это гораздо проще способа, ис- использованного нами в инверторе напряжения. Нашей целью будет форми- формирование прямоугольного сигнала с изменяемым коэффициентом заполне- заполнения. Коэффициент заполнения представляет собой отношение длитель- длительностей ВЫСОКОГО (лог. 1) и НИЗКОГО (лог. 0) уровней выходного сигнала. Управляя значением этого коэффициента, мы можем управлять значением выходного напряжения, которое является средним значением выходного прямоугольного сигнала (см. Рис. 5.1). При использовании та- такого сигнала может потребоваться RC-фильтр, аналогичный применяемо- применяемому в инверторе напряжения. Рис. 5.1. Формы выходного сигнала при различных значениях коэффициента заполнения Функция сравнения используется для автоматического формирования ШИМ-сигнала с разрешением 8, 9 или 10 бит. Например, если мы переве- переведем таймер Т/С1 в режим 8-битного ШИМ, его состояние будет изменять- изменяться от 0x00 до OxFF, потом опять до 0x00 и т.д. После этого можно задать по- пороговые уровни, записывая определенные значения в регистры модуля сравнения. Когда Т/С1 достигает этого уровня при прямом счете (вверх), вывод ОС1 микроконтроллера (РВЗ в модели 2313) устанавливается в 1. Когда Т/С1 достигает этого значения при обратном счете (вниз), вывод ОС1 сбрасывается в 0. В результате на выводе ОС1 формируется 8-битный ШИМ-сигнал, как показано на Рис. 5.2. -152-
ШИМ — широтно-импульсная модуляция Рис. 5.2. Формирование 8-битного ШИМ-сигнала В режиме 9-битного ШИМ максимальное значение Т/С1 равно OxlFF, что дает нам увеличение разрешения на один бит. Соответственно, в режи- режиме 10-битного ШИМ максимальное значение Т/С1 равно 0x3FF. Кроме того, вы можете инвертировать выходной сигнал, так чтобы вывод ОС1 сбрасывался при переходе Т/С1 через порог во время прямого счета и ус- танавливался при переходе Т/С1 через порог во время обратного счета. Работой ШИМ управляет регистр TCCR1A, назначение битов которого приведено на Рис. 5.3. Прежде всего, обратите внимание на то, что состояние вывода ОС1 мо- может изменяться при возникновении прерывания по совпадению, даже ес- если таймер не находится в режиме ШИМ. Мы могли бы использовать эту возможность в проекте музыкального синтезатора (для автоматического управления выходом динамика), если бы подключили динамик к выводу ОС1. Вам наверняка интересно, каким образом в режиме ШИМ возникает прерывание по переполнению Т/С1 (поскольку в этом режиме никогда не бывает переполнения Т/С1). В режиме ШИМ прерывание по пере- переполнению Т/С1 возникает каждый раз, когда Т/С1 начинает считать с 0x0000. Более того, в режиме ШИМ вывод ОС1 функционирует как выход, независимо от состояния соответствующего бита в регистре DDRx. В режиме ШИМ таймер имеет одну очень интересную особенность, проявляющуюся при изменении коэффициента заполнения. Коэффици- Коэффициент заполнения определяется содержимым регистров OCR1AH и OCR1AL, однако, если вы измените их в произвольный момент времени, а не в мо- момент достижения счетчиком максимального значения (в частности, 0x1 FF для 9-битного ШИМ), на выходе могут возникнуть паразитные выбросы. Эти выбросы представляют собой импульсы, длительность которых равна разности между старым и новым значением. Если вы используете ШИМ для передачи некоторой информации, закодированной в длительности импульсов, то совершенно очевидно, что в процессе передачи она будет искажена, поскольку при каждом изменении ширины импульса на выход -153-
Глава 5. Продвинутые возможности TCCR1A —регистр управления А таймера/счетчика 1 ($2F) Бит б 5 4 3 2 1 Название СОМ1А1 СОМ1А0 - - - - PWM11 PWM10 I 00 01 10 11 Режим ШИМ выключен 8-битный ШИМ 9-битный ШИМ 10-битный ШИМ В режиме ШИМ Таймер отключен от вывода ОС1 Таймер отключен от вывода ОС1 ОС1 сбрасывается при прямом счете, устанавливается при обратном ОС1 устанавливается при прямом счете, сбрасывается при обратном Не в режиме ШИМ Таймер отключен от вывода ОС1 ОС1 переключается при возникновении прерывания по совпадению ОС1 сбрасывается при возникновении прерывания по совпадению ОС1 устанавливается при возникновении прерывания по совпадению Рис. 5.3. Регистр TCCR1A будет выдаваться «мусор». К счастью, в режиме ШИМ при изменении со- содержимого регистров OCR1AH и OCR1AL новые значения первоначально запоминаются во временных регистрах, а обновление собственно регист- регистров таймера происходит только при достижении Т/С1 максимального зна- значения. UART Название UART образовано от Universal Asynchronous Receiver and Transmitter — универсальный асинхронный приемопередатчик. UART — это стандартный (правда, несколько устаревший) метод передачи данных между различными устройствами. Модуль UART имеется в ряде моделей микроконтроллеров AVR, таких как 2313, 4433 и 8515. Модуль UART осу- осуществляет посылку 8- или 9-битных пакетов данных (обычно такой пакет является байтом или байтом + бит четности). Эти 8- или 9-битные пакеты -154-
UART называются символами. Бит четности — это дополнительный бит, который посылается вместе с байтом данных для контроля ошибок. Если в байте данных имеется нечетное количество установленных битов (например, ObOOl 10100), то бит четности будет равен 1, в противном случае — 0. 8 результате, если при передаче произойдет сбой, состояние бита четности перестанет соответствовать содержимому байта данных. Благодаря этому приемник сможет определить, что пакет передан неверно, и отправить за- запрос на повторную передачу байта. Если же ошибка произойдет в двух би- битах, бит четности будет верным, однако в обычных приложениях вероят- вероятность ошибки в двух битах настолько мала, что не стоит заострять на этом внимание. УПРАЖНЕНИЕ 5.1. Повышенной еложности! Напишите корот- короткий фрагмент кода, который считывает число из регистра (напри- (например, temp) и вычисляет для него бит четности. При передаче модуль UART добавляет к входному символу (8 или 9 бит) в начале — старт-бит (нуль), а в конце — стоп-бит (единица), фор- формируя таким образом 10- или И-битную последовательность. Полученное значение помещается в сдвиговый регистр, который поочередно выдвигает биты на выход передатчика TXD (к примеру, вывод PD1 в модели 2313). Этот процесс показан на Рис. 5.4. Скорость выдачи битов на выход пере- передатчика определяется параметром baud rate (скорость передачи информа- информации; измеряется в бодах), которым вы можете управлять. Рис. 5.4. Процесс передачи битов Приемник модуля UART непрерывно проверяет состояние входа RXD, на котором при отсутствии данных устанавливается ВЫСОКИЙ уровень. В действительности приемник считывает информацию с входа в 16 раз быстрее задаваемой скорости передачи, т.е. на каждый бит приходится -155-
Глава 5. Продвинутые возможности 16 отсчетов. При обнаружении на выводе RXD НИЗКОГО уровня (т.е. воз- возможного старт-бита) микроконтроллер пропускает шесть отсчетов, а затем делает три выборки. Эти выборки приходятся на отсчеты 8, 9 и 10 для каж- каждого принимаемого бита, и, таким образом, считывание значения бита происходит в середине интервала его передачи, что позволяет работать с сигналами, имеющими фронты большой длительности. Если микроконт- микроконтроллер обнаруживает, что на выводе RXD все еще присутствует НИЗКИЙ уровень, т.е. определенно пришел старт-бит, модуль UART переходит в рабочий режим и начинает считывать байт. Если же на выводе RXD уже присутствует ВЫСОКИЙ уровень, считается, что первый отсчет был прос- просто шумом, и модуль переходит к ожиданию корректного символа. Если приемник определил, что пришел истинный символ, он начинает брать по три отсчета каждого бита в середине интервала его передачи. Если значе- значения всех трех отсчетов бита не совпадают, то значение бита принимается равным значению двух одинаковых отсчетов. В завершение модуль считы- считывает выборки, относящиеся, по его мнению, к стоп-биту. Для того чтобы было принято решение о корректном приеме символа, по крайней мере, две из этих выборок должны быть равны единице. В противном случае мо- модуль считает символ неверно кадрированным и регистрирует ошибку кадрирования (framing error). Прежде чем использовать только что считан- считанное значение байта, вы всегда должны проверять, не было ли ошибки кад- кадрирования. К счастью, все эти операции выполняет за нас модуль UART, имею- имеющийся в микроконтроллерах AVR. Этот модуль содержит 4 регистра вво- ввода/вывода: • UDR (UART Data Register, $0C) — регистр данных UART, содержит при- принимаемый или передаваемый байт данных (биты 0...7 символа). • UCR (UART Control Register, $0A) — регистр управления, управляет кон- конфигурацией UART, а также содержит 8-й бит данных. • USR (UART Status Register, $0B) — регистр статуса UART, отображает со- состояние модуля UART, в частности флагов прерываний. • UBRR (UART Baud Rate Register, $09) — регистр скорости передачи, за- задает скорость передачи данных по UART. Назначение битов регистров UCR и USR приведено на Рис. 5.5 и Рис. 5.6 соответственно. Как уже было сказано, регистр UBRR используется для управления скоростью передачи данных. Естественно, она должна быть одинаковой для принимающего и передающего устройств. Скорость передачи опреде- определяется по формуле: Baud rate -156-
UART UCR — регистр управления UART ($0A) Бит 7 6 5 4 3 2 10 Название RXCIE TXCIE UDRIE RXEN TXEN CHR9 RXB8 TXB8 I 8-й бит передаваемых данных: При обмене 9-битными данными содержит 9-й бит передаваемого символа 8-й бит принимаемых данных: При обмене 9-битными данными содержит 9-й бит принятого символа Формат посылок: 0 — 8-битные данные (плюс старт-бит и стоп-бит) 1 — 9-битные данные (плюс старт-бит и стоп-бит) Разрешение передачи: 0 — запрещает работу передатчика (но только после окончания текущей передачи) 1 — разрешает работу передатчика Разрешение приема: 0 — запрещает работу приемника (соответствующие флаги отключены) 1 — разрешает работу приемника Разрешение прерывания при очистке регистра данных UART: 0 — прерывание «регистр данных UART пуст» запрещено 1 — прерывание «регистр данных UART пуст» разрешено (cm.5-h6htUSR) Разрешение прерывания по завершении передачи: 0 — прерывание «передача по UART завершена» запрещено 1 — прерывание «передача по UART завершена» разрешено Разрешение прерывания по завершении приема: 0 — прерывание «прием по UART завершен» запрещено 1 — прерывание «прием по UART завершен» разрешено Рис. 5.5. Регистр UCR Например, если мы используем резонатор 4 МГц, а в регистре UBRR записано 25, скорость передачи будет примерно равна 9615 бит/с. Вообще говоря, имеется определенный набор стандартных скоростей передачи: 2400,4800, 9600 и т.д., которого желательно придерживаться для обеспече- обеспечения совместимости вашего устройства с другими. По этой причине «ров- «ровные» значения тактовых частот, например 4 МГц, не очень хороши для устройств, использующих UART, поскольку в этом случае трудно получить точные стандартные значения скорости (попробуйте, например, исполь- использовать в приведенном примере UBRR = 26). Намного удобнее использо- -157-
Глава 5. Продвинутые возможности USR — регистр состояния UART ($0В) Бит 7 6 5 4 3 2 10 Название RXC TXC UDRE FE OR - - - I Переполнение: 0 — содержимое UDR было успешно перегружено в сдвиговый регистр 1 — содержимое UDR было перезаписано до того, как байт был скопирован в сдвиговый регистр Ошибка кадрирования: 0 — ошибка кадрирования отсутствует (корректный стоп-бит) 1 — обнаружена ошибка кадрирования (некорректный стоп-бит) Регистр данных UART пуст: 0 — байт, содержащийся в регистре UDR, еще не перегружен в сдвиговый регистр 1 — содержимое регистра UDR перегружено в сдвиговый регистр Передача завершена: Устанавливается в 1 после передачи символа в случае отсутствия других данных в UDR. Если прерывание разрешено, этот флаг автоматически сбрасывается. Если прерывание запрещено, флаг должен сбрасываться записью в него 1 Прием завершен: Устанавливается в 1 после приема символа и сохранения его в UDR. Если прерывание разрешено, то для сброса этого флага следует прочесть регистр UDR Рис. 5.6. Регистр USR вать резонаторы с такими частотами, как 1.8432 МГц, 2.4576 МГц, 3.6864 МГц, 4.608 МГц, 7.3728 МГц и 9.216 МГц. Разумеется, можно ис- использовать и более высокие частоты, только убедитесь, что выбранная ва- вами модель микроконтроллера их поддерживает. Если мы возьмем, скажем, частоту 3.6864 МГц, то увидим, что при UBRR = 23 скорость передачи по- получается равной точно 9600. Г7Т ПРИМЕР 5.1. Передать число из рабочего регистра Identity другому '—' устройству UART: ldi temp,ObO0001000 ; Разрешаем передачу- out UCR,temp ; out UDR,Identity ; Посылаем число Если мы собираемся послать следующий блок данных, то должны по- подождать, пока не установится бит UDRE регистра USR, сигнализируя нам о том, что предыдущий байт был перегружен в сдвиговый регистр и ре- регистр данных UDR готов к приему нового байта. -158
UART Вы можете использовать UART для связи с последовательным портом (RS-232) компьютера. Чтобы послать данные через последовательный порт ПК, проще всего воспользоваться программой HyperTerminal, входя- входящей в состав ОС Windows® (Пуск -> Программы -> Стандартные -> Связь). В этой программе вы можете создать соединение с конкретным портом (например, С0М1), выбрать скорость передачи, количество передаваемых битов, установить контроль четности и т.д. После установки связи с СОМ-портом все символы, которые вы будете вводить в окне HyperTerminal, будут передаваться (в формате ASCII) через последователь- последовательный порт. Если у вас имеется отладочная плата, такая как STK500, на ней обязательно есть контакты, связанные с разъемом порта RS-232, которые вы можете подключить непосредственно к выводам RXD и TXD. Если у вас нет такой отладочной платы, вам нужно будет самостоятельно подклю- подключить соответствующие выводы разъема порта к выводам RXD и TXD мик- микроконтроллера, а также обеспечить защиту микроконтроллера, ограничи- ограничивая напряжение на выводах порта (которое может колебаться от 3 до 12 В) на безопасном уровне (около 5 В) 1К На Рис. 5.7 показано, как можно со- соединить выводы 9-контактного разъема RS-232 непосредственно с AVR. Некоторые из оставшихся выводов являются выводами линий квитирова- квитирования (handshaking), которое можно эмулировать, соединив эти выводы друг с другом, как показано на рисунке. о Земля о Передача (подключается к выводу RXD AVR) о Прием (подключается к выводу TXD AVR) DB-9 Рис. 5.7. Соединение выводов порта СОМ 1 с AVR ]) Стабильность работы такого решения зависит от множества факторов, осо- особенно от характеристик конкретного экземпляра микросхемы приемопередатчика в компьютере. Для обеспечения надежной связи рекомендуется использовать спе- специальные микросхемы преобразователей уровней, такие как ST232, МАХ232, AD232 или аналогичные. — Примеч. пер. -159-
Глава 5. Продвинутые возможности Программа О. Конвертер клавиатуры • UART • Генерация звука • Управление семисегментными индикаторами • Функция сравнения (output compare) Мы можем воспользоваться программой HyperTerminal для передачи данных через UART нашему музыкальному автомату. Таким образом, пос- поставив в соответствие различным символам определенные ноты, мы можем легко превратить клавиатуру компьютера в клавиатуру музыкального син- синтезатора. Например, я нажимаю в HyperTerminal букву «а», и компьютер передает символ «а» на вход модуля UART. Это событие может вызвать прерывание, в котором ASCII-код буквы «а» будет преобразован в значе- значение частоты ноты «С». Я назначил клавиши таким образом, чтобы их рас- расположение было похоже на клавиатуру фортепьяно, однако вы можете расположить их любым удобным для вас образом. Мое решение приведено на Рис. 5.8. Рис. 5.8. Клавиатура «фортепиано» Мы также будем использовать семисегментный индикатор для отобра- отображения обозначения воспроизводимой ноты; это может помочь пользова- пользователю запомнить, какой клавише соответствует та или иная нота. Также нам потребуется отдельный СИД для индикации знака диеза (#). Схема такого устройства приведена на Рис. 5.9, а блок-схема программы — на Рис. 5.10. В секции Init необходимо инициализировать входы и выходы, а также сконфигурировать вывод ОС1 так, чтобы он переключался при каждом со- событии совпадения (такое решение позволит нам формировать звуковые сигналы без использования подпрограммы обработки прерывания по сов- совпадению). Сконфигурируйте таймер так же, как и в музыкальном автома- автомате, задайте скорость UART равной 9600, включите приемник UART и раз- разрешите прерывание по окончании приема. Как и ранее, основное тело программы будет представлять собой пус- пустой цикл, в котором не производится никаких действий. Прерывание по -160-
Программа О. Конвертер клавиатуры Рис. 5.9. Принципиальная схема конвертера клавиатуры ОСНОВНОЕ ТЕЛО ПРОГРАММЫ ПРЕРЫВАНИЕ ПО ЗАВЕРШЕНИИ ПРЕРЫВАНИЕ ПРИЕМА UART ПО ПЕРЕПОЛНЕНИЮ Т/СО Инициализация Запомнить принятый байт Отключить вывод ОС1 от таймера Т/С 1 НЕТ Возврат Сделать байт равным 26 Преобразовать байт в значение для OCR1 AH.L X Преобразовать байт в код семисегментного индикатора для порта В иСИД«#» X Задать переключение ОС1 по совпадению и сбросить TCNT0 х Возврат Рис. 5.10. Блок-схема программы для конвертера клавиатуры -161- х^Символ^4^ ^жду'а'и'гТ,
Глава 5. Продвинутые возможности окончании приема сообщает нам о том, что был принят новый символ, ко- который мы должны преобразовать в значение частоты и изменить соответ- соответствующим образом регистры OCR1AH и OCR1AL. Обработчик прерыва- прерывания должен начинаться с команды считывания содержимого регистра UDR в регистр ZL. Таблица символов ASCII приведена в Приложении G. Я буду использовать только буквы a...z (все строчные), соответствующие кодам ASCII 0x61...0х7А, так что для того, чтобы получить число от 0 до 25, необходимо вычесть из регистра ZL число 0x61. Если ZL больше 25, зна- значит, была нажата неверная клавиша. В этом случае следует записать в ZL число 26, чтобы гарантированно не выйти за пределы написанной нами таблицы перекодировки. Далее умножаем ZL на 2, чтобы вычислить адрес слова. Мы будем считывать данные из памяти программ в регистр R0, ис- используя команду lpm, а затем копировать R0 в регистры OCR1AH и OCR1AL. Мы можем делать это сразу же (т.е. нам не требуется изменять считанное значение в соответствии с октавой, и т.п.), поэтому регистры NoteL и NoteH нам не нужны. При записи следует помнить золотое прави- правило — первым записывается старший байт. Это условие можно выполнить двумя способами. Первый — сформировать данные в таблице таким обра- образом, чтобы старший байт значений располагался в памяти программ пер- первым. Например, если я поставил в соответствие ноте «С» число 0x1 Е84, то первой строкой таблицы будет: .dw 0х841Е Это несколько неудобно, гораздо проще устанавливать регистр ZL на старший байт. Другими словами, если адрес первого байта таблицы в па- памяти программ равен 26, то прибавьте к ZL не 26, а 27, с тем чтобы ZL ука- указывал на старший байт. После этого декрементируйте ZL, чтобы счи- считать младший байт. /gS УПРАЖНЕНИЕ 5.2. Повышенной сложности! Напишите пер- ^^ вые 12 строк подпрограммы обработки прерывания по завершении приема UART, которая использует принятые данные для записи но- новых значений в регистры OCR1AH и OCR1AL. Для индикатора нам потребуется другая таблица, которая будет распо- располагаться, начиная со слова по адресу 43. Чтобы сослаться на эту таблицу, мы можем просто добавить 60 C0 х 2) к регистру ZL. В этой таблице будут храниться коды для буквенных обозначений нот. Третий бит значений бу- будет использоваться для управления СИД «#». /gS УПРАЖНЕНИЕ 5.3. С помощью каких шест и команд можно уста- ^^ новить указатель на вторую таблицу, считать из нее значение и вы- выдать его в регистр PortB? После этого необходимо маскировать все -162-
Программа О. Конвертер клавиатуры биты регистра R0, содержащего считанное из таблицы число, кроме 3-го, и записать результат в регистр PortD для управления СИД «#». Поскольку вы не можете применять команду andi к регистрам R0...R15, считанное из таблицы число придется скопировать в ре- регистр temp. /gS УПРАЖНЕНИЕ 5.4. Какие пять команд будут переключать состо- ^^ яние вывода 0С1 при каждом прерывании по совпадению Т/С1, сбрасывать таймер Т/СО и выходить из прерывания? /gS УПРАЖНЕНИЕ 5.5. Из каких трех команд состоит обработчик *^~^ прерывания по переполнению Т/СО, который должен отключить 0С1 от таймера и выйти из прерывания? Написанная программа, в принципе, уже будет работать, однако вас может раздражать имеющаяся в клавиатуре задержка повторения. Вы мо- можете попробовать уменьшить ее через Панель Управления или же просто увеличить минимальную длительность ноты до значения, перекрывающе- перекрывающего эту задержку. Если же задать частоты, лежащие вне диапазона слыши- слышимости, на базе этого проекта можно создать устройство для более зловеще- зловещего применения. Кстати, вы никогда не думали заняться шпионажем? Еще одно устройство, использующее UART, можно построить на базе детектора палиндромов, разработанного нами в главе 3, и связать его с компьютером через последовательный порт. Использование прерывания по завершении приема значительно упростило бы нашу программу. Последовательный интерфейс SPI Интерфейс UART, рассмотренный нами в предыдущем разделе, имеет несколько недостатков. Прежде всего он является полудуплексным (другое название — симплексный), т.е. по каждой линии вы можете передавать дан- данные только в одном направлении. Соединение вывода TXD одного уст- устройства с выводом RXD другого поддерживает передачу данных только в одном направлении, а именно от TXD к RXD. Интерфейс SPI обеспечива- обеспечивает полнодуплексный обмен — посылку данных в обоих направлениях одно- одновременно. Кроме того, этот интерфейс реализует синхронный режим пере- передачи. Это означает, что все связанные устройства подключены также к об- общему тактовому сигналу, за счет чего они могут синхронизироваться друг с другом и обмениваться данными на более высокой скорости. Посылка данных по SPI осуществляется так же просто, как и по UART. Вместе можно соединить любое количество SPI-устройств; однако одно устройство называется Master (ведущий), а остальные — Slaves (ведомые). Ведущий может обмениваться данными с ведомым, так же как и ведомый -163-
Глава 5. Продвинутые возможности может обмениваться данными с ведущим, однако ведомые не могут обме- обмениваться данными друг с другом. Ведущий формирует тактовый сигнал, синхронизирующий обмен, а также определяет, кто должен передавать данные — он или ведомый. На Рис. 5.11 показано соединение ведущего устройства с двумя ведомыми. Рис. 5.11. Соединение ведущего устройства с двумя ведомыми При загрузке значения в регистр данных SPI ведущего он сразу же на- начинает генерировать тактовый сигнал на выводе SCK (SPI Clock) и побит- но выдвигать данные на вывод MOSI (Master Out, Slave In), который со- соединен с входами MOSI ведомых устройств. Ведомое устройство получит данные только в том случае, если оно было выбрано ведущим, т.е. если на его выводе SS (Slave Select) присутствует НИЗКИЙ уровень. Соответ- Соответственно, используя два дополнительных выхода (на Рис. 5.11 — РВО и РВ1), ведущий может выбирать конкретного ведомого, с которым он хочет общаться. Как только ведущий начинает выдавать данные на вывод MOSI, ведомый сразу же начинает выдавать содержимое своего регистра данных на вывод MISO (Master In, Slave Out). Таким образом, два 8-битных сдви- сдвиговых регистра ведущего и ведомого функционируют как один цикличес- циклический 16-битный сдвиговый регистр — при передаче каждого бита от веду- ведущего к ведомому, передается также бит от ведомого к ведущему. Вывод SS ведущего можно сконфигурировать как выход и использовать его как вы- выходной контакт общего назначения. Если же он будет сконфигурирован как вход, его необходимо подключить к шине Vcc, как показано на Рис. 5.11. Если на входе SS ведущего устройства появится НИЗКИЙ уро- уровень, оно решит, что другой ведущий хочет обратиться к нему как к ведо- ведомому, и переключится в режим ведомого! Такая логика обеспечивает опре- определенную иерархию между ведущими в сложных системах на базе SPI. -164-
Программа О. Конвертер клавиатуры В модуле SPI имеется три регистра ввода/вывода: • SPDR (SPI Data Register, $0F) — регистр данных, содержит посылаемый или принятый байт данных. • SPCR (SPI Control Register, $0D) — регистр управления, определяет функционирование модуля SPI. • SPSR (SPI Status Register, $0E) — регистр состояния, отображает состоя- состояние модуля SPI (например, флагов прерывания). Регистр SPDR представляет собой регистр данных, в который вы долж- должны поместить байт для передачи его другому устройству. После заверше- завершения передачи в этом регистре содержится принятый байт. Прежде чем про- произвести запись очередного посылаемого байта в регистр SPDR, необходи- необходимо дождаться окончания текущей передачи. А чтобы прочитать принятый байт, у вас есть немного больше времени. Вы можете прочитать принятый байт, пока осуществляется передача следующего байта, но как только пе- передача будет завершена, предыдущий принятый байт будет перезаписан. Следовательно, вы должны прочитать принятый байт до завершения транзакции. Регистр SPSR содержит два флага. Бит 6 является флагом коллизий за- записи, который устанавливается при попытке записи в регистр SPDR до окончания текущей передачи. Бит 7 является флагом прерывания SPI, кото- который устанавливается при завершении передачи. Назначение битов регистра управления SPCR приведено на Рис. 5.12. Интересной задачей была бы разработка устройства электронных шах- шахмат, в котором используются два микроконтроллера AVR, связанные друг с другом по шине SPI. Любой из игроков может ввести ход в свое устрой- устройство, которое затем перешлет этот ход другому устройству. Состояние игры можно сохранять в EEPROM, позволяя тем самым возобновлять игру пос- после выключения питания и разъединения устройств. Для этого потребуется 64 байта (по одному байту на каждую клетку). Число, хранящееся в EEPROM, будет соответствовать фигуре, стоящей на этой клетке. Напри- Например, 00 — пустая клетка, 01 — черная пешка, 02 — черный король,..., 81 — белая пешка, 82 — белый король и т.д. Для выполнения ходов используется сложение или вычитание определенных констант из текущих позиций фи- фигур. Например, разрешенный ход для слона — прибавление или вычитание из номера текущей позиции числа, кратного 9 или 7. Чтобы разобраться в этом, обратитесь к Рис. 5.13. Однако необходимо будет контролировать, чтобы фигуры не перескакивали друг через друга и не выходили за грани- границы доски. Ходы можно вводить в международной шахматной нотации (напри- (например, Ве2 соответствует «слон на Е2») или с помощью некоторого подобия дисплея, изображающего шахматную доску. Разработку такого устройства оставим любителям шахмат, однако мне интересно будет узнать ваше ре- -165-
Глава 5. Продвинутые возможности SPCR — регистр управления SPI ($0В) Бит 7 6 5 4 3 2 10 Название SPIE SPE DORD MSTR CPOL CPHA CPR1 CPRO Скорость передачи 00 01 10 11 Частота SCK равна СК/4 Частота SCK равна СК/16 Частота SCK равна СК/64 Частота SCK равна СК/128 Фаза тактового сигнала: 0 — данные считываются по нарастающему фронту SCK 1 — данные считываются по спадающему фронту SCK Полярность тактового сигнала: 0 — во время ожидания на SCK присутствует НИЗКИЙ уровень 1 — во время ожидания на SCK присутствует ВЫСОКИЙ уровень Выбор режима работы: 0 — режим ведомого (Slave) 1 — режим ведущего (Master) Порядок передачи данных: 0 — первым передается MSB 1 — первым передается LSB Разрешение SPI: 0 — модуль SPI выключен 1 — модуль SPI включен. Задействованы выводы MOSI, MISO, SCK и SS Разрешение прерывания от SPI: 0 — прерывание от SPI запрещено 1 — прерывание от SPI разрешено Рис. 5.12. Регистр SPCR шение этой задачи (мой адрес электронной почты приведен в Приложе- Приложении I). В заключение следует сказать, что оба интерфейса, UART и SPI, могут быть реализованы в микроконтроллере программно, без использования аппаратных модулей. Для получения дополнительной информации по этому вопросу вы можете обратиться к книге Клауса Кюхнеля, указанной в Приложении I, однако я настоятельно рекомендую использовать микро- микроконтроллеры, содержащие требуемые вам аппаратные модули. -166
Программа О. Конвертер клавиатуры Рис. 5.13. Коды позиций шахматных фигур Нестандартный Таймер 1 модели Tiny 15 Сделаем небольшое отступление и посмотрим на модель Tiny 15. В этой модели реализован 8-битный таймер/счетчик Т/С1, а также имеется ряд других особенностей, из-за которых Tiny 15 выделяется среди остальных моделей семейства. В то время как в других моделях максимальная такто- тактовая частота таймеров/счетчиков Т/СО и Т/С1 равна СК, Т/С1 модели Tinyl5 может работать на частоте, превышающей СК. Частота тактового сигнала таймера может принимать значения 16СК, 8СК, 4СК, 2СК, а так- также СК и меньше. Эти установки приведены на Рис. 5.14, где описывается назначение битов регистра управления T/Cl TCCR1. Таймер может рабо- работать на частоте, большей СК, потому что в микроконтроллере имеется так называемый быстрый тактовый сигнал (РСК), частота которого равна 16СК. Значения 8СК и 4СК получаются предварительным делением этого тактового сигнала. Поскольку Т/С1 является 8-битным, формируемый ШИМ-сигнал то- тоже имеет разрядность 8 бит. Вместо того чтобы при генерации ШИМ-сиг- нала выполнять счет в обоих направлениях, Т/С1 всегда считает только в прямом направлении, а состояние вывода ОС1 изменяется при достиже- достижении таймером максимального значения. Это значение хранится в регистре ввода/вывода OCR1B. Как и в остальных микроконтроллерах, предусмот- предусмотрена защита от появления случайных выбросов (глитчей), т.е. обновление содержимого OCR1A происходит при достижении таймером Т/С1 макси- максимального значения, как показано на Рис. 5.15. Видимо решив, что этого мало, разработчики добавили в Tiny 15 новый регистр ввода/вывода с загадочным названием SFIOR ($2С) — регистр специальных функций. Этот регистр позволяет сбрасывать предделителъ любого таймера/счетчика. Что же, черт возьми, это означает? Давайте пос- посмотрим, как работает предделитель. Вообще говоря, предделитель -167-
Глава 5. Продвинутые возможности TCCR1 — регистр управления таймера/счетчика 1 ($30) в модели Tinyl 5 Бит 7 б 5 4 Название СТС PWM1 СОМ1А1 СОМ 1 АО ADIE 3 ADIE 2 1 ( ADPS2 ADPS1 У I I I ) \DPS0 I Выбор источника тактового сигнала Т/С1 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 Т/С1 остановлен Частота Т/С1 равна 16хСК Частота Т/С1 равнавхСК Частота Т/С1 равна4хСК Частота Т/С1 равна 2 х СК Частота Т/С1 равна СК Частота Т/С1 равна СК/2 Частота Т/С 1 равна СК/4 Частота Т/С1 равна СК/8 Частота Т/С1 равнаСК/16 Частота Т/С1 равна СК/32 Частота Т/С1 равна СК/64 Частота Т/С1 равна СК/128 Частота Т/С1 равна СК/256 Частота Т/С1 равнаСК/512 Частота Т/С1 равна СК/1024 В режиме ШИМ Таймер отключен от вывода ОС1 Таймер отключен от вывода ОС1 ОС1 сбрасывается при совпадении, устанавливается при переполнении Т/С1 ОС1 устанавливается при совпадении, сбрасывается при переполнении Т/С1 Не в режиме ШИМ Таймер отключен от вывода ОС1 ОС1 переключается при возникновении прерывания по совпадению ОС1 сбрасывается при возникновении прерывания по совпадению ОС1 устанавливается при возникновении прерывания по совпадению Разрешение ШИМ: 0 — режим ШИМ выключен 1 — режим ШИМ (8-битный) включен Сброс таймера/счетчика при совпадении: 0 — не сбрасывать Т/С1 при событии «совпадение» 1 — сбрасывать Т/С 1 в $00 при событии «совпадение» Рис. 5.74. Регистр TCCR1 -168-
Программа О. Конвертер клавиатуры OCR1B Вывод ОС 1А Рис. 5.15. Формирование ШИМ-сигнала представляет собой 10-битный регистр, содержимое которого декременти- руется с частотой СК. Когда, к примеру, тактовая частота Т/С1 задается равной СК/2, на вход таймера подается сигнал с 0-го бита предделителя. Если тактовая частота Т/С1 задается равной СК/64, на вход таймера пода- подается сигнал с 5-го бита и т.д. (см. Рис. 5.16). ск Предделитель Рис. 5.16. Регистр SFIOR Когда вы сбрасываете предделитель, вы загружаете в него число 0x00, обеспечивая более точный счет. Скажем, вы собираетесь установить такто- тактовую частоту Т/СО, равную СК/1024. При работе в установившемся ре- режиме процесс счета будет точным, однако в самом начале работы никто не гарантирует, что содержимое предделителя равно 1023, поэтому первое изменение Т/СО может произойти гораздо быстрее, чем это ожидалось. Для сброса предделителя Т/СО просто установите бит 0 регистра SFIOR (потом бит сбросится автоматически). Для сброса предделителя Т/С1 уста- установите бит 1 регистра SFIOR. И наконец 2-й бит регистра позволяет при- принудительно установить вывод ОС1А в состояние, определяемое 4-м и 5-м битами регистра TCCR1. Другими словами, мы «обманываем» вывод, гово- говоря, что произошло событие «совпадение»; однако при этом прерывание не генерируется и таймер/счетчик Т/С 1 не сбрасывается. Хотя на момент написания этой книги микроконтроллер Tiny 15 был единственной моделью с таким таймером/счетчиком Т/С 1У можно ожидать, что подобный таймер появится и в других моделях микроконтроллеров AVR. -169-
Глава 5. Продвинутые возможности Сокращение объема кода Существует несколько способов, позволяющих сделать программу стройной и изящной. Одним из наиболее простых способов является ис- использование директивы ассемблера .macro. Эта директива позволит вам создавать, по сути дела, свои собственные команды. .macro nopnop ; Имя макроса - порпор rjmp PC+1 .endmacro Теперь в теле программы вы можете написать команду порпор, и ассемблер интерпретирует ее как команду rjmp PC+1. Почему я назвал свою команду порпор? Переход на следующую строку программы с по- помощью команды rjmp занимает два такта, поскольку команда rjmp выпол- выполняется в 2 раза дольше, чем большинство остальных команд. Таким обра- образом, запись rjmp PC+1 эквивалентна двум пор, но занимает одну команду. Также макрос может иметь операнды, обращение к которым производится как@0, @1 и т.д. Соответственно, если нам потребуется умножить число в регистре Seconds на число в регистре Counter, мы просто сможем написать в про- программе: multiply Seconds,Counter Замечу, что в макросах можно спокойно использовать метки —- при ас- ассемблировании они будут транслированы в относительные смещения. В результате исключается сама возможность совпадения меток, поэтому такие макросы можно вызывать в программе многократно. j&S УПРАЖНЕНИЕ 5.6. Напишите макрос с именем skeq, пропускаю- *^~^ щий следующую команду, если флаг нуля установлен. — 170 — ПРИМЕР 5.1. В начале программы напишите следующие строки: ПРИМЕР 5.2 .macro multiply ; Имя макроса - multiply mov temp,@0 ; clr @0 ; Сбрасываем регистр результата tst @1 ; Проверяем множитель breq PC+4 add @0,temp ; Прибавляем множимое к самому себе dec @1 rjmp ¦ PC-4 ; Повторяем .endmacro
Программа О. Конвертер клавиатуры rgS УПРАЖНЕНИЕ 5.7. Напишите макрос с именем HiWait, ждущий ^^ установки в 1 заданного бита регистра ввода/вывода. Важно четко различать макросы и подпрограммы. Макрос является простейшим способом сокращения большей или меньшей части кода в од- одно действие, обозначенное коротким выражением. Ассемблер преобразует это выражение в последовательность команд, так что в конечном счете программа будет такого же размера, как и без использования макроса (но вы этого никогда не увидите). Использование подпрограмм действительно сделает вашу программу короче (т.е. она будет занимать меньше места в памяти программ), но, возможно, замедлит ее выполнение. Команда rcall выполняется за три такта, а команда ret — за четыре, так что подпрограм- подпрограммы буквально «пожирают» время, если используются для реализации действительно коротких задач. Обзор семейства Mega Освещение огромного числа новых функций, появившихся в микро- микроконтроллерах AYR семейства Mega, не входит в задачу данной книги. Тем не менее, я скажу об этих микроконтроллерах несколько слов, чтобы вы могли, по крайней мере, решить, стоит ли изучать их дальше. Прежде все- всего, следует отметить, что микроконтроллеры этого семейства имеют боль- большее количество всего того, что мы рассматривали до сих пор: больше тай- таймеров, больше каналов ШИМ, больше каналов АЦП, больше контактов ввода/вывода, больше памяти и больше команд. Новые команды делятся на три категории. Появление некоторых но- новых команд связано с встроенным аппаратным умножителем — специаль- специальным модулем, выполняющим операцию умножения за два такта. Команда mul используется для перемножения содержимого двух рабочих регистров. Также предусмотрены специальные версии этой команды (знаковое/без- (знаковое/беззнаковое/дробное и т.д.). Команды call и jmp предназначены для непос- непосредственного вызова подпрограмм и непосредственного перехода. Для пользователя единственное отличие заключается в том, что у него появля- появляется возможность выполнять переходы или вызовы подпрограмм из любого места программы. Тем не менее, пока вы не будете писать действительно большие программы, вы, скорее всего, и не заметите этого ограничения семейств Tiny и Classic. В числе новых команд также имеются команды до- доступа к памяти, наиболее значимая из которых — stm. Данная команда со- сохраняет значение, хранящееся в регистрах R0 и R1, в памяти программ. Благодаря такой возможности программа может самостоятельно изменять свой код! Другой полезной особенностью большинства новых микроконтролле- микроконтроллеров AVR является наличие интерфейса JTAG. Это стандарт, разработанный -171-
Глава 5. Продвинутые возможности для облегчения отладки цифровых устройств. Интерфейс JTAG позволяет микроконтроллеру посылать содержимое своих регистров (ввода/вывода, рабочих, статического ОЗУ) на ПК, так что вы сможете наблюдать процес- процессы, происходящие внутри микроконтроллера во время его работы в соста- составе реального устройства. Заключительная программа Р. Робот, управляемый компьютером • Последовательный обмен • ШИМ для управления двигателем • Семисегментные индикаторы для отображения сообщений В качестве заключительного проекта я выбрал очень интересную зада- задачу создания робота, управляемого компьютером. В этом проекте объеди- объединены сразу несколько тем, рассмотренных в книге. Устройство, которое мы будем разрабатывать, — это своего рода основа, на базе которой можно построить робота с ограниченными интеллектуальными возможностями. Мы можем посылать роботу команды, соединив последовательный порт компьютера с модулем UART микроконтроллера. Скорость двигателя бу- будет регулироваться с помощью ШИМ, а для отображения сообщений бу- будет использоваться дисплей, состоящий из семисегментных индикаторов, что позволит роботу «говорить». Разумеется, можно добавить еще ряд ин- интересных функций, таких как использование EEPROM для запоминания перемещений и применение музыкальных модулей. Можно разместить на роботе датчики, чтобы он отсылал в компьютер информацию, зависящую от состояния этих датчиков. Еще интереснее было бы разработать более сложную программу для ПК, под управлением которой робот смог бы вес- вести себя подобно конечному автомату и отвечать на различные входные воздействия, однако такая задача выходит за рамки данной книги. Прин- Принципиальная схема базового робота приведена на Рис. 5.17. Оба двигателя управляются ШИМ-сигналом, поступающим с вывода ОСЬ Чтобы робот мог поворачиваться, предусмотрена возможность вы- выключения левого двигателя путем установки на выводе PD2 напряжения ВЫСОКОГО уровня. Таким образом, робот может вращаться только в од- одном направлении, однако нам этого достаточно. Более совершенные мо- модели микроконтроллеров AVR, такие как 8515, имеют два выхода ШИМ (выводы ОС1А и ОС1В), т.е. при использовании таких моделей можно управлять двигателями независимо друг от друга. Команды, которые мы будем посылать роботу, приведены в Табл. 5.1. -172-
Заключительная программа Р. Робот, управляемый компьютером — 173 — Рис. 5.17. Принципиальная схема базового робота
Глава 5. Продвинутые возможности Таблица 5.1. Команды, посылаемые роботу Буква g t + - s [ ] Код ASCII 0x67 0x74 0х2В 0x2D 0x73 0х5В 0x5D Функция Старт/Стоп Начать/завершить поворот (вкл/выкл левый двигатель) Увеличить скорость Уменьшить скорость Изменить скорость (сопровождается двухразрядным числом, например, s25) Начало сообщения (которое должно быть отображено на дисплее) Конец сообщения Сообщение ПК «Go» или «Stop» «Turning» «Speeding up» «Slowing down» «Speed set to...» <сообщение> Остальные символы мы будем игнорировать. При получении каждой команды робот будет отсылать в компьютер соответствующее подтвержде- подтверждение. Например, если был послан символ «t», робот ответит «Turning». На дисплее можно отобразить не все символы — чтобы иметь возможность отображать на дисплее все необходимые нам символы, нужен более совер- совершенный дисплей (например, 14-сегментный). Но поскольку у нас его нет, мы не сможем отображать символы «k», «m», «q», «v», «w» и «х». Структура программы очень проста: все управление осуществляется в прерываниях. При возникновении прерывания по приему UART програм- программа идентифицирует принятый символ и посылает соответствующий ответ. Для упрощения подпрограммы Display мы можем сделать так, чтобы она вызывалась при каждом переполнении Т/СО. Это не только избавит нас от необходимости заботиться о периодическом вызове этой подпрограммы, но и позволит исключить регистр счетчика, отвечающий за то, чтобы обра- обработка дисплея осуществлялась при каждом 50-м вызове подпрограммы. Соответственно, мы должны сконфигурировать Т/СО таким образом, что- чтобы его переполнение происходило достаточно часто. Обновление дисплея должно происходить не реже 25 раз в секунду, а учитывая наличие 4 инди- индикаторов, подпрограмма Display должна вызываться, по крайней мере, 100 раз в секунду. Поскольку период переполнения таймера Т/СО равен 256 тактам, минимальная частота Т/СО должна быть равна 25.6 кГц. При использовании резонатора на 2.4576 МГц такая частота соответствует уста- установке предделителя в режим СК/64. В секции Ink сконфигурируйте входы, выходы и таймер/счетчик Т/СО. Задайте частоту Т/С 1, равную СК, и сброс ОС1 при достижении порогово- порогового значения таймером Т/С1 при прямом счете и установке в 1 при обрат- обратном счете (это означает, что, чем больше число в регистре OC1AH/L, тем -174-
Заключительная программа Р. Робот, управляемый компьютером больше скорость двигателя). Выключите пока ШИМ (ШИМ будет вклю- включен после приема от компьютера символа «g»). He забудьте инициализиро- инициализировать регистры стека. В модели 2313 этим регистром является только SPL, в который вы должны загрузить константу RAM END. Разрешите прерыва- прерывание по приему UART и разрешите прием. Задайте скорость передачи, рав- равную 9600, и разрешите прерывания. Доработайте подпрограмму Display из предыдущего проекта, так, что- чтобы она поддерживала четыре индикатора. Семисегментные коды отобра- отображаемых символов будут храниться в регистрах R21...R24. Обратите внима- внимание: поскольку эти рабочие регистры будут хранить семисегментный код, их содержимое можно непосредственно пересылать в регистр PortB. /gS УПРАЖНЕНИЕ 5.8. Внесите необходимые изменения в подпро- ^^ грамму Display. При осуществлении прерывания по завершении приема необходимо прежде всего понять, как воспринимать принятые данные — как команду или как часть текстового сообщения. Для индикации текстового сообще- сообщения будет использоваться бит Т регистра SREG (т.е. команда начала пере- передачи «[» установит бит Т, а команда конца передачи «]» сбросит его). В сек- секции Init этот бит необходимо сбросить. Таким образом, обработка преры- прерывания должна начинаться с проверки на прием символа конца сообщения и выполнять переход к метке EndMessage, если это так. Далее следует про- проверить состояние бита Т и, если он установлен, перейти к метке Message. Остальные символы («g», «t», «+»,«—») можно проверять в любом порядке; тем не менее, проверку на символ «[» удобнее всего выполнять в послед- последнюю очередь. Если принят символ «[», необходимо установить бит Т. Ос- Остальные символы следует игнорировать. Секция Turning должна прежде всего изменить состояние вывода PD2, который управляет работой левого мотора. После этого следует выключить приемник UART и включить передатчик. Загрузите в регистр temp код ASCII буквы «Т», а затем вызовите подпрограмму Send. Эта подпрограмма будет передавать при помощи модуля UART байт, находящийся в регистре temp; саму подпрограмму мы напишем позже. Мы также должны переда- передавать символ новой строки (называемый также переводом строки) и символ возврата каретки, для того чтобы каждое сообщение, посылаемое в ПК, печаталось в новой строке. Коды этих символов — ОхОА и OxOD соответ- соответственно, однако они будут одинаковыми для всех посылок. Поэтому после посылки символа «g» просто перейдем к секции EndMessage, в которой выполним действия, общие для всех посылок. В этой секции мы сбросим флаг Т, передадим символы ОхОА и OxOD, a затем выключим передатчик и включим приемник. -175-
Глава 5. Продвинутые возможности Подпрограмма Send должна копировать содержимое temp в регистр UDR, а затем в цикле проверять состояние флага завершения передачи (бит ТХС в регистре UCSR). В теле этого цикла вы не должны осуществ- осуществлять запись в регистр UDR (т.е. команда перехода в конце цикла должна ссылаться не на Send, а на Send + 1), так как эта операция сбрасывает флаг ТХС, а это означает, что мы останемся в цикле навсегда. После установки флага ТХС вы должны его сбросить, записав в него 7, а затем выйти из подпрограммы. Секция SpeedUp будет считывать число из регистра OCR1AL и увели- увеличивать его на 10 (при установке флага переноса число принимается рав- равным OxFF), а затем заносить обратно в OCR1AL. Отмечу, что вы не смо- сможете использовать команду: subi temp,-10 ; поскольку в действительности эта команда прибавляет к регистру temp число 246, что практически всегда будет приводить к установке флага пе- переноса. Поэтому вы должны загрузить число 10 в какой-либо рабочий ре- регистр, а затем сложить его с регистром temp, используя команду add. В ка- качестве альтернативного решения можно было бы воспользоваться регист- регистром ZL и командой adiw. Затем вы должны выполнить те же действия, что и в секции Turning, чтобы отослать соответствующее сообщение компьютеру. Аналогично, в секции SlowDown регистр OCR1AL уменьшается на 10 и, ес- если результат получился отрицательным, сбрасывается в 0. Посылка ответа компьютеру выполняется обычным образом. Секция GoStop немного сложнее. Сначала вы должны определить со- состояние ШИМ (т.е. включен ли он) путем проверки бита 0 регистра TCCR1A. Если ШИМ включен, выключите его и отошлите в компьютер сообщение «STOP!». Если же он выключен, выполните переход к метке Go. В этой секции следует включить 8-битный ШИМ (установить бит 0 регистра TCCR1A) и послать компьютеру сообщение «GO!». В секции ChangeSpeed необходимо дождаться приема двух дополни- дополнительных символов (значение скорости). Секция должна начинаться с цик- цикла ожидания первого символа (т.е. ожидания установки бита RXC регистра USR). Первая принятая цифра должна быть помещена в рабочий регистр speed 10. Это число следует скопировать во временный регистр и вычесть из него 0x30. В результате из кодов ASCII символов «0»...«9» мы получаем собственно числа 0...9. Итоговое число следует умножить на 10, так как это разряд десятков. Потом необходимо принять следующую цифру и сохра- сохранить ее в регистре speed 1. Также преобразовываем ее в число (вычитаем 0x30) и прибавляем результат к десяткам. Нам важно сохранить неизмен- неизменными значения speed 10 и speed 1, так как они потребуются для посылки от- ответа компьютеру. Полученное число может принимать значения из диапа- -176-
Заключительная программа Р. Робот, управляемый компьютером зона 0...99. Нам хотелось бы преобразовать его в число из диапазона 0...255 — проще всего будет умножить исходное значение на 3, ограничи- ограничивая результат числом 255. Полученное число должно быть помещено в ре- регистр OCR1AL. После этого необходимо послать ответ вида «Speed Set to хх», где хх — новое значение скорости. Для передачи букв мы загружаем их коды ASCII в регистр temp, как и раньше. Для передачи значения скорости достаточно скопировать speed 10 или speed 1 в temp и вызывать подпрограм- подпрограмму Send. После посылки числа speed 1 эта секция должна перейти на EndMessage. И наконец, самая сложная секция — Message. В этой секции осуществ- осуществляется преобразование принятых символов из кода ASCII в код семисег- ментного индикатора и прокручивание результата на дисплее при приеме очередного символа. Регистры дисплея назовем Thousands, Hundreds, Tens и Ones. При поступлении нового символа Hundreds копируется в Thousands, Tens — в Hundreds, Ones — в Tens, а новое значение сохраняется в Ones. Однако сначала мы должны преобразовать ASCII-код в код семи- сегментного индикатора. Мы будем отображать только цифры от 0 до 9, строчные буквы от «а» до «z» и заглавные буквы от «А» до «Z», за исключе- исключением указанных ранее. В случае, если строчную букву отобразить нельзя, а заглавную — можно (например, «е» и «Е»), возвращается код для заглав- заглавной буквы. Таким образом гарантируется, что программа будет пытаться вывести буквы требуемого размера, однако приоритет отдается буквам, получившимся после обработки. Как вы могли догадаться, это преобра- преобразование осуществляется с помощью одной большой таблицы. Первая зада- задача — просто ответить компьютеру только что принятым символом. Это очень легко: достаточно скопировать UDR в ZL, выключить передатчик, включить приемник, скопировать ZL в temp, а затем вызвать подпрограм- подпрограмму Send. После этого надо перейти обратно в режим приема и выключить передатчик, а затем вычесть 0x10 из ZL. Коды цифр начинаются с числа 0x30, так что после вычитания символу «0» будет соответствовать число 0x20, и т.д. Это адрес байта, а для получения адреса слова полученное зна- значение необходимо разделить на 2, т.е. символу «0» соответствует код, рас- расположенный по адресу слова 0x10. Начиная с этого адреса, можно помес- поместить нашу таблицу, указав перед ней директиву .org 0x10. Первые пять 2-байтных слов нашей таблицы будут соответствовать числам от 0 до 9. Обязательно занесите в таблицу самостоятельно определенные значения кодов, а не копируйте их из моей программы, так как разводка вашей пе- печатной платы может отличаться от моей. Коды ASCII букв «A»...«Z» начи- начинаются с числа 0x41. Вместо того чтобы заполнять таблицу пустыми стро- строками, просто напишите .org 0x18, указывая компилятору поместить новую часть таблицы по адресу 0x18 памяти программ. При побайтной адресации эта позиция имеет адрес 0x30, который соответствует символу с ASCII-ko- -177-
Глава 5. Продвинутые возможности дом 0x40. Первый байт в этой части таблицы может быть любым, однако значение второго должно соответствовать букве «А» и т.д. И наконец, коды ASCII букв «a»...«z» начинаются с числа 0x61, поэтому перед частью табли- таблицы, относящейся к строчным буквам, необходимо написать директиву .org 0x28. При отладке программы я понял, что символ пробела является очень важным символом. Код ASCII этого символа @x20) соответствует адресу 0x10 при побайтной адресации и адресу 0x08 при пословной адресации. Та- Таким образом, по адресу 0x08 можно просто поместить команду пор (эта ко- команда транслируется ассемблером в код 0x0000). Код этой команды будет считываться, как и все остальные байты, возвращая число 0Ь00000000, при котором все сегменты индикатора будут выключаться (т.е. будет выводить- выводиться пробел). По адресу 0x08 располагается вектор неиспользуемого нами прерывания «регистр данных UART пуст», так что мы спокойно можем рас- расположить по этому адресу команду пор. Если же в силу каких-либо непред- непредвиденных обстоятельств это прерывание все же возникнет, то ничего страшного не произойдет — просто будет выполнена команда пор, а затем команда reti, расположенная по адресу 0x09. Таким образом, программа ос- останется устойчивой к непредвиденной генерации прерывания «регистр данных UART пуст». Заканчивается секция Message считыванием данных из памяти программ и перемещением значений между регистрами. Вот и вся программа. Моя версия этой программы приведена в Прило- Приложении J {Программа Р). Я надеюсь, что вы все-таки попытаетесь собрать это устройство и продолжите работу по его усовершенствованию, чтобы оно стало более похожим на настоящего робота. Это действительно хоро- хорошая база для множества интересных проектов. Заключение Я предлагаю вам при отладке своих программ поступать следующим образом. Во-первых, попытайтесь разбить программу на обособленные блоки, которые могут быть проверены независимо друг от друга. Это по- поможет вам быстро локализовать ошибки. Другой досадной проблемой мо- может стать невозможность просмотра содержимого регистров микроконт- микроконтроллера в процессе выполнения программы. Для решения этой задачи лучше всего использовать эмулятор, однако имеется и более простой спо- способ. В определенных местах программы можно посылать содержимое вы- выбранных регистров через UART в компьютер и наблюдать, как они изме- изменяются. Внедрение в программу модуля передачи UART, как правило, тре- требует дополнительных усилий, однако это решение позволит получить информацию о том, что происходит внутри AVR — аналогично JTAG или эмулятору. -178-
Заключение На протяжении всей книги не раз встречались ситуации, в которых мы пытались решать какую-либо задачу, пользуясь ограниченными возмож- возможностями, а затем изучали новые средства, которые позволяли нам решить эту же задачу гораздо проще. Очень часто получается так, что, чем сложнее становятся микроконтроллеры, тем проще становятся программы. Эта си- ситуация подводит нас к пониманию компромисса, на который вынуждены идти разработчики микроконтроллеров, стоящие перед необходимостью увеличения функциональных возможностей микроконтроллера, сохраняя его относительно простым. Эта простота необходима не только для низкой стоимости микроконтроллера, но и для того, чтобы легче было захватить рынок. Я нисколько не сомневаюсь, что в новых моделях AVR, которые появятся после выхода этой книги, будут новые возможности. Эти воз- возможности обязательно будут группироваться вокруг определенных регист- регистров ввода/вывода, возможно имеющих биты, управляющие различными аспектами функционирования новых блоков. Всю эту информацию мож- можно получить из документации на микроконтроллеры, которая теперь не должна казаться вам такой страшной, как раньше, когда вы только начали изучать микроконтроллеры AVR. При чтении документации удостоверь- удостоверьтесь, что в вашем распоряжении имеется самая последняя версия, — это облегчит вам жизнь как программисту! -179-
Приложение А ОСНОВНЫЕ ПАРАМЕТРЫ НЕКОТОРЫХ МОДЕЛЕЙ AVR Модель Tinyll Tiny 12 Tinyl5 1200 2313 2323 2343 4433 8515 8535 Число выводов 8 8 8 20 20 8 8 28 40 40 Число контактов ввода/вывода 6 6 6 15 15 3 4 20 32 32 FLASH [байт] 1К 1К 1К 1К 2К 2К 2К 4К 8К 8К ОЗУ [байт] - - 128 128 128 128 512 512 EEPROM [байт] 64 64 64 128 128 128 256 512 512 Особенности 8-битный таймер, WDT, аналоговый компаратор, 4 прерывания, встроенный генератор Как у Tiny 11,5 прерываний Как у Tinyll, два 8-битных таймера, 4 канала АЦП, 8 прерываний, ШИМ Как у Tiny 11,3 прерывания Расширенный набор команд, 10 прерываний, UART, 8-битный и 16-битный таймеры, ШИМ,\\Т>Т, аналоговый компаратор Расширенный набор команд, 2 прерывания, 8-битный таймер, WDT Как у 2313, встроенный генератор Расширенный набор команд, 14 прерываний, SPI, UART, 8-битный и 16-битный таймеры, IUHM,WDT, аналоговый компаратор, 6 каналов 10-битного АЦП Расширенный набор команд, 11 прерываний, SPI, UART, 8-битный и 16-битный таймеры, UIHM,WDT, аналоговый компаратор Каку8515,15 прерываний, два 8-битных таймера, 3 ШИМ, таймер часов реального времени, 8 каналов 10-битного АЦП -180-
Приложение В ЦОКОЛЕВКА НЕКОТОРЫХ МОДЕЛЕЙ AVR Вид сверху AT90S1200 AT90S2313 AT90S8515 Tiny 10 AT90S8535 Tiny12 Tiny 15 -181-
Приложение С ОБЗОР СИСТЕМЫ КОМАНД ПОДПРОГРАММЫ | PUSH I сохранение ; регистра в стеке POP | извлечение регистра и#зстека РЕГИСТРЫ ВВОДА/ВЫВОДА _ СВР сброс бита РВВ SBI* установка бита РВВ IN загрузка РВВ в регистр OUT сохранение регистра в РВВ ВЕТВЛЕНИЕ С А 1.1... абсол ют н ы й вы зов ICALL к'осненный вызов RCALL относительный вызов RET возврат RETI возврат с разрешением прерываний .IMP абсолютный переход IJMP косвенный переход RJMP относительный переход BRBC переход, если бит SREG сброшен BRBS переход, если бит SREG установлен SBIC* пропуск, если бит РВВ сброшен SBIS* пропуск, если бит РВВ установлен SBRC пропуск, если бит регистра сброшен SBRS пропуск, если бит регистра установлен SREG BRBC сброс бита в SREG BRBS установка бита в SREG j I SBIC загрузка ! i бита из Т j -182-
Приложение С. Обзор системы команд ОБЩЕГО НАЗНАЧЕНИЯ ОЗУ NOP нет операции — пропуск такта WDR сброс сторожевого таймера SLEEP перевести в спящий режим АРИФМЕТИКА ADC ADD ADIW DEC INC LDP* MUL SBC SBCI** SBIW SUB SUBI** COM NEG CLR SER** SWAP сложение двух регистров с учетом переноса сложение двух регистров сложение регистровой пары с константой декрементирование регистра инкрементирование регистра загрузка константы в регистр умножение двух регистров вычитание двух регистров с учетом переноса вычитание константы из ре- регистра с учетом переноса вычитание константы из ре- регистровой пары вычитание двух регистров вычитание константы из регистра инвертирование всех битов регистра изменение знака сброс всех битов регистра установка всех битов регистра перестановка тетрад регистра Примечания: Команды, выделенные серым цветом, поддерживаются не всеми моделями. * Эти команды применимы только к ре- регистрам ввода/вывода с адресами $00...$ 1F. ** Эти команды применимы только для ре- регистров R16... R31. LD косвенное чтение из стати- статического ОЗУ ST косвенная запись в статичес- статическое ОЗУ LDS непосредственное чтение in статического ОЗУ STS непосредственна51 запись в статическое ОЗУ LPM к ос не в мое чт ей ие из па мяти программ ЛОГИКА AND ANDF* EOR OR ORI** «Логическое И» двух регистров «Логическое И» регистра и константы «Исключающее ИЛИ» двух регистров «Логическое ИЛИ» двух регистров «Логическое ИЛИ» регистра и константы СДВИГ ASR LSR LSL ROL ROR арифметический сдвиг вправо логический сдвиг вправо логический сдвиг влево сдвиг влево через перенос сдвиг вправо через перенос СРАВНЕНИЕ СР сравнение двух регистров СРС сравнение двух регистров с учетом переноса CPI* сравнение регистра с константой CPSE сравнение и пропуск, если равно -183-
Приложение С. Обзор системы команд ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ CBR SBR TST BRCC BRCS BRSH BRLO BRNE BREQ BRPL BRMI BRVC BRVS BRLT BRGE BRHC BRHS BRTC BRTS Команда Rd,Obxxxxxxxx Rd,Obxxxxxxxx Rd <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> <метка> Описание Сброс бита(ов) в регистре Установка бита(ов) в регистре Проверка на нуль или минус Переход к <метка>, если флаг С сброшен Переход к <метка>, если флаг С установлен Переход, если «больше или равно» (С = 0) Переход, если «меньше» (С = 1) Переход, если «не равно» (Z = 0) Переход, если «равно» (Z = 1) Переход, если «положит, значение» (N = 0) Переход, если «отрицат. значение» (N = 1) Переход к <метка>, если флаг V сброшен Переход к <метка>, если флаг V установлен Переход, если «меньше чем» (S = 0) Переход, если «больше или равно» (S = 1) Переход к <метка>, если флаг Н сброшен Переход к <метка>, если флаг Н установлен Переход к <метка>, если флаг Т сброшен Переход к <метка>, если флаг Т установлен Эквивалентная команда ANDI ORI AND BRBC BRBS BRBC BRBS BRBC BRBS BRBC BRBS BRBC BRBS BRBC BRBS BRBC BRBS BRBC BRBS Rd,0bxxxxxxxx Rd,0bxxxxxxxx Rd,Rd 0,<метка> 0,<метка> 0,<метка> 0,<метка> 1,<метка> 1,<метка> 2,<метка> 2,<метка> 3,<метка> 3,<метка> 4,<метка> 4,<метка> 5,<метка> 5,<метка> 6,<метка> 6,<метка> -184-
Приложение С. Обзор системы команд ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ Команда BRID <метка> BRIE <метка> CLC CLZ CLN CLV CLS CLH CLT CLI SEC SEZ SEN SEV SES SEH SET SEI Описание Переход к <метка>, если прерывания запрещены Переход к <метка>, если прерывания разрешены Сброс флага переноса Сброс флага нуля Сброс флага отрицательного числа Сброс флага переполнения дополнительного кода Сброс флага знака Сброс флага половинного переноса Сброс флага Т Сброс флага I (глобальное запре- запрещение прерываний) Установка флага переноса Установка флага нуля Установка флага отрицательного числа Установка флага переполнения дополнительного кода Установка флага знака Установка флага половинного переноса Установка флага Т Установка флага I (глобальное разрешение прерываний) Продолжение Эквивалентная команда BRBC 7,<метка> BRBS 7,<метка> BCLR 0 BCLR 1 BCLR 2 BCLR 3 BCLR 4 BCLR 5 BCLR 6 BCLR 7 BSET 0 BSET 1 BSET 2 BSET 3 BSET 4 BSET 5 BSET 6 BSET 7 Команды, выделенные серым цветом, обычно использу- используются после команд сравнения или вычитания, таких как CP,CPI,SUBhSUBI -185-
Приложение D СПРАВОЧНИК КОМАНД В данном приложении приведены все команды, используемые микро- микроконтроллерами AVR семейств Classic и Tiny. Микроконтроллеры семейства Mega поддерживают несколько больше команд (включая, например, ко- команду умножения). При описании команд используются следующие обозначения: reg — любой из 32 рабочих регистров hreg — любой из старших рабочих регистров (R16...R31) ioreg — любой из 64 регистров ввода/вывода lioreg — младшая половина регистров ввода/вывода ($O...$1F) longreg— один из 16-битных регистров, т.е. X, Y или Z number— константа Команда adc regl, reg2 add regl, reg2 adiw longreg, number and regl, reg2 andi hreg, number asr reg bdr bit bid reg, bit Описание Складывает содержимое регистров regl и reg2 и прибавляет флаг переноса, со- сохраняя результат в регистре regl Складывает содержимое регистров regl и reg2, сохраняя результат в регистре regl Прибавляет константу @...63) к содер- содержимому регистровой пары (одного из 16-битных регистров) (X, Y, Z) Выполняет операцию «Логическое И» между содержимым регистров regl и reg2, сохраняя результат в регистре regl Выполняет операцию «Логическое И» между содержимым старшего регистра hreg и константой @...255), сохраняя результат в этом же регистре Выполняет арифметический сдвиг вправо содержимого регистра reg G-й бит не изменяется) Сбрасывает в 0 заданный бит регистра SREG Копирует флаг Т регистра SREG в за- заданный бит регистра Флаги [HSVNZC] [HSVNZC] [SVNZC] [SVNZ] [SVNZ] [SVNZC] [ITHSVNZC] И Примечание Кроме моделей 1200 и Tiny -186-
Приложение D. Справочник команд Продолжение Команда brbc bit, label brbs bit, label brcc label brcs label breq label brge label brhc label brhs label brid label brie label brio label brlt label brmi label brne label Описание Проверяет состояние бита регистра SREG и переходит к метке label, если бит сброшен. Метка должна находить- находиться в пределах 63 команд от команды brbc Проверяет состояние бита регистра SREG и переходит к метке label, если бит установлен. Метка должна нахо- находиться в пределах 63 команд от коман- команды brbs Проверяет флаг переноса (С) и перехо- переходит, если он сброшен Проверяет флаг переноса (С) и перехо- переходит, если он установлен Проверяет флаг нуля (Z) и переходит, если он сброшен (регистры равны) Проверяет флаг знака (S) и переходит, если он сброшен («больше или равно» для знаковых данных) Проверяет флаг половинного переноса (Н) и переходит, если он сброшен Проверяет флаг половинного переноса (Н) и переходит, если он установлен Проверяет флаг глобального разрешения прерываний (I) и переходит, если он сбро- сброшен Проверяет флаг глобального разреше- разрешения прерываний (I) и переходит, если он установлен Проверяет флаг переноса (С) и перехо- переходит, если он установлен («меньше» для беззнаковых данных) Проверяет флаг знака (S) и переходит, если он установлен («меньше» для зна- знаковых данных) Проверяет флаг отрицательного числа (N) и переходит, если он установлен («отрицательное значение») Проверяет флаг нуля (Z) и переходит, если он сброшен (регистры не равны) Флаги н [-] Примечание -187-
Приложение D. Справочник команд Продолжение Команда brpl label brsh label brtc label brts label brvc label brvs label bset bit bst reg, bit call label cbi lioreg, bit cbr reg, number dc dh cli dn dr reg ds dt dv Описание Проверяет флаг отрицательного числа (N) и переходит, если он сброшен («по- («положительное значение») Проверяет флаг знака (S) и переходит, если он сброшен («больше или равно» для беззнаковых данных) Проверяет флаг Т и переходит, если он сброшен Проверяет флаг Т и переходит, если он установлен Проверяет флаг V и переходит, если он сброшен Проверяет флаг V и переходит, если он установлен Устанавливает заданный бит регистра SREG в 1 Копирует заданный бит регистра в флаг Т регистра SREG Вызывает подпрограмму, обозначен- обозначенную меткой label. Метка может разме- размещаться в любом месте программы Сбрасывает в 0 заданный бит регистра ввода/вывода lioreg ($O...$1F) Сбрасывает в 0 биты регистра reg в со- соответствии с маской, задаваемой конс- константой number (сбрасываются только те биты, которые сброшены в маске) Сбрасывает флаг переноса С Сбрасывает флаг половинного перено- переноса Н Сбрасывает флаг общего разрешения прерываний I Сбрасывает флаг отрицательного зна- значения N Сбрасывает все биты регистра reg в 0 Сбрасывает флаг знака S Сбрасывает флаг Т Сбрасывает флаг переполнения V Флаги [ITHSVNZC] [Т] н н [SVNZ] [С] [Н] [I] [N] [SVNZ] [S] [Т] [V] Примечание Только для семейства Mega -188-
Приложение D. Справочник команд Продолжение Команда clz com reg ср regl,reg2 cpc regl,reg2 cpi hreg, number cpse regl,reg2 dec reg eor regl,reg2 icall ijmp in reg,ioreg inc reg jmp label Описание Сбрасывает флаг нуля Z Вычисляет обратный код содержимого регистра reg (инвертирует все биты) Сравнивает содержимое регистров regl и reg2 путем вычитания reg2 из regl. Содержимое регистров не изменяется Сравнивает содержимое регистров regl и reg2 с учетом переноса путем выпол- выполнения операции (regl - reg2 - С). Содержимое регистров не изменяется Сравнивает содержимое регистра hreg с константой number путем вычитания константы из регистра. Содержимое регистров не изменяется Сравнивает содержимое двух регистров и пропускает следующую команду, если в регистрах находятся одинаковые зна- значения Декрементирует (уменьшает на едини- единицу) содержимое регистра reg, записы- записывая результат обратно в регистр Выполняет операцию «Исключающее ИЛИ» между содержимым регистров regl и reg2, сохраняя результат в регист- регистре regl Выполняет косвенный вызов подпро- подпрограммы, адрес которой находится в ре- регистре Z Выполняет косвенный переход по ад- адресу, находящемуся в регистре Z Пересылает содержимое регистра ввода/вывода ioreg в рабочий регистр reg Инкрементирует (увеличивает на еди- единицу) содержимое регистра reg, запи- записывая результат обратно в регистр Выполняет переход к секции програм- программы, обозначенной меткой label. Метка может размещаться в любом месте про- программы Флаги И [SVNZC] [HSVNZC] [HSVNZC] [HSVNZC] [-] [SVNZ] [SVNZ] Н н [-] [SVNZ] н Примечание Кроме моделей 1200 и Tiny Кроме моделей 1200 и Tiny Только для семейства Mega -189-
Приложение D. Справочник команд Продолжение Команда Id reg, longreg Id reg, longreg+ ld reg, -longreg Idd reg, longreg+number ldi hreg, number Ids reg, number 1pm lsl reg lsr reg mov regl, reg2 neg reg Описание Загружает один байт из памяти данных (адрес ячейки памяти содержится в ре- регистре longreg) в рабочий регистр reg Загружает один байт из памяти данных (адрес ячейки памяти содержится в ре- регистре longreg) в рабочий регистр reg, после чего инкрементирует содержи- содержимое регистра longreg Декрементирует содержимое регистра longreg, после чего загружает один байт из памяти данных (адрес ячейки памя- памяти содержится в регистре longreg) в ра- рабочий регистр reg Загружает один байт из памяти данных по адресу, находящемуся в регистре Y или Z, в рабочий регистр reg, после че- чего увеличивает содержимое регистра longreg на значение number @...63) Загружает число number @...255) в стар- старший регистр hreg Загружает один байт из памяти данных по адресу number в рабочий регистр reg. Значение константы должно быть в диапазоне 0...65535, т.е. не более 64К Загружает один байт из памяти про- программ (адрес ячейки памяти содержит- содержится в регистре Z) в регистр R0 Выполняет логический сдвиг влево со- содержимого регистра reg G-й бит копи- копируется в флаг С, 0-й бит сбрасывается вО) Выполняет логический сдвиг вправо содержимого регистра reg @-й бит ко- копируется в флаг С, 7-й бит сбрасывает- сбрасывается в 0) Копирует содержимое регистра reg2 в регистр regl Вычисляет дополнительный код числа, содержащегося в регистре reg, и сохра- сохраняет его в регистре B0 преобразуется в -20, которое эквивалентно 236) Флаги [-] н н 1-1 [-] [-] н [SVNZC] [SVNZC] н [HSVNZC] Примечание Кроме моделей 1200 и Tiny Кроме моделей 1200 и Tiny Кроме моделей 1200 и Tiny Замечание: не работает с регистром X Кроме моделей 1200 и Tiny Кроме модели 1200 -190-
Приложение D. Справочник команд Продолжение Команда пор or regl, reg2 ori hreg, number out reg, ioreg pop reg push reg rcall label ret reti rjmp label rol reg ror reg Описание Пустая команда, которая ничего не де- делает — просто занимает один такт Выполняет операцию «Логическое ИЛИ» между содержимым регистров regl и reg2, сохраняя результат в регист- регистре regl Выполняет операцию «Логическое ИЛИ» между содержимым старшего регистра hreg и константой @...255), со- сохраняя результат в этом же регистре Пересылает содержимое рабочего ре- регистра reg в регистр ввода/вывода ioreg Извлекает один байт из стека в регистр reg Сохраняет содержимое регистра reg в стеке Вызывает подпрограмму, обозначен- обозначенную меткой label, которая должна нахо- находиться в пределах 2048 команд от ко- команды rcall (относительный вызов) Выполняет возврат из подпрограммы, переходя на команду, следующую за ис- исходной командой call Выполняет возврат из подпрограммы и устанавливает флаг общего разрешения прерываний Выполняет переход к секции програм- программы, обозначенной меткой label, кото- которая должна находиться в пределах 2048 команд от команды rjmp (относитель- (относительный переход) Выполняет циклический сдвиг влево содержимого регистра reg (флаг С за- загружается в 0-й бит регистра, а 7-й бит копируется в флаг С) Выполняет циклический сдвиг вправо содержимого регистра reg (флаг С за- загружается в 7-й бит регистра, а 0-й бит копируется в флаг С) Флаги н [SVNZ] [SVNZ] Н [-] Н н [-] [I] н [SVNZC] [SVNZC] Примечание Кроме моделей 1200 и Tiny Кроме моделей 1200 и Tiny -191-
Приложение D. Справочник команд Продолжение Команда sbc regl, reg2 sbci hreg, number sbi lioreg, bit sbic lioreg, bit sbiw longreg, number sbr reg, number sbrc reg, bit sbrs reg, bit sec seh sei sen ser reg ses set Описание Вычитает из регистра regl содержимое регистра reg2 и флаг переноса, сохраняя результат в регистре regl Вычитает константу number @...255) и флаг переноса из старшего регистра hreg, сохраняя результат в этом же регистре Устанавливает в 1 заданный бит регистра ввода/вывода lioreg ($O...$1F) Проверяет заданный бит регистра ввода/вывода lioreg ($O...$1F) и пропускает следующую команду, если он сброшен Вычитает константу @...63) из содержимого регистровой пары (X,Y,Z) Устанавливает в 1 биты регистра reg в соответствии с маской, задаваемой 8-битной константой number (устанавливаются только те биты, которые установлены в маске) Проверяет состояние заданного бита регистра reg и пропускает следующую команду, если он сброшен Проверяет состояние заданного бита регистра reg и пропускает следующую команду, если он установлен Устанавливает флаг переноса С Устанавливает флаг половинного переноса Н Устанавливает флаг общего разреше- разрешения прерываний I Устанавливает флаг отрицательного значения N Устанавливает все биты регистра reg Bl Устанавливает флаг знака S Устанавливает флаг Т Флаги [HSVNZC] [HSVNZC] Н Н [SVNZC] [SVNZ] [-] [-] [С] [Н] [I] [N] [SVNZ] [S] [Т] Примечание Кроме моделей 1200 и Tiny -192-
Приложение D. Справочник команд Продолжение Комацда sev sez sleep st reg, longreg st reg, Iongreg+ st reg, -Iongreg std reg, longreg+number sts reg, number sub regl,reg2 subi hreg, number swap reg Описание Устанавливает флаг переполнения V Устанавливает флаг нуля Z Переводит микроконтроллер в режим пониженного энергопотребления (выход из этого режима произойдет по сбросу или прерыванию) Сохраняет содержимое рабочего регистра reg в памяти данных (адрес ячейки памяти содержится в регистре longreg) Сохраняет содержимое рабочего регистра reg в памяти данных (адрес ячейки памяти содержится в регистре longreg), после чего инкре- ментирует содержимое регистра longreg Декрементирует содержимое регистра longreg, после чего сохраняет содержи- содержимое рабочего регистра reg в памяти данных (адрес ячейки памяти содержится в регистре longreg) Сохраняет содержимое рабочего ре- регистра reg в памяти данных по адресу, находящемуся в регистре Y или Z, пос- после чего увеличивает содержимое ре- регистра longreg на значение number @...63) Сохраняет содержимое рабочего регистра reg в памяти данных по адресу number. Значение константы должно быть в диапазоне 0...65535, т.е. не более 64К Вычитает из регистра regl содержимое регистра reg2, сохраняя результат в ре- регистре regl Вычитает из старшего регистра hreg значение константы number, сохраняя результат в этом же регистре Переставляет местами младший и стар- старший полубайты содержимого регистра reg Флаги [V] [Z] Н Н [-] [-] Н [-] [HSVNZC] [HSVNZC] [SVNZC] Примечание Кроме моделей 1200 и Tiny Кроме моделей 1200 и Tiny Кроме моделей 1200 и Tiny Замечание: не работает с регистром X Кроме моделей 1200 и Tiny -193-
Приложение D. Справочник команд Продолжение Команда tst reg wdr Описание Проверяет содержимое регистра reg на нулевое или отрицательное значение путем выполнения операции «Логичес- «Логическое И» регистра с самим собой (содер- (содержимое регистра не изменяется). Для за- завершения проверки необходимо затем проверить состояние флага нуля, ис- используя команды breq или Ьгпе Сбрасывает сторожевой таймер (эту ко- команду следует выполнять через опреде- определенные промежутки времени, чтобы избежать нежелательного сброса мик- микроконтроллера) Флаги [SVNZ] [-] Примечание -194-
Приложение Е ТАБЛИЦА ВЕКТОРОВ ПРЕРЫВАНИЙ —195 — TinylO Tuivi? TinviS Ч7ПП 'МП '2323 <2333 '4434 'Я*К Tinyll lmylZ liny15 1Z0U 2SU 42343 '4433 48535 8515 $000 RESET RESET RESET RESET RESET RESET RESET RESET RESET $000 $001 INTO INTO INTO INTO INTO INTO INTO INTO INTO $001 $002 PINCHG PINCHG PINCHG T/COOVF INT1 T/COOVF INT1 INTI INT1 $002 $003 T/COOVF T/COOVF T/Cl COMP ANACOMP T/Cl CAPT T/Cl CAPT T/C2COMP T/Cl CAPT $003 $004 ANACOMP EERDY T/Cl OVF T/Cl COMP T/Cl COMP T/C2OVF T/Cl COMPA $004 $005 ANACOMP T/COOVF T/Cl OVF T/Cl OVF T/Cl CAPT T/Cl COMPB $005 $006 EERDY T/COOVF T/COOVF T/Cl COMPA T/Cl OVF $006 $007 ANA COMP UART RXC SPI DONE T/Cl COMPB T/C0 OVF $007 $008 ADC DONE UARTUDRE UART RXC T/Cl OVF SPI DONE $008 $009 UARTTXC UARTUDRE T/COOVF UART RXC $009 $00A ANACOMP UARTTXC SPI DONE UARTUDRE $00A $00B ADC DONE UART RXC UARTTXC $00B $00C EERDY UARTUDRE ANACOMP $QOC $00D ANACOMP UARTTXC $00D $00E ADC DONE $00E $00F EERDY $00F $010 ANACOMP $010
Приложение Е. Таблица векторов прерываний Расшифровка названий источников прерываний Источник RESET INTO INT1 PINCHG T/COOVF T/Cl OVF T/Cl COMP T/Cl COMPA T/Cl COMPB T/Cl CAPT T/C2 OVF T/C2 COMP SPI DONE UART RXC UART UDRE UARTTXC EERDY ANA COMP ADC DONE Описание Аппаратный сброс Внешнее прерывание 0 Внешнее прерывание 1 Изменение сигнала на входах Переполнение таймера/счетчика Т/СО Переполнение таймера/счетчика Т/С1 Событие «совпадение» таймера/счетчика Т/С1 Событие «совпадение А» таймера/счетчика Т/С 1 Событие «совпадение В» таймера/счетчика Т/С1 Событие «захват» таймера/счетчика Т/С1 Переполнение таймера/счетчика Т/С2 Событие «совпадение» таймера/счетчика Т/С2 Передача по SPI завершена Передача по UART завершена Регистр данных UART пуст Прием по UART завершен EEPROM готово Готов результат аналогового компаратора Преобразование АЦП завершено -196
Приложение F ПРЕОБРАЗОВАНИЕ ШЕСТНАДЦАТЕРИЧНЫХ ЧИСЕЛ 0 1 2 3 4 5 6 7 8 9 А В С D Е F 0 0 16 32 48 64 80 96 112 128 144 160 176 192 208 224 240 1 1 17 33 49 65 81 97 113 129 145 161 177 193 209 225 241 2 2 18 34 50 66 82 98 114 130 146 162 178 194 210 226 242 3 3 19 35 51 67 83 99 115 131 147 163 179 195 211 227 243 4 4 20 36 52 68 84 100 116 132 148 164 180 196 212 228 244 5 5 21 37 53 69 85 101 117 133 149 165 181 197 213 229 245 6 6 22 38 54 70 86 102 118 134 150 166 182 198 214 230 246 7 7 23 39 55 71 87 103 119 135 151 167 183 199 215 231 247 8 8 24 40 56 72 88 104 120 136 152 168 184 200 216 232 248 9 9 25 41 57 73 89 105 121 137 153 169 185 201 217 233 249 А 10 26 42 58 74 90 106 122 138 154 170 186 202 218 234 250 В 11 27 43 59 75 91 107 123 139 155 171 187 203 219 235 251 С 12 28 44 60 76 92 108 124 140 156 172 188 204 220 236 252 D 13 29 45 61 77 93 109 125 141 157 173 189 205 221 237 253 Е 14 30 46 62 78 94 110 126 142 158 174 190 206 222 238 254 F 15 31 47 63 79 95 111 127 143 159 175 191 207 223 239 255 -197-
Приложение G ТАБЛИЦА КОДОВ СИМВОЛОВ ASCII 0 1 2 3 4 5 6 7 0 NUL DLE SP 0 @ Р Р 1 SOH DC1 1 1 А Q а q 2 STX DC2 « 2 В R b г 3 ЕТХ DC3 # 3 с S с s 4 EOT DC4 $ 4 D Т d t 5 ENQ NAK % 5 E U e u 6 ACK SYN & 6 F V f v 7 BEL ETB '0 7 G W g w 8 BS CAN ( 8 H X h X 9 TAB EM ) 9 I Y i У A LF SUB * J Z j z В VT ESC + ; К [ к { С FF FS < L \ 1 1 D CR GS - = M ] m } E SO RS > N A n ~ F SI US / ? 0 _ 0 DEL Например, код символа «Т» = 0x54 NUL ($00) SOH ($01) STX ($02) ЕТХ ($03) EOT ($04) ENQ ($05) ACK ($06) BEL ($07) BS ($08) HT($09) LF($0A) VT($0B) FF($0C) CR($0D) 50 ($0E) 51 ($0F) DLE ($10) - пусто (конец строки) - начало заголовка - начало текста - конец текста - конец передачи - запрос - подтверждение - звуковой сигнал - возврат на шаг - гориз. табуляция - перевод строки - вертик. табуляция - перевод страницы - возврат каретки - раздвинуть - сдвинуть - оставить канал данных DC1 ($11) DC2($12) DC3($13) DC4($14) NAK ($15) SYN ($16) ETB ($17) CAN ($18) EM ($19) SUB($1A) ESC($1B) FS($1C) GS($1D) RS($1E) US($1F) SP($20) DEL($7F) - управление устройством 1 - управление устройством 2 - управление устройством 3 - управление устройством 4 - отрицательное подтверждение - синхронизация - конец блока данных - отмена - конец носителя - замена - сброс - разделитель файлов - разделитель групп - разделитель записей - разделитель полей - пробел - удаление -198-
Приложение Н ЕСЛИ НИЧЕГО НЕ ПОЛУЧАЕТСЯ, ПРОЧТИТЕ ЭТО Через какое-то время вы обнаружите, что время от времени делаете в программах одни и те же ошибки (я, по крайней мере, делаю). В этом при- приложении я перечислил наиболее распространенные из них. ? Поставили ли вы двоеточие после метки, т.е. start:, а не start? ? Не используете ли вы команды sbi, cbi, sbis или sbic с регистрами вво- ввода/вывода $20...$3F? ? Не забыли ли вы сбросить регистры программных счетчиков? ? Задали ли вы корректные начальные значения регистров в секции Init? ? Не забыли ли вы, что в моделях Tiny 10 и Tiny 11 PB5 работает только как вход? ? Инициализировали ли вы указатель стека (SPL/SPH), если это необхо- необходимо? ? В правильном ли порядке вы осуществляете запись/чтение 2-байтных регистров ввода/вывода, таких как TCNT1H/L? ? Если вы ощущаете себя как в страшном сне и НИЧЕГО не работает... задали ли вы используемую модель микроконтроллера в начале про- программы? -199-
Приложение I КОНТАКТНАЯ ИНФОРМАЦИЯ И ДОПОЛНИТЕЛЬНАЯ ЛИТЕРАТУРА Джон Мортон (John Morton): help@to-pic.com. Web-сайт компании Atmel: www.atmel.com, www.atmel.ru. Claus Kuhnel. AVR RISC Microcontroller Handbook, Newnes A998) — кни- книга предоставляет более подробную информацию о внутренней архитектуре микроконтроллеров AVR. Brimicombe M. W. Introducing Electronic Systems, Nelson A987) — клас- классная книга по электронике. Некоторые интересные проекты на AVR: http://riccibitti.com/designs.htm. -200-
Приложение J ПОЛНЫЕ ТЕКСТЫ УЧЕБНЫХ ПРОГРАММ Программа А. Светодиод (LEDon) ; Автор: Джон Мортон * ; Дата: 5/2/2002 * ; Версия: 1.0 * ; Имя файла: LEDon.asm * ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: включает СИД .device at90sl200 .nolist .include nC: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc .list ; Объявления: .def temp =rl6 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ser temp ; PB0 - выход, остальные не используются out DDRB,temp ; out DDRD,temp ; PD0...7 - не используются clr temp ; Все выходы порта В - выкл out PortВ,temp ; out PortD,temp ; Все выходы порта D - выкл Start: sbi PortB,0 ; Включаем СИД rjmp Start ; Возвращаемся к метке Start -201-
Приложение J. Полные тексты учебных программ Программа В. Кнопка -202- ; Автор: Джон Мортон * ; Дата: 5/2/2002 * ; Версия: 1.0 * ; Имя файла: PushA.asm * ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: включает СИД при нажатой кнопке .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list ; Объявления: .def temp =rl6 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ser temp ; PB0 - выход, остальные не используются out DDRB,temp ldi temp,0blllllll0 ; PD0 - вход, остальные не используются out DDRD,temp ; clr temp ; Все выходы порта В - выкл out PortB,temp ; ldi temp,0b00000001 ; PD0 - подтяжка, остальные не используются out РогtD,temp ; Start: sbis PinD,0 ; Проверяем, нажата ли кнопка rjmp LEDoff ; Переходим к метке LEDoff sbi PortB,0 ; Включаем СИД rjmp Start ; Возвращаемся к метке Start LEDoff: cbi PortB,0 ; Выключаем СИД rjmp Start ; Возвращаемся к метке Start
Приложение J. Полные тексты учебных программ Программа С. Кнопка -203- ; Автор: Джон Мортон * ; Дата: 5/2/2002 * ; Версия: 2.0 * ; Имя файла: PushB.asm * ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: включает СИД при нажатой кнопке .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list ; Объявления: .def temp =rl6 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ser temp ; PBO - выход, остальные не используются out DDRB,temp ; ldi temp,Obll111110 ; PD0 - вход, остальные не используются out DDRD,temp ; clr temp ; Все выходы порта В - выкл out PortB,temp ldi temp,0b00000001 ; PD0 - подтяжка, остальные не используются out РогtD,temp ; Start: in temp,PinD ; Считываем состояние кнопки out PortB,temp ; Изменяем состояние СИД rjmp Start ; Возвращаемся к метке Start
Приложение J. Полные тексты учебных программ Программа D. Счетчик -204- ; Автор: Джон Мортон * ; Дата: 7/2/2002 * ; Версия: 1.0 * ; Имя файла: counter.asm * ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: считает число нажатий на кнопку @-9) .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list ; Объявления: .def temp =rl6 .def Counter =rl7 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ser temp ; PB0-7 - выходы out DDRB;temp ; ldi temp,0blllllll0 ; PD0 - вход, остальные не используются out DDRD,temp ldi R20, ObOllllllO ; Код для «0» - при включении out PortВ,temp ldi temp,0b00000001 ; PD0 - подтяжка, остальные не используются out PortD,temp ; ldi R21,0b00110000 ; Код для «1» ldi R22,0b01101101 ; Код для «2» ldi R23,0b01111001 ; и т.д. ldi R24,0b00110011 ldi R25,0b01011011 ldi R26/0b01011111 ldi R27,0b01110000 ldi R28/0b01111111 ldi R29,0b01111011 ; Код для «9» clr Counter ; При включении Counter = 0
Приложение J. Полные тексты учебных программ -205- Start: sbic PinD,0 ; Кнопка нажата? rjmp Start ; Нет, остаемся в цикле inc Counter ; Да, увеличиваем Counter на 1 cpi Counter,10 ; Counter = 10? brne PC+2 ; Нет, пропускаем команду clr Counter ; Да, сбрасываем Counter ldi ZL,20 ; Устанавливаем ZL на R20 add ZL,Counter ; Прибавляем Counter к ZL Id temp,Z ; Считываем Rx в temp out PortB,temp ; Выводим temp в порт В rjmp Start ; Возвращаемся к метке Start Программа Е. Счетчик (версия 2.0) ; Автор: Джон Мортон * ; Дата: 7/2/2002 * ; Версия: 2.0 * ; Имя файла: counter.asm * ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: считает число нажатий на кнопку @-9) .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list ; Объявления: .def temp =rl6 .def Counter =rl7 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ser temp ; PBO-7 - выходы out DDRB,temp ; ldi temp,OblllllllO ; PD0 - вход, остальные не используются out DDRD,temp
Приложение J. Полные тексты учебных программ -206- ldi R20,0b01111110 ; Код для «0» - при включении out PortB,temp ; ldi temp,0b00000001 ; PDO - подтяжка, остальные не используются out PortD,temp ; ldi R21,0b00110000 ; Код для «1» ldi R22,0b01101101 ; Код для «2» ldi R23,0b01111001 ; и т.д. ldi R24,0b00110011 ldi R25;0b01011011 ldi R26,0b01011111 ldi R27/0b01110000 ldi R28/0b01111111 ldi R29/0b01111011 ; Код для «9» clr Counter ; При включении Counter = 0 Start: sbic PinD,0 ; Кнопка нажата? rjmp Start ; Нет, остаемся в цикле inc Counter ; Да, увеличиваем Counter на 1 cpi Counter,10 ; Counter = 10? brne PC+2 ; Нет, пропускаем команду clr Counter ; Да, сбрасываем Counter ldi ZL,20 ; Устанавливаем ZL на R20 add ZL,Counter ; Прибавляем Counter к ZL Id temp,Z ; Считываем Rx в temp out PortB,temp ; Выводим temp в порт В ReleaseWait: sbis PinD,0 ; Кнопка отпущена? rjmp ReleaseWait ; Нет, остаемся в цикле rjmp Start ; Да, возвращаемся к началу Программа F. Бегущий огонек ; Автор: Джон Мортон * ; Дата: 7/2/2002 * ; Версия: 1.0 * ; Имя файла: chaser.asm * ; Для AVR: 1200 * ; Тактовая частота: 2.4576 МГц * ; Выполняемые функции: формирует узор в виде бегущего огонька ; с меняющейся скоростью
Приложение J. Полные тексты учебных программ -207- .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list • ================= ; Объявления: .def temp =rl6 .def Mark240 =rl7 .def Counter =rl8 .def Speed =rl9 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ser temp ; PBO-7 - выходы out DDRB,temp ; ldi temp,0bllllll00 ; PD0,l - входы, остальные не используются out DDRD,temp ; ldi temp,0b00000001 ; При старте включен только РВО out PortВ,temp ldi temp,0b00000011 ; PD0,l - подтяжка, остальные ; не используются out PortD,temp ldi temp,0b00000101 ; Частота таймера СК/1024 out TCCRO,temp ldi Mark240,240 ldi Counter,5 ldi Speed,5 ; Start: sbic PinD,0 ; Проверяем кнопку уменьшения скорости rjmp UpTest ; He нажата, переходим inc Speed ; Уменьшаем скорость cpi Speed,11 ; Speed = 11? brne ReleaseDown ; Переходим к ReleaseDown, если нет dec Speed ; Уменьшаем Speed на 1 ReleaseDown: sbis PinD,0 ; Ждем отпускания кнопки уменьшения ; скорости rjmp ReleaseDown
Приложение J. Полные тексты учебных программ -208- UpTest: sbic PinD,l ; Проверяем кнопку увеличения скорости rjmp Timer ; He нажата, переходим dec Speed ; Увеличиваем скорость brne ReleaseUp ; Переходим к Timer, если не О inc Speed ; Увеличиваем Speed на 1 ReleaseUp: sbis PinD,0 ; Ждем отпускания кнопки увеличения ; скорости rjmp ReleaseUp ; Timer: in temp,TCNTO ; Считываем состояние Т/СО в temp ср temp,Mark240 ; Сравниваем temp с Mark240 brne Timer ; Если не равно, возвращаемся к Timer subi Mark240,-240 ; Прибавляем 240 к Mark240 dec Counter ; Уменьшаем Counter на 1 brne Start ; Если не 0, возвращаемся к метке Start ; Заданное время прошло, меняем СИД mov Counter,Speed ; Сбрасываем Counter in temp,PortB ; Считываем текущее состояние СИД lsl temp ; Сдвигаем влево brcc PC+2 ; Если переноса не было, пропускаем команду ldi temp,0b00000001 ; Сбрасываем: РВО - вкл, остальные - выкл out PortB,temp ; Выводим в PortB rjmp Start ; Возвращаемся к метке Start Программа G. Счетчик (версия 3.0) ; Автор: Джон Мортон * ; Дата: 9/2/2002 * ; Версия: 3.0 * ; Имя файла: counter.asm * ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: считает число нажатий на кнопку @-9) .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list
Приложение J. Полные тексты учебных программ -209- ; Объявления: .def temp =rl6 .def Counter =rl7 .def Delayl =rl6 .def Delay2 =rl8 .def Delay3 =rl9 ; Начало программы rjmp Init ; Первая выполняемая команда ; Подпрограммы Debounce: ldi Delayl,0x80 ; Инициализируем регистры счетчика ldi Delay2,0x38 ldi Delay3,0x01 Loop: subi Delayl,1 ; Формируем задержку sbci Delay2,0 sbci Delay3,0 ; brcc Loop ; ret ; Выходим из подпрограммы Init: ser temp ; PBO-7 - выходы out DDRB,temp ; ldi temp,0blllllll0 ; PD0 - вход, остальные не используются out DDRD,temp ; ldi R20,0b01111110 ; Код для «О» - при включении out PortB,temp ; ldi temp,0b00000001 ; PDO - подтяжка, остальные не используются out PortD,temp ldi R21,0b00110000 ; Код для «1» ldi R22/0b01101101 ; Код для «2» ldi R23,0b01111001 ; и т.д. ldi R24,0b00110011 ldi R25/0b01011011 ldi R26,0b01011111 ldi R27,0b01110000 ldi R28,0b01111111 ldi R29,0b01111011 ; Код для «9» clr Counter ; При включении Counter = 0
Приложение J. Полные тексты учебных программ -210- • ================= Start: sbic PinD,0 ; Кнопка нажата? rjmp Start ; Нет, остаемся в цикле inc Counter ; Да, увеличиваем Counter на 1 cpi Counter,10 ; Counter = 10? brne PC+2 ; Нет, пропускаем команду clr Counter ; Да, сбрасываем Counter ldi ZL,20 ; Устанавливаем ZL на R20 add ZL,digit ; Прибавляем Counter к ZL Id temp,Z ; Считываем Rx в temp out PortB,temp ; Выводим temp в порт В rcall Debounce ; Вставляем требуемую задержку ReleaseWait: sbis PinD,0 ; Кнопка отпущена? rjmp ReleaseWait ; Нет, остаемся в цикле rcall Debounce ; Вставляем требуемую задержку rjmp Start ; Да, возвращаемся к началу Программа Н. Светофор ; Автор: Джон Мортон * ; Дата: 7/2/2002 * ; Версия: 1.0 * ; Имя файла: traffic.asm * ; Для AVR: 1200 * ; Тактовая частота: 2.4576 МГц * ; Выполняемые функции: имитирует светофор на пешеходном переходе .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list ; Объявления: .def temp =rl6 .def Counter =rl7 .def tog =rl8 .def Delayl =rl9 .def Delay2 =r20 .def Delay3 =r21 .def Mark240 =r22 .def Count250 =r23
Приложение J. Полные тексты учебных программ — 211 — ; Начало программы rjmp Init ; Первая выполняемая команда ; Подпрограммы: HalfSecond: clr Delayl ; Инициализируем счетные регистры ldi Delay2,OxCO ldi Delay3,0x03 HalfLoop: subi Delayl,1 ; Формируем задержку sbci Delay2;0 sbci Delay3,0 ; brcc HalfLoop ret Timer: brts PC+2 ; Если бит Т = 1, пропускаем команду ret ; Если бит Т = 0, возвращаемся in temp,TCNTO ; Считываем состояние Т/СО в temp cpse temp,Mark240 ; Сравниваем temp с Mark240 ret ; Если не равно, возвращаемся subi Mark240,-240 ; Прибавляем 240 к Mark240 dec Count250 ; Уменьшаем Count250 на 1 breq PC+2 ; Если 0, пропускаем команду ret ; Если не 0, возвращаемся ldi Count250,250 ; Переустанавливаем Count250 clt ; Сбрасываем бит Т ret Init: ser temp ; PBO-5 - выходы, остальные не используются out DDRB,temp ; ldi temp,0blllllll0 ; PD0 - вход, остальные не используются out DDRD,temp ldi temp,ObOO000001 ; PD0 - подтяжка, остальные не используются out PortD,temp ldi temp,0b00000101 ; Частота таймера равна СК/1024 out TCCR0,temp ldi Mark240,240
Приложение J. Полные тексты учебных программ -212- ldi Count250,250 clt ; Сбрасываем бит Т Start: ldi . temp,0b00010001 ; Автомобили: зеленый out PortB,temp ; Пешеходы: temp rcall Timer ; Проверяем время sbic PinD,0 ; Проверяем кнопку rjmp Start ; He нажата sbi PortB,5 _ ; Включаем сигнал «ЖДИТЕ» Loop: rcall Timer ; Проверяем время brts Loop ; Остаемся в цикле до сброса бита Т sbi PortB,l ; Желтый (авто) - вкл cbi PortB,0 ; Зеленый (авто) - выкл ldi temp,8 ; Задержка 4 с FourSeconds: rcall HalfSecond dec temp ; brne FourSeconds ldi temp,ObO0001100 ; Автомобили: красный out PortB,temp ; Пешеходы: зеленый ldi temp,16 ; Задержка 8 с EightSeconds: rcall HalfSecond dec temp ; brne EightSeconds ; ldi tog,ObOO001010 ; Автомобили: желтый out PortB,tog ; Пешеходы: зеленый ldi Counter,8 ; Инициализируем регистр Counter FlashLoop: rcall HalfSecond ; Ждем 0.5 с in temp,PinB ; Считываем состояние сигналов eor temp,tog ; Переключаем out PortB,temp ; Выводим dec Counter ; Повторяем 8 раз brne FlashLoop set ; Устанавливаем бит Т rjmp Start ; Возвращаемся к метке Start
Приложение J. Полные тексты учебных программ Программа I. Симулятор логических элементов ; Автор: Джон Мортон * ; Дата: 9/2/2002 * ; Версия: 1.0 * ; Имя файла: logic.asm * ; Для AVR: Tiny12 * ; Тактовая частота: 2.4576 МГц * ; Выполняемые функции: имитирует логические элементы AND, NAND, IOR, NOR, ; XOR, XNOR, NOT и BUFFER .device atTinyl2 .nolist .include "C: \Program Files\Atmel\AVR Studio\Appnotes\tnl2def.inc" .list ; Объявления: .def temp =rl6 ; Начало программы rjmp Init ; Первая выполняемая команда ; Lookup Table: .dw ObOOOOOOOlOOOlOOll ; Код для AND и IOR .dw ObOOllOOlOOOlOOOOO ; NAND и NOR .dw ОЬООЮОООЮООЮОЮ ; ENOR и EOR .dw ObOOllOOOOOOOOOOll ; NOT и BUFFER Init: ldi temp,0b000001 ; PBO - выход, остальные входы out DDRB,temp ldi temp,0blllll0 ; PB1-5 - подтяжка out PortB,temp ; PBO сначала О Start: in ZL,PinB ; Считываем состояние PinB andi ZL,0b001110 ; Маскируем биты 0, 4 и 5 lsr ZL ; Сдвигаем subi ZL,-2 ; Прибавляем 2 к ZL 1pm ; Считываем содержимое таблицы в R0 -213-
Приложение J. Полные тексты учебных программ -214- sbis PinB,4 ; Проверяем Вход А swap R0 ; Если 0 - переставляем тетрады sbis PinB,5 ; Проверяем Вход В ror R0 ; Если 0 - сдвигаем вправо mov temp,R0 ; Копируем R0 в temp ori temp,0b00001110 ; Устанавливаем биты 1-4 out PortB,temp ; Выводим результат rjmp Start ; Возвращаемся к метке Start Программа J. Частотомер ; Автор: Джон Мортон * ; Дата: 14/02/02 * ; Версия: 1.0 ' * ; Имя файла: frequency.asm * ; Для AVR: AT90S1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: отображает частоту входного сигнала на трех ; семисегментных индикаторах .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVRStudio\Appnotes\1200def.inc" .list ; Объявления .def temp =rl6 .def temp2 =rl7 .def temp3 =rl8 .def lowerbyte =rl9 .def upperbyte =r20 .def DisplayCounter =r21 .def DisplayNumber =r22 .def Delayl =r23 .def Delay2 =r24 .def Delay3 =r25 .def Hundreds =r26 .def Tens =r27 .def Ones =r28
Приложение J. Полные тексты учебных программ -215- .def store =r29 .def store2 =rl9 .def Counter =r20 ; R0-R12 - коды для дисплея ; Вектор сброса rjmp Init ; Перейдем к секции инициализации ; Инициализация Init: ser temp ; РВО: СИД «Гц»/«кГц» out DDRB,temp ; PBl-7 - сегменты семисегментных ; индикаторов ldi temp,0blll01111 ; PDO-2 - выбор индикатора out DDRD,temp ; PD4 - вход, остальные не используются clr temp ; Подтяжка выключена out PORTB,temp ; Все выходы выкл ldi temp,0b00000001 ; Выбираем один индикатор out PORTD,temp ; Все выходы выключены ldi temp,0b00001110 ; Сторожевой таймер срабатывает out WDTCR,temp ; один раз в секунду ldi temp,0b00110000 ; Разрешаем режим «Power Down» out MCUCR,temp ldi Hundreds,12 ldi Tens,12 ldi Ones,12 clr ZH ; Сбрасываем старший байт указателя Z ldi DisplayCounter,50 clr DisplayNumber ldi temp,0bllllll00 ; 0 mov RO,temp ldi temp,0b01100000 ; 1 mov Rl,temp ldi temp,0bll011010 ; 2 mov R2,temp ldi temp,0bllll0010 ; 3 mov R3,temp ldi temp,0b01100110 ; 4
Приложение J. Полные тексты учебных программ -216- mov R4,temp ldi temp;0bl0110110 ; 5 mov R5,temp ldi temp,0bl0111110 ; 6 mov R6,temp ldi temp,0blll00000 ; 7 mov R7,temp ldi temp,OblllllllO ; 8 mov R8,temp ldi temp,0bllll0110 ; 9 mov R9,temp ldi temp,0b01101110 ; H mov RIO,temp ldi temp,0b00000010 ; - mov Rll,temp rjmp Start • ================= ; Подпрограммы для работы с дисплеем Display: dec DisplayCounter ; Обновляем дисплей при каждом 50-м вызове breq PC+2 ret wdr ; «Бросим кость собаке» ldi DisplayCounter,50 ; inc DisplayNumber ; cpi DisplayNumber,3 ; brne PC+2 ; clr DisplayNumber ; ldi ZL,26 ; Устанавливаем ZL на R26 add ZL,DisplayNumber ; Id temp,Z ; Копируем конвертируемое число в temp clr ZL ; Устанавливаем ZL на R0 add ZL,temp ; Прибавляем temp к ZL Id temp,Z ; Считываем Rx в temp sbic PortB,7 ; Проверяем СИД «кГц» ori temp,Obi0000000 ; Если включен, то не выключаем out PortB,temp ; Выводим temp в порт В in temp,PinD ; lsl temp ; sbrc temp,3 ; ldi temp,0b00000001 out PortD,temp ret
Приложение J. Полные тексты учебных программ -217- ; Конвертирует 4-разрядное шестнадцатеричное число в 3 цифры десятичных ; разрядов DigitConvert: clr Hundreds ; clr Tens ; clr Ones ; FindHundreds: subi lowerbyte,100 ; sbci upperbyte,0 ; brcs FindTens ; inc Hundreds rjmp FindHundreds ; FindTens: subi lowerbyte,-100 subi lowerbyte,10 ; brcs FindOnes inc Tens rjmp FindTens+1 FindOnes: subi lowerbyte,-10 ; Возвращаем последний десяток mov Ones,1owerbyte ; Число в lowerbyte = количество единиц ret ; Закончили преобразование ; НАЧАЛО ПРОГРАММЫ • = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =: = = = := = = =:=: = = = = = = = := = =: = = = ; Секция обработки сигналов с частотой более 1 кГц Start: ldi Delayl,00- ldi Delay2,0x7D ldi temp,0bl0000000 ; Очищаем индикаторы и включаем СИД «кГц» out PortВ,temp ; ldi temp,0b00000111 ; TCNTO считает по нарастающему фронту out TCCRO,temp ; Ha выводе ТО (PD4) clr upperbyte out TCNTO,upperbyte in temp,TCNTO ; Highspeed: subi Delayl,l ; Считаем в течение 0.064 с sbci Delay2,0 brcs DoneHi /
Приложение J. Полные тексты учебных программ -218- mov temp2/temp in temp,TCNTO ; cp temp,temp2 brsh Highspeed ; 8 циклов inc upperbyte ; cpi upperbyte, OxFA ; Слишком большое значение? breq TooHigh ; subi Delayl,1 sbci Delay2,0 brcs DoneHi пор ; rjmp Highspeed ; DoneHi: in lowerbyte,TCNTO ; Запоминаем содержимое TCNTO cp lowerbyte,temp ; Сравниваем с предыдущим значением brsh PC+2 inc upperbyte ; cpi upperbyte,0xFA breq TooHigh Divide64: ldi temp,6 ; lsr upperbyte ; ror lowerbyte ; dec temp ; brne Divide64+1 cpi upperbyte,0 ; Старший байт = О? brne PC+3 ; Пропускаем 2 следующие команды cpi lowerbyte,0 ; Младший байт =0? breq LowSpeed ; Если частота < 1 кГц, необходимо перейти ; в режим измерения низкой частоты rcall DigitConvert ; ldi Delayl,0x2A ldi Delay2,ОхСб ldi Delay3,0x01 HalfSecond: rcall Display ; Обрабатываем дисплей во время задержки subi Delayl, ; sbci Delay2,0 sbci Delay3,0 brcc HalfSecond ; rjmp Start TooHigh: ldi Hundreds,11 ldi Tens,10
Приложение J. Полные тексты учебных программ — 219 — ldi Ones,1 rjmp HalfSecond-3 ; Секция обработки сигналов с частотой менее 1 кГц LowSpeed: ldi temp,ObOOO00001 ; Частота TCNTO равна СК out TCCR0,temp clr Delay2 clr Delay3 ; cbi PortB,7 ; Сбросим РВ7, чтобы включить СИД «Гц» in store,PinD ; Запоминаем начальное значение FirstChange: rcall Display ; Обработаем дисплей in store2,PinD eor store2,store ; Сравниваем с текущим значением andi store2,0b00010000 ; Игнорируем все биты, кроме 4-го breq FirstChange ; Остаемся в цикле, ; пока PD4 не изменится ldi Counter,2 ; Counter = 2 clr temp2 ; Сбрасываем таймер ТО out TCNTO,temp2 in store,PinD ; Запоминаем начальное значение LowLoop: in store2,PinD ; eor store2,store ; Сравниваем с текущим значением andi store2,0b00010000 ; Игнорируем все биты, кроме 4-го brne Change ; Переходим к Change при изменении PD4 rcall Display ; Обработаем дисплей mov temp2,Delayl ; in Delay1,TCNTO cp Delayl,temp2 brsh LowLoop inc Delay2 brne LowLoop ; inc Delay3 cpi Delay3,0x3E ; Слишком малое значение? breq TooSlow ; rjmp LowLoop Change: in store,PortB ; Обновим значение dec Counter ; brne LowLoop ldi temp,0x0F
Приложение J. Полные тексты учебных программ -220- ldi temp2,0x00 cpi Delayl,OxAO cpc Delay2,temp ; cpc Delay3,temp2 ; brcs Start ; Да, переходим к метке Highspeed ldi temp,0x00 ldi temp2,0x09 ldi temp3,0x3D clr lowerbyte clr upperbyte Divide: sub temp,Delayl sbc temp2,Delay2 sbc temp3,Delay3 brcs DoneDividing inc lowerbyte; brne Divide inc upperbyte rjmp Divide DoneDividing: rcall DigitConvert rjmp LowSpeed TooSlow: out PortD,temp ; Выключаем дисплей sleep rjmp LowSpeed Программа К. Измеритель скорости реакции ; Автор: Джон Мортон * ; Дата: 5/2/02 * ; Версия: 1.0 * ; Имя файла: reaction.asm * ' ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: определяет и отображает время реакции .device at90sl200 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .List
Приложение J. Полные тексты учебных программ -221- ; Объявления: .def temp =rl6 .def Random =rl7 .def Five =rl8 .def TimeL =rl9 .def TimeH =r20 .def Hundreds =r21 .def Tens =r22 .def Ones =r23 .def CountX =r24 .def DisplayNumber =r25 .def DisplayCounter =r26 .def tempH =r27 .def Count4 =r28 ; Начало программы rjmp Init ; Первая выполняемая команда rjmp Extlnt rjmp TCNTOlnt Extlnt: sbis PinD,0 ; Проверяем СИД rjmp Cheat clr temp ; Останавливаем TCNTO out TCCRO in TimeL,TCNTO ; Считываем состояние Т/СО in temp,TIFR ; Проверяем Т/СО на переполнение sbrc temp,l inc TimeH subi TimeL,0xA2 ; Вычитаем 0хА2 из общего sbci TimeH,0 ; времени реакции ldi temp,0b00000101 ; Перезапускаем Т/СО на частоте СК/1024 out TCCRO,temp ldi Count4,4 ; Умножаем время реакции на 5 mov temp,TimeL ; mov tempH,TimeH Times5: add temp,TimeL ; adc tempH,TimeH ; dec Count4 brne Times5 clr TimeL clr TimeH
Приложение J. Полные тексты учебных программ -222- Dividel2: subi temp,12 ; sbci tempH,0 ; brcs DoneDividing ; inc TimeL brne Dividel2 ; inc TimeH ; rjmp Dividel2 ; DoneDividing: rcall DigitConvert ; ret ; Возвращаемся, НЕ РАЗРЕШАЯ прерывания Cheat: ldi Hundreds ДО ; «b» ldi Tens,11 ; «A» ldi Ones,12 ; «d» ret ; Возвращаемся, НЕ РАЗРЕШАЯ прерывания TCNTOInt: sbic PinD,0 ; Проверяем СИД rjmp TInt__LEDon ; dec CountX breq PC+2 reti ldi temp,0xA2 out TCNTO,temp ; sbi PortD,0 ; Включаем СИД reti TInt_LEDon: inc TimeH ; Инкрементируем старший байт cpi TimeH,ОхОА ; Проверяем на максимальное время breq PC+2 ; Пропускаем, если слишком мало reti ldi Hundreds,13 ; «-» ldi Tens,14 ; «H» ldi Ones,l ; «I» ret ; Подпрограммы для работы с дисплеем Display: dec DisplayCounter ; Обновляем дисплей при каждом 50-м вызове breq PC+2 ret wdr ; «Бросим кость собаке» ldi DisplayCounter,50 ;
Приложение J. Полные тексты учебных программ -223- inc DisplayNumber ; cpi DisplayNumber,3 ; brne PC+2 clr DisplayNumber ldi ZL,21 ; Устанавливаем ZL на R21 add ZL,DisplayNumber ; Id temp,Z ; Копируем конвертируемое число в temp ldi ZL,0 ; Устанавливаем ZL на R0 add ZL,temp ; Прибавляем temp к ZL Id temp,Z ; Считываем Rx в temp out PortB,temp ; Выводим temp в порт В brtc PC+2 ; Проверяем бит Т sbi PortB,0 ; Включаем СИД «кГц» in temp,PinD ; lsl temp ; sbrc temp,7 ; ldi temp,0b00010000 ori temp,0b00000110 out PortD,temp ; ret ; Конвертирует 4-разрядное шестнадцатеричное число в 3 цифры ; десятичных разрядов DigitConvert: clr Hundreds ; clr Ones clr Tens FindHundreds: subi TimeL,100 sbci TimeH,0 brcs FindTens ; inc Hundreds rjmp FindHundreds FindTens: subi TimeL,-100 subi TimeL,10 brcs FindOnes • inc Tens rjmp FindTens+1 FindOnes: subi TimeL,-10 ; Прибавим последний десяток mov Ones^imeL ; Число в lowerbyte = количество единиц ret ; Закончили преобразование
Приложение J. Полные тексты учебных программ -224- Init: ldi temp,Obllllllll ; PBl-7 - выходы, РВО - не используется out DDRB,temp ; ldi temp,0blllll001 ; PD0,4-6 - выходы, PD3,7 - не используются out DDRD,temp ; PD1,2 - входы ldi temp,0b00000000 out PortB,temp ; , ldi temp,ObOO010110 ; Выберем 1-й индикатор, подтяжка out PortD,temp ; на обеих кнопках ldi temp,0b00000101 ; Частота TCNTO равна СК/1024 out TCCR0,temp ldi temp,0b00000000 ; Прерывание INTO по спадающему фронту out MCUCR,terap ldi temp,ObO1000000 ; Разрешаем прерывание INTO out GIMSK,temp ldi temp,0b00000010 ; Разрешаем прерывание Т/СО out TIMSK,temp ldi DisplayCounter,50 clr DisplayNumber ldi temp,0bllllll00 ; 0 mov R0,temp ldi temp,0b01100000 ; 1 mov Rl, temp ldi temp,0bll011010 ; 2 mov R2,temp ldi temp,0bllll0010 ; 3 mov R3,temp ldi temp,0b01100110 ; 4 mov R4,temp ldi temp,0bl0110110 ; 5 mov R5,temp ldi temp,0bl0111110 ; 6 mov R6,temp ldi temp,0blll00000 ; 7 mov R7,temp ldi temp,0blllllll0 ; 8 mov R8,temp ldi temp,0bllll0110 ; 9 mov R9,temp ldi temp,0b00111110 ; b mov R10,temp ldi temp,0blll01110 ; A mov Rll,temp
Приложение J. Полные тексты учебных программ ldi temp,0b01111010 ; d mov Rl2/temp ; Основное тело программы: Start: rcall Display ; Обработаем дисплей sbic PinD,l ; Ждем нажатия кнопки «Готов» rjmp Start ; Остаемся в цикле ; Вычисляем следующее случайное число, mov temp,Random ; умножаем на 5 и... add Random,temp ; add Random,temp ; add Random,temp ; add Random,temp inc Random ; ... прибавляем 1 mov CountX,Random lsr CountX ; Делим на 2 и прибавляем 60 subi CountX,-60 ldi temp,0b01000000 ; Сбрасываем флаг прерывания INTO out GIFR ldi temp,0b00000010 ; Сбрасываем флаг прерывания по ; переполнению Т/СО out TIFR sei ; Разрешаем прерывания clr TimeH ; Очищаем регистры, хранящие время out PortB,TimeH ; Также выключаем дисплей Loopy: brid Start ; Если прерывания запрещены, ; выходим из цикла rjmp Loopy ; Ждем Программа L. 4-битный аналого-цифровой преобразователь ; Автор: Джон Мортон * ; Дата: 25/2/02 * ; Версия: 1.0 * ;• Имя файла: atod.asm * ; Для AVR: 1200 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: 4-битный АЦП .device at90sl200 -225-
Приложение J. Полные тексты учебных программ .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\1200def.inc" .list ; Объявления: .def temp =rl6 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ldi temp,0bllllll00 ; PBOД - аналоговые входы out DDRB,temp ; PD2-7 - не используются ldi temp;0bllllllll ; PDO-3 - выходы, PD4-7 - не исп. out DDRD,temp clr temp out PortB,temp ldi temp,ObOO001000 ; Выбираем MSB out PortD,temp ldi temp,0bl0000000 ; Включаем аналоговый компаратор out ACSR,temp ; Основное тело программы: Start: sbis ACSR,5 ; Проверяем результат АК cbi PortD,3 ; Сбрасываем 3-й бит sbi PortD,2 sbis ACSR,5 ; Проверяем результат АК cbi PortD,2 ; Сбрасываем 2-й бит sbi PortD,l sbis ACSR,5 ; Проверяем результат АК cbi PortD,l ; Сбрасываем 1-й бит sbi PortD,0 sbis ACSR,5 ; Проверяем результат АК cbi PortD,0 ; Сбрасываем 0-й бит in temp,PortD ; Считываем окончательный ответ swap temp ; Переставляем тетрады out PortB,temp ; Выводим результат rjmp Start ; Зацикливаемся -226-
Приложение J. Полные тексты учебных программ -227- Программа М. Инвертор напряжения ; Автор: Джон Мортон * ; Дата: 25/2/02 * ; Версия: 1.0 * ; Имя файла: inverter.asm * ; Для AVR: Tiny15 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: формирует напряжение ; величиной 5 - Vin .device atTinyl5 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\Tnl5def.inc" .list ; Объявления: .def temp =rl6 .def tempH =rl7 .def Desired =rl8 .def Actual =rl9 ; Начало программы rjmp Init ; Первая выполняемая команда Init: ldi temp,01H11100 ; PB0,l,5 - входы out DDRB,temp ; PB2-4 - не важно clr temp ; Нет подтяжки out PortB,temp ldi temp,Obi1101011 ; Включаем АЦП, такт = CK/8 out ADCSR,temp clr temp ; Выбираем ADC0, опора - VCC out ADMUX,temp ; Выравнивание вправо ; Основное тело программы: Start: cbi ADMUX,0 ; Выбираем вход ADC0 sbi ADCSR,ADSC ; Запускаем преобразование sbic ADCSR,ADSC
Приложение J. Полные тексты учебных программ -228- rjmp Start+2 in Desired,ADCH ; Считываем 8-битный результат com Desired ; Вычисляем 5 - АЦП sbi ADMUX,0 ; Выбираем вход ADCl sbi ADCSR,ADSC ; Запускаем преобразование по выходу Wait: sbic ADCSR,ADSC ; Ждем окончания преобразования rjmp Wait in Actual,ADCH ; Считываем результат для выхода ср Actual,Desired ; Сравниваем реальное с требуемым brio TooLow ; Слишком мало? ср Desired,Actual brio TooHigh ; Слишком велико? cbi DDRB,0 ; То, что надо, делаем РВО входом rjmp Start ; Снова задаем ADC0 TooLow: sbi DDRB,0 ; Слишком мало, делаем РВО выходом sbi PortB,0 ; и устанавливаем его в 1 rjmp Start ; Снова задаем ADC0 TooHigh: sbi DDRB,0 ; Слишком велико, делаем РВО выходом cbi PortB,0 ; и сбрасываем его в 0 rjmp Start ; Снова задаем ADC0 Программа N. Музыкальный автомат ; Автор: Джон Мортон * ; Дата: 22/3/02 * ; Версия: 1.0 * ; Имя файла: music.asm * ; Для AVR: 2313 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: проигрывает мелодию, зашитую в EEPROM .device at90s2313 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\2313def.inc" .list ; Объявления: .def temp =rl6
Приложение J. Полные тексты учебных программ -229- .def NoteL =rl9 .def NoteH =r23 .def Length =r20 .def address =r21 ; Начало программы rjmp Init ; Первая выполняемая команда reti ; $001 - INTO reti ; $002 reti ; $003 rjmp ToggleOut ; $004 - «Сравнение А» reti ; $005 - Переполнение ТС1 rjmp ChangeNote ; $006 - Переполнение ТС0 .org 0x13 LookUpTable: .dw OxOECB ; $00 = С .dw 0x0DF7 ; $01 = C# .dw 0x0D2E ; $02 = D .dw 0x0C71 ; $03 = D# .dw OxOBBE ; $04 = E .dw OxOB15 ; $05 = F .dw 0x0A76 ; $06 - F# .dw 0x09E0 ; $07 = G .dw 0x0952 ; $08 = G# .dw 0x08CC ; $09 = A .dw 0x084D ; $0A = A# .dw 0x07D6 ; $0B = В ToggleOut: in temp,PortD ; Переключим выход динамика com temp ; для формирования прямоугольного сигнала out РогtD,temp ; reti ChangeNote: dec Length ; Ждем окончания ноты breq PC+2 reti Rest: in temp,TIFR ; Формируем короткую паузу между нотами sbrs temp,l ; rjmp Rest ; ldi temp,0b00000010 ; out TIFR,temp
Приложение J. Полные тексты учебных программ -230- Read_EEPROM: out EEARL,address ; Задаем следующий адрес sbi EECR,0 ; Читаем in ZL,EEDR ; Получаем код ноты из EEPROM andi ZL,0b00001111 ; Маскируем биты 4-7 cpi ZL,0x0C ; Если ОхОС, возвращаемся к 1-му адресу breq Reset brio PC+2 ; Если больше ОхОС, меняем на ОхОВ ldi ZL,0x0B lsl ZL ; Умножаем на 2 для получения адреса слова subi ZL,-0x26 ; Прибавляем 26 для указания на таблицу lpm ; Читаем значение из таблицы mov NoteL,R0 ; Сохраняем результат inc ZL ; Читаем следующую позицию таблицы lpm mov NoteH,RO ; Сохраняем результат ; Вычисляем октаву in temp,EEDR ; Снова читаем из EEPROM swap temp ; andi temp,0b00000011 ; Маскируем биты, кроме 4-го и 5-го GetOctave: breq GetLength ; Для определения октавы ; используем биты 4 и 5 lsl NoteL ; Делим на 2 для получения следующей октавы rol NoteH dec temp ; rjmp GetOctave ; GetLength: ; Получаем длительность out 0CR1AH,NoteH ; Сохраняем итоговое значение частоты out 0CR1AL,NoteL ; в регистрах Output Compare in temp,EEDR ; Снова читаем из EEPROM andi temp,ObllOOOOOO ; Маскируем все биты, кроме б и 7 swap temp lsr temp ; Используем эти биты для формирования ; Length =2, 4, б или 8 subi temp,-2 ; mov Length,temp ; Сохраняем в Length inc address ; Выбираем следующий адрес EERPOM ; (следующая нота) reti Reset: clr address ; Сбрасываем адрес EEPROM rjmp Read_EEPROM
Приложение J. Полные тексты учебных программ — 231 — Init: ldi temp,0b01000000 ; РВО-5 - вход от клавиатуры out DDRB,temp ; РВб - не исп., РВ7 - «Record» ldi temp,0b01111011 ; PDO - не исп., PDl - динамик out DDRD,temp ; PD2 - «Play», PD3-6 - выход клавиатуры ldi temp,Obi0000000 ; Отключить подтяжку на входах порта В out PortB,temp ; ldi temp,0b00000100 ; Подтяжка на входе кнопки «Play» out PortD,temp ldi temp,0b00000101 ; Частота TC0 равна СК/1024 out TCCRO,ternp clr temp ; Выключить ШИМ out TCCRlA,temp ldi temp,0b00001001 ; Частота ТС1 равна СК, сбрасываем ТС1 out TCCRlB,temp ; при совпадении при сравнении ldi temp,0b01000010 ; Разрешаем прерывание от TC0 out TIMSK,temp ; Разрешаем прерывание TCI CompA ldi temp,0b00000000 ; Запрещаем остальные прерывания out GIMSK,temp ldi temp,RAMEND ; Инициализируем стек out SPL,temp clr ZH clr address ; out EEARH,address rcall Read_EEPROM ; Считываем 1-ю ноту ; Основное тело программы: Start: rjmp Start Программа О. Конвертер клавиатуры .; Автор: Джон Мортон * ; Дата: 25/2/02 * ; Версия: 1.0 * ; Имя файла: keyboard.asm * ; Для AVR: 2313 * ; Тактовая частота: 4 МГц *
Приложение J. Полные тексты учебных программ -232- ; Выполняемые функции: превращает клавиатуру компьютера в клавиатуру ; музыкального синтезатора .device at90s2313 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\2313def.inc" .list ; Объявления: .def temp =rl6 .def data =rl7 .def Length =rl8 ; Начало программы rjmp Init ; Первая выполняемая команда reti ; $001 - INTO reti ; $002 reti ; $003 reti ; $004 - «Сравнение А» reti ; $005 reti ; $006 - Переполнение ТС1 rjmp EndNote ; $007 - Переполнение ТС0 reti ; $008 rjmp Change ; $009 - Прием по UART reti ; $00A reti ; $00B reti ; $00C .org 13 ; Таблица нот .dw 0xlE84 ; 'a' = С .dw OxFFFF,OxFFFF ; 'b', 'с' = не используются .dw 0x1838 ; 'd' = E .dw 0xl9A9 ; 'e1 = D# .dw 0xl6DC ; 'f = F .dw 0xl45E ; 'g' = G .dw 0x1225 ; 'h1 = A .dw OxFFFF ; 'i' = не используется .dw 0xl02A ; 'j' = В .dw 0x0F42 ; 'kf = С hi .dw 0x0D98 ; Ч1 = D hi .dw OxFFFF,OxFFFF ; 'm'( 'n' = не используются .dw 0x0E67 ; 'o1 = C# hi .dw 0x0CC8 ; 'p1 = D# hi .dw OxFFFF,OxFFFF ; ' q' , •r' = не используются .dw 0xlB30 ; 's' = D .dw 0x1594 ; 'f = F#
Приложение J. Полные тексты учебных программ -233- .dw 0x1120 ; 'u1 = A# .dw OxFFFF ; 'v' = не используется .dw OxlCCE ; 'W = C# .dw OxFFFF ; 'x' = не используется .dw 0x1339 ; 'у1 = G# .dw OxFFFF ; 'z' = не используется .dw OxFFFF ; 26 = не используется .org 43 ; Таблица кодов семисегментного индикатора .db ObOlllOOOl ; С .db 0bl0000000,0bl0000000 ; тире .db ObllllOOOl ; E .db OblOlllllO ; d# .db OblllOOOOl ; F .db ObOlll-0101 ; G .db OblllOOlll ; A .db OblOOOOOOO ; тире .db 0Ы1110100 ; b .db ObOlllOOOl ; С .db OblOllOllO ; d .db OblOOOOOOO,OblOOOOOOO ; тире .db ObOllllOOl ; C# .db OblOlllllO ; d# .db OblOOOOOOO,OblOOOOOOO ; тире .db OblOllOllO ; d .db 0Ы1101001 ; F# .db 0Ы1101111 ; A# .db OblOOOOOOO ; тире .db ObOllllOOl ; C# .db OblOOOOOOO ; тире .db ObOlllllOl ; G# .db OblOOOOOOO,OblOOOOOOO ; тире EndNote: clr temp out TCCRlA,temp; reti ; Change: in ZL,UDR ; Читаем данные subi ZL;0x61 ; Вычитаем 0x61 cpi ZL,26 ; Если ZL > 25, brio PC+2 ; ограничиваем ZL = 26 ldi ZL,26 lsl ZL ; Умножаем ZL на 2 subi ZL,-27 ; Прибавляем 27, указываем на старший байт lpm ; Считываем старший байт
Приложение J. Полные тексты учебных программ -234- out OCR1AH,RO ; Сохраняем в OCR1AH dec ZL ; Указываем на младший байт lpm ; Считываем младший байт out OCR1AL,RO ; Сохраняем в OCR1AL subi ZL,-60 ; Указываем на 2-ю таблицу lpm ; Читаем из таблицы out PortB,R0 ; Отображаем результат mov temp,R0 ; Копируем R0 в temp andi temp,0b00001000 ; Маскируем все биты, кроме 3-го out PortD,temp ; Копируем в PortD для включения СИД «#» ldi temp,ObOlOOOOOO ; 0С1 переключается при каждом прерывании out TCCRlA,temp ; по совпадению clr temp ; Сбрасываем TCNTO out TCNTO reti Init: ser temp ; Семисегментный код out DDRB, ; РВб - не используется, РВ7 - «Record» ldi temp,OblllllllO ; PDO - прием (RXD) out DDRD,temp ; PDl - передача (TXD) clr temp ; Подтяжка на входах порта В выключена out PortB,temp out PortD,temp ; ldi temp,0b00000101 ; Частота ТСО равна СК/1024 out TCCRO,temp ldi temp,ObOlOOOOOO ; ШИМ выключен out TCCRlA,temp ldi temp,0b00001001 ; Частота ТС1 равна СК, сбрасывать out TCCRlB,temp ; TCI при совпадении ldi temp,0b01000010 ; Разрешаем прерывание ТСО out TIMSK,temp ; Разрешаем прерывание TCI по совпадению А ldi temp,ObOOOOOOOO ; Запрещаем остальные прерывания out GIMSK,temp ldi temp,RAMEND out SPL,temp ldi temp,15 ; Скорость = 9600 out UBRR,temp ; ldi temp,Obi0010000 ; Разрешаем прием и прерывания по приему out UCR,temp ldi NoteH,0xlE ; При первом включении играем ноту «С» ldi NoteL,0x84
Приложение J. Полные тексты учебных программ -235- out OCRlAH,NoteH out OCRlAL,NoteL sei ; Разрешаем прерывания clr ZH ; Сбрасываем старший байт указателя ; Основное тело программы: Start: rjmp Start Программа Р. Робот, управляемый компьютером ; Автор:' Джон Мортон * ; Дата: 25/2/02 * ; Версия: 1.0 * ; Имя файла: robot.asm * ; Для AVR: 2313 * ; Тактовая частота: 4 МГц * ; Выполняемые функции: простейший робот, который обменивается командами ; с компьютером .device at90s2313 .nolist .include "С: \Program Files\Atmel\AVR Studio\Appnotes\2313def.inc" .list ; Объявления: .def temp =rl6 .def toggle =rl7 .def data =rl8 .def speedlO =rl9 .def speedl =r20 .def Hundreds =r21 .def Tens =r22 .def Thousands =r23 .def Ones =r24 .def DisplayNumber =r25 ; Начало программы
Приложение J. Полные тексты учебных программ -236- rjmp Init ; Первая выполняемая команда reti ; $001 reti ; $002 reti ; $003 reti ; $004 reti ; $005 rjmp Display ; $006 - переполнение ТС0 rjmp Received ; $007 - прием по UART пор ; $008 - буфер UART пустой reti ; $009 - передача по UART reti ; $00A ; Преобразование ASCII в семисегментный код .org 16 .db 0b00111111f0b00000110 ; 0, 1 .db 0b01011011;0b01001111 ; 2, 3 .db 0b01100110,0b01101101 ; 4, 5 .db 0b01111101,0b00000111 ; 6, 7 .db 0b01111111,0b01101111 ; 8, 9 .org 24 .db 0b01000000,0b01110111 ; -, A .db 0b01111100,0b00111001 ; b, С .db 0b01011110,0b01111001 ; d, E .db 0b01110001,0b00111101 ; Ff G .db 0b01110110,0b00000110 ; H, I .db 0b00011110,0b01000000 ; J, - .db 0b00111000,0b01000000 ; L, - .db 0b00110111,0b00111111 ; N, О .db 0b01110011,0b01000000 ; P, - .db 0b01010000,0b01101101 ; r, S .db 0b01111000,0b00111110 ; t, U .db 0b01000000,0b01000000 ; -, - .db 0b01000000,0b01101110 ; -, у .db ObOlOllOll ; Z .org 40 .db 0b01000000,0b01110111 ; -, A .db 0b01111100,0b01011000 ; b, с .db 0b01011110,0b01111001 ; d, E .db 0b01110001,0b01101111 ;F, g .db 0b01110100,0b00000100 ; h, i .db ObOOOllllO^bOlOOOOOO ; J, - .db 0b00000110,0b01000000 ; 1, - .db 0b01010100,0b01011100 ; n, о .db 0b01110011,0b01000000 ; P, - .db 0b01010000,0b01101101 ; rf S .db 0b010ul000,0b00011100 ; t, u .db 0b01000000,0b01000000 ; -, - .db 0b01000000,0b0110U10 ; -, у .db ObOlOllOll ; Z
Приложение J. Полные тексты учебных программ -237- ; Принята команда Received: in Data,UDR ; Запоминаем принятый байт cpi Data,0x5D ; Сравниваем его с символом «]» brne PC+2 ; Если не равен, пропускаем команду rjmp EndMessage ; Сбрасываем бит Т brtc PC+2 ; Проверяем бит Т (признак сообщения) rjmp Message cpi Data,0x67 ; Сравниваем байт с «g» breq GoStop cpi Data,0x74 ; Сравниваем байт с «t» breq Turning ; cpi Data,0x73 ; Сравниваем байт с «s» brne PC+2 rjmp ChangeSpeed cpi Data,0x2B ; Сравниваем байт с «+» brne PC+2 rjmp SpeedUp cpi Data,0x2D ; Сравниваем байт с «-» brne PC+2 rjmp SlowDown ; cpi Data,0x5B ; Сравниваем байт с «[» brne PC+2 set ; Устанавливаем бит Т reti ; Выходим из прерывания GoStop: in temp,TCCRlA ; Считываем текущее состояние ШИМ sbrc temp,0 rjmp Stop ; sbr temp,l ; Запускаем ШИМ out TCCRlA,temp ; cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу ldi temp,0x47 ; «G» rcall Send ; ldi temp,0x4F ; «0» rcall Send ldi temp,0x21 ; «!» rcall Send ; rjmp EndMessage
Приложение J. Полные тексты учебных программ -238- Stop: cbr temp,l ; Выключаем ШИМ out TCCRlA,temp cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу ldi temp,0x53 ; «S» rcall Send ; ldi temp,0x54 ; «T» rcall Send ldi temp,0x4F ; «0» rcall Send ldi temp,0x50 ; «P» rcall Send ; ldi temp,0x21 ; «!» rcall Send ; rjmp EndMessage Turning: in temp,PortB ; Переключаем левый двигатель eor temp,toggle ; out PortB,temp ; cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу ldi temp,0x54 ; «T» rcall Send ; ldi temp,0x75 ; «u» rcall Send ldi temp,0x72 ; «r» rcall Send ; ldi temp,0x6E ; «n» rcall Send ldi temp,0x69 ; «i» rcall Send ldi temp,0x6E ; «n» rcall Send ; ldi temp,0x67 ; «g» rcall Send rjmp EndMessage ChangeSpeed: sbis USR,RXC ; Ждем следующего байта rjmp ChangeSpeed ; in speedlO,UDR ; Считываем десятки mov data,speedl0 clr temp subi data,0x30 ; Сбрасываем в 0 TimeslO: breq CS2 subi temp,-10 ; dec data rjmp TimeslO ;
Приложение J. Полные тексты учебных программ -239- CS2: sbis USR,RXC rjmp CS2 in speedl,UDR ; Считываем единицы mov data,speedl subi data,0x30 add temp,data ; Прибавляем к десяткам mov data,temp ; Умножаем temp на 3 add temp,temp add temp,data ; brcc PC+2 ; ldi temp,O0xFF ; Ограничиваем по значению OxFF out OCRlAL,temp ; Выводим результат cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу ldi temp,0x53 ; «S» rcall Send ; ldi temp,0x70 ; «p» rcall Send ldi temp,0x65 ; «e» rcall Send ldi temp,0x65 ; «e» rcall Send ldi temp,0x64 ; «d» rcall Send ldi temp,0x20 ; « » rcall Send ; ldi temp,0x73 ; «s» rcall Send ldi temp,0x65 ; «e» rcall Send ldi temp,0x74 ; «t» rcall Send ldi temp,0x20 ; « » rcall Send ldi temp,0x74 ; «t» rcall Send ldi temp,0x6F ; «o» rcall Send ; ldi temp,0x20 ; « » rcall Send mov temp,speedlO ; Первая цифра rcall Send mov temp,speedl ; Вторая цифра rcall Send rjmp EndMessage
Приложение J. Полные тексты учебных программ -240- SpeedUp: in temp,OCRlAL ; Считываем текущее значение ldi data ДО add temp,data ; Прибавляем 10 brcc PC+2 ; Переполнение? ldi temp,0xFF ; Да, ограничиваем до OxFF out OCRlAL,temp ; Записываем обратно cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу ldi temp,0x53 ; «S» rcall Send ; ldi temp,0x70 ; «p» rcall Send ldi temp,0x65 ; «e» rcall Send ; ldi temp,0x65 ; «e» rcall Send ; ldi temp,0x64 ; «d» rcall Send ; ldi temp,0x69 ; «i» rcall Send ; ldi temp,0x6E ; «n» rcall Send ; ldi temp,0x67 ; «g» rcall Send ldi temp,0x20 ; « » rcall Send ; ldi temp,0x55 ; «U» rcall Send . ldi temp,0x70 ; «p» rcall Send ; rjmp EndMessage SlowDown: in temp,OCRlAL ; Считываем текущее значение subi temp,10 ; Вычитаем 10 brcc PC+2 ; Переполнение? clr temp ; Да, сбрасываем в 0 out OCRlAL,temp ; Записываем обратно cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу ldi temp,0x53 ; «S» rcall Send ; ldi temp,0x6C ; «1» rcall Send ; ldi temp,0x6F ; «o» rcall Send ldi temp,0x77 ; «w» rcall Send ; ldi temp,0x69 ; «i»
Приложение J. Полные тексты учебных программ -241 rcall Send ; ldi temp,0x6E ; «n» rcall Send ldi temp,0x67 ; «g» rcall Send ldi temp,0x20 ; « » rcall Send ; ldi temp,0x44 ; «D» rcall Send ; ldi temp,0x6F ; «o» rcall Send ; ldi temp,0x77 ; «w» rcall Send ; ldi temp,0x6E ; «n» rcall Send ; rjmp EndMessage Message: in ZL,UDR ; Считываем байт cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу mov . temp,ZL ; Передаем обратно в ПК rcall Send ; cbi UCR,TXEN ; Запрещаем прием sbi UCR,RXEN ; Разрешаем передачу subi ZL,0xl0 ; Вычитаем 16 lpm mov Thousands,Hundreds ; mov Hundreds,Tens ; mov Tens,Ones ; mov Ones,R0 ; reti EndMessage: clt ; Сбрасываем бит Т cbi UCR,RXEN ; Запрещаем прием sbi UCR,TXEN ; Разрешаем передачу ldi temp,0x0A ; Новая строка rcall Send ldi temp,0x0D ; Перевод каретки rcall Send ; cbi UCR,TXEN ; Запрещаем прием sbi UCR,RXEN ; Разрешаем передачу reti Send: out UDR,temp ; sbis USR,TXC rjmp Send+1 ; sbi USR,TXC ret
Приложение J. Полные тексты учебных программ -242- ; Подпрограмма для работы с дисплеем Display: inc DisplayNumber ; cpi DisplayNumber,4 brne PC+2 clr DisplayNumber ; ldi ZL,21 ; Устанавливаем ZL на R21 add ZL,DisplayNumber Id temp,Z out PortB,temp ; Выводим temp в порт В in temp,PortD lsl temp sbrc temp,7 ; Перемахнули? ldi temp,0b00001000 ; out PortD,temp ; reti Init: ldi temp,Obllllllll ; PBO-7 - выходы out DDRB,temp ldi temp;0blllllll0 ; PDO - вход, PD1-6 - выходы out DDRD,temp ; ldi temp,ObO0000000 ; Все индикаторы выключены out PortB,temp ; ldi temp, ObOO000100 ; Выберем 1-й индикатор out PortD,temp ; ldi temp,0b00000011 ; Частота Т/СО равна СК/64 out TCCR0,temp ldi temp,0bl0000000 ; Включаем 8-битный ШИМ out TCCRlA,temp ; Сбрасываем при прямом счете ldi temp,0b00000001 ; Частота Т/С1 равна СК out TCCRlB,temp ldi temp,ObO0000010 ; Разрешаем прерывание ; по переполнению Т/СО out TIMSK,temp ldi temp,0bl0010000 ; Разрешаем прерывания RXC и ТХС out UCR,temp ; Разрешаем прием ldi temp,15 out UBRR,temp ; ldi temp,RAMEND ; Инициализируем стек out SPL,temp
Приложение J. Полные тексты учебных программ -243- ldi toggle, 0Ы000000 clr DisplayNumber ; clr Thousands clr Hundreds clr Tens clr Ones ; clr ZH sei clt ; Сбрасываем бит Т ; Основное тело программы: Start: rjmp Start;
ОТВЕТЫ К УПРАЖНЕНИЯМ Ответы к упражнениям Главы 1 УПРАЖНЕНИЕ 1.1 а) Наибольшая степень двойки, меньшая 199, — это 128 = 27. Бит 7 = 1 Остается 199 - 128 = 71. 64 < 71, поэтому бит 6 = 1. Остается 71 — 64 = 7. 32 > 7, поэтому бит 5 = 0. 16 > 7, поэтому бит 4 = 0. 8 > 7, поэтому бит 3 = 0. 4 < 7, поэтому бит 2 = 1. Остается 7-4 = 3. 2 < 3, поэтому бит 1 = 1. Остается 3-2=1. 1 = 1, поэтому бит 0 = 1. Искомое двоичное число равно 11000111. ИЛИ... б) Делим 199 на 2. Частное 99, остаток 1. Делим 99 на 2. Частное 49, остаток 1. Делим 49 на 2. Частное 24, остаток 1. Делим 24 на 2. Частное 12, остаток 0. Делим 12 на 2. Частное 6, остаток 0. Делим 6 на 2. Частное 3, остаток 0. Делим 3 на 2. Частное 1, остаток 1. Делим 1 на 2. Частное 0, остаток 1. Таким образом, двоичный эквивалент числа равен 11000111. -244-
Ответы к упражнениям УПРАЖНЕНИЕ 1.2 а) Наибольшая степень двойки, меньшая 170, — это 128 = 27. Бит 7=1. Остается 170 - 128 = 42. 64 > 42, поэтому бит 6 = 0. 32 < 42, поэтому бит 5 = 1. Остается 42 - 32 = 10. 16 > 10, поэтому бит 4 = 0. 8 < 10, поэтому бит 3 = 1. Остается 10 — 8 = 2. 4 > 2, поэтому бит 2 = 0. 2 = 2, поэтому бит 1 = 1. Ничего не осталось, поэтому бит 0 = 0. Искомое двоичное число равно 10101010. ИЛИ... б) Делим 170 на 2. Частное 85, остаток 0. Делим 85 на 2. Частное 42, остаток 1. Делим 42 на 2. Частное 21, остаток 0. Делим 21 на 2. Частное 10, остаток 1. Делим 10 на 2. Частное 5, остаток 0. Делим 5 на 2. Частное 2, остаток 1. Делим 2 на 2. Частное 1, остаток 0. Делим 1 на 2. Частное 0, остаток 1. Таким образом, двоичный эквивалент числа — 10101010. УПРАЖНЕНИЕ 1.3 В числе 199 двенадцать раз по 16, разность 199 - 192 = 7. Таким образом, 1-й разряд = 12 = С, а 0-й разряд = 7. Соответственно, результат равен С7. УПРАЖНЕНИЕ 1.4 В числе 170 десять раз по 16, разность 170 — 160 = 10. Таким образом, 1-й разряд = А, а 0-й разряд = 10 = А. Соответственно, результат равен АА. УПРАЖНЕНИЕ 1.5 1110= 14 = Е. 0111=7. Соответственно, искомое число равно Е7. -245-
Ответы к упражнениям УПРАЖНЕНИЕ 1.6 //// 01011010 = 90 + 00001111 = 15 01101001 = 105 УПРАЖНЕНИЕ 1.7 40 = 00101000 50 = 00110010 -40=11010111 + 1 = 11011000 //// 11011000 = -40 + 00110010 = 50 00001010=10 УПРАЖНЕНИЕ 1.8 8 Кбайт памяти программ 512 байт EEPROM 512 байт ОЗУ УПРАЖНЕНИЕ 1.9 1. Для 15 кнопок требуется пять + три = восемь выводов (пять входов, три выхода). 2. Для семисегментных индикаторов требуется четыре + семь = один- одиннадцать выходов. Итого требуется девятнадцать контактов ввода/вывода. Соответственно, самым простым микроконтроллером из указанных в Приложении А, под- подходящим для такого устройства, является модель 4433. -246-
Ответы к упражнениям УПРАЖНЕНИЕ 1.10 УПРАЖНЕНИЕ 1.11 ObOOOOOOOl 001 0x01 ОЬООООООЮ 002 0x02 ОЬОООООЮО 004 0x04 ОЬООООЮОО 008 0x08 ОЬОООЮООО 016 0x10 ObOOlOOOOO 032 0x20 ObOlOOOOOO 064 0x40 OblOOOOOOO 128 0x80 ObOOOOOOOl 001 0x01 и так дал ее... -247- Инициализация Сдатчик давления сработал? Сбросить таймер Кнопка А нажата? Кнопка В или С нажата? ., Прошло Юс? Кнопка В нажата? Кнопка А или С ч^ нажата? ^ Прошло Юс? Кнопка С нажата? у^ ^ч Кнопка А или В ^ нажата? . Прошло Юс? ТРЕВОГА!
Ответы к упражнениям УПРАЖНЕНИЕ 1.12 В произвольном порядке: ObOOOOOOll 003 0x03 0Ь00000101 005 0x05 ObOOOOOllO 006 0x06 ObOOOOOlll 007 0x07 УПРАЖНЕНИЕ 1.13 Ответы к упражнениям Главы 2 УПРАЖНЕНИЕ 2.1 УПРАЖНЕНИЕ 2.3 0: ObllllllOO 1: ObOllOOOOO или ObOOOOllOO 2: 0Ы1011010 3: ObllllOOlO 4: ObOllOOllO 5: 0Ы0110110 6: OblOlllllO 7: OblllOOOOO 8: OblllllllO 9: ObllllOllO или OblllOOllO A: OblllOlllO b: ObOOlllllO c: ObOOOllOlO d: ObOllllOlO -248- ldi temp,OblllllllO ; PB0 - вход, РВ1...3 - выходы, out DDRB,temp ; PB4...7 - не используются ldi temp,0b000111 ; PD0...2 - выходы, PD3...5 - входы, out DDRD,temp ; PD6, PD7 - не используются ldi temp,ObO0000001 ; PB0 - подтяжка вкл, РВ1...3 = 0 out PortB,temp ; clr temp ; PD0...2 = 0, подтяжки нет out PortD,temp ; cbi PortB,0 ; Включаем СИД rjmp Start ; Возвращаемся к началу УПРАЖНЕНИЕ 2.2 LEDoff: sbi PortB,0 ; Выключаем СИД rjmp Start ; Возвращаемся к началу
Ответы к упражнениям -249- Е: 0Ы0011110 F: 0Ы0001110 УПРАЖНЕНИЕ 2.4 clr ZL ; Очистим ZL clr ZH ; Очистим ZH ClearLoop: st ZL,Z ; Загружаем ZL в Rx inc ZL ; Перейти к следующему адресу cpi ZL,16 ; Ушли слишком далеко? brne ClearLoop ; Нет, продолжаем УПРАЖНЕНИЕ 2.5 Start: sbic PinD,0 ; Кнопка нажата? rjmp Start ; Нет, остаемся в цикле inc Counter ; Да, увеличиваем Counter на 1 УПРАЖНЕНИЕ 2.6 cpi Counter,10 ; Counter = 10? brne PC+2 ; Нет, пропускаем команду clr Counter ; Да, сбрасываем Counter УПРАЖНЕНИЕ 2.7 Idi ZL,20 ; Устанавливаем ZL на R20 add ZL,Counter ; Прибавляем Counter к ZL Id temp,Z ; Считываем Rx в temp out PortB,temp ; Выводим temp в порт В rjmp Start ; Возвращаемся к Start УПРАЖНЕНИЕ 2.8 ReleaseWait: sbis PinD,0 ; Кнопка отпущена? rjmp ReleaseWait ; Нет, остаемся в цикле rjmp Start ; Да, возвращаемся к Start УПРАЖНЕНИЕ 2.9 Счет внешних импульсов осуществляется по нарастающему фронту, поэтому число равно ОЬОООООШ.
Ответы к упражнениям -250- УПРАЖНЕНИЕ 2.10 UpTest: sbic PinD,l ; Проверяем кнопку увеличения скорости rjmp Timer ; He нажата, переходим dec Speed ; Уменьшаем Speed на 1 brne ReleaseUp ; Переходим к ReleaseUp, ; если не О inc Speed ; Увеличиваем Speed на 1 ReleaseUp: sbis PinD,l ; Ждем отпускания кнопки rjmp ReleaseUp ; УПРАЖНЕНИЕ 2.11 mov Counter,Speed ; Копируем Speed в Counter УПРАЖНЕНИЕ 2.12 Загружает число ОЗС в PC. УПРАЖНЕНИЕ 2.13 400 000 тактов. Делим на 5, получаем 80 000 = 0x13880. Для хранения этого числа потребуется три регистра. Стартовые значе- значения этих регистров будут составлять 0x80, 0x38 и 0x01. УПРАЖНЕНИЕ 2.14 Debounce: ldi Delayl,0x80 ; Инициализируем регистры счетчика ldi Delay2,0x38 ldi Delay3,0x01 Loop: subi Delayl,l ; Формируем задержку sbci Delay2,0 sbci Delay3,0 brcc Loop ret ; Выходим из подпрограммы УПРАЖНЕНИЕ 2.15 Start: ldi temp,ObO0010001 ; Автомобили: зеленый out PortB,temp ; Пешеходы: temp
Ответы к упражнениям -251- УПРАЖНЕНИЕ 2.16 sbic PinD,0 ; Проверяем кнопку rjmp Start ; He нажата УПРАЖНЕНИЕ 2.17 sbi PortB,5 ; Включаем сигнал «ЖДИТЕ» УПРАЖНЕНИЕ 2.18 Loop : rcall Timer ; Проверяем время brts Loop ; Остаемся в цикле, пока флаг Т ; не установится УПРАЖНЕНИЕ 2.19 sbi PortB,l ; Автомобили, желтый - вкл cbi PortB,0 ; Автомобили, зеленый - выкл УПРАЖНЕНИЕ 2.20 ldi temp,ObOOOOl100 ; Автомобили: красный out PortB,temp ; Пешеходы: зеленый УПРАЖНЕНИЕ 2.21 ldi temp,16 ; 8-секундная задержка EightSeconds: rcall HalfSecond dec temp brne EightSeconds ; УПРАЖНЕНИЕ 2.22 ldi temp,ObOOOOl010 ; Автомобили: желтый out PortB,temp ; Пешеходы: зеленый УПРАЖНЕНИЕ 2.23 0Ы0110011 -> ObOlOOllOO УПРАЖНЕНИЕ 2.24 ldi tog,0b00001010 ; Инициализируем регистр tog in temp,PinB ; Считываем состояние сигналов eor temp,tog ; Переключаем out PortB,temp ; Выводим
Ответы к упражнениям -252- УПРАЖНЕНИЕ 2.25 ldi tog,ObOOOOlOlO ; Инициализируем регистр tog ldi Counter,8 ; Инициализируем регистр Counter FlashLoop: rcall HalfSecond ; Ждем 0.5 с in temp,PinB ; Считываем состояние сигналов eor temp,tog ; Переключаем out PortB,temp ; Выводим dec Counter ; Повторяем 8 раз brne FlashLoop УПРАЖНЕНИЕ 2.26 set ; Устанавливаем бит Т rjmp Start ; Возвращаемся к Start УПРАЖНЕНИЕ 2.27 1 228 800 тактов. Делим на 5, получаем 245 760 = ОхЗСООО. Разбиваем на три регистра, их начальные значения составляют соот- соответственно 0x00, ОхСО и 0x03. HalfSecond: clr Delayl ; Инициализируем регистры ldi Delay2,0xC0 ; счетчика ldi Delay3,0x03 HalfLoop: subi Delayl,1 ; Вставляем задержку sbci Delay2,0 sbci Delay3,0 ; brcc HalfLoop ; ret УПРАЖНЕНИЕ 2.28 Timer: brts PC+2 ; Проверяем бит T, пропускаем, если 1 ret ; Возвращаемся, если Т = О УПРАЖНЕНИЕ 2.29 in temp,TCNT0 ; Сохраняем состояние таймера ; в регистре temp cpse temp,Mark240 ; Сравниваем temp с Mark240 ret ; Если не равны - возвращаемся subi Mark240,-240 ; Прибавляем 240 к Mark240 dec Count250 ; Вычитаем 1 из Count250 breq PC+2 ; Если 0 - пропускаем следующую команду
Ответы к упражнениям -253- ret ; Если не 0 - возвращаемся ldi Count250,250 ; Переустанавливаем Count250 clt ; Сбрасываем бит Т ret УПРАЖНЕНИЕ 2.30 I Инициализация I < Определить тип симулируемого лог, элемента i Считать входные сигналы и сформировать соответствующий выходной сигнал I УПРАЖНЕНИЕ 2.31 lsr УПРАЖНЕНИЕ 2.32 Start: in ZL,PinB ; Считываем сигналы с порта В andi ZL,0b001110 ; Маскируем биты 0, 4 и 5 lsr ZL ; Сдвигаем УПРАЖНЕНИЕ 2.33 subi ZL,-2 ; Прибавляем 2 к ZL lpm ; Считываем в R0 значение из таблицы УПРАЖНЕНИЕ 2.34 NAND 1110 -> 00110010 NOR 1000 -> 00100000 ENOR 1001 -> 00100001 EOR ОНО -> 00010010 ШТ 1100 -> 00110000 BUFFER ООН -> 00000011
Ответы к упражнениям -254- УПРАЖНЕНИЕ 2.35 .dw ОЬООНООЮООЮОООО ; NAND и NOR .dw ОЬООЮОООЮООЮОЮ ; ENOR и EOR .dw ObOOllOOOOOOOOOOll ; NOT и BUFFER УПРАЖНЕНИЕ 2.36 mov temp,R0 ; Копируем RO в temp ori temp,ObllllO ; Устанавливаем биты 1...4 out PortB,temp ; Выводим результат rjmp Start ; Возвращаемся к Start УПРАЖНЕНИЕ 2.37 64 мс = 256 000 тактов Делим на 8, получаем 32 000 приращений = 0x7D00. Стартовые значения: Delayl = 0x00, Delay2 = 0x7D. УПРАЖНЕНИЕ 2.38 DoneHi: in lowerbyte,TCNTO ; Сохраняем состояние TCNTO cp lowerbyte,temp ; Сравниваем с предыдущим значением brsh Divide64 ; Если норма - переходим к Divide64 inc upperbyte ; Инкрементируем старший байт cpi upperbyte,OxFA ; Не слишком ли велико? breq TooHigh ; Если да, переходим к TooHigh УПРАЖНЕНИЕ 2.39 lsr upperbyte ; Сдвигаем вправо, бит 7=0 ror lowerbyte ; Сдвигаем вправо, ; бит 7 = флаг переноса УПРАЖНЕНИЕ 2.40 Divide64: ldi temp,б ; Пишем в temp б lsr upperbyte ; Делим 2-байтное слово на 2 ror lowerbyte ; dec temp ; Повторяем б раз brne Divide64+1 ; Остаемся в цикле до конца УПРАЖНЕНИЕ 2.41 cpi upperbyte,0 ; Старший байт = О? brne РС+3 ; Пропускаем 2 команды
Ответы к упражнениям -255- cpi lowerbyte,0 ; Младший байт =0? breq LowSpeed ; Переходим, если f < 1 кГц УПРАЖНЕНИЕ 2.42 Надо потратить 2 000 000 тактов. Длина цикла 14 тактов => 142 857 = 0х022Е09 Соответственно, запишем в регистры 0x09, 0х2Е и 0x02. ldi Delayl,0x09 ; Инициализируем регистры задержки ldi Delay2,0x2E ldi Delay3,0x02 HalfSecond: rcall Display ; Включаем дисплей на 0.5 с subi Delayl,1 ; sbci Delay2,0 sbci Delay3,0 brcc HalfSecond ; rjmp Start ; Возвращаемся к Start УПРАЖНЕНИЕ 2.43 TooHigh: ldi Hundreds,11 ; Код для «-» ldi Tens ДО ; Код для «Н» ldi Ones,l ; Код для «I» rjmp HalfSecond-3 ; Отображаем на 0.5 с УПРАЖНЕНИЕ 2.44 Display: dec DisplayCounter ; Обновляем изображение каждый 50-й вызов breq РС+2 ; Пропускаем, если 50-й вызов ret ; Выходим wdr ; «Бросим кость собаке» ldi DisplayCounter,50 ; Переустанавливаем DisplayCounter УПРАЖНЕНИЕ 2.45 inc DisplayNumber ; Инкрементируем DisplayNumber cpi DisplayNumber,3 ; Уже З? brne PC+2 ; Нет, пропускаем команду clr DisplayNumber ; Да, сбрасываем DisplayNumber УПРАЖНЕНИЕ 2.46 ldi ZL,26 ; Устанавливаем ZL на R26 add ZL,DisplayNumber ; Указываем на требуемый разряд Id temp,Z ; Загружаем код в temp clr ZL ; Устанавливаем ZL на R0
Ответы к упражнениям -256- add ZL,temp ; Прибавляем temp к ZL Id temp,Z ; Считываем Rx в temp sbic PortB,7 ; Проверяем СИД «кГц» ori temp,0bl0000000 ; Если включен, не гасим его out PortB,temp ; Выводим temp в порт В УПРАЖНЕНИЕ 2.47 in temp,PinD ; Считываем сигналы порта D lsl temp ; Сдвигаем влево sbrc temp,3 ; Проверяем 3-й бит ldi temp,0b00000001 ; Сбрасываем, если ушли out PortD,temp ; Выводим результаты в порт D ret ; Возвращаемся из подпрограммы УПРАЖНЕНИЕ 2.48 LowSpeed: ldi temp,0b00000001 ; Период счета TCNT0 = СК out TCCR0,temp clr Delay2 ; Сбрасываем регистры задержки clr Delay3 cbi PortB,7 ; Сбрасываем РВ7 (включаем СИД «Гц») УПРАЖНЕНИЕ 2.49 ldi Counter,2 ; Инициализируем Counter clr Delayl ; Сбрасываем Delayl и TCNTO out TCNTO,Delayl УПРАЖНЕНИЕ 2.50 in store,PinD ; Запоминаем исходное состояние LowLoop: in store2,PinD ; Считываем текущее состояние eor store2,store ; Сравниваем sbrc store2,4 ; Пропускаем, если PD4 не изменился rjmp Change ; Переходим, если PD4 изменился УПРАЖНЕНИЕ 2.51 гcall Display ; Обрабатываем дисплей mov temp2,Delayl ; Сохраняем старое значение in Delayl,TCNTO ; Считываем новое значение ср Delayl,temp2 ; Сравниваем brsh LowLoop ; Возвращаемся, если новое >= старого inc Delay2 ; Инкрементируем старший байт brne LowLoop ; Возвращаемся, если не О
Ответы к упражнениям -257- inc Delay3 ; Инкрементируем старший байт cpi Delay3,0x3E ; Слишком низкая частота? breq TooSlow ; Да rjmp LowLoop ; Нет, возвращаемся УПРАЖНЕНИЕ 2.52 Divide: sub temp,Delay1 ; Вычитаем результат из 400 000 sbc temp2/Delay2 sbc temp3,Delay3 ; brcs DoneDividing ; Если С = 1, заканчиваем деление inc lowerbyte ; Если нет, увеличиваем на 1 brne Divide ; Переполнение? inc upperbyte ; Да, инкрементируем старший байт rjmp Divide ; Начинаем новый проход цикла УПРАЖНЕНИЕ 2.53 DoneDividing: rcall DigitConvert ; Преобразуем ответ в разряды rjmp LowSpeed ; Возвращаемся к началу секции УПРАЖНЕНИЕ 2.54 TooSlow: clr temp ; out PortD,temp ; Выключаем индикаторы sleep ; Засыпаем Ответы к упражнениям Главы 4 УПРАЖНЕНИЕ 4.1 rjmp Init ; Первая выполняемая команда rjmp Extlnt ; Обработчик внешнего прерывания rjmp Overflowlnt ; Обработчик прерывания Т/СО УПРАЖНЕНИЕ 4.2 ldi temp,0b01000000 ; Устанавливаем бит б - разрешаем out GIMSK,temp ; внешнее прерывание INTO clr temp ; по НИЗКОМУ уровню out MCUCR, temp ldi temp,0b00000010 ; Разрешаем прерывание от TCNT0 out TIMSK,temp
Ответы к упражнениям УПРАЖНЕНИЕ 4.3 Start: rcall Display ; Обработаем дисплей sbic PinD,l ; Ждем нажатия кнопки «Готов» • rjmp Start ; Остаемся в цикле, если не нажата УПРАЖНЕНИЕ 4.4 mov. temp,Random ; Умножаем на 5 и... add Random,temp add Random,temp ; add Random,temp ; add Random,temp ; inc Random ; ... прибавляем 1 УПРАЖНЕНИЕ 4.5 mov CountX,Random ; lsr CountX ; Делим на 2 subi CountX,-60 ; и прибавляем 60 УПРАЖНЕНИЕ 4.6 clr TimeH ; Сбрасываем регистр out PortB,TimeH ; Выключаем дисплей ldi temp,0b0100000 ; Сбрасываем флаг внешнего out GIFR ; прерывания INTO ldi temp,0b00000010 ; Сбрасываем флаг прерывания out TIFR ; по переполнению Т/СО УПРАЖНЕНИЕ 4.7 sei ; Разрешаем прерывания Loopy: brid Start ; Переходим к Start, если rjmp Loopy ; прерывания запрещены УПРАЖНЕНИЕ 4.8 Extlnt: sbis PinD,0 ; Проверяем СИД rjmp Cheat ; clr temp ; Останавливаем Т/СО out TCCRO,temp in TimeL,TCNT0 ; Считываем значение TCNTO in temp,TIFR ; Проверяем TCNTO на переполнение sbrc temp,l ; inc TimeH subi TimeL,0xA2 ; Вычитаем 0хА2 из суммарного -258-
Ответы к упражнениям -259- sbci TimeH,0 ; времени реакции ldi temp,ObOOOO0101 ; Перезапускаем TCNT0 с out TCCR0,temp ; периодом счета СК/1024 УПРАЖНЕНИЕ 4.9 Cheat: ldi Hundreds ДО ; «b» ldi Tens ,11 ;. «A» ldi Ones,12 ; «d» ret УПРАЖНЕНИЕ 4.10 clr TimeL ; Сбрасываем регистры clr TimeH ; результата Dividel2: subi temp,12 ; Вычитаем 12 sbci tempH,0 brcs DoneDividing ; Выходим при переносе inc TimeL ; Инкрементируем младший байт результата brne Dividel2 ; Младший байт =0? inc TimeH ; Да, инкрементируем ; старший байт результата rjmp Dividel2 ; Возвращаемся к началу цикла УПРАЖНЕНИЕ 4.11 Start: ldi temp,0b00001000 ; Задаем начальное состояние порта D out PortD,temp ; sbis ACSR,5 ; Проверяем результат АК cbi PortD,3 ; Сбрасываем 3-й бит, если 0 sbi PortD,2 ; Устанавливаем 2-й бит УПРАЖНЕНИЕ 4.12 sbis ACSR,5 ; Проверяем результат АК cbi PortD,2 ; Сбрасываем 2-й бит, если 0 sbi PortD,l ; Устанавливаем 1-й бит sbis ACSR,5 ; Проверяем результат АК cbi PortD,l ; Сбрасываем 1-й бит, если 0 sbi PortD,0 ; Устанавливаем 0-й бит sbis ACSR,5 ; Проверяем результат АК cbi PortD,0 ; Сбрасываем 0-й бит, если 0
Ответы к упражнениям УПРАЖНЕНИЕ 4.13 in temp,PortD ; Считываем результат swap temp ; Меняем местами биты 0-3 и 4-7 out PortB,temp ; Выводим результат rjmp Start УПРАЖНЕНИЕ 4.14 0Ы1100011 ¦> ADCSR 0Ь00000000 -> ADMUX УПРАЖНЕНИЕ 4.15 Start: cbi ADMUX,0 ; Выбираем вход ADCO sbi ADCSR,ADSC ; Начинаем преобразование sbic ADCSR,ADSC ; Преобразование завершено? rjmp Start+2 ; Нет, ждем УПРАЖНЕНИЕ 4.16 in Desired,ADCH ; Считываем 8-битный результат com Desired ; 5 - результат sbi ADMUX,0 ; Выбираем вход ADC1 sbi ADCSR,ADSC ; Запускаем преобразование Wait: sbic ADCSR,ADSC ; Преобразование завершено? rjmp Wait ; Нет, ждем УПРАЖНЕНИЕ 4.17 in Actual,ADCH ; Считываем текущее напряжение cp Actual,Desired ; Сравниваем с требуемым brio TooLow ; Слишком мало? cp Desired,Actual ; brio TooHigh ; Слишком велико? cbi DDRB,0 ; Текущее равно требуемому, rjmp Start ; поэтому делаем РВО входом ; и возвращаемся к Start УПРАЖНЕНИЕ 4.18 TooHigh: sbi DDRB,0 ; Делаем РВО выходом sbi PortB,0 ; и устанавливаем на нем 5 В rjmp Start ; Возвращаемся к Start TooLow: sbi DDRB,0 ; Делаем РВО выходом -260-
Ответы к упражнениям — 261 — cbi PortB,О ; и устанавливаем на нем 0 В rjmp Start ; Возвращаемся к Start УПРАЖНЕНИЕ 4.19 clr Address ; Первый адрес 0x00 ldi Data,0x30 ; Код ASCII для «0» - 0x30 ASCIILoop: out EEAR,Address out EEDR,Data sbi EECR,1 ; Начнем запись ЕЕWait: sbic EECR,1 ; Ждем окончания записи rjmp EEWait ; (пока EECR.l не станет = 0) inc Address ; Выбираем следующий адрес inc Data ; Задаем следующий код ASCII cpi Data,0x3A ; Цифры закончились? brne PC+2 ; Если да, пропустить команду ldi Data,0x41 ; Код ASCII для «А» - 0x41 cpi Data,0x47 ; Все сделали? brne ASCIILoop ; Да, прекращаем цикл УПРАЖНЕНИЕ 4.20 ObOOOOllOl ¦> TCCR1B ; Тактовая частота Т/Cl равна СК/1024 ; Сбрасывать Т/Cl при совпадении 0Ь01000000 -> TIMSK ; Разрешить прерывание по совпадению OxOF -> OCR1AH ; 4 МГц/1024 = 3906 Гц 0x42 -> OCR1AL ; 3906 = 0xF42 УПРАЖНЕНИЕ 4.21 ToggleOut: in temp,PortB ; Считаем содержимое PortB com temp ; Инвертируем биты out PortB,temp ; Изменяем состояние порта В reti ; Возвращаемся УПРАЖНЕНИЕ 4.22 ChangeNote: dec Length breq PC+2 ; Пропускаем, если reti ; прошло достаточно времени Rest: in temp,TIFR ; Ждем установки флага sbrs . temp,l ; переполнения Т/СО rjmp Rest
Ответы к упражнениям ldi temp,ObOOOOOOlO ; Сбрасываем флаг прерывания out TIFR,temp УПРАЖНЕНИЕ 4.23 Read_EEPROM: out EEARL,address ; Задаем адрес sbi EECR,0 ; Начинаем чтение EEPROM in ZL,EEDR ; Сохраняем считанное значение andi ZL,0b00001111 ; Маскируем старшую тетраду cpi ZL,0x0C ; Сравниваем с ОхОС breq Reset ; Повторяем мелодию, если ОхОС brio РС+2 ; ZL < ОхОС? ldi ZL;0x00 ; Если нет, выбираем ноту 'С УПРАЖНЕНИЕ 4.24 lsl ZL ; Умножаем ZL на 2 subi ZL,-0x26 ; Прибавляем 26, чтобы указать на таблицу lpm ; Читаем значение из таблицы mov NoteL,R0 ; Сохраняем младший байт inc ZL ; Переходим к следующему адресу lpm ; Читаем значение из таблицы mov NoteH,R0 ; Сохраняем старший байт УПРАЖНЕНИЕ 4.25 in temp,EEDR ; Запоминаем данные swap temp ; Переставляем тетрады andi temp,0b00000011 ; Выделяем требуемые биты SetOctave: breq GetLength ; Переходим, если О lsr NoteH ; Сдвигаем старший байт ror NoteL ; Сдвигаем младший байт (с учетом переноса) dec temp ; Повторяем для каждой октавы rjmp GetOctave ; УПРАЖНЕНИЕ 4.26 SetLength: out OCR1AH,NoteH ; Записываем значение новой out OCR1AL, NoteL ; ноты в регистры сравнения in temp,EEDR ; Повторно читаем из EEPROM andi temp,0bll000000 ; Маскируем биты swap temp ; Переставляем тетрады lsr temp ; Сдвигаем на один бит subi temp,-2 ; Прибавляем двойку mov Length,tempM ; Копируем в Length reti ; Возвращаемся -262-
Ответы к упражнениям -263- Ответы к упражнениям Главы 5 УПРАЖНЕНИЕ 5.1 ldi Counter,8 ; Инициализируем счетчик clr parityreg ; Сбрасываем регистр четности Parity: lsr temp ; Сдвигаем temp вправо brcc PC+2 inc parityreg ; dec Counter,8 ; Сдвинули 8 раз? brne Parity ; Теперь в бите 0 регистра parityreg находится бит четности для содержи- содержимого регистра temp. УПРАЖНЕНИЕ 5.2 Change: in ZL,UDR ; Читаем данные subi ZL,0x61 ; Вычитаем 0x61 cpi ZL,26 ; Если ZL > 25, brio PC+2 ; ограничиваем ZL = 26 ldi ZL,26 lsl ZL ; Умножаем ZL на 2 subi ZL,-27 ; Прибавляем 27 (указываем на старший байт) lpm ; Считываем старший байт out OCR1AH,RO ; Сохраняем в OCR1AH dec ZL ; Указываем на младший байт lpm ; Считываем младший байт out OCR1AL,RO ; Сохраняем в OCR1AL УПРАЖНЕНИЕ 5.3 subi ZL,-60 ; Указываем на 2-ю таблицу lpm ; Читаем из таблицы out PortB,R0 ; Показываем результат mov temp,R0 ; Копируем R0 в temp andi temp,0b00001000 ; Маскируем все биты, кроме 3-го out PortD, temp ; Выводим в PortD для включения СИД «#» УПРАЖНЕНИЕ 5.4 ldi temp,0b01000000 ; 0C1 переключается при каждом out TCCRlA,temp ; прерывании по совпадению clr temp ; Сбрасываем TCNTO
Ответы к упражнениям -264- out TCNT0 reti УПРАЖНЕНИЕ 5.5 EndNote: clr temp ; Отключаем вывод 0С1 out TCCRlA,temp ; от таймера Т/С1 reti ; УПРАЖНЕНИЕ 5.6 .macro skeq ; Назовем этот макрос skeq breq PC+2 .endmacro УПРАЖНЕНИЕ 5.7 .macro HiWait ; Назовем этот макрос HiWait sbis @0,@l ; Проверим операнд rjmp PC-1 ; Остаемся в цикле, пока ; заданный бит не установится .endmacro УПРАЖНЕНИЕ 5.8 Display: inc DisplayNumber ; Выберем следующий индикатор cpi DisplayNumber,4 ; Ушли слишком далеко? brne РС+2 clr DisplayNumber ; Да, снова указываем на 1-й ldi ZL,21 ; Устанавливаем ZL на R21 add ZL,DisplayNumber ; Прибавляем N индикатора Id temp,Z ; Считываем значение out PortB,temp ; Выводим его в порт В in temp,PortD ; Считываем текущее значение lsl temp ; Указываем на следующий индикатор sbrc temp,7 ; Перескочили? ldi temp,0b00001000 ; Переходим к первому out PortD,temp ; Выводим результат в порт reti ; Возвращаемся, разрешая прерывания
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ -265- .asm — 37 .cseg — 141 .db — 141 .dw — 150 .endmacro — 170 .eseg — 141 .macro — 170 .org — 141, 149 A ACSR — 128 adc — 94 ADCH — 132, 133 ADCL — 132, 133 ADCSR — 133, 134 add — 94 adiw — 114 ADMUX — 132, 134 AND — 84 and — 94 andi — 87 ASCII — 141, 160, 162, 174, 198 asr — 67 AVR Studio — 39, 40, 45 В Baud rate — 755, 156, 159 bclr — 91 bid — 95 brbc — 53 brbs — 53 brcc — 68 breq — 53 brne — 5J brts — 80 bset — 91 bst — P5 BUFFER — 85 С call — 171 cbi — J<?, 45 clr — 35 com — 81 cp — 52 cpi — 5J, 65 cpse — P5 D DDRB — 34 DDRD — 34 dec — 6J, 66, 71 E EEAR — 140 EECR — /40 EEDR — 140 EEPROM — 20, 140, 141, 146, 165 ENOR — 84 EOR -— 81, 82 eor — 82 F FLASH — 20 G GIFR — 120 GIMSK — 119 H Нех-файл — 25 HyperTerminal — 159, 160 I icall — 113 ICE см. Внутрисхемная эмуляция ICP — 143 ICR1H — 143 1CR1L— 143 Idle см. Спящий режим ijmp — 113 in — 49 inc — 66 Input capture см. Функция захвата INTO см. Внешнее прерывание IOR — 81
Предметный указатель Power Down см. Спящий режим push — 114 R RAM см. ОЗУ rcall — 73 RCEN см. Конфигурационные ячейки ret — 73 reti — 120, 145 RISC — 19 rjmp — 33, 38, 48, 72 rol — 67 ror — 67 RS-232 — 159, 161 RXD — 155, 159 S sbc — 94 sbci — 71 sbi — 38, 45 sbic — 47, 65 sbis — 48 sbiw — 114 sei — 125 ser — 35 set — 82 SFIOR см. Регистр специальных функций sleep — 93 SPCR — 165, 166 SPDR — 165 SPH — 112 SPI см. Последовательный периферийный интерфейс SPI EN см. Конфигурационные ячейки SPL — 112 SPSR — 165 srbc — 95 srbs — 95 SREG см. Регистр состояния st — 52, 111 std — 113 Step Into см. Команды симулятора Step Out см. Команды симулятора Step Over см. Команды симулятора STK500 — 46, 159 stm — 171 -266- J jmp — 171 JTAG см. Интерфейс JTAG L Id — 54, 111, 112, 113 ldd — 113 ldi — 35 Ids — 113 lpm — 55, 86, 90, 149 LSB — 14 lsl — 67, 149 lsr — 67 M MCUCR — 94, 119 MegaAVR — 171 mov — 67 MSB — 75 mul — 171 N NAND.— 84 neg — 94 пор — 94, 100, 145 NOR — 84 NOT — 85 О OC1 — 152 OCR1AH — 143, 146, 153 OCR1AL — 143, 146, 153 or— 94 ori — 90 out — 35, 49 Output compare см. Функция сравнения P PCK — 167 PIC — 13 PinB — 27, 41 PinD — 27, 29, 49 pop — 114 PortB — 27, 38, 41 PortD — 27
Предметный указатель -267- sts — 113 sub — 94 subi — 62, 71 swap — 90 т TCCRO — 60 TCCR1 — 168 TCCR1A — 154 TCCR1B — 144 TCNTO — 60, 69 TCNT1H — 142 TCNT1L— 142 TEMP — 142 TIFR — 120 TIMSK — 119, 143, 146 Tiny 15 — 133, 167 TinyAVR — 85, 133, 167 TXD — /55, 159 U UART — 154, 178 UBRR — 156 UCR — 156, 157 UDR — 156 USR — 156, 158 w wdr — 92 WDTCR — 92, 93 X x— in XOR — 81, 82 XTAL1/2 — 42 Y Y— HI Z • Z — 25, 26, 51, 86 ZH — 26, 52 ZL — 26, 52 A Адрес (байт) — 88, 149 Адрес (слово) — 88, 89, 149 Адресация косвенная — 49, 51 побайтная — 88, 149 пословная — 88, 149 прямая — 51 Аналоговый компаратор — 128 Аналого-цифровой преобразователь — 129 Ассемблирование — 20, 25, 32, 39 АЦП 10-битный — 132 4-битный — 129 Б Бегущий огонек — 63 Безопасное программирование — 145 Бит — 14, 16 Бит Т — 92, 95 Бит четности — 155 Блок-схема — 20, 23 В Ведущий/ведомый — 163 Включаемый файл — 32 Внешнее прерывание — 118, 119 Внутрисхемная эмуляция — 41 Временные интервалы — 60 Временный бит см. Бит Т Входы — 19, 27, 34 Выходы — 19, 28, 34 д Двоичная система — 14, 19 Действительный знак — 18 Деление — 97, 109 Десятичная система — 14, 19 Директива — 32, 89, 141, 170 .cseg — 141 .db — 89, 90, 141 .def— 33 .device — 32 .dw — 89, 150 .endmacro — 170 .equ — 33 .eseg — 141 .exit — 36 .include — 32 .list — 32
Предметный указатель -268- . macro — 770 .nolist — 32 .org — 141, 149 Дифференциальный усилитель — 134 Дополнительный код — 17, 18, 94 Дребезг контактов — 58, 75 3 Загрузка — 54, 111, 112 Задержка повторения — 163 И Измеритель скорости реакции — 120 Инвертирование — 81 Интерфейс JTAG — 171, 178 Исходный текст — 37 к Квитирование — 159 Команда adc — 94 add — 94 adiw — 114 and — 94 andi — 87 asr — 67 bclr — 91 bid — 95 brbc — 53 brbs — 53 brcc — 68 breq — 53 brne — 53 brtc — 80 bits — 80 bset — 91 bst — 95 call — 171 cbi — 38, 45 clr — 35 com — 81 cp — 52 cpc — 95 cpi — 53, 66 cpse — 95 dec — 63, 66, 71 eor — 82 icall — 113 ijmp — Ш in — 49 inc — 66 jmp — 777 Id — 54, 111, 112, 113 ldd — 113 ldi — 35 Ids — 77J lpm — 55, 86, 90, 149 lsl — 67, 149 lsr — 67 mov — 67 mul — 777 neg — 94 пор — 94, 100, 145 or — 94 ori — 90 out — J5, 49 pop — 774 push — 114 rcall — Z? ret — 73 reti — 120, 145 rjmp — JJ, 38, 48, 72 rol — 67 ror — 67 sbc — 94 sbci — 77 sbi — 38, 45 sbic — 47, 65 sbis — 48 sbiw — 774 sei — 125 ser — J5 set — 82 sleep — 93 srbc — P5 srbs — 95 st — 52, 777 std — 113 stm — 777 sts — 77J sub — 94 subi — 62, 71 swap — 90 wdr — 92 Команды — 12, 29, 182, 186 Команды симулятора Step Into — 40
Предметный указатель Step Out — 75 Step Over — 75 Комментарии — 30 Контакты ввода/вывода — 19, 27, 34 Конфигурационные ячейки RCEN — 46 SPIEN — 46 Косвенный переход — ИЗ Коэффициент заполнения — 152 Л Линейно-конгруэнтный метод — 124 Логическая 1 — 19 Логическая операция — 81 Логические элементы буфер (BUFFER) — 85 Включающее ИЛИ (IOR) — 81 И (AND) — 84 ИЛИ-НЕ (NOR) — 84 И-НЕ (NAND) — 84 Исключающее ИЛИ (EOR, XOR) - 81, 82, 106 Исключающее ИЛИ-НЕ (ENOR) - 84 НЕ (NOT) — 85 Логический 0 — 19 м Макетная плата — 45 Макрос — 170, 171 Маркер — 62 Маскирование — 84, 87, 90 Метка — 29 МЗР — 14 Микроконтроллер — 11, 19, 20 Модель ЦПУ — 20 Модуль сравнения — 143 Музыкальный автомат — 146 н Написание программы — 24 Непосредственный операнд — 29 О Обратный код — 19, 81 Общий анод — 51 Общий катод — 57 Объявления — 33 ОЗУ — 20, 111, 112 Операнд — 29 Основание 10 — 14 Основание 16 — 14 Основание 2 — 14 Относительный переход — 34 Отрицательное число (двоичное) — 17,19, 94 Ошибка — 25 Ошибка кадрирования — 156 п Палиндром — 115 Память программ — 20, 72, 88 Перенос — 17,92 Подавление дребезга — 75 Подавление помех — 143, 144 Подпрограмма — 72 обработки прерываний — 119 Подтяжка — 34 Полнодуплексная связь — 163 Полубайт — 16 Полудуплексная связь — 163 Порты — 27, 34 Последовательный периферийный интерфейс — 163, 164, 165 Последовательный порт — 159, 161 Предделитель — 167 Предупреждение — 25 Преобразование непрерывное — 132 однократное — 132 Прерывания — 118 аналоговый компаратор — 118 внешнее — 118, 119 переполнение таймера — 118, 126 Проверка — 40, 178 Программирование — 45 Псевдо-объемный звук — 140 Р Разрешение прерываний — 92, 119, 125 Регистр сдвиговый — 755, 164 состояния — 91,92 специальных функций — 767
Предметный указатель -270- Регистры — 25, 777 ввода/вывода — 25, 27, 34, 35, 111 рабочие — 25, 26, 111 счетные — 62 С Сброс — 42, 93 Сдвиг — 67, 88, 90, 97 арифметический — 67 логический — 68 циклический — 68 Семисегментный индикатор — 49, 75, 96 СЗР — 15 Символ — 755 возврата каретки — 175 новой строки — 175 перевод строки — 175 Симплексная связь — 163 Симуляция — 40 Синхронный обмен — 163 Системы счисления — 14, 19 Скорость передачи — 155, 156, 159 Слово — 25, 26 Сложение (двоичное) — 17,91 Случайные числа — 123 Сохранение — 52, 111 Спидометр — 144 Спящий режим — 93 Idle — 94 Power Down — 94 Сравнение — 52, 95 Старт-бит — 155 Статическое ОЗУ — 20 Стек — 72, 111, 114 Стоп-бит — 755 Сторожевой таймер — 91,95 период — 93 Стробирование — 21, 22, 96 Суммирующий усилитель — 130 Счетчик — 55, 57, 58, 62, 64 команд — 71, 120 т Таблица векторов прерываний — 118, 145 истинности — 81 перекодировки — 57 переходов — 774 Таймер/счетчик 1 — 142, 146, 152, 153, 167 Тактовая частота — 32, 157 Тактовый генератор — 32 Тактовый сигнал АЦП — 133, 134 Такты — 32, 71 Тетрада — 16, 90, 91 У Указатель стека — 772 Умножение — 170, 171 Ф Файл листинга — 32, 39 Флаг — 92 знака — 18, 92 коллизий записи — 165 нуля — 53, 92 отрицательного числа — 92 переноса — 67, 70, 92 переполнения дополнительного кода — 92 переполнения таймера — 99 половинного переноса — 92 прерывания — 720 прерывания SPI — 755 прерывания от АЦП — 134 Функция захвата — 143 Функция сравнения — 146, 160 ц ЦАП см. Цифро-аналоговый преобразователь Цифро-аналоговый преобразователь — 136, 137 ч Частотомер — 95 ш Шаблон — 30, 31 Шестнадцатеричная система — 14, 19, 197 Шестнадцатеричный код — 25 ШИМ — 152, 153, 167, 169 э Эмуляция — 41
Мортон Джон Микроконтроллеры AVR Вводный курс Переводчик А. В. Евстифеев Ответственный редактор Т. Е. Брод Художник А. С. Майоров График А. Н. Клочков Корректор Г. Б. Абудеева Подписано в печать 25.05.2006. Формат 60x90/16. Бумага типографская № 2. Гарнитура «NewtonC». Печать офсетная. Объем 17,0 п. л. Усл. печ. л. 17,0. Тираж 2000 экз. Изд. № 80. Заказ №1300. Издательский дом «Додэка-ХХ1» ОКП 95 3000 105318 Москва, а/я 70 Тел./факс: D95) 366-24-29, 366-09-22 E-mail: books@dodeca.ru; red@dodeca.ru Отпечатано с готовых диапозитивов в ОАО «Типография «Новости». 105005 Москва, ул. Ф. Энгельса, 46