Текст
                    Юрий Ревич
AVR
Программирование
микроконтроллеров
oArduino
к ассемблеру
Санкт-Петербург
«БХВ-Петербург»
2020


УДК 004.431.4 ББК 32.973.26-018.1 Р32 Ревич Ю. В. Р32 Программирование микроконтроллеров AVR: от Arduino к ассемблеру. — СПб.: БХВ-Петербург, 2020. — 448 с: ил. — (Электроника) ISBN 978-5-9775-4076-6 Рассмотрено практическое программирование микроконтроллеров AVR, в том числе популярной платформы Arduino. Рассказано, как выйти за рамки ограни- чений Arduino, когда следует применять прямое программирование на ассемблере, а когда использовать языки высокого уровня. Изложены общие принципы устройства микроконтроллеров AVR и их про- граммирования, система команд, программирование таймеров, арифметические операции, память, интерфейсы, режимы энергосбережения и сторожевой таймер, программы реального времени, обмен данными с персональным компьютером. Особое внимание уделено переносу типичных Arduino-проектов на ассемблер. Даны готовые рецепты для программирования большинства основных функций современной микроэлектронной аппаратуры. Для учащихся, инженерно-технических работников и радиолюбителей УДК 004.431.4 ББК 32.973.26-018.1 Группа подготовки издания: Руководитель проекта Евгений Рыбаков Зав. редакцией Екатерина Сависте Компьютерная верстка Ольги Сергиенко Дизайн серии Марины Дамбиевой Оформление обложки Карины Соловьевой Подписано в печать 07 04 20 Формат 70хЮ01/16 Печать офсетная. Усл. печ. л 36,12 Тираж 1000 экз. Заказ № 5615. "БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20. Отпечатано в ОАО «Можайский полиграфический комбинат» 143200, Россия, г Можайск, ул Мира, 93 www oaompk ru, тел (495) 745-84-28, (49638) 20-685 ISBN 978-5-9775-4076-6 © Ревич Ю В , 2020 © Оформление ООО "БХВ-Петербург", ООО "БХВ", 2020
Оглавление Введение. Почему ассемблер? 8 ЧАСТЬ I. ОБЩИЕ ПРИНЦИПЫ УСТРОЙСТВА И ФУНКЦИОНИРОВАНИЯ ATMEL AVR 13 Глава 1. Обзор микроконтроллеров AVR 15 AVR и другие 16 Почему AVR? 18 Краткий обзор возможностей AVR 21 Семейства и модификации AVR 23 Основные принципы маркировки AVR 25 Глава 2. Общее устройство, организация памяти, тактирование, сброс 28 Память программ , 29 Память данных (ОЗУ, SRAM) 31 Энергонезависимая память данных (EEPROM) 33 Способы тактирования 35 Сброс 39 Глава 3. Периферийные устройства и прерывания 43 Порты ввода/вывода 44 Таймеры-счетчики 46 Аналого-цифровой преобразователь 48 Последовательный порт 51 Интерфейс UART (USART) 52 Интерфейс SPI 57 Интерфейс TWI (12С) 61 Универсальный последовательный интерфейс USI 62 Прерывания 62 Порядок выполнения прерываний 64 Разновидности прерываний 65 Об общих принципах использования прерываний 67 Глава 4. Микроконтроллеры AVR на практике 69 Особенности практического использования МК AVR 69 Корпуса МК и их установка на плату 71
Оглавление Необходимое оборудование и приспособления 73 Панельки 73 Макетные платы 75 Адаптер для UART 76 Светодиоды-пробники 79 Мультиметр 80 Осциллограф 81 Генератор 82 Источники питания .;84 Потребление МК AVR 87 Примеры AVR-контроллеров 91 Глава 5. Подготовка к программированию МК AVR 93 Ассемблер без излишних сложностей 94 Редактор ASM Editor 95 Ассемблер Avrasm 97 Обустройство ассемблера 98 Об AVR Studio 100 Способы загрузки программ в контроллер 101 ISP-программаторы 102 Arduino как ISP-программатор 107 Конфигурационные ячейки (fuse-биты) 109 ЧАСТЬ II. ПРОГРАММИРОВАНИЕ МИКРОКОНТРОЛЛЕРОВ AVR НА АССЕМБЛЕРЕ 117 Глава 6. Основы программирования МК AVR 119 Общая структура ассемблерной программы и ее выполнение 120 Инструкции и нотация AVR-ассемблера 121 Числа и выражения 123 Директивы 125 Оформление вызова подпрограмм 129 Обработка прерываний 131 Процедура RESET 136 Использование макросов 138 НЕХ-файлы и их загрузка в контроллер 140 OBootloader 145 Простейшая программа 146 Таймер без прерываний 149 Задержка 150 Программа счетчика 152 Использование прерываний 158 Программа счетчика с использованием прерываний 158 Сравнение ассемблерной программы с программами Arduino и другими языками высокого уровня 161 Глава 7. Система команд AVR 163 Обзор команд 164 Команды передачи управления и регистр SREG 164 Команды проверки-пропуска 170
Оглавление Команды логических операций 174 Команды сдвига и операции с битами 175 Команды арифметических операций 177 Команды пересылки данных 180 Команды управления системой 187 Выполнение на ассемблере типовых процедур 188 О стеке, локальных и глобальных переменных 191 Ассемблерное представление символов и строк 194 Глава 8. Арифметические операции и операции в двоично-десятичном формате 197 Стандартные арифметические операции 199 Умножение многоразрядных чисел 200 Деление многоразрядных чисел 203 Операции с вещественными числами 206 Генератор случайных чисел 208 Операции с числами в двоично-десятичном формате (BCD) 210 Отрицательные и вещественные числа в МК 215 Представление отрицательных чисел 215 Представление вещественных чисел 218 ЧАСТЬ III. ПРАКТИЧЕСКОЕ ПРОГРАММИРОВАНИЕ МИКРОКОНТРОЛЛЕРОВ AVR 221 Глава 9. Программирование таймеров 223 8-и 16-разрядные таймеры 223 Формирование заданного значения частоты 227 Отсчет времени 230 Точная коррекция времени 237 Частотомер и периодомер 238 Частотомер 239 Периодомер 242 Управление динамической индикацией 246 LED-индикаторы и их подключение 246 Программирование динамической индикации 252 Таймеры в режиме ШИМ 254 Расчет режима ШИМ для инвертора 256 Программная реализация ШИМ 259 О схемотехнике инвертора 264 Другие применения ШИМ 267 Глава 10. Использование EEPROM 271 Еще раз о сохранности данных в EEPROM 271 Запись и чтение EEPROM 275 Регулируемый светильник с запоминанием состояния 278 Хранение констант в EEPROM 283 Глава 11. Аналоговый компаратор и АЦП 286 Аналоговые операции: понятие погрешности и построение градуировочных уравнений 286 Среднее значение и градуировочные уравнения 288
Оглавление Аналого-цифровые операции и их погрешности 291 Работа с аналоговым компаратором 294 Устройство компаратора 295 Система контроля батарейки 296 Встроенный АЦП 300 Питание и опорное напряжение » 301 Задание режима работы 303 Простейшее использование АЦП 306 Схема измерений с помощью АЦП 310 Перевод результатов в физические величины 318 Глава 12. Интерфейс SPI 323 Основные операции через SPI 324 Аппаратный вариант 324 Программный вариант 327 О разновидностях энергонезависимой памяти 328 Запись и чтение flash-памяти через SPI 330 Операции с микросхемой памяти 45DB011В 330 Программа обмена с памятью 45DB01 IB no SPI 333 Запись и чтение flash-карт 334 Подключение картММС 335 Подача команд и инициализация ММС 337 Запись и чтение ММС 341 Глава 13. Интерфейс TWI (12С) и его применение 343 Базовый протокол 12С , 344 Программная эмуляция протокола 12С 347 Часы с интерфейсом 12С 349 Особенности записи и чтения внешней памяти с 12С-интерфейсом 358 Дисплей МТ-1 ОТ И 361 Глава 14. Режимы энергосбережения и сторожевой таймер 365 В каком случае нужен режим энергосбережения? 367 Программирование режима энергосбережения 368 Выход по внешнему прерыванию 369 Применение сторожевого таймера 374 Инициализация, запуск и сброс WDT 376 Примеры использования WDT 379 О правильном построении малопотребляющих схем 383 Экономичный термометр на батарейках 384 Глава 15. Программирование UART и обмен данными с персональным компьютером 387 Способы обмена данными с ПК 389 Правила техники безопасности при подключении к ПК 389 Программы для связи ПК с контроллером 391 Дистанционная связь через UART 392 Программирование UART 394 Примеры использования UART в разных режимах 395
Оглавление Вывод и ввод символов через UART 400 Программа установки часов DS13 07 401 Как с помощью UART организовать выход из режима энергосбережения? 401 Глава 16. Некоторые Arduino-задачи на ассемблере 403 Дисплеи 403 4-разрядный цифровой дисплей на основе ТМ1637 404 Часы на дисплее ТМ1637 406 Ультразвуковой дальномер на дисплее ТМ1637 407 Термометр на дисплее ТМ163 7 409 Знакосинтезирующие дисплеи на базе HD44780 и его аналогов 410 Инициализация и вывод символов 415 Пример управления ЖК-дисплеями конфигурации 16x2 418 Дисплей МТ-10S1 фирмы МЭЛТ 419 OLED-дисплеи фирмы Winstar 420 Часы с календарем на OLED-дисплее 421 ИК-приемник 422 Управление серводвигателем 425 Приложение 1. Ликбез 429 Десятичные, двоичные и шестнадцатеричные числа 429 Запись чисел в различных форматах 431 Двоично-десятичный формат BCD 432 Перевод из одной системы счисления в другую 433 Булевы операции 434 Об обозначениях на принципиальных схемах 436 Приложение 2. Основные параметры некоторых микроконтроллеров AtmelAVR 438 Литература 444 Предметный указатель 446
ВВЕДЕНИЕ Почему ассемблер? По статистике на 2018 год 8-разрядные чипы, в том числе семейство AVR, все еще занимают около 12-13% рынка универсальных контроллеров, что совсем немало, учитывая его миллиардные объемы в количественном исчислении. Дешевые, не- прихотливые, несложные в программировании и схемотехнике, 8-разрядные кон- троллеры с большим количеством задач справляются собственными силами, без дорогих дополнительных компонентов. Вопрос в том, как проще всего приспосо- бить эти контроллеры под свои все расширяющиеся нужды? Вы уже познакомились с Arduino, имеете представление о возможностях AVR- контроллеров. Изначально рассчитанная на людей, не имеющих инженерного обра- зования, но склонных мастерить нужные вещи своими руками, платформа Arduino в несколько лет завоевала заслуженную популярность любителей по всему миру, породив целую отрасль индустрии. Платы и среда программирования, распростра- няющиеся под свободной лицензией, породили огромное количество подражаний и ответвлений, развивающих платформу в ту или иную сторону. Одновременно неис- числимое количество фирм по всему миру наладили выпуск самой разнообразной периферии: датчиков, исполнительных устройств, устройств сопряжения с комму- никационными сетями. Наверное, не осталось такой любительской задачи в облас- ти конструирования электронных устройств, которую нельзя было бы решить в рамках Arduino, причем чаще всего несколькими способами. Вероятно, важнейшая особенность экосистемы Arduino, которая и позволила завое- вать ей такую популярность — то, что порог вхождения (т. е. сумма необходимых априорных знаний и умений для начала работы) здесь сведен к достижимому ми- нимуму. Можно не разбираться ни в науке об электричестве, ни в программирова- нии — и тем не менее создавать своими руками вполне работоспособные схемы. Но даже непрофессионалу ясно, что создавая столь удобный инструмент, пришлось чем-то серьезно пожертвовать. Ограничения Arduino начинают чувствоваться сра- зу, как только вы выходите за рамки макета и пытаетесь создать удобный, эконо- мичный и эстетично выглядящий прибор. Закрадывается мысль — а зачем мне в нем столько всего лишнего, которое потребляет ток, занимает кучу места и после закачки отлаженной программы в контроллер уже никак не используется?
Почему ассемблер? Эта книга для тех, кому надоело раз за разом повторять готовые рецепты из Интер- нета, закачивая в громоздкую «этажерку» плат готовые решения. Конечно, вам тут же предлагают перейти на нормальный С и работать напрямую с AVR Studio и дру- гими подобными монстрами. И не подумайте, что я вас от этого пути буду отгова- ривать — смотря, какие задачи вы перед собой ставите. Конечно, на «чистом» С программировать контроллеры удобнее, никто не спорит, и стать профессионалом можно только на этом пути. И переносится такой код при смене модели контролле- ра даже в рамках одного только семейства AVR лучше, и значительную часть опе- раций проводить проще, текст программы получается компактнее. Заметки на полях Многие любители соблазняются средами программирования microPascal для AVR и BASCom, в которых они могут перейти на знакомые еще по школьным урокам инфор- матики языки Pascal или Basic. Вот этого действительно делать не следует — и не по- тому, что код получается гораздо хуже, чем в случае С, а потому что так вы еще дальше уходите от сути происходящего внутри контроллера, ничего не приобретая взамен, кроме сиюминутного удобства. Значительная часть преимуществ языка С кроется в доступности готовых библиотек на все случаи жизни, а в случае BASCom и тем более microPascal подобной развитой экосистемы даже близко не сложилось. Из этих сред вам почти некуда развиваться, а в случае С вы, как минимум, приобретаете базу для дальнейшего роста. Но даже профессионалы вряд ли будут спорить с тем, что имея за плечами только Arduino, с полпинка вы на С ничего хорошего не напишете. Чтобы писать эффек- тивные программы в условиях тотального дефицита ресурсов, надо знать устра- шающее количество различных тонкостей. Знания того, что означают, например, термины static и volatile, недостаточно — надо отчетливо понимать детали меха- низма действия этих и других спецификаторов в различных случаях, и еще много чего сверх того. А вы твердо уверены, что именно этого хотели— углубиться в изучение тонкостей программирования? Все споры о преимуществах того или иного подхода обычно ведутся во вполне виртуальном пространстве абстракций, игнорирующих собственно поставленную задачу. Как выразился один знаток программирования, «одна из сторон доказыва- ет, что дерево плавает, а другая, что кирпич тонет. Естественно, что при такой постановке каждая из сторон уверена в своей правоте и будет вечно ее отстаи- вать». Мы постарается не ввязываться в беспочвенные холивары, а сразу сформу- лируем задачу: мы собираемся создать экономичный, эстетично выглядящий и удобный в применении прибор. Надо ли для этого преодолевать многочисленные нюансы реализации языка С для 8-разрядных контроллеров? Да, если вы хотите посвятить этому всю жизнь. Но и в этом случае профессионалы (см., например, [2]) рекомендуют начинать с ассемблера, потому что только так можно полностью освоить возможности контроллера. Тем, кто готовится програм- мировать контроллеры профессионально, тоже будет небезынтересно узнать, как в 150 байт кода вмещается программка генерации вполне качественного синуса частотой 50 Гц, способная управлять инвертором напряжения киловаттной мощ- ности (см. главу 9).
tO Введение Заметки на полях Тут следует еще упомянуть исторический факт, что вообще-то языки высокого уровня, включая С, создавались не для контроллеров гарвардской архитектуры, к которым от- носятся в том числе и AVR. Для архитектуры фон Неймана, на которую они рассчита- ны, принципы составления программ существенно другие (иная парадигма, как гово- рят программисты). Да, с помощью С можно получить код, почти столь же эффектив- ный, как ассемблерный. Но даже в самом идеальном случае эффективность кода на С будет все-таки только «почти» такая же: живой пример этого — практически все про- граммы этой книги, которые при переводе на С дадут код, заведомо медленнее вы- полняющийся и большего объема. Специальный пример сравнения программ на Arduino, «чистом» С и ассемблере мы продемонстрируем в главе 6. Изучение собственно AVR-ассемблера, в отличие от С, занимает минимум време- ни. Краткое описание от Atmel когда-то занимало ровно 4 странички (сейчас оно существенно раздулось [4], но в основном из-за пояснений в виде примеров кода). Его вариант, переведенный на русский, также дополненный примерами и таблицей основных команд, укладывается в 17 страниц [3]. Не сравнить с многостраничными фолиантами руководств по языку С, правда? А почему так? А потому что в элемен- тарном ассемблере специфических для программирования деталей — раз, два и об- челся. Все остальное — структура контроллера, т. е. чистая электроника. И изучать возможности контроллера, когда все время идет речь об установках тех или иных битов в различных регистрах, в терминах команд ассемблера оказывается намного проще и нагляднее. Мы в этой книге обсудим многие существенные моменты, которые обычно обхо- дятся не только в отношении любительского Arduino, но и часто упускаются из ви- ду профессионалами: как сделать так, чтобы контроллер тратил на операцию ми- нимальное время? Как по максимуму использовать все, что может дать 10-разряд- ный АЦП? Как снизить потребление схемы до оптимального уровня и правильно настроить режим sleep'? И постараемся не забывать, что программирование — это всего лишь такой универсальный способ заставить работать схему по заданному алгоритму, а хорошо работающий прибор — это в первую очередь его схемотех- ника. Подробности При изучении материала этой книги читатель, оценивший компактность и предсказуе- мость ассемблерных программ, но не желающий отказываться от удобств языка С, рано или поздно задаст вопрос: а нельзя ли объединить то и другое в одном проекте? Компилятор AVR-GCC это позволяет делать, как минимум, со стороны проекта на С — вставлять в него ассемблерные фрагменты. Причем вызывать их можно даже двумя способами — см. [9], где описан один из них (а внутри статьи есть ссылка на второй, более распространенный способ). Способы применимы в любой среде программиро- вания, основанной на AVR-GCC, в том числе и в Arduino (правда, лаконичность чисто ассемблерных программ и возможность их составления в любом текстовом редакторе вы при этом теряете). Обратной возможности— использования С-библиотек в ас- семблерном тексте, как это позволяют «большие» ассемблеры для ПК (см. [13]), AVR- ассемблер, к сожалению, не предоставляет. За базовый контроллер мы возьмем традиционный ATmega8 — упрощенную вер- сию ардуиновского ATmega328, которая, однако, умеет почти все то же самое. Две другие базовые модели, программы для которых приведены в этой книге:
Почему ассемблер? 1J_ ATmega8535 (с большим числом выводов) и ATtiny2313 (с меньшим). Интересно, что ATtiny2313, по идее относящийся к младшему семейству, обладает некоторыми функциями более современных моделей. Эти три модели мы будем считать «наши- ми». Почти без изменений программы, которые вы здесь встретите, пригодны и для ATmegal6, который, если не считать большего объема памяти (совершенно для нас не существенного), в общем, отличается от ATmega8 только увеличенным числом выводов. Особенности переноса программ и на более «продвинутые», и на более простые модели AVR мы уточним по ходу дела. В первую очередь мы узнаем, что гонять контроллер на частоте 16 МГц совершен- но необязательно— для огромного большинства задач достаточно куда более скромной частоты с одновременным снижением потребления в разы. Научимся устанавливать разные режимы контроллера, в том числе от встроенного тактового генератора, когда ему вовсе не нужны никакие дополнительные компоненты. Не обойдем мы и ограничения ассемблера — вы сами сможете нащупать порог, когда более высокое качество ассемблерных программ уже перестает окупаться затрата- ми времени на их создание. Схемы у нас будут только принципиальные. Картинки монтажной платы с распо- ложением деталей, которые можно так красиво делать в пакете Frizing, только за- путывают, потому что самое главное на них отсутствует, и никакой реально необ- ходимой информации о схеме эти картинки не дают. Только принципиальная схема с указанием названий, номиналов, типов и полярностей всех компонентов может дать исчерпывающее представление о том, что вы делаете. Но ничто не мешает сделать ее более наглядной — даже ГОСТ не запрещает располагать компоненты в соответствии с реальной разводкой выводов, делая схему как бы частично мон- тажной. Именно в таком стиле исполнены все схемы в этой книге. Некоторые эле- ментарные правила, касающиеся обозначений на принципиальных схемах, приве- дены в приложении 7, а интересующихся подробностями отсылаем к моей кни- ге [10]. Автор предполагает, что читатель худо-бедно знаком с двоичными и шестнадцате- ричными цифрами и основными логическими операциями, умеет читать принципи- альные схемы и знает, как связаны напряжение и ток, и зачем светодиоду нужен резистор. Тем, кто в таких вопросах плавает, адресован краткий «Ликбез» (см. при- ложение 7), а также совет сначала ознакомиться с книжкой [10], где все эти вопро- сы разъясняются детально. Таблицы с основными характеристиками некоторых моделей микроконтроллеров Atmel AVR из числа самых ходовых приведены в при- ложении 2. Обсуждений радиолюбительских технологий в этой книге вы также почти не встре- тите— предполагается, что все примеры и макеты конкретных конструкций выполняются на беспаечной макетной плате, а особенности переноса их в закон- ченные устройства и нюансы подбора компонентов обговариваются только в слу- чаях, когда это критично с точки зрения функциональности. Книга построена по традиционному принципу «от общего к частному»: в части I рассматриваются общие вопросы устройства контроллеров, определяются необхо-
f2 Введение димые приспособления и инструменты, описаны программные средства и их на- стройка. К практическому программированию мь| переходим в части //, которая начинается с простейших примеров ассемблерных программ и краткого обзора сис- темы команд. В части III изучение продолжается примерами использования кон- кретных узлов контроллеров. В архиве, который вы можете скачать по адресу http://revich.lib.ru/AVR/AVR- asm.zip, вы найдете тексты и примеры большинства программ, размещенных в этой книге, распределенные по папкам с названием главы. В тексте глав указываются имена файлов, соответствующих этим программам. Все включенные в книгу схе- мы, программы и отдельные алгоритмы проверены на макетах, и их работоспособ- ность гарантируется при соблюдении указанных для них условий. Связаться с автором можно по электронной почте revich@lib.ru, можно также оставить личное сообщение на сайте habr.com пользователю YRevich.
ЧАСТЬ I Общие принципы устройства и функционирования Atmel AVR Глава 1. Обзор микроконтроллеров AVR Глава 2. Общее устройство, организация памяти, тактирование, сброс Глава 3. Периферийные устройства и прерывания Глава 4. Микроконтроллеры AVR на практике Глава 5. Подготовка к программированию МК AVR
ГЛАВА 1 Обзор микроконтроллеров AVR Говорят, что в 1960-е годы, наблюдая за участниками студенческих демонстраций протеста, Гордон Мур заметил: «Истинные революционеры — это мы». Ученик и сотрудник одного из изобретателей транзистора У. Шокли, в числе прочего счи- тающегося основателем знаменитой Кремниевой долины, в свою очередь основа- тель и лидер компаний, которым суждено было сыграть ведущую роль в развитии микроэлектроники, Мур знал, что говорил. Парадоксальным образом именно изо- бретениям Мура и его сотрудников было суждено стать основой того мира, в кото- ром впоследствии сконцентрировалась деятельность «бунтующей молодежи» 1960-х. Современные хакеры (не компьютерные хулиганы из новостей, а настоящие увле- ченные своим делом компьютерщики) — прямые идеологические наследники сор- боннских студентов и американских демонстрантов, сменившие девиз «Make love not war»1 на «Не пишите лозунги — пишите код». Неслучайно многие известные деятели электронно-компьютерной индустрии, авторы изобретений, сформировав- ших лицо современного мира, — выходцы из среды, близкой той самой «бунтую- щей молодежи». Наша история о микроконтроллерах началась с того, что в 1957 году Гордон Мур совместно с Робертом Нойсом и еще шестью сотрудниками Shockley Semiconductor Labs (Шокли назвал их «предательской восьмеркой») основали компанию Fairchild Semiconductor. Ей мы обязаны не только развитием полупроводникового рынка и внедрением микросхем в инженерную практику, но и тем, что она стала своеобраз- ной кузницей кадров и генератором идей для молодой отрасли. Из изобретений сотрудников Fairchild берет свои истоки большая часть ключевых инноваций, сформировавших лицо современного мира, начиная прямо с самой основы — изо- бретения микросхемы Робертом Нойсом. Из всего урагана событий, источником которых стала Fairchild, для нашего повест- вования важно то, что в числе прочих инноваций сотрудники Fairchild первыми стали продвигать полупроводниковую память. Сейчас, в век CD и DVD, твердо- тельных жестких дисков и flash-карточек, нам трудно представить себе, что в нача- 1 «Занимайтесь любовью, а не войной» — лозунг хиппи 1960-х, протестующих против войны во Вьет- наме.
_/б Часть I. Общие принципы устройства и функционирования AtmelAVR ле 1960-х годов программы для компьютеров хранились в основном на картонных листочках (перфокартах), конструкторы ломали голову над дорогущими модулями ОЗУ на ртутных линиях задержки, осциллографических трубках и ферритовых колечках в полмиллиметра диаметром, где каждый бит «прошивался» вручную. Самое компактное в те годы электронное устройство для хранения данных на маг- нитных дисках под названием RAM АС 305 емкостью 5 Мбайт было размером с промышленный холодильник и сдавалось в аренду за 5 тыс. долларов в месяц. Компактная полупроводниковая память была нужна абсолютно всем — от военных и NASA до изготовителей бытовых приборов. Почуяв, откуда дует ветер, в 1968 году Мур с Нойсом оставили Fairchild и основали Intel, как специализиро- ванную компанию по разработке и производству памяти. Они еще не ведали, что самым популярным детищем Intel станет вовсе не память, а микропроцессор, раз- работка которого первоначально затевалась, как вспомогательный этап в проекти- ровании обычного калькулятора. С набора из четырех небольших микросхем Intel под названием «семейство 4000» началось победное шествие микропроцессоров по всему миру. Они весьма быстро разделились на несколько разновидностей, в основном относящихся к двум глав- ным группам: собственно микропроцессорам (МП) и микроконтроллерам (МК). Первые предназначены для использования в составе вычислительных систем, са- мые распространенные из которых — персональные компьютеры (ПК), поэтому их еще часто называют «процессорами для ПК» (к этой же группе обычно относят также и производительные МП для серверов и некоторые другие). МК отличаются от МП тем, что они в первую очередь предназначены для управления различными системами, поэтому при относительно более слабом вычислительном ядре они включают в себя много дополнительных узлов. То, что для обычного МП предпо- лагается размещать во внешних «чипсетах» или дополнительных модулях (память, порты ввода/вывода, таймеры, контроллеры прерываний, узлы для обработки ана- логовых сигналов и пр.), в МК располагается прямо на кристалле, отчего их часто называют «computer-on-chip» («однокристальный компьютер»). И действительно, в простейшем случае для построения полностью функциони- рующего компьютера достаточно единственной микросхемы МК с подсоединен- ными к ней устройствами ввода/вывода. Современные модели рядовых однокри- стальных МК превышают вычислительные возможности IBM PC AT на 286-м про- цессоре образца второй половины 1980-х. Есть быстроразвивающиеся области, где границу между МП и МК провести трудно — таковы, например, процессоры для мобильных устройств, от обычных телефонов до смартфонов и планшетов, в кото- рых процессорный узел должен обладать развитыми вычислительными функциями и управлять многочисленными внешними компонентами. AVR и другие Собственно история AVR-контроллеров началась с того, что 1962 году в Калифор- нии появилась семья Перлегос, греческих эмигрантов, уроженцев города Триполис. Родители занялись, как и на родине, виноградарством, а сыновья Джордж и Гюст
Глава 1. Обзор микроконтроллеров AVR 17_ Перлегос выбрали модную специальность инженера-электронщика — оба окончи- ли вначале университет Сан-Хозе, а затем Стэнфордский университет. В 1974 году в возрасте 24 лет младший из братьев Джордж Перлегос начал работать в компании Intel, где попал на одно из самых передовых направлений — разработку электриче- ски стираемой памяти для замены «прожигаемой» ОТР ROM. При его участии, а затем и под его непосредственным руководством были созданы две технологии, ставшие точкой роста для всей отрасли по производству flash-памяти — одного из главных столпов современной «цифровой революции». В начале 1980-х Джордж Перлегос увольняется из Intel. С братом Гюстом и еще несколькими сотрудниками он в 1984 году создает вскладчину на личные средства компанию, полное название которой звучит как Advanced Technology MEmory and Logic или сокращенно — Atmel. Сначала продукцией Atmel были микросхемы энергонезависимой памяти всех раз- новидностей: как «однократно программируемых» ОТР EPROM и «перезаписы- ваемых» EEPROM с последовательным и параллельным доступом, так и Flash. В 1985 году Atmel выпустила первую в мире EEPROM по доминирующей ныне КМОП-технологии, а в 1989 году— первую flash-память с питанием от одного на- пряжения +5 В. В конце 1980-х Intel вознамерилась наказать ряд компаний- производителей EPROM, в том числе и Atmel, якобы за нарушение патентов, но в конце концов им удалось договориться об обмене лицензиями. Причем в конеч- ном итоге Atmel перепала лицензия на производство классического микроконтрол- лера 8051, от поддержки которого Intel уже в то время постепенно отходила, сосре- доточившись на процессорах для ПК. Подробности Напомним, что EEPROM отличается от flash-памяти тем, что первая допускает раз- дельный доступ к любой произвольной ячейке, а вторая — лишь к целым блокам. По- этому EEPROM меньше по объему (характерный объем специализированных микро- схем EEPROM — от единиц килобит до единиц мегабит) и дороже, в настоящее время ее используют в основном для хранения данных, в том числе в составе микроконтрол- леров. Flash-память проще и дешевле, и к тому же дает значительный выигрыш в ско- рости при больших объемах информации, особенно при потоковом чтении/записи, ха- рактерном для медиаустройств (вроде цифровых камер или МРЗ-плееров). В составе микроконтроллеров flash-память служит для хранения программ. Некоторые подроб- ности о различных типах памяти и их функционировании приведены также в главе 10. Так Atmel оказалась «втянута» в число производителей микроконтроллеров, в ко- тором очень быстро оказалась на первых позициях, — в 1993 году началось произ- водство первых в отрасли МК АТ89С51 со встроенной flash-памятью программ. Это означало начало переворота во всей инженерной практике, потому что сущест- вовавшие ранее МК обладали либо однократно программируемой ОТР-памятью, либо УФ-стираемой, которая значительно дороже в производстве, и работа с ней приводит к большим потерям времени разработчиков. Число циклов перезаписи для УФ ППЗУ не превышает нескольких десятков, а прямой дневной свет, попав- ший на такой кристалл, может привести к стиранию информации. Поэтому даже мелкосерийные устройства приходилось изготавливать преимущественно с исполь-
j[8 Часть I. Общие принципы устройства и функционирования AtmelAVR зованием ОТР ROM, что значительно рискованнее, — изменить в случае даже ма- лейшей ошибки записанную программу уже было невозможно. Появление flash- памяти изменило весь «ландшафт» в этой области — именно в результате ее вне- дрения стали возможными такие операции, как программное обновление BIOS компьютера или «перепрошивка» управляющих программ для бытовых электрон- ных устройств. В 1995 году два студента Норвежского университета естественных наук и техноло- гии из города Тронхейма, Альф Боген и Вегард Воллен, выдвинули идею 8-разрядного RISC-ядра, которую предложили руководству Atmel. Имена разработ- чиков вошли в название архитектуры AVR: Alf + Vergard + RISC. Идея настолько понравилась, что в 1996 году в Тронхейме был основан исследовательский центр Atmel, и уже в конце того же года выпущен первый опытный микроконтроллер но- вой серии AVR под названием AT90S1200. Во второй половине 1997 года корпора- ция Atmel приступила к серийному производству семейства AVR. Почему AVR? У AVR-контроллеров «с рождения» есть две особенности, которые отличают это семейство от остальных 8-разрядных МК. Во-первых, это наличие конвейера, бла- годаря чему для AVR не существует понятия машинного цикла: большинство команд выполняются за один такт. Для сравнения отметим, что пользующиеся большой популярностью МК семейства PIC выполняют команду за 4 такта, а клас- сические 8051 — вообще за 12 или даже 24 такта. Вторая особенность — наличие 32 оперативных регистров, не совсем равноправ- ных, но позволяющих в ряде случаев вообще не обращаться к оперативной памяти и не использовать в явном виде стек (что в принципе невозможно в том же семей- стве лг51). Более того, в младших моделях Tiny AVR стек вообще недоступен для программиста. Потому структура ассемблерных программ для AVR стала подозри- тельно напоминать программы на языке высокого уровня, где операторы взаимо- действуют не с ячейками памяти и регистрами, а с абстрактными переменными и константами. Программы при этом работают много быстрее и с точно предсказуе- мым временем выполнения отдельных операций. Суммировав мнения из различных источников и опираясь на собственный опыт, автор пришел примерно к такому подразделению областей применения самых рас- пространенных семейств 8-разрядных контроллеров: □ контроллеры классической архитектуры jc51 (первые микросхемы семейства 8051 были выпущены еще в начале 1980-х) уже давно не применяются на прак- тике и лучше всего подходят для общего изучения предмета— по аналогии с языками Pascal или Basic при обучении программированию. Intel-ассемблер замечательно продуман, программы на нем хорошо читаются, число различных команд для jc51 весьма велико (*51, в отличие от многих других, имеет не RISC-, а CISC-архитектуру, для которой характерно наличие большого числа весьма функциональных команд). Авторитетный Di Halt [2], отмечал: «Что касается лично меня, то я обожаю архитектуру 51 за ее чертовски приятный ассемблер,
Глава 1. Обзор микроконтроллеров AVR 19_ на котором просто кайфово писать. Под эту архитектуру уже написаны гига- байты кода, созданы все мыслимые и немыслимые алгоритмы»', П семейство AVR рекомендуется для начинающих электронщиков-практиков — в силу универсальности устройства, легкости загрузки программ, преемственно- сти структуры для различных типов контроллеров, разнообразия типов корпу- сов, простоты схемотехники, практически лишенной каких-либо специфических особенностей, затрудняющих освоение новичками. Отдельно следует отметить наличие отличной базы для начала работы с этими МК в виде платформы Arduino и всего с ней связанного (подробнее о платформе Arduino рассказано чуть далее); □ контроллеры PIC фирмы Microchip не имеют столь удобной системы команд (которых всего около трех десятков), отдельные представители семейства не универсальны, и требуют тщательного выбора «под задачу». Зато у них низкое энергопотребление и быстрый старт. PIC идеально подходят для проектирова- ния несложных устройств, особенно предназначенных для тиражирования. Тра- диционно они используются в «умных» узлах автомобилей, а также в устройст- вах бытовой сигнализации; □ последние годы набирает обороты семейство STM8, впервые выпущенное фир- мой STMicroelectronics в 2008 году. STM8 в целом похожи на AVR и PIC, но от- личаются как от них, так и от своих старших собратьев — STM32, в сторону не- которых удобств: большего диапазона питания, большей скорости выполнения, команд за счет большей глубины конвейера, большей тактовой частоты при меньшем потреблении, мелкими усовершенствованиями периферии ядра и т. п. Но главное то, что наученные горьким опытом своих коллег из других фирм разработчики архитектуры STM8 сосредоточились на максимальной совмести- мости всех контроллеров семейства между собой. AVR в этом смысле далеко не идеальны (хотя, заметим, все-таки получше других семейств), но STM8 пре- взошли всех, заявив совместимость по выводам — одни и те же узлы в разных контроллерах выводятся на одни и те же выводы корпуса, что позволяет менять один контроллер на другой без переделки кода и даже без переделки платы. Свихнувшимся на простейших инструментах адресована ST Visual Develop — фирменная среда разработки, позволяющая в том числе программировать на ас- семблере STM8, куда более простая и в установке, и в освоении, чем монструоз- ная Atmel Studio. Правда, архитектура STM8 намного лучше AVR приспособле- на к языку С, и там использование ассемблера почти теряет смысл. Возможно, единственное препятствие для распространения STM8 в широких кругах люби- телей — то, что STM8 выпускаются исключительно в совместимых друг с дру- гом планарных корпусах с мельчайшим шагом 0,5-0,6 мм и потому трудно под- даются макетированию и пайке «на коленке». Как ни странно, но 8-разрядные контроллеры вопреки предсказаниям десятилетней давности не стали исчезать под натиском 32-разрядных чипов: в конце предыдуще- го десятилетия они составляли более 50% всех продаваемых микропроцессорных изделий, а сейчас их доля сократилась до 12-13%, но вот уже несколько лет остает-
20 Часть I. Общие принципы устройства и функционирования Atmel AVR ся постоянной. Это понятно — зачем нужен высокопроизводительный 32-разряд- ный процессор для управления стиральной машиной? Несмотря на сопоставимую цену 32-разрядных моделей, вследствие более простой схемотехники, а также эко- номии времени на программировании и отладке, популярность 8-разрядных кон- троллеров не падает. Об Arduino Популярность как любительского конструирования электронных приборов вообще, так и конкретно AVR-микроконтроллеров в среде любителей существенно возросла с по- явлением платформы Arduino. Платформа возникла в среде сотрудников Interaction Design Institute (что можно перевести как «Институт конструирования взаимодейст- вий») из итальянского городка Ивреа и получила свое почти толкиеновское название по имени реально существовавшего короля Ардуина, правившего этой местностью в начале прошлого тысячелетия. Arduino выросла из задачи научить студентов не- профильных специальностей создавать электронные устройства, причем быстро и, желательно, без опоры на углубленное изучение электроники, электротехники и про- граммирования. В конце концов группа, руководимая программистом Массимо Банци, создала универ- сальную аппаратную платформу на основе дешевых, удобных и доступных микрокон- троллеров Atmel AVR и решила ее распространять на принципах open source. Такие свободные лицензии, как знаменитая GPL, разработанная применительно к софту, для «железа» напрямую не годится, потому создатели взяли за основу пакет лицензий Creative Commons для творческих продуктов. Лицензия Arduino запрещает использо- вание этой торговой марки для каких бы то ни было сторонних продуктов, кроме рас- ширений основного проекта. Это привело к тому, что от Arduino стали отпочковывать- ся аналогичные проекты, совместимые с ним, но желающие иметь иные названия — например, такие, как Freeduino, Craftduino, Carduino и многие другие. Пик роста возможностей Arduino приходится на конец нулевых — начало десятых го- дов XXI века, потом темпы развития платформы снизились. Разработчики поневоле загнали себя в тупик— ограничив выбор контроллеров двумя-тремя моделями и стандартизировав дизайн плат, они сильно облегчили порог вхождения пользовате- лям-непрофессионалам и производителям совместимого оборудования, но своими руками закрыли себе пути совершенствования. Отсюда и интерес к расширенному ис- пользованию контроллеров AVR, которое в том числе призвана утолить эта книга. Но скорее всего Arduino еще долго будет доминировать в любительском секторе — пока кто-нибудь не придумает что-нибудь столь же простое и удобное, только уже, навер- ное, на 32-разрядной платформе. Примерно то же самое, что и с Arduino, происходит с самими AVR-контроллерами. В руководстве Atmel «отец flash-памяти» и лауреат многих отраслевых наград Джордж Перлегос пребывал до 2006 года, когда оставил свой пост. Через десять лет, в 2016-м, компания была поглощена фирмой Mirochip. За это десятилетие в руководстве не нашлось инициативного человека, способного предложить идею кардинального обновления модельного ряда так, чтобы и преемственность сохра- нить, и обеспечить рост на долгий период (подобно тому, как это сделали в STMicroelectronics, предложив STM8 вместо мало кому интересных STM7). Но это, конечно, не значит, что AVR вымирают — достаточно посмотреть на обшир- ный список моделей на сайте Microchip, напротив которых стоит «In Production», причем не такая уж маленькая часть этого списка входит в категорию «New product». Для AVR еще на долгие годы обеспечена своя ниша, и уж в любительском секторе даже признаков увядания семейства пока не наблюдается.
Глава 1. Обзор микроконтроллеров AVR 21_ Краткий обзор возможностей AVR Atmel AVR представляет собой семейство универсальных 8-разрядных микрокон- троллеров на основе общего ядра с различными встроенными периферийными уст- ройствами. Возможности МК AVR позволяют решить множество типовых задач, возникающих перед разработчиками радиоэлектронной аппаратуры. Особенности микроконтроллеров Atmel AVR: □ Производительность порядка 1 MEPS/МГц, где MIPS (Millions of Instructions Per Second, миллион команд в секунду) — одна из самых старых и во многом формальная характеристика производительности процессоров, т. к. наборы команд для различных процессоров различаются, и, соответственно, одно и то же число инструкций на различных системах даст разную полезную работу. Тем не менее для простых 8-разрядных вычислительных систем, не содержащих ко- манд, оперирующих с большими числами, числами с плавающей точкой и мас- сивами данных, это неплохой показатель для сравнения их производительности. Вычислительное ядро AVR на ряде задач по производительности превосходит 16-разрядный процессор 80286. □ Усовершенствованная RISC-архитектура — концепция RISC (Reduced Instruc- tion Set Computing, вычисления с сокращенным набором команд) предполагает наличие набора команд, состоящего из минимума компактных и быстро выпол- няющихся инструкций. При этом такие более громоздкие операции, как вычис- ления с плавающей точкой или арифметические действия с многоразрядными числами, предполагается реализовать на уровне подпрограмм. Концепция RISC упрощает устройство ядра (в типовом ядре AVR содержится лишь 32 тыс. тран- зисторов, в отличие от десятков миллионов в процессорах для ПК) и ускоряет его работу — типовая инструкция выполняется за один такт, кроме команд вет- вления программы, обращения к памяти и некоторых других, оперирующих с данными большой длины. В AVR имеется простейший двухступенчатый кон- вейер, когда команда выполняется в одном такте с выборкой следующей. В отличие от Intel-архитектур, в «классическом» AVR не было аппаратного ум- ножения/деления, однако в подсемействе Mega появились операции умножения. □ Раздельные шины памяти команд и данных — AVR (как и большинство дру- гих микроконтроллеров) имеет т. н. гарвардскую архитектуру, где области па- мяти программ и данных разделены (в отличие от классической архитектуры фон Неймана в обычных компьютерах, где память общая). Раздельные шины для этих областей памяти значительно ускоряют выполнение программы — данные и команды могут выбираться одновременно. □ 32 регистра общего назначения (РОН) — Atmel была первой компанией, дале- ко отошедшей от классической модели вычислительного ядра, в которой выпол- нение команд предусматривает обмен данными между АЛУ и запоминающими ячейками в общей памяти. Введение РОН в таком количестве (напомним, что в архитектуре л:86 всего четыре таких регистра, а в х51 понятие РОН, как тако- вое, отсутствует) в ряде случаев позволяет вообще отказаться от расположения
22_ Часть I. Общие принципы устройства и функционирования Atmel AVR глобальных и локальных переменных в ОЗУ и от явного использования стека, операции с которым усложняют и загромождают программу. Подробности В ранних архитектурах обращение к памяти занимало много процессорного времени, потому в целях ускорения выполнения арифметических операций их стали произво- дить в специально выделенных регистрах (регистрах общего назначения, РОН), к ко- торым часто добавлялся специальный регистр-аккумулятор. В AVR от единого акку- мулятора отказались в пользу большого количества равноправных РОН, что облегча- ет создание программ непосредственно в инструкциях процессора (на ассемблере), но усложняет и запутывает задачу компилятора с языков высокого уровня. Наконец, в архитектуре STM (и 8-ми и 32-битовых) опять отказались от концепции РОН в пользу единственного аккумулятора — все операции АЛУ производит непосредственно с пе- ременными в оперативной памяти, — но на этот раз они выполняются за один такт. Иными словами, в архитектуре STM вы можете насоздавать сколько угодно аналогов регистров общего назначения — ограничивает только объем памяти. Платой за это в STM8 служит, во-первых, весьма запутанная система распределения памяти, за чем приходится внимательно следить (помните первую компьютерную аксиому: «Заклады- вая что-то в память компьютера, помните, куда вы это положили»?), во-вторых, неко- торая неопределенность с временем выполнения команд перехода из-за наличия трехуровневого конвейера. Зато и по части совместимости с языком С — никаких про- блем. □ Большое количество разнообразных команд (инструкций), номенклатура кото- рых (примерно от 90 до 130, в зависимости от модели контроллера) для AVR больше, чем в других RISC-семействах. Противоречия с концепцией RISC тут нет, поскольку значительная часть этих инструкций — псевдонимы, и введены они исключительно для удобства программирования. Так что изучать все инст- рукции сразу не потребуется. □ Flash-память программ (10 000 циклов стирание/запись)— с возможностью внутрисистемного перепрограммирования, т. е. загрузки программ прямо в го- товой схеме (In System Programming, ISP). ISP не следует путать с программиро- ванием через последовательный (Serial) порт с помощью отдельной программы- загрузчика (Bootloader), как это делается в Arduino (подобно об этом рассказано в главах 5 и 6). □ Отдельная область энергонезависимой памяти (EEPROM, 100 000 циклов стирание/запись) — для хранения данных с возможностью записи программным путем или внешней загрузки через ISP, подобно программам (см. главу 10). П Встроенные устройства для обработки аналоговых сигналов: аналоговый компаратор и многоканальный 10-разрядный АЦП (см. главу 77). □ Сторожевой таймер, позволяющий осуществлять автоматическую перезагрузку контроллера через определенные промежутки времени (например, для выхода из «спящего» режима — см. главу 14). □ Последовательные интерфейсы SPI, TWI (12С) и UART (USART), позволяю- щие осуществлять обмен данными с большинством стандартных датчиков и других внешних устройств аппаратными средствами (см. главы 12,13 и 75).
Глава 1. Обзор микроконтроллеров AVR 23^ □ Таймеры-счетчики с предустановкой и возможностью выбора источника счет- ных импульсов: как правило, один-два 8-разрядных и как минимум один 16-разрядный, в том числе могущие работать в режиме PWM (ШИМ) — много- канальной 8-, 9-, 10-, 16-битовой широтно-импульсной модуляции (см. главу 9). □ Возможность работы при тактовой частоте от 0 Гц до 16-20 МГц. □ Диапазон напряжений питания от 2,7 до 5,5 В (в некоторых случаях от 1,8 или даже 0,7 В). □ Многочисленные режимы энергосбережения, отличающиеся числом узлов, остающихся подключенными. Выход из «спящих» режимов осуществляется по сторожевому таймеру или по внешним прерываниям (см. главу 14). П Встроенный монитор питания — детектор падения напряжения (Brown-out Detector). Здесь упомянуты далеко не все особенности, характерные для различных моделей AVR. С некоторыми другими мы познакомимся в дальнейшем, а также на практике рассмотрим упомянутые подробнее. Но сначала дадим общую характеристику раз- личных семейств AVR с точки зрения их преимущественного назначения. Семейства и модификации AVR В 2002 году фирма Atmel начала выпуск новых подсемейств 8-разрядных МК на базе AVR-ядра. С тех пор МК этого семейства стали делиться на три основные группы (подсемейства): Classic, Tiny и Mega. В 2008 году к ним добавилось семей- ство Xmega, которое популярности не снискало, и потому в этой книге мы его не рассматриваем. МК семейства Classic (AT90Sjarar) уже давно не выпускаются — дольше всего в производстве «задержалась» очень удачная (простая, компактная и быстродействующая модель) AT90S2313, но и она была еще в 2005 году заменена HaATtiny2313. Все «классические» AVR с первыми цифрами 2, 4 и 8 в наименовании модели (что означает количество килобайт памяти программ) первоначально имели полные аналоги в семействах Tiny и Mega, и большая часть таких совместимых моделей выпускается до сих пор. Для Mega-аналогов при программировании возможна ус- тановка специального бита совместимости, который позволяет без каких-либо из- менений использовать программы, созданные для семейства Classic. Поэтому, если вы найдете на интернет-ресурсах примеры программирования AVR семейства Classic, то их можно будет задействовать в современных моделях без каких-либо доработок. Тем не менее в книге все примеры адаптированы для современных мо- делей Tiny и Mega. Микросхемы Tiny в основном имеют Flash-ПЗУ программ объемом 1-8 кбайт (в некоторых современных моделях расширенное до 16 кбайт) и размещаются в корпусах с 6-32 выводами, т. е. они в целом предназначены для более простых и дешевых устройств. Это не значит, что их возможности во всех случаях более ог-
24 Часть /. Общие принципы устройства и функционирования AtmelAVR раниченны, чем у семейства Mega. Так, например, ATtiny26 содержит таймер с вы- сокоскоростным ШИМ-режимом, а также 11-канальный АЦП с возможностью ра- боты в дифференциальном режиме с регулируемым входным усилителем и встро- енным источником опорного напряжения, что характерно для семейства Mega. Микросхема ATtiny2313, как уже говорилось, представляет собой улучшенную вер- сию одного из наиболее универсальных и удобных «классических» AVR AT90S2313, дополненную возможностями новых семейств. Старшие модели семейства Tiny в настоящий момент по некоторым параметрам даже обходят младшие Mega — на- пример, у ATtiny2313 есть расширенное внешнее прерывание PCINT на почти всех цифровых выводах портов (что дает возможность без излишних сложностей выво- дить его из режима энергосбережения), а у ATmega8/16 такая возможность отсут- ствует. Подсемейство Mega оснащено Flash-ПЗУ программ объемом 4-256 кбайт и имеет корпуса с 28-100 выводами. В целом МК этой группы более «навороченные», чем Tiny, имеют более разветвленную систему встроенных устройств с более развитой функциональностью. Таблицы с основными характеристиками некоторых моделей Tiny и Mega из числа самых ходовых приведены в приложении 2. Там же даны некоторые общие техни- ческие характеристики семейства AVR. Таблицы ориентированы на основные нуж- ды радиолюбителей, в основном не выходящие за рамки сведений из последующих глав этой книги, и представляют собой небольшую выборку из фирменной пара- метрической таблицы 8-разрядных AVR, которую можно найти на сайте www.microchip.com (не путать с таблицей Quick Reference Guide, доступной прак- тически с главной страницы сайта). Надо отметить, что пользоваться этой таблицей не слишком удобно — там все модели помещены внавал по непонятному принципу сортировки, а некоторые важные параметры отсутствуют. Поэтому, помимо при- ложения 2, рекомендую еще две более полные (хотя и частично устаревшие) вы- держки из фирменной таблицы на русском языке отдельно для Mega и Tiny [5], где ориентироваться гораздо проще. Более подробные сведения можно почерпнуть из фирменной технической докумен- тации, которая доступна на сайте www.microchip.com для всех без исключения мо- делей, включая снятые с производства. Документация размещается в англоязычных файлах в формате PDF, традиционно называемых Datasheets (мы их будем по- свойски именовать «даташитами»), и только там имеется полная официальная информация по каждому контроллеру. Нельзя сказать, что в «даташитах» вовсе не бывает ошибок и недоработок (например, таковые найдены автором в свежей ре- дакции файла по контроллеру ATmega8a, выпущенной уже под лейблом Microchip, а не Atmel), и поэтому в конце каждого такого файла ведется учет «ревизий» пре- дыдущих версий. Но в целом информации из этих документов можно и нужно доверять, и всегда следует сверяться именно с ними в случаях, когда сведения из какого-то постороннего источника вызывают сомнения. Наиболее полный и скрупулезно выполненный перевод официальных «даташитов» для ряда моделей AVR представляют книги А. В. Евстифеева [6,7]. Книги эти не-
Глава 1. Обзор микроконтроллеров AVR 25_ сколько устарели с точки зрения представленного модельного ряда, но все нужные нам модели в них охвачены, и их очень полезно иметь под рукой с другой точки зрения — в этих книгах приведен грамотный и в литературном, и в техническом плане перевод на русский язык не всегда внятно написанной фирменной докумен- тации, американский технический язык которой может быть весьма далек от анг- лийского литературного, который мы все изучали в школе. Обратите внимание: первые издания этих книг (под маркой издательства «Додэка») решительно отстали от современности по части ассортимента представленных там контроллеров. И если они вдруг у вас имеются, то из них могут пригодиться только переведенные на рус- ский подробные описания команд из официального руководства [8]. Такие же опи- сания команд на русском есть и в более современных изданиях [6,7]. Кроме Datasheets, официальная документация представлена в виде Application Notes, что можно перевести как «замечания к применению». «Аппноты» нумеру- ются подряд по мере их выхода, без какого-либо отношения к содержанию, и найти в них что-то можно только полнотекстовым поиском. За двадцать с лишним лет существования AVR «аппнот» накопилось на немаленькое собрание сочинений, и значительная часть их содержит описания каких-то не очень интересных частных случаев. Тем не менее в них можно найти и конкретные примеры программирова- ния часто встречающихся задач, и описания построения рекомендуемых алгорит- мов, и тонкости различий между родственными моделями контроллеров, и еще много разной полезной информации, так что мы будем периодически на них ссы- латься. Основные принципы маркировки AVR Все модели каждого семейства могут иметь несколько модификаций. Если не счи- тать некоторых младших моделей с объемом памяти менее 2 кбайт в серии Tiny (Tiny4, 5 и 9), то первые одна или две цифры в наименовании модели сразу после обозначения семейства (ATtiny или ATmega) означают объем flash-памяти про- грамм в килобайтах, так: ATmega8 — это контроллер с памятью программ 8 кбайт, a ATmega 16 — контроллер с памятью программ 16 кбайт. Если имеются более со- временные модификации этих контроллеров, то они обозначаются прибавлением еще одной или нескольких цифр — например, ATmega88 есть расширенная версия ATmega8 (учтите, что это фактически другая ветвь AVR, и без модификации ас- семблерные программы, написанные для Mega8, применять к Mega88 не получит- ся). ATmega88, в свою очередь, имеет ряд вариантов с разным объемом памяти программ: ATmega48 (4 кбайта), ATmegal68 (16 кбайт), в том числе и наш люби- мый ардуиновский ATmega328 (32 килобайта). Буква «L» в обозначении старых моделей говорила о расширенном диапазоне пи- тания 2,7-5,5 В при сниженной допустимой тактовой частоте (8 МГц). Отсутствие такой буквы означало диапазон питания 4,5-5,5 В при повышении допустимой так- товой частоты (16 МГц). В настоящее время появились и другие буквы: например, буква «А» говорит о переходе на новую технологию с уменьшенными технологи-
26^ Часть I. Общие принципы устройства и функционирования AtmelAVR ческими нормами (60 нм вместо 90), и такой кристалл перекрывает возможности обеих старых версий («L» и не-«Ь») при еще более уменьшенном потреблении. Буква «V» означает версии контроллеров, работающих при расширенном напряже- нии питания в пределах 1,8-5,5 вольта, буква «U»— сверхнизкое допустимое питание от 0,7 вольта, буква «Р» — понижение потребления в «спящем» режиме (не путать с «Р» в обозначении варианта исполнения, о чем см. далее). После этих букв может идти еще одна буква позднейшей модификации — например, недавно вышедшая версия ATmega328PB сильно отличается от оригинальных ATmega328 или ATmega328P. Поэтому при выборе конкретного типа микросхемы нужно быть внимательным и проверять, что означают те или иные буквы в полном наименовании. Для примера: поскольку L-версии одновременно также и менее быстродействующие, то у боль- шинства из них максимальная тактовая частота ограничена значением 8 МГц, а для «обычных» или А-версий максимальная частота составляет 16 или 20 МГц. Хотя, как правило, при разгоне L-микросхем с напряжением питания 5 В до частот 10- 12 МГц неприятностей ожидать не следует (аналогично версии без буквы «L» вполне могут работать при напряжении питания около 3 В, разумеется, не на экс- тремальных значениях частот), тем не менее при проектировании высоконадежных устройств следует учитывать это требование. После основного обозначения модели через дефис идет обозначение варианта ис- полнения. Первые одна или две цифры здесь обозначают максимальную рабочую частоту в мегагерцах, последняя буква— условное обозначение температурного диапазона (чаще всего у нас будет встречаться буквы «U» или «I», означающие ин- дустриальный температурный диапазон от -40°С до +85°С). А вот буква посереди- не, сразу после частоты, нас будет очень интересовать, потому что она означает тип корпуса (подробнее об этом рассказано в главе 4). Подчеркнем, что с точки зрения внутренней начинки, а следовательно, и програм- мирования, никакой разницы между AVR-контроллерами с разными буквенными индексами нет (упомянутый новый ATmega328PB представляет собой неприятное исключение, так что проверять все-таки надо). Основные различия относятся к по- треблению, максимальной рабочей частоте, а также отчасти к схемотехнике — микросхемы в четырехсторонних планарных корпусах TQFP имеют на четыре вывода больше, чем в двухрядных DIP. Внимательное сравнение разводки выводов в разных корпусах, однако, показывает, что никакой особой дополнительной функ- циональности лишние выводы не несут— дублируются выводы питания Vcc и GND, а также появляется пара дополнительных каналов АЦП. Интересно, что эти дополнительные каналы (ADC6 и ADC7) имеются на некоторых платах Arduino, использующих контроллер в планарном корпусе: например, они выведены на от- дельные штырьки на плате Mini, а вот на Arduino Nano отсутствуют — их место занял ISP-разъем. Кроме этих двух семейств, на базе AVR-ядра выпускаются «навороченные» AVR семейства Xmega, специализированные микросхемы с промышленным интерфей-
Глава 1. Обзор микроконтроллеров AVR сом CAN для управления ЖК-дисплеями, с беспроводным интерфейсом IEEE 802.15.4 (ZigBee) для предприятий торговли, высокотемпературные для использо- вания в автомобильной промышленности (версии Automotive). Из этого обширного перечня нас могут заинтересовать микросхемы с буквой «U» после обозначения типа — это микросхемы со встроенным USB-интерфейсом. Именно на основе тако- го контроллера (ATmega32U4) спроектирован Arduino Leonardo, отчего он при под- ключении к компьютеру может служить заменой мыши или клавиатуры. На основе подобного контроллера ATmegal6U2 также сделан встроенный адаптер USB-UART в плате Arduino Uno современной версии Rev.3.
ГЛАВА 2 Общее устройство, организация памяти, тактирование, сброс Изучение Arduino традиционно начинается с краткого ознакомления с функцио- нальностью внешних выводов и возможностями среды программирования. После этого вам предлагают написать традиционную программку мигания светодиодом, указывают на веб-ресурсы по решению типовых задач, а также на справочник по языку, и считается, что первый урок вы усвоили, — можете приступать к работе. Эта подкупающая простота оборачивается тем, что вы можете многие годы про- граммировать контроллеры Arduino и так и не узнать, как на деле реализуются функции delay о или minis о, каковы вообще возможности таймеров, для чего их еще можно использовать, и как это делать правильно, не мешая другим функциям. Поэтому мы здесь такого верхоглядства не допустим и выберем более консерва- тивный подход — начнем все-таки с общего обзора структуры AVR-контроллеров, а в следующей главе рассмотрим кратко те их возможности, которые в Arduino обычно вовсе выносятся за скобки. Практические примеры вы начнете осваивать, начиная с главы 6, так что пока наберитесь терпения — без этих предварительных сведений вообще не стоило бы затевать всю историю с ассемблером. Заметки на полях Ручаюсь, что после прочтения некоторых разделов этой главы у вас в голове образу- ется полная каша — все нюансы различий в разных моделях контроллеров запомнить нереально (хотя бы потому, что моделей 8-разрядных AVR существует около 200, и все они хоть в чем-то да различаются). Не стоит, однако, впадать в уныние — здесь вам представлена общая картина, так сказать, набросок панорамы с высоты птичьего полета. В дальнейшем (см. главу 4) мы ограничимся несколькими моделями контрол- леров, для которых детально будет расписано, что и где нужно устанавливать в тех или иных условиях эксплуатации. А руководствуясь общими сведениями из этой гла- вы, вы уже будете представлять, на что обращать внимание при необходимости ис- пользовать другие модели контроллеров. Общая структура внутреннего устройства МК AVR приведена на рис. 2.1. Здесь показаны все основные компоненты AVR (за исключением модуля JTAG, который имеется не во всех моделях, и мы его касаться не будем). В отдельных моделях
Глава 2. Общее устройство, организация памяти, тактирование, сброс 29 обоих семейств некоторые составляющие могут отсутствовать или различаться по характеристикам, неизменным остается только общее 8-разрядное процессорное ядро (GPU, General Processing Unit). Рис. 2.1. Общая структурная схема микроконтроллеров AVR Кратко опишем наиболее важные компоненты, большинство из которых мы под- робно будем изучать в дальнейшем. И начнем с памяти. В структуре AVR имеются три разновидности памяти: flash- память программ, ОЗУ (SRAM) для временного хранения данных и энергонезави- симая память (EEPROM) для долговременного хранения констант и данных. Рас- смотрим их по отдельности. Память программ Объем встроенной flash-памяти программ в 8-разрядных AVR-контроллерах со- ставляет от 0,5 кбайта у ATtiny4 до 256 кбайт у ATmega2560. Как мы уже отмеча- ли, первое число в наименовании модели (в серии Tiny — с оговорками для неко- торых младших моделей с объемом памяти менее 2 кбайт) соответствует величине этой памяти из ряда: 1, 2, 4, 8, 16, 32, 64, 128 и 256 кбайт. Память программ, как и любая другая flash-память, имеет страничную организацию (размер страницы,
30 Часть I. Общие принципы устройства и функционирования AtmelAVR в зависимости от модели, составляет от 64 до 256 байтов). Страница может про- граммироваться только целиком. Число циклов перепрограммирования достигает 10 тысяч. С точки зрения программиста память программ можно считать построенной из от- дельных ячеек — слов по 2 байта каждое. Устройство памяти программ (и только этой памяти!) по двухбайтовым словам— очень важный момент, который нужно твердо усвоить. Такая ее организация обусловлена тем, что большая часть команд в AVR имеет длину ровно 2 байта. Исключение составляют команды jmp, call и некоторые другие (например, lds), которые оперируют с 16-разрядными и более длинными адресами, — длина этих команд равна четырем байтам, и они встреча- ются лишь в моделях с памятью программ объемом свыше 8 кбайт (подробнее об этом рассказано в главе б). Во всех остальных случаях счетчик команд сдвигается при выполнении очередной команды на 2 байта (одно слово), поэтому необходи- мую емкость памяти легко подсчитать, зная число используемых ассемблерных команд (вот еще один плюс ассемблера в сравнении с языками высокого уровня). Абсолютные адреса в памяти программ (указываемые, например, в таблицах векто- ров прерываний в техническом описании МК) также отсчитываются в словах (а не в байтах!), что нужно учитывать при прямом обращении к памяти программ. Заметки на полях Приведем пример интересного случая адресации, который представляет команда для чтения констант из памяти LPM (а также ELPM в МК с памятью программ 128 кбайт и более). Эта команда подразумевает чтение по байтовому адресу, указанному в двух старших РОН (образующих т. н. регистр z, см. об этом далее). Однако, чтобы не на- рушать «чистоту» концепции организации памяти программ по словам, разработчики решили этот вопрос следующим образом: в описании команды указано, что старшие 15 разрядов регистра z адресуют слово в памяти, а младший разряд выбирает млад- ший или старший байт этого слова (при равенстве разряда 0 или 1 соответственно). Легко, однако, заметить, что байтовая и пословная организации памяти при таком подходе полностью эквивалентны. Последний адрес существующего объема памяти программ для конкретной модели обозначается константой flashend. По умолчанию все контроллеры AVR всегда начинают выполнение программы с адреса $оооо]. Если в программе нет прерыва- ний, то с этого адреса может начинаться прикладная программа. В противном слу- чае по этому адресу располагается т. н. таблица векторов прерываний, подробнее о которой мы будем говорить в главах 3 и 6. Здесь укажем лишь, что по умолчанию первым в этой таблице (по тому же адресу $оооо) почти всегда размещается вектор сброса reset, который указывает на процедуру, выполняющуюся при сбросе МК (в том числе и при включении питания). 1 В ассемблере AVR можно обозначать шестнадцатеричные числа в «паскалевском» стиле, предваряя их знаком $, при этом стиль языка С (0x00) тоже действителен, а вот «интеловский» способ (00h) не работает. В тексте книги мы будем пользоваться в основном «паскалевским» способом из-за его крат- кости. Подробнее об обозначениях чисел различных систем счисления в AVR-ассемблере рассказано в приложении 1.
Глава 2. Общее устройство, организация памяти, тактирование, сброс 31_ В последних адресах памяти программ контроллеров семейства Mega может распо- лагаться т. н. загрузчик (Bootloader) — специальная программа, которая управляет загрузкой и выгрузкой прикладных программ из основного объема памяти. В этом случае положение вектора сброса и всей таблицы векторов прерываний (т. е. фак- тически начального адреса, с которого начинается выполнение программы) может быть изменено установкой специальных конфигурационных ячеек (см. главу 5). Память данных (ОЗУ, SRAM) В отличие от памяти программ, адресное пространство памяти данных адресуется побайтно (а не пословно). Адресация полностью линейная, без какого-либо деления на страницы, сегменты или банки, как это принято в некоторых других системах. Надо отметить, что в составе семейства Tiny осталась одна модель без памяти дан- ных вообще (ATtiny28L), остальные младшие МК семейства Tiny имеют SRAM объемом от 32 байт. В других моделях объем встроенной SRAM колеблется от 128 байт в представителях семейства Tiny (например, у ATtiny2313) до 4-8 кбайт у старших моделей Mega. Как видите, ОЗУ контроллера — совсем не то, что ОЗУ компьютерных систем, где его объем измеряется гигабайтами (а если привосокупить к нему объем жестких дисков, которые формально находятся в том же пространстве памяти, то в совре- менных системах уже и терабайтами). Причем львиную часть памяти компьютеров занимают не программы, а именно данные: тексты, картинки, видео. В 8-битных контроллерах, которые для обработки таких объемных данных не предназначены, программы обычно занимают куда больший объем, чем данные. Адресное пространство статической памяти данных (SRAM) условно делится на несколько областей (рис. 2.2). Темной заливкой выделена часть, относящаяся к соб- ственно встроенной SRAM, до нее по порядку адресов расположено адресное про- странство регистров: первые 32 байта занимают регистры общего назначения (РОН), еще 64 — регистры ввода/вывода (РВВ). На рис. 2.2 показан максимальный объем SRAM, равный 8 килобайт (последний адрес $2iff— у контроллера ATmega2560, например), в большинстве моделей она меньше, но принцип остается тем же. Заметки на полях В архитектуре МК AVR понятие «ввода/вывода» употребляется в двух смыслах: во- первых, имеются «порты ввода/вывода» (I/O ports), которые мы рассмотрим далее. Во-вторых, «регистрами ввода/вывода» (РВВ, I/O registers) в структуре AVR называют- ся регистры, которые обеспечивают доступ к дополнительным компонентам, внешним по отношению к GPU, за исключением ОЗУ (в том числе и к портам ввода/вывода). Та- кое подразделение приближает структуру МК AVR к привычной конфигурации персо- нального компьютера, где доступ к любым внешним по отношению к центральному процессору компонентам, кроме памяти, осуществляется через порты ввода/вывода. У старших моделей Mega все регистры устройств, внешних по отношению к ядру, в 64 адреса не умещаются, поэтому, например, уже у ATmega88 для дополнитель- ных РВВ выделяется отдельное адресное пространство (от $60 до максимально
32 Часть I. Общие принципы устройства и функционирования AtmelAVR возможного в байтовой адресации значения $ff— итого таких дополнительных регистров может быть всего 160). Разработчики ядра в свое время не предусмотре- ли возможности этого расширения, вследствие чего такая модернизация влечет за собой изменение в способах адресации этой расширенной части регистров. Отме- тим, что в этой книге — чтобы не усложнять программы — мы намеренно не будем использовать моделей контроллеров с расширенным количеством регистров, хотя о способах их адресации, разумеется, расскажем (см. главу 7). 32 регистра общего назначения (R0-R31) 64 регистра ввода/вывода 0 ДОПОННИТйЛЬЙЫХ Внутреннее стати чес jioe ОЗУ (SRAM) Внешняя память данных $0000 $001F $0020 $005F $0060 $OOFF $0100 Рис. 2.2. Адресное пространство статической памяти данных (SRAM) микроконтроллеров AVR $025F-$21FF $0260-$2200 $FFFF Для некоторых моделей Mega (ATmega8515, ATmega64, ATmegal61, ATmegal28, ATmega2560 и др.) предусмотрена возможность подключения внешней памяти SRAM объемом до 64 кбайт (конечный адрес $ffff) с параллельным интерфейсом, так что она становится продолжением внутренней памяти. В настоящее время го- раздо удобнее применять для хранения данных внешнюю память с последователь- ным интерфейсом (аналогично тому, как в компьютерных системах хранят данные на внешних дисках— см. главу 12), потому вопросы о подключении внешней параллельной памяти мы обойдем. Отметим, что адреса РОН и РВВ не отнимают пространство у ОЗУ данных — так, если в конкретной модели МК имеется 512 байт SRAM, а пространство регистров
Глава 2. Общее устройство, организация памяти, тактирование, сброс 33_ занимает первые 96 байт (до адреса $бо), то адреса SRAM займут адресное про- странство от $0060 до $025F (т. е. от 96-й до 607-й ячейки включительно, что и со- ставит ровно 512 байт). Конец встроенной памяти данных обозначается константой ramend, которая для каждой модели контроллера имеет свое значение. Не очень зна- чимое исключение представляет адресация подключаемой внешней памяти упомя- нутых ранее моделей Mega — т. к. ее максимальный адрес ограничен значением $ffff (65535), то пространство, занимаемое регистрами, вычитается из этой вели- чины. Операции чтения/записи в память одинаково работают с любыми адресами из дос- тупного пространства, и с этим связано несколько нюансов при употреблении ассемблерных команд. При работе с SRAM нужно быть внимательным — вместо записи в память вы легко можете «попасть» в какой-нибудь регистр. Например, команда загрузки значения регистра г1б в регистр г о (mov ro,ri«6) равносильна записи в SRAM по нулевому адресу (sts $oooo,ri6), т. к. адрес в памяти для РОН совпадает с его номером. Для того чтобы записать что-то в первую ячейку именно SRAM, необходимо обратиться по адресу $60 (а в контроллерах с дополнительными регистрами— по адресу $юо). Чтобы не заниматься вычислениями для каждого контроллера, первый адрес памяти SRAM определяется константой sramstart. В то же время для непосредственной записи в РВВ по его адресу в памяти к номеру регистра следует прибавить $20— так, регистр флагов sreg, который для многих моделей располагается в конце таблицы РВВ по адресу $3F, в памяти имеет адрес $5F. Устанавливать РВВ прямой адресацией памяти необходимо лишь при наличии этих самых дополнительных регистров, а в остальном пользоваться ей неудобно — такая запись всегда отнимает 2 такта вместо одного, характерного для большинства других команд (хотя иногда это позволяет обойти ограничения на манипуляции с некоторыми РОН и РВВ). Именно поэтому мы постараемся здесь не пользоваться контроллерами с расширенным количеством регистров — увлеченному ассемб- лерщику, возможно, все эти нюансы греют душу, а нормального человека они только запутывают. В наших программах мы будем обращаться к РОН и РВВ с помощью специально предназначенных для этого команд, где абсолютные адреса скрыты за мнемониче- скими обозначениями регистров. Однако забывать о наличии всех этих нюансов не следует — так, при переносе готовой программы, работающей с SRAM, на старшие модели контроллеров нужно быть внимательным из-за того, что в них младшие адреса SRAM могут перекрываться дополнительными РВВ. Энергонезависимая память данных (EEPROM) Все модели МК AVR (кроме самых младших представителей Tiny) имеют встроен- ную EEPROM для хранения констант и данных при отключении питания. В разных моделях объем ее варьируется от 64 байт (ATtinyH) до 4 кбайт (старшие модели Mega). Конец EEPROM обозначается константой eepromend, причем, если этой па- мяти нет вовсе, константа будет равна нулю. Число циклов перепрограммирования EEPROM может достигать 100 тысяч.
34 Часть I. Общие принципы устройства и функционирования AtmelAVR EEPROM отличается от Flash возможностью выборочного программирования по- байтно (вообще-то даже побитно, но здесь этот способ недоступен пользовате- лю, — подробно об этом рассказано в главе 12). Для единообразия технологическо- го процесса EEPROM в AVR-контроллерах просто эмулируется за счет части flash- памяти, отсюда некоторые ее особенности в сравнении с «обычной» EEPROM (на- пример, то, что запись в EEPROM не может выполняться одновременно с записью во flash-память). Но на практике, как при загрузке по последовательному каналу (т. е. через SPI-интерфейс программирования), так и при записи или чтении EEPROM из программы, эта особенность не имеет значения, и доступ осуществля- ется побайтно. Чтение из EEPROM формально осуществляется в течение одного машинного цикла (правда, на практике оно растягивается на 4 цикла, во время которых CPU просто останавливается, но программисту следить за этим специально не требуется). А вот запись в EEPROM протекает значительно медленнее и к тому же с разной ско- ростью у разных моделей— цикл стирания/записи может занимать от ~3,4 до -8,5 мс (в среднем меньшая величина у Tiny и более новых Mega, и большая — у старых Mega, но если эта величина критична, то необходимо ее уточнять для каждой конкретной модели). К тому же процесс записи регулируется встроенным RC-генератором, частота которого нестабильна (при более низком напряжении пи- тания можно ожидать, что время записи будет больше). За такое время при обыч- ных тактовых частотах МК успевает выполнить несколько тысяч команд, так что программирование процедуры записи требует аккуратности— например, нужно следить, чтобы в момент записи не «вклинилось» прерывание (подробнее об этом рассказано в главах 6 и 8). Главная же сложность при работе с EEPROM — возможность повреждения ее со- держимого при недостаточно быстром снижении напряжения питания в момент выключения. Обусловлено это тем, что при уменьшении напряжения питания до некоторого порога (ниже порога стабильной работы, но недостаточного для полно- го выключения) из-за колебаний напряжения МК начинает выполнять произволь- ные команды, в том числе может осуществить процедуру записи в EEPROM. Если учесть, что типовая команда МК AVR выполняется за десятые доли микросекунды, то ясно, что никакой реальный источник питания не может обеспечить снижение напряжения до нуля за нужное время. В экспериментах автора с семейством Classic при питании от обычного стабилизатора типа LM7805 с рекомендованными значе- ниями емкости конденсаторов на входе и на выходе содержимое EEPROM неиз- бежно портилось примерно в половине случаев. В современных семействах Mega и Tiny вероятность порчи куда ниже, в основном из-за наличия встроенного монито- ра питания (Brown-out Detector, BOD — см. о нем далее в этой главе), но тоже не равна нулю, отчего в критичных случаях приходится принимать дополнительные меры (подробнее об этом рассказано далее — в разд. «Сброс» этой главы, а также в главе 10). Отметим, что, хотя в документации это не отражено, вероятность порчи содержи- мого, видимо, резко снижается, если в программе отсутствуют процедуры записи в EEPROM, а константы записывают в нее только при программировании МК.
Глава 2. Общее устройство, организация памяти, тактирование, сброс 35 Большая сохранность данных в таких случаях подтверждается и эмпирическими наблюдениями, и тем, что разрешение записи в EEPROM — процедура двухсту- пенчатая, и случайное возникновение такой последовательности команд практиче- ски исключено. Способы тактирования За тактирование в основном отвечают конфигурационные ячейки (они же fuse- биты) cksel, но их количество и комбинации включения у разных моделей могут различаться очень существенно, потому их всегда нужно проверять по документа- ции (хорошо помогают уже упомянутые ранее пособия Евстифеева [6,7]). Канонический способ тактирования МК— подключение кварцевого резонатора к соответствующим выводам XTAL1 и XTAL2 (рис. 2.3, а). Емкость конденсаторов С1 и С2 в типовом случае должна составлять 15-22 пФ (может быть увеличена до 33-47 пФ с одновременным повышением потребления). Кварцевый резонатор (для краткости его часто называют просто «кварцем») также можно заменить керамиче- ским. Такой возможностью сейчас, вероятно, уже мало кто пользуется — это когда- то керамические резонаторы стоили принципиально дешевле более стабильных кварцевых, и замена имела смысл. Все эти возможности при резонансных частотах кварца от 0,4 МГц до максимальной тактовой частоты контроллера у всех моделей Tiny и Mega реализуются одинаково, и у разных моделей различаются лишь спосо- бом задания режима. не подключен сигнал от внешнего генератора яг — XTAL2 XTAL1 Рис. 2.3. Способы тактирования МК AVR с использованием внешних элементов: а — кварцевого резонатора; б— внешнего генератора; в — RC-цепочки Подробности У многих контроллеров Меда предусмотрена возможность работы от «часового» квар- ца (частотой 32,768 кГц), для чего надо соответствующим образом выставить конфи- гурационные ячейки. Схема ничем не отличается от рис. 2.3, а, за исключением того, что рекомендуемая емкость конденсаторов С1 и С2 здесь равна 36 пФ. Если вам за- чем-то это понадобится, то проверяйте наличие этой возможности по документации (раздел «даташита» должен называться Low-frequency Crystal Oscillator) — например, у ATtiny2313 она отсутствует, и в низкочастотном режиме эта модель может быть
36 Часть I. Общие принципы устройства и функционирования Atmel AVR включена только от встроенного или внешнего генератора. Режим работы от «часового» кварца может понадобиться лишь в исключительных случаях — потребление кристал- ла при этом падает менее, чем до 100 мкА, оказывается удобно отсчитывать проме- жутки времени с большой точностью, но и множество функций остаются за пределами возможностей (например, при такой частоте невозможно «завести» последовательный порт UART со стандартными значениями скорости обмена). Заметим, что у некоторых моделей (например, у ATtiny2313— как бы в компенсацию невозможности подключе- ния «часового» кварца) имеется еще отдельный внутренний генератор 128 кГц. В та- ком режиме потребление тоже падает примерно до 100 мкА, но к нему также относят- ся все оговорки относительно работы на «часовой» частоте. Более подробно целе- сообразность эксплуатации контроллеров на сверхнизких частотах мы рассмотрим в разд. «Потребление MKAVR» главы 4. В большинстве моделей Mega имеется специальный конфигурационный бит скорт, который позволяет регулировать потребление. При установке этого бита в 1 (неза- программированное состояние) размах колебаний генератора уменьшается, однако при этом сужается возможный диапазон частот и снижается общая помехоустойчи- вость, поэтому при высоких частотах рекомендуется сменить этот режим на пол- ный размах (скорт = о). Автору этих строк удавалось запускать МК на нестандартных частотах, используя вместо кварца в том же подключении миниатюрную индуктивность (при ее значе- нии 4,7 мкГн и емкостях конденсаторов 91 пФ частота получается около 10 МГц). Заметки на полях При тактировании от кварцевого резонатора возникает законное желание получить максимальную точность отсчета промежутков времени. При этом надо учитывать, что кварцевые резонаторы бывают очень разные. Погрешность порядка 10 , которую поч- ти гарантированно можно получить от любого кварца, кажется очень маленькой, и для огромного большинства применений это так и есть — ни одну физическую величину, кроме времени, вы в домашних условиях с такой точностью (0,01%) не измерите. Но сама по себе эта величина погрешности не так уж и хороша — часы с такой точностью будут уходить на минуты в месяц, что уже почти неприемлемо. Так что при необходи- мости получить более высокую точность смотрите, что приобретаете, — при прочих равных можно ожидать, что кварц в полноразмерном корпусе HC-49U будет точнее и стабильнее, чем в обычном низком «гробике» HC-49S (проверить это можно по доку- ментации на конкретную марку). Кроме того, стабильность зависит от схемы генерато- ра, над которой вы тут не властны. Поэтому, чтобы гарантированно получить точный отсчет времени, лучше употреблять не простые кварцевые резонаторы, а готовые прецизионные кварцевые генераторы, которые гарантируют точность и стабильность частоты (самые популярные выпускает фирма Epson). Вторая возможность тактирования, наличествующая у всех без исключения совре- менных Tiny и Mega, — от встроенного RC-генератора (Internal RC Oscillator). Она не требует внешних элементов, потому включена по умолчанию. Для работы от внешнего кварца следует не забывать перестроить конфигурацию fuse-битов, иначе вы можете быть весьма удивлены результатами работы, например, таймеров. А вот возможные частоты этого встроенного генератора могут отличаться у разных моде- лей. У «наших» моделей Mega (т. е. тех, на которые мы будем преимущественно ориентироваться в этой книге) — можно выбирать из ряда: 1, 2, 4 и 8 МГц (1 МГц по умолчанию), у Tiny2313 — из двух значений: 4 и 8 МГц (8 МГц по умолчанию).
Глава 2. Общее устройство, организация памяти, тактирование, сброс 37^ А в других случаях значения могут быть сильно различаться — например, у «лю- бимого» ардуиновского ATmega328 (как и его аналогов с меньшим объемом памя- ти Mega88 и Mega 168) доступна только одна встроенная частота 8 МГц, зато есть некоторые модели (весьма редкие), у которых встроенный генератор может рабо- тать на частоте 20 МГц и на «часовой» частоте 32,768 кГц. Обратите внимание, что у многих контроллеров при тактировании от встроенного генератора выводы XTAL1 и XTAL2 могут использоваться как дополнительные выводы обычных портов. У ATtiny2313, например, это два вывода'в остальном от- сутствующего порта А (РАО и РА1), у ATmega8 — дополнительные выводы порта В (РВ6 и РВ7) и т. д. Особенно выгодно это использовать у младших Tiny с малым количеством контактов корпуса. На практике следует ожидать, что у всех моделей, вне зависимости от частоты ге- нератора, тактовая частота «чистого» кристалла по умолчанию будет равна 1 МГц, Дело в том, что даже у тех контроллеров, у которых частота встроенного генератора не может быть установлена равной 1 МГц (ATtiny2313, ATmega328 и т. п.), имеется конфигурационная ячейка под названием ckdiv8, которая по умол- чанию запрограммирована (установлена в нулевое значение), что означает деление частоты генератора на 8. Не забывайте ее переустановить перед использованием внешнего резонатора (отметим, что в русскоязычных пособиях Евстифеева [6,7] этот момент отражен нечетко, так что всегда смотрите документацию на конкрет- ный контроллер!). У некоторых моделей (ATtiny2313) имеется также внутренний делитель тактовой частоты (регистр clkpr), с помощью которого частоту тактиро- вания можно менять программно. По идее эта возможность работает при любом способе тактирования (так можно, например, регулировать потребление контролле- ра), но более актуальна она при тактировании от встроенного генератора, частоту которого саму по себе мы изменить не можем. Частоту встроенного генератора, которая и сама по себе имеет разброс от одного кристалла к другому и «плавает» от изменения внешних условий (температуры и напряжения питания), можно регулировать весьма в широких пределах. Но пользо- ваться возможностями такой калибровки нужно очень осмотрительно — докумен- тация не рекомендует менять частоту более, чем на 10% от номинальной, иначе мо- гут быть сложности, например, с записью во flash- и EEPROM-память. Для обыч- ных применений заводской калибровки вполне достаточно, и усложнять себе жизнь, пытаясь стабилизировать параметры заведомо нестабильной схемы, не име- ет смысла — проще подключить стабильный внешний кварц или, если очень надо, готовый прецизионный генератор. Заметки на полях В одной из публикаций на сайте, посвященном конструированию всяческих автомо- бильных прибамбасов, мне встретились жалоба на отказы встроенного генератора Tiny2313 при температурах ниже примерно -20 °С и указание на то, что при этом по- могает установка обычного внешнего кварца. В документации на контроллеры про это ничего не сказано, наоборот, приведен график изменения частоты RC-генератора от температуры вплоть до граничного рабочего значения минус 40 °С (причем при сни- жении температуры частота у некоторых моделей повышается, у других — снижается, но эти изменения не очень существенные, на 2-3% от среднего значения). Потому
38^ Часть I. Общие принципы устройства и функционирования AtmelAVR есть подозрение, что контроллер тут ни при чем, но в домашних условиях досконально проверить это нереально — нужны лабораторные климатические испытания при низ- ких температурах. Можно только тщательно проанализировать схему на предмет ра- ботоспособности всех компонентов при таких температурах и проверить надежность паек, которые при температурной деформации могут отходить. И все-таки стоит дер- жать возможность отказов в уме и по возможности проводить натурные испытания — не во всех реальных случаях можно теоретически учесть все возможные сочетания причин. Все модели AVR также имеют возможность тактирования от внешнего генератора (рис. 2.3, б), ограниченного только значением максимальной частоты самого кон- троллера. Сигнал, разумеется, должен соответствовать требованиям к уровням и таймингам сигналов, установленных для контроллера. На практике эта возмож- ность может быть востребована при необходимости строго синхронизировать рабо- ту нескольких контроллеров, а также для получения строго определенной тактовой частоты от внешнего прецизионного генератора. Отметим, что случайная установка в режим работы от внешнего генератора (этот режим у всех AVR задается одинаково — все имеющиеся ячейки cksel переводятся в нулевое значение) делает невозможным внутрисхемное программирование кон- троллера. Эта распространенная ошибка новичков — следствие запутанной терми- нологии в установках конфигурационных ячеек и, как следствие, разнобоя их уста- новок в разных программаторах. Подробнее этот вопрос мы обсудим в главах 4 и 5, а здесь заметим, что паника в этом случае совершенно необоснованна, — если кри- сталл перестал откликаться, соберите любой внешний генератор из деталей, кото- рые есть под рукой (подробнее об этом рассказано в главе 5). А вообще-то все рав- но следует обзавестись готовым цифровым генератором (о чем речь пойдет в гла- ве 4) — он и в работе с Arduino требуется очень часто. Еще одна возможность тактирования связана с использованием внешней RC-це- почки, подключаемой к выводу XTAL1 (рис. 2.3, в). Заметим, что этот способ дос- тупен не у всех моделей (в частности, у ATtiny2313 такая возможность отсутству- ет). Он задействуется в случаях, когда стабильность частоты не важна, а возможно- сти встроенного генератора по какой-либо причине вас не устраивают. Частота при таком подключении рассчитывается по примерной формуле f ~ 1/3RC. Из ограни- чений на элементы R и С (R в диапазоне 3,3-100 кОм, минимальное значение С = 22 пФ) вытекает, что максимальная частота, достижимая таким способом, равна примерно 5 МГц. Заметки на полях Интересное применение этого способа тактирования МК — динамическая подстройка частоты в системах измерения, регулирования и контроля. Например, когда ваш МК задействован в системе управления синхронным двигателем или генератором, рабо- тающим с частотой бытовой сети, может потребоваться точная синхронизация управ- ляющих сигналов с сетевой частотой 50 Гц. В этом случае несложно заменить сопро- тивление R на регулируемый извне преобразователь «частота — значение тока» и получить простую в налаживании аналоговую автоподстройку вместо долгой возни с алгоритмами цифрового регулирования частоты и фазы. Разумеется, при точно не известной и тем более переменной тактовой частоте некоторые функции МК будут не- доступны (передача по UART, например), но в таких ситуациях они, как правило, и не требуются.
Глава 2. Общее устройство, организация памяти, тактирование, сброс 39_ Отметим еще для галочки, что в некоторых моделях (ATmega8, ATmega8535 и др.) имеются внутренние конденсаторы 36 пФ, которые можно подключить, если запи- сать логический ноль в конфигурационную ячейку скрот. Они оказываются под- ключенными между выводами XTAL1 и XTAL2 и «землей», подобно конденсато- рам на схеме рис. 2.3, а. Иными словами, их можно использовать в схемах с низко- частотным кварцем или в схеме с внешней RC-цепочкой, а вот в основной схеме с кварцевым резонатором их лучше не употреблять, т. к. номинал их почти вдвое выше рекомендуемого (от этого может заметно возрасти потребление и генератор будет перегружаться). Способ этот не универсальный (во многих моделях контрол- леров эти конденсаторы отсутствуют), да и сами способы тактирования, где он при- годен, достаточно экзотичные, поэтому мы его употреблять не будем. Неправильным будет также не упомянуть, что во многих моделях (из «наших» это ATtiny2313) имеется предделитель тактовой частоты, управляемый программно битами clkps (не путать с fuse-битами ckdivs и cksel, управляющими собственно значением частот генератора и устанавливаемыми до загрузки программы). В ATtiny2313 можно поделить частоту генератора по всему двоичному ряду от 1 до 256. Такое может понадобиться, например, если от внутреннего генератора, ко- торый обычно сам по себе работает на частоте 8 МГц, захочется получить некую частоту тактирования, отличную от «умолчательной» 1 МГц. Возможно, у кого-то возникнет искушение попробовать работу на супернизких частотах — при исход- ных 8 мегагерцах деление на 256 позволяет получить тактовую частоту меньше 32 кГц. Для КМОП-схем это означает практическое выключение: потреблять будет только сам генератор, что, впрочем, может быть тоже немало — при 5 вольтах пи- тания он потребляет около 50 мкА. Сброс Сбросом (RESET) называется установка начального режима работы МК (аппарат- ная или, как это раньше называли, «холодная» перезагрузка). При этом все РВВ устанавливаются в состояние по умолчанию — как правило, это нули во всех раз- рядах за небольшим исключением. На практике то же самое относится и к РОН (как и к ячейкам SRAM)— они по сбросу обнуляются, но не случайно этот момент никак не оговорен официально. В некоторых случаях (например, после выхода из «сна» — об этом см. главу 14) РОН и ячейки SRAM после сброса могут принимать произвольные значения, поэтому при необходимости обязательно начинать с ка- кой-то определенной величины переменные следует устанавливать в начале про- граммы принудительно. Программа после аппаратного сброса начинает выполняться не сразу, а с некоторой задержкой (см. описание fuse-битов suto и suti в главе 5). Выполнение происходит с заданного начального адреса (по умолчанию это адрес $оооо), что позволяет при желании организовать софтверную («горячую») перезагрузку изнутри программы, просто организовав безусловный переход на нулевой адрес. При этом все регистры и память сохраняют свое состояние на момент сброса. Правда, зачем такое может понадобиться, я так и не придумал — сторожевой таймер в этом качестве задейст- вовать куда удобнее.
j40 Часть I. Общие принципы устройства и функционирования Atmel AVR А вот при наличии программы-загрузчика контроллер обращается сначала к нему, и запуск откладывается на время ожидания, — а вдруг извне придет команда на перезапись программы? Подробно об этом рассказано в главе 6, а здесь только от- метим, что время ожидания может составлять несколько секунд, что иногда бывает критично, — еще одно ограничение, присущее в том числе и платформе Arduino. Сброс всегда происходит при включении питания. Кроме этого, источниками сбро- са могут быть следующие события: □ аппаратный сброс, т. е. подача низкого уровня напряжения на вход RESET. По- скольку активный уровень сброса у всех микросхем всегда низкий (исключения мне неизвестны), то правильнее его обозначать с инверсией: RESET. Если вы встретили такое обозначение, то речь идет именно о выводе сброса, просто функция сброса, понятно, именуется без всякой инверсии; □ — окончание отсчета установленного интервала сторожевого таймера; □ — срабатывание схемы BOD. Подробности Значение четырех младших битов регистра состояния mcucsr должно сигнализировать о том, от какого источника производился сброс предыдущий раз (установка в 1 бита о — сброс при включении, бита 1 — аппаратный сброс, бита 2 —■ от схемы ВСЮ, бита 3 — от сторожевого таймера). На практике, по опыту автора, по состояниям этого ре- гистра надежно отличаются от всех остальных лишь состояния сброса по сторожево- му таймеру (прочие флаги могут оказаться установленными все одновременно). Тем не менее, эта информация может быть полезной, например, при анализе причин пе- рерывов в работе круглосуточно работающих устройств (см. главу 14). В младших МК семейства Tiny (кроме ATtiny28L) нет встроенного «подтягиваю- щего» резистора на выводе RESET, поэтому для надежной работы следует преду- смотреть подключение внешнего резистора величиной 2-10 кОм от этого вывода к напряжению питания. Автор также настоятельно рекомендует устанавливать по- добный резистор для любых моделей AVR, т. к. встроенный резистор имеет часто большой номинал (30-60 кОм), и на нем могут наводиться помехи, способные при- вести к непредсказуемому сбросу. Не помешает также (хотя в технических описаниях такой рекомендации и не со- держится) установка конденсатора 0,1-1 мкФ от вывода RESET на «землю» — это сглаживает неизбежный дребезг напряжения и при включении немного затягивает фронт нарастания напряжения на выводе RESET по сравнению с увеличением на- пряжения питания — когда наступит порог срабатывания схемы сброса, напряже- ние питания всего МК уже установится. В пользу этой рекомендации говорит также факт, что на всех платах Arduino такая внешняя цепочка 10 кОм/0,1 мкФ по выводу RESET обязательно имеется совместно с кнопкой ручного сброса. Впрочем, если питание организовано надлежащим образом, то нарушение этой рекомендации не приводит к каким-то фатальным последствиям. Конденсатор можно не устанавли- вать также, если за сбросом следит внешний монитор питания (супервизор — под- робнее о нем рассказано далее).
Глава 2. Общее устройство, организация памяти, тактирование, сброс 41 Во многих моделях контроллеров, если не требуется внешний сброс, вывод RESET может выполнять функции обычного порта ввода/вывода (подобно выводам такти- рования XTAL1 и XTAL2 — о них подробно рассказывалось ранее). С одним толь- ко нюансом — обычно для такого применения приходится режим вывода специ- ально переключать (в отличие от XTAL1 и XTAL2, которые переключаются авто- матически при выборе режима тактирования и подключении к ним внешнего резонатора). У некоторых младших Tiny вывод RESET включен в режим обычного порта по умолчанию. У ATtiny28L при конфигурировании этого контакта на выход он работает как вывод с открытым коллектором, а не как обычный логический эле- мент (о конфигурации выводов портов рассказано в главе 3). Для корректного сброса при включении и выключении питания служит встроенная схема BOD (Brown-out Detector), которая обеспечивает время срабатывания поряд- ка микросекунд с задержкой на возврат в рабочее состояние после восстановления напряжения, определяющейся теми же установками, что и задержка сброса (конфи- гурационные ячейки ckselo и sut). Для выбора режима работы BOD служат конфи- гурационные ячейки bodlevel, с помощью которых можно установить порог сраба- тывания около 4,0 В (в некоторых моделях около 4,3) для питания 5 вольт или око- ло 2,6-2,7 В для питания 3-3,3 вольта. Для предотвращения дребезга BOD имеет гистерезис по напряжению величиной около 50-150 мВ, а также задержку включе- ния около 2 мкс и выключения с типовым значением около 4-5 мс и максимально возможным порядка 65-69 мс. BOD вполне работает при штатных источниках питания — стабилизаторах с низ- ким выходным сопротивлением и рекомендованными емкостями конденсаторов. Но его характеристики могут оказаться недостаточными для обхода дребезга, воз- никающего при снижении напряжения питания всяких других источников (напри- мер, батареек, ведущих себя при истощении заряда очень по-разному, — см. врезку «Подробности» далее). Кроме того, схема BOD повышает потребление в режимах энергосбережения, и документация рекомендует ее в таких случаях отключать (экономия должна составить около 20-30 мкА). Поэтому самый надежный способ организации сброса при включении и выключении питания — установка внешнего монитора питания (супервизора), который подает сигнал сброса на вывод RESET контроллера в момент снижения напряжения питания ниже допустимого порога и снимает его при повышении напряжения обычно с некоторой задержкой. Обратим внимание, что BOD работает от того же самого встроенного опорного ис- точника (ИОН) 1,1-1,3 В, к которому может подключаться аналоговый компаратор и АЦП, и BOD наследует все связанные с этим ошибки (подробно об этом см. в гла- вах 3 и 70). Самым популярным монитором питания при питании 5 В когда-то был МС34064, но он уже решительно устарел по одному только уровню потребления (0,5 мА), к тому же имеет неоптимальный порог срабатывания 4,6 вольта. Поэтому лучше использовать более современные компоненты. Выпускают мониторы питания очень многие фирмы в неисчислимом количестве разновидностей, и нужно быть очень внимательным, чтобы не промахнуться, — есть супервизоры, у которых ак-
42 Часть /. Общие принципы устройства и функционирования Atmel AVR тивный уровень RESET высокий, а не низкий, их применительно к AVR придется включать с инвертирующим транзистором, что, конечно, лишнее. При 5-вольтовом питании подойдет, например, микросхема DS1813-15 с порогом срабатывания 4,1 В и потреблением ЗОмкА, при трехвольтовом— DS1818-20 (2,6 В и 35 мкА), оба в корпусах ТО-92. Есть также очень хорошие супервизоры МСР102/103 фирмы Microchip с потреблением 1 мкА, но их сложно достать в удобных корпусах (ТО-92 или DIP-8). Упомянутые марки самые простые— трехвыводные (питание, «земля», вывод управления сбросом). Супервизоры DS1813 и DS1818 заменяют RC-цепочку на вы- воде RESET контроллера и имеют возможность корректной работы совместно с ручной кнопкой Reset. МСР 102/103 имеют выход с открытым коллектором, пото- му требуют подтягивающий резистор, конденсатор RC-цепочки при этом оказыва- ется ненужным. Типовое время срабатывания этих микросхем при снижении на- пряжения— микросекунды, что обеспечивает сохранность данных в EEPROM. При повышении напряжения они создают достаточную временную задержку (по- рядка 0,15-0,25 секунды), что позволяет надежно осуществлять сброс МК без дре- безга. Конечно, есть и куда более «навороченные» супервизоры питания — с двумя порогами, с дополнительными таймерами и т. д. Подробности Еще один плюс применения внешнего монитора заключается в том, что можно подоб- рать оптимальный порог срабатывания под конкретный случай. Гальванические эле- менты имеют пологую кривую разряда с повышенным внутренним сопротивлением вблизи точки полного истощения, поэтому оставлять МК на произвол судьбы при ба- тарейном питании нехорошо даже при отсутствии взаимодействия с такими критичны- ми компонентами, как EEPROM, — кто знает, какие произвольные команды начнут при этом выполняться? Но с точки зрения оптимального расхода энергии автономных ис- точников — батареек и аккумуляторов — величина раз и навсегда заданного порога срабатывания монитора питания, да еще и различающегося от модели к модели, мо- жет быть далеко не лучшим выбором. Щелочные элементы имеют начальное напря- жение 1,6 вольта и конечное около 1,0 вольта, ниже этого значения у них непредска- зуемо растет выходное сопротивление. Поэтому питание 5 вольт удобно организовать из четырех таких элементов с дополнительным стабилизатором, ограничивающем общее начальное напряжение на уровне 5 вольт и монитором питания, рассчитанным на 4,0 вольта, что гарантирует достаточно полное исчерпание энергии батареек, — в этом идеальном случае уровни встроенной схемы BOD вполне на высоте. На прак- тике встроенной BOD следует избегать по другой причине — большого разброса поро- га от экземпляра к экземпляру, отчего приходится калибровать схему каждый раз ин- дивидуально. А вот если вы хотите непременно обойтись без стабилизатора, то вам придется ста- вить 3 элемента (4,8 В максимум) и величина порога BOD 2,7 вольта (0,9 вольта на элемент) маловата — из-за пологой характеристики и высокого выходного сопротив- ления элементов питания схема может начать вести себя непредсказуемо задолго до достижения минимально допустимого напряжения. Еще хуже ситуация при использо- вании литиевых элементов, которые хороши из-за пологой характеристики и мини- мального саморазряда— для них величины порога 2,7 (для элементов-«монеток» с номинальным напряжением 3 вольта) или 4,0 (для трех «пальчиковых» элементов с номинальным напряжением 1,5 вольта каждый) будут уже неприемлемо низкими, глубоко заходящими в зону полной неработоспособности элемента. Подробнее о при- менении мониторов питания в этих случаях рассказано в главе 10.
ГЛАВА 3 Периферийные устройства и прерывания Периферийными в структуре AVR называются все устройства, внешние по отноше- нию к ядру и памяти. В «компьютерных» процессорах такие устройства, как прави- ло, реализуют в виде отдельных микросхем (входящих в состав чипсетов) или целых модулей на отдельных платах. Общее для «computer-on-chip» и «обычных» вычислительных систем свойство периферийных устройств — их переменный со- став, т. е. в разных системах (для МК — в разных моделях контроллеров) те или иные компоненты могут отсутствовать. Наиболее популярные периферийные устройства (таймеры, порты UART и SPI, аналоговый компаратор или сторожевой таймер) имеются практически во всех моделях, но это не значит, что вы их обязательно должны задействовать. Тем не менее их номенклатуру следует учитывать при выборе той или иной модели. Во- первых, наличие большого числа периферийных устройств, даже если они не задействованы, увеличивает общее потребление микросхемы, во-вторых, нет ника- кого резона ставить ATmega2560 в 100-выводном корпусе там, где достаточно ATtiny2313 в корпусе с 20-ю выводами. Кроме того, некоторые периферийные уст- ройства приходится принимать во внимание даже в том случае, если они не исполь- зуются — типичным примером может служить аналоговый компаратор, который по умолчанию всегда включен и потому может оказывать влияние на потребление в «спящем» режиме (хотя и мизерное). Все периферийные устройства адресуются через регистры ввода/вывода (РВВ, I/O registers), аналогичные портам ввода/вывода в архитектуре IBM PC. Отметим, что в PIC-архитектуре взаимодействие с периферийными устройствами организо- вано проще — там можно заносить данные непосредственно в порты, без промежу- точного переноса значения РВВ в РОН и обратно с помощью команд in и out. В AVR прямая модификация значений РВВ ограничена и обставлена рядом усло- вий, но в целом это не приводит к значительным трудностям — программы для AVR в общем все равно получаются более эффективными. Сложности добавляет факт, что РВВ в новых моделях контроллеров перестали умещаться в первоначально отведенном для них пространстве адресов емкостью 64 байта (от $20 до $5f). Как мы отмечали ранее, уже у ATmega88 для дополнитель- ных РВВ пришлось выделить отдельное адресное пространство (от $60 до макси-
44 Часть I. Общие принципы устройства и функционирования AtmelAVR мально возможного в байтовой адресации значения $ff). Для регистров в этом до- полнительном пространстве не работают многие удобные команды: in/out, sbi/cbi и т. п., и их можно адресовать только универсальным способом — как ячейки па- мяти. Между тем в это пространство могут «переехать» не только регистры допол- нительных по отношению к младшим моделям устройств, но и многих из привыч- ного состава периферии: регистры управления UART, Timerl и Timer2, TWI и т. д. Чтобы не создавать лишних сложностей при изучении ассемблера, мы не станем пользоваться такими моделями контроллеров, — как вы увидите, для львиной доли любительских задач они просто не потребуются. Но, конечно, далее (см. главу 6) я расскажу в подробностях, как и чем можно заменить привычные команды при переходе к более «продвинутым» моделям, чтобы вы не потерялись при возникно- вении такой задачи, — ведь среди них, между прочим, и ардуиновская ATmega328. В отличие от «регистров ввода/вывода», в архитектуре AVR, как и в других архи- тектурах МК, термин «порты ввода/вывода» (I/O ports) обозначает параллельные порты для обмена данными с внешними устройствами. С них мы и начнем рас- смотрение периферийных устройств. Порты ввода/вывода Портов ввода/вывода в разных моделях может быть от 1 до 7. Номинально все пор- ты 8-разрядные, в некоторых случаях разрядность ограничена числом выводов корпуса и может быть меньше восьми. Порты обозначаются буквами А, В, С, D и далее, причем необязательно по порядку — в младших моделях могут наличест- вовать, например, только порты В и D (как в ATtiny2313) или вообще только один порт В (как в младших Tiny). Для сокращения числа контактов корпуса в подавляющем большинстве случаев внешние выводы, соответствующие портам, кроме своей основной функции (дву- направленного ввода/вывода) несут также и дополнительную. Отметим, что ника- кого специального переключения выводов портов не требуется — просто, если вы, к примеру, в своей программе инициализируете последовательный порт UART, то соответствующие выводы RxD и TxD (в ATmega8 они совпадают с выводами PD0 и PD1 порта D) будут работать именно в альтернативной функции — как ввод и вы- вод UART. При этом в промежутках между таким специальным использованием выводов они остаются доступны как обычные двунаправленные выводы. На прак- тике при одновременном использовании приходится применять схемотехнические меры для изоляции функций друг от друга, поэтому злоупотреблять этой возмож- ностью не рекомендуется, — особенно это критично для выводов того порта (обычно это порт А), к которому подсоединены входы АЦП. Более того — при на- личии АЦП не рекомендуется задействовать для выполнения логических функций другие выводы того же порта (которые в работе АЦП не участвуют), потому что это значительно увеличивает уровень помех (подробнее об этом рассказано в этой главе далее, а также в главе 11). Выводы портов в достаточной степени автономны, и их режим может устанавли- ваться независимо друг от друга. По умолчанию при включении питания все до-
Глава 3. Периферийные устройства и прерывания 45_ полнительные устройства отключены, а порты работают на вход, причем находятся в состоянии с высоким импедансом (т. е. высоким входным сопротивлением — та- кой режим порта еще называют «третьим» состоянием). Работа на выход требует специального указания, для чего в программе нужно установить соответствующий нужному выводу бит в регистре направления данных (этот регистр обозначается ddrx, где х — буква, обозначающая конкретный порт. Например, для порта А это будет ddra). Если бит сброшен (т. е. равен логическому 0), то вывод работает на вход (установка по умолчанию), если установлен (т. е. равен логической 1) — то на выход. Для установки выхода в состояние 1 нужно отдельно установить, а для установки в 0 — сбросить, соответствующий бит в регистре данных порта (обозна- чается portx). Направление работы вывода (вход-выход, регистр ddrx) и его состоя- ние (0-1, portx) путать не следует. В Arduino этим установкам соответствуют функции pinMode () (установка направления) и digitaiwrite () (состояние вывода). Регистр данных portx фактически есть просто выходной буфер — все, что в него записывается, тут же оказывается на выходе. Но если установить вывод порта на вход (т. е. записать в регистр направления логический 0), как это сделано по умол- чанию, то регистр данных portx будет играть несколько иную роль: установка его разрядов в 0 (так по умолчанию) означает, что вход находится в третьем состоянии с высоким сопротивлением, а установка в 1 подключит к выводу «подтягивающий» (pull-up) резистор сопротивлением 20-50 кОм. В Arduino этим действиям соответ- ствуют КОНСТаНТЫ INPUT И INPUT_PULLUP фуНКЦИИ pinMode () . «Подтягивающие» резисторы необходимы при подключении двухвыводной кноп- ки, при соединении с выходом с «открытым коллектором» (пример— шина TWI/I2C) и в ряде других ситуаций. Как и в случае вывода RESET (см. главу 2), необходимо отметить, что несмотря на некоторое снижение сопротивления «подтя- гивающего» резистора в современных семействах (в семействе Classic оно состав- ляло 35-120 кОм), встроенного pull-up-резистора во многих случаях может оказать- ся для надежной работы недостаточно, и лучше устанавливать дополнительный внешний резистор с сопротивлением от 1 до 10 кОм параллельно этому внутренне- му. Не стоит забывать, что каждый такой резистор при установке вывода в состоя- ние нуля увеличивает потребление, потому в критичных случаях его величину можно увеличить до 20-30 кОм и дополнительно стоит тщательно продумать поря- док перехода к энергосберегающему режиму (подробнее об этом рассказано в гла- ве 14). Заметки на полях Обратите внимание на тот момент (отраженный, кстати, в описаниях контроллеров), что непосредственное переключение между противоположными состояниями выводов портов (т. е . между состояниями ddrx:portx = 11 и ddrx: portx = 00, а также состоя- ниями ddrx:PORTx = 10 и ddrx:PORTx = 01) невозможно. В качестве промежуточных сами собой образуются, пусть и на очень короткое время, состояния, когда пере- ключен только один из этих разрядов портов (состояния 01 или 10 в первом случае и 11 или 00 во втором). У автора это ни разу не привело к каким-то проблемам, но не- обходимо помнить о том, что, например, при переключении из состояния логического нуля на выходе в состояние «на вход» с «подтягивающим» резистором, вся линия на
46^ Часть I. Общие принципы устройства и функционирования AtmelAVR короткий момент оказывается либо «подвешенной в воздухе», либо подключенной к высокому уровню напряжения, — для быстродействующих присоединенных устройств это может оказаться критичным. Это еще один аргумент за то, чтобы не лениться ис- пользовать внешние «подтягивающие» резисторы, в присутствии которых как мини- мум состояние «подвешенности» никогда не возникает. А если у вас вдруг возникает противоположная проблема — мешает внутренний «под- тягивающий» резистор, на короткое время подключающийся при переключении выво- да из состояния логической единицы на выходе на вход в «третьем состоянии» (хотя я лично не могу себе представить такой ситуации, когда это критично), то надо знать, что во многих моделях (например, в «наших» ATmega8 и ATmega8535) встроенными pull-up-резисторами можно оптом управлять через бит pud (бит 2) регистра sfior. По умолчанию он равен нулю, но если установить его в единицу, то все pull-up-резисторы во всех портах окажутся отключенными — входы МК превратятся в обычные высоко- омные входы КМОП (в третьем состоянии). Процедура чтения уровня на выводе порта, если он находится в состоянии работы на вход, на ассемблере не совсем тривиальна (в Arduino это функция digitaiRead ()). Возникает искушение прочесть данные из регистра данных portx, но это ничего не даст — вы получите только то, что там записано вами же ранее. А для чтения того, что действительно имеется на входе (непосредственно на выводе микросхемы), предусмотрена другая возможность— нужно обратиться к некоторому массиву, который обозначается pinx. Обращение осуществляется так же, как и к отдельным битам обычных регистров (см. главу 6), но pinx — это не регистр, а просто некий диапазон адресов, чтение по которым предоставляет доступ к информации из бу- ферных элементов на входе порта. Записывать что-то по адресам pinx, естественно, нельзя. Таймеры-счетчики В большинстве МК AVR наличествуют два или три таймера-счетчика, один из ко- торых 16-разрядный, а оставшиеся один или два— 8-разрядные (в старших моде- лях Mega общее число таймеров может достигать шести). Все счетчики имеют воз- можность предварительной загрузки значений и могут работать от тактовой часто- ты (СК) процессора непосредственно или от поделенной на 8, 64, 256 или 1024 (в отдельных случаях еще на 16 и 32), а также от внешнего сигнала. В модели ATtinyl5, содержащей внутренний умножитель частоты, таймер может работать от более высокой частоты, чем тактовая (до 16СК). В архитектуре AVR 8-разрядным счетчикам-таймерам присвоены номера 0 и 2, а 16-разрядным— 1, 3 и далее. Некоторые 8-разрядные счетчики (обычно Timer 2, если их два) могут работать в асинхронном режиме от отдельного тактового гене- ратора, причем продолжать функционировать даже в случае «спящего» состояния всей остальной части МК, что позволяет использовать их в качестве часов реально- го времени. В большинстве моделей AVR предусмотрена возможность сброса содержимого счетчика-предделителя таймеров для того, чтобы счет всегда начинался с заданного интервала. Предделитель (он общий, хотя обычно и не для всех таймеров — в «на- ших» случаях он управляет таймерами 0 и 1) работает независимо от самих тайме-
Глава 3. Периферийные устройства и прерывания 47 ров, потому для синхронизации запуска таймеров предделитель можно останавли- вать и запускать по команде, — еще одна функция, недоступная в Arduino. Подробности В Arduino все временные функции завязаны на работу TimerO, потому этот таймер в общем случае нельзя использовать для каких-либо еще надобностей — это нарушит ход внутренних часов контроллера и приведет к неработоспособности трех основных функций отсчета времени: minis о, micros о и delay о (только функция deiayMicroseconds () работает независимо от таймеров). Нельзя также запрещать (по крайней мере надолго) прерывания функцией ciio (то же самое, что nointerrupts о) — таймер при этом просто остановится (подробно об этом рассказано в этой главе далее). Наоборот, сама по себе delay о на время задержки заблокирует все прерывания, а внутри обработчиков прерывание выполняться просто не будет. С наперед заданной скоростью работы TimerO (с коэффициентом деления тактовой частоты 1:64) связано ограниченное разрешение функции micros (), равное 4-м микро- секундам — это при периоде тактовой частоты в 1Лб микросекунды! Вероятно, отме- ченные ограничения — первое, с чем сталкивается пытливый любитель, пытаясь усо- вершенствовать свою программу. При программировании на ассемблере, конечно, ни- каких таких ограничений нет, а точное знание времени выполнения команд позволяет рассчитать все вплоть до длительности одного такта. Расскажем о некоторых возможностях счетчиков-таймеров, которые в Arduino либо недоступны вовсе, либо реализуются с большими сложностями (подробнее о про- граммировании счетчиков-таймеров мы поговорим в главе 9). Для начала упомянем о возможности их работы в качестве обычных счетчиков внешних импульсов (при- чем возможна реакция как по спаду, так и по фронту импульса). При этом частота подсчитываемых импульсов не должна превышать половины частоты тактового генератора МК (кстати, при несимметричном внешнем меандре инструкция реко- мендует и еще меньшее значение предельной частоты — 0,4 от тактовой). Это обу- словлено тем, что при счете внешних импульсов их фронты обнаруживаются син- хронно (в моменты положительного перепада тактового сигнала). Кроме того, сле- дует учитывать, что задержка обновления содержимого счетчика после прихода внешнего импульса может составлять до 2,5 периода тактовой частоты. Это весьма существенное ограничение, так что создавать, например, универсальный частото- мер с помощью МК не очень удобно — быстродействующие схемы лучше проек- тировать на соответствующей комбинационной логике или на ПЛИС (программи- руемых логических интегральных схемах). При переполнении счетчика (вне зависимости от источника сигнала) возникает со- бытие, которое может вызывать соответствующее прерывание. 8-разрядный счет- чик TimerO в ряде случаев этой функцией и ограничивается. Счетчик Timer2, если он имеется, может также вызывать прерывание по совпадению подсчитанного зна- чения с некоторой заранее заданной величиной. 16-разрядные счетчики— более «продвинутая» штука и могут вызывать прерывания по совпадению с двумя неза- висимо заданными числами: А и В. При этом счетчики могут обнуляться или про- должать счет, а на специальных выводах при этом— генерироваться импульсы (аппаратно, без участия программы), что позволяет создать, например, очень про- стой, гибко настраиваемый и работающий независимо от программы генератор импульсов.
48_ Часть I. Общие принципы устройства и функционирования AtmelAVR Кроме того, 16-разрядные счетчики имеют возможность осуществлять «захват» (capture) внешних одиночных импульсов на специальном выводе. При этом может вызываться прерывание, а содержимое счетчика помещается в некий регистр. Сам счетчик при этом может обнуляться и начинать счет заново или просто продолжать счет. Такой режим удобен для измерения периода внешнего сигнала или для под- счета неких нерегулярных событий (вроде прохождения частиц в счетчике Гейге- ра). Немаловажно, что источником таких событий может быть также встроенный аналоговый компаратор. Как минимум, 16-разрядные счетчики-таймеры в семействе Mega допускают работу в различных режимах PWM, т. е. в качестве 8-, 9-, 10- или 16-битовых широтно- импульсных модуляторов (ШИМ). Если допускающих такие режимы таймеров не- сколько, то они будут работать независимо друг от друга, что позволяет осуществ- лять многоканальную ШИМ. В технической документации этим режимам в силу их сложности, многовариантности и громоздкости посвящено много страниц. В отно- шении удобства реализации ШИМ платформа Arduino с ее единственной функцией anaiogwrite (), конечно, кроет ассемблер по всем статьям — если не считать того, что в реальности от упомянутых режимов можно получить гораздо больше, чем позволяет эта функция. ШИМ, в связи с легкостью ее реализации в интегрирован- ных счетчиках контроллеров, а также с появлением в последние десятилетия деше- вых, надежных и простых ключей на МОП-транзисторах, — чрезвычайно модная тема в современной схемотехнике, и мы ей отведем надлежащее время в дальней- шем изложении. Кроме таймеров-счетчиков, во всех без исключения AVR-контроллерах есть сто- рожевой (Watchdog) таймер. Он предназначен в основном для вывода МК из режи- ма энергосбережения через определенный интервал времени, но может служить и для просто аварийного перезапуска МК, — так, если работа программы зависит от прихода внешних сигналов, то при их потере (например, из-за обрыва на линии) МК может «повиснуть», а Wathcdog-таймер выведет его из этого состояния. Под- робнее о сторожевом таймере рассказано в главе 14. Аналого-цифровой преобразователь Многоканальный АЦП входит в большинство современных моделей МК AVR: практически во все Mega и в некоторые Tiny. Число каналов, обычно привязанных к выводам порта А или С, зависит от числа выводов корпуса и в разных моделях может варьировать от 4 каналов в младших моделях семейства Tiny до 6 каналов в ATmega8 в DIP-корпусе и 8 каналов в 40-выводном корпусе ATmega8535. Есть и модели специального назначения с экстремально большим количеством каналов АЦП— например, 28 каналов в ATtiny828. Многоканальность означает, что на входе единственного модуля АЦП установлен аналоговый демультиплексор, кото- рый может подключать этот вход к различным выводам МК для осуществления измерений нескольких независимых аналоговых величин с разнесением во време- ни. Входы демультиплексора могут работать по отдельности (в несимметричном режиме для измерения напряжения относительно «земли») или (в некоторых моде-
Глава 3. Периферийные устройства и прерывания 49_ лях) объединяться в пары для измерения дифференциальных сигналов. Иногда АЦП дополнительно снабжается усилителем напряжения с фиксированными зна- чениями коэффициента усиления 10 и 200. Сам АЦП в базовой конфигурации представляет собой преобразователь последова- тельного приближения с устройством выборки-хранения и фиксированным числом тактов преобразования, равным 13 (или 14 для дифференциального входа); первое преобразование после включения потребует 25 тактов для инициализации АЦП. Тактовая частота формируется аналогично тому, как это делается для таймеров, — с помощью специального предделителя тактовой частоты МК, который может иметь коэффициенты деления от 1 до 128. Но, в отличие от таймеров, выбор такто- вой частоты АЦП не совсем произволен, т. к. быстродействие аналоговых компо- нентов ограничено. Поэтому коэффициент деления следует выбирать таким, чтобы при заданном «кварце» тактовая частота АЦП укладывалась в рекомендованный диапазон 50-200 кГц (т. е. максимум около 15 тыс. измерений в секунду). Увеличе- ние частоты выборки допустимо, если не требуется достижение наивысшей точно- сти преобразования. Подробности Следящие преобразователи такого типа, как включенные в состав МК AVR, работают по следующей схеме. Берется цифроаналоговый преобразователь (ЦАП) нужной раз- рядности. На его цифровые входы подается с некоего регистра код по определенному правилу, о котором далее. Выход ЦАП соединяется с одним из входов компаратора, на другой вход которого подается преобразуемое напряжение. Результат сравнения подается на схему управления, которая связана с этим самым регистром — формиро- вателем кодов. Для того чтобы получить фиксированную длительность преобразова- ния, правило формирования кодов следующее: сначала все разряды кода равны ну- лю. В первом такте самый старший разряд устанавливается в единицу. Если выход ЦАП при этом превысил входное напряжение, т. е. компаратор перебросился в проти- воположное состояние, то разряд возвращается в состояние логического 0, в против- ном же случае он остается в состоянии логической 1. В следующем такте процедуру повторяют для следующего по старшинству разряда. Такой метод позволяет за число тактов, равное числу разрядов, сформировать в регистре код, соответствующий вход- ному напряжению. Алгоритм имеет существенный недостаток: если за время преобра- зования входное напряжение меняется, то схема может ошибаться, поэтому здесь обязательно наличие устройства выборки-хранения входной величины, которое до- полнительно замедляет процесс и вносит свою погрешность в конечный результат. Разрешающая способность базовой конфигурации АЦП в МК AVR— 10 двоичных разрядов, чего для большинства типовых применений достаточно (около 0,1% от шкалы измерения). В некоторых модификациях AVR — например, в моделях с ин- дексом HVA, предназначенных для контроля напряжения аккумуляторных батарей (таких, как ATmegal6HVA или ATmega8HVA),— разрядность АЦП повышена до 12, хотя принцип работы остался тем же. Абсолютная погрешность преобразо- вания зависит от ряда факторов и в идеальном случае не превышает +2 младших разрядов, что соответствует общей точности измерения примерно в 8 двоичных разрядов (погрешность 0,4% от шкалы измерения). Для достижения этого результа- та необходимо принимать специальные меры: не только «вгонять» тактовую часто- ту в рекомендованный диапазон, но и снизить по максимуму интенсивность цифро-
J50 Часть I. Общие принципы устройства и функционирования AtmelAVR вых шумов. Для этого рекомендуется, как минимум, не использовать оставшиеся выводы того же порта, к которому подключен АЦП (обычно это порт А), для обра- ботки цифровых сигналов, правильно разводить платы, а как максимум — допол- нительно к тому еще и включать специальный режим ADC Noise Reduction (что, впрочем, не всегда удобно — см. главу 11). На самом деле добиться указанной в документации абсолютной точности в 8 разря- дов можно только в теории — слишком от многих факторов она зависит. Поэтому прецизионной измерительной системы на основе такого АЦП построить не удаст- ся— на практике следует ориентироваться на абсолютную точность в пределах 0,8-1% от шкалы, чего достаточно для рядовых бытовых приборов. Для подобных задач всегда важней не точность, а стабильность преобразования (чтобы прибор при одинаковом входном сигнале всегда показывал одно и то же значение и не «мельтешил» младшим разрядом), а в этом плане встроенный АЦП вполне на уровне, особенно если применить программные меры подавления дребезга (см. главу 77). К любому аналого-цифровому преобразователю обязательно прилагается источник опорного напряжения, который играет роль эталонной измерительной линейки — относительно него отсчитываются уровни входного напряжения. Обычно для встроенного АЦП за такой источник принимают напряжение питания аналоговой части AVCC (заметьте, что оно по идее должно быть отдельным или хотя бы «раз- вязанным» с цифровым, чем в Arduino пренебрегают). Но иногда необходимо иметь абсолютную шкалу относительно точно известной величины напряжения, тогда к АЦП лучше подключать внешний прецизионный опорный источник, — напри- мер, из серии REF19x фирмы Analog Devices (вывод AREF — он имеется и на неко- торых платах Arduino). В составе AVR имеется и встроенный опорный источник (ИОН) напряжением около 1,1-1,3 вольта (для АЦП он во многих моделях усили- вается до 2,56 вольта), но по разбросу от экземпляра к экземпляру (в пределах ±0,08 вольта, т. е. примерно ±6%) он годится только для не очень ответственных случаев. Заметки на полях Мне всегда было интересно, почему в некоторых моделях Tiny (включая ATtiny2313) при номинальном напряжении 1,1 вольта встроенный ИОН имеет существенно мень- ший разброс, чем у Меда: ±0,9% (от 1,11 до 1,12 В), и почему нельзя было добиться того же самого в семействе Меда? Сам по себе опорный источник AVR не так уж и плох— он основан на вполне стандартном способе получения точного напряжения: это т. н. «ибэ-стабилитрон», или стабилитрон «с напряжением запрещенной зоны». По сути, это два последовательных р-п-перехода в диодном включении, которые ком- пенсируют друг друга при изменениях токи и температуры. Тот же способ использует- ся для получения очень точных опорных источников вроде упомянутой серии REF19x фирмы Analog Devices. Источник мало потребляет (микроамперы) и имеет стабиль- ность в рабочем диапазоне температур в единицы милливольт (температурный дрейф, если верить графикам в документации, порядка 0,005% на °С, что в целом очень неплохо). Неприятный разброс от экземпляра к экземпляру (на уровне обычного стабилитрона) остается основным препятствием к его применению — так что, кроме уж очень некритичных к точности применений, схема с участием встроенного ИОН требует индивидуальной калибровки.
Глава 3. Периферийные устройства и прерывания 51_ АЦП может работать в двух режимах: одиночного и непрерывного преобразования. Второй режим целесообразен лишь при необходимости максимально возможной частоты выборок. В остальных случаях его следует избегать, т. к. обойти связанную с ним обработку цифровых сигналов, осуществляемую одновременно с АЦП-пре- образованием, как правило, невозможно, а это означает еще большее снижение точности преобразования. Заметки на полях В принципе функции АПЦ перенесены в Arduino вполне аккуратно, и удобству пользо- вания им уделено весьма значительное внимание. За кадром по сути остались лишь способы повышения точности, выбора между точностью и скоростью, не используются также прерывания АЦП (явно) и некоторые другие мелочи, — но на самом деле все это не очень-то и нужно в реальных задачах. Потому, если не считать того факта, что собственно измерения на ассемблере, в отличие от Arduino, проводятся с максималь- но возможной и предсказуемой скоростью, АЦП представляет собой тот случай, когда сторонники языка С могут с удовлетворением констатировать: вот область, где ас- семблер явно проигрывает. Поэтому, если вам нужно просто измерить аналоговый сигнал, преобразовать его в физические величины и отобразить результат на индика- торе— тут удобство Arduino вне конкуренции. Зато, если при этом еще и возникает задача поместить все это в минимальные габариты и запитать от батареек, — вот тут уже однозначно рулит ассемблер, т. к. относительная сложность инициализации АПЦ и преобразования кода в физические величины окажется при его использовании дале- ко не самой существенной частью проекта. Применение традиционно входящего в состав всех AVR аналогового компаратора в Arduino не предполагается вовсе, и это неудивительно — аналоговый компаратор есть по сути однобитный АЦП и в силу этого способен заменить АЦП во многих случаях обработки аналоговых сигналов. Но контроллер тогда оказывается просто не нужен — простейший термостат вполне можно построить на одном компараторе и полудюжине рассыпных компонентов безо всякого контроллера. Однако встро- енный компаратор, во-первых, часто может заменить АЦП тогда, когда последний отсутствует (как в ATtiny2313), во-вторых, может служить в качестве входного фильтра при подсчете внешних событий — чтобы формировать надежные логиче- ские уровни из аналоговой линии с непредсказуемыми формами сигналов, и еще во многих других практических случаях. Например, с контролем аккумуляторов с целью не допустить их глубокого разряда аналоговой компаратор при аккуратном подходе к составлению схемы справляется лучше АЦП и при этом работает совер- шенно независимо от остальных компонентов контроллера. Ввиду простоты анало- гового компаратора, мы его рассмотрим сразу в практических приложениях в гла- ве 11. А сейчас остановимся на другом важнейшем компоненте МК AVR— после- довательных портах, которые служат основным каналом коммуникации МК с внешним миром. Последовательный порт Последовательными эти порты называют, потому что в них в каждый момент вре- мени передается только один бит (в некоторых случаях возможна одновременная передача и прием, но суть дела от этого не меняется). Самое главное преимущество
52 Часть I. Общие принципы устройства и функционирования AtmelAVR последовательных портов перед параллельными (когда одновременно производится обмен целыми байтами или полубайтами — тетрадами) — снижение числа соеди- нений. Но оно не единственное — как ни парадоксально, но последовательные ин- терфейсы дают значительную фору параллельным на высоких скоростях, когда на скорость передачи начинают влиять задержки в линиях. Последние невозможно сделать строго одинаковыми, и это одна из причин того, что последовательные интерфейсы в настоящее время доминируют (типичные примеры: USB вместо LPT и SCSI или Serial ATA вместо IDE в компьютерах). В микроконтроллерных устройствах с малыми объемами данных, конечно, ско- рость передачи нас волнует во вторую очередь, но вот число соединительных про- водов — очень критичный фактор. Поэтому все внешние устройства, представлен- ные в этой книге, будут иметь последовательные интерфейсы (за исключением не- которых типов дисплеев для отображения данных). Практически любой последовательный порт можно имитировать программно, ис- пользуя обычные выводы МК. Когда-то так и поступали даже в случае самого по- пулярного из таких портов — UART. Однако с тех пор МК обзавелись аппаратны- ми последовательными портами, что, впрочем, не всегда удобно: так, по глубокому убеждению автора, аппаратная реализация порта TWI (12С) в AVR далека от идеала, и в простых программах целесообразнее пользоваться программным имитатором, не привязанным притом к конкретным выводам контроллера (вопреки тому, как это делается в Arduino). Но давайте обо всем по порядку. Интерфейс UART (USART) Сначала уточним соответствующие термины. В компьютерах есть СОМ-порт (а если и нет, то его всегда можно эмулировать через USB, как мы увидим далее), часто ошибочно называемый портом RS-232. Правильно сказать так: СОМ-порт передает данные, основываясь на стандарте последовательного интерфейса RS-232 (буквы RS и означают Recommended Standard). Последний, кроме собственно про- токола передачи, стандартизирует также и электрические параметры, и даже всем знакомые 9-контактные разъемы DB-9 (рис. 3.1), как и некоторые другие (ранее еще употреблялся разъем DB-25, но теперь, кажется, не встречается вовсе), иногда еще употребляется круглый аудиоразъем с тремя контактами. Рис. 3.1. Разъем DB-9f (гнездо) — устанавливается со стороны устройства (на кабеле) В собственно стандарте RS-232 (у компьютерного СОМ-порта, например) принята отрицательная логика, т. е. логическая единица передается низким, логический ноль — высоким уровнем. Притом RS-232 имеет дело с разнополярными уровнями
Глава 3. Периферийные устройства и прерывания 53_ сигнала: от ±3 до ±12 или даже ±15 вольт (отрицательный уровень напряжения — логическая единица, положительный— логический ноль). Отрицательная логика удобна для передачи по линиям «с открытым коллектором», а разнополярные уровни с достаточно высоким, по современным представлениям, напряжением до- бавляют помехоустойчивости. Обычный RS-232, соответствующий всем требова- ниям стандарта в части электрических соединителей, способен без сбоев работать на расстояниях до 150 метров при скорости 9600 бит/с и почти до километра— на скорости 2400. С увеличением скорости расстояние безошибочной передачи резко падает: при 19 200 бит/с — это уже только 15 метров, потому и принято использо- вать величину 9600, как наиболее удобную со всех точек зрения. Подробности На практике для UART единицу скорости обмена принято именовать бод (baud— по имени французского телеграфиста Эмиля Бодо). В случае UART и RS-232 эта едини- ца совпадает с числом передаваемых битов в секунду (включая в расчет все переда- ваемые биты, в том числе служебные: стартовый и стоповый). Но в общем случае это не так — боды означают число переданных «символов» (посылок) в секунду, каждый из которых в случае модемов может нести от 1 до 16 битов. Чтобы не заплутать в этих терминологических дебрях, автор предпочитает использовать менее подверженный разночтениям термин бит в секунду (бит/с, bps). При вычислении информационной скорости, измеряемой в байтах в секунду (Bps), нужно учитывать, что байт здесь как бы увеличился на два служебных бита, потому битовую скорость надо делить не на 8, а на 10, — 9600 бит/с эквивалентно величине 9600/10 = 960 байт/с. Под названием UART (Universal Asynchronous Receiver-Transmitter, универсальный асинхронный приемопередатчик) скрывается основная часть любого устройства, поддерживающего RS-232, и не только его (недаром он «универсальный») — на- пример, стандарты RS-485 и RS-422 также реализовываются через UART, посколь- ку они отличаются от RS-232 только электрическими параметрами и допустимыми скоростями, а не общей логикой построения. Через UART также организуется связь внешних устройств с компьютерами по интерфейсу USB (как, например, это сдела- но в Arduino). RS-232 возник еще в первое десятилетие существования микросхем, в конце 1960-х, и за полвека существования этот стандарт, как и его основа — UART, были отпо- лированы до мелочей. Поэтому сейчас это самый простой и надежный способ орга- низации связи между устройствами на различных расстояниях— от обмена дан- ными между контроллерами в пределах устройства до телеметрии на километровых расстояниях по радиоканалу. Его единственный крупный недостаток— то, что RS-232 в принципе предназначен только для связи «точка-точка» (т. е. двух уст- ройств между собой), — можно преодолеть различными способами. Но стандартов на этот счет не существует, и этот факт, наряду с невысокой скоростью передачи, стал главной причиной замены в компьютерных устройствах СОМ-порта на USB, произошедшей еще в конце прошлого века (стандарт РС-99). Тем не менее и ныне при связи внешних устройств с компьютерами через USB в системе возникает виртуальный СОМ-порт, общение с которым в дальнейшем происходит по старинке, как будто в вашем ноутбуке установлен настоящий разъем DB-9. Другую категорию представляют медиаустройства вроде USB-накопителей,
54 Часть I. Общие принципы устройства и функционирования AtmelAVR флеш-карточек или веб-камер, где решающую роль играет скорость соединения, поэтому там обмен информацией происходит по протоколам, отличным от архаич- ного СОМ-порта. Подробности Термин USART (Universal Synchronous-Asynchronous Receiver-Transmitter) — следст- вие в общем-то довольно эклектичной попытки улучшить традиционный UART, как в сто- рону синхронизации приема-передачи отдельных битов, так и в сторону мультикон- троллерного обмена. Алгоритмы приема-передачи при этом усложняются, простота использования пропадает (появляются всяческие master-slave, занимается дополни- тельный вывод и т. д.), а выигрыш не настолько велик, чтобы сопутствующая возня имела смысл. Возможно, есть какие-то применения, где без дополнительных функций USART не обойтись, но на практике в таких случаях проще использовать специально для того предназначенные интерфейс I2C (TWI) или куда более скоростной SPI, чем возиться с синхронизацией не приспособленного для этого UART. Именно поэтому далее в этой книге мы станем использовать преимущественно аб- бревиатуру UART, a USART будет возникать только изредка — как напоминание о том, что некоторые возможности этого последовательного порта при работе в обычном асинхронном режиме расширены по сравнению с каноническим UART. Собственно UART позволяет общаться между собой только двум контроллерам с одинаковым питанием 3,3 или 5 вольт. Для того чтобы UART превратить в кон- кретный порт: в СОМ-порт для общения с ПК, в интерфейс RS-485 для промыш- ленных устройств, наконец, в USB (включая и Serial-порт Arduino) — нужны спе- циальные адаптеры. Они давно стандартизированы и выпускаются в больших количествах: и в виде отдельных микросхем, и в виде готовых адаптеров на раз- личной основе. Самый простой из них — адаптер RS-232, т. к. в обычных примене- ниях для превращения UART в СОМ-порт достаточно лишь двух линий: приема RxD и передачи TxD — с преобразованием логических уровней. В стандарт RS-232 входит еще много всяких разных линий, необходимых для кор- ректной организации связи, например, с модемами. В UART дополнительные ли- нии RS-232 не используются, и при необходимости могут быть организованы с по- мощью других выводов контроллера. Эти линии иногда выручают, заменяя отсут- ствующие в разъеме RS-232 контакты питания (в первую очередь отрицательного, необходимого для нормального функционирования RS-232), — во всех компьюте- рах традиционно имеется линия питания -12 В, но на внешние разъемы она не вы- водится. Именно от этих дополнительных линий когда-то питались компьютерные мыши, подключавшиеся к СОМ-порту. Для нас имеет значение существование в стандарте UART линий RTS и DTR— одна из них посылает сигнал Reset при программировании Arduino по последовательному каналу. Для основных наших нужд эти линии не потребуются, поэтому адаптер нам подойдет любой, в том числе и простейший самодельный, но иметь в виду эти нюансы нужно (подробнее о раз- новидностях адаптеров рассказано в главах 4 и 75). Рассмотрим подробнее, как, собственно, происходит обмен. Идея интерфейса RS-232 заключается в передаче целого байта по одному проводу в виде последова- тельных импульсов, каждый из которых может находиться в состоянии 0 или 1. Если в определенные моменты времени считывать состояние линии, то можно вос- становить то, что было послано.
Глава 3. Периферийные устройства и прерывания 55_ Однако эта простая идея натыкается на определенные трудности. Для приемника и передатчика, связанных между собой двумя проводами (два сигнальных провода: «туда» — TxD и «обратно» — RxD), приходится задавать скорость передачи и приема, которая должна быть одинакова для устройств на обоих концах линии. Число передаваемых/принимаемых битов в секунду носит название битрейта (bitrate). Как мы уже говорили, в нашем случае биты в секунду совпадают с бода- ми — числом посылок в секунду, поэтому нередко скорость передачи указывают именно в бодах (в Arduino это общепринятая практика), хотя, строго говоря, это неправильно. Примечание В обозначениях интерфейсов (двухпроводный, трехпроводный и т. п.) принято считать только сигнальные линии. Но не следует забывать, что в большинстве случаев, по крайней мере, шины заземления устройств также должны соединяться, поэтому фор- мально двухпроводный UART предполагает как минимум три соединительных прово- да, трехпроводный SPI — четыре и т. д. Вместе с тем есть и «настоящие» двухпро- водные интерфейсы — к ним относится, скажем, RS-485 (с некоторыми нюансами), модемная линия или коаксиальный Ethernet. Однако установления нужной скорости передачи еще недостаточно. Проблема со- стоит в том, что приемник и передатчик — это физически совершенно разные сис- темы, и скорости эти для них не могут быть строго одинаковыми в принципе (хотя бы из-за разброса параметров тактовых генераторов), и даже если их каким-то образом синхронизировать в начале, то они в любом случае быстро «разъедутся». Чтобы избежать этого, в RS-232 передача каждого байта сопровождается старто- вым битом, который служит для начальной синхронизации. После него идут восемь (или девять — если задана проверка на четность, а также в некоторых других слу- чаях) информационных битов, а затем стоповые биты, которых может быть один, два и более (в большинстве случаев это не имеет принципиального значения, пото- му что в паузах между импульсами линия находится обычно в состоянии непре- рывного стопового бита). Общая диаграмма передачи таких последовательностей показана на рис. 3.2. Здесь она приведена в обычных логических уровнях UART, т. е. в положительной логике с логическими уровнями 0—5 вольт или 0-3 вольта, в зависимости от питания. Хит- рость заключается в том, что состояния линии при передаче стартового и стопового битов имеют разные уровни: стартовый бит передается низким уровнем напряже- ния (логическим нулем), а столовый — высоким уровнем (логической единицей), потому начало стартового бита всегда распознается однозначно. В момент начала передачи стартового бита и происходит синхронизация. Приемопередатчики UART в AVR тактируются 16-кратной частотой по отношению к установленной скорости обмена. Приемник отсчитывает от фронта стартового бита несколько тактов (чтобы попасть в середину стартового бита) и три такта подряд проверяет состояние линии (оно должно быть логическим нулем). Если все три состояния совпали, то прини- мается решение, что действительно пришел стартовый бит. Тогда восемь (или девять, если это задано заранее) раз подряд с заданным периодом регистрируется состояние линии (это т. н. процедура восстановления данных). Данные в UART
56 Часть I. Общие принципы устройства и функционирования AtmelAVR всегда передаются младшими битами вперед — надписи МЗР и СЗР на рис. 3.2 как раз и означают «младший знаковый разряд» и «старший знаковый разряд». После этого линия переходит в состояние стопового бита (высокого уровня напряжения) и может в нем пребывать сколь угодно долго, пока не придет следующий старто- вый бит. Итого посылка одного байта соответствует передаче 10 битов, потому при скорости 9600 бит/с она займет 1,04 миллисекунды. 10010110 шиш оооооооо •МЗР СЗР 8 битов данных £ пауза 2 (timeout) Рис. 3.2. Диаграмма передачи данных по стандарту RS-232 в логических уровнях UART, формат кадра 8п1 Обычный формат данных, по которому работает львиная доля всех устройств, обо- значается 8nl, что читается так: 8 информационных битов, no parity, 1 столовый бит. «No parity» означает, что 9-го бита не посылается, и проверка на четность не производится. На диаграмме (см. рис. 3.2) показана передача трех вариантов: некоего произвольного кода, а также байтов, состоящих из всех единиц и из всех нулей. Стоповых битов в формате кадра может быть задано больше одного — это нужно, например, для того, чтобы приемник «знал», какой наименьший интервал времени ему нужно ожидать следующего стартового бита (как минимум, это может быть, естественно, один период частоты обмена, т. е. один столовый бит). Если по исте- чении этого времени стартовый бит не придет, приемник может регистрировать так называемый timeout, т. е. по-русски «перерыв», и посчитать это за конец передачи блока данных. Если мы не применяем подобные протоколы с временным разделе- нием блоков, нам в общем-то все равно, сколько стоповых битов будет. Из описанного алгоритма работы понятно, что погрешность несовпадения скоро- стей обмена должна быть такой, чтобы фронты не «разъезжались» за время переда- чи/приема всех десяти битов более чем на полпериода, т. е. в общем случае факти- ческая разница частот тактовых импульсов может достигать 4-5%, особенно при небольших скоростях. На практике их стараются сделать как можно ближе к стан-
Глава 3. Периферийные устройства и прерывания 57_ дартным величинам — рекомендуемое в документации отклонение составляет не более 0,2%, иначе за помехозащищенность линии фирма ответственности не несет. Существуют специальные кварцы для удобного формирования частот, кратных стандартным скоростям передачи RS-232 (например: 1,8432 МГц, 4,608 МГц и др.), которые позволяют сформировать скорости передачи с нулевой.ошибкой. Они мо- гут применяться там, где необходима исключительная стабильность и помехоза- щищенность линии. Заметим, что в большинстве случаев в AVR точность встроен- ных генераторов тактовой частоты при «умолчательной» частоте 1 МГц позволяет обеспечить более или менее нормальную работу порта UART лишь при скоростях 1200 бит/с и менее (чем выше тактовая частота, тем выше может быть и это значе- ние скорости). Но пользоваться UART в таком режиме неудобно, помехоустойчи- вость при нестабильном генераторе резко падает, потому UART мы всегда будем подключать только в режиме тактирования от внешнего кварца. UART удобен для обмена данными на сравнительно большие расстояния при связи между собой устройств разной природы. Большинство устройств в пределах одной платы или конструктива (память, датчики, внешние АЦП) имеют иные интерфейсы для обмена данными. Их мы сейчас и рассмотрим. Интерфейс SPI Идея передачи информации побитно с определенными интервалами времени лежит в основе всех последовательных интерфейсов, они различаются только способами синхронизации. В интерфейсе SPI (Serial Peripheral Interface, последовательный пе- риферийный интерфейс) синхронизирующие импульсы передают по отдельной, специально выделенной линии. Это облегчает задачу синхронизации (не требуется специально задавать скорости обмена), но требует большего числа сигнальных проводов — не менее трех (см. врезку «Примечание» в предыдущем разделе). В по- давляющем большинстве случаев необходим еще один— четвертый— провод, который присутствует всегда при подключении более чем двух микросхем к одно- му интерфейсу, но иногда требуется и при одиночном подключении. Сначала рассмотрим функции этого четвертого провода. Очевидно, что при под- ключении более чем двух устройств к одной линии, им нужно как-то «разбираться» между собой, иначе может получиться невесть что, когда два или более устройства «захотят» вдруг одновременно что-то передать. Притом непонятно, кому именно передаваемая информация предназначается. Эта проблема решается по-разному: в самом общем случае каждому устройству присваивается индивидуальный адрес — так работает интерфейс 12С (о нем будет рассказано далее в этой главе), аналогично можно устроить многоканальный обмен по UART (его расширенный вариант USART специально приспособлен для такого обмена). В других случаях адрес заменяется выводом «выбор кристалла» (Chip Select, CS — как и в случае RESET, правильно обозначать его со значком инверсии, потому что выбор производится в отрицательной логике— при подаче низкого уровня). Конечно, аппаратно реализовать этот способ проще, чем идентификацию многих битов адреса, потому он получил широкое распространение с давних времен, при-
5Q Часть I. Общие принципы устройства и функционирования AtmelAVR меняясь, например, при адресации микросхем памяти. Таким образом, мы получаем возможность разобраться, к какому именно устройству в тот или иной момент про- изводится обращение. Несколько таких выводов могут обеспечить управление не- сколькими устройствами, подсоединенными к шине данных, при этом остальные линии шины объединяются. На вывод CS могут быть «навешены» и дополнительные функции (например, перевод микросхемы в «спящее» состояние с микропотреблением). Потому даже когда соединены всего два устройства, линия CS обычно все равно требует специ- ального управления. Рассмотрим функционирование этой линии применительно к интерфейсу SPI в МК AVR подробнее. Чтобы исключить конфликты при попытке одновременной пере- дачи данных, в каждый момент времени одно из устройств выполняет функции ве- дущего (Master), а все остальные — ведомого (Slave, что означает «слуга»). Отме- тим, что в общем случае роли ведущий — ведомый в процессе работы могут ме- няться, но ведущий на линии в каждый момент времени может быть только один. Четвертый провод в аппаратном интерфейсе SPI и является аналогом «выбора кри- сталла» и носит название SS (Slave Select, выбор ведомого). Правильно обозначать его также с инверсией— SS, т. к. он во всем аналогичен описанной ранее функ- циональности вывода CS. Естественно, в большинстве случаев ведущим устройством выступает контроллер. Для ведущего состояние его входного вывода SS не меняется, и вывод можно было бы использовать для других надобностей, с одним только нюансом — при работе с аппаратным SPI следует учесть, что если его вывод SS сконфигурирован как вход (по умолчанию), то на него должен быть подан высокий уровень напряжения. При подаче низкого уровня модуль SPI автоматически переключится в режим ведомого, даже если это произойдет во время передачи данных. Таким образом, чтобы этот вывод не мешал работе, его следует либо сконфигурировать на выход (тогда его можно задействовать как обычный вывод порта), либо оставить сконфигурирован- ным на вход с подключением «подтягивающего» резистора (т. е. с записью значе- ния 1 в этот разряд). В последнем случае для нормальной работы SPI никакие сигналы на него подавать нельзя. А для управления выводом SS (CS) ведомой микросхемы можно использовать любой цифровой выход— в том числе и SS, сконфигурированный на выход. Названия остальных линий SPI знакомы каждому, кто уже имел опыт последова- тельного программирования AVR, — это информационные линии: MISO (Master In Slave Out), MOSI (Master Out Slave In) и линия для подачи тактовых импульсов SCK. Как правило, тактовые импульсы подаются ведущим (как вариант — внеш- ним источником), в режиме ведомого вывод SCK работает только на вход. В отличие от симметричного UART, где вывод TxD может только передавать дан- ные, a RxD — только принимать (поэтому у разных устройств их следует соединять перекрестно), в интерфейсе SPI одноименные выводы MISO и MOSI соединяются между собой, а направление передачи задается выбором режима ведущий— ве- домый.
Глава 3. Периферийные устройства и прерывания 59 Схема обмена данными по SPI между контроллером и внешним устройством пока- зана на рис. 3.3. Устроен этот обмен элементарно просто — как можно видеть, два 8-разрядных регистра (источника и приемника) образуют единый регистр сдвига, соединенный в кольцо линиями MISO и MOSI. С началом передачи «заводится» генератор синхроимпульсов (в общем случае он не обязательно входит в ведущее устройство, может быть и внешним), из ведущего по линии MOSI начинают «выталкиваться» биты, одновременно вытесняя из ведомого биты по линии MISO. Через восемь тактов регистры полностью обмениваются информацией между со- бой. Если после этого генератор продолжает работать, то информация так и будет «крутиться» в этом кольце. Чтобы ее обновить, нужно после каждого цикла обмена считывать принятый байт в приемнике и записывать новый в передатчике. Таким образом запись (в ведомый) и чтение (из ведомого) фактически представляют собой одну и ту же операцию: заданное направление определяет только те или иные дей- ствия, которые при этом выполняет контроллер. !ст. 1 1 \ ! разряд Master мл. разряд 8-разрядный сдвиговый регистр Генератор синхроимпульсов MISO MISO MOSI MOSI Ст. разряд Slave Мл. разряд 8-разрядный сдвиговый L SCK SCK упр. SS регистр CS i 1 Рис. 3.3. Схема передачи данных по интерфейсу SPI Заметки на полях Заметим, что как и I С (о нем рассказано далее в этой главе), название SPI является зарегистрированной торговой маркой (в данном случае — фирмы Motorola). Потому вы можете встретить в технических описаниях разные названия и самого этого интер- фейса (что еще не так страшно), и, главное, его выводов. Чаще всего можно встретить общее название Three-Wire Serial Interface, трехпроводный последовательный интер- фейс (хотя, как мы говорили, на самом деле требуется четыре провода, включая «землю», и даже пять и более, если учитывать «выбор кристалла»). Сложнее привес- ти варианты названий выводов, потому что они сильно зависят от назначения устрой- ства, и могут различаться в каждом отдельном случае. Так, например, в АЦП фирмы Analog Devices вывод SCK носит название SCLK, MISO называется DOUT (потому что АЦП всегда— ведомый), a MOSI, соответственно, DIN. Другие обозначения вам встретятся в случае, например, энергонезависимой памяти производства той же Atmel (см. главы 10 и 13), где MOSI будет просто SI, a MISO — просто SO. Название вывода SS характерно лишь для аппаратного SPI в МК AVR. В других устройствах чаще всего имеется обычный вход «выбор кристалла» CS, а иногда совместно с ним и другие управляющие выводы: например, в памяти АТ25 есть еще вывод задержки обмена HOLD, практически во всех микросхемах энергонезависимой памяти есть вывод за- прета записи WR. Во внешних АЦП иногда встречается вывод «готовности данных» (он может называться, например, DRDY). По этим причинам полный протокол обмена
j>0 Часть I. Общие принципы устройства и функционирования Atmel AVR по SPI у различных устройств может сильно различаться, хотя в его основе всегда лежит последовательный побитный сдвиг в каждом такте частоты, задаваемой «мас- тером» на выводе SCK. Существуют также различающиеся по комбинации логических уровней режимы (mode) 0, 1, 2 и 3 (см. главу 12), а также SPI с ббльшим количеством линий данных (их мы рассматривать здесь не будем). Для сигнализации об окончании цикла сдвига в аппаратном SPI MK AVR преду- смотрено отдельное прерывание и установка специального флага spif. Буфериза- ция по передаче одинарная (т. е. нельзя записать следующий байт, пока полностью не передали предыдущий), а при чтении — двойная (т. е. принятый байт можно прочесть во время обмена). При этом необходимо следить, чтобы принятая посылка была считана из регистра данных SPI прежде, чем завершится цикл сдвига новой посылки. В противном случае первый байт будет потерян. Никаких стартовых-стоповых битов в SPI не предусматривается, и если обмен не- прерывный, то выделить начало байта, чтобы синхронизировать чтение/запись, не- возможно. Поэтому обычно на время чтения генератор приостанавливают и заодно подают сигнал высокого уровня на CS (если этот вывод используется вообще), чтобы остановить ведомого и привести его в исходное состояние. По паузе в пере- даче (во время которой уровень на линии SCK может принимать выбранное в зави- симости от режима значение) можно устанавливать синхронизацию начала очеред- ного байта или кадра, если это необходимо. Другой способ выделить начало ин- формационной последовательности приводится в главе 12 применительно к обмену с картами памяти. Значения скорости обмена по SPI-интерфейсу не стандартизованы и могут дости- гать у некоторых микросхем десятков Мбит/с (так, в микросхемах памяти серии AT45DBjcxxD тактовая частота может достигать 66 МГц). Аппаратный SPI MK AVR предполагает максимальную частоту тактирования равной 74 от частоты так- тового сигнала МК. Отметим, что SPI-интерфейс последовательного программиро- вания, имеющийся во всех моделях MK AVR, — не то лее самое, что пользова- тельский SPI для обмена информацией (обратите внимание, что на схеме, показан- ной на рис. 2.1, они размещены в разных местах), и в общем случае у них могут даже не совпадать выводы. Кроме того, не все модели MK AVR обладают полно- функциональным аппаратным SPI, несмотря на то, что в них имеется возможность последовательного программирования по этому интерфейсу. В младших моделях семейства Tiny с объемом памяти 1 кбайт SPI вообще отсутствует, а в других моде- лях (вроде ATtiny2313) он может быть реализован только через интерфейс USI (о котором рассказано в этой главе далее). Существуют, разумеется, и программные методы имитации SPI (в частности, они описаны в Application Notes AVR320). Более подробно об обращении с SPI рассказано в главе 12. SPI, как правило, служит для высокоскоростного обмена между микросхемами в пределах одной платы или, как максимум, одного устройства. Обмен данными со многими типами карточек памяти, включая и наиболее распространенные в на- стоящее время карточки Secure Digital (SD), есть также просто вариант SPI. Кроме собственно SPI, в различных микросхемах можно встретить его модификацию под названием интерфейс Microwire (3-wire). Он отличается тем, что работает сразу
Глава 3. Периферийные устройства и прерывания 61_ с 16-разрядными словами, и применяется в тех случаях, когда нужно передавать за один прием большое количество данных, — например, при управлении многораз- рядными дисплеями. Для более удаленных компонентов (датчиков, дисплеев, кла- виатур и т. п.) чаще используется более простой в схемотехнической реализации (но более сложный в программном отношении) интерфейс TWI, или, иначе, 12С. Интерфейс TWI (12С) Аббревиатурой TWI (Two-Wire Interface, двухпроводной интерфейс) компания Atmel в надежде, видимо, избежать патентных разборок, назвала свою реализацию последовательной шины данных 12С, разработанную фирмой Philips еще в начале 1980-х. Не знаю, насколько это получилось, но с 1 октября 2006 года лицензионные отчисления за использование протокола PC все равно отменены (остались только отчисления за выделение эксклюзивного адреса на шине PC, но непосредственно к МК это не относится — там адрес выделяется программистом каждый раз зано- во), так что можно называть вещи своими именами. Иногда в документации на раз- личные датчики можно встретить и название 2-Ware (или Two Ware) — обычно под этим подразумевается также 12С или его модификации. Интерфейс 12С, как и UART, требует двухпроводного соединения с обязательным объединением «земель», т. к. сигналы в нем абсолютные, а не дифференциальные, отсчитываются относительно «земли» и соответствуют уровням КМОП-логики. Как и в SPI, в интерфейсе 12С устройства могут работать в режиме «ведущий» (Master) или «ведомый» (Slave). В отличие от большинства других интерфейсов, ведомые устройства с интерфейсом 12С (память, часы реального времени, различ- ные датчики) должны иметь индивидуальный адрес, присваиваемый производите- лем (тот самый, за который платятся отчисления). Для различения одинаковых уст- ройств, если их более двух на одной линии, в некоторых типах устройств имеются дополнительные адресные линии, выводы для установки индивидуального адреса или входы типа «выбор кристалла». 12С значительно более медленный интерфейс, чем SPI, сравним в этом отношении с UART (типовое значение скорости обмена— 100 кбит/с) и потому применяется там, где не требуется скоростной передачи данных. Но он гораздо удобнее UART, т. к. позволяет без дополнительных проблем подключать большое количество внешних устройств, занимая при этом одни и те же два вывода контроллера. В об- щем-то с помощью этого интерфейса можно подключать и достаточно удаленные устройства, если не нужна высокая помехозащищенность. Есть и близкий родственник 12С под названием 1 -Ware, позволяющий осуществлять двухстороннюю связь по одной линии (причем она же обычно является и шиной питания). Интересно, что существуют устройства (например, датчики температуры фирмы Dallas Semiconductor, в настоящее время являющейся подразделением фир- мы Maxim), которые способны работать вовсе без питания, получая его от сигналь- ных линий интерфейса 12С или 1-Wire. Более подробно о работе с интерфейсом 12С рассказывается в главе 13.
Ъ2_ Часть I. Общие принципы устройства и функционирования AtmelAVR Универсальный последовательный интерфейс USI В AVR семейства Tiny, за исключением некоторых старших моделей, UART (или USART), SPI и TWI вообще отсутствуют. Вместо них предлагается интерфейс USI (Universal Serial Interface), который представляет собой по сути «голый» сдвиговый регистр (usidr) с регистром управления (usicr) и статуса (usisr) без каких-либо буферов данных. С USI связано аппаратное прерывание. В состав USI входит 4-битовый счетчик тактовых импульсов, управляющийся от регистра usisr. USI можно использовать и в качестве трехпроводного SPI, и в качестве двухпроводного TWI, и для имитации UART, и еще для ряда применений — например, тактовый счетчик совместно с TimerO может образовывать 12-разрядный таймер, с помощью USI также можно организовать дополнительное внешнее прерывание и т. п. Управ- ляется сдвиг в регистре usidr либо «вручную» (пользовательской программой), ли- бо от прерывания переполнения TimerO, либо от внешнего источника (что допуска- ет функционирование устройства с USI в качестве ведомого). С USI связаны три внешних вывода: вход и выход данных (DI, DO) и ввод/вывод тактовых импульсов (USCK). USI так и не получил широкого признания, и к тому же в большинстве моделей, включая все семейство Mega, он вообще отсутствует, поэтому этот интерфейс мы в книге не рассматриваем, — на практике проще выбрать контроллер с наличием нужного порта или выполнить имитацию программным путем. Если хотите попро- бовать USI для какого-нибудь экзотического применения, то в Tiny2313, например, нет ни SPI, ни TWI, зато есть и USART, и USI. Прерывания Возможность реагировать на события и аппаратно прерывать выполнение основной программы — важнейшая функция современных микроконтроллеров. Без нее МК никогда бы не заслужили звания «интеллектуальных устройств» и не вышли бы за уровень «навороченной» заготовки для создания сложных логических схем. На- помним, что всю деятельность любого компьютерного устройства — от миниатюр- ного смартфона и компьютерных «мозгов» автомобиля до мощной системы для обработки графики или управления сложным технологическим процессом — мож- но представить в виде разветвленной системы реакций на различные события, которую осуществляет аппаратная часть совместно с операционной системой. Через обработчики нерегулярно возникающих событий (читай— через прерыва- ния) реализуется 99% функциональности всего компьютерного устройства, и лишь небольшая ее часть представляет собой «плановое» выполнение линейного кода: в основном в начале и в конце текущего сеанса работы. Остальное время любая система проводит в ожидании событий, на которые нужно реагировать. В архитектуре IBM PC аппаратные прерывания осуществляет специальный кон- троллер прерываний, в МК же, в полном соответствии с концепцией «computer-on- chip», эта функция является встроенной. Любое из периферийных устройств может вызывать свое прерывание, а чаще всего и не одно. В этом разделе мы рассмотрим
Глава 3. Периферийные устройства и прерывания 63_ общие свойства прерываний МК AVR и различные варианты общей идеологии их использования. Событиями, на которые нужно реагировать, могут быть сигналы от других уст- ройств, как посылаемые автоматически, так и обусловленные действиями пользо- вателя. Могут такие сигналы возникать и внутри контроллера, как следствие рабо- ты его внутренних узлов. Как можно обнаружить и обработать такие события? Наиболее универсальный способ, который применяется в системах всех уров- ней^ — организация основной программы в виде бесконечного цикла. Внутри этого цикла тем или иным способом отслеживается возникновение неких событий. Такой цикл, например, представляет собой почти любая Windows-программа (как и Windows в целом). Но организация реакции на события внутри такого цикла в Windows — отдельная и весьма сложная тема, а в МК все может быть сделано просто и прозрачно: при наступлении события устанавливается определенное зна- чение некоей переменной, часто именуемой флагом (в простейшем случае это мо- жет быть один бит в специально отведенном регистре или в ячейке памяти или да- же просто состояние вывода МК, задаваемое извне — например, нажатием кнопки). Основная программа в цикле последовательно проверяет значение всех необходи- мых флагов, регистров или выводов и при изменении какого-либо из них переходит к соответствующей процедуре (подпрограмме) обработки события. Раньше, когда еще никаких встроенных прерываний в микропроцессорах не было, только так всегда и поступали (пример такой простейшей программы приведен в главе 6). Ориентирует на подобную организацию работы и Arduino (по крайней мере, на начальном этапе). Логика такой линейно выполняющейся программы по- нятна даже полному чайнику, кроме того, она проста в отладке, хоть элементарны- ми методами, хоть с применением специальных отладочных сред. В качестве альтернативы такому «ручному» способу отслеживания событий и были придуманы прерывания (interrupts). «Ручной» способ во многих отношениях неудо- бен: во-первых, события могут требовать безотлагательного внимания, а программа бывает при этом занята «своими делами». Во-вторых, при таком подходе некое со- бытие нетрудно попросту потерять — во время обработки очередного из них про- грамма выходит из цикла отслеживания, и до возврата в него может пройти доста- точно много времени, когда условия для наступления следующего события уже ис- чезнут. Если при этом первое по порядку отслеживания событие происходит весьма часто и требует много времени на обработку, то не исключена ситуация, когда ко всем остальным программа попросту не успеет перейти или будет их отслеживать выборочно, — многим пользователям Windows знакома такая ситуация, когда не- кая программа, выполняя громоздкие процедуры вроде записи больших массивов информации на флешку, просто «вешает» все остальные программы, не давая сис- теме даже отслеживать перемещение мыши. Использование аппаратных прерываний позволяет полностью избежать подобных ситуаций, если нагрузка на МК не превышает его возможностей, и сгладить по- следствия в случае, когда в отдельные моменты времени нагрузка на МК его воз- можности все-таки превысила. По сути аппаратные прерывания организуются так же, как и «ручное» обнаружение, — в цикле: при возникновении условий для пре-
64 Часть I. Общие принципы устройства и функционирования AtmelAVR рывания автоматически устанавливаются некие биты-флаги, сигнализирующие о наступлении определенного события. Но если в первом случае отслеживание зна- чений этих флагов и реагирование на событие возлагается на программиста, то при возникновении аппаратного прерывания переход к его обработке осуществляется автоматически, — программисту надо лишь установить условия для возникновения нужного прерывания и написать правильно оформленную процедуру обработки. Во время обработки каждого прерывания другие прерывания произойти не могут (если вы это специально не разрешите— о чем рассказано далее), но установленный автоматически флаг никуда не денется, и отложенное прерывание произойдет по окончании обработки предыдущего. Порядок выполнения прерываний Для надлежащего управления процессом возникновения прерываний флаги разре- шения прерываний образуют иерархию. Во главе этой структуры стоит бит i реги- стра флагов sreg, который разрешает (если установлен в логическую единицу) или запрещает (если установлен в логический ноль) аппаратные прерывания вовсе. Как правило, непосредственно с этим битом (как и вообще с регистром sreg) програм- мисту дело можно не иметь — для общего разрешения (запрещения) прерываний предусмотрены специальные команды: sei (разрешить) и cli (запретить), устанав- ливающие этот бит в нужное состояние (аббревиатуры sei/cii и означают set I/clear I). Отметим, что по умолчанию бит i регистра флагов sreg сброшен, т. е. прерывания при запуске МК запрещены. Для того чтобы их разрешить, необходимо в процедуре начальной инициализации, выполняющейся по сбросу МК, разместить команду sei. Кроме общего флага прерываний, для каждого конкретного прерывания имеется свой разрешающий/запрещающий бит, расположенный в соответствующем регист- ре (например, для таймеров — это регистры timsk или etimsk, для внешних преры- ваний— gimsk, в более поздних моделях зачем-то переименованный в gicr1, и т. п.). При использовании прерываний эти биты необходимо устанавливать в со- стояние логической единицы, в противном случае автоматического вызова преры- ваний не произойдет. Если во время обработки прерывания произошли еще какие-то события, вызываю- щие прерывания, то их флаги окажутся установленными, и процедуры их обработ- ки начнут выполняться незамедлительно (точнее, после выполнения одной коман- ды основной программы, — о чем рассказано далее) в том порядке, в каком они расположены в таблице векторов прерываний. Программист может нарушать этот порядок, разрешая вложенные прерывания, но делать это без крайней нужды не рекомендуется, — такая операция требует учета большого числа неочевидных фак- торов (некоторые примеры приведены далее) и намного усложняет отладку про- 1 В файлах макроопределений (*.inc) часто стоит и то, и другое, потому в этой книге я буду пользо- ваться преимущественно старым обозначением gimsk, которое работает для всех «наших» моделей одинаково. Если в каких-то моделях это название будет недействительно, то ассемблер укажет на ошибку.
Глава 3. Периферийные устройства и прерывания 65^ граммы. Подробнее обработку прерываний мы рассмотрим в главе 6 — в сравнении с выполнением обычных подпрограмм. Разновидности прерываний Всего в различных моделях AVR существует от 6—8 (младшие Tiny) до нескольких десятков прерываний (например, в ATmega2560 их 57). Как и в ПК, прерывания в микроконтроллерах бывают двух видов. Но если в ПК прерывания делятся на аппаратные (например, от таймера или клавиатуры) и программные (фактически не прерывания, а подпрограммы, записанные в BIOS, — с распространением Windows это понятие почти исчезло из программистской практики), то в МК, естественно, все прерывания аппаратные, а делятся они на внутренние и внешние. Внутренние прерывания могут возникать от любого устройства, которое является дополнительным по отношению к ядру системы: таймеров, аналогового компаратора, последовательного порта и т. п. Внутреннее прерывание — это событие, которое возникает в системе и прерывает выполнение основной программы. Внутренних прерываний в AVR довольно много, и в совокупности они служат для взаимодейст- вия устройств с ядром системы, — мы еще к этому вопросу будем неоднократно возвращаться при рассмотрении конкретных устройств. Отдельной главы для рассмотрения внешних прерываний в этой книге не преду- смотрено, потому что они почти всегда играют вспомогательную роль для выпол- нения каких-либо других функций. Вы встретитесь с внешним прерыванием уже в главе 6 при рассмотрении простейших программ взаимодействия с контроллером, и в дальнейшем мы еще не раз будем о них говорить. А сейчас рассмотрим основ- ные особенности внешних прерываний. Основных внешних прерываний у большинства МК AVR два или три. В старших Mega их может быть и больше — например, у ATmega2560 (основа для Arduino Mega) их целых восемь. Основные внешние прерывания могут возникать незави- симо друг от друга. Кроме основных, в ряде контроллеров, в том числе в основном «ардуновском» ATmega328, имеются дополнительные внешние прерывания типа pcint (Pin Change Interrupt, прерывания по изменению уровня на выводе), поэтому суммарное число их оказывается равно 24. Итак, внешнее прерывание — это событие, которое наступает при наличии сигнала на одном из входов, специально предназначенных для этого (например, into и inti). Выделяют четыре вида событий, вызывающих такое прерывание: это может быть низкий уровень напряжения, любой перепад (фронт) напряжения, а также от- дельно положительный или отрицательный фронт на соответствующем выводе. Любопытно, что прерывания по всем этим событиям выполняются, даже если соот- ветствующий вывод порта сконфигурирован на выход (это, в частности, позволяет вызывать такие прерывания программно). Кратко рассмотрим особенности этих режимов. Прерывание по низкому уровню (режим установлен по умолчанию, для его инициализации достаточно разрешить соответствующее прерывание) возникает всякий раз, когда на соответствующем входе присутствует низкий уровень. «Всякий раз» — это значит, что действительно
(№ Часть I. Общие принципы устройства и функционирования AtmelAVR всякий, т. е. если отрицательный импульс длится какое-то время, то прерывание, закончившись (когда выполнится соответствующий обработчик), повторится снова и снова, почти не давая основной программе работать. Поэтому обычная схема использования этого режима — сразу же по возникновении в начале обработчика запретить это прерывание (процедура обработки при этом, раз уж началась, выпол- нится до конца) и разрешить опять только тогда, когда внешнее воздействие долж- но уже закончиться, — например, если это нажатие кнопки, то его следует опять разрешить по таймеру через одну-две секунды. В отличие от прерываний по низкому уровню, прерывания по фронту или спаду выполняются один раз на каждый импульс. Конечно, от дребезга контактов здесь никакой защиты нет и быть не может, потому что МК не способен отличить дре- безг от серии коротких импульсов. Если это критично, нужно либо принимать внешние меры по защите от дребезга, либо прибегнуть к тому же способу, что и для прерывания по уровню: внутри процедуры обработчика прерывания первой командой запретить само прерывание, а через некоторое время в другой процедуре (например, по таймеру или по иному событию) опять его разрешить. Но если по нажатию кнопки просто какая-то переменная или вывод порта устанавливается в некое состояние (зажигает светодиод, например), то ничего страшного не случит- ся, если оно установится несколько раз подряд, — только следует учесть, что по- следний раз это может произойти с отпусканием кнопки. У внимательного читателя возникает законный вопрос — а зачем вообще нужен режим внешнего прерывания по уровню, которое специально еще требуется снача- ла запрещать, а потом разрешать? Дело в том, что оно во всех моделях выполняется асинхронно — в тот момент, когда низкий уровень появился на выводе МК (точно так же асинхронно, кстати, выполняются и прерывания pcint, — о чем рассказано далее). Конечно, прерывание можно обнаружить только по окончании текущей команды, так что очень короткие импульсы могут пропасть. Но в режиме управле- ния по фронту у большинства моделей прерывания into и inti определяются на- оборот, только синхронно — в момент перепада уровней тактового сигнала кон- троллера (поэтому их длительность не должна быть короче одного периода такто- вого сигнала). И по большому счету разницы в этих режимах никакой бы не было, если не учесть то обстоятельство, что синхронный режим требует непременно наличия этого самого тактового сигнала. Потому асинхронное внешнее прерывание может «раз- будить» контроллер, находящийся в одном из режимов «глубокого» энергосбере- жения, когда тактовый генератор не работает, а синхронное — нет. Старые МК се- мейства Classic вывести из глубокого «сна» путем внешнего прерывания (напри- мер, нажатием кнопки) можно было только внешним прерыванием по уровню, что не всегда удобно. У большинства же моделей семейства Mega (из младших моделей — кроме ATmega8), имеется еще одно внешнее прерывание — int2, которое, в отличие от into и inti, происходит только по фронтам и только асинхронно. У некоторых старших моде- лей Mega (ATmega64/128, ATmega 164/324/644, ATmega640-2160 и др.) асинхронно выполняются все прерывания во всех режимах. Это значительно повышает удобст- во пользования семейством Mega в режимах энергосбережения.
Глава 3. Периферийные устройства и прерывания 67 Дополнительные прерывания pcint возникают при появлении любого фронта им- пульса на входах портов, причем асинхронно. Прерывания типа pcint, ввиду их многочисленности, обычно объединены в небольшое количество групп, каждая из которых связана со своим вектором (до восьми выводов на один вектор). Например, в ATtiny828 в отдельном регистре pcicr отведено четыре бита (pcieo-з), устанавли- вающихся каждый тогда, когда наступило событие на одной из четырех групп вы- водов (PCINTO-7, PCINT8-15, PCINT16-23, или PCINT24-27). При этом возникает одно из прерываний: pcinto, pcinti, pcint2, pcint3. Выделять, на каком именно выводе (выводах) перехвачено прерывание, необходимо заранее, для чего служит регистр pcmsk. При этом прерывание типа pcint наступает при любом переключе- нии уровня (и из нуля в единицу, и обратно), и, в отлитие от обычных внешних прерываний intx, управлять этим процессом невозможно. Зато их также можно ис- пользовать для вывода контроллера из «сна» гораздо более удобным способом, чем с помощью прерываний into и inti по уровню. Из «наших» моделей контроллеров INT2 доступен в ATmega8535, а дополнитель- ные прерывания типа pcint— в ATtiny2313. Примеры использования этих преры- ваний приведены в главе 14. Об общих принципах использования прерываний При переносе основной функциональности программы в прерывания в ряде случа- ев основная программа может состоять только из единственной процедуры беско- нечного пустого цикла: cykie: r jmp cycle (что равносильно известному оператору while (1) в языке С). Все остальное (в том числе начальная инициализация по сбро- су) будет осуществляться через прерывания. Поясним подробнее, чем такая концепция лучше и в каких случаях наступают ее ограничения. Во-первых, потери событий оказываются намного менее вероятными. Прерывания могут потеряться только в том единственном случае, когда контроллер начинает захлебываться событиями, идущими одно за другим с временными про- межутками менее длительности обработки. Причем, если у вас пришло два подряд прерывания, то с ними ничего не случится при любом промежутке времени между ними (разве что при промежутке менее периода тактовой частоты некоторые собы- тия могут просто остаться незамеченными). Второе прерывание, как мы говорили, запоминается в состоянии своего флага и будет обработано, можно сказать, «свое- временно или несколько позже». Критичный случай наступает, когда с минималь- ным промежутком следуют подряд три одинаковых события или больше — вот то- гда промежуточные события действительно потеряются. Рассмотрим еще подробнее условия возникновения такой ситуации. Критичные промежутки между событиями при обычных тактовых частотах составят менее единиц-десятков микросекунд. Это цифра увеличивается до миллисекунд в случае, если задействовать в прерываниях медленные процедуры (ожидание отправки по UART, обмен по 12С, запись в EEPROM и т. п.), но и такой промежуток совершенно не является запредельно большим в обычных случаях использования контроллера. Если нужно обработать несколько событий, происходящих подряд друг за другом,
(№ Часть I. Общие принципы устройства и функционирования Atmel AVR то их и правда лучше принять с минимальными задержками, сложить в память дан- ных полученные значения и.потом спокойно обработать, но это не очень частая за- дача, которую спокойно можно выделить в частный случай. Иное дело, когда есть непрерывный поток таких событий — если действительно надо непрерывно обра- батывать события с частотами порядка сотен килогерц-мегагерц, то просто следует признать, что 8-битные AVR тут непригодны, и выбрать платформу пошустрее. Во-вторых, программы, состоящие из законченных подпрограмм, логика выполне- ния которых не размазана по всему коду, легче составляются и отлаживаются. В непрерывных прыжках от одного потока команд к другому легко запутаться, и это является одной из главных причин возникновения трудно отслеживаемых ошибок времени выполнения у новичков. Да, программу, состоящую в основном из обработчиков прерываний, отлаживать с помощью программных эмуляторов (Atmel Studio или, например, Proteus) более трудоемко, чем линейный код в глав- ном цикле, что сидит у многих привычных к этим инструментам программистов в подкорке и бессознательно заставляет их по возможности избегать прерываний. Помещение в прерывание логически законченного кода позволяет значительно об- легчить эту процедуру: обработчик просто отлаживается как отдельная программа, которая затем подключается к основному коду как готовый модуль. Конечно, такая идеализированная картина достижима далеко не всегда, но стремиться к ней вполне возможно. Разумеется, мы не будем считать такой подход за догму и стремиться непременно все выполнить по прерываниям. Все зависит от удобства и конкретной ситуации — есть вполне штатные функции, которые положено выполнять именно по прерыва- ниям, а мы, тем не менее, от этого откажемся, и ничего, как увидите, не потеряем. Но в целом наша концепция будет стремиться к переносу функциональности в пре- рывания с минимализацией действий в основном цикле — как вы увидите далее, он частенько будет оставаться пустым. Заметки на полях Нередко можно услышать мнения, противоречащие этой концепции, — чаще всего утвер- ждается, что в обработчике прерывания программа должна совершить минимум необ- ходимых действий и как можно быстрее выйти обратно в основной цикл. Эта точка зрения распространилась не без влияния автора [2] и абсолютно справедлива для тех, кто работает с многозадачными операционными системами для контролеров, — вы бы, конечно, тоже не захотели, чтобы запись на какую-нибудь флешку повесила вам все остальные процессы в Windows. К сожалению, читатели из целевой аудито- рии не смогли отнестись к ней критически и осознать границы ее применимости — ав- тор [2], благодаря большому опыту работы на самых разных системах, всегда в пер- вую очередь принимает во внимание условие переносимости кода, а семейства кон- троллеров все разные, и в некоторых системах действительно полагаться полностью на прерывания было бы крупной ошибкой. Но в частных случаях одного только 8-бит- ного AVR, без применения каких-то ОС и притом с ограниченным числом используе- мых моделей, эти соображения практически теряют свое значение. В реальности, если говорить о конкретно AVR-контроллерах, преимущественный пе- ревод кода в обработчики прерываний в большинстве случаев будет только благом. Как выразился в обсуждении этого вопроса сам автор [2], «по сути дела, это бесплат- ная многозадачность». Следует только подходить к этому вопросу разумно, и ради одной только идеи не перебарщивать ни в ту ни в другую сторону.
ГЛАВА 4 Микроконтроллеры AVR на практике В дальнейшем повествовании мы не будем пытаться объять необъятное и терять время на обсуждение всевозможных нюансов практического применения всех представителей многочисленного семейства AVR. Конечно, я буду стараться по возможности представить дело в широком контексте, чтобы вам при необходимо- сти было легче осваивать другие модели, но в основном мы сосредоточимся на не- многих самых удобных и простых в программировании — пусть они и не самые современные, но в них есть все, что нужно, и даже еще много сверх того. В преды- дущих главах такие модели упоминались под названием «наших» контроллеров: это ATmega8, ATmega8535 и ATtiny2313. В конце этой главы мы подытожим их основные характеристики. Но сначала еще уточним некоторые общие вопросы их применения на практике и разберемся с необходимыми для этого инструментами и оборудованием. Сразу укажем, что работу с любой моделью AVR надо начинать со скачивания ее «даташита» с официального сайта Microchip. Документация на конкретную разно- видность МК потребуется, как минимум, для того, чтобы картинка разводки его выводов была всегда перед глазами — обращаться к ней придется на каждом шагу. В известной степени официальную англоязычную документацию могут заменить упоминавшиеся уже книги Евстифеева [6,7] — по крайней мере для тех контролле- ров, которые в них представлены. С одним только нюансом: в них вы на русском языке найдете информацию по устройству и функциям базовых моделей, а харак- теристики, касающиеся потребления, рабочих частот и тонкостей работы в различ- ных режимах могут для контроллеров разных модификаций (с отличающимися буквами) существенно различаться. Потому в любом случае надо иметь официаль- ное описание именно той модификации, которая у вас имеется. Особенности практического использования МК AVR При использовании AVR возникает ряд вопросов практического характера, игно- рирование которых может иногда привести к порче самой микросхемы, к неработо- способности или сбоям устройства. Подобные проблемы мы еще неоднократно
70 Часть I. Общие принципы устройства и функционирования Atmel AVR будем рассматривать в соответствующих главах применительно к конкретным узлам, здесь же остановимся на некоторых общих вопросах подключения МК AVR. Начнем с выводов питания. У семейства Tiny таких выводов обычно два: VCC и GND — и подключение их не вызывает вопросов. У Mega из-за наличия аналого- цифрового преобразователя выводов питания больше: добавляется еще аналоговое питание AVCC и как минимум еще один «земляной» вывод GND. В Сети гуляет заблуждение, первоисточником которого, видимо, следует считать первые издания справочника Евстифеева: дополнительный вывод «земли», находящийся вблизи аналогового питания AVCC, по аналогии именуют «аналоговой землей» — AGND. На самом деле термин «аналоговая земля», встречающийся в описаниях AVR, от- носится не к контроллеру, а к аналоговой части остальной схемы. И никакой раз- ницы во всех «земляных» выводах (а у многовыводных AVR в планарных корпусах выводов GND может быть много) нет — все они соединены внутри кристалла, и в цифровых функциях любой из них (или все вместе) можно использовать в качестве общего провода схемы (в изданиях [6,7] эта ошибка почти везде исправлена). За- метьте, что вывод питания AVCC необходимо подключать только в том случае, если вы используете АЦП, иначе его можно оставлять без внимания. Подробности Однако есть-таки один нюанс — если схема имеет аналоговую часть, которая подклю- чается к АЦП, то общий провод этой схемы (аналоговую «землю» платы) следует под- ключать именно к тому выводу GND, который размещается вблизи AVCC. Во-первых, потому, что так банально удобнее (для того и выводили отдельную «землю» рядом с аналоговым питанием, чтобы не тащить проводники через всю плату), во-вторых, этот вывод находится рядом с узлом АЦП на кристалле, и на этот узел будет меньше попадать помех по внутренней шине питания. Иными словами, в особом наименова- нии этого вывода — AGND — имеется определенный смысл, но еще раз повторим, что он возникает только при наличии аналоговых подключений в АЦП, а в остальном все «земли» равноправны. Микросхемы AVR, как и всякая КМОП-логика, благодаря высокому порогу сраба- тывания эффективно защищены от помех по шине «земли». Однако они ведут себя гораздо хуже при помехах по шине питания. Поэтому не забывайте о развязываю- щих конденсаторах, которые нужно устанавливать непосредственно у выводов питания (керамические 0,1-1 мкФ), а также про качество сетевых выпрямителей и стабилизаторов. Собственно, все то же самое рекомендуется и для Arduino, где часть этих требований уже выполнена изготовителем при проектировании плат. У выводов портов МК при работе в качестве входных линий имеется внутренний подключаемый «подтягивающий» резистор (pullup, т. е. подсоединенный к шине питания). Это, казалось бы, решает одну из обычных схемотехнических проблем, когда наличие такого резистора требуется для подключения двухвыводных кнопок или выходов с «открытым коллектором». Однако, как мы уже говорили ранее (см. главу 5), в большинстве случаев надежность и помехоустойчивость схемы заметно повысится, если использовать внешний резистор сопротивлением 1-10 кОм (в кри- тичных для потребления случаях его можно увеличить до 30 кОм). «Подтягивающий» резистор следует устанавливать и на выводе RESET (о чем также уже говорилось в главе 2). Кроме того, такие резисторы желательно устанав-
Глава 4. Микроконтроллеры AVR на практике 71_ ливать на выводы SCK, MOSI и MISO соответствующих портов в случае, если они используются для программирования и подключены к программирующему разъему ISP (см. главу 5). Обязательно их устанавливать также по выводам внешних преры- ваний into-int2 и pcint, если они задействованы. Если эти выводы при выполне- нии названных функций не «подтягивать» к напряжению питания дополнительны- ми резисторами (пусть это и не оговорено в технической документации), то не ис- ключены ложные срабатывания внешних прерываний, перезапуски системы, а при очень мощных помехах— даже порча программы в памяти программ. С другой стороны, когда выводы программирования служат в качестве обычных портов, сконфигурированных на выход, а в устройстве применяются режимы энергосбере- жения, наличие «подтягивающих» резисторов может привести к лишнему потреб- лению тока (при установке вывода в логический ноль через резистор потечет ток от источника питания на вход МК). Так что если реализован один из режимов энерго- сбережения, нужно тщательно проанализировать схему, чтобы исключить ситуа- ции, при котором через эти резисторы протекает ток. Также всегда следует устанавливать внешние резисторы при работе выводов МК на общую шину, как в интерфейсе 12С, или просто при подсоединении входа МК к вы- ходу другого устройства с открытым коллектором — например, к выходу монито- ров питания (см. главу 2). Сопротивление встроенного резистора в таких случаях слишком велико для того, чтобы электромагнитные помехи («наводки») на нем эф- фективно «садились», а в случае 12С при большой емкости шины (т. е. если она дос- таточно длинная) могут недопустимо затягиваться фронты импульсов. При этом еще раз повторим, что каждый дополнительный «подтягивающий» резистор увели- чивает потребление схемы, и в случае экономичных режимов их установку стоит продумывать отдельно — возможно, иногда от них целесообразно и отказаться. Корпуса МК и их установка на плату Примеры некоторых типов корпусов, в которых выпускаются микросхемы AVR, приведены на рис. 4.1. Более подробную информацию на эту тему можно найти в приложении 2 и в технической документации на контроллеры. Для любительских целей при ручной пайке плат пригодна фактически только одна разновидность корпуса (см. рис. 4.1) — это DIP или, иначе, PDIP-корпус (буква «Р» впереди просто означает, что корпус из пластика, а не керамический). Такой тип корпуса обозначается буквой «Р» после значения частоты (например, ATmega8- 16PU) — и он одинаково удобен и для запайки в плату «наглухо», и для установки на панельку. Именно в таком корпусе устанавливается контроллер в плату Arduino Uno, поэтому его можно оттуда извлекать и использовать плату и контроллер от- дельно друг от друга. Надо только учитывать, что DIP-корпуса с более чем 40 кон- тактами не встречаются, поэтому не все старшие модели ATmega удобны для ра- диолюбительской практики (см. табл. П2.2 в приложении 2). Ранее, когда внутрисхемного программирования не существовало, были популярны корпуса типа PLCC с подпружиненными контактами (обозначаются буквой «J»). Вместе с обязательно необходимой в этом случае панелькой они занимают на плате
72_ Часть I. Общие принципы устройства и функционирования Atmel AVR места даже больше, чем DIP-корпуса, зато их можно неоднократно вставлять и из- влекать из панельки без риска повреждения контактов. К сожалению, современные разновидности контроллеров в таких корпусах практически не выпускаются — из доступных AVR это фактически только старинные ATmega8515 и ATmega8535. Рис. 4.1. Примеры некоторых типов корпусов для МК AVR Если у вас есть возможность быстро и недорого разводить и изготавливать платы, то тут все преимущества имеют корпуса с планарным расположением выводов (ти- па SO, SOT или, иначе, SOIC) или — для большего количества контактов — типа TQFP (с выводами со всех четырех сторон). В планарных корпусах, кроме всего прочего, доступны любые модели контроллеров. Корпуса типа SO (SOT, SOIC) обозначаются буквой «S», корпуса TQFP— буквой «А». Они занимают гораздо меньшую площадь на плате, но это преимущество почти теряется при необходимо- сти их перепрограммировать по ходу работы. Так как извлекать их нельзя, то обя- зательным атрибутом платы становится ISP-разъем, который сам занимает доста- точно много места. Bootloader— загрузчик через последовательный порт (о нем рассказано в главах 5 и 6) — тут поможет мало, потому что к нему требуются свои внешние компоненты: как минимум разъем для подключения к порту, а также адаптер. Так что SOIC/TQFP-корпус хорош в основном тогда, когда нужно изгото- вить несколько идентичных экземпляров уже готового и отлаженного на опытном образце устройства. Подробности Бывает, что в продаже имеются микросхемы нужной разновидности только в планар- ных корпусах (типа SO или SOIC), а изготовить плату под них затруднительно. В этом случае выйти из положения можно, если приобрести переходную плату на DIP-корпус,
Глава 4. Микроконтроллеры AVR на практике 73 показанную на рис. 4.2, слева и в центре. Эта плата годится для числа контактов от 8 до 28. К сожалению, она подходит только для двухрядных корпусов с шагом 1,27 мм и вдвое меньше (TSSOP 0,65 мм, на обратной стороне). Для различных микросхем (в том числе и для некоторых AVR) часто встречаются корпуса с промежуточным ша- гом 0,80 мм, для которых эта плата не подходит. В этом случае, а также для корпусов с выводами с четырех сторон (TQFP), можно поискать среди имеющихся в продаже макетных плат подходящую с площадками под нужные типоразмеры корпусов (пример для случая TQFP приведен на рис. 4.2, справа, а для двухрядных корпусов с меньшим числом выводов — на рис. 4.4, справа), из которых можно вырезать фрагмент и при- паять туда микросхему. W^v-%4' ^--' Рис. 4.2. Платы для перехода от корпусов с пленарными выводами к шагу 2,5 мм Показанный на рис. 4.1, вверху справа корпус типа QFN занимает на плате меньше всего места, но практически не подходит для любительских конструкций, т. к. обычным паяльником надежную пайку его контактов выполнить нереально, — та- кие корпуса паяются только с помощью нанесения на контактные площадки специ- альной пасты и последующего прогрева всей платы по определенному алгоритму. Необходимое оборудование и приспособления В этом разделе мы обсудим различные варианты оборудования и приспособлений, необходимых для нашей деятельности. Кстати, все то же самое рекомендуется и для облегчения работы с Arduino — просто обычно об этом стараются лишний раз не упоминать, чтобы не отпугивать новичка. Итак, нам потребуются... Панельки Любительские конструкции обычно создаются в одном экземпляре, который одно- временно служит и отладочным макетом. В этом случае отладка значительно упро- стится, если предусмотреть не просто возможность программирования прямо в схеме (т. е. установку соответствующего ISP-разъема, — см. главу 5), но и уста- новку панельки для контроллера. В последнем случае контроллер можно будет без-
_74 Часть I. Общие принципы устройства и функционирования Atmel AVR болезненно заменить, если вы своими экспериментами умудрились вывести его из строя. Впрочем, можно не устанавливать ISP-разъем, если изменений предполага- ется немного, сэкономив место на плате и время на пайку, — проще тогда извлечь контроллер, запрограммировать его отдельно и вставить обратно. Как мы уже говорили, самый удобный корпус контроллера для этих целей — DIP (PDIP). Панельки для него выпускаются двух видов: обычные с плоскими контак- тами и цанговые (рис. 4.3). Вторые существенно надежнее, но хуже приспособлены для многократного извлечения и вставки микросхемы, — требуется большее уси- лие, и наличествует риск погнуть контакты при перекосе корпуса. Рис. 4.3. Панельки для микросхем: вверху простые, внизу-— цанговые DIP-корпуса бывают узкие и широкие, и это следует учитывать при выборе панель- ки под конкретный контроллер. Так, например, показанные на рис. 4.3 панельки DIP-28— узкие (подходят под ATmega8/88/l68/328), a DIP-40— широкие (ATmegal6, ATmega8535 и т. д.). Подробности Учтите, что для макетирования на беспаечной макетной плате не годятся «голые» DIP-корпуса— их выводы просто не достанут до контактов платы в гнездах, поэтому панельки понадобятся для всех микросхем, присутствующих в схеме, а не только для контроллера. Большинство обычных панелек (с плоскими контактами) не подойдут по той же причине. Существуют, правда, панельки с длинными выводами, которые под- ходят для вставки в макетную плату, но встречаются в продаже они отнюдь не на каж- дом углу, тем более всех необходимых конфигураций. Поэтому цанговые панельки, у которых выводы длиннее, чем у обычных, — единственный надежный и беспро- блемный вариант для макетирования без пайки. Только не забывайте отформатиро- вать выводы новой микросхемы, чтобы они стояли точно под углом 90° к корпусу, что особенно актуально для цанговых панелек. Тонкая часть вывода при неточном попа- дании в отверстие легко загибается под самым причудливым углом, а вырубленные из плоского листа мягкого металла выводы DIP-корпуса этого не переносят совершен- но — обламываются уже после двух-трех перегибов.
Глава 4. Микроконтроллеры AVR на практике 75^ Макетные платы Макетные платы бывают беспаечные (со специальными зажимными контактами) и печатные (для пайки компонентов). Беспаечные макетные платы, как я надеюсь, читателям уже знакомы, т. к. с них обычно начинается обучение электронике, в том числе и Arduino. Про беспаечные платы нужно отметить только один момент — не приобретайте их в случайных интернет-магазинах! Популярность этих плат такова, что их делают очень разные производители, в числе которых много не слишком добросовестных. И если стенную розетку, из которой через месяц начинает выва- ливаться вилка, не так уж и сложно заменить на фирменную, то макетная плата приобретается не на один год, и внезапно обнаружить, что схемы перестают рабо- тать из-за хронического неконтакта в плате, бывает очень грустно, — прочих при- чин вполне хватает, чтобы из-за сэкономленных пары сотен рублей создавать себе еще и такие проблемы. Хорошие макетные платы продают в торгующих такого ро- да товарами фирменных магазинах. Рис. 4.4. Печатные макетные платы: слева — универсальная с шагом 2,5 мм; справа — для перехода от пленарных корпусов с мелким шагом Макетные печатные платы под пайку не стандартизированы и имеют самые разные конфигурации. Для обычных нужд целесообразно приобрести (или заказать) уни- версальную плату простейшей конфигурации (рис. 4.4, слева). Отверстия на ней расположены с шагом 2,5 мм, никак не соединяются между собой и обязательно должны быть металлизированными с контактными площадками с обеих сторон.
76^ Часть I. Общие принципы устройства и функционирования AtmelAVR В дополнение к такой плате неплохо приобрести макетную плату под планарные корпуса (вроде показанной на рис. 4.4, справа) — из нее можно вырезать переход- ники от планарных корпусов к шагу 2,5 мм, если DIP-вариантов каких-то позарез необходимых микросхем в продаже не оказалось. Можно, конечно, для этой цели использовать и переходные платы для микросхем с большим количеством выводов, показанные на рис. 4.2, — но так получается просто дешевле, и вариантов подоб- ных плат для различного шага выводов больше. Адаптер для UART Вообще-то адаптер последовательного порта для подключения вашего устройства к компьютеру может понадобиться и в качестве постоянного компонента схемы. Вопрос о том, как удобнее организовать интерфейс для обмена законченных уст- ройств с компьютером, мы обсудим подробнее в главе 75, а сейчас остановимся только на готовых решениях для подключения макетов— они понадобятся нам с самого начала. В работе с Arduino адаптер USB-UART (или, как вариант, UART-RS232) требовал- ся для программирования платы Arduino Mini. Для других нужд он там, в общем, не нужен, т. к. макетирование и отладку все равно удобнее производить на Arduino Uno или Nano, где адаптер уже встроен в плату. А у нас никаких таких отладочных плат не,будет (не станем же мы приобретать дорогущие фирменные отладочные комплекты, правда?), потому адаптер для общения с компьютером станет жизненно необходимым аксессуаром. СОМ-порта, как такового, в современных компьютерах не имеется, но никто не мешает его установить дополнительно. Если вы хотите иметь со стороны компью- тера именно СОМ-порт, а не USB, то лучше, если это будет отдельная вставная плата, а не переходной кабель-адаптер USB-COM, — последние часто работают из рук вон плохо. Такую плату с СОМ-портами, конечно, можно установить только в традиционный АТХ-корпус настольного компьютера (уже в мини-АТХ и, тем бо- лее, в модные «коробочки» микро-ПК лишняя плата может и не влезть, а про ноут- буки и говорить нечего). Если плату установить нет возможности, то лучше всего перестать упрямиться и перейти к варианту адаптера USB-UART, о котором речь пойдет далее. Только следует учесть, что при этом лучше не работать с двумя такими адаптерами одновременно: их драйверы могут конфликтовать, и придется отключать либо одно устройство, либо другое (см. в главе 5 врезку «Примечание» о программаторах AS-4 и AS-2). Для адаптера UART-RS232 (нередко встречающегося также под названием UART- СОМ) нужен только преобразователь уровней UART (однополярных в положи- тельной логике) в двухполярные в отрицательной логике уровни RS-232 (см. гла- ву 3). Сложность тут в том, что для двухполярных сигналов RS-232, кроме положи- тельного, необходимо и отрицательное питание. Оно имеется внутри компьютера, но на разъем СОМ-порта питание традиционно не выводится, потому приходится выходить из положения разными способами. Облегчается это положение тем, что в стандарте RS-232 допускаются очень широкие пределы для обоих напряжений: от 3 до 12-15 вольт по абсолютной величине и необязательно симметричные. По-
Глава 4. Микроконтроллеры AVR на практике 77_ требляемые токи при этом минимальны и не превышают единиц-долей миллиампе- ра, чем и пользуются в разных вариантах схем адаптеров. Положительное напряже- ние часто просто берется от питания схемы (обычно это +3 или +5 вольт), а вот для отрицательного прибегают к разным ухищрениям. Адаптеры UART-RS232 весьма просты по конструкции и легко поддаются самодельному изготовлению, им также не требуется специальный драйвер. В Сети можно найти большое количество раз- личных вариантов, начиная от простейших любительских конструкций на двух транзисторах, и если кого-то заинтересует этот вопрос в подробностях, пишите, проконсультирую. Для более универсального адаптера USB-UART (иногда их называют адаптерами USB-TTL — по названию стандарта логических уровней TTL) городить самодель- ные конструкции не имеет особого смысла: хороший USB-UART хлопотней в изго- товлении, чем RS232-UART, а в продаже имеется достаточное количество вариан- тов самой разной цены и функциональности. Существуют USB-адаптеры, в кото- рых выведены дополнительные линии СОМ-порта, — т. е. они полноценно заменяют настоящий СОМ-порт, только уровни сигналов уже приведены к уровням контроллера. Если же вы встретите название USB-RS232, то учтите, что это либо простая путаница (имеется в виду USB-UART), либо очень уж экзотическое уст- ройство для каких-то специфических надобностей, и оно нам не подходит. Заметки на полях У многих старых дешевых мобильников, имеющих разъем USES, его использовали только для подзарядки. Между тем, он вполне способен служить для связи с компью- тером, просто представляет собой интерфейс UART, а не USB. В контроллерах мо- бильников (обычно они основаны на ядре ARM) все равно имеется последовательный порт, потому его вывести на разъем не представляет труда, а дополнительный адап- тер USB-UART стоит денег и занимает место. К таким мобильным телефонам часто предлагался довольно дорогой переходный кабель, который и представляет собой этот недостающий адаптер. Если вам захочется возиться, можете попробовать под- ключить к мобильнику любой другой адаптер из имеющихся под рукой, — вся пробле- ма только в правильном подключении выводов разъема со стороны UART в мобиль- нике. Иногда наоборот, кабели от таких мобильников используют в качестве USB- адаптера — когда-то это была очень распространенная практика. Если вы хотите, чтобы ваше устройство при подключении к компьютеру выдавало собственное название, а не безликое наименование фирмы или микросхемы, на ко- торой это все спроектировано, то придется перепрограммировать содержимое EEPROM микросхемы адаптера. Но и тут необязательно городить все с нуля — как правило, для фирменного адаптера тоже можно найти возможности перепрограм- мирования. Подробную информацию на эту тему несложно разыскать в Сети. Заметки на полях Для тех, кто не ищет легких путей, а непременно хочет сделать все своими руками, укажем на публикацию, где приведена схема подключения контроллера ATmega168 к микросхеме адаптера FT232R: https://www.drive2.ru/b/2955382/. Эта схема по функ- циональности аналогична платам Arduino, но в ней может быть использован любой другой контроллер, необязательно даже из семейства Меда, лишь бы он был снабжен портом UART/USART. Попытка воспроизвести эту схему, возможно, притормозит ва- ши устремления к максимальной независимости — микросхема FT232R выпускается
78^ Часть I. Общие принципы устройства и функционирования Atmel AVR в планарном корпусе ssop-28 с шагом выводов 0,65 мм (см. переходные платы на рис. 4.2), и если вам удалось ее запаять вручную с первого раза и при этом не испор- тить, значит, вы настоящий гений паяльных наук. Поскольку USB имеет линию питания 5 вольт, то от него можно запитать схему. Причем в этом вопросе есть свои тонкости. В отличие от адаптеров USB-UART, последовательные ISP-программаторы, о которых пойдет речь далее (см. главу 5), всегда питаются от программируемой схемы. Там это сделано для того, чтобы гарантированно избежать повреждения неподключенного к питанию программато- ра при соединении его со включенной схемой. Но заодно это предохраняет и от ситуации, когда оба источника питания включены одновременно и начинают бо- роться друг с другом в электронной версии армрестлинга. Раунд заканчивается за считанные миллисекунды и без видимых последствий, за исключением того, что все перестает работать. И еще хорошо, если победу одержал USB, ну а если ваш стабилизатор оказался сильнее и повредил питание USB на материнской плате компьютера? Адаптеры USB-UART от этой проблемы не защищены, потому здесь надо внимательно следить за тем, чтобы всегда подавалось только одно из питаний. Заметки на полях А вот интересно, как этот вопрос решается в Arduino? Там ведь можно включить одно- временно кабель USB и внешний источник в разъем Vin, и они не будут конфликтовать друг с другом. Секрет простой: питание 5 В со встроенного стабилизатора Arduino и питание от разъема USB развязаны друг с другом так, что при подаче внешнего пита- ния USB-питание просто отключается от схемы. Причем исследование принципиаль- ных схем Uno и Nano показывает, что принципы развязки у них разные: в «наворочен- ном» Uno стоит транзисторный ключ, отключающий USB-питание, а в более простом Nano эту функцию выполняет обычный диод. Напомним, что сигнальные выводы UART и одноименные сигнальные выводы лю- бого адаптера (у контроллера они называются RxD и TxD, у адаптера обычно про- сто RX и ТХ) соединяются перекрестно: RxD с ТХ, и наоборот, TxD с RX (единст- венное исключение описано далее). Кроме сигнальных, обязательно соединяются выводы «земли» (GND) и, если необходимо, выводы питания Vcc (еще раз напом- ним: следите, чтобы было включено либо собственное питание схемы, либо пита- ние от адаптера, но не оба одновременно). На худой конец в качестве адаптера USB-UART можно использовать плату Arduino Uno, если извлечь из нее «родной» ATmega328 и соединить одноименные выводы платы Uno с «нашим» контроллером. Слово одноименные выделено потому, что в этом случае RxD контроллера соединяется с RX платы Uno, a TxD контроллера с ТХ платы, — ведь наш контроллер здесь выступает вместо извлеченного Mega328, и перекрестное соединение уже выполнено внутри платы. Не забудьте также объ- единить «земли» (GND) платы и вашей схемы и, если надо, подключите вывод питания Vcc. Кстати, точно таким же образом можно программировать плату Arduino Mini, если дополнительно еще соединить выводы RESET . Заметки на полях В Сети можно встретить рекомендацию — вместо извлечения «родного» контроллера из Uno просто заглушить его последовательный порт, что можно сделать двумя путя- ми: либо накоротко соединить вывод RESET с GND, погрузив контроллер в состояние
Глава 4. Микроконтроллеры AVR на практике 79^ сброса, либо загрузить в ATmega328 простенький скетч, устанавливающий оба циф- ровых вывода 0 и 1 (соответствующих RX и ТХ) в состояние работы на вход. В этом случае в качестве адаптера можно использовать также и плату Arduino Nano, из кото- рой контроллер не извлекается. Способ не очень надежный— некоторые клоны Arduino таким образом работать отказываются — и годится разве что совсем в безвы- ходных положениях, когда ничего, кроме Nano, под рукой не имеется. Заметим еще, что для нормальной работы с адаптером USB-UART нужно про- граммное обеспечение. С драйвером под Windows (а также Linux) обычно не воз- никает вопросов— по крайней мере в Windows 7 их специально устанавливать обычно не требуется, они уже имеются в составе ОС. В крайнем случае поинтере- суйтесь, на основе какой именно микросхемы сделан приобретенный вами адаптер, и разыщите драйвер на сайте производителя или просто в Интернете. Однако кроме драйвера нужна еще какая бы то ни было программа для приема данных через порт (вроде монитора порта в Arduino) — о таких программах мы поговорим подробнее в главе 15. Светодиоды-пробники Простейший пробник логических уровней на основе светодиода бывает очень поле- зен— он позволяет непрерывно контролировать состояние вывода, к которому подключен, и является, таким образом, простейшим средством отладки. Не надо ничего особенного выдумывать: возьмите двухцветный двухвыводной светодиод, который при подаче тока в одном направлении светится, например, красным цве- том, а в другом— зеленым. Предпочтительнее выбирать светодиоды с матовой линзой — так их свечение лучше видно со всех сторон. Светодиод можно взять и обычный, но двухсторонний удобнее, т. к. не придется раздумывать над поляр- ностью подключения. К одному выводу светодиода припаяйте токоограничиваю- щий резистор (не укорачивая его выводы), другой — нарастите на ту же длину от- резком луженой медной проволоки такого же диаметра, как и выводы (около 0,5 мм). Изолируйте все неизолированные участки обоих выводов тонким термо- усадочным кембриком, оставив без изоляции по 10-15 мм у концов выводов и у корпуса светодиода. Номинал резистора: 4-10 кОм — такого сопротивления достаточно для уверенного свечения светодиода, но оно практически не будет влиять на уровень напряжения на выводе. Сделайте несколько штук таких пробников с резисторами у одного и того же вывода светодиодов, чтобы цвет свечения при одинаковой ориентации от- носительно плюса и минуса приложенного напряжения был одинаковым. Такой пробник хорошо совместим с беспаечной макетной платой и заодно позволит под- цеплять щуп мультиметра или осциллографа, для которых иначе приходится спе- циально делать выводы из схемы. На изготовление полудюжины пробников вы за- тратите полчаса времени и около 100 рублей денег, зато вместо возни со щупами мультиметра будете иметь простейшее средство контроля, которого во многих слу- чаях бывает вполне достаточно.
80^ Часть I. Общие принципы устройства и функционирования AtmelAVR Мул ьти метр Мультиметр — обязательная принадлежность любого электронщика, даже самого неопытного (более того— особенно неопытного!). Контролировать уровни напря- жений в схеме приходится на каждом шагу — например, при подключении стаби- лизатора питания к схеме прежде всего необходимо убедиться, что вы не ошиблись в разводке выводов, и еще до соединения со схемой промерить напряжение на вы- ходе. Пренебрежение этой простейшей операцией, случается, приводит к печаль- ным последствиям даже у опытных радиолюбителей. Кроме того, возможно, вы «на раз» читаете цветовой код резисторов, а лично я за- мучался определять, как именно тот или иной производитель, умудрившийся по- красить резистор в густо-синий или бледно-зеленый цвет, представляет себе цвет полоски «оранжевый» или «фиолетовый». Поэтому во избежание фатальных оши- бок советую всякий раз промерять сопротивление резисторов мультиметром. Обращение с цифровыми мультметрами и описание их возможностей приводится в огромном количестве источников, и здесь нет смысла их повторять, — разве что дополнить напоминанием, что вместе с мультиметром обязательно следует приоб- рести пару зажимов-«крокодилов», иначе работать с ним неудобно. Сделаем еще несколько замечаний практического характера применительно к на- шим задачам. Так, при выборе мультиметра обращайте внимание на полноту имеющихся диапазонов. В первую очередь он должен измерять, как минимум: □ постоянное напряжение в пределах от 200 мВ (а лучше еще меньше) до 2000 вольт с шагом в порядок величины: 2, 20, 200 и т. д.; □ переменное напряжение до 1000 вольт (можно до 700 вольт действующего зна- чения); □ сопротивление от предела 200 Ом (а лучше еще меньше) до 20 МОм с шагом в порядок величины без пропусков. Все остальное в мультиметре опционально. Например, я почти никогда не пользу- юсь возможностью измерения величины тока и вам не советую: в таком режиме мультиметр нужно подключать последовательно в разрыв цепи, и он становится существенным элементом схемы, без которого она неработоспособна. А так как мультиметр подключается временно, то одновременно он оказывается и самым не- надежным элементом — никогда не знаешь, схема действительно не работает, или просто у мультиметра сработал предохранитель по току из-за неправильного выбо- ра диапазона. Кроме того, при необходимости контролировать попеременно ток и напряжение в двух-трех разных точках схемы очень муторно и долго переключать щупы мультиметра, не забывая при этом их переносить в другие гнезда, и очень легко ошибиться, случайно совершив единственную операцию, которую с мульти- метром делать нельзя, — подключить его к выводам питания в режиме измерения тока. Для измерения тока я имею несколько резисторов двух номиналов, запасенных специально для этой цели: номиналом в 1 Ом — для токов единицы-сотни милли- ампер, и 0,1 Ом (самостоятельно изготовленный из нихромовой проволоки) — для
Глава 4. Микроконтроллеры AVR на практике 81_ токов выше одного ампера. Такие резисторы легко и надежно подключаются к схе- ме, на ее работу практически не влияют (при токе 100 мА, например, на резисторе в 1 Ом «садится» всего 0,1 вольта), к ним просто подключить мультиметр в режиме измерения напряжения и быстро перенести его в другое место, не опасаясь забыть переключить режим и очередной раз пожечь предохранитель. Функция измерения емкости конденсаторов может пригодиться, но почему-то она у всех встречавшихся мне недорогих мультиметров реализована крайне неудоб- но— до контактов в гнездах выводы большинства современных конденсаторов просто не достают. Может также оказаться полезной встречающаяся у современных моделей функция измерения частоты — особенно, если у вас нет осциллографа. Только следите, что- бы пределы измерений начинались с десятков герц и заканчивались в районе 100— 200 кГц, иначе эта функция может так и остаться невостребованной. Не ждите, ко- нечно, от этой возможности портативного мультиметра чего-то выдающегося в смысле точности измерений — калибровать часовой кварцевый генератор у вас определенно не получится. Но проверять установленную частоту— например, ШИМ или звукового сигнала, таким способом очень даже удобно. И, кстати, если есть такая возможность — купите мультиметр настольного испол- нения. Они, как правило, более точные, более функциональные, не капризничают в режиме измерения тока, дисплей у них светящийся, а не слепой жидкокристалли- ческий, и пользоваться ими на постоянном рабочем месте банально удобнее. Осциллограф Конечно, в случаях необходимости измерения временных параметров сигнала ос- циллограф вне конкуренции, но и не только их. Осциллограф, по сути, заменяет собой отдельный частотомер/периодомер (за исключением особо точных образцо- вых приборов), он может выполнить значительную часть функций мультиметра (за исключением измерения сопротивления или, например, емкости) и притом делает это гораздо нагляднее и удобнее — напряжение сигнала можно измерить в точно определенный момент времени или пробежаться по всем его значениям в нужном временном промежутке. Лучше всего, конечно, иметь современный цифровой прибор, но если у вас зава- лялся за шкафом старинный аналоговый осциллограф (с этаким винтажным экран- чиком зеленого свечения), — пусть он многих удобных функций и не имеет, но тоже окажется немалым подспорьем. У осциллографов только один крупный недоста- ток — они дороги. Цена самых дешевых начинается в районе 15-20 тысяч рублей и быстро взлетает в диапазон десятков тысяч и выше. Некоторый компромисс для любительских целей могут предоставить модели, известные под названием «осцил- лограф-ручка», — они подсоединяются к компьютеру через шнурок USB и вполне выполняют свои функции, но при этом существенно дешевле стационарных. В общем, если средства позволяют— обзаведитесь осциллографом обязательно, жить сразу станет легче и веселее. А вот пытаться повторить самодельные конст- рукции осциллографов в надежде, что это выйдет дешевле, не советую — конст-
82 Часть I. Общие принципы устройства и функционирования AtmelAVR руирование осциллографа вполне может быть отдельным любительским проектом, но не в замену покупного прибора. И если вы имеете для этого достаточную квали- фикацию, значит, у вас осциллограф уже есть, без него вы, создавая свой осцилло- граф, все равно не обойдетесь. Генератор Наоборот, генератор сигналов покупать не нужно вовсе. Я за всю жизнь не встре- чал задачи, в которой мне непременно понадобился бы генератор фирменного изго- товления. То есть это я такой задачи не встречал — наверняка в какой-нибудь аку- стике или звукотехнике такие задачи попадаются на каждом шагу, но мы-то здесь собрались по другим вопросам. Не так сложно изготовить самостоятельно генера- тор синусоидального сигнала, если он вдруг вам понадобится, а цифровой, который понадобится обязательно, — еще намного проще, потому его стоит сделать в пер- вую очередь. Ничего особенного нам от такого генератора не нужно — требуется только набор точно известных частот в пределах от единиц или долей герца до десятков кило- герц. Нет особых проблем соорудить такой генератор на микроконтроллере, но, во- первых, Arduino для этого подходит не очень хорошо, а с другими контроллерами мы работать еще не умеем. Во-вторых, это просто не нужно: ничего особенного вы здесь не выиграете по сравнению с элементарной конструкцией, описанной далее, а по стоимости и трудозатратам наша окажется даже дешевле. Схема цифрового генератора представлена на рис. 4.5. Как видите, она весьма про- ста, и сборка ее требует только аккуратности при отсчете выводов микросхем. Схе- ма работает от часового кварца и выдает четырнадцать точных значений частот, кратных степени двойки: от 0,5 до 32 768 Гц, исключая два значения: 4096 и 8192 герца (это такая особенность использованного счетчика 561ИЕ16). Если хоти- те еще упростить схему, то удалите дополнительный счетчик на DD3 — тогда при- дется обходиться минимальной частотой 2 Гц (но не забудьте, что в этом случае остаются свободные элементы «И-НЕ», и их входы надо присоединить или к «зем- ле», или к питанию). Использование древних КМОП-микросхем серии 561 придает схеме одну приятную особенность — она без проблем работает в диапазоне пита- ний от 3 до 12 вольт (на самом деле даже больше— микросхемы выдержат и до 15-18 вольт, просто страшно за часовой кварцевый резонатор, на такие на- пряжения явно не рассчитанный). Поэтому схема годится для отладочных работ совместно с практически любой радиолюбительской конструкцией. Другая приятная особенность схемы, которая в некоторых случаях бывает важ- на, — все частотные сигналы образуют строго симметричный меандр, когда дли- тельность импульсов точно равна длительности промежутка между ними. При этом фронты импульсов со всех выходов точно (ну, почти точно, т. к. счетчик асинхрон- ный) сфазированы, что позволяет путем их логического сложения получать им- пульсы разной, но также точно известной скважности. Схему генератора следует собрать с помощью пайки на макетной плате и вывести все четырнадцать значений частоты на ее край, оформив в виде клеммника с соеди-
Глава 4. Микроконтроллеры AVR на практике 83 5,1 М К выв. 14 DD1,- DD3 -о+3-12 В К выв. 7 DD1, DD3 100,0 х16В - 32768Гц "22пф 22пф DD1 561ЛА7 DD2 561ИЕ16 Л DD3 561ТМ2 DD1/3 32768 Гц 10 Q13 Q12 Q11 Q10 СТ DD2 Q0 Q3 Q4 Q5 Q6 Q7 Q8 Q9 -о 16384 -о 2048 -о 1024 -о 512 1Гц о 0,5 Гц о -о 256 13 12 14 -о 128 -0 64 -0 32 -о 16 R S DD3/1 11 9 юПГ1 R S С Q D Q< DD3/2 13 12 -о 2 Гц 13 Рис. 4.5. Схема цифрового лабораторного генератора сигнала нениями под винт. Внешний вид такого клеммника показан на рис. 4.5, справа. До- полнительно в другом месте платы ставится такой же клеммник для подачи пита- ния и на него «вешаются» все положенные развязывающие конденсаторы. Питание туда подается от питания основной схемы, благодаря чему уровни напряжений все- гда полностью согласованы с обеих сторон. Единственное ограничение, о котором надо помнить: частотные выходы здесь рассчитаны на подключение к высокоом- ному входу другой КМОП-микросхемы или контроллера, но не должны подклю- чаться к какой-либо значительной нагрузке. Светодиод с резистором 3-5 кОм они еще выдержат, но более значительная нагрузка (например, в виде звукоизлучателя) может нарушить работу счетчика. Можно дополнить выходы буферными усили- тельными элементами из микросхем CD4050/CD4049 (КР1561ПУ4/КР1561ЛН2), но конструкцию это загромоздит, а на практике такое ограничение на использование генератора по назначению все равно не влияет. Отечественные микросхемы серии 561 без каких-либо изменений можно заменить на аналогичные из серии 1561 или на импортные: 561ИЕ16 на CD4020A/B, 561ТМ2 на CD4013B, а 561ЛА7 на CD4011В. Часовой кварц лучше выбирать из импортных
84 Часть I. Общие принципы устройства и функционирования AtmelAVR в цилиндрическом корпусе 8x3 мм, они без видимых проблем работают при напря- жениях питания до 12 В. Отечественный РК-206 имеет еще меньший корпус: 6x2 мм, но и рассеиваемая мощность у него меньше, так что с ним лучше ограни- читься напряжением питания 3-6 В. Функциональность генератора, собранного по этой схеме, будет перекрывать наши потребности если и не на все 100%, то по крайней мере на 99% совершенно точ- но — исключая разве что редкий случай потребности в частотах порядка мегагерц. Источники питания Все схемы в этой книге рассчитаны на универсальное питание 5 вольт — на кото- рое ориентировано большое количество внешних устройств, выпускаемых различ- ными фирмами. При этом нет особых трудностей в подключении 3-вольтовой пе- риферии к 5-вольтовому контроллеру, т. к. уровень в 3 вольта превышает мини- мально допустимый высокий уровень AVR, равный 2,4 вольта (по стандарту, пошедшему еще с древней TTL-логики). Чтобы не перегружать защитные диоды 3-вольтового входа периферии при обратном подключении, лучше ставить на 5-вольтовом выходе контроллера делитель напряжения в соотношении пример- но 3:2 (например, 33 кОм в верхнем плече и 68 кОм в нижнем). Подробности Существуют, разумеется, интегральные решения в виде преобразователей уровней 3,3-5 вольт. Есть специально предназначенная для 12С-интерфейса микросхема РСА9306 (она имеет два двунаправленных канала, — см. также главу 13). Для более общих случаев могут применяться, например, микросхемы SN74LVC1T45/SN74LVC2T45, представляющие собой один или, соответственно, два двунаправленных буфера, ра- ботающих на каждой стороне от своего питания в диапазоне 1,65-5,5 вольт, или их аналоги от других фирм (ADG3301). Очень удобен для SPI-соединений преобразова- тель ADG3304, содержащий четыре двунаправленных канала. Есть преобразователи уровней и с ббльшим количеством каналов — например, TXB0108PWR имеет 8 двуна- правленных линий преобразования сигналов напряжением 1,2-3,6 вольта на низко- вольтной стороне и 1,65-5,5 вольта на высоковольтной. Общим недостатком всех этих решений является их относительная громоздкость: SN74LVC1T45 имеет 6 выводов, ADG3304 — 14, a TXB0108PWR вообще 20, причем выпускаются они в неудобных для ручного монтажа пленарных корпусах с шагом 0,5 или 0,65 мм. Поэтому в любительских условиях может быть предпочтительнее самостоятельное решение. Помимо указанного ранее простого резистивного делителя, есть еще много вариантов схем активных согласователей уровней, один из которых показан на рис. 4.6. Схема обеспечивает двунаправленный интерфейс 3,3 вольта — 5 вольт, причем об- ладает приятным свойством предотвращать утечку тока по выводу с 5-вольтовой сто- роны при отключении питания 3,3 вольта. MOSFET-транзистор можно взять любой маломощный с п-каналом и управлением от логических уровней (logic level transistor)— кроме указанного на схеме BS108 (в корпусе ТО-92) для наших нужд подойдут маломощные типы с первыми буквами IRL в наименовании (в удобных кор- пусах— например: IRLD024PBF, IRLD110PBF и др.) Будут работать и такие, как 2N7000, BS107, BSS295 и пр. Для существенной части проектов на основе микроконтроллеров какие-либо спе- циальные источники питания не требуются. Достаточно иметь любой источник
Глава 4. Микроконтроллеры AVR на практике 85 +3,3 В +5 В Vcc - 3,3 В внешнее устройство GND |10к 10к| BS108ZL1G Vcc = 5 В AVR-контроллвр GND ' 1 ' Рис. 4.6. Схема сопряжения уровней 3,3 вольта и 5 вольт с помощью MOSFET-транзистора напряжения величиной 5 В с допуском не более 5%— от примерно 4,75 до 5,25 вольта. Это максимально допустимые величины отклонения, обычно любой стабилизатор обеспечивает гораздо лучшие значения, — не следует только исполь- зовать нестабилизированные источники. Для большей части примеров из этой кни- ги подойдет максимальный выходной ток 0,33-0,5 А, но хуже не будет, если эту величину увеличить до 1 ампера, — запас карман не тянет. Можно приобрести готовый адаптер, встроенный в вилку, — они предлагаются на всех торговых сайтах, посвященных Arduino. Выходной разъем у таких адапте- ров — круглый «джек» диаметром 5,5 мм (толще, чем у подобных адаптеров для других целей), соответственно этому размеру надо приобретать и разъем-гнездо для наших нужд, — точно такое же, как внешний разъем платы Uno. Положитель- ный вывод у него — внутренний, отрицательный («земля», GND) — внешний. Если вы приобрели тшсой стабилизированный адаптер ровно на 5 вольт, то к нему нужно приобрести только переходное гнездо с клеммником для подключения обычных проводов-перемычек, которые будут вставляться прямо в макетную плату. Заметки на полях Не выбрасывайте зарядные устройства от отработавших свой срок мобильников! Су- щественную часть своих потребностей в источниках питания автор давно уже удовле- творяет за их счет. Конечно, в каждом конкретном случае нужно внимательно читать, что написано на обязательно имеющемся на таком источнике шильдике, чтобы сильно не промахнуться, но обычно это довольно-таки качественные источники напряжением 5 вольт, часто импульсные, т. е. они могут запросто обеспечить вас током в пару ам- пер в минимальных габаритах. Более того, во многих случаях удобно встраивать такие источники в готовые конструкции — если распилить корпус и извлечь оттуда плату. Зарядники для мобильной электроники, как и любые другие адаптеры питания для бы- товых устройств, обязательно имеют гальваническую развязку от сети, и вам не при- дется приобретать отдельно трансформаторы и городить схему выпрямления. Однако обратите внимание, что вход внешнего питания Arduino допускает подачу напряжения от 5 до 12 вольт, т. к. стабилизатор там встроен в плату. По указанной причине напряжение у предлагаемых Arduino-адаптеров может оказаться не 5 вольт, а выше. В этом нет особых трудностей, т. к. мы в своих макетах всегда
86 Часть I. Общие принципы устройства и функционирования AtmelAVR можем точно так же дополнить адаптер отдельным стабилизатором: это ровно три дополнительных компонента, собственно интегральный стабилизатор и два кон- денсатора к нему. Питание следует обеспечивать фирменными интегральными стабилизаторами, са- моделки здесь недопустимы. Обычно рекомендуемые «чайникам» LM7805 (или LM78L05 в маломощном варианте) решительно устарели из-за очень высокого ми- нимально допустимого проходного напряжения 2,4 вольта, — т. е. минимальное напряжение на входе должно быть не менее 7,5 вольт, и как минимум треть всей мощности будет расходоваться впустую. Для малопотребляющих схем годятся 100-миллиамперные стабилизаторы LM2931 (5-вольтовый) или LP2950 (на напря- жение 5 В, 3,3 В и 3 В) в корпусах ТО-92, которые отличаются сверхмалым собст- венным потреблением (несколько десятков микроампер) и сверхнизкой разницей напряжений на входе и выходе, при которой стабилизатор еще выполняет свои функции, — достаточно перепада в несколько сотен милливольт (только не забы- вайте про пульсации, если на входе сетевой выпрямитель!). С мощными стабилизаторами в корпусах ТО-220, подобных LM7805, положение несколько хуже из-за того, что обычно предлагается вариант с малым проходным напряжением, но с достаточно высоким собственным потреблением (LM2940 с максимальным током 1 А, проходным напряжением 0,5-0,8 В и потреблением 10 мА, или L4940 с максимальным током 2 А, проходным напряжением 0,2-0,4 В и потреблением 5 мА). Можно применять и привычный по Arduino Uno стабилизатор LM1117-5.0, у которого характеристики скорее средние, но все-таки лучше, чем у LM7805 (максимальный ток 0,8-1 А, ток потребления 5 мА, проходное напряже- ние от 1,2 В). Есть два иногда встречающихся случая, когда стабилизированного источника на напряжение 5 вольт не хватает: во-первых, как мы уже говорили, могут встретиться периферийные устройства под напряжение 3 вольта, во-вторых, для некоторых внешних компонентов потребуется большее напряжение (до 12 вольт) и ток (до 5- 10 ампер). Для первого случая достаточно просто запастись отдельными инте- гральными стабилизаторами под напряжение 3,3 вольта в малогабаритном корпусе ТО-92 — они встраиваются прямо в схему (идеально подойдут стабилизаторы из серии LP2950). А вот второй случай потребует более основательного подхода. Просто для провер- ки работоспособности маломощных схем с повышенным напряжением, потреб- ляющих при 12 вольтах до 1 ампера, можно обойтись еще одним стабилизирован- ным адаптером на 12 В или вообще использовать таковой в качестве основного (правда, ток потребления пятивольтовой части схемы при этом не должен превы- шать примерно 0,2-0,3 А, иначе отдельный стабилизатор на 5 вольт будет перегре- ваться, и его придется ставить на радиатор). Для случаев, требующих более мощных источников, наилучшее решение — приоб- рести настольный источник необходимой мощности (до 13-15 вольт и до 10-12 ам- пер). Наилучшее перерастет в идеальное, если источник такой будет с двумя кана- лами, каждый — с регулируемым напряжением и заданием выходного тока. Один
Глава 4. Микроконтроллеры AVR на практике 87 такой источник перекроет практически любые радиолюбительские нужды, но он довольно дорог и требуется далеко не повседневно. В качестве более простого варианта для испытания всяческих силовых устройств можно приобрести зарядное устройство для автомобильных аккумуляторов, кото- рое по сути представляет собой тот же источник постоянного напряжения, причем регулируемый и рассчитанный на весьма значительные токи. Только внимательно смотрите на характеристики: устройство должно иметь только ручную регулировку выходного напряжения и тока (не должно быть всяких «интеллектуальных» функ- ций вроде определения типа аккумулятора, степени разряда, автоматического от- ключения при окончании заряда или по времени и т. п.) У подходящих для наших нужд автомобильных зарядников в инструкции должно быть написано примерно так: «подходят в качестве многоцелевого источника постоянного тока для питания аппаратуры». Конечно, если у вас есть мощный трансформатор, то соорудить подобный источник самостоятельно также не представляет особой сложности, даже с регулируемым напряжением, вот только от функций установки выходного тока, скорее всего, при- дется отказаться, — уж больно громоздки они в реализации. В этой книге мощный источник напряжением 12 вольт нам понадобится только один раз (если вы захотите повторить схему инвертора напряжения из главы 9), и на такой единичный случай можно будет обойтись каким-нибудь временным ре- шением — например, автомобильным аккумулятором. Кроме всего здесь рассмотренного, для работы с контроллерами потребуется еще программатор, о котором пойдет речь в следующей главе. Потребление МК AVR Вопросы потребления AVR-контроллеров имеют очевидную связь с темой реали- зации функций энергосбережения, но ее мы рассмотрим во всех подробностях в главе 14, а здесь остановимся только на собственно потреблении. Потребление контроллеров AVR при напряжении 5 В и тактовых частотах до 8- 16 МГц лежит в пределах от 3 мА — для младших Tiny и до 15-20 мА — для стар- ших Mega. Ток потребления также зависит от модификации — для модернизиро- ванных контроллеров с буквой «Д» в названии он в среднем почти вдвое меньше, чем у старых: например, ATmega8 при частоте 16 МГц потребляет около 20 мА, a ATmega8A— чуть больше 10 (рис. 4.7). При этом ток потребления, по крайней мере в подсемействе Mega, не очень зависит от степени «навороченности» моде- ли— например, для ATmega8 или ATmega8535, относящихся к младшей ветви семейства, этот ток почти такой же, как для мощной ATmega2560. Потребление заметно снижается только у самых простых представителей Tiny. Зато потребляемый ток сильно зависит от тактовой частоты и напряжения питания (на рис. 4.7 приведены графики зависимости тока потребления от этих параметров для ATmega8/ATmega8L и ATmega8A, а на рис. 4.8 — графики зависимости тока потребления от напряжения питания для ATmega8A при работе от встроенного генератора на разных частотах).
88 Часть I. Общие принципы устройства и функционирования Atmel AVR ATmega8(L) ACTIVE SUPPLY CURRENT vs. FREQUENCY 30 25 20 15 10 zzzz-—■ ==^ 2.7V ——- 3.0V _~——- 3.3V 5.5V 5.0V 4.5V 6 8 10 Frequency (MHz) 12 14 16 ATmega8A ACTIVE SUPPLY CURRENT vs. FREQUENCY 3 6 -d — ^^ - 27V ^; ^^ 33V ""36V "40V 55V 50V 45V 0 2 4 6 8 10 12 14 16 Frequency (MHz) Рис. 4.7. График зависимости тока потребления от напряжения питания и тактовой частоты для ATmega8/ATmega8L (вверху) и модернизированного ATmega8A {внизу) Из сравнения графиков, приведенных на рис. 4.7 и 4.8, видно, что встроенный гене- ратор съедает довольно значительную долю тока от общего потребления: на часто- те 8 МГц с тактированием от внешнего кварца контроллер с буквой «А» при 5 вольтах питания потребляет около 6 мА, а от внутреннего генератора при тех же условиях — около 7,5 мА. Из сравнения этих графиков можно сделать и другие интересные выводы. Графики, приведенные на рис. 4.7, показывают, что снижение тактовой частоты практически адекватно снижению напряжения питания: снизив то. или другое вдвое, вы вдвое снизите потребление. А при работе от внутреннего генератора (рис. 4.8) это не так:
Глава 4. Микроконтроллеры AVR на практике 89 ATmega8A 2.5 ACTIVE SUPPLY CURRENT vs. Vcc INTERNAL RC OSCILLATOR, 8 MHz -~s 7 - £ о ° 6 - с _ 3- -40 °C 25 °C 85 °C 3.5 4 VCC(V) 45 55 ATmega8A ACTIVE SUPPLY CURRENT vs. Vcc INTERNAL RC OSCILLATOR. 1 MHz 55 Рис. 4.8. График зависимости тока потребления от напряжения питания для ATmega8A при работе от встроенного генератора на тактовых частотах 8 МГц (вверху) и 1 МГц (внизу) снижение тактовой частоты в 8 раз снижает потребление не в 8 раз, а всего только в 4,5 раза (при 5 вольтах — с 7,6 мА до почти 1,7). В то же время двукратное сни- жение напряжения питания при 8 МГц по-прежнему вдвое снижает потребление, а при 1 МГц зависимость сложнее и куда более пологая (в «даташитах» есть еще отдельный график для частот ниже одного мегагерца). Потому при желании про- гнозировать потребление в различных сочетаниях этих величин нужно тщательно анализировать конкретную ситуацию. Например, из приведенных графиков следу- ет, что тактирование от внешнего кварца энергетически выгоднее тактирования от
$Ю Часть I. Общие принципы устройства и функционирования Atmel AVR встроенного генератора, хотя не забудем, что последнее осуществляется гораздо проще и без дополнительных компонентов на плате. График, показанный на рис. 4.9, может пролить свет на заманчивую на первый взгляд возможность кардинально снизить потребление при работе на сверхнизких частотах. Действительно, потребление снижается до уровня десятков микроампер, что уже вполне приемлемо для батарейного питания: от щелочных батареек ААА, имеющих емкость около 1500 мА- часов, такой контроллер сам по себе проработает около трех лет. Но следует при этом принять во внимание, что многие функции окажутся недоступными (работа через UART, например), а выполнение других операций замедлится настолько, что они окажутся бесполезными, — скажем, будет практически невозможной работа с LCD-дисплеем, под вопросом запись во внеш- нюю память и т. д. АТтедавА ACTIVE SUPPLY CURRENT vs. Vcc EXTERNAL OSCILLATOR, 32 kHz 70 65 60 45 40 25 °C 25 35 4 Vcc(V) 45 5.5 Рис. 4.9. График зависимости тока потребления от напряжения питания для АТтедавА при работе от внешнего кварца на тактовой частоте 32 кГц Потому и с точки зрения нормального функционирования контроллера, и с точки зрения экономии энергии куда выгоднее использовать режимы энергосбережения при обычной частоте тактирования от внешнего кварца в пределах 1-16 МГц. Подробности Давайте подтвердим эти соображения ориентировочным расчетом. Если подсчитать количество энергии, приходящейся на один такт работы контроллера, то при 8 МГц тактовой частоты и 5 вольтах питания (см. рис. 4.7) оно окажется примерно равным 5 мА х 5 В х 125 не = 3,125 нДж, а при 32 килогерцах (рис.-4.9) — 65 мкА * 5 В * 30 мкс « = 10 нДж, т. е. в три раза больше. Программа продолжительностью в 100 тактов по- требует в первом случае затрат около 0,3 мкДж, а во втором — около 1 мкДж, причем в первом случае она займет время 12,5 мкс, а во втором — целых 300 мкс. Остальное время контроллер тоже потребляет, но если мы применим во втором случае энерго- сбережение, то теоретически можем свести потребление в паузах до совершенно не-
Глава 4. Микроконтроллеры AVR на практике значительной величины в единицы микроампер (см. обсуждение режимов энергосбе- режения в главе 14). При частоте 8 МГц выполнение за секунду 10 процедур по 100 тактов займет всего /вооо часть времени, потому при подсчете среднего потребле- ния время работы вообще может не учитываться. Итого, работа при частоте 32 кГц окажется в разы или даже десятки раз менее выгодной, чем использование режимов Power Down или Power Save, причем доставит намного больше проблем. Еще один интересный вывод из графиков, показанных на рис. 4.7-4.8, заключается в том, что при использовании основного режима энергосбережения (Power Down mode, — см. главу 14) тактовая частота с точки зрения суммарного потребления практически не имеет значения. В пределах 1-16 МГц количество энергии на один такт обратно пропорционально частоте и прямо пропорционально длительности такта, потому при изменении частоты оно не изменится. А вот при снижении час- тоты ниже 1 МГц количество энергии на такт, как мы видели, растет, и использо- вать такой режим уже невыгодно. Но еще раз повторим, что каждый конкретный случай нужно тщательно анализировать отдельно, — так, например, у старых кон- троллеров кривые потребления могут проходить иначе, чем у выполненных по мо- дернизированной технологии. По поводу потребления отметим еще такой момент. Выводы AVR могут в долго- временном режиме отдавать значительный ток (до 20—40 мА), однако не следует забывать об общем суммарном ограничении на потребление по выводу питания (обычно 200 мА, — см. табл. П2.3 в приложении 2). Следует также отметить, что при подаче аналоговых напряжений на входы АЦП входной цифровой КМОП- элемент (вход соответствующего цифрового порта) не отключается, и при значении такого напряжения вблизи порога срабатывания элемента это может приводить к возрастанию потребления за счет протекания сквозного тока через выходные кас- кады КМОП (в том числе иногда и при нахождении микросхемы в «спящем» ре- жиме, — см. главу 14). Указанного недостатка лишены некоторые контроллеры, выполненные по модернизированной технологии, но в общем случае и этот момент надо учитывать в проектировании. Примеры AVR-контроллеров В нашей небольшой книжке невозможно описать не только все нюансы всех моде- лей контроллеров семейства AVR, затруднительно даже бегло пробежаться по са- мым употребительным. Посмотрите на толщину изданий [6,7], в которых представ- лена лишь небольшая часть не самых современных представителей этого семейст- ва, и только в виде перевода сухой официальной документации, без развернутых примеров и пояснений. Поэтому мы поневоле ограничимся несколькими моделями, возможности которых покрывают большую часть потребностей пользователя при решении типовых задач. А по ходу дела постараемся обозначить пути распростра- нения этих решений на более «продвинутые» и современные модели. Три основных контроллера AVR, с которыми мы будем иметь дело в дальнейшем изложении, следующие: □ ATtiny2313 — старший (и один из первых) представителей семейства Tiny, он представляет собой модернизированную версию одной из самых удачных моде-
92^ Часть I. Общие принципы устройства и функционирования AtmelAVR лей еще семейства Classic. Весьма функциональный и одновременно компакт- ный контроллер в корпусе с 20-ю выводами. Обладает многими возможностями младших Mega, исключения: отсутствует АПЦ и некоторые расширенные функ- ции таймеров. Из других особенностей: отсутствуют аппаратные последова- тельные порты SPI и TWI (UART имеется). У ATtiny2313 также нет внутреннего монитора питания (системы BOD), потому при работе с EEPROM необходимо принимать отдельные меры. Зато, в отличие от младших Mega, снабжен допол- нительным внешним прерыванием типа pcint. Современная версия ATtiny2313V отличается расширенным диапазоном напряжений питания, в остальном все версии идентичны и работают при тактовых частотах вплоть до 20 МГц. □ ATmega8 — этот контроллер, выпускаемый в варианте DIP в корпусе с 28-ю вы- водами, у нас будет базовым. До сих пор он один из самых популярных кон- троллеров, в котором простота программирования сочетается с большей частью возможностей современных Mega. Из недостатков в сравнении с более совре- менными моделями (ATmega88 — предшественником ардуиновского ATmega328): отсутствуют внешние асинхронные прерывания INT2 и pcint, урезаны некоторые возможности таймеров и еще кое-что по мелочи. ATmega8 работает на тактовой частоте до 16 МГц, и в более современной версии— ATmega8A— отличается пониженным потреблением. Иногда мы будем обращаться к его ближайшему родственнику ATmegal6, также завоевавшему заслуженную популярность. Эта модель почти ничем не отличается от ATmega8, за исключением наличия пре- рывания int2, несколько более «продвинутыми» возможностями таймеров, и, главное, большим количеством выводов. □ ATmega8535— как и Tiny2313, представляет собой модернизацию одноимен- ного контроллера семейства Classic. Основные преимущества по сравнению с ATmega8: больше выводов (40-контактный DIP-корпус) и имеется асинхрон- ное прерывание INT2. Программирование столь же простое, потому мы его будем использовать вместо ATmega8 в случаях, когда требуются именно эти функции. Как и ATmega8, ATmega8535 допускает тактовую частоту до 16 МГц. Заметки на полях Отметим еще также давнюю модель АТтеда8515, которая по названию кажется похо- жей на АТтеда8535, но в реальности существенно от нее отличается (например, в АТтеда8515 отсутствует АЦП). У нее есть одна довольно редкая функция, которая еще присутствует только в некоторых старших моделях (у АТтеда328 ее нет, но эта функция имеется в АТтеда2560, лежащем в основе Arduino Меда). К контроллерам с такой функцией можно подключать внешнюю память типа SRAM с параллельным интерфейсом так, что последняя становится надстройкой, расширяющей адресное пространство встроенной SRAM. Всего таким образом становится доступно дополни- тельно 64 кбайт памяти, что может потребоваться в случаях обработки больших мас- сивов данных. Энергонезависимая память типа EEPROM так же просто не расширяет- ся, но при необходимости сохранять данные при отсутствии питания можно использо- вать решение фирмы Dallas Semiconductor (ныне подразделение фирмы Maxim) под названием NV SRAM (Nonvolatile SRAM). Там обычную SRAM просто-напросто запи- тали от встроенной литиевой батарейки, и обещают сохранность данных в течение 9 лет. При этом появляются такие, например, возможности, как восстановление памя- ти контроллера при сбоях в подаче питания, — с обычной EEPROM это не проходит из-за ограничений на количество допустимых перезаписей содержимого.
ГЛАВА 5 Подготовка к программированию МК AVR В работе с микроконтроллерами очень легко забыть, что контроллер — это всего лишь электронный компонент, пусть и достаточно сложный, но точно такой же, как светодиоды, транзисторы, усилители, компараторы и любые другие составляющие единой схемы. В компьютерных устройствах акцент давно перенесен с «железа» на программное обеспечение, и это совершенно справедливое положение, т. к. компо- ненты компьютера — отдельная отрасль электроники, полностью отданная на от- куп фирмам-производителям. Поэтому там все творчество «масс» давно сместилось в область создания программ, а по части «харда» остается лишь выбор между гото- выми компонентами того или иного вендора. У универсальных микроконтроллеров совсем другое предназначение — они разра- батываются, чтобы стать «кирпичиками» при создании самых разнообразных устройств. Причем зачастую успех зависит от верного выбора всех компонентов в совокупности— т. е. от того, что когда-то авторы замечательного учебника [12] назвали «искусством схемотехники». Программа для микроконтроллера в решении этой задачи иногда может занимать вообще последнее место и по трудозатратам, и по уровню необходимой квалификации. И успех в такой деятельности нередко зависит от схемотехника в гораздо большей степени, чем от программиста. О сказанном слишком часто забывают, и создание электронных приборов незамет- но перемещается в руки профессиональных программистов, которые слыхом не слыхивали о предельных мощностях и токах, насыщении магнитных сердечников, глубине обратных связей, показателях наработки на отказ, метрологических харак- теристиках и прочих премудростях науки электроники. Программисты привыкли к высокому качеству компьютерных компонентов и подсознательно считают, что фирма «хуже не сделает». А индустрия с радостью идет им навстречу, выпуская дешевые компоненты, условно-оптимизированные под представления среднестати- стического покупателя. Результаты можно видеть на примере, скажем, многочис- ленных датчиков температуры-влажности, совместимых с Arduino. За редчайшим исключением производители этих датчиков никогда не слышали о понятии «ка- либровка», а 9 из 10 моделей годятся в лучшем случае только для комнатных усло- вий.
94_ Часть I. Общие принципы устройства и функционирования AtmelAVR Заметки на полях Больше всего примеров неграмотного подключения плат Arduino связано с напряже- нием питания 5 В. По какой-то причине производители традиционно не указывают пределы величины тока, который может выдержать встроенный стабилизатор платы Arduino Uno при различных напряжениях подключенного адаптера Vin. Понять их мож- но — слишком сложная для любителя получается картина зависимости выделяющей- ся на стабилизаторе мощности от входного напряжения и нагрузки, чтобы можно было однозначно указать параметры. Между тем, для выбранного создателями платы Uno стабилизатора NCP1117 в миниатюрном корпусе SOT-223 предельная температура кристалла (150 °С) при рекомендуемом напряжении адаптера 12 вольт будет достигнута уже при нагрузке около 130 мА, а при некотором ее превышении сработает защита, и схема начнет вести себя непредсказуемо. И ведь во избежание такой ситуации дос- таточно ограничить входное напряжение — например, при Vin = 7,5-8 вольт предель- ное значение тока нагрузки сразу возрастает до 300 мА. С другой стороны, NCP1117 может выдержать и 20 вольт входного напряжения при соответствующем ограничении потребления (если больше ничего потребляющего более нескольких миллиампер, кроме самой платы, к нему не подключено). А для напряжения 3,3 вольта, наоборот, совершенно необоснованно указано ограничение тока 50 мА, тогда как примененный в Uno стабилизатор LP2985 допускает ток до 150 мА, и при входном напряжении 5 вольт, к которому он подключен на плате, далеко не достигнет опасных температур корпуса даже при этом максимальном значении тока (у меня есть подозрение, что цифра 50 мА задержалась в документации с каких-то старых ревизий платы). Пример некорректного подключения нагрузки к Arduino Uno при не оговоренном входном на- пряжении представляет почти любой сетевой ресурс, описывающий применение сер- вомоторов (см. главу 16). Ограничение тока также может наступать про подключении светодиодных дисплеев с достаточно большим числом светящихся сегментов. Если учесть, что производители клонов Arduino могут ставить вместо указанных типов ста- билизаторов все, что окажется у них под рукой, то неопределенность с питанием ока- зывается уже совсем неприемлемой, — типичный результат программистского подхо- да к проектированию. Мы не пойдем на поводу у этой тенденции. И в первую очередь постараемся отне- стись к программам для контроллера не как к самостоятельной сущности, требую- щей отдельной квалификации, а просто как к еще одному этапу при создании схем. Отсюда и принципы подхода к процессу программирования, который можно сфор- мулировать в трех словах: ничего сверх необходимого. Мы не станем терять время в поисках красивых и удобных инструментов отладки, в обсуждении возможностей и целесообразности построения микроконтроллерной операционной системы, в сравнительных оценках различных сред программирования. Мы потому и выбра- ли ассемблер, что он позволяет ничего этого не делать, а сосредоточиться строго на целевой задаче. Ассемблер без излишних сложностей Начнем мы со средств создания ассемблерных программ. За многие годы ситуация в этой области мало изменилась — ничего принципиально нового не предлагается, только обновления некоторых уже известных инструментов, и притом не всегда удачные. Мы пока попробуем обойтись вовсе без них — минимально необходимым набором инструментов. Это редактор текста, собственно ассемблер (компилятор текста в загружаемый НЕХ-файл), программа-загрузчик (соответственно выбран- ному вами программатору), а также еще некоторые необходимые файлы.
Глава 5. Подготовка к программированию MKAVR 95 Редактор ASM Editor Программы на ассемблере можно писать в любом текстовом редакторе. Не подой- дут разве что MS Word или OpenOffice — они слишком «навороченные», и простые файлы типа «чистый текст» в них получаются плохо. А нам здесь нужны именно средства создания чистого текста без каких-либо служебных заголовков, разметки, форматирования и иных включений, не относящихся к делу. Причем для ассембле- ра важны лишь символы из первой половины таблицы кодировки ASCII — т. е. цифры, знаки препинания и некоторые общеупотребительные значки, а также анг- лийские буквы, большие и маленькие. Поскольку эта часть таблицы кодов симво- лов одинакова для всех кодировок (исключение представляет разве что редко упот- ребляемый четырехбайтовый Unicode), то и установленная кодировка не имеет зна- чения, — а комментарии можете писать на любом языке, компилятором они все равно игнорируются. Иными словами, программу на ассемблере можно писать хоть в Блокноте или его многочисленных заменителях — компилятор вас поймет. Но на практике делать это неудобно. В текстовом редакторе для эффективного создания ассемблерных про- грамм желательны некоторые особые «фичи», которые мы сейчас приведем: □ автоматическая нумерация строк — она совершенно необходима потому, что компилятор при выводе ошибок и предупреждений указывает номер строки, и наличие их автоматической нумерации позволяет сразу и без разночтений найти ошибочный оператор в программе; □ подсветка синтаксиса — обычное свойство всех редакторов для создания про- граммных текстов. В тексте программы автоматически выделяются цветом или шрифтом служебные слова или целые фрагменты, отмеченные специальными старт-стопными символами (например, комментарии). Подсветка задается обычно в отдельных настройках (потому что для каждого языка программиро- вания она своя), и ее можно редактировать, подгоняя под конкретные условия; О возможность осуществлять компиляцию, не выходя из редактора, — если пер- вые две особенности имеют множество текстовых редакторов разной степени фирменности, то возможность компиляции прямо из редактора— считанные единицы. Конечно, можно обойтись и без этого (вообще без всего можно обой- тись, как мы говорили), но необходимость прыгать из приложения в приложение сильно замедляет процесс создания программы,— проверять созданный код с помощью попытки его скомпилировать иногда приходится ежеминутно. В итоге выбор-то получается не такой уж и большой. Идеально, конечно, если бы еще и загрузка программы в контроллер происходила в той же среде, но как раз это свойство необязательное. Для компиляции достаточно вызвать программу- ассемблер из командной строки, чтобы получить всю ее функциональность, и это не так уж сложно интегрировать в любую программу. А загружаем мы уже готовый вариант, что происходит относительно редко, и желательная функциональность загрузчика слишком обширна, чтобы ее было удобно отображать таким же об- разом.
96^ Часть I. Общие принципы устройства и функционирования Atmel AVR Всяческие интегрированные среды вроде Atmel Studio и прочих Proteus'oB все упо- мянутое умеют, конечно (и еще многое сверх того), но мы договорились обходить- ся простейшими методами. Вы можете порыться в закромах Интернета и рассмот- реть популярные среди программистов и весьма «крутые» MultiEdit'bi вместе с Notepad'aMH++ — они всем хороши, кроме запредельной «навороченное™» в со- временных версиях. Я вот уже два десятилетия не изменяю скромному ASM Editor, который идеально заточен именно под ассемблер. Притом, что последняя версия (2.2d) недавно справила пятнадцатилетие, и редактор заметно устарел— при активной работе начинает замечаться отсутствие некоторых привычных удобств. В современных версиях Windows приходится вспомнить, что есть такая штука, ко- торая называется «режим совместимости», — при установленной «совместимости с Windows XP» пропадают некоторые баги в пользовательском интерфейсе, отнюдь не присущие ASM Editor изначально. Но на основную функциональность это все никак не влияет, а замены этому редактору по простоте работы и удобству настроек под личные вкусы я так и не нашел, хотя регулярно предпринимаю поиски. Надо сказать, что само название ASM Editor достаточно распространенное, и по такому запросу вы можете найти очень много разных продуктов, похожих по на- значению. Вам же нужен тот ASM Editor, который располагается в разделе Про- граммы сайта avt-lab.ru. Устанавливать его не надо — просто разверните архив в удобное место на диске, причем желательно не в Program Files, а в отдельную пап- ку (у меня она традиционно носит название AVRTOOLS). Там же вы в дальнейшем разместите ассемблер и другие необходимые файлы, а также программу для загруз- ки и каталог со своими проектами. Такое компактное размещение позволит вам при необходимости скопировать на другой компьютер или на флешку всю папку AVRTOOLS целиком и сразу получить копию готовой и настроенной среды вместе со всеми проектами. Единственная привязка к Windows, которую целесообразно сделать, — ассоцииро- вать расширение .asm с редактором ASM Editor. Делается это обычным спосо- бом — щелкните в Проводнике Windows на файле с таким расширением правой кнопкой мыши, выберите опцию Открыть с помощью, отыщите ASM Editor (файл asm_ed.exe) и установите ассоциацию с ним. Это удобно еще и потому, что ASM Editor при открытии нового файла с программой через меню File | Open или через список недавно открытых файлов закрывает старое окно, а при вызове через Про- водник по расширению файла можно создать сколько угодно окон редактора с раз- ными проектами. Можно еще привязать расширение .hex к программе-загрузчику (о ней рассказано далее), если вы сочтете, что так удобнее. Там же, на сайте автора ASM Editor, отдельно доступен файл AVR.shk, содержащий схемы подсветки синтаксиса для AVR-ассемблера. Его надо будет подключить, т. к. по умолчанию ASM Editor настроен на работу с ассемблером MASM. Однако имеющийся там файл охватывает лишь небольшую часть служебных слов для очень ограниченного круга контроллеров. Поэтому я предлагаю вам сразу скачать с моего сайта по адресу http://revich.lib.ru/AVR/AVRshk.zip его доработанный ва- риант. Для подключения файла подсветки скопируйте его в папку, где находится редактор (файл asm_ed.exe). Затем запустите программу, обратитесь к опции меню
Глава 5. Подготовка к программированию MKAVR 97_ Highlight | Add new и введите имя файла AVR.shk в появившейся строке ввода. По- сле этого название AVR появится в меню Highlight отдельной строкой, которая должна быть вами выбрана. Если необходимо что-то изменить (в дополненном файле также представлены не все возможные варианты для различных контролле- ров), то подсветку можно будет исправить или дополнить по имеющимся образцам через опции меню Highlight | Keywords и Highlight | Start-stop keys. Много других настроек ASM Editor можно найти в меню Service | Properties, из них нам в дальнейшем понадобится изменить некоторые из находящихся на вклад- ке Project. Но сначала давайте обзаведемся другими необходимыми компонентами. Ассемблер Avrasm Во-первых, нам понадобится собственно ассемблер. В настоящее время целесооб- разно пользоваться последней его версией, которая носит название avrasm2 (а ис- полняемый файл, соответственно, avrasm2.exe). Нет никакого криминала в том, что- бы использовать устаревший вариант под названием avrasm32, но это просто не- удобно: он не поддерживает модели контроллеров, выпущенные примерно после 2010 года, не имеет некоторых расширенных возможностей, введенных в avrasm2, а также ориентирован на файлы определений, несколько отличающиеся по содер- жанию от более современных. Нужно учитывать, что avrasm2 строже старой версии по отношению к синтаксису, потому не исключено, что некоторые давно написан- ные программы для компилирования с его помощью придется подправлять. Кроме того, если у вас уже имеется старая версия Atmel Studio (не выше 4jc), to прила- гающиеся к ней файлы определений (inc-файлы, — о них рассказано далее) придет- ся заменить. Для того чтобы добыть avrasm2.exe, проще всего установить Atmel Studio и извлечь его из каталогов этого пакета. Так как до версий 4.x включительно в Atmel Studio входил старый avrasm32, то годится любая более поздняя. Проще всего скачать с сайта Microchip то, что там предлагается (на момент подготовки книги — это вер- сия Atmel Studio 7). При установке можете оценить, в какого монстра превратилась когда-то вполне компактная среда программирования, — достаточно указать, что заодно установится и Microsoft Visual Studio, и еще несколько подобных компонен- тов, так что придется раз десять, не меньше, нажимать на кнопочку I accept. Ассемблер должен находиться в папке с названием ...AtmelWmel Studio <номер вер- aw>\avrassembler. Скопируйте оттуда файл avrasm2.exe в папку под условным назва- нием AVRTOOLS, куда мы ранее поместили ASM Editor. Во-вторых, нам обязательно понадобятся включаемые файлы определений, в кото- рых мнемонические названия регистров привязаны к абсолютным адресам кон- кретных контроллеров. Без такого файла ассемблер будет выдавать ошибку на каж- дой строке в вашей программе, поскольку не будет знать, что означает PortA или DDRB (подробнее об inc-файлах рассказано далее в этой главе). В настоящее время эти файлы для всех контроллеров можно добыть только из Atmel Studio. В папке avrassembler должен находиться каталог с названием INCLUDE, который можно ско- пировать себе целиком, поместив его все в ту же AVRTOOLS.
98 Часть I. Общие принципы устройства и функционирования AtmelAVR После копирования ассемблера и папки с inc-файлами саму Atmel Studio со всеми сопутствующими компонентами можно и удалить, но советую подождать до под- робного обсуждения ее достоинств и недостатков в этой главе далее. Обустройство ассемблера Далее я буду ориентироваться на то, что вы используете ASM Editor, но в общем-то схема обустройства рабочей среды одинакова для любого редактора, поддержи- вающего компиляцию прямо из своего окна. Для начала работы нам предваритель- но нужно создать командный файл. Предположим, файл avrasm2.exe находится в упомянутой ранее папке C:\AVRTOOLS. Запустите Блокнот и введите следующий текст (соответственно измените путь, если папка другая): c:\avrtools\avrasm2 -fl %l.asm Строка эта может выглядеть и несколько иначе (пример вы найдете в главе 6). Со- храните эту строку в командном файле, дав ему имя, например asm.bat, и обязатель- но в той же папке, что и avrasm2.exe. Теперь запустите ASM Editor, выберите в меню пункт Service | Properties, в открывшемся окне найдите вкладку Project, а в ней — строку Assemble ASM file (на рис. 5.1 она выделена). В эту строку, как и показано на иллюстрации, введите путь к нашему bat-файлу. Больше ничего настраивать не требуется — остальные пункты можно оставить, как есть, или очи- стить — по желанию. Рис. 5.1. Настройка редактора ASM Editor Теперь по нажатию комбинации клавиш <Alt>+<A>, вне зависимости от того, где находится файл с редактируемой в настоящий момент программой, он скомпилиру- ется, и результат (т. н. hex-файл, о чем далее), если нет ошибок, окажется в той же папке, где и исходный текст. Одновременно с компиляцией отдельно открывается консольное окно с сообще- ниями компилятора (рис. 5.2). Сразу смотрите на последние строки, где должно
Глава 5. Подготовка к программированию MKAVR 99_ быть сообщение Assembly complete, 0 errors (ассемблирование выполнено, 0 оши- бок). Дополнительно в этой же строке должно стоять и 0 warnings (т. е. О преду- преждений), хотя, даже если предупреждения и есть, на процессе компиляции это не отразится. Если ошибок действительно не найдено, то ищите готовый hex-файл в папке с исходным текстом. В противном случае hex-файла не окажется, а в этом окне вам будут выведены номера содержащих ошибки строк в исходном тексте (самой программы или включаемых файлов) и примерное описание проблемы. На- пример, сообщение Undefined variable reference (ссылка на неопределенную пере- менную) означает, что у вас в этой строке имеется идентификатор, который нигде не определен, и т. д. Благодаря таким сообщениям, программный текст удается сра- зу вычистить от синтаксических ошибок. Рис. 5.2. Консольное окно результатов компиляции Обратите внимание, что предыдущий вариант hex-файла, если он существовал пе- ред началом новой компиляции, будет стерт в любом случае, и при наличии оши- бок вы окажетесь без какого-либо скомпилированного варианта. Поэтому при же- лании сохранить промежуточные варианты отлаживаемой программы оба файла — ASM с исходным текстом и скомпилированный HEX — надо скопировать в какую- нибудь другую папку или переименовать. Кроме этого, при успешной компиляции в окне сообщений можно увидеть точный размер скомпилированной программы в байтах, общий объем всех разновидностей памяти, включая EEPROM, и процент, который в них займет программа (см. рис. 5.2). Отметим, что предыдущие версии компилятора Atmel выдавали толь- ко объем скомпилированной программы в словах (words), и для получения размера в байтах приходилось умножать это число на два, а неиспользованную память вы- числять самостоятельно. Учтите еще, что объем hex-файла размеру программы со- ответствовать не будет (подробнее об этом рассказано далее), так что узнать точ- ный объем занимаемой памяти можно либо только из окна компиляции, либо из программы-загрузчика, прилагаемой к программатору.
100 Часть I. Общие принципы устройства и функционирования AtmelAVR Об AVR Studio Приступая к этой книге, я долго размышлял над целесообразностью включения в нее описания работы в Atmel Studio (до версии 5 включительно — AVR Studio). С одной стороны, это заслуженный инструмент, которым пользуются тысячи раз- работчиков, с другой — существенная часть ее функций без особых проблем реали- зуется куда более простыми методами, а без оставшихся в наших несложных зада- чах можно обойтись. Окончательное решение помогла мне принять попытка уста- новить последнюю (седьмую) версию Atmel Studio— это оказался тяжеленный и неповоротливый монстр объемом под гигабайт, который уже одним только коли- чеством проблем в процессе установки напрочь отбивает всякое желание разби- раться с ним далее. Недаром достаточно подробные описания работы в Atmel Studio, которые можно встретить в литературе и в Интернете, большей частью относятся к версии 4jc, до- вольно-таки компактной и не предъявлявшей особых требований к компьютеру. Как мы уже говорили, с «нашими» весьма древними контроллерами можно разо- браться и с помощью этой старой версии, включавшей ассемблер avrasm32, но он все-таки сильно устарел, так что это будет уже не изучение AVR, а одноразовое решение для конкретного случая. Давайте разберемся, чего же мы лишаемся при отказе от освоения Atmel Studio (на- помню, что речь идет не о сложных программных проектах, а о решении относи- тельно простых любительских задач). Прежде всего, мы будем вынуждены обхо- диться без отладчика. Профессиональные разработчики уделяют этому вопросу большое внимание — такое, что средства отладки внедряются в сами контроллеры, невзирая на их усложнение (речь идет об интерфейсах debugWare или более «про- двинутом» JTAG). Для работы с ними нужны соответствующие программные инст- рументы (и Atmel Studio необязательно самый удобный из них), а также и опреде- ленные схемотехнические меры. Именно для таких целей выпускаются специаль- ные фирменные отладочные комплекты, достаточно дорогие, кстати. Подробное обсуждение этих вопросов представляет отдельную и довольно обширную тему, поэтому тех, кто хочет получить хотя бы поверхностное представление о ней, от- сылаем к [2]. Заметки на полях Хотя наша книга и не посвящена Arduino, но стоит отметить интересную возможность программирования Arduino через Atmel Studio (версии не ниже 6). Для этого необхо- дима загрузка надстройки Visual Micro, с которой можно познакомиться на посвящен- ном ей сайте visualmicro.com (в том числе и на русском языке). Создатели надстрой- ки уверяют, что там «все, как в Arduino» (включая доступность всех инструментов и библиотек для Arduino IDE), но при этом доступны также и все возможности Atmel Studio: т. е. эмулятор, трассировщик, отладчик и т. д. Отладку можно производить и без специальных аппаратных средств, на программ- ном эмуляторе — наличием такого инструмента и отличается Atmel Studio (и она не единственная такая). Беда в том, что разработчики подобных средств всегда пы- таются объять необъятное и напихать в них максимум функциональности, отчего
Глава 5. Подготовка к программированию MKAVR 101_ они превращаются в неподъемных чудищ, требующих специального освоения, зна- ния ограничений и методов обхода содержащихся в них ошибок, которых там пре- достаточно (Atmel Studio последних версий тому типичный пример). Если вы дос- таточно хорошо знакомы с интегрированными средствами разработки приложений под Windows вроде Visual Studio или Delphi, то, несомненно, будете страдать от отсутствия трассировщика, возможности установить точки остановки выполнения программы и поменять по ходу дела значение переменных или условий. Легче будет тому, кто такого никогда не изучал, — не зная о таких возможностях, обхо- диться без них гораздо проще. Ведь обходятся как-то в Arduino без официального отладчика, и ничего, правда? Большая часть программ в этой книге также поддается простейшей отладке с по- мощью мультиметра или светодиодов-пробников, в крайнем случае понадобится осциллограф (но он все равно рано или поздно понадобится и вовсе не только для именно отладки, сами увидите). Мы будем иногда оставлять в программах заком- ментированные строки, предназначенные специально для отладки такими способа- ми. Однако уже в главе 7 вы встретите изрядно запутанную по логике работы про- грамму перемигивания разноцветными светодиодами, которую так и хочется запи- хать в отладчик и пройти по шагам. Для этой цели мы далее (см. главу 15) обсудим практические способы выполнения отладки программ единственным доступным без дополнительных сложностей способом — в точности так же, как это делается в Arduino, т. е. с помощью последовательного порта. Это, безусловно, примитивный суррогат настоящего отладчика, но в большинстве случаев его будет вполне доста- точно. Я обсуждение UART потому и отнес в конец книги, что в решении типовых задач мы постараемся обходиться без специальных средств отладки, а потребовать- ся они могут при самостоятельном проектировании уже законченных устройств. Способы загрузки программ в контроллер Когда-то, в начале эры господства микропроцессоров, это была, пожалуй, самая сложная и специфическая тема, представлявшая существенное препятствие для инженера-электронщика, вознамерившегося перейти от обычных логических схем к микроэлектронике. Не так сложно освоить программирование, как смириться с необходимостью городить специальный программатор, требующий нескольких напряжений питания (или искать, где бы приобрести готовый, что в те годы было непросто). Ко всему прочему, никакой флеш-памяти еще не существовало, потому программы в микропроцессорные устройства можно было либо прошивать одно- кратно, без возможности их изменения (так называемая OTR-память), либо возить- ся с предварительным стиранием памяти с помощью ультрафиолетовой лампы (UV-память). Все это, конечно, популярности микропроцессорной технике не до- бавляло. Появление в 1990-х годах EEPROM и флеш-памяти с возможностью многократного перепрограммирования и притом не требующих при этом отдельного повышенного напряжения питания, совершило настоящую революцию. Контроллеры AVR по- явились в разгар этого процесса, и своей популярностью в немалой степени обяза-
102 Часть I. Общие принципы устройства и функционирования Atmel AVR ны тому обстоятельству, что с самого начала предусматривали возможность в том числе и программирования прямо в устройстве. Все AVR, конечно, поддерживают и возможность загрузки программы через от- дельный (параллельный) программатор, как наиболее универсальный способ. При отсутствии источника внешнего тактового сигнала параллельный программатор является единственно возможным способом оживить контроллер, если в нем слу- чайно были неправильно запрограммированы конфигурационные ячейки (см. далее в этой главе). Но параллельный программатор — сложное универсальное устройст- во, которое обычно рассчитано на многие тысячи разновидностей микросхем, стоит немалых денег, и приобретать его только для этой цели, мягко говоря, нецелесооб- разно. Для полноты картины отметим, что, помимо всего этого, для многих моделей есть еще один способ программирования— с помощью JTAG, стандартизированного (IEEE Std 1149.1-1990) интерфейса для загрузки, отладки и тестирования микро- контроллерных устройств, о котором мы уже упоминали. Но мы не будем здесь на нем останавливаться — в любительских конструкциях он не применяется. Поэтому мы перейдем сразу к последовательному способу программирования «прямо в схе- ме» с помощью ISP-программаторов. ISP-программаторы Сокращение ISP расшифровывается как In-System Programmer (внутрисистемный программатор). Программирование при этом производится через последовательный интерфейс, аналогичный по устройству интерфейсу SPI, потому названия выводов программирования такие же, как и у этого интерфейса: MISO, MOSI и SCK. В большинстве контроллеров выводы программирования совпадают с выводами собственно SPI, но не у всех, так что надо быть внимательными. Для очистки со- вести отметим также, что последовательное программирование поддерживают не все контроллеры AVR из семейства Tiny, но сейчас из таких остался, кажется, только один ATtiny28L (который ущербен еще и в других отношениях), потому можно спокойно об этом ограничении забыть. ISP-программаторы не предполагают отдельного устройства для подсоединения и питания программируемой микросхемы, а заканчиваются обычным плоским кабе- лем с двухрядным гнездовым разъемом типа IDC, который подсоединяется к спе- циально предусмотренной штыревой части, располагаемой на плате вашего макета. Питание на программатор при этом поступает от самой схемы, поэтому в разъеме есть отдельная линия питания. Если вы не хотите загромождать готовую схему лишними элементами, то несложно соорудить отдельную платку с панелькой и таким разъемом и, при необходимости что-либо изменить, загружать через нее исправленную программу в контроллер, извлеченный из схемы (или просто соорудить временный программатор на макет- ной плате, — о чем рассказано далее). Мы уже упоминали в главе 4, что много раз так делать не рекомендуется, поскольку можно повредить выводы микросхемы в DIP-корпусе.
Глава 5, Подготовка к программированию MKAVR 103 Подробности Процесс последовательного программирования начинается с того, что на вывод SCK подается напряжение уровня «земли», далее на RESET — короткий положительный импульс, и затем последний также оставляют в состоянии низкого уровня не менее чем на 20 мс (можно предварительно подключить эти выводы к низкому уровню, а за- тем включить питание контроллера). При этом микросхема переходит в режим ожида- ния команд программирования. Поэтому установка программирующего разъема может в некоторых случаях снижать помехоустойчивость схемы — такое сочетание уровней иногда может возникнуть в процессе эксплуатации из-за наводок. Однако на практике для этого нужно слишком невероятное сочетание условий при очень высоком уровне помех, так что вероятность возникновения такой ситуации можно не принимать во внимание. Но и добиться ее полного исключения тоже не очень сложно: на всякий случай стоит устанавливать по выводам программирования внешние подтягивающие резисторы (как это обсуждалось в главе 4) — они эффективно гасят возможные помехи. Надо также отметить, что использованию программирующих выводов в качестве обычных портов ничто, в общем, не мешает. Единственное, чего нельзя делать с вы- водами программирования, если предполагается их использовать в таком качестве, — подсоединять их к выходам какой-то другой микросхемы (или, например, к коллектору открытого транзистора), — тогда и программирования не получится, и вообще можно повредить и схему, и программатор. Можно защититься и от этого (установить по дио- ду последовательно с резисторами 510 Ом — анодом к выводу МК и контактам разъ- ема), но смысла городить подобные сложности нет, — проще взять контроллер с большим числом выводов или программировать его отдельно от схемы. Способы подключения ISP-программаторов к контроллеру стандартизированы, и их разнообразие сводится всего к двум разновидностям одного и тоже типа двух- рядных игольчатых разъемов типа IDC: 6- или 10-контактной. На плате устанавли- вают штырьковую часть, на плоском кабеле, идущем от программатора, — гнездо- вую. Следует отметить, что название IDS относится к разъемам с корпусом, исклю- чающим неверную ориентацию при подключении, но он занимает больше места, и его наличие усложняет разводку платы, потому в реальности на платах обычно ставят бескорпусной вариант того же типа разъема, более известный под названием PLD. Внешний вид штыревой части одной из разновидностей программирующего разъ- ема— 6-контактной— можно увидеть на любой плате Arduino (рис. 5.3). При установке бескорпусного разъема PLD на плату следует всегда помечать вывод номер 1, чтобы не перепутать ориентацию при подключении (на платах Arduino он обычно помечен точкой или цифрой 1). На кабельной части первый вывод всегда находится с той стороны разъема, куда подходит крайний проводник плоского ка- беля, помеченный красным. Сразу разберемся с нумерацией выводов штырьковых разъемов и с двумя разно- видностями стандартной разводки. Нумерация контактов разъемов типа PLD и IDC отличается от обычной и делается не в обход, как у микросхем, а следующим обра- зом: если смотреть на штыри сверху (т. е. со стороны подсоединения ответной час- ти), то вывод номер 1, как и положено, находится слева внизу, вывод 2 — над ним, вывод 3 — следующий за первым в нижнем ряду и т. д., т. е. нижний (левый на рис. 5.4) ряд— это все нечетные, а верхний (правый на рис. 5.4)— все четные контакты. Это сделано потому, что такие разъемы могут иметь не строго фиксиро-
104 Часть I. Общие принципы устройства и функционирования AtmelAVR ISP-разъем Рис. 5.3. Программирующий ISP-разъем на платах Arduino Uno (слева) и Arduino Nano (справа) Рис. 5.4. Разводка выводов 6-контактного (слева) и 10-контактного (справа) ISP-разъемов в схеме переходника от одной конфигурации к другой ванное количество контактов, но при любом их количестве имеющиеся контакты будут нумероваться одинаково. На рис. 5.4 показана разводка выводов для обеих разновидностей. Отличаются они, как видите, только количеством выводов «земель» (GND). Вывод 3 в 10-контактной разновидности никуда не подключается и иногда используется различными про- граммами для дополнительных функций вроде подключения сигнального свето- диода. Сама Atmel когда-то ввела минимальную 6-контактную разновидность, ко- торая встречается чаще и считается «более стандартной» — на нее ориентирована большая часть программаторов, как фирменных, так и самодельных. Поскольку на платах могут встречаться оба вида разъемов (6-контактный занимает банально меньше места, потому и встречается чаще, но это вовсе необязательно), то в хозяй- стве удобно иметь переходник с одного вида на другой. На таком переходнике со стороны, которая подключается к плате, должна быть гнездовая часть штырькового разъема (IDC-10F или IDC-6F в корпусе с выступом или соответствующий PBD без
Глава 5. Подготовка к программированию MKAVR 1(05 корпуса), со стороны программатора— штырьковая (IDC-6M/IDC-10M, соответст- венно, или PLD, как на плате). Подробности Можно не заморачиваться никакими переходниками, если макет устройства собран на обычной беспаечной макетной плате. Необходимые для программирования выводы контроллера: MISO, MOSI и SCK, а также RESET «земля» (GND) и питание Vcc (см. разводку выводов в «даташите» на конкретный контроллер) — соединяются с разъемом программатора обычными проводами-перемычками, идущими в комплекте к макетной плате. Штекеры перемычек отлично входят в гнездовой разъем типа ЮС, и соединение при этом не зависит от числа контактов со стороны программатора. «Лишние» выводы GND в 10-контактном варианте можно при этом для надежности соединить с «землей» макетной платы все параллельно, а можно ограничиться только одним (любым) из них. Точно так же можно быстро соорудить временный переходник для загрузки любого контроллера, извлеченного из схемы, где программирующий разъем не был установ- лен, — для этого, кроме макетной платы с перемычками в комплекте, понадобится па- нелька, а также кварцевый резонатор с конденсаторами или внешний генератор, если в контроллере установлен соответствующий режим работы. Причем при программи- ровании частота кварца или внешнего генератора не имеет значения — можно всегда использовать одни и те же компоненты. Но такая благостная картина всеобщей стандартизации касается только разъемов. Самих программаторов— тьма-тмущая. Так как протоколы программирования AVR расписаны в подробностях в «даташите» каждого контроллера, то каждый второй когда-то считал своим долгом изобрести собственный программатор. С тех пор ситуация несколько нормализовалась, но в результате встречающиеся на рынке программаторы очень разные по удобству, сложности конструкции (и, соответст- венно, по цене) и пригодности работы в различном окружении. Еще можно встре- тить распространенные в свое время простейшие программаторы, которые может за полчаса изготовить каждый, — они подключались к портам LPT или СОМ и пред- ставляли собой просто переходник от одного разъема к другому. При этом вся нагрузка по реализации протокола ложилась на программу, которую написать по силам даже не очень опытному программисту. Другим полюсом стали фирменные программаторы со всяческими гарантиями, в том числе и по части работы на со- временных компьютерах, и по части совместимости с Atmel Studio или другими системами разработки и отладки. Есть и совсем «продвинутые» модели, совме- щающие программатор и эмулятор-отладчик, с соответствующей стоимостью. Разнообразие ISP-программаторов столь велико, что я не буду даже пытаться здесь обозреть хотя бы основные виды. Если кто заинтересуется, то автор [2] попробовал немало разновидностей (хотя и достаточно давно) и приводит их плюсы и минусы. По большому счету все программаторы делают (или, по крайней мере, должны делать) одно и то же, и вся задача выбора сводится к поиску модели, которая бы делала все это наиболее надежным и беспроблемным образом. На мой взгляд, цена при этом не имеет принципиального значения: программатор вы приобретаете не на один раз, нужен он будет постоянно, и вопросы удобства пользования тут на первом месте. Цена фирменных ISP-систем, если не считать очень уж «наворочен-
106 Часть I. Общие принципы устройства и функционирования Atmel AVR ных», не выходит за пределы эквивалента примерно 50 долларов, что величина со- вершенно не смертельная, — приличный мультиметр обойдется дороже. Я тут укажу на программатор, которым в разных модификациях пользуюсь уже в течение двух десятилетий, и ни одного значимого недостатка до сих пор не вы- явил. В последней версии этот программатор называется AS-4E (рис. 5.5), подклю- чается к U SB и имеет гальваническую развязку схемы и компьютера, что значи- тельно снижает вероятность повреждения и того, и другого при каких-либо про- блемах с питанием. При этом питается программатор, как мы уже неоднократно говорили, от схемы, а не от USB. Программирующий разъем— 10-контактный (разводка как на рис. 5.4, справа), и на него будут ориентированы все схемы в этой книге. т. J00 : й' '-v\- '■:•'- '"- > tS;<% :';% Рис. 5.5. Программатор AS-4E (фото с сайта фирмы AS-kit hardware) Программатор AS-4E, как и предыдущие версии AS-программаторов, совместим с Atmel Studio, без проблем работает в любых версиях Windows и позволяет «про- шивать» прямо в системе многие микросхемы Atmel (не только 8-разрядные AVR). Причем интерфейс управляющей программы ASISP настраивается на русский язык и очень нагляден. Когда с появлением семейств Tiny и Mega стали появляться со- общения о сложностях с программированием fuse-битов (о них рассказано далее), я долго не понимал, в чем там проблема — в программе для AS-4 ничего перепу- тать просто невозможно. Поддерживается пакетная работа (когда стирание, запись и проверка объединяются в одну операцию), а также имеется функция перепро- шивки самого программатора, если он вдруг начинает сбоить. Функция эта мне ни разу не пригодилась, но, вероятно, может быть полезной при необходимости рас- ширить список поддерживаемых микросхем. Методику работы с программой ASISP мы продемонстрируем в главе 6, когда перейдем непосредственно к про- граммированию. Заметки на полях Если вы работаете на настольном ПК и у вас есть возможность вставить в него до- полнительную плату с аппаратными СОМ-портами (об этом мы говорили в главе 4), то лучше приобрести не USB-вариант программатора (AS-4E), а традиционный AS-2M,
Глава 5. Подготовка к программированию MKAVR 1_07_ подключаемый через RS-232, — он вполне доступен на сайте as-kit.ru наряду с со- временными версиями. AS-2M делает все то же самое, работает с той же программой ASISP, но имеет одно маленькое преимущество, заключающееся в том, что аппарат- ный СОМ-порт физически отделен от виртуальных, создаваемых различными адапте- рами USB-COM. С точки зрения Windows, AS-4E — точно такой же адаптер, потому если вы подключите в USB одновременно, например, программатор и адаптер USB- UART для чтения данных с контроллера (что очень удобно для отладки программ, — см. главу 75), то два драйвера этих двух портов, не исключено, будут конфликтовать, и придется их подключать по очереди. Подключать к СОМ-порту именно программа- тор проще, чем адаптер, т. к. он, кроме физического СОМ-порта, больше ничего не требует, и все остальное останется по-старому. Arduino как ISP-программатор Честно говоря, завести разговор за эту тему меня заставила исключительно ее по- пулярность — на различных сетевых ресурсах можно встретить более или менее дилетантские откровения «чайников», вдруг обнаруживших, что контроллеры AVR — это совсем не только ATmega328, и решивших, что именно таким путем их проще всего осваивать. Давайте для начала немного разберемся в том множестве возможностей, скрывающихся под единым заголовком «Программирование кон- троллеров AVR через Arduino», попробуем их классифицировать и оценить с точки зрения практической полезности. Самая простая и очевидная возможность обусловлена наличием ISP-разъема — плата Arduino при этом используется просто как адаптер. Конечно, так можно ис- пользовать только Arduino Uno, поскольку «родной» контроллер при таком исполь- зовании извлекается, чтобы не мешал. Далее вы соединяете программируемый кон- троллер с ISP-разъемом через выводы платы (12 — MISO, 11 — MOSI, 13 — SCK, Res — Reset, а также GND и Vcc) и программируете его обычным способом, через любой ISP-программатор. Вы уже, конечно, заметили неудобства: если ваш кон- троллер запрограммирован на работу от внешнего кварца, то нужно еще дотяги- ваться до выводов XTAL1 и XTAL2 в панельке извлеченного «родного» контрол- лера, а если от внешнего генератора, то как-то пристраивать его к плате. Потому относительно удобно этим способом пользоваться только в случае ATmega8 и всех его родственников в 28-выводном корпусе DIP — у них совпадает разводка выво- дов, потому этими контроллерами просто заменяют «родной» в панельке, и ника- ких проводов никуда тащить не требуется. Хотя, на мой взгляд, это ничуть не удобнее описанного ранее способа подключения через макетную плату. Вторая возможность— использование среды Arduino IDE вместо программы- загрузчика для загрузки AVR-контроллеров через какой-либо из обычных про- грамматоров (без участия платы Arduino). Подразумевается, что при этом вы за- гружаете туда скетч, созданный в Arduino IDE. Это возможно только для програм- маторов, поддерживаемых средой, причем на первый взгляд их достаточно много (см. меню Arduino IDE Инструменты | Программатор...), но из популярных там только стандартный AVR ISP и USBasp, остальные встречаются на практике значи- тельно реже. Поэтому такую возможность целесообразно применять для загрузки
108 Часть I. Общие принципы устройства и функционирования Atmel AVR в саму плату Arduino внутреннего загрузчика, работающего через последователь- ный порт (Bootloader'a), если он был по каким-то причинам испорчен (вопрос о возможности и целесообразности установки Bootloader'a на обычные контролле- ры мы рассмотрим в главе 6). Третья возможность интереснее: плату Arduino, как и любой универсальный кон- троллер, можно запрограммировать для работы в качестве программатора (в том числе, кстати, и универсального параллельного, на чем мы не будем останавливать- ся). Для этого удобнее применять опять же Uno, но в принципе можно в любую из плат загрузить соответствующий скетч, который называется ArduinoISP. Он нахо- дится в папке с интегрированными примерами (Examples) и доступен прямо из сре- ды через меню Файл | Примеры (в Arduino IDE 1.6.9 он расположен последним, под номером 11). Загрузив его в контроллер платы обычным путем из Arduino IDE, вы превратите Arduino в последовательный программатор, с помощью которого можно осуществлять все описанное в предыдущем абзаце, только с меньшим коли- чеством проблем. Внешний контроллер (или другая плата Arduino) подключается так же, как в первом случае, т.е. к выводам 12 (MISO), 11 (MOSI), 13 (SCK) и т. д. — теперь они играют роль просто интерфейса SPI. За одним важным исклю- чением — вывод RESET программируемого контроллера подключается к выводу 10 (вывод SS интерфейса SPI), а не соединяется с выводом Reset платы, как ранее. Заметки на полях Я лично для указанных целей не стал бы долго разбираться со средой, а просто ис- пользовал бы имеющийся программатор в штатном режиме. В случае Bootloader'a нужный НЕХ-файл можно найти в папке установленной у вас Arduino IDE в каталоге hardware\arduino\avr\bootloaders. He забудьте еще установку «фьюзов», необходимую конфигурацию которых можно найти в файле hardwareXarduino\avr\boands.txt, — при за- грузке из Arduino IDE среда их устанавливает сама, а здесь придется вручную (под- робности приведены в следующем разделе). Но не все знают, что подготовленный и скомпилированный в среде Arduino скетч тоже можно загрузить в любой контроллер таким же способом. Нужные НЕХ-файлы придется, правда, поискать, но это не очень сложно. Результаты деятельности Arduino IDE в случае Windows 7 и более поздних версий размещаются в недрах папки Пользователи\<имя nonb3oeamenn>\AppData\ LocahTemp. Там вы найдете кучу папок с расширением tmp, название которых начина- ется с build (например, build290388496895462656.tmp) — внутри одной из них и находится искомый НЕХ-файл, имя которого должно совпадать с именем файла скетча. Заметим еще, что при программировании из среды Arduino нет доступа к конфигу- рационным ячейкам, а принудительное их изменение внешним программатором может привести к неработоспособности загруженного скетча, и почти наверняка выведет из строя Bootloader, если вы загружали именно его (подробности на эту тему приведены далее). Из этого краткого обзора видно, что из Arduino программа- тор получается плохой и пригодный в основном для каких-то специфических це- лей. Тех же целей можно достичь быстрее и без проблем с помощью обычного про- грамматора в штатном режиме.
Глава 5. Подготовка к программированию MKAVR 109 Конфигурационные ячейки (fuse-биты) В англоязычной инструкции конфигурационные ячейки по традиции называют fuse bits, что не совсем точно отвечает их назначению: fuse в переводе означает «предо- хранитель», а fuse-биты всего лишь отвечают за начальную конфигурацию кон- троллера. В русскоязычной среде их часто называют фъюзами. Они задают началь- ное состояние контроллера и могут быть изменены только с помощью программа- тора— загруженная программа к ним может иметь доступ только на чтение. Причем наличие загрузчика через последовательный порт (Bootloader'a) уже пред- полагает определенную конфигурацию fuse-битов, при их изменении он может просто не заработать, и именно поэтому в Arduino конфигурационные ячейки дос- тупны только теоретически, и достаточно сложным путем (через команды загруз- чика avrdude). Прибегать к этому способу и менять что-то в установках конфигура- ционных ячеек в Arduino решительно не рекомендуется. «Фьюзы» представляют собой байтовые ячейки обычной памяти EEPROM, с чем и связаны сложности в истолковании их состояния. Ячейка EEPROM по умолчанию находится в состоянии логической единицы, и для перевода в состояние логическо- го нуля ее надо «запрограммировать». Сотрудники фирмы Atmel когда-то посчита- ли терминологию «запрограммированная» {programmed, состояние логического нуля) и «незапрограммированная» (unprogrammed, состояние логической единицы) естественной, в чем жестоко промахнулись, а исправлять задним числом было уже, конечно, поздно. Автор [2] уверяет, что все дело в разнице между мышлением про- граммистов и электронщиков, — именно первые заварили кашу, интерпретировав programmed как «включено» (In), a unprogrammed как «выключено» (Out), что мо- жет быть и естественно с точки зрения языка, но обычно в электрике и электронике «включено» все-таки подразумевает логическую единицу, а не наоборот. В результате в одних прошивающих программах отмеченная ячейка (установленная галочка или крестик) означает, что опция «включена» («запрограммирована», т. е. в fuse-бит записывается ноль), а в других, написанных под контролем электронщи- ков, вся промежуточная терминология игнорируется, и отмеченная ячейка просто означает, что в нее записана единица. В этой ситуации меньше шансов что-то пере- путать имеет тот, кто не изменяет одной и той же программе прошивки, как автор этих строк. Но иногда приходится переходить из программы в программу и на этот случай приведем общее правило для всех загрузочных программ без исключения: перед тем, как что-либо менять в конфигурационных ячейках, обязательно загрузите их текущее состояние. Это надо не только для обретения однозначности в вопросе, что означает установленная галочка, но и потому, что иначе вы рискуете записать про- извольную конфигурацию, пропустив установку тех «фьюзов», которые должны быть установлены для нормальной работы контроллера. Из таких ячеек по крайней мере одна заведомо будет отмечена, как «запрограмми- рованная», т. е. находящаяся в состоянии логического нуля. Эта ячейка имеется во всех контроллерах, допускающих последовательное программирование (к тем не- многим, у которых ее нет, вы все равно доступа через ISP-программатор не имеете),
110 Часть I. Общие принципы устройства и функционирования Atmel AVR и она носит название spien (SPI Enable). При логической единице в этой ячейке ISP-программирование запрещается. После загрузки текущей конфигурации по со- стоянию spien можно определить, что именно в вашем загрузчике означает отме- ченная или неотмеченная ячейка. Есть и второй такой же «фьюз», который при последовательном программирова- нии, наоборот, всегда должен быть «^запрограммирован», т. е. находиться в со- стоянии логической единицы — он называется rstdisbl (reset disable), и при уста- новке в состояние нуля отключает вывод RESET, превращая его в один из обыч- ных выводов. В отличие от spien, ячейка rstdisbl имеется не во всех контроллерах, а только в тех, где выводов у корпуса относительно немного, и каждый вывод пор- та на счету (например, на рис. 5.7 и 5.8 она есть, а на рис. 5.9 — отсутствует). Оба этих «фьюза» могут отрубить возможность ISP-программирования, но важнейшее отличие spien от rstdisbl состоит в том, что spien невозможно изменить через по- следовательный программатор, а вот rstdisbl— запросто. И по этой причине его лучше не трогать вовсе: при случайном его программировании для восстановления доступа к контроллеру придется добывать дорогущий параллельный программа- тор — более простыми методами тут не отделаешься, так что проще бывает кон- троллер просто выбросить. По сути «фьюзом» rstdisbl возможные катастрофические вмешательства в конфи- гурационные ячейки и ограничиваются. Все остальное можно исправить через ISP- программатор повторным изменением состояния просто так или с применением некоторых несложных подручных средств. К последнему случаю относится извест- ная ошибка с fuse-битами группы cksel (обычно их четыре — от ckselo до cksel3), задающими режим тактирования. Их состояние перепутать проще всего, т. к. со- стояние оооо у всех контроллеров означает тактирование от внешнего генератора, а оно симметрично состоянию mi, которое означает работу от кварцевого резона- тора. Почему-то страх перед возможностью их ошибочного программирования ис- пытывают даже опытные пользователи. Как мы уже говорили, исправить положение можно за пару минут, если у вас есть готовый цифровой генератор (см. главу 4\ или максимум за полчаса, которые потребуются, чтобы найти в закромах детали для сборки на макетке простейшего генератора. Частота не имеет значения — кое-кто из радиолюбителей уверяет, что освоил способ временной подачи тактовых импульсов с помощью пальца, прило- женного к выводу XTAL1. Подробности На всякий случай приведем на выбор две схемы простейших генераторов, пригодных для этой цели. Для схемы, показанной на рис. 5.6, слеве, имеется возможность выбо- ра из весьма широкого ассортимента микросхем — кроме указанных на схеме отече- ственных микросхем серии 561 и их импортных аналогов, подойдут микросхемы с ин- верторами отечественных серий 1561, 1564, импортных 74НС и т.д.. Разводка выво- дов на рис. 5.6 показана для 561ЛА7 или ЛЕ5, для их аналогов из других серий разводка будет отличаться. Можно также использовать любую микросхему с одновхо- довыми инверторами (561ЛН2, CD4049 и их аналоги из других серий). В схеме, пока- занной на рис. 5.6, справа, взят за основу таймер 555 (отечественный аналог КР1006ВИ1,
Глава 5. Подготовка к программированию MKAVR 111 его можно заменить на КМОП-версию ICM7555 или КР1441ВИ1). Эта схема сложнее и требует больше деталей, но сам таймер по неведомой мне причине жутко популярен у радиолюбителей и вполне может оказаться у вас под рукой. +5 В к выводу 14, GNO к выводу 7 10-100 нФ | п DD1.1 1-100кОм 5 DD1.2 тт OD1 561ЛА7, 561ЛЕ5 CD4011, СО4001 1-100 кОм I • R2*R1 I «■ 10-100 нФ ■ h La ■ < 1 и- +5 В 555 4=' ; юиоо нф Рис. 5.6. Простейшие генераторы При указанных на схемах величинах резисторов и конденсаторов частота окажется в пределах от десятков герц до десятков килогерц. Никто не мешает раздвинуть гра- ницы указанных предельных значений, только следите, чтобы произведение R (в омах) на С (в фарадах) не выходило за границы примерно 10"2—10", иначе частота окажется либо слишком маленькой, либо слишком большой для нормальной работы классических серий КМОП 561 или CD4000. На трех иллюстрациях, приведенных далее (рис. 5.7-5.9), в окне установки fuse- битов программы ASISP показано состояние по умолчанию конфигурационных ячеек трех «наших» типов контроллеров, упомянутых в конце главы 4. Как видите, перепутать тут что-либо достаточно сложно: отжатая кнопка означает единичное состояние ячейки, нажатая— нулевое, а для ясности с «запрограммированным» состоянием разработчики программы специально предусмотрели соответствующую памятку. Конфигурационные ячейки делятся на три группы размером в один байт каждая, называемые Fuse Low Byte, Fuse High Byte и Fuse Extended Byte. В программе ASISP эти названия не приведены (одна из мелких недоработок программы), и группы выделены только их расположением: на рис. 5.7-5.9 в секции под заголов- ком Fuse биты слева размещается Fuse Low, посередине — Fuse High и справа — Fuse Extended. Группа Extended, как видите, из трех представленных контроллеров присутствует только в ATtiny2313, в остальных хватило и двух байтов. Нумерация битов каждого байта идет сверху вниз (в таблицах, которые вы можете найти в «да- ташитах» на каждый контроллер, в разделе Memory Programming— наоборот!), т. е. во всех трех примерах бит по названием CKSEL0 — младший бит группы Fuse Low. Подробности В упомянутой ранее задаче по ручной загрузке Bootloader'a в контроллеры Arduino значения «фьюзов» придется извлекать из hardware\arduino\avr\boards.txt, где они запи- саны в шестнадцатеричной форме для каждой из групп Low, High, а также Extended, если она имеется. Исходя из сказанного, интерпретировать такую запись очень про-
112 Часть I. Общие принципы устройства и функционирования AtmelAVR сто — например, для случая, показанного на рис. 5.7, байт группы Low запишется как 01100Ю0, или $64 в шестнадцатеричной форме (0x64, если вам так привычнее). Есть программы для загрузки (например, extreme Burner, работающая с программатором USBasp), где «фьюзы» вообще загружаются именно в шестнадцатеричной форме. Так, конечно, менее наглядно, зато труднее перепутать «запрограммированное»- «незапрограммированное» состояния, а также забыть установить в нужное положение какую-то из ячеек. Рис. 5.7. Конфигурационные ячейки ATtiny2313 Рис. 5.8. Конфигурационные ячейки ATmega8
Глава 5. Подготовка к программированию MKAVR 113 Рис. 5.9. Конфигурационные ячейки ATmega8535 Кратко охарактеризуем назначение конфигурационных ячеек, показанных на иллю- страциях. Будем для начала ориентироваться на рис. 5.7, где показан ATtiny2313, а в конце дополним описанием тех ячеек, которые имеются в других «наших» моделях. Группа Fuse Low обычно начинается с одной из самых востребованных подгрупп fuse-битов: ячеек CKSEL, задающих режим тактирования. В «наших» контролле- рах их по четыре штуки: от CKSEL0 до CKSEL3. Для краткости их записывают в виде обычного двоичного числа, которое обозначается CKSEL3:0 (т. е.: CKSEL3 — старший разряд, a CKSEL0— младший). Для ATtiny2313, как видите, их значение по умолчанию равно оюо. Если справиться с таблицей в документации (она для всех контроллеров находится в разделе System Clock and Clock Options), то мы уз- наем, что такое значение задает режим работы от встроенного генератора 8 МГц. Постойте, а почему 8 МГц, когда частота тактирования по умолчанию, как мы зна- ем из главы 2, всегда подгоняется под 1 МГц? Все просто — внутренний генератор ATtiny2313 может работать только в трех режимах: кроме 8 МГц, это 4 МГц (CKSEL3:0 = оою) и 128 кГц (CKSEL3:0 = оно). А чтобы обеспечить 1 МГц, уста- навливается в логический ноль («программируется»!) еще и специальный «фьюз» под названием CKDIV8 — старший бит в группе Fuse Low, который обеспечивает при запуске деление тактовой частоты в 8 раз. В других контроллерах, где генератор сам может настраиваться на частоту 1 МГц по умолчанию, fuse-бит CKDIV8 отсутствует (см. рис. 5.8-5.9), и, очевидно, был введен только для этой цели. Хотя документация говорит, что CKDFV8 может ис- пользоваться еще и в случае, если при тактировании от внешнего генератора его частота превышает допустимую для этой модификации контроллера. В остальном он не очень нужен, т. к. именно в ATtiny2313 есть еще довольно гибкая система
114 Часть I. Общие принципы устройства и функционирования Atmel AVR управления тактовой частотой изнутри программы через регистр CLKPR, в кото- ром с помощью битов CLKPS3:0 можно установить коэффициент деления частоты от 1 до 256. При работе от внешнего «кварца» следует устанавливать CKSEL3:0 в состояние mi, а от внешнего генератора— в обратное состояние оооо. Эти установки уни- версальные для всех AVR-контроллеров, и справляться с таблицей в «даташите» на практике нужно только для случая внутреннего генератора, для которого установки для разных типов чаще всего различаются. Никаких, конечно, керамических резо- наторов мы не употребляем, потому остальные варианты установок CKSEL нам не потребуются. При включении кварцевого резонатора для ATmega8 и ATmega8535 не забывайте также про ячейку СКОРТ, о которой далее. Следующие по порядку биты SUT (SUT0 и SUT1) управляют временем запуска по- сле сброса, в том числе и при включении питания. По умолчанию они устанавли- ваются в состояние SUT1:O = ю, что при запуске от внутреннего или внешнего RC-генератора означает время около 65 мс + 6 тактов. При работе от внешнего кварца контроллер запускается быстрее: это же состояние означает время запуска около 4 мс + 16 000 тактов (4 мс при 4МГц, всего 8 мс), — в первом случае RC-re- нератору дается дополнительное время на «раскрутку». Для наших целей эти ячей- ки лучше оставлять в «умолчательном» состоянии. Бит CKOUT из «наших» контроллеров имеется также только в ATtiny2313 и под- ключает выход тактового сигнала к одноименному внешнему выводу контроллера (вывод 6, он же вывод номер 2 порта D). Это представляет определенный интерес при желании синхронизировать тактовые сигналы нескольких контроллеров или внешних устройств, а также просто при необходимости иметь в схеме генератор стабильной частоты. На практике эта возможность используется редко (вывод но- мер 2 порта D чаще всего требуется другими своими функциями), но следует учесть, что она имеется не только в ATtiny2313, но и в некоторых других контрол- лерах Tiny и Mega (включая знакомых нам «продвинутых» наследников Mega8 в лице ATmega88/ATmega328). Переходим к группе Fuse High. О fuse-битах RSTDISBL и SPIEN мы уже говорили ранее — в наших случаях их трогать категорически не рекомендуется (а в случае SPIEN при последовательном программировании пытаться его переключить еще и бесполезно). «Фьюзы» BODLEVEL, которых здесь аж три штуки, задают уровни срабатывания системы мониторинга питания BOD. В состоянии BODLEVEL2:0 = m, как по умолчанию, система BOD выключена, остальные комбинации задают уровни сбро- са при падении напряжения до 1,8 вольта (но), 2,7 вольта (ioi) и 4,3 вольта (юо). Все комбинации при нулевом состоянии старшего бита (BODLEVEL2 = 0) помече- ны, как Reserved, т. е. их устанавливать нельзя. В контроллерах ATmega8 и ATmega8535 (см. рис. 5.8 и 5.9) управление BOD реа- лизовано несколько проще: там ячейка BODEN управляет включением (BODEN = о — BOD включена, BODEN = i — BOD выключена), а ячейка BODLEVEL — уровнем
Глава 5. Подготовка к программированию MKAVR порога сброса (BODLEVEL = о — уровень 4,0 вольта, BODLEVEL =1 — уровень 2,7 вольта). При питании ниже 2,7 вольта эти контроллеры работать не могут. «Фьюз» WDTON — принадлежность всех контроллеров, оснащенных сторожевым таймером WatchDog, т. е. можно считать, что абсолютно всех. Но этот бит обеспе- чивает не простое включение-выключение сторожевого таймера, как можно поду- мать, исходя из названия. WDTON = l (по умолчанию) обеспечивает полную дос- тупность к управлению «вотчдогом» из программы, на чем мы подробно остано- вимся в главе 14. А вот WDTON = о просто навсегда включит этот таймер в режим периодического сброса по истечении заданного интервала времени (сам интервал можно задавать из программы). Есть и другие нюансы в использовании этого бита в различных моделях, поэтому здесь только отметим, что в обычных случаях этот бит трогать не надо. Ячейка EESAVE есть в большинстве контроллеров, оснащенных памятью EEPROM (не оснащены ей только некоторые младшие Tiny). По умолчанию она установлена в единичное состояние и ни на что не влияет, а установка ее в состоя- ние нуля защищает EEPROM от стирания в режиме полного сброса контроллера (при подаче команды «Стирание кристалла» перед загрузкой программы). Так вы можете записать в EEPROM, например, серийный номер изделия, индивидуальные калибровочные коэффициенты датчиков и тому подобные данные, меняющиеся от экземпляра к экземпляру, и, выключив стирание EEPROM, сколько угодно менять основную программу без необходимости записывать эти данные каждый раз за- ново. Fuse-бит DWEN характерен для контроллеров со встроенным отладочным интер- фейсом debugWare (более примитивный аналог JTAG), и просто запрещает или разрешает его работу. По умолчанию работа запрещена (и слава богу, мы туда ла- зать не будем). Не понадобится нам и последний бит у ATtiny2313, который един- ственный оказался в группе Fuse Extended, и у нас тут носит название SPMEM. В других моделях, где он имеется, он может называться SELFPRGEN, в том числе это относится и к более поздним модификациям ATtiny2313 (в «даташите» вы встретите именно его). Второе название, несмотря на неблагозвучное звучание, логичнее, т. к. этот бит управляет возможностью самопрограммирования (self programming). Нам он не понадобится, т. к. мы внутренний загрузчик использовать в наших задачах не будем. А при необходимости записи Bootloader'a в Mega 8 или в его «ардуиновских» наследников (ATmega 168-328) этот fuse-бит учитывать не надо, так там он все равно отсутствует. Остановимся на «фьюзах» с рис. 5.8 и 5.9 для контроллеров ATmega8 и ATmega8535, которые мы не описывали ранее. Во-первых, в этих моделях, как и во многих других Mega, имеется не простой self programming, а полномасштабный контроль загрузки в виде fuse-бита BOOTRST и двух битов: BOOTSZ0 и BOOTSZ1. Первый из них (при установке в нулевое значение) задает перенос адре- са старта выполнения программы после загрузки в начало специальной boot- области для размещения загрузчика (Bootloader'a) ближе к концу памяти, а вторые два задают размер этой области (максимально 1024 байта).
116 Часть I. Общие принципы устройства и функционирования AtmelAVR Подробности Для нас «фьюзы» управления загрузкой могут иметь значение для понимания, что именно мы делаем, когда загружаем Bootloader в контроллер. Значение Fuse High, указанное в ...avr\boardstxt для контроллера ATmega328 равно Oxda. Учитывая, что эти биты в нем, как и в ATmega8, являются тремя младшими битами этого байта, получа- ем, что BOOTRST = о, a BOOTSZ1:0 = 01. Можем проверить по «даташиту», что такое сочетание означает перенос старта программы в область загрузки, величина которой равна 512 байтов, — столько отнимается от основного массива flash-памяти для раз- мещения Bootloader'a. И последняя конфигурационная ячейка, также отсутствующая в ATtiny2313, носит название СКОРТ и выполняет весьма существенные функции. Мы о ней уже упо- минали в главе 2 при рассмотрении тактирования — режим по умолчанию («неза- программированное» состояние, СКОРТ = l) предусматривает уменьшение размаха колебаний генератора, за счет чего вроде бы снижается потребление. При частотах, близких к предельной (для ATmega8 и ATmega8535 это 16 МГц), СКОРТ необхо- димо «запрограммировать», т. е. установить в нулевое состояние, иначе генератор может вовсе не заработать. Потому этот fuse-бит при тактировании от кварцевого резонатора вообще рекомендуется сразу переключать в нулевое состояние, а не ос- тавлять в положении по умолчанию — экономия там мизерная, а так проще не за- быть его «включить» при смене кварца. Обращать внимание на состояние СКОРТ необходимо также при включении от внешнего генератора или низкочастотного внешнего кварца — при соответствующей конфигурации ячеек CKSEL он «заведу- ет» подключением внутренних конденсаторов 36 пФ к выводам XTAL. В случае внешнего генератора с не слишком мощным выходом (выход микросхемы КМОП «классической» серии 4000 или отечественной 561) его, наверное, лучше отклю- чать, переводя в единичное состояние. Вы заметили, конечно, что на рис. 5.7-5.9 представлена еще одна секция под назва- нием Биты защиты (Lock bits). Они защищают загруженную программу от изме- нений и копирования. Рекомендуем к этой секции не обращаться вовсе — возмож- но, прочесть вы программу еще сможете (в зависимости от установок), но что-то изменить уже не получится, в том числе и в части установки Lock/Fuse-битов. При случайной установке каких-то из битов защиты единственная возможность полу- чить доступ к контроллеру — полностью его очистить, сделать все установки зано- во и снова загрузить программу. * * * В первой части книги вы немного познакомились с возможностями контроллеров семейства AVR и теперь в первом приближении знаете, что можно от них полу- чить, если не ограничивать себя куцыми рамками Arduino. Вы также узнали, какие приспособления и инструменты минимально необходимы для разработки и загруз- ки программ и как их настроить. В следующей части мы, наконец, перейдем к практическому воплощению полученных знаний. Начнем с простейших примеров ассемблерных программ, познакомимся с их общей структурой, сравним со знако- мыми вам программами Arduino, а также кратко рассмотрим систему команд.
ЧАСТЬ II Программирование микроконтроллеров AVR на ассемблере Глава 6. Основы программирования МК AVR Глава 7. Система команд AVR Глава 8. Арифметические операции и операции в двоично-десятичном формате
ГЛАВА 6 Основы программирования МК AVR В ассемблере, как ни в каком другом языке программирования, важно различать основу— средства, которые абсолютно необходимы для создания работающей программы, — от всяческих надстроек, призванных сделать жизнь программиста удобнее. Вряд ли вас сочтут грамотным программистом, если вы не знаете, что такое макросы и как их употреблять, но для электронщика это совершенно нор- мально. Поскольку мы тут не пишем учебник по языку ассемблера, а всего только показываем элементарные способы его применения в приложении к реальным за- дачам, то в дальнейшем изложении мы постараемся ограничиваться минимумом необходимых средств, а об остальном упоминать лишь для общего образования. Концептуальная особенность, отличающая ассемблерные программы, которые вы встретите в этой книге, от программ для AVR, написанных на любом языке высо- кого уровня, заключается в том, что мы здесь будем пользоваться памятью данных SRAM (аналогом оперативной памяти обычного микропроцессора) только в от- дельных случаях, строго по необходимости. Как мы уже знаем, в ядре AVR присут- ствуют 32 байтовых регистра общего назначения, которые мы преимущественно и станем использовать для хранения текущих переменных. Этим мы, во-первых, из- бавляемся от многочисленных операций чтения/записи в память, которые занимают много процессорного времени, во-вторых, делаем программу более компактной и читаемой. Уменьшается также опасность залезть в область стека или, наоборот, в область регистров РВВ и РОН, потому что они тоже размещаются в линейном пространстве общей памяти, и запись в них может осуществляться теми же коман- дами, что и в область памяти данных SRAM. А память данных можно использовать строго по ее назначению, вытекающему из названия: для хранения именно оперативных данных, т. е. величин, которые в про- цессе выполнения программы могут меняться, но достаточно редко (например, зна- чение даты: числа, дня недели, месяца и года в часах реального времени). Хранить в оперативной памяти неизменные константы — варварство даже в среде Arduino, причем среди программистов, если судить по примерам на официальном сайте arduino.cc, это весьма распространенное заболевание. Константы, которые вообще не изменяются, нужно хранить вместе с программой, для чего любой язык дает достаточно возможностей, — примером могут служить адреса регистров конкрет- ного контроллера, хранящиеся в отдельном подключаемом include-файле. Констан-
120 Часть II. Программирование микроконтроллеров AVR на ассемблере ты, индивидуальные для каждого экземпляра устройства (например, калибровоч- ные коэффициенты датчиков) следует хранить в EEPROM, откуда их легко про- честь и при необходимости изменить отдельной процедурой калибровки, не затра- гивая саму программу. Такое соответствие каждой разновидности памяти ее назна- чению позволяет оптимальным образом использовать ресурсы контроллера и выжать из него намного больше, чем позволяет любой язык высокого уровня. Как вы уже знаете, при работе МК последовательно выполняет команды програм- мы, записанной в памяти программ. Программист может менять порядок выполне- ния команд, организуя циклы и различные переходы. Одно из самых мощных средств программирования — вызов подпрограмм или процедур (в нашем случае это одно и то же), т. е. кусков кода, которые могут использоваться неоднократно. Во всех ассемблерах вызов процедур предусмотрен обязательно. Заметки на полях В Pascal и других классических алголоподобных языках подпрограммы делятся на процедуры и функции, а в языке С и всех остальных, основанных на его синтаксисе, есть только функции. В ассемблере, строго говоря, существуют только подпрограммы, хотя в основе это все одно и то же — пример еще одной путаницы в головах програм- мистов, в которую вникать совсем не хочется. Потому пусть читатель меня извинит, если я иногда буду вперемешку употреблять термины «процедура» и «подпрограм- ма», подразумевая, что это означает одинаковые сущности. А вот «функций», как та- ковых, собственно в ассемблере не бывает вовсе. Функция — это всего лишь такой особый способ передачи параметров из подпрограммы, характерный именно для язы- ков высокого уровня. Для ассемблера функция ничем не отличается от любой другой подпрограммы. Кроме выполнения просто процедур-подпрограмм, важное значение имеет обра- ботка прерываний. По сути, это такая же процедура, но ее выполнение отличается некоторыми существенными аппаратными нюансами, потому необходимые коман- ды для обработки прерываний другие. Давайте разберемся во всем этом подробнее. Общая структура ассемблерной программы и ее выполнение Естественно, программу сначала нужно записать в память МК, причем так, чтобы МК «знал», откуда начинать при включении питания или после подачи импульса на вывод RESET . Это его «знание» в случае современных МК AVR также програм- мируется, однако пока для простоты будем считать, что программа всегда начинает выполняться с самой первой ячейки памяти программ — т. е. с нулевого адреса. Исходя из этих обстоятельств, программа должна иметь определенную структуру. По этому начальному (нулевому) адресу почти всегда располагается одна и та же команда безусловного перехода, название которой во всех ассемблерах происходит от английского jump (прыжок). Для AVR она может записываться следующим образом: rjmp RESET ИЛИ jmp RESET
Глава 6. Основы программирования MKAVR 727 Форма написания (jmp или rjmp) зависит от выбранного контроллера — если в нем объем памяти программ меньше или равен 8 кбайт, то используется команда rjmp (relative jump, т. е. относительный безусловный переход). Она занимает в памяти два байта — как и практически все остальные команды AVR. Код самой команды в этих двух байтах занимает обычно старшую тетраду старшего байта — т. е. четыре бита, остальные 12 битов представляют собой адрес, куда переходить — в рассмат- риваемом случае компилятор подставит адрес команды, следующей сразу за меткой reset, с которой и начнется собственно выполнение программы. Метка с именем reset, естественно, всегда должна присутствовать, но может быть расположена уже в любом другом удобном месте программы, за исключением, возможно, еще нескольких первых адресов, о назначении которых далее. Метка, впрочем, может называться и не reset, а любым другим именем (например, Main, или begin, или Setup, или как-то еще), просто у нас так будет принято для удобства чтения: хотите найти в любой программе ее начало — ищите метку reset. Вернемся к форме записи команды. 12 битов адреса могут представлять 4096 раз- личных адресов. Так как единицей объема памяти программ служит слово из двух байтов, то общий объем адресуемой таким образом памяти и составит 8 кбайт. А вот если памяти больше, то приходится прибегнуть к команде jmp (абсолютный безусловный переход) — она состоит из четырех байтов, в которых адрес займет 22 бита, и потому может адресовать до 4 М слов (до 8 Мбайт) памяти. Те же соображения относятся к другим командам, адресующим память про- грамм, — к паре rcaii и call (а также, с некоторыми нюансами, к lpm и eipm). В примерах этой книги со старшими моделями семейства Mega мы не работаем, и потому ограничимся командами rjmp, lpm и rcaii, но постараемся о сказанном не забывать. Заметим, что в системе команд AVR семейства Mega есть еще команды icaii и i jmp (косвенный вызов и косвенный переход), которые по определению могут адресо- вать 64 К слов (до 128 кбайт) памяти,— для этих команд адрес задается 16-би- товым регистром z (о нем будет рассказано далее). Однако их употребляют не- часто, а в начале программы (в таблице прерываний, описанной далее) их вообще указывать нельзя чисто технически (нужно заранее задавать значение z, а до начала программы это сделать невозможно). Инструкции и нотация AVR-ассемблера Для начала отметим, что все последующее изложение рассчитано на использование ассемблера avrasm2, т. к. старый avrasm32 имеет заметно отличающийся набор ди- ректив компилятора. Правда, различия в основном касаются расширений для avrasm2 (и приближения к нотации языка С), потому много переделывать не при- дется. Учебное пособие [11], кстати, единственный более или менее подробный учебник по современной версии AVR assembler, который я смог разыскать (если не считать, конечно, справочника с официального сайта [4]), но опыт показывает, что и краткой русскоязычной справки [3] вкупе с описаниями команд по-русски [6,7] или по-английски [8] для вполне полноценной работы тоже достаточно.
122 Часть II. Программирование микроконтроллеров AVR на ассемблере Особенности мнемонической записи большинства команд в AVR-ассемблере такие же, как и в любых других ассемблерах. Сначала идет собственно команда (в AVR команды бывают двух-, трех- и четырехбуквенные), затем через пробел или знак табуляции (этих знаков может быть любое количество больше нуля) следуют опе- ранды. Некоторые команды операндов не имеют (lpm, reti), в других есть один операнд (inc ri6, rjmp reset). Если команда имеет два операнда, то сначала указы- вают приемник, затем источник (это т. н. прямая польская запись). Между прием- ником и источником обязательно должна стоять запятая (с любым числом пробелов до или после нее, или вообще без них). Так, выражение sub ri6, ri7 означает, что из содержимого г1б нужно вычесть содержимое г 17, а результат окажется в г 16. Общее правило для использования пробелов и знаков табуляции такое: нельзя раз- бивать идентификатор на части и, наоборот, нельзя сливать разные идентификато- ры между собой — хотя бы один пробел, знак табуляции (или знак препинания, если это предусмотрено форматом команды) между наименованиями инструкций, переменных, регистров и т. п. должен быть. Сразу заметим, что AVR-ассемблер регистр букв не различает, в том числе и в при- своенных программистом именах переменных, констант, меток и т. п. (одинаково правильной будет форма записи Jmp, jmp и jmp, так же как Reset, reset и reset). С легкой руки А. А. Зубарева [24] по сайтам пошло гулять представление о том, что avrasm2, в отличие от старого avrasm, различает регистр символов, но, как легко убедиться методом проб и ошибок, это представление, к счастью, оказалось непра- вильным, так что переписывать все программы нам не придется. Зато новый ас- семблер не терпит переопределений и «внештатного» использования зарезервиро- ванных слов — назвать процедуру обработки прерывания по внешнему воздейст- вию into у вас не получится, потому что into уже употребляется в файлах определений для обозначения вывода, соответствующего этому прерыванию. Каждая команда должна занимать отдельную строку (в большинстве языков высо- кого уровня операторы можно записывать в одной строке, например, разделяя их знаками препинания, — здесь это не допускается). Длина строки ограничена 120-ю символами. Разбивать команду на части разрывом строки нельзя, в avrasm2 (но не в старой версии!) можно применять прием из языка С, где строки исходных кодов могут быть продолжены посредством символа \— обратной косой черты (обратного слеша) в конце строки, что полезно для длинных макроопределений и для длинных директив . db. Кроме команды, строка может содержать метки и примечания. Метка (label) — идентификатор произвольной длины, придуманный программистом и заканчиваю- щийся двоеточием без пробела перед ним (metka:). Метку можно располагать и в отдельной строке. Кроме простого указания на адрес перехода для команд ветв- ления, метки служат также указанием на адрес подпрограмм (процедур) и заодно являются их именем. Примечания можно добавлять в конце строки после знака «точка с запятой». Все, что расположено после точки с запятой и до знака конца строки (обычно в тексто- вом формате DOS/Windows это пара символов $оа $od, которая вводится при нажа-
Глава 6. Основы программирования MKAVR 123_ тии клавиши <Enter>. Редакторы их не показывают, но нумеруют строки именно по наличию этих символов), игнорируется, поэтому комментарий может быть и на русском. Если нужно продлить комментарий на следующую строку, то эту строку нужно опять начинать со знака «точка с запятой». В avrasm2 допускается С-подобная нотация, когда примечание вместо точки с запятой отделяется двумя прямыми косыми чертами (прямыми слешами): //, или выделение целого блока примечания вот такими скобками: /*здесь многострочное примечание*/. Подсветка подобных комментариев в ASM Editor серым фоном отчасти уже введена в экземп- ляре файла подсветки ASM Editor, который вы найдете в архиве, доступном по уже упомянутому в главе 5 адресу http://revich.lib.ru/AVR/AVRshk.zip, а для скачанно- го с оригинального сайта ее придется настраивать отдельно (через меню Highlight | Start-Stop keys...). Числа и выражения В некоторые команды можно включать выражения и числовые значения. Числа по умолчанию считаются десятичными, за исключением чисел с ведущим нулем, кото- рые, если не имеют дополнительных признаков, воспринимаются как восьмерич- ные. Шестнадцатеричные числа можно записывать двумя способами: как в языке С (ОхОа) и как в языке Pascal или в ассемблере для контроллеров Motorola ($oa). Мы, как правило, будем употреблять последнюю форму записи, как более короткую. «Интеловская» форма записи (оаь) не допускается. Двоичные числа записывают по аналогии с шестнадцатеричными в языке С: оьооооюю. В команды можно включать алгебраические и логические выражения — например: idi r30,ci+c2 (где el и с2 — константы). В выражениях допустимы все арифмети- ческие и логические операции, включая даже операции сравнения (за их полным перечнем я отсылаю к фирменному описанию ассемблера). Однако действия в вы- ражениях могут, естественно, производиться только над константами, а не над содержимым регистров, которое при компиляции неизвестно. Хитрый нюанс тут заключается в том, что адреса в программе — тоже константы, поэтому допустима такая, например, конструкция: rjmp metka+i. По этой команде произойдет переход не на команду, помеченную меткой metka, а на следующую за ней. Впрочем, увле- каться этим не стоит, т. к. дейкстровская1 «лапша» условных и безусловных пере- ходов и без того затрудняет чтение ассемблерных программ. Укажем на одну операцию с участием выражений, которой мы часто будем пользо- ваться, — это логический сдвиг влево, обозначающийся знаком «. Оператор этот хорошо известен знатокам языка С, для всех остальных поясним, что выражение х«п равносильно выражению jc-2", или, другими словами, это число х9 сдвинутое 1 Эдсгер Дейкстра (1930-2002)— голландский программист, один из авторов концепции структурно- го программирования, лежащей в основе всех современных языков высокого уровня. Называл про- граммы с активным использованием операторов условного и безусловного перехода «лапшой» за многочисленные линии перехода на метки. О его критике оператора безусловного перехода goto и современном состоянии этого давнего спора см. статью Владимира Мегре на сайте Habr.com (https://habr.com/ru/post/271131/).
124 Часть II. Программирование микроконтроллеров AVR на ассемблере влево на п двоичных разрядов. В совокупности с побитовым ИЛИ (« | ») операцию эту удобно применять для установки поименованных битов сразу «всей кучей» — например: ldi temp, (1«INTO) | (1«INT1) out GIMSK,temp Эта последовательность операторов установит разрешение двух прерываний: into и inti — в один прием. Запись i«into означает число 1, сдвинутое на into разрядов влево, т. е. оказавшееся в позиции into. Можно устанавливать сразу два и более бита, если они идут подряд: например, запись ldi temp, (3«into) равносильна записи ldi temp, (1«into) I (1«inti), причем указывать нужно самый младший бит. Раздельная запись лучше читается и употребляется чаще. Вместо логического побитного сложения « | », кстати, в этом случае можно применить обычное ариф- метическое «+». Скобки в выражениях используются по тем же правилам, что и в обычной алгеб- ре, — для явного указания старшинства операций. Старшинство (приоритет) опе- раций соответствует их положению в таблице, приведенной в описании AVR- ассемблера (чем ниже положение, тем приоритет выше). В приведенном только что примере при указании единственного знака логического побитного сложения скоб- ки можно не ставить. Однако при записи нескольких операций подряд лучше не разбираться в старшинстве, чтобы не плодить источники возможных ошибок, и всегда использовать скобки. В выражениях можно употреблять некоторые стандартные функции. Подчеркнем, что эти функции — свойство самого ассемблера, и выполняются они в процессе подготовки текста программы к компиляции. Для вычисления по ходу программы различных функций переменных (синуса, экспоненты и т. п.) в языках высокого уровня есть готовые стандартные операторы, но в ассемблере придется реализовы- вать их самостоятельно. Из доступных функций (их достаточно много, — см. [8]) наибольшее практическое значение имеют те, что предназначены для выделения отдельных байтов из кон- стант или результатов вычислений, если эти результаты размером более одного байта. Наиболее часто из них употребляются функции Low (выделяет младший байт из многобайтового числа) и High (выделяет второй байт, если число двухбайтовое, то он же старший). Например, загрузка значения 62 500 в регистры сравнения тай- мера-счетчика Timer 1 может происходить так: ldi temp,high(62500) out OCRlAH,temp ldi temp,low(62500) out OCR1AL, temp Если число имеет размер, больший 16 битов, то можно воспользоваться функция- ми: ВYTE3 (выражение) — ВОЗВращает третий байт выражения И BYTE4 (выражение) — возвращает четвертый байт выражения.
Глава 6. Основы программирования MKAVR 125 Директивы Кроме собственно команд, в ассемблерной программе могут встречаться директивы компилятора. Их предостаточно, но самых употребительных, которые есть практи- чески в каждой программе, три: .def (definitions), .equ (equvalent) и .include. Пер- вые две предназначены для определения имен пользовательских переменных и констант, соответственно (обратите внимание на точку перед именем директивы): .equ max_value = $11 /константа max_value = 17 .def temp = rl6 ;регистр rl6 есть переменная temp .def counter = rO5 /регистр гО5 есть переменная counter Эти определения в целях структурирования программы обычно располагают в на- чале текста. Только учтите, что никаких проверок, кроме синтаксических, тут не производится, потому возможно объявить два разных имени для одного регистра, и они будут восприниматься как синонимы: .def temp = г.16 /регистр г16 есть переменная temp .def counter = rl6 /регистр г16 есть переменная counter Изменение temp будет автоматически означать изменение counter и наоборот. Ино- гда этим пользуются, если в разных частях программы один регистр применяется для разных по смыслу значений (и вы можете встретить примеры этого в фирмен- ных «аппнотах»). В общем случае такую возможность советую использовать лишь в исключительных случаях — слишком много ошибок можно наделать. При нали- чии в программе такого двойного определения avrasm2 будет выдавать предупреж- дение (warning), хотя компиляции это не помешает. С помощью директивы .equ, вообще говоря, можно определять достаточно слож- ные выражения, но этим пользуются редко — гораздо чаще ее применяют для оп- ределения переменных, которые располагаются не в регистрах, а в области SRAM. Например, следующая последовательность директив и команд (листинг 6.1) запи- шет содержимое регистра counter в SRAM по адресу $60 (для «наших» моделей — это первый свободный адрес после занятых адресами регистров). .equ counter_addr = $60 .def counter = rl6 clr ZH ldi ZL,counter_addr st Z,counter Все принятые в технических описаниях Atmel наименования регистров и прочие необходимые константы вводят точно таким же способом и собирают в специаль- ных файлах определений с расширением .inc, о которых мы уже упоминали, — та- кие файлы придаются к каждой модели контроллера (например, tn2313def.inc — для
126 Часть II. Программирование микроконтроллеров AVR на ассемблере модели ATtiny2313, m8535def.inc — для модели ATmega8535 и т. п.). В обновленном наборе inc-файлов, которые прилагаются к современным версиям Atmel Studio, вы можете встретить даже разные файлы для модификаций одного и того же контрол- лера,— например: m8def.inc и m8adef.inc (но не m8hvadef.inc или m8u2def.inc— это другие контроллеры!), что только путает, потому что по содержанию они, естест- венно, одинаковые. Файлы определений, как мы уже упоминали, нужны потому, что ассемблер абсо- лютно не «подозревает» о существовании таких понятий, как PortA или ddrc, a «знает» только числовые адреса соответствующих регистров (единственное, о чем ассемблер «осведомлен от рождения», — это о существовании РОН с названиями ro-r3i). Соответствие между этими мнемоническими обозначениями и адресами и устанавливается с помощью inc-файлов, причем для разных моделей эти адреса могут различаться. Для того чтобы включить эти соответствия в текст вашей про- граммы, служит директива . include: .include "tn2313def.inc" Здесь подключается файл с определениями констант и адресов для контроллера ATtiny2313 (разумеется, если файл находится не в текущем каталоге, то следует указать полный к нему путь). Подобной строкой должен начинаться текст любой AVR-программы, иначе ассемблер может вас «не понять». Разумеется, директивой . include можно вставить в ваш текст содержимое любого другого файла — напри- мер, содержащего типовые процедуры или макросы (и мы этим будем пользовать- ся). Имя файла здесь может быть абсолютно произвольным — по директиве include компилятор, не раздумывая, разыщет файл с указанным именем (напомним, что он должен находиться в текущем каталоге, или для него должен быть указан полный путь), скопирует из него текст и вставит этот текст в том месте вашей программы, где расположена директива, и только потом начнет разбираться. Потому с директи- вами include следует быть аккуратными— если файл содержит команды, а не только определения, то вставлять его нужно уже не в начале текста, а после всех векторов прерываний (см. далее), иначе выполнение программы начнется с него. Весьма полезна директива .device, которая должна ставиться в начале програм- мы, — она отдельно указывает ассемблеру на применяемую модель МК: .device ATtiny2313 Если jnc-файлы указывают ассемблеру на истинные адреса регистров для конкрет- ной модели, то такая директива не позволит использовать команды, которые этой моделью не поддерживаются. В avrasm2 эта простая и лаконичная директива более не поддерживается, вместо нее действует одна из множества директив #pragma, ко- торыми можно указать компилятору много чего разного. Вообще говоря, самостоя- тельно указывать ни старую директиву (в случае, если у вас старая версия ассемб- лера), ни новую в программе не нужно, т. к. с них все равно начинается любой inc-файл. Тем не менее директиву .device вы встретите в программах далее— как дань традиции (ее рекомендовалось указывать для первых версий avrasm) и как памятку для нас самих об используемой модели контроллера.
Глава 6. Основы программирования MKAVR 727^ Рассмотрим еще директиву . db (define byte), которая позволяет хранить непоимено- ванные константы или массивы во flash-памяти программ или в долговременной памяти EEPROM (для памяти данных SRAM используется другой способ, — об этом рассказано далее). В языках высокого уровня (в том числе и для Arduino) та- кая возможность имеется только теоретически (во встроенном ассемблере), по- скольку модель «общения» с памятью у них совершенно другая, и никому не при- дет в голову хранить данные «внавал», когда их можно поименовать и заодно ука- зать тип. А в ассемблере это используется достаточно часто — здесь нет массивов, как таковых, потому удобно прибегать к такому способу. Причем, кроме .db, есть еще директива . dw (define word, т. е. двухбайтовое слово) и даже . dd и . dq (двойное и четвертное слова), но мы не будем отвлекаться на все нюансы адресации, кото- рые при этом возникают, и обойдемся одной . db — на практике она употребляется наиболее часто. Для того чтобы показать, куда именно писать данные, совместно с . db можно ука- зать директиву .eseg (для EEPROM). Память программ при этом обозначается директивой .cseg (code segment). Некоторые авторы советуют всегда записывать в начале программы, как минимум, директиву . cseg, чтобы указать, что последую- щий текст представляет собой именно текст программы, а не набор констант, но мне кажется, что это только запутывает дело, потому и советую употреблять эти директивы тогда, когда это действительно надо. Если директива явного указания на область памяти отсутствует, то данные по ди- рективе .db будут сохраняться вместе с командами в памяти программ (пример приведен в разд. «Команды пересылки данных» главы 7). Если же присутствует директива .eseg, то потом, чтобы указать, что область данных EEPROM в тексте закончилась, и следует опять перейти к памяти программ, необходимо это явно определить, т. е. поставить директиву . cseg (code segment). Естественно, распола- гать в зоне действия директивы .eseg что-либо, кроме констант, определенных директивой .db, бессмысленно. Все сказанное иллюстрируется, например, фраг- ментом текста, заимствованным из «аппноты» Atmel № 240 (листинг 6.2). .eseg ;EEPROM segment .org 0 .db 1,2,3/15,4,5,6,14,7,8,9,13,10,0,11,12 . ***• Source code ******************* .cseg ;CODE segment .org 0 rjmp reset ;Reset handler Применение директивы . org мы рассмотрим в этой главе далее, а здесь только от- метим следующее. Согласно листингу 6.2, все, что записано через запятую после
128 Часть II. Программирование микроконтроллеров AVR на ассемблере директивы .db, будет помещено в область EEPROM, начиная с нулевого адреса. Если директивой . org этот нулевой адрес не указывать, то размещение будет осу- ществлено все равно с него, за исключением случая, когда где-то еще в тексте ранее встречалась директива . eseg, — тогда размещение произойдет по порядку адресов. Отметим, что содержимое EEPROM для контроллеров AVR предлагается загру- жать через отдельный файл того же формата (*.hex), что и для кода программы, но с расширением .еер. Для того чтобы такой файл создавался при наличии директивы . eseg в коде программы, требуется указать специальную опцию компилятора, тогда строка в нашем ВАТ-файле (см. разд. «Обустройство ассемблера» главы 5) будет выглядеть так: c:\avrtools\avrasm32 -е %1.еер -fl %l.asm В этом случае в той же папке, где находится asm-файл, дополнительно создаст- ся файл с расширением .еер, который будет содержать данные для загрузки в EEPROM. Если директива . eseg в тексте программы не встречается, то в создан- ном еер-файле данных не окажется, и компилятор удалит его по окончании процес- са, сообщив вам об этом. Для размещения данных в SRAM есть директива .dseg. Специальных опций ком- пилятора указывать в командной строке тут не требуется, зато почему-то данные по директиве .dseg помечаются не директивой .db (или .dw), a .byte, которая имеет иной синтаксис, — после нее должна идти константа, указывающая число резерви- руемых байтов. Других параметров не допускается, потому .byte может приме- няться только для выделения места под переменные в SRAM, но не для инициали- зации ее содержимого. К тому же есть некоторая путаница с адресацией в отноше- нии SRAM — счетчик адресов здесь по умолчанию равен не нулю, как в других случаях, а 32-м (поскольку адреса 0-31 заняты РОН), но с этого адреса вообще-то начинается файл РВВ, т. е. регистров ввода/вывода, а не собственно SRAM. Чтобы не путаться и не попасть в какой-нибудь регистр или вообще выйти за пределы памяти, здесь лучше использовать константы sramstart и ramend, указывающие на начало и конец свободной SRAM для каждого контроллера. По сути, единственное преимущество .byte в том, что нам не приходится думать в процессе программирования об адресах в SRAM, а можно просто располагать данные по метке (причем адресация здесь побайтовая, а не пословная, как в памяти программ, о чем далее). Такой способ загрузки данных используют значительно реже остальных— заполнять память конкретными значениями все равно прихо- дится программно, так что много от применения .byte мы не выигрываем. Пример применения этой директивы вы найдете в программе вывода на дисплей с 12С-ин- терфейсом в главе 13, Применение директивы .macro мы рассмотрим далее отдельно. Стоит упоминания, что, как и любой другой серьезный язык программирования, AVR-ассемблер пред- полагает возможность условной компиляции (директивы if, endif, else, ifdef и им подобные). В совокупности с директивой .device (ранее) или конструкцией #pragma
Глава 6. Основы программирования MKAVR 129_ (теперь) они позволяют писать универсальные программы, например, для разных контроллеров. Но я этого делать не советую, потому что логика таких программ бывает слишком запутанная, разобраться в них сложно, а наделать ошибок легко. В некоторых простых случаях, однако, эти директивы бывают полезны — пример использования условной компиляции для выбора из двух вариантов вы найдете в главе 16. Рассматривать другие директивы мы здесь не будем, т. к. без них в большинстве случаев можно обойтись (интересующихся отсылаю к описанию AVR-ассемблера [3,4] или к пособию [11]). Подведем некоторый итог: мы уже узнали многое о структуре типовой ассемблер- ной программы для AVR. Текст должен начинаться с директивы . include, ссылаю- щейся на файл с определениями имен для конкретного процессора, далее обычно идут пользовательские определения переменных (директива .def) и констант (директива . equ), а программа должна начинаться с безусловного перехода на мет- ку reset. Начало собственно программы будет располагаться сразу после этой мет- ки где-то в другом месте программы. А почему так странно — нельзя ли начать прямо сразу с нулевого адреса, строка за строкой, зачем нужны какие-то переходы? Можно, отвечают нам авторы описаний AVR. Простейшая программа может начинаться с нулевого адреса, и никаких пере- ходов не потребуется, но только в одном случае: если мы обязываемся отказаться от прерываний. Мы далее встретим несколько примеров программ, которые совсем не используют прерываний, и увидим, что нередко они все равно начинаются с пе- рехода на метку reset — так просто удобнее. Оформление вызова подпрограмм Это, наверное, самая простая тема во всем введении в ассемблер, потому что со- держит очень немного оговорок, касающихся особенностей разных типов AVR. В общем виде это выглядит примерно так: если у вас есть какой-либо повторяю- щийся участок кода, то вместо того, чтобы его копировать много раз, следует запи- хать его в подпрограмму (процедуру), а потом вызывать каждый раз, когда потре- буется. Пусть у нас есть некая программа, которая требует несколько раз в разных местах осуществить задержку на строго определенное время. Тогда саму задержку целесо- образно оформить в виде подпрограммы (как это реализовать в действительности, мы рассмотрим далее) и вызывать, когда потребуется (листинг 6.3). ; процедура Delay Delay: <код, осуществляющий задержку> ret
130 Часть II. Программирование микроконтроллеров AVR на ассемблере ; программа: < что-то делаем > rcall Delay < что-то делаем > rcall Delay < что-то делаем > Здесь метка Delay указывает на начало подпрограммы (и одновременно является ее именем), а команда ret осуществляет корректный выход из подпрограммы с воз- вратом именно в то место кода, из которого ее вызывали. В реальности происходит следующее: команда вызова rcall сначала сохраняет текущее значение адреса в стеке (области в памяти SRAM, которая располагается в самом конце этой памяти и растет в сторону начала), потом в программный счетчик PC загружается адрес метки Delay. Далее выполняется код задержки, и по команде ret осуществляется обратная операция— сохраненный в стеке адрес, откуда прервали, помещается в программный счетчик, и выполнение продолжается со следующей команды вы- зывавшего участка кода. Оговорка здесь только одна: для контроллеров с памятью более 8 кбайт вместо rcall полагается вызывать более длинную команду call, которая действует на весь диапазон такой памяти. На самом деле это необязательно, потому что руками на ассемблере вы все равно не напишете код, превышающий 8 кбайт, а если и напише- те, то на ошибку вам укажет компилятор. Причем у контроллеров с памятью 8 кбайт и менее команда call (так же, как и аналогичная по адресуемой памяти команда jmp) вовсе отсутствует, и там ее вы применить не сможете. Существенный нюанс здесь состоит в том, что команда rcall занимает 3 такта про- цессорного времени, а команда ret — еще 4. Потому выполнение подпрограммы всегда длится на 7 тактов больше, чем если бы ее код оставили просто в том месте, где он выполняется. При этом мы тут еще не учитываем, что может захотеться со- орудить настоящую процедуру-функцию, передав в нее какие-то параметры и по- том получив результаты обратно (см. далее в этой главе), или просто сохранить в стеке некоторые регистры на время выполнения подпрограммы. Каждое такое сохранение и извлечение — это еще по такту процессорного времени на выполне- ние каждой из команд push и pop, а таких пар может быть много. И если здесь мы сможем почти всегда избегать такой работы со стеком, пользуясь добротой фирмы Atmel, предоставившей нам 32 регистра общего назначения, то языки высокого уровня даже не стараются как-то оптимизировать вызовы функций в этом отноше- нии. Отсюда понятно, почему Arduino-программы намного больше ассемблерных по размеру и выполняются намного дольше. Есть альтернативное средство, которое позволяет избежать замедления выполнения программы, и в то же время сократить ее текст, сделав ее более читаемой и понят- ной. Это средство— макросы, и когда применять целесообразно именно их, а когда — подпрограммы, мы обсудим немного далее.
Глава 6. Основы программирования MKAVR 131_ Обработка прерываний Как мы уже знаем (см. главу 3\ AVR по умолчанию ожидает, что сразу после пер- вой команды с адресом $оооо идет таблица т. н. векторов прерываний. Вектор — это просто отсылка по нужному адресу с помощью команды г jmp (или jmp — в за- висимости от степени «продвинутое™» контроллера). Вообще-то вектор, разме- щенный по нулевому адресу, на который программа переходит по сбросу {вектор сброса или вектор начальной загрузки), тоже считается прерыванием, хотя он занимает особое место (о векторе сброса рассказано в следующем разделе). Адрес обозначается меткой, может располагаться в любом месте программы и ука- зывает на начало процедуры обработки прерывания. Первый вектор располагается по адресу $oooi (а для МК с памятью более 8 К— по адресу $0002, потому что по адресу $oooi по идее должна находиться вторая половина более длинной команды jmp от вектора сброса), причем напомним, что для памяти программ адрес этот оз- начает номер двухбайтового слова в памяти, а не отдельного байта. В контроллерах с памятью меньше 8 кбайт нам вообще можно не думать про абсолютные адреса и их нумерацию — первый вектор программы (гjmp reset) автоматически располо- жится по нулевому адресу, второй— по адресу $oooi, и т. д. Найдя какую-нибудь команду перехода по метке, компилятор автоматически подставит абсолютные ад- реса. Порядок следования векторов и их число в таблице жестко заданы в соответствии с типом МК. Потому самое первое, что вы должны сделать, приступая к програм- мированию, — открыть руководство по применению выбранного типа контролле- ров и скопировать оттуда эту таблицу. Можно сделать это прямо через буфер обме- на из PDF-описания (если вам позволят — в последних версиях описаний копиро- вание текста через буфер обычно запрещено, но всегда можно вывернуться с помощью программ, удаляющих или обходящих ограничения PDF) — так меньше вероятность что-то пропустить, только придется потом удалить указанные там абсолютные адреса, стоящие в начале каждой строки. Листинг 6.4 иллюстрирует начало программы для МК ATmega8. ;=========прерывания================ rjmp RESET ; Reset Handler rjmp EXT_INT0 ; IRQ0 Handler rjmp EXT_INT1 ; IRQ1 Handler rjmp TIM2_COMP ; Timer2 Compare Handler rjmp TIM2_OVF ; Timer2 Overflow Handler rjmp TIM1_CAPT ; Timerl Capture Handler rjmp TIM1_COMPA ; Timerl CompareA Handler rjmp TIM1_COMPB ; Timerl CompareB Handler rjmp TIM1_OVF ; Timerl Overflow Handler rjmp TIM0_OVF ; TimerO Overflow Handler rjmp SPI_STC ; SPI Transfer Complete Handler rjmp USART_RXC ; USART RX Complete Handler
132 Часть II. Программирование микроконтроллеров AVR на ассемблере rjmp USARTJJDRE ; UDR Empty Handler rjmp USART_TXC ; USART TX Complete Handler rjmp ADC ; ADC Conversion Complete Handler rjmp EE_RDY ; EEPROM Ready Handler rjmp ANA_COMP ; Analog Comparator Handler rjmp TWSI ; Two-wire Serial Interface Handler rjmp SPM_RDY ; Store Program Memory Ready Handler Но постойте: мы что, обязаны использовать все прерывания? Конечно, нет. Для не- используемых прерываний в контроллерах с памятью программ менее 16 кбайт команду rjmp <метка> следует заменить на reti— выход из прерывания (return interrupt). На самом деле можно было бы указать и команду пор — пустую опера- цию (см. описание команд далее). Но мы будем ставить именно reti, т. к. тогда нам не будет важно, что какое-либо прерывание оказалось случайно инициализирова- но, — оно все равно не станет выполняться, а писать и отлаживать программы так удобнее. Я в своих программах просто дополняю стандартные строки командой reti и точкой с запятой, чтобы закомментировать команду rjmp (листинг 6.5). .include "m8def.inc" .def temp = rl6 ;рабочая переменная rjmp RESET ; Reset Handler reti ;rjmp EXT_INT0 ;IRQ0 Handler reti ;rjmp EXT_INT1 ;IRQ1 Handler Теперь заготовка начала программы готова: при необходимости в дальнейшем ис- пользовать какое-то прерывание, мы удаляем из соответствующей строки фрагмент reti;, а затем где-то в программе ставим нужную метку и пишем обработчик, за- канчивающийся командой reti (листинг 6.6). Листинг i.6 rjmp RESET ;Reset Handler rjmp EXT_INT0 ;IRQO Handler EXT_INT0: /процедура обработки прерывания INTO <код обработчика> reti ;окончание процедуры обработки прерывания INTO Чаще используется более корректный универсальный способ оформления таблицы векторов прерываний. Его особенно целесообразно применять для старших Mega,
Глава 6. Основы программирования MKAVR 133 где число прерываний может достигать нескольких десятков, а из-за четырехбайто- вого формата команды jmp автоматически заменить ее на единственную reti или пор не получается, и легко наделать ошибок. Способ основан на использовании директивы .org, которая устанавливает абсолютный адрес в памяти программ. В jnc-файлах есть специальные определения констант для адресов прерываний, на- пример (взято из файла m16def.inc для модели ATmegal6): External Interrupt Request 0 External Interrupt Request 1 Timer/Counter2 Compare Match Timer/Counter2 Overflow Timer/Counterl Capture Event Timer/Counterl Overflow Timer/CounterO Overflow External Interrupt Request 2 Timer/CounterO Compare Match Store Program Memory Ready equ equ equ equ equ equ equ equ equ equ INTOaddr INTladdr OC2addr OVF2addr ICPladdr OVFladdr OVFOaddr INT2addr OCOaddr SPMRaddr = 0x0002 = 0x0004 = 0x0006 = 0x0008 = 0x000a = 0x0010 = 0x0012 = 0x0024 = 0x0026 = 0x0028 Тогда, если вам, к примеру, никакие иные прерывания не требуются, кроме внеш- них прерываний into и int2, а также прерывания переполнения Timeri, то начало программы может быть таким, как показано в листинге 6.7. ;Установка векторов прерываний .org 0 /начало программы после сброса rjmp RESET .org INTOaddr ;адрес прерывания INTO rjmp Int_0 .org OVFladdr ;адрес прерывания OVF1 rjmp OVF_Timerl .org INT2addr ;адрес прерывания INT2 rjmp Int_2 .org INT_VECTORS_SIZE ; Конец таблицы прерываний - начало кода <программа> Здесь into, ovFTimeri и int_2 — процедуры, которые выполняются при возникно- вении соответствующих прерываний. Если первым идет вектор сброса rjmp reset, то .org о в начале ставить необязательно. В общем-то необязательно и направлять в конец таблицы прерываний (intvectorssize), но только если остальные указа- тели идут строго по их очередности в этой таблице. Обратите внимание на то, что при таком способе записи при смене модели не требуется что-либо исправлять (при
134 Часть II. Программирование микроконтроллеров AVR на ассемблере условии, конечно, что эта другая модель будет поддерживать такие же прерыва- ния — INT2, например, есть не у всех Mega). Ставить именно двухбайтовую коман- ду jmp вместо rjmp также необязательно— как и в случае команд caii/rcaii, до- пустить ошибку в объеме ассемблерного кода сложно, а в случае неправильного употребления на ошибку вам укажут. Причем jmp, как и call, в младших моделях вовсе отсутствует. Не следует также высчитывать конец таблицы прерываний по их общему числу и указывать начало кода в абсолютном значении адреса — в других моделях это обя- зательно будет другое значение, т. к. количество прерываний различается, в то вре- мя как константа intvectorssize уже указывает на правильный адрес для любого контроллера. Внимание! При использовании прерываний не забывайте после всех установок устанавливать общий флаг разрешения прерываний командой sei. Следует учесть, что если не использовать никаких прерываний, кроме самого пер- вого (сброса), то команду sei можно не указывать. В этом случае стоящая на первом месте в программе команда rjmp reset, очевидно, вырождается в простой безусловный переход на метку. Общая схема обработки аппаратных прерываний следующая. При возникновении любого прерывания флаг i регистра sreg аппаратно сбрасывается, тем самым за- прещая обработку других прерываний. При нормальном течении событий он авто- матически устанавливается опять, когда обработка прерывания заканчивается (по команде reti). Отметим, что при необходимости этот флаг можно «вручную» уста- новить в подпрограмме-обработчике (напрямую или командой sei), разрешив вло- женные прерывания. После сброса флага i контроллер определяет, запрос на обра- ботку какого именно прерывания произошел, — это делается по флагу конкретного прерывания, который также автоматически устанавливается при возникновении прерывания (например, для таймеров эти флаги находятся в регистре tifr или etifr, для внешних прерываний— в регистре gifr или eifr и т. п.). Отметим, что эти регистры при инициализации МК рекомендуется очищать, что делается записью единиц (не нулей!): ser temp ;temp=$FF out GIFR,temp ;сбросить флаги внешних прерываний out TIFR,temp ;сбросить флаги прерываний таймеров Критично это только в случае, когда мы вообще используем прерывания, и их непреднамеренное возникновение может что-то испортить, иначе можно этим пре- небречь. После определения типа прерывания контроллер автоматически вычисляет адрес соответствующего вектора прерывания. Перед тем как перейти по вектору преры- вания, МК сбрасывает флаг произошедшего прерывания (тем самым разрешая его на будущее) и автоматически сохраняет содержимое счетчика команд в стеке.
Глава 6. Основы программирования MKAVR Отметим, что иногда обработка прерываний может мешать выполнению каких- нибудь длинных процедур, которые нельзя прерывать (например, не допускается возникновение прерывания во время записи в EEPROM). В этом случае прерывания всегда можно временно запретить командой cli. Естественно, потом их следует опять разрешить командой sei (обратите внимание на то, что инструкция сразу за sei будет выполнена в любом случае до начала обработки любого прерывания). При вызове таких длительных процедур изнутри обработчика прерывания пробле- ма снимается, если, конечно, не разрешены вложенные прерывания, что еще один аргумент за перенос функциональности программы преимущественно в прерыва- ния (см. далее). Не следует также забывать, что содержимое регистра флагов sreg при переходе к обработке прерывания не сохраняется. Регистр sreg, кроме флага разрешения прерываний, содержит другие флаги, задействованные в арифметических операци- ях, командах сравнения и др. Поэтому, если прерывание «вклинится», например, между командой сравнения и командой перехода в зависимости от его результата, то при наличии в обработчике команд, модифицирующих sreg, выполнение этой последовательности команд может оказаться некорректным. При опасности такого развития событий регистр sreg также следует сохранять в стеке (ну, или запрещать прерывания во время выполнения процедур выбора). Переход в обработчик всегда осуществляется по окончании выполнения текущей команды (даже если она занимает несколько тактов), а при выходе из прерывания всегда выполняется, по крайней мере, одна команда основной программы, прежде чем контроллер перейдет к выполнению следующего прерывания. Обработчик пре- рывания всегда (кроме reset) должен заканчиваться командой reti. Только с ее помощью программа, во-первых, снова разрешит прерывания (которые автомати- чески запрещаются при переходе по вектору сбросом флага i в регистре sreg), во- вторых, вернется в то место программы, на котором ее выполнение было прервано. Обращение к прерыванию и возврат из него (команды rjmp + reti) без учета вы- полнения команд до и после добавляет 6 тактов к длительности обработки преры- вания. Вообще говоря, обработчик можно вызвать в любой момент и программно, подобно обычной процедуре, но встретить ситуацию, в которой это может приго- диться, мне ни разу не довелось. Подробности об обработке прерываний вы найде- те далее, в конкретных примерах программ. Заметки на полях Если мы рассмотрим прерывания с точки зрения общей организации процесса выпол- нения программы в контроллере, то можем заметить, что выполнение его обработчика очень похоже на вызов обычной подпрограммы. Тут уместно напомнить об одном очень важном принципе, лежащем в основе всего микропроцессорного подхода к соз- данию электронной аппаратуры, о котором часто забывают упомянуть, потому что для всех он становится очевидным уже на начальных стадиях изучения микропроцессоров и программирования, — это принцип эквивалентности аппаратного и программного обеспечения. Понятные иллюстрации этого принципа представляют собой некоторые команды выполнения арифметических действий, которые мы будем рассматривать далее. Выполнение аппаратных прерываний также является ярким примером этого принципа — в основе это тот же самый вызов подпрограммы по некоторому условию, а вот оформление (и аппаратное, и программное) этих вызовов принципиально раз- ное.
136 Часть II. Программирование микроконтроллеров AVR на ассемблере Задумаемся над вопросом: а почему обычные процедуры вызываются специальной командой caii/rcaii, а обработчики прерываний — простым безусловным переходом jmp/r jmp? Если в принципе это одно и то же, то нельзя ли все привести к единой фор- ме? Вы уже, наверное, сообразили, почему нельзя: переход по вектору прерывания происходит автоматически, и также автоматически перед этим адрес возврата поме- щается к стек. Функциональность команд caii/rcaii здесь просто излишняя. И наобо- рот — вызов обычной процедуры простым «прыжком» (jmp) на метку ее начала в луч- шем случае (если в стек еще ничего не писалось, и в ячейках стоят нули) по окончании процедуры приведет к перезагрузке контроллера, т. к. он по завершающей команде ret отправится по нулевому адресу. А выходы из обработчика или подпрограммы так- же различаются (reti или ret), но по другой причине: потому что перед выходом из обработчика надо опять разрешить прерывания, установив сброшенный ранее флаг разрешения прерываний i. Если вы вдруг его уже установили программно (команда sei), разрешив вложенные прерывания, то повторная установка с помощью reti ниче- му не помешает. По этому поводу еще заметим, что команду ret можно указать в любом месте про- граммы, и опытные программисты, манипулируя содержимым стека, могут выделы- вать разные необычные кульбиты, для которых в ассемблере есть все условия. Но мы подобными вещами заниматься не станем, а любопытствующих я отправляю к посо- бию [2], автор которого всякие такие финты просто обожает. Процедура RESET Теперь обратимся к процедуре reset, т. е. к истинному началу программы — что там должно быть? Как мы уже неоднократно говорили, когда контроллер доходит до команды вызова подпрограммы, или в нем происходит прерывание, он должен сохранить состояние программного счетчика с тем, чтобы потом знать, куда вер- нуться. Это, как мы уже слышали краем уха, происходит в специальной области памяти SRAM, которая называется стек (stack). Потому в любой программе на AVR-ассемблере, допускающей прерывания или просто использующей подпро- граммы, как мы уже обсуждали в главе 4, первыми после метки reset должны идти следующие строки: RESET: ldi temp,low(RAMEND) /загрузка указателя стека out SPL,temp ldi temp,high(RAMEND) /загрузка указателя стека out SPH,temp Этими операторами вы указываете, где компилятору расположить программный стек — а именно, в конце SRAM (что обозначается константой ramend, объявленной в соответствующем inc-файле). Для моделей Tiny с аппаратным стеком (ATtiny28) эти строки следует опустить, а для тех моделей, у которых стек программный, но объем SRAM не превышает 256 байт (ATtiny2313, ATtiny26 и др.), запись сокраща- ется: RESET: ldi temp,RAMEND ;загрузка указателя стека out SPL,temp
Глава 6. Основы программирования MKAVR 137 После задания стека иногда ставят следующие строки: ldi temp,l«ACD out ACSR,temp ;выкл. аналог, компаратор Почему «иногда», а не всегда? Потому что по умолчанию аналоговый компаратор всегда включен, и, соответственно, расходует питание. Если вы его не используете, то зачем лишнее потребление? Правда, это практически не скажется на потребле- нии в нормальном режиме работы — доля компаратора очень мала. Потому кри- тичной вставка этих строк становится только в случае, если задействованы режимы энергосбережения, а в обычных режимах эти команды просто ни на что не повлия- ют. Если компаратор необходим (см. главу 77), то, конечно, эти строки нужно ис- ключить. После всего этого в процедуре reset обычно идет секция инициализации, где раз- решаются конкретные прерывания, устанавливаются состояния выводов портов, инициализируются начальные значения переменных, и т. п. Примеры мы еще встретим в тексте этой книги неоднократно. Секция инициализации, как мы гово- рили ранее, обязательно должна заканчиваться командой sei — общим разрешени- ем прерываний, т. к. по умолчанию они запрещены. Теперь вроде бы все готово к работе, но что станет делать контроллер в ожидании прерываний? Ведь основная функциональность наших программ будет сосредото- чена именно в обработчиках прерываний, и в простейшем случае контроллер по- просту ничего не должен делать, пока не придет сигнал очередного прерывания. Поэтому простейшая программа должна заканчиваться пустым циклом, перед которым обязательно должно идти общее разрешение прерываний, если вы их, ко- нечно, используете (команда sei): sei ;разрешаем прерывания LOOP: rjmp LOOP Я намеренно употребил здесь для наименования метки хорошо знакомое вам слово loop, потому что замкнутый цикл здесь делает ровно то же самое, что и функция loop () в среде Arduino (название метки, понятно, никакой роли не играет). Именно внутри такого цикла работают программы, не использующие прерываний (или ис- пользующие их неявно, как в Arduino). И правда, в этом цикле можно делать что-то полезное — например, войти в один из режимов энергосбережения, или отслежи- вать изменение состояния какого-либо вывода, или, например, ожидать прихода байта через UART и т. п. — в дальнейшем мы увидим примеры подобных дейст- вий. Но мы во всех случаях, когда это возможно, постараемся без таких действий обойтись — так программа получается компактнее, а ее логика работы становится более понятной и проще проверяемой. Заметки на полях Если вы рассмотрите внимательно структуру любой программы для Arduino, то увиди- те, что она устроена совершенно аналогично нашей ассемблерной. Единственное различие — там тип контроллера указывается не в программе, а определяется от-
138 Часть II. Программирование микроконтроллеров AVR на ассемблере дельно, указанием разновидности используемой платы через меню. А вот далее все так же: сначала идут включаемые файлы через директиву #±nciude, потом определе- ния имен переменных (в немного отличающейся форме, потому что обязательно ука- зывается тип) и констант. В последнем случае применяется либо та же директива (псевдооператор) #define, либо специальное служебное слово const. У нас не встре- тишь только одного почти обязательного компонента Arduino-программы — в случае использования библиотек C++ их надо инициализировать, создавая экземпляры объ- ектов, а у нас такие библиотеки отсутствуют. Потом в Arduino обычно идут тексты ис- пользуемых функций (которые здесь у нас носят более корректное название подпро- грамм или процедур) — и у нас далее будет точно так же. Среди этих функций есть две главные, которые встречаются практически в любой Arduino-программе: setup о и loop (). В случае AVR-ассемблера все начальные установки будут делаться в начале программы (после метки Reset, так что эту метку можно считать неким аналогом setup), а заканчиваться программа будет также бесконечным циклом, пустым или со- держащим выполняемые команды. Вообще любая законченная программа в мире, которая должна работать сколько- нибудь продолжительное время, представляет собой подобный бесконечный цикл, в который она переходит после некоторой начальной инициализации при включении. Примеры на виду: скажем, и вся ОС Windows в целом, и каждое из ее приложений в отдельности представляют собой такой цикл. Исключением являются однократно выполняемые алгоритмы, которые срабатывают один раз после включения и потом останавливаются. Примеров таких программ тоже не счесть: это, скажем, стиральная машина, которая с точки зрения математика является автономным конечным автома- том1 — она выполняет заданную последовательность действий и останавливается. Все подпрограммы (процедуры, функции) также относятся к таким примерам, незави- симо от того, автономный конечный автомат они собой представляют или нет, — во втором случае подпрограмма просто имеет несколько (иногда даже очень много) ва- риантов действий в зависимости от каких-то внешних событий. Но чтобы программа в целом могла выполнять что-то полезное долгое время, все такие процедуры-под- программы обычно связаны с неким главным циклом, где по нужным событиям вызы- ваются те или иные алгоритмы их обработки. Причем в простейшей программе такой вызов осуществляется просто переходом по некоему условию (вспомните прямо- угольнички-ромбики в типовых блок-схемах программ), а в реальности их часто проще и надежнее делать с помощью прерываний — по сути это то же самое, но проверка условий и сам переход осуществляются аппаратно, оставаясь для основной програм- мы «за кадром». Именно в этом моменте будут отличаться наши ассемблерные про- граммы от обычно предлагаемого подхода в Arduino — там все в основном предлага- ется делать в главном цикле, а у нас — по большей части в прерываниях. Использование макросов Макросом называется средство, которое позволяет упаковать в единую конструк- цию сразу несколько команд или действий пользователя и вызывать их одной командой или действием. Макросы широко распространены в профессиональных программах и в компьютерных играх. Как только кто-то преодолевает свою при- 1 Чтобы не запутать читателя, следует пояснить, что здесь термин «конечный автомат» используется в абстрактно-математическом смысле, как синоним понятия алгоритма (известная машина Тьюринга есть пример такого конечного автомата). В программировании этим термином часто называют просто один из способов создания программ, реализующих некую сложную логическую функцию с различ- ными действиями для различных состояний входа.
Глава 6. Основы программирования MKAVR 139 родную лень и осваивает пользование макросами в MS Word или Photoshop, то очень быстро переходит в состояние «а как я без этого раньше жил?». Конечно, возня с макросами обретает смысл только тогда, когда приходится повто- рять некоторую последовательность действий очень часто. Делать что-то такое на один раз особого смысла не имеет, хотя в самой процедуре создания макроса нет ничего сверхсложного. Здесь мы коснемся только принципов обращения с макро- сами в AVR-ассемблере, а использовать их в повседневной деятельности или нет — принимайте решение сами. С одной стороны, развитая система макросов позволяет практически создать свой собственный язык программирования, с другой — как и любая индивидуализация, удаляет вас от стандарта, осложняет перенос и распро- странение программ. Создание несложного макроса мы покажем на примере модификации операции Ис- ключающее ИЛИ (XOR), которая в AVR имеет ограничения. Сама операция может потребоваться, например, для быстрого инвертирования битов какого-нибудь реги- стра — всех сразу или выборочно по заданной маске. Выполняется она командой, которая здесь называется еог, а ограничение ее в том, что эта команда действует лишь на пару регистров общего назначения. Следующая команда произведет опе- рацию XOR содержимого регистра г 17 с содержимым г1б (и поместит результат, понятно, в г17): еог г17,г16 На практике, однако, нам обычно требуется в качестве второго операнда не ре- гистр, а число — заданная маска битов. Поэтому мы создаем следующий макрос под названием eori (листинг 6.8) .macro eon push rl6 ldi rl6,@l eor @0,rl6 pop rl6 .endm Вызов такого макроса для преобразования регистра г17: eori rl7,mask где mask — битовая маска в виде числа (о ней подробно рассказано ъразд. «Коман- ды логических операций» главы 7). Отметим, что текст макроса в тексте программы обязательно должен стоять до его вызова, иначе компилятор его не найдет. Иными словами, макросы надо располагать в начале программы — либо там же, где дирек- тива . include и определения переменных, либо сразу после таблицы прерываний. Имя мы присвоили по аналогии с другими подобными операциями с непосредст- венными значениями (immediate), а не с регистрами, — таким же образом авторы
140 Часть II. Программирование микроконтроллеров AVR на ассемблере AVR-ассемблера образовали subi от sub и т. д. Фактически мы дополнили список команд еще одной, причем не задействовав ни одного лишнего регистра— г 16, ко- торый тут используется временно, благодаря помещению в стек после операции остается в неприкосновенности. Параметры, необходимые для выполнения макро- са, нумеруются просто по порядку, начиная с нуля, причем, как видите, никакого указания типа не требуется: вместо @о компилятор подставит то, что будет стоять на первом месте при вызове, вместо @1 — на втором, и т. д. Выполнение макроса займет ровно 4 такта (вместо одного в случае чистой еог), но ни единого лишнего сверх того. Так в чем же тут собака порылась — почему нельзя всегда вместо подпрограмм, вызов которых занимает лишние такты, требует инициализации стека (даже если в самой процедуре стек не используется) и тем самым свободного пространства в SRAM, применять макросы, которые никаких ресурсов не требуют? Очень про- сто: макрос, строго говоря, не является средством программирования — это лишь удобный способ сокращения записи, не более того. Компилятор во всех местах, где встретит имя макроса, тупо подставит соответствующую последовательность ко- манд, и при обратном дизассемблировании вы никаких следов макросов уже не об- наружите. И кстати, указание компилятора на ошибку при вызове макроса также отправит совсем не на ту строку, где она допущена. И если вынос повторяющихся участков кода в подпрограммы реально снижает объем кода и экономит память, то макросы ничего такого не делают. Вместо процедур макросы можно применять просто для удобства тогда, когда вынести за скобки нужно две-три назойливо по- вторяющиеся строчки кода, выполнение которых будет длиться меньше, чем вызов подпрограммы. Но надо при этом учитывать, что читаемость программы тоже ухудшится, и ошибки будет искать труднее. НЕХ-файлы и их загрузка в контроллер Большинство команд в AVR имеют размер в два байта, из которых собственно код команды может занимать первые 4-7 битов, остальное занимают параметры, если они предусмотрены форматом команды. Все это вместе носит название командное слово, или код операции (КОП, опкод), и у AVR его формат выгодно отличается относительным единообразием: выбиваются из двухбайтового формата только jmp, call и немногие другие, у которых сами параметры могут занимать по два байта и более. Компилятор записывает каждый код операции в выходной файл с расширением .hex, который затем используется программатором для записи в контроллер. Кроме hex-файлов, есть и другие форматы записи готовых программ (самый распро- страненный— бинарный, который используется, например, во всех известных windows/dos-форматах ЕХЕ и СОМ), но hex-формат для микроконтроллеров самый распространенный, и мы будем рассматривать только его. Рассматриваемый формат придуман фирмой Intel (есть и другие «гексы»!) и отли- чается тем, что содержит числа в текстовом представлении, причем в шестнадцате-
Глава 6. Основы программирования MKAVR 141_ ричной записи. Поэтому в случае чего его можно даже править в обычном тексто- вом редакторе. Кстати, точно такой же формат применяется для записи констант в EEPROM, если это потребуется. Рассмотрим формат HEX подробнее. На рис. 6.1 представлен файл короткой про- граммы, открытый в обычном Блокноте. На первый взгляд, тут сам черт ногу сло- мит, но на самом деле все достаточно просто, хотя чтение затрудняется тем, что строки не поделены на отдельные байты (и никаких, конечно, добавок в виде $ или Ох тут не имеется). Разбираться будет проще, если вы скопируете этот файл под другим именем и расставите в нем пробелы после каждой пары символов. , Рис. 6.1. Файл формата HEX в Блокноте Основную часть файла занимают информационные строки, содержащие непосред- ственно КОП. Они состоят из ряда служебных полей и собственно данных. Каждая строка начинается двоеточием и заканчивается парой символов: «возврат карет- ки»/«перевод строки», на экране не отображаемых. После двоеточия идет число байтов в строке — кроме первой и последней, везде стоит, как видите, число ю (де- сятичное 16), т.е. в каждой строке будет ровно 16 информационных байтов {ис- ключая служебные). Затем следуют два байта адреса памяти — куда писать (в пер- вой строке оооо, во второй это; естественно, оою, т. е. предыдущий адрес плюс 16, и т. д.). Наконец, после адреса расположен еще один служебный байт, обозначаю- щий тип данных, который в информационных строках равен о о, а в первой и по- следней — 02 и 01, о чем далее. Только после этих шести байтов начинаются собст- венно байты данных, которые означают соответствующие КОП, записанные по- словно, причем так, что младший байт идет первым. КОП для AVR, напоминаю, занимают в основном два байта, и память в этих МК также организована пословно. Таким образом, запись в первой информационной строке засо в привычном нам «арабском» порядке, когда самый старший разряд располагается слева, должна вы- глядеть, как со за.
142 Часть II. Программирование микроконтроллеров AVR на ассемблере Заметки на полях Обратите внимание, что путем такого формата можно адресовать максимум $FFFF+1 (65 536) адресов (наследие Intel 8086, в котором память делилась на сегменты по 64 К), т. е. охватить объем памяти программ в 131 072 байта (128 К). Для контролле- ров, содержащих больший объем памяти (ATmega2560), этот адрес должен, очевидно, интерпретироваться как-то иначе. Мы не станем на этом останавливаться, т. к. на ассемблере вы никогда не напишете код такого объема, — просто запомните этот факт на всякий случай. В первой строке служебный байт типа данных равен 02, и это означает, что данные в ней представляют сегмент памяти, с которого должна начинаться запись (в рас- сматриваемом случае оооо). Заканчивается НЕХ-файл всегда строкой :00000001ff — значение типа данных oi означает конец записи, данных больше не ожидается. А что означает ff? Самым последним байтом в каждой строке идет контрольная сумма (дополнение до 2, иначе она называется LRC, Longitudinal Redundancy Check) всех остальных бай- тов строки, включая служебные. Алгоритм вычисления LRC очень простой — нужно вычесть из числа 256 значений всех байтов строки (не обращая внимание на перенос) и взять младший байт результата. Соответственно, проверка целостности строки еще проще — нужно сложить значения всех байтов (включая контрольную сумму), и младший байт результата должен равняться нулю. Так, в первой строке число информационных байтов всего два, оба равны нулю, плюс (в начале) число информационных байтов, равное 2, плюс служебный байт типа данных, равный также 2, итого контрольная сумма всегда равна 256 -2-0-0-2 = 252 = $FC. В последней строке одни нули, кроме типа данных, равного 1,— соответственно, контрольная сумма равна 256 - 1 = 255 = $FF. Теперь попробуем немного расшифровать данные. Первое слово в первой инфор- мационной строке, как мы выяснили, равно $соза. Если мы возьмем фирменное описание команд, то обнаружим, что значению старшей тетрады в КОП, равной $с (1100 в двоичной системе), соответствует команда rjmp— как мы знаем, практиче- ски любая программа начинается с безусловного перехода на метку reset. Теперь очевидно, что остальные биты в этом значении ($оза) представляют абсолютный адрес в программе, где в тексте стояла метка reset. Попробуем его найти — для этого вспомним, что адреса отсчитываются по словам, а не по байтам, т. е. число $за (десятичное 58) нужно умножить на 2 (получится 116 = $74) и искать в этой об- ласти. Разыщем строку с адресом $0070, отсчитаем от начала шесть служебных бай- тов, потом три слова (три пары байтов) от начала данных, и найдем там фрагмент F894, который в нормальной записи будет выглядеть как $94F8, а это, как легко убе- диться по справочнику, есть код команды cli, запрещающей прерывания (которая в начале программы лишняя, т. к. они все равно запрещены, но, видимо, поставлена на всякий случай). Следующая команда будет начинаться с байта $Е5, и первая тет- рада в ней обозначает код команды idi (1110— проверьте!), а пятерка, очевидно, есть фрагмент адреса конца памяти (ramend), который в силу «зеркального» форма- та записи получается на самом деле равным $025f (cm. значение младшего байта, равное $2f). Это соответствует значению ramend, определенному в inc-файлах для
Глава 6. Основы программирования MKAVR 143 МК с 512-ю байтами встроенного ОЗУ ($025F = 607, т. е. всего адресов 608, из кото- рых 96 ($5f) занимают регистры, итого получается 512 ($0200) незанятых байтовых ячеек, составляющих ОЗУ). Все, как и должно быть, — если мы обратим внимание опять на первую-вторую строки с данными, то увидим повторяющийся фрагмент 1895, который, как легко догадаться, должен быть командой reti из таблицы пре- рываний — если проверите по справочнику, то так оно и окажется. Как видите, разобраться хоть и сложно, но при некотором навыке и наличии под рукой таблицы двоичных/шестнадцатеричных кодов команд вполне можно. Имен- но так работает программа, которая превращает код обратно в текст, — дизассемб- лер (он входит в AVR Studio). Впрочем, в дизассемблированной программе разо- браться бывает еще сложнее, чем в самом hex-файле, т. к. там, естественно, нет никаких адресных меток и определений, все в абсолютных числах. Так что если действительно надо дизассемблировать программу, то лучше делать это «вживую» в AVR Studio. А зачем это может понадобиться на практике? Дело в том, что в памяти программ часто хранят константы — те, что предположительно не будут изменяться в про- цессе эксплуатации, — например, устанавливаемые по умолчанию значения каких- либо величин. Но, разумеется, по истечении некоторого времени или при переносе на другое устройство эти константы обязательно захочется изменить. И если у вас текст программы по каким-то причинам отсутствует (например, программа взята из публикации в журнале или скачана с радиолюбительского сайта), а загрузочный hex-файл имеется, то всегда можно «хакнуть» исходный код и немного подправить его под свои нужды. Загружаются hex-файлы программой, которая прилагается к вашему ISP-про- грамматору. В AVR Studio это делается прямо из среды программирования, для чистого ассемблера необходимо программу-загрузчик запустить отдельно. На рис. 6.2 показано окно программы ASISP, прилагающейся к описанным в главе 5 программаторам AS-2/3/4. Подробно я ее описывать не стану — к ней прилагается вполне толковое описание на русском, и даже без него разобраться в ней не так уж сложно. Укажем только правильную последовательность действий — при ее нару- шении вы, скорее всего, ничего не испортите, но и загрузки не произойдет. В прин- ципе, те же самые действия необходимо произвести в любом другом загрузчике. Первым делом программу следует настроить на нужный последовательный порт. Для этого подключите программатор к компьютеру, обратитесь в Диспетчер уст- ройств, где в группе Порты СОМ и LPT должен появиться новый последователь- ный порт, и запомните его номер (например, СОМ 6). Этот порт нужно указать в меню Настройки | Настройки интерфейса. Номер порта сохраняется, и если вы в дальнейшем не будете менять гнездо USB, к которому подключен программатор, то настраивать его придется только при первом включении. Затем подключите контроллер через выводы ISP (см. главу 5) к программатору и включите питание устройства, где установлен контроллер. В программе нажмите на кнопку Чтение сигнатуры, после чего программа должна автоматически вы-
144 Часть II. Программирование микроконтроллеров AVR на ассемблере светить название контроллера в окошке выше этой кнопки (на рис. 6.2 это ATmega8535). Если вы этого не сделаете, то в дальнейшем вам укажут на ошибку. Это действие одновременно служит проверкой, что все подключено правильно. После этого необходимо через меню Flash | Открыть указать hex-файл, который вы собираетесь загрузить. Затем можно уже произвести загрузку, для чего последо- вательно нажать на кнопки Стирание микросхемы и Программир. Flash. В об- щем-то этого достаточно, но для очистки совести можно еще осуществить провер- ки: после стирания нажать на кнопку Проверка на чистоту, а после загрузки — на кнопку Проверка Flash. Рис. 6.2. Программа ASISP Все это можно делать гораздо быстрее, если настроить действия кнопки Автопро- граммирование, для чего в окне, открывшемся по команде меню Настройки | На- стройки проекта, расставить пять флажков: Стереть, Проверить, Перезагрузить, Запрограммировать, Проверить в группе Настройки автопрограммирования. При этом у вас автоматически каждый раз будет заново загружаться измененная версия hex-файла, и опасность забыть ее обновить после правки программы исчезнет. Только не забудьте загрузить вручную hex-файл с другим именем, если вы перешли к другой программе, — читать ваши мысли программа ASISP пока еще не обучена. Настройки автопрограммирования не сохраняются, и при каждом за- пуске программы их надо настраивать заново, что совершенно оправданно: мало ли чего вы там понастроили прошлый раз.
Глава 6. Основы программирования MKAVR 145_ И еще одно обязательное действие обычно требуется выполнить перед началом эксплуатации нового контроллера — установить ему fuse-биты. Причем это делает- ся один раз, и выносить в автопрограммирование это действие я не рекомендую, — повторять его нет нужды. Настройка fuse-битов вынесена в отдельное меню На- стройки | Lock/Fuse биты (см. рис. 5.7-5.9 в главе 5) и единственное, что требует- ся по этому поводу выучить наизусть: перед внесением любых изменений в fuse- биты следует обязательно прочитать из контроллера их текущее состояние соответ- ствующей кнопкой. Надо учитывать, что после программирования контроллер перезагрузится (даже два раза — после программирования и после проверки Flash) и начнет работать сразу, т. к. на него подано питание. Поэтому я по мере возможности стараюсь не занимать выводы программирования каким-либо функциями, но, конечно, это соблюдать необязательно, а иногда и невозможно. На этот счет можно быть спокойными — чтобы испортить тут что-то неправильным подключением, надо очень постараться. Единственное неудобство в том, что, как правило, при занятых выводах програм- мирования приходится каждый раз для проверки работы схемы отключать про- грамматор. О Bootloader Наличие в Arduino предустановленной загрузочной программы Bootloader несрав- ненно облегчает и упрощает взаимодействие с платами контроллеров. Если бы не эта «фича», позволяющая загружать и отлаживать программы буквально одним щелчком мыши, то Arduino никогда бы не получила такой популярности. Однако существенная часть ограничений платформы Arduino также проистекает из этого факта. Для начала Bootloader отъедает кусок памяти программ. Но на удивление незначи- тельный — если посмотрите на результат компиляции любого скетча, то по поводу занимаемой памяти там будет написано, что «всего доступно 32 256 байт». До пол- ного объема памяти ATmega328, равной 32 К, недостает 32 768 - 32 256 = 512 бай- тов, что и есть объем, занимаемый Bootloader'ом. 0,5 кбайта из 32 (1,5%)— согла- ситесь, это небольшая цена за такое удобство. Гораздо хуже другое: Bootloader есть точно такая же программа, как любая другая, потому он привязан, во-первых, к определенному типу контроллеров, во-вторых, что самое главное, — к конкретной рабочей частоте. Изменение типа МК в преде- лах группы контроллеров, обладающих схожим набором периферийных устройств, в принципе возможно, но никогда не знаешь, какие при этом встретятся подводные камни. Изменение тактовой частоты, наоборот, невозможно, иначе перестанет ра- ботать последовательный порт, и Bootloader окажется бесполезным (можно это обойти, если выбирать кратные частоты тактирования и, соответственно, менять скорость порта, но такое решение тоже не универсальное). И наконец, наличие Bootloader'а принципиально замедляет загрузку: он обязан перехватывать управле- ние в первый момент и ждать, не «постучится» ли что-то в последовательный порт, и лишь потом передавать управление основной программе.
146 Часть II. Программирование микроконтроллеров AVR на ассемблере Кроме того, к Bootloader'y должна прилагаться периферия, соответствующая задей- ствованному порту (это может быть не только UART, хотя это и самый частый слу- чай). Все это вместе взятое ограничивает применение Bootloader'a определенной сферой. В профессиональной практике наличие встроенного загрузчика не столь уж редкое явление — дистанционная смена прошивки всяких там смартфонов именно так и производится. А вот в нашем деле — с изготовлением уникальных единичных экземпляров аппаратуры — возня с Bootloader'ом скорее помешает основному де- лу. Исключение могут составить случаи, когда вы хотите наладить удобную заме- ну, например, калибровочных коэффициентов в EEPROM без доступа к «телу» контроллера. Но не забывайте при этом, что Bootloader'bi не бывают универсаль- ными, и их всегда надо подбирать под конкретный тип МК, а потом еще, возможно, и подстраивать под выбранную тактовую частоту. И напоследок одно замечание: имеющийся Bootloader почти наверняка будет ис- порчен, если попытаться запрограммировать содержащий его контроллер через обычный внутрисхемный ISP-программатор,— Bootloader после этого придется загружать заново. Поэтому применять тот и другой способ вперемешку нельзя: либо последовательный порт, либо ISP. Простейшая программа Сначала мы покажем, что простейшие ассемблерные программы для AVR не нуж- даются ни в каком специальном оформлении. Для этого рассмотрим уже ставший классическим пример с попеременной установкой вывода в высокий и низкий уро- вень (простейшую генерацию частоты,— см. подробный разбор этого примера в [1]). Заодно продемонстрируем, почему язык С (не говоря уж об Arduino) все-таки отчуждает нас от того, что реально происходит в контроллере. Если у вас есть под рукой Arduino Uno или любая другая плата Arduino на основе ATmega328, загрузи- те в нее такой простой скетч (листинг 6.9). void setup() { pinMode(5, OUTPUT); //вывод 5 - на выход } void loop () { digitalWrite(5, HIGH); digitalWrite(5, LOW); Если вы подключите к выводу 5 Arduino осциллограф, то сможете убедиться, что на нем генерируются прямоугольные импульсы с частотой чуть больше 70 кГц. Есть много других вариантов выполнения подобного алгоритма, но если в них
Глава 6. Основы программирования MKAVR 147_ установка уровня на выводе осуществляется функцией digitaiwrite (), то частота не выйдет за пределы 70-80 кГц. Но что же это такое, в самом деле? В Arduino контроллер работает на частоте 16 мегагерц, и 70-80 килогерц — это максимум того, что он может выдать в таком простом алгоритме?! Убедиться, что виноват в этом случае чересчур навороченный язык Arduino несложно, даже не выходя за рамки среды Arduino, если использовать прямое программирование порта на С. Тот же самый скетч, но напрямую обра- щающийся к портам контроллера, будет выглядеть для Arduino так (листинг 6.10). void setup() { DDRD = 0x20; while (true) { PORTD = 0x20; PORTD = 0; void loop() {} Цифровой вывод 5 — это бит номер 5 порта D (см. таблицу соответствия выводов ATmega328 и плат Arduino, которая имеется на любом официальном сайте Arduino), и маска 0x2 о (= ОЬОО100000), приложенная к порту ddrd, установит этот вывод на выход. Этот скетч выдаст на выводе 5 частоту, почти равную 4 МГц, что куда больше похоже на работу 16-мегагерцевого контроллера. Теперь повторим то же самое на ассемблере, выбрав в качестве «подопытного кро- лика» ATmega8. Хотя с таким же успехом могли бы выбрать любой другой кон- троллер, в котором присутствует 5-й вывод порта D (а у тех, у которых порт D от- сутствует или его выводов меньше пяти, можно заменить его на любой другой вы- вод любого порта). Тактовую частоту можно установить любую желаемую, от внутреннего генератора или внешнего кварца, как удобнее (см. по этому поводу разд. «Конфигурационные ячейки» главы 5). Ассемблерная программа, соответст- вующая последнему скетчу, будет состоять из следующих строк (листинг 6.11). Листинг 6,11 ; .include ffm8def.inc" ldi г16,0Ь00100000 ;регистр г16 = 0x20 out DDRD,rl6 ;вывод PD5 на выход clr г17 ;регистр rl7 = 0 Gcykle: out PortD,rl6 ;PD5=1 - 1 такт out PortD,rl7 ;PD5=0 - 1 такт rjmp Gcykle ;переход обратно 2 такта
148 Часть II. Программирование микроконтроллеров AVR на ассемблере Ее можно еще упростить, если, во-первых, отказаться от включаемого INC-файла, во-вторых, удалить команду обнуления регистра ri7 (cir ri7), которая здесь вклю- чена больше для порядка: регистры общего назначения и без того обнуляются при сбросе в момент включения питания. А inc-файл, содержащий мнемонические на- именования регистров, включен для упрощения переноса на другие модели — проще раз и навсегда запомнить название PortD, чем лазать по даташитам в поисках абсолютных адресов регистров ввода/вывода для каждой модели. В этом случае для другой модели нужно будет только заменить название inc-файла и, конечно, прове- рить, существуют ли у нее используемые порты. Эта программка содержит 6 команд и займет в памяти 12 байтов. Первые две команды выполняются один раз, а далее в цикле по одному такту занимают коман- ды out и еще два такта — команда безусловного перехода г jmp, так что цикл зани- мает четыре такта, и, следовательно, при любой тактовой частоте выдаст на выводе PD5 (выводе ATmega8 под номером 11) частоту, равную ровно четверти от тактовой. Если у вас есть осциллограф, можете попробовать различные кварцы или менять частоту встроенного генератора и убедиться, что частота каждый раз дейст- вительно равна ровно 74 от установленной. Но программа из листинга 6.11 не очень хороша с точки зрения экономии ресурсов контроллера: мы занимаем два регистра общего назначения г1б и г 17 на все время, пока работает цикл. К тому же эти регистры не меняют своего состояния, т. е. хра- нят константы. Нельзя ли действовать как-то попроще, если нам всего-то надо установить или сбросить бит? Конечно, можно (листинг 6.12). . include flm8def. inc" ldi г16,0Ь00100000 /регистр г16 = 0x20 out DDRD,rl6 ;вывод PD5 на выход Gcykle: sbi PortD,5 ;PD5=1 - 2 такта cbi PortD,5 ;PD5=0 - 2 такта rjmp Gcykle /переход обратно 2 такта Эта программа делает то же самое, но регистров не занимает, а непосредственно устанавливает/сбрасывает бит в порту D. Единственное отличие: каждая операция sbi /cbi занимает не один такт, а два. Потому частота на выходе будет равна уже не V4 от тактовой, a V6. Можете проверить этот факт осциллографом. Вы еще увидите далее примеры того, что любую программу можно оптимизировать по числу зани- маемых регистров, можно — по времени выполнения, а можно и по количеству ко- манд, т. е. по объему, занимаемому в памяти. Разительное отличие от Arduino, где мы можем вроде бы делать то же самое, но даже не знаем, как именно организован вывод в порт по команде portd = о, и сколько времени это занимает, правда? Примечание Напомню, что все законченные программные примеры из этой книги читатель может найти в архиве по адресу, указанному во введении, — в данном случае в папке GLAVA6.
Глава 6. Основы программирования MKAVR 149 Для более серьезного примера в следующем разделе мы, как положено новичкам, станем мигать светодиодами, но не просто так, а по нажатию кнопки. Это будет несложная тренировочная программа, в которой не нужны прерывания. В ней мы покажем, как можно создать аналоги функций delay о и deiayMicrosecondO, а так- же отслеживать события без привязки и с привязкой к внешним прерываниям. Таймер без прерываний Программа будет считать нажатия кнопки и демонстрировать их в двоичном коде на светодиодах (LED). В схеме для простоты мы ограничимся тремя светодиодами, т. е. будем считать до 8 нажатий (хотя без каких-то переделок программы можно увеличить число светодиодов до 8, и соответственно, считать до 256). Выберем одну из самых простых и удобных моделей ATmega8. Схема, для которой мы на- пишем программу, представлена на рис. 6.3. + 5В С1 » + 5В R1 + 5В 1 Reset 2RXD 3TXD 4PD2 5 PD3 6PD4 7Vcc 8GND 9 XTAL1 10 XTAL2 11PD5 12PD6 13PD7 14PB0 PC4 27 PC3 26 PC2 25 PC124 PCO 23 GND22 AREF 21 AVCC 20 SCK19 MISO 18 PB216 PB1 15 ATmega8 R1,R2 5,1 к R3-R5 330 C1.C2 0,1 мк R3-R5 VD1-VD3 Рис. 6.З. Схема двоичного счетчика нажатий В этой схеме серым цветом показано подключение программирующего 10-кон- тактного разъема, включая «подтягивающие» резисторы по выводам программиро- вания, о необходимости установки которых «так долго говорили большевики» (см. главу 3). Сам разъем, включая разводку выводов, описан в главе 5. На макетной плате разъем этот будет только мешать (и резисторы там тоже не нужны), и здесь он показан для образца — как это должно выглядеть в конечном изделии, если вы его туда будете устанавливать. Больше на схемах мы этот разъем показывать не станем, но подразумевается, что каким-то образом контроллер программируется, так что программирующие выводы постараемся не использовать в других целях. RC-цепочка на выводе RESET стандартная (см. главу 2), керамический «развязы-
150 Часть II. Программирование микроконтроллеров AVR на ассемблере вающий» конденсатор С2 по питанию устанавливается всегда, его емкость можно выбирать любой в пределах от 0,1 до 2,2 мкФ. Кнопку Кн1 для нашего случая можно подсоединять к любому из свободных кон- тактов любого порта, но именно к выводу PD2 я подключил ее с далеко идущими целями, о чем вы узнаете далее. Резистор R2 также можно не устанавливать, если обойтись встроенным «подтягивающим» резистором, но, как мы уже говорили, без него помехоустойчивость схемы будет значительно ниже: без такого резистора, может, например, происходить ложное срабатывание кнопки при бросках питания (скажем, при переключении питания с сетевого блока на резервную батарею). На- личие внешнего резистора гарантированно решает такие проблемы, поэтому лучше его устанавливать Всегда. Проблема, которую нам предстоит преодолеть, заключается в том, что любая кноп- ка дребезжит, и потому при ее нажатии или отпускании генерируется множество импульсов, из которых придется выбрать один. Использование прерывания, как следовало бы сделать «по-правильному», не избавляет от необходимости учиты- вать дребезг, поэтому в принципе решение проблемы и там, и здесь одинаково, — придется делать искусственную задержку реакции МК, для чего нам и понадобится имитатор таймера. Задержка Давайте начнем с формирования временного интервала. Нам нужно сформировать задержку порядка секунд или долей секунды. Метод без таймера основан на том, что каждая команда в МК выполняется за строго определенное время. В AVR счи- тать время вообще очень просто: большинство простых команд выполняется за один такт, и потому, например, для формирования интервала в одну десятую секунды при тактовой, частоте 1 МГц (которая, напомним, устанавливается для контроллера «по умолчанию» за счет встроенного RC-генератора), нам требуется выполнить какую-нибудь (в общем-то неважно, какую) последовательность из ста тысяч команд. Обычно программисты используют декрементирование (т. е. последовательное уменьшение на единицу) какой-либо величины. Предположим, что необходимое число займет два регистра-разряда, что даст максимальную величину числа 65 535. Схема действий такая: мы последовательно уменьшаем самый младший разряд (на- зовем его Razro) на единицу, когда его величина достигает нуля, уменьшаем на единицу следующий (Razri) и переходим опять к уменьшению младшего, начиная со значения 255 (это значение загружать специально не требуется, т. к. при вычита- нии единицы из нуля результат получится равным 255 автоматически), и так до тех пор, пока все разряды не станут нулями. Предварительно следует загрузить в пере- менные RazrO и Razri нужное число. Какое? Это зависит от конкретного алгоритма. Следующая последовательность команд (листинг 6.13) реализует этот алгоритм «в лоб».
Глава 6. Основы программирования MKAVR 151 Delay: dec RazrO brne Delay dec Razrl brne Delay <все равны 0 — конец задержки> Обратите внимание, что при использовании команды dec никаких дополнительных команд сравнения при достижении нуля не требуется (подробно об этом будет рас- сказано в следующей главе). Некрасивость приведенного решения заключается в наличии команд перехода, которые выполняются за один такт, если условие (в нашем случае равенство нулю) не выполняется, и за два такта, если оно выпол- няется. К тому же число циклов в каждой итерации, вообще говоря, разное. Поэто- му точно подсчитать число циклов становится достаточно сложно. Это хорошо, что в этой нашей задаче необязательно выдерживать точный интервал, а если надо? Значительно более компактной и предсказуемой будет реализация алгоритма на основе команды вычитания с учетом переноса (листинг 6.14). Delay: subi RazrO,1 sbci Razrl,О ;sbci Razr2,0 - если потребуется 3-й регистр brcc Delay Работает это так: команда sbci вычитает сразу две величины: то, что записано в самой команде, плюс флаг переноса с. Если результат предыдущего вычитания устанавливает флаг переноса (что происходит при переходе через ноль, когда из ноля вычитается единица), то команда sbci вычтет его значение, равное единице, если нет, то не вычтет ничего (точнее, вычтет ноль). В результате в каждой итера- ции выполняется строго определенное число команд и за строго определенное вре- мя: по одному такту на каждое вычитание, плюс два такта на переход обратно к началу цикла (для команды brcc условие перехода выполняется, если флаг пере- носа не установлен), — всего при двух регистрах четыре такта. (Для особо въедли- вых отметим, что самый последний цикл будет на один такт короче). Итак, для того чтобы получить ровно 100 000 тактов, нам нужно записать в регист- ры Razr2 - RazrO число 100 000/4 = 25 000, или $61А8. Это даст интервал в 0,1 с при тактовой частоте 1 МГц. Можно считать и иначе: одно вычитание происходит за четыре такта, что равносильно снижению тактовой частоты вчетверо, до 250 кГц. 1 секунда будет тогда равносильна 250 тыс. таких суммарных тактов, а 0,1 секун- ды — 25 тысячам. В общем случае число N9 соответствующее нужному интервалу времени Т (с) при тактовой частоте/^акт (Гц), можно получить по формуле N= Т/тгкт/(г + 2), где г —
152 Часть II. Программирование микроконтроллеров AVR на ассемблере число регистров (каждый регистр — еще одна команда sbci продолжительностью в 1 такт). Всего с двумя регистрами и тактовой частотой 1 МГц мы можем получить задержку до 0,26 с, если запишем в них число 65 535 = $ffff, а с тремя регистрами при той же тактовой частоте — задержку почти до 84 с, если запишем в них число 16 777 215 = $FFFFFF. Программа счетчика Сначала разберемся с нажатием кнопки. Отслеживать состояние вывода удобно командами sbic или sbis (Skip if Bit in I/O register Clear/Set, пропустить следующую команду, если бит в РВВ очищен/установлен) применительно к второму биту мас- сива PinD, к которому и подключена кнопка. Листинг 6.15 иллюстрирует простей- ший цикл слежения за состоянием кнопки (когда кнопка нажимается, состояние вывода меняется с единицы на ноль). Pincykle: ;цикл отслеживания кнопки sbic PinD,2 ;пропустить, если нажата rjrnp Pincykle /вернуться обратно, если не нажата <кнопка нажата — что-то делаем> rjmp Pincykle /вернуться обратно к отслеживанию Даже когда нам требуется отслеживать только нажатие (а не последующее отпус- кание, как описано далее), то такой простейший цикл работал бы отвратительно: вся процедура вместе с возможными действиями по факту нажатия займет микро- секунды, а даже при самом быстром ударе по кнопке замкнутое состояние контак- тов будет продолжаться доли секунды. Потому, обнаружив при переходе к началу цикла, что замкнутое состояние продолжается, процедура будет выполняться снова и снова, пока вы кнопку не отпустите (сравните с описанием работы прерывания по уровню в главе 3). Чтобы этого не происходило, как минимум, необходимо ввести задержку перед возвращением к началу цикла, а еще лучше при этом проверять со- стояние кнопки несколько раз, чтобы уберечься от случайной помехи. Чтобы потом не повторять процедуру задержки в коде несколько раз с разными значениями, мы сразу оформим задержку в виде макроса (можно в виде функции, но тогда надо еще не забыть инициализировать стек, и мы будем терять по 7 тактов при каждом вызове, что в других случаях может оказаться существенным при ма- лых задержках). Тогда вся программа, включая секцию определений, будет такой, как в листинге 6.16. ;Программа счета нажатий кнопки в двоичном коде .device АТтедаЭ .include "in8def.inc" ;частота по умолчанию 1 МГц
Глава 6. Основы программирования MKAVR 153 .macro Delay /процедура задержки ldi Razrl,@0 ;старший байт задержки ldi RazrO,@1;младший байт задержки R_sub: subi RazrO,1 sbci Razrl,0 brcc R_sub .endm .def temp = rl6 ;рабочая переменная .def RazrO = rl7 ;разряды задержки .def Razrl = rl8 .def Counter = r20 ;счетчик .org 0 ;необязательно, просто для ориентировки ;============ Программа ============ ldi temp, ОЬОООООЮО ;для второго разряда порта D out PORTD,temp ;подтягивающий резистор на всякий случай ldi temp, 0Ы1111111 ;порт С все контакты на выход out DDRC,temp clr Counter ;очищаем счетчик Pincykle: ;цикл отслеживания кнопки sbis PinD,2 /пропустить, если не нажата rjmp push_pin rjmp Pincykle /вернуться обратно, если не нажата push_pin: /кнопка нажата Delay 0,$19 /задержка 100 мкс с, N = $0019 sbic PinD,2 /пропустить, если по-прежнему нажата rjmp Pincykle /вернуться обратно к отслеживанию inc Counter / точно нажата - увеличиваем счетчик out PORTC,Counter /выводим счетчик в порт В Delay $СЗ,$50 /задержка 0,2 с, N = $С350 = 50000 Delay $СЗ,$50 /задержка 0,2 с, N = $С350 = 50000 rjmp Pincykle /вернуться обратно к отслеживанию Как видите, программа небольшая — в памяти она займет 56 байтов. Здесь сначала дважды с паузой в 100 мкс кнопка проверяется на состояние нажатия (на PinD,2 должен быть тогда логический ноль). Если оба состояния совпадают, то увеличива- ем счетчик, выводим его в порт со светодиодами и, сделав длинную паузу, возвра- щаемся к отслеживанию кнопки. Здесь пришлось ввести две задержки по 0,2 с — это было сделано по результатам практических испытаний, т. к. 0,2 секунды слиш- ком мало, чтобы успеть снять палец с кнопки, и скажется эффект многократного повтора, который мы тут пытались обойти. Можно избежать двукратного повторе- ния кода задержки, если добавить еще один регистр. Подробности Разумеется, значения старшего и младшего байта можно не высчитывать вручную — компилятор сделает все за вас, если вы оформите вызов задержки вот так: Delay high(50000),low(50000) /задержка 0,2 с, N = 50000
154 Часть II. Программирование микроконтроллеров AVR на ассемблере Я обычно пишу в hex-форме только потому, что так короче и наглядней, и под рукой у меня всегда калькулятор, который переводит в Лех-форму и обратно одним щелчком мыши. Счетчик counter будет считать «вкруговую» — при переполнении он опять начнет с нуля. Программа выводит состояние счетчика в порт С целиком, таким образом работа ее не зависит от того, сколько именно светодиодов вы подключите в выво- дам этого порта, — все шесть (доступных в ATmega8) или только три, как на схеме. Подробности Пояснений здесь требует момент, связанный с определением переменных, — почему мы начали сразу с регистра г 16, а не с го, например? Одно из самых больших не- удобств AVR заключается в том, что команды, оперирующие с константами (idi, sbci и subi — в нашем случае, а также команды cpi, andi и др.) не работают с первыми шестнадцатью РОН (от го до ri5), их можно использовать только для регистров ri6 - r3i (заметим сразу про еще одно аналогичное ограничение: команды поразрядного досту- па к РВВ sbi, cbi, sbis и sbic работают только для первых тридцати двух РВВ, до но- мера $if включительно). Для сокращения программы рабочие переменные (temp, счетчики) всегда желательно выбирать из этой половины регистрового файла. Поло- жение осложняется тем, что регистры из старшей половины наиболее дефицитны — последние шесть из них объединены в пары х, y и z для работы с памятью и некото- рых других операций (см. далее), г24 и г25 задействованы в команде adiw, и т. п. Если переменных не хватает, то, чтобы не связываться с локальными переменными или не переходить на работу с памятью, загрузку регистра из первой половины регистрового файла (допустим, это ri5) непосредственным значением приходится осуществлять парой команд: Idi temp,10 mov r15,temp Но самое главное не это— возникает вопрос, нельзя ли улучшить алгоритм так, чтобы не приходилось думать о том, как быстро надо снять палец с кнопки? Это можно сделать, если дополнительно еще отслеживать отпускание кнопки, что не так уж и просто, поскольку из-за дребезга нажатие и отпускание — с точки зрения контроллера — по большому счету различаются только начальной и конечной фа- зой, а в остальном они представляют собой одинаковые пачки импульсов общей длительностью, как правило, несколько десятков микросекунд. Поэтому общая схема «отлова» «настоящего» отпускания кнопки должна быть в этом случае такой: «ловим» нажатие (т. е. появление низкого уровня на выводе PD2), делаем паузу, чтобы пропустить дребезг, и начинаем «ловить» отпускание (т. е. первое появление высокого уровня на выводе). Затем проделываем необходимые действия и опять выдерживаем «антидребезговую» паузу перед тем, как все начать сначала. Дли- тельность пауз нужно достаточно точно рассчитать, иначе возможен пропуск коротких и быстро следующих друг за другом нажатий. Примем для простоты, что обе паузы будут составлять 0,1 с (возможно, в реальной конструкции эти величины потребуется подогнать «по месту»). Заметки на полях Наверное, вы замечали, что экранные кнопки в графическом интерфейсе Windows реагируют именно на отпускание кнопки мыши. Психологически нажатие есть опера- ция, к которой человек — особенно нетренированный «чайник» — должен подгото-
Глава 6. Основы программирования MKAVR 155_ виться: прицелиться пальцем (или курсором) и выбрать свободный ход кнопки, чтобы четко зафиксировать нажатие в определенный момент времени. В то же время отпус- кание никакой специальной подготовки не требует— просто расслабьтесь, и палец сам соскользнет с кнопки. Потому всегда, когда требуется зафиксировать определен- ный момент времени, кнопка должна реагировать на отпускание (сравните с подготов- кой гранаты-«лимонки» к бою, когда сначала вынимается кольцо, а граната срабаты- вает лишь после того, как вы ее выпустите из рук, освободив рычаг чеки). Иной случай представляет собой, например, компьютерная клавиатура, где задача стоит не зафик- сировать момент времени, а обеспечить как можно больше нажатий в единицу време- ни, — там кнопки реагируют именно на нажатие (но и работу с клавиатурой приходит- ся специально осваивать). Также именно на нажатие следует реагировать в случае однократного действия, когда момент отпускания безразличен, а дребезг не оказывает никакого влияния, — примером может служить кнопка «Пуск» с блокировкой на пульте управления каким-нибудь станком. /Программа счета отпусканий кнопки в двоичном коде .device ATmega8 .include "m8def.inc" /частота по умолчанию 1 МГц .macro Delay /процедура задержки ldi Razrl,@0 /старший байт задержки ldi RazrO,@l/младший байт задержки R_sub: subi RazrO,l sbci Razrl,0 brcc R_sub .endni .def temp = rl6 /рабочая переменная .def RazrO = rl7 /разряды задержки .def Razrl = rl8 .def Counter = r20 /счетчик .org 0 /необязательно, просто для ориентировки /============ Программа =========== ldi temp,ObOOOOO100 /для второго разряда порта D out PORTD,temp /подтягивающий резистор на всякий случай ldi temp,Obllllllll /порт С все контакты на выход out DDRC,temp clr Counter /очищаем счетчик Pincykle: /цикл отслеживания кнопки sbis PinD,2 /пропустить, если нажата rjmp Pincykle /вернуться обратно, если не нажата /кнопка нажата - пауза push_pin: /кнопка нажата Delay 0, $19 /задержка 100 мкс с, N = $0019 sbic PinD,2 /пропустить, если по-прежнему нажата
156 Часть II. Программирование микроконтроллеров AVR на ассемблере rjmp Pincykle /вернуться обратно к отслеживанию Delay $61,$А8 /задержка 0,1 с, N = $61А8 Pin_release: /отслеживаем отпускание sbis PinD,2 /пропустить, если отпущена rjmp Pin_release /вернуться обратно, если еще нажата inc Counter /если отпущена, увеличиваем счетчик out PORTC,Counter /выводим счетчик в порт С /кнопка отпущена - пауза Delay $61,$А8 /задержка 0,1 с, N = $61А8 rjmp Pincykle /вернуться обратно к отслеживанию Как видите, программа (листинг 6.17) не больше предыдущей — в памяти она зай- мет 58 байтов. Работает она гораздо лучше: не дребезжит, если вы забыли вовремя снять палец, и на меньшее время «тормозит» контроллер— 0,2 с вместо целой секунды с лишним в предыдущем случае. Можно еще снизить эту величину, если подогнать паузы «по месту» — реальный дребезг, конечно, никогда не продлится целых 100 миллисекунд. Подробности Да, кстати, а почему мы тут не проверяем дважды состояние при отпускании? А пото- му, что никакая помеха не сможет пробиться на вывод, который накоротко замкнут с «землей». На «подтягивающий» резистор вполне может (особенно если вы забыли поставить внешний и обошлись внутренним), а если уж на замкнутой ранее кнопке вдруг образовался высокий уровень — это ее однозначно отпустили. Ради любопытства давайте проверим упрощенный Arduino-аналог второго варианта (листинг 6.18). byte count=0; void setup() { DDRC = Oxff; PORTD=0x04; } void loop() { if (digitalRead(2)!=1){ delayMicroseconds(10000) ; if (digitalRead(2)==l){ delayMicroseconds(10000); count++; PORTC=count; Здесь мы по возможности минимизировали код, не проверяя состояние дважды и не пользуясь штатными ардуиновскими функциями pinMode () и digitaiwrite () (иначе
Глава 6. Основы программирования MKAVR 157_ пришлось бы к тому же придумывать конвертер значения count в двоичное пред- ставление для вывода в отдельные биты порта С — выводы А0-А2, работающие в нашем случае как обычные цифровые порты). Не стали также возиться с собст- венной программной задержкой, использовав штатную deiayMicroseconds () (о ве- личине задержки 10 000 мкс— см. врезку «Подробности» далее). По идее deiayMicroseconds () является аналогом того же самого подхода с задержкой под- счетом тактов, никаких таймеров она не использует, так что мы тут ничем не по- грешили по сравнению с ассемблерной программой. Исключение составляет digitaiRead (), но можете заменить и ее тоже на непосредственное чтение PinD, от этого мало что изменится. А вот что важно: такая совсем несложная программа, вдвое меньше по объему текста, чем наша ассемблерная, тем не менее после ком- пиляции оказывается примерно в 20 раз объемнее и занимает в памяти 762 байта. Подробности ПОСМОТРИМ, ЧТО МЫ еще Тут ВЫИГраЛИ. Да, фуНКЦИЯ deiayMicroseconds О реализована не на прерываниях, а в виде простой программной задержки, — по идее полный ана- лог нашего подхода. Но! Во-первых, в комментариях на официальном сайте Arduino про эту функцию сказано, что «...наибольшее число, позволяющее сформировать точ- ную задержку — 16383». То есть более 16 мс она может работать кое-как, и проверить это трудно, почему мы и выбрали 10 000 мкс в своей программе. Но еще хуже она се- бя должна вести при очень малых задержках, т. к. оформлена в виде функции (макро- сов нам в С не предлагается). А вызов функции в Arduino — это совершенно отдель- ная песня, которая требует также отдельного обсуждения (если кому-то это интерес- но, можете поизучать вопрос путем дизассемблирования кода Arduino в AVR Studio). Наш же макрос будет безупречно работать при любых необходимых задержках: захо- тим — добавим в него еще регистр, получим секунды и десятки секунд, доведем число регистров до 4-х— получим уже часы, и т. д. Точно отмерить задержки порядка мик- росекунд и менее можно только так — даже в Arduino это делают с помощью inline- ассемблера. Другое дело, что при использовании прерываний и задержках более миллисекунд к такому способу прибегать нецелесообразно: прерывания внесут неоп- ределенность, если разместить задержку в главном цикле, или могут потеряться, если ее задействовать в каком-нибудь из обработчиков. В реальности в Arduino с кнопкой правильно обращаться именно так, как мы сейчас показали, может быть чуть усложненным способом, отслеживая несколько состоя- ний с небольшой паузой между ними. Я предлагаю вам самим попробовать разо- браться в коде, который получается, если подойти к этому делу грамотно (привожу без пояснений фрагмент Arduino-программы для одного из моих приборов). Выгля- дит это примерно так (листинг 6.19): boolean yes = false; byte i=0; const byte button = 2; кнопка while(!yes){ //проверяем кнопку i++; boolean statel = (digitaiRead(button)&HIGH); deiayMicroseconds(100); //задержка в 100 микросекунд yes=(statel & !digitaiRead(button));
158 Часть II. Программирование микроконтроллеров AVR на ассемблере if (i>4) break; //пробуем пять раз } if (yes) ... < выполнение кода далее> Если вы посчитаете, что этот текст понятнее, чем наша ассемблерная программа, то вам прямая дорога в С-программисты. Использование прерываний Теперь давайте попробуем сделать ту же самую программу «по-человечески» — а именно с использованием прерываний. Обычно контроллер не только следит за кнопками, а еще делает разные другие полезные операции, поэтому нехорошо за- нимать все его время пустыми циклами в ожидании, когда наконец кто-то соизво- лит нажать на кнопку. Хотя, отметим, пустые циклы, в которых МК больше ничего не делает, кроме отслеживания подобных крайне редких, с его точки зрения, собы- тий (даже за время ожидания пересылки байта через UART, занимающее при ско- рости 9600 бит/с около одной миллисекунды, контроллер успеет выполнить не- сколько тысяч команд), как раз представляют собой наименьшее зло, поскольку легко могут быть прерваны на время выполнения других процедур без опасности что-то потерять. Но в общем случае лучше, если мы будем стараться по возможно- сти освободить контроллер от бесцельного зацикливания в ожидании событий — при этом, например, невозможно задействовать режимы энергосбережения, о кото- рых пойдет речь в главе 14. Итак, вернемся к схеме и обратим внимание, что кнопка Кн1 у нас подсоединена к контакту PD2, который, если вы взглянете в описание модели ATmega8, одновре- менно служит входом внешнего прерывания into. Таким образом, чтобы ловить кнопку на отпускание, у нас вырисовывается следующая схема действий: сначала мы инициируем прерывание into по спаду и, определив таким образом, что кнопка была нажата, делаем небольшую паузу (2 мс), чтобы отфильтровать дребезг при нажатии. Затем опять разрешаем прерывание into, но уже по фронту, по возникно- вению прерывания определяем момент отпускания, производим нужные манипу- ляции со счетчиком, снова разрешаем прерывания по спаду, и все повторяется сна- чала. Программа счетчика с использованием прерываний Здесь нам придется развернуться «по полной программе», т. е. включить в состав программы таблицу прерываний, написать секцию инициализации (reset) и т. п. Таблицу прерываний мы включим полностью, т. к. для ATmega8 она невелика, а в дальнейшем, если потребуется, такой текст программы гораздо легче дорабатывать (с этой же целью я сохранил в тексте названия даже неиспользуемых прерываний). Учтите только, что предыдущие программы без использования прерываний (см. листинги 6.12-6.13), в целом годятся без переделок для любой модели МК AVR, имеющей порты D и С, а вот новую придется перед переносом внимательно просмотреть на предмет совпадения адресов прерываний и наименований регист-
Глава 6. Основы программирования MKAVR 159_ ров. Первые три строки в таблице прерываний (считая reset) одинаковые для всех контроллеров с памятью 8 Мбайт и менее, а вот дальше могут начинаться разно- чтения, в зависимости от наличия тех или иных устройств. Именно этим универ- сальный способ записи (с помощью директивы .org) удобнее прямого указания всей таблицы, но он менее наглядный и приспособленный для дальнейших дорабо- ток. Отметьте также, что, разумеется, именовать обработчики прерываний (в т. ч. и вектора сброса reset) можно любыми именами, необязательно использовать то, что приводится в описании, но мы ради единообразия сохраним фирменные имена. Код программы приведен в листинге 6.20. /Программа счета нажатий по прерыванию INTO .device ATmega8 .include Ifm8def.inc" /частота 1 МГц .def temp =rl6 .def RazrO = rl7 /разряды задержки .def Razrl = rl8 .def Counter = r20 /счетчик /============ прерывания ============ rjmp RESET /Reset Handle rjmp EXT_INT0 /External InterruptO Vector Address reti /rjmp EXT_INT1 /External Interruptl Vector Address reti /rjmp TIM2_COMP ; Timer2 Compare Handler reti /rjmp TIM2_OVF ; Timer2 Overflow Handler reti /rjmp TIM1_CAPT ; Timerl Capture Handler reti /TIM1_COMPA / Timerl CompareA Handler reti /rjmp TIM1_COMPB ; Timerl CompareB Handler reti /rjmp TIM1_OVF ; Timerl Overflow Handler reti /rjmp TIM0_OVF ; TimerO Overflow Handler reti /rjmp SPI_STC ; SPI Transfer Complete Handler reti /rjmp USART_RXC ; USART RX Complete Handler reti /rjmp USARTJJDRE ; UDR Empty Handler reti /rjmp USART_TXC ; USART TX Complete Handler reti /rjmp ADC ; ADC Conversion Complete Handler reti /rjmp EE_RDY ; EEPROM Ready Handler reti /rjmp ANA_COMP ; Analog Comparator Handler reti /rjmp TWSI ; Two-wire Serial Interface Handler reti /rjmp SPM_RDY ; Store Program Memory Ready Handler; .macro Delay /процедура задержки ldi Razrl,00 /старший байт N ldi RazrO,01/младший байт N R_sub: subi RazrO,1
160 Часть II. Программирование микроконтроллеров AVR на ассемблере sbci Razrl,0 brcc R_sub .endm EXT_INTO: ;кнопка нажата clr temp ;запрещаем прерывание INTO out GICR,temp Delay 1,$F4 ;задержка 2 мс, sbic PinD,2 ;пропустить, если нажата rjinp push_butt ; иначе ничего не делаем inc Counter ;если нажата, увеличиваем счетчик out PORTC,Counter ;выводим счетчик в порт С push_butt: ldi temp, (1«INTO) ;разрешаем прерывание INTO out GICR,temp reti /конец прерывания INTO Reset: ldi temp,low(RAMEND) ;устанавливаем указатель.на стек out SPL,temp ldi temp,high(RAMEND) out SPH,temp ldi temp,ObOOOOOlOO ;для второго разряда порта D out PORTD,temp ;подтягивающий резистор на всякий случай ldi temp,Obi1111111 ;порт С все контакты на выход out DDRC,temp clr Counter ;очищаем счетчик ldi temp, (l«ISC01) | (l«ISC00) ) ; npep. INTO по фронту out MCUCR,temp ldi temp, (1«INTO) ;разрешаем прерывание INTO out GICR,temp sei /разрешаем прерывания Gcykle: ;цикл отслеживания кнопки rjmp Gcykle /вернуться обратно, если не нажата Как видите, логика работы упростилась, но это не единственный выигрыш. Другой положительный момент заключается в том, что контроллер у нас основное время совершенно свободен. В прерывании по ручному нажатию имеется задержка в 2 мс, и с точки зрения контроллера эти события настолько редкие, что их можно не учи- тывать. Если вы при этом параллельно будете еще отслеживать какие-нибудь дат- чики, выводить данные на дисплей и передавать их по радиоканалу, то контроллер этого даже не заметит. И даже если вдруг какие-то другие действия случайно сов- падут с моментом нажатия таймера, и задержка продлится на миллисекунду боль- ше, чем запланировано, — велика ли беда? Причем задержка в 2 мс — это с боль- шим запасом, в расчете на самые плохие кнопки, обычно достаточно задержки
Глава 6. Основы программирования MKAVR 161 в пределах 100-300 микросекунд. А если вдруг даже такая задержка будет мешать, то всегда можно подключить к делу таймер и отсчитывать задержки с его помощью аппаратно, вообще не тормозя контроллер. Программа при этом получается слож- нее, потому мы здесь ее не приводим, т. к. на практике такой метод требуется не- часто (пример такой программы приведен в главе 12). Примечание Напоминаю, что все эти программы на счет по нажатию (count__push.asm и county pushjnt.asm), а также по отпусканию (count_release.asm и countjreleasejntasm) вы можете найти в архиве по адресу, указанному во введении. Наверняка читатель еще далеко не во всем разобрался, и в применении некоторых команд многое ему непонятно. В последующих главах мы рассмотрим многие во- просы подробнее, а сейчас пойдем дальше и посмотрим, что происходит с про- граммами после того, как вы их написали и откомпилировали. Сравнение ассемблерной программы с программами Arduino и другими языками высокого уровня Как писал классик программирования Дональд Кнут, «каждый, кто всерьез интере- суется компьютерами, должен рано или поздно изучить по крайней мере один ма- шинный язык». Ассемблер — это первично, а все остальное вторично. Но ассемб- лер обладает одним большим недостатком: программы на его основе получаются громоздкими и неудобочитаемыми. Это вызвано тем, что здесь любую операцию приходится разлагать на составляющие. Особенно это характерно для RISC- архитектур (к которым относится и AVR) — например, в системе команд AVR нет даже операции деления, да и операция умножения работает не во всех моделях. Никакой речи о том, чтобы следовать знаменитому лозунгу Дейкстры «программи- рование без goto», тут идти не может, т. к. ассемблерная программа, если можно так выразиться, состоит из сплошных goto— «лапши» условных и безусловных переходов, по выражению Дейкстры (он намекал на многочисленные линии ветв- ления, которые возникают в блок-схемах таких программ). Таким образом, для ра- боты на ассемблере требуется учить реализацию всех типовых приемов програм- мирования — различных циклов с условными и безусловными переходами, мате- матических операций, самостоятельно организовывать деление на локальные и глобальные переменные и т. п. Это и служит основным аргументом в пользу языков высокого уровня, где подобные реализации в большинстве случаев уже сделаны за вас. «Обычные» программисты, как огня, боятся замкнутых циклов, условие выхода из которых может не выполниться хотя бы теоретически. Бесконечное ожидание при- хода символа с клавиатуры или из СОМ-порта, в котором не предусмотрено ника- ких альтернативных средств выхода из цикла, повесит не только саму программу, но и всю систему, если речь идет об однозадачной DOS или «кооперативной много-
162 Часть II. Программирование микроконтроллеров AVR на ассемблере задачности» Windows Зле, доставляло немало неприятностей в «компромиссных» Windows 9х, и даже в настоящих многозадачных ОС, по крайней мере, заставит об- рывать работу программы принудительно. Потому в таких задачах программисты «на уровне подкорки» приучены применять средства, позволяющие выйти из тако- го цикла альтернативными методами: по таймеру или какими-то пользовательски- ми действиями (нажатием клавиши <Esc> или кнопки Cancel). Вся очередь задач в Windows, по сути, есть одно из таких решений, встроенное в систему изначально и направленное на то, чтобы не позволить компьютеру зациклиться в ожидании какого-то события. В программировании для МК подход иной. И в фирменных «аппнотах», и в приме- рах в тексте технических описаний контроллеров вы запросто можете встретить бесконечный цикл ожидания очистки какого-нибудь бита, и в теории, если этот бит никогда не очистится, МК так и «повиснет». ПК-программист от такой ситуации пришел бы в ужас, и в Arduino, в основном как раз программистами и создавав- шемся, такой подход тоже не афишируется. Конечно, есть средства вывести кон- троллер из этого состояния— наиболее кардинальным является использование сторожевого таймера, который в конце концов перезапустит систему. Более гра- мотно было бы заставить контроллер ожидать не самой по себе очистки бита, а свя- занного с этим прерывания (что позволит выполнять остальные функции без за- держек), но далеко не всегда это возможно — например, запись байта во встроен- ную EEPROM заставляет МК «висеть» несколько миллисекунд, пока этот процесс не закончится, но соответствующее прерывание в ряде моделей просто отсутствует, а если даже имеется, то задействовать на практике его неудобно из-за значительно- го усложнения логики построения программы. Однако если вдуматься, стоит задать себе вопрос: а так ли это страшно в случае контроллеров? Если по ходу дела требуется записать что-то в EEPROM, а она ока- зывается неисправной, то, очевидно, функциональность системы и так окажется нарушенной, и тут уже выходом из «зависания» делу не поможешь. В критичных случаях, разумеется, системы оснащают резервированием с возможностью автома- тического переключения между запасными модулями, но для обычных бытовых устройств принятие подобных мер только безосновательно удорожает изделие. Но если вы будете бояться подобных ситуаций, то это только хорошо: понимание, что вы делаете что-то не совсем так, как «надо бы», еще никому не мешало. Зато с ассемблером вы имеете все преимущества, о которых уже упоминалось: прежде всего это полное владение всеми системами контроллера с точным расче- том того, что происходит, когда, по каким причинам и чем закончится. Даже «чис- тый» С, как уже упоминалось, не может дать гарантии того, сколько именно про- длится та или иная процедура и сколько времени займет ее вызов. В Arduino, ис- пользующем теневые прерывания и Bootloader, эта неопределенность намного больше, что в значительной степени обесценивает, например, режимы энергосбе- режения. И кроме этого есть еще много всяких задач, где необходима лаконичность кода и точный расчет, которые без ассемблера провести было бы затруднительно.
ГЛАВА 7 Система команд AVR Познакомившись в предыдущей главе с простейшими программами для AVR, мы теперь попробуем рассмотреть систему команд AVR в целом, чтобы понять, какие возможности нам предоставляются. Всего для AVR насчитывается от 90 до 133 команд (в зависимости от контроллера), и их подробное описание в офици- альном PDF-справочнике [8] занимает 160 страниц. Потому все команды, которые к тому же часто взаимозаменяемы, мы описывать не станем, для этого существуют справочники. С некоторыми из тех команд, что выпадут из рассмотрения в этой главе, мы познакомимся по ходу дела в дальнейшем. Очень рекомендуется всегда иметь перед собой полный справочник по командам— лучше всего указанный официальный с подробными описаниями (в формате PDF или онлайн [8]), а не про- сто перечень команд, который приводится в конце каждого «даташита» на кон- кретные контроллеры. К тому же, в некоторых последних описаниях, выпущенных уже под лейблом Microchip, к составлению этих перечней отнеслись как-то ...легко- мысленно, если не сказать жестче. Обе версии официального справочника имеют очень удобную навигацию по командам, а отсутствие некоторых редких команд (в PDF-версии их всего 128) не должно вас смущать, потому что они относятся к тем контроллерам, которые вы никогда на ассемблере программировать не будете. В книгах Евстифеева [6,7] есть перевод на русский подробных описаний команд из официального справочника и, кроме того, удобные сводные таблицы команд, сгруппированных по выполняемым функциям с краткими комментариями на рус- ском языке к их использованию. Но перевод этот все-таки несколько сокращенный, и англоязычный оригинал остается основным пособием. Заметки на полях Как и в отношении главы 2, я уверен, что при первом прочтении вы из материала этой главы мало что усвоите. Но я все-таки советую прочесть ее внимательно в качестве занимательного чтения перед сном, а потом регулярно сюда возвращаться, когда вы в практических действиях столкнетесь с той или иной непонятной командой, — так ма- териал лучше запомнится. Обращу также ваше внимание на то, что большинство ко- манд устанавливают те или иные флаги в регистре sreg, что позволяет их применять для сокращения программ в нестандартном качестве. Примером может служит коман- да уменьшения на единицу dec, которую мы в главе 6 использовали одновременно в качестве детектора достижения нуля. Другой пример вы встретите в главе 9, где
164 Часть II. Программирование микроконтроллеров AVR на ассемблере команда логического И с константой andi будет применена для отсчета каждого чет- вертого прерывания. Такие подробности в этой главе про каждую команду привести невозможно — они есть только в английском оригинале справочника [8,12], и это еще одна причина всегда держать его под рукой. Обзор команд Конечно, мы не сможем и не будем пытаться описать все команды. Это и не очень нужно, во-первых, потому что свыше трети (а может, и поболее) команд — сино- нимы, т. е. делают одно и то же и даже имеют одинаковый КОП, и введены только для удобства и лучшей читаемости текста программы. Во-вторых, потому, что все команды никто никогда и не использует — у каждого программиста есть свой лю- бимый набор основных команд. А в-третьих, потому, что некоторые команды опи- сывать особо и не требуется— их применение понятно из употребления, а если нет — всегда можно обратиться к пособиям, упомянутым ранее. Необходимо отдельно отметить, что все операции с константами (idi, cpi, subi, andi, ori и т. п.) действуют только в пределах второй половины регистрового фай- ла, т. е. для РОН с номерами от г1б до г31. Поэтому во всех ассемблерных про- граммах стараются использовать под переменные именно эти регистры. Так как число их ограничено, а регистры, начиная с г24, еще и бывают задействованы в различных шестнадцатиразрядных операциях, то часто бывает необходимо при- влечь регистры из первой половины с номерами гО-г15. При этом нужно либо про- следить, что операции с константами с ними не проводились, либо использовать промежуточные операции с передачей значения констант временным регистрам из старших. Так, пара команд: mov rl6,10 ср г5,г16 равносильна недопустимой операции cpi г5,ю. Команды передачи управления и регистр SREG В языках высокого уровня была всего одна команда перехода на метку (goto), и то Дейкстра на нее набросился. А в ассемблере AVR таких команд — пруд пруди, бо- лее 30! Зачем? На самом деле без доброй половины из них, если не больше, можно обойтись во всех жизненных случаях, т. к. они в значительной степени взаимозаме- няемы, а немалая часть из них просто синонимы. Разнообразие это, если угодно, дань памяти великому программисту Эдсгеру Дейкстре — для повышения читае- мости программ. Мы рассмотрим только ключевые команды из этого перечня. С командами безусловного перехода г jmp и jmp, а также вызова подпрограмм rcaii и call мы уже достаточно подробно познакомились в предыдущей главе, так что только напомним несколько моментов. Четырехбайтовые команды jmp и call пред- назначены для контроллеров с памятью программ более 8 К, и AVR с меньшим объемом памяти их просто не поддерживают. Потому в дальнейшем в этой книге вы встретите только г jmp и rcaii. Вызовы подпрограмм rcaii и call — это те же
Глава 7. Система команд AVR 765 самые безусловные переходы на метку, но отличаются от команд безусловного пе- рехода тем, что в момент перехода к процедуре контроллер автоматически сохра- няет в стеке адрес текущей команды, чтобы потом знать, куда вернуться (потому длительность выполнения этих команд на такт больше, чем для простого перехо- да, — об этом рассказано далее). А как МК «узнает», когда именно нужно возвра- щаться? Для этого каждая процедура-подпрограмма заканчивается специальным образом — не отличаясь сначала ничем от любого другого участка программного кода, обозначенного меткой, в месте возврата она содержит команду ret (от return, возврат). По этой команде МК извлекает из стека сохраненное содержимое счетчика команд и продолжает выполнение прерванной основной программы. Подробности Кстати, хочу обратить ваше внимание, что никто не запрещает записать команду rjmp о, передав тем самым управление самой первой команде программы, находящейся по нулевому адресу. Вы уже сообразили, что это полный аналог того, что называется программной («горячей») перезагрузкой, — запуска программы на выполнение заново. В отличие от аппаратного сброса Reset (по включению питания, внешнему импульсу или по срабатыванию сторожевого таймера), программная перезагрузка, во-первых, происходит без задержек, во-вторых, сохраняет все регистры (и РОЙ, и РВВ), а также память SRAM, в неприкосновенности — в том состоянии, в котором их застал момент перехода. Я с трудом могу себе представить случай, когда такой прием может пона- добиться в простой однозадачной системе, — возможно, как простой вариант выхода из ситуации с недопустимым сочетанием каких-то условий (в результате, например, ошибочных действий пользователя). В обычных случаях (при зависании программы, например) все равно выручает только аппаратная перезагрузка. Но возможность эту надо иметь в виду. Аналогично обрабатываются прерывания — только специальной команды, как вы знаете, там нет, вызов производится обычным переходом rjmp (или jmp), но по- скольку он осуществляется с определенного адреса (там, где стоит вектор прерыва- ния), то контроллер делает то же самое— сохраняет в стеке адрес командного счетчика, на котором выполнение основной программы было грубо нарушено, начинает выполнять прерывание и ожидает команды возврата — только здесь она записывается как reti (return interrupt) — в отличие от ret, эта команда еще и вос- станавливает состояние флага прерываний i в регистре sreg. Укажем еще, что простой переход rjmp выполняется за 2 такта (jmp за 3), вызов rcaii — за 3 такта (call за 4), возвраты ret и reti — за 4 такта. В самой обширной группе команд передачи управления названия начинаются с символов Ьг (от слова branch, ветка). Это команды условного перехода, которые считаются одними из самых главных в любой системе программирования, посколь- ку позволяют организовывать циклы — базовое понятие программистских наук. По смыслу все эти команды сводятся к банальному if ... then (если ... то). Мы будем пользоваться лишь некоторыми командами, потому что они во многом взаимозаме- няемы. Смысл остальных вам будет понятен из их описания. Наиболее часто употребляется пара: bme (Branch if Not Equal — перейти, если не равно) и breq (Branch if Equal — перейти, если равно). Уже из смысла этих команд понятно, как они пишутся, — после команды следует метка, на которую нужно
166 Часть II. Программирование микроконтроллеров AVR на ассемблере перейти. Вопрос только, откуда здесь берется собственно условие? Для этого обе указанные команды ветвления обязательно употребляют в паре с одной из команд, устанавливающих флаг нуля z в регистре состояния sreg. Обычно для этой цели используют команды: ср (от compare, сравнить), которая сравнивает регистры, или cpi (сравнить с непосредственным значением). Для по- нимания того, что именно при этом происходит, нужно учесть, что указанные команды по сути вычитают второй операнд из первого, отличаясь от команд sub или subi только тем, что результат теряется, операнды остаются теми же, а меня- ются только значения соответствующих флагов регистра sreg, из которых нас интересуют в первую очередь флаг нуля z, отрицательного значения n и переноса с. Указанные флаги устанавливаются не только в результате сравнения, но и при вы- полнении операций с битами или арифметических операций, когда значение ре- зультата, соответственно, становится нулевым, отрицательным или превышает диапазон 8-битового числа (255). Что означают флаги регистра sreg в целом, можно посмотреть в табл. 7.1, где относящиеся к операциям сравнения, а также к арифме- тическим операциям, флаги выделены светлым фоном. Таблица 7.1. Биты регистра состояния sreg № п/п 5 4 3 2 Обозна- чение н S V N Наименование Флаг половинного переноса Флаг знака Флаг переполнения дополнительного кода Флаг отрицательного значения Описание Устанавливается в 1, если произошел перенос из младшей половины байта (т. е. из третьего разряда в четвертый) или заем из старшей половины байта при выполнении некоторых арифметических опера- ций Равен результату операции «Исключающее ИЛИ» (XOR) между флагами N и v. Соответственно, этот флаг устанавливается в 1, если результат выполне- ния арифметической операции меньше нуля Устанавливается в 1 при переполнении разрядной сетки знакового результата. Используется при работе со знаковыми числами (представленными в дополнительном коде) Устанавливается в 1, если старший (седьмой) раз- ряд результата операции равен единице. В против- ном случае флаг равен о
Глава 7. Система команд AVR 167 Таблица 7.1 (окончание) № п/п 1 0 Обозна- чение z с Наименование Флаг нуля Флаг переноса Описание Устанавливается в 1, если результат выполнения операции равен нулю Устанавливается в 1, если в результате выполнения операции произошел выход за границы байта Подробности Для всех флагов регистра sreg есть особые пары команд, устанавливающие или сбра- сывающие их. С одной такой командой мы уже знакомы— это установка флага i командой sei, разрешающей прерывания, к которой есть парная команда сброса этого флага си, запрещающая прерывания. Команды для остальных флагов формируются по тому же мнемоническому шаблону: например, установка флага переноса с осуще- ствляется командой sec, а сброс — командой cic, установка и сброс флага т — коман- дами set и cit. Все эти команды — лишь синонимы пары команд bset,s и bcir,s (где s — номер бита в регистре sreg), позволяющих установить или сбросить любой флаг единообразным способом. Специально для сравнения многобайтовых чисел существует команда срс, учиты- вающая флаг переноса с (сравните с командами adc и sbc в разд. «Команды ариф- метических операций» далее в этой главе). Команда эта, как и команда ср, предна- значена для сравнения с регистром, а не с константой, хотя cpi также меняет флаг переноса и может использоваться в паре с командой срс. Листинг 7.1 иллюстрирует как, к примеру, можно проверить пару переменных AddrH:AddrL, обозначающих адреса в некоей внешней памяти, на превышение числа 32 767 = $7fff (что может потребоваться для проверки выхода за диапазон допустимых адресов, если объем памяти ограничен величиной 32 кбайта). cpi AddrLfOxFF ldi temp,0x7F срс AddrH,temp brio continue_ ;если меньше, на продолжение clr AddrH ;иначе начинаем отсчет clr AddrL ;c нулевого адреса continue : Попробуем посмотреть, как команды перехода работают на практике для организа- ции циклов. Например, простейший цикл, в котором переменная temp последова- тельно принимает значения от 1 до 10, показан в листинге 7.2.
168 Часть II. Программирование микроконтроллеров AVR на ассемблере clr temp /обнулить temp back_loop: inc temp ;увеличиваем temp на 1 <что-то делаем, необязательно с помощью temp> cpi temp,10 brne back_loop Обратите внимание: если требуется, чтобы temp начинала с нулевого значения, то фрагмент «что-то делаем» следует вставить до команды inc (цикл с постусловием), но тогда последним рабочим значением temp в цикле будет 9, а не 10 (а по выходу из процедуры— все равно 10). Иногда проще построить декрементный цикл — когда переменная уменьшается от заданного значения до нуля (листинг 7.3). ldi temp,10 /загружаем 10 в temp back_loop: dec temp /уменьшаем temp на 1 <что-то делаем с помощью temp или без нее> brne back_loop Как мы уже отмечали в предыдущей главе, при использовании для обнаружения нулевого значения команды dec, вообще никакой специальной команды сравнения не требуется, потому что она при достижении нуля сама установит флаг z (то же относится к команде tst, которая проверяет на условие «равно или меньше нуля», к командам вычитания sub и subi, и т. д.). И если даже при наличии dec задейство- вать регистр из первой половины регистрового файла (где команды загрузки непо- средственного значения или сравнения с константой не работают), то лишняя команда понадобится не в каждом цикле, а только один раз — для загрузки предва- рительного значения: ldi temp,10 /загружаем 10 в temp mov rO5,temp /^загружаем 10 в гО5 и далее его используем в команде dec*/ Кроме этих команд перехода по равенству (неравенству), проверяющих значение флага z, есть команды, которые осуществляют переход по значениям других разря- дов в регистре флагов sreg, устанавливаемых в зависимости от результатов опера- ции ср или cpi. Например, команды brio (перейти, если меньше) и brsh (перейти, если больше или равно, — не следует путать ее с командой brhs, проверяющей флаг н на равенство единице) осуществляют переход в зависимости от состояния флага переноса с. Если в команде sub ri, г 2 (или ср ri, r2) первый регистр окажет- ся содержащим значение меньше, чем у второго, то флаг переноса будет установ- лен, а по команде brio произойдет переход. По команде brsh переход произойдет в противоположном случае — если флаг с оказался сброшен из-за того, что ri > г2.
Глава 7. Система команд AVR 169 Команда brio при этом полностью эквивалентна команде brcs (перейти, если флаг с установлен), а команда brsh — уже встречавшейся нам в предыдущей главе коман- де Ьгсс (перейти, если флаг с сброшен). Причем эти эквивалентные пары команд имеют также идентичные коды операций и по сути представляют собой синонимы одной команды, введенные для удобства мнемонического восприятия. Мало того, если внимательно изучить коды операций для команд условного пере- хода (а их порядка 20-ти), то окажется, что все они являются разными синонимами всего двух команд: brbs s, k и brbc s, k, где s — номер бита в регистре sreg (a k — адрес перехода). Как мы видим, эти команды более универсальны, однако их упот- ребляют редко, — для любого из флагов регистра sreg есть свои команды, которые употреблять удобнее. Важная особенность команд типа Ьгхх состоит в том, что они могут адресовать метку, отличающуюся от исходного адреса в последовательности команд не более чем на 63 позиции вперед или на 64 позиции назад, т. е. пригодны только для пере- хода «поблизости». Второй важный нюанс в работе всех команд перехода также заключается в том, что они могут занимать разное количество тактов: один или два, в зависимости от того, выполняется условие или нет. В AVR используется про- стейший конвейер команд, который «тупо» полагает, что следующей будет выпол- няться команда сразу после команды перехода. Естественно, если ветвление необ- ходимо, конвейер останавливается на один лишний такт, в течение которого проис- ходит выборка адреса перехода. Однако этот недостаток с лихвой компенсируется тем, что за счет конвейера почти все остальные команды выполняются за один такт. Наконец, нужно учитывать, что регистр sreg не сохраняется при переходе к обра- ботке прерывания, и если в прерывании встречаются команды, его модифицирую- щие, то содержимое этого регистра может быть испорчено. Поэтому, если есть тео- ретическая возможность, что прерывание «вклинится» между командой сравнения (или другой, устанавливающей биты sreg) и командой условного перехода, то sreg нужно сохранять и восстанавливать принудительно. Проще всего это делать через стек— командами push и pop в начале и в конце обработчика прерывания (лис- тинг 7.4). TIMER1: /прерывание от таймера push temp /сохраняем в стеке рабочую переменную in temp,SREG push temp /сохраняем в стеке SREG <процедура прерывания> pop_int: pop temp /извлекаем SREG out SREG,temp pop temp /извлекаем рабочую переменную reti /возврат из прерывания
170 Часть II. Программирование микроконтроллеров AVR на ассемблере Разумеется, сохранение заодно и рабочей переменной temp необязательно, и здесь приводится лишь в качестве примера. Просто в операциях сравнения нередко уча- ствует и рабочая переменная, а она также может быть испорчена в процессе обра- ботки «вклинившегося» прерывания. Заметки на полях Отметим, что во многих случаях такой прием с сохранением регистра sreg можно все же обойти, чтобы не загромождать код и не удлинять процедуры, хоть с точки зрения программистов это выглядит и неграмотно (и я вам этого не советовал!). Все дело в вероятности наихудшего стечения обстоятельств, которую всегда желательно прики- нуть (это называется инженерным подходом). Пусть, например, программа в основном цикле ожидает приема некоей команды по UART, игнорируя любые другие пришедшие байты: G_cykle: rcall in_com ; вызов процедуры приема байта - см. главу 15 cpi temp,Command_A ; определяем, та ли команда breq proc_A ;если та, то на процедуру обработки команды rjmp G_cykle /иначе все сначала Как вы увидите в главе 15, внутри процедуры incom возникновение прерываний нам не страшно. Так что неприятность случится только, если прерывание возникнет между командами cpi и breq, т. е. если оно появится за время выполнения команды cpi (и то, напомним, только, когда в прерывании есть команда, модифицирующая значение, — в рассматриваемом случае— флага нуля z). Процедура incom для приема байта по UART при скорости 9600 бит/с может длиться порядка одной миллисекунды (и весь цикл займет примерно столько же), тогда, при типовом времени выполнения команды cpi порядка долей микросекунды, вероятность неблагоприятного стечения обстоя- тельств составит всего несколько случаев на 10 000 поданных команд. Если предпо- ложить, что внешний компьютер непрерывно «бомбардирует» МК командами по UART, то вероятность сбоев достаточно велика, если же подача команды осуществ- ляется пользователем вручную раз в сутки (в месяц, в год), то, скорее всего, на такую возможность можно не обращать внимания. Гораздо удобнее выстроить «правиль- ный» протокол взаимодействия, подразумевающий контроль обмена командами (о чем мы также будем говорить в главе 15), — в критичных случаях это все равно по- требуется, потому что сбои могут происходить и по иным причинам. Но если требует- ся абсолютная надежность, конечно, лучше вдвойне застраховать себя от всех воз- можных ситуаций. И еще заметим «до кучи», что команды, заведомо не модифицирующие значение соответствующего флага в регистре sreg, можно «вклинивать» между командами, его изменяющими, и командами условного перехода, его проверяющими. Так, между проверкой «на ноль» и ветвлением по этому условию (например, парой dec ... bme) можно вставить сколько угодно команд пересылки данных или, к примеру, извлечения/сохранения регистра в стеке. Сам переход по командам семейства Ъгхх также не модифицирует содержимого sreg, и возможны конструк- ции, когда ставятся подряд две и более команды перехода по одному условию. Команды проверки-пропуска Это чрезвычайно распространенная группа команд, которые осуществляют пропуск следующей по порядку команды в зависимости от состояния отдельного бита
Глава 7. Система команд AVR 171 в РОН или РВВ. Основные из них: пары команд sbrs/sbrc (для РОН) и sbis/sbic (для РВВ). С последними вы уже знакомы по программам нажатия кнопки в пре- дыдущей главе. Они очень удобны для организации процедур, аналогичных опера- тору выбора case в языках высокого уровня, но, к сожалению, обладают непривыч- ной логикой: «пропустить следующую команду, если условие выполняется», и по- тому для новичка могут показаться слишком заумными. Чтобы их лучше понять, приведем дополнительный пример достаточно сложной по логике работы, но характерной для микроконтроллерной техники процедуры, в которой задача фор- мулируется так: при наступлении некоторого условия мигать попеременно зеленым и красным светодиодами (Led). Введем несколько предположений: □ условие мигания задается состоянием бита з в некоем рабочем регистре, кото- рый, как и в предыдущей главе, назовем регистром флагов^ — Flag. Если бит з регистра Flag равен единице (установлен) — надо мигать, если нет (сброшен) — оба светодиода погашены; □ красный светодиод подсоединен к выходу порта D номер 7, а зеленый — к выходу порта В номер 0 (они на ATmega8 находятся рядом: выводы 13 и 14. Разумеется, это могут быть любые другие выводы других портов); □ текущее состояние светодиодов задается битом 4 в том же регистре флагов Flag. Для реализации мигания можно, разумеется, и обойтись без дополнительного бита состояния светодиодов (Led) в регистре флагов, если вместо него отслеживать те- кущее состояние непосредственно выводов портов, но мы сейчас хотим проиллю- стрировать именно применение команд sbrs/sbrc, а к командам sbis/sbic вернемся позже. Алгоритм работы такой программы реализуется типичным вложенным опе- ратором выбора, код программы на языке высокого уровня приведен в листинге 7.5 (здесь и далее подобные фрагменты будут приводиться в Pascal-подобном псевдо- коде, который легче читать, чем С). case <бит 3 per. Flag> of 0: <погасить оба Led> 1: case <бит 4 per. Flag> of 1: устанавливаем PortC, вых. 7>; //зажечь зеленый Led <сбрасываем PortD, вых. 7>; //погасить красный Led <сбрасываем бит 4 Flag>; {следующей раз горим красным) 0: устанавливаем PortD, вых. 7>; //зажечь красный Led <сбрасываем PortC, вых. 7>; //погасить зеленый Led устанавливаем бит 4 Flag>; //следующий раз горим зеленым end; end; 1 Не путать с «официальным» регистром флагов SREG, описанным ранее.
172 Часть II. Программирование микроконтроллеров AVR на ассемблере Разобравшийся в алгоритме читатель уже, конечно, задает вопрос — а как обеспе- чить цикличность? В Arduino вы, без сомнения, использовали бы delay о, а в ас- семблере подобный код грамотней всего включить в обработчик события по тайме- ру с секундным, например, интервалом (хотя, конечно, можно и использовать про- стые задержки, как в процедурах для кнопки в предыдущей главе). Но с таймерами мы будем еще разбираться дополнительно, потому полного кода примера я пока не привожу — просто посмотрим, как реализовывается в AVR собственно алгоритм (листинг 7.6). sbrs Flag,3 ;если флаг 3 стоит, будем мигать rjmp dark ;иначе будем гасить sbrs Flag,4 ;если флаг 4 стоит, будем гореть зеленым rjmp set4 ;иначе красным cbr Flag,ObOOOlOOOO /следующий раз горим красным cbi PortD,7 ;гасим красный sbi PortB,0 ;горим зеленым rjmp continue ;все готово set4: ;если флаг 4 не стоит, будем гореть красным sbr Flag,ObOOOlOOOO /следующий раз горим зеленым cbi PortB,0 ;гасим зеленый sbi PortD, 7 ;горим красным rjmp continue ;все готово dark: cbi PortB,0 ;гасим оба cbi PortD,7 continue: В архиве программ для этой книги, расположенном по указанному во введении ад- ресу, вы найдете демовариант реализации этого алгоритма с использованием TimerO (файл migalka_yar1.asm). Попробовав его, подумайте заодно над вопросом: почему там не задано никаких предварительных значений для регистра counttime, отмеряющего задержку? Это непосредственно связано с темой предыдущего разде- ла о командах передачи управления. С используемыми здесь командами установки и сброса отдельных битов (sbi, sbr и т. п.) мы подробнее познакомимся чуть далее, а здесь задержимся на ключевой команде всего алгоритма — sbrs, что расшифровывается как Skip if Bit in Register is Set (пропустить, если бит в регистре установлен). Повторим, что «пропустить» нужно следующую команду, в зависимости от состояния бита (в рассматриваемом случае — если он set, т. е. установлен в единицу). В качестве этой «следующей» обычно выступает одна из команд ветвления, как здесь, но далеко не всегда — так удобно, например, организовывать выход из прерывания или подпрограммы по ка- кому-то условию, если поставить следующей после sbrs команду reti или, соответ- ственно, ret.
Глава 7. Система команд AVR 173_ Противоположная по логике процедура записывается как sbrc (Skip if Bit in Register is Cleared, пропустить, если бит в регистре очищен, т. е. равен нулю). Упоминав- шаяся ранее пара аналогичных команд: sbis и sbic — используется, когда нужно отследить состояние бита в регистре ввода/вывода, а не в РОН. Обе эти команды применимы, к сожалению, лишь к первым тридцати двум РВВ (до номера $if) — для остальных приходится передавать их значение командой in в РОН и работать с ним, как с обычной переменной. Кроме этих двух наиболее часто употребляемых пар команд (sbis и sbic, sbrs и sbrc), к группе команд проверки-пропуска относится оригинальная команда cpse, которая выполняет пропуск следующей команды при равенстве двух РОН. Она действует для всех РОН, потому может быть удобной в циклах по достижению оп- ределенной величины, когда команду сравнения с константой cpi применить нельзя из-за того, что она не работает для первых 16 регистров. С помощью аналогичных команд sbis/sbic, действующих для РВВ, несколько ис- кусственный алгоритм мигания по условию, приведенный ранее, можно переписать более компактно, отказавшись от регистра Flag вовсе и поставив конкретное усло- вие мигания при удержании нажатой кнопки на выводе PD2 (листинг 7.7). Подклю- чение этой кнопки показано на рис. 6.1 в главе 6. sbic PinD,2 ;если кнопка нажата, мигаем rjmp dark ;иначе гасим sbis PortD,7 ;если PD7=1 - красный, будем гореть зеленым rjmp set4 ;иначе красным cbi PortD,7 ;гасим красный sbi PortB,0 ;горим зеленым rjmp continue ;все готово set4: ;если PD7=0, будем гореть красным cbi PortB,0 ;гасим зеленый sbi PortD,7 ;горим красным rjmp continue;все готово dark: cbi PortB,0 ;гасим оба cbi PortD,7 continue: Обратите внимание на то, что проверка состояния выводов здесь проводится по- разному: в команде sbic PinD, 2 мы проверяем уровень, который установлен извне на выводе, работающем на вход. А в команде sbis PortD, 7 — уровень, который мы до этого установили в порту сами, причем неважно, на выход или на вход установ- лен вывод. Эту разницу в применении регистров Pinx и Portx в командах проверки- пропуска sbis/sbic не следует забывать, иначе можно долго ковыряться, пытаясь понять, почему программа не работает.
174 Часть II. Программирование микроконтроллеров AVR на ассемблере Этот алгоритм, как и предыдущий, оформлен в виде законченной программы (файл migalka_var2.asm), которую вы можете найти в архиве по адресу, указанному во вве- дении. Там же помещен и третий вариант программы мигания (файл migalka_ var3.asm)— как некий итог к теме команд передачи управления и проверки- пропуска. Этот вариант реализует переключение по нажатию кнопки: первое нажа- тие включает мигание, второе — выключает. Для запоминания текущего состояния (мигаем или нет) использован бит т регистра sreg, специально предназначенный для хранения битов и отличающийся тем, что он не может быть изменен никакими командами, кроме специально для этого предназначенных (а значит, не может быть случайно испорчен). Для работы с этим битом мы используем команды его сбро- са/установки (cit/set), а также команды ветвления программы по его состоянию (brtc/ brts). Команды логических операций Команды логических операций составляют важную часть функциональности любо- го компьютера. Значительная доля функций процессора осуществляется именно через логические операции с регистрами (так же, как и через операции с битами, о которых далее). Краткий ликбез по простейшим логическим операциям примени- тельно к электронике вы найдете в приложении 1. Логические операции применимы только к РОН. В этой группе представлены все стандартные логические операции: побитовое and (И), or (ИЛИ) и еог (Исключаю- щее ИЛИ), а также перевод в обратный код com и в дополнительный neg (обратите внимание, что операции инверсии битов соответствует команда com, а не neg). С помощью этих операций можно образовать любые другие логические функции, если это требуется. Отметим, что две команды, работающие с константами: andi и ori, применимы лишь к старшим РОН, начиная с г 16. Подробности Подчеркнем, что все эти операции работают именно с отдельными битами: операции, возвращающие логическое значение (как, к примеру, функция Логическое ИЛИ, обо- значаемая в языке С как « 11 », которая возвращает единицу, если хотя бы одно вы- ражение не равно нулю), в ассемблере не представлены, что, конечно, не мешает вам при необходимости их организовать самостоятельно. Поэтому в дальнейшем под, к примеру, операцией ИЛИ всегда будет подразумеваться Побитовое ИЛИ, а не Логи- ческое ИЛИ в строгом смысле этого слова (подробнее об этом рассказано в приложе- нии 1). Составление программ в терминах комбинационной логики для МК нехарактерно, наиболее часто команды логических операций выполняют маскирование отдельных битов или их групп: так, операция andi temp, Obooooim позволит оставить млад- шую тетраду переменной temp без изменений, а старшую — обнулить. Наоборот, команда ori temp,obooooiiii позволит оставить старшую тетраду без изменений, а в младшей все биты установить в единичное состояние. Установка или сброс отдельных битов вообще-то делается с помощью команд сле- дующей группы, но можно делать это и посредством логических операций по маске (о битовых масках рассказано в следующем разделе):
Глава 7. Система команд AVR 175_ □ ori г1б,оьооооюю — установит в регистре ri6 биты i и з в единичное состоя- ние, не трогая остальных; □ andi ri6,Obmioioi— сбросит в регистре ri6 биты 1 и з в нулевое состояние, не трогая остальных. Операцию еог (Исключающее ИЛИ) можно назвать «элементом несовпадения» — она позволяет зафиксировать те биты, которые совпадают и не совпадают в обоих операндах (совпадающие установятся в нули, а не совпадающие— в единицы). К сожалению, аналогичной операции с числом (eori) не существует, и мы в пред- шествующей главе придумывали ей замену в виде макроса (см. листинг 6.8). Если применить такой макрос совместно с маской, где на нужных местах стоят единицы, то можно инвертировать биты выборочно (а не полностью во всем байте, как это делает команда com). Так, команда: eori ri6,Obiiiioooo инвертирует биты старшей тетрады регистра г 16. Операция еог пригодна и для элементарного шифрования данных: примененная дважды к одному операнду (второй операнд, называемый ключом, при этом остает- ся без изменений) она возвращает его в исходное состояние. Таким образом, зало- жив неизвестный посторонним ключ в программу контроллера, можно зашифро- вать данные, а в удаленном компьютере их тем же ключом расшифровать. В главе 9 мы рассмотрим способ генерации случайных чисел в МК, который в том числе годится и для формирования «правильных» ключей для подобных целей. Только не надейтесь, что такой простейший способ убережет вас от серьезных шпионов: на- стоящая криптография сложна, предполагает многоэтапную защиту данных и клю- чи длиной порядка сотен и тысяч битов (а главное, соответствующую арифметику, которая умеет обращаться с числами такой длины). Кроме рассмотренных здесь стандартных логических операций, к этой группе команд часто относят также команды cir (очистить все биты), ser (установить все биты), упоминавшуюся ранее команду tst (проверка на отрицательное или нулевое значение) и очень полезную в двоично-десятичных операциях команду swap, кото- рая меняет местами тетрады одного байта. Команды сдвига и операции с битами Это также одна из важнейших групп команд. Сначала рассмотрим подробно, в силу их важности и относительной сложности, команды установки отдельных битов. Причем команды, устанавливающие значения битов в регистрах общего назначения (sbr и cbr), иногда относят к группе арифметических операций, а команды, уста- навливающие биты в регистрах ввода/вывода (sbi и cbi)— к рассматриваемой группе битовых операций. Впрочем, в некоторых пособиях их причисляют к одной группе, — операций с битами, что, конечно, логичней. Но почему такой разнобой, если они по сути делают одно и то же? Механизм работы этих команд существенно различается. Очевиднее всего устрое- ны sbi и cbi: так, уже знакомая нам команда sbi PortB, 7 установит в единичное состояние разряд номер 7 порта В (не трогая остальных). Если этот разряд порта
176 Часть II. Программирование микроконтроллеров AVR на ассемблере сконфигурирован на выход, то единица появится непосредственно на соответст- вующем выводе микросхемы, если на вход, то эта операция управляет подключе- нием «подтягивающего» резистора. Надо только не забывать, что непосредственная установка битов с помощью этих команд возможна лишь для первых 32 регистров из всего массива РВВ с адресами не старше $if (адреса проще всего посмотреть в соответствующем inc-файле либо в описании, где есть раздел под названием Register Summary, — подробнее об этом рассказано далее, в разд. «Команды пере- сылки данных»). Для остальных требуется несколько иная операция, которая также будет рассмотрена чуть позже. Гораздо сложнее действуют команды установки битов в регистрах общего назначе- ния— взгляните на листинг 7.6, где имеется команда sbr Flag, оьоооюооо, уста- навливающая бит номер 4 в единицу. Почему так сложно, да еще и в двоичной сис- теме? Дело в том, что эти команды просто осуществляют логическую операцию между значением регистра и заданным числом размером в байт, который в этом случае называют битовой маской. По сути это те же логические операции, что и в предыдущем разделе, только названные иначе: так, указанная команда расшифро- вывается как Flag or оьоооюооо. Понятно, почему предпочтительно двоичное представление маски — так легче отсчитывать биты. Аналогично работает команда сброса бита cbr Flag,оьоооюооо. Только для того чтобы ее можно было записывать в точности в том же виде, что и sbr, логическая операция, которую она осуществляет, сложнее: Flag and (not оьоооюооо). Здесь сначала в маске инвертируются все биты, а затем полученное число и значение регистра комбинируются операцией логического умножения (а не сложения, как в предыдущем случае). В результате четвертый бит обнулится обязательно, а все остальные останутся в неприкосновенности. Подробности Причем, если предыдущую операцию sbr можно просто заменить на ori (у них даже совпадают коды операций, т. е. это синонимы одной и той же команды), то cbr заме- нить напрямую на andi не получается, тогда придется записать ее следующим обра- зом: andi Flag,-(оьоооюооо) — где «~» есть заимствованный из С символ Поразряд- ное НЕ. Но сами команды cbr и andi также являются синонимами (у них тоже совпа- дают коды операций), откуда следует вывод, что операцию инверсии маски осуществляет компилятор заранее, когда встречает команду cbr вместо andi. Не следует забывать об этой разнице: в sbi/cbi задается номер бита, a sbr/cbr — битовая маска. В программе нажатия кнопки по таймеру (см. листинг 6.17 в гла- ве 6) вы встречали команду sbr Flag, l, которая для наглядности может быть пере- писана так: sbr Flag, Oboooooooi. Здесь сразу видно, что эта команда устанавливает бит номер о. В то же время имеющаяся там же команда sbrs Flag, о проверяет этот бит именно по номеру. Я и сам до сих пор попадаюсь на том, что в случае необхо- димости обнуления бита 1 в рабочем регистре temp записываю cbr temp, l (анало- гично верной команде cbi PortB, l), хотя такая операция обнулит в temp не первый бит, а нулевой. А операция cbr R, о (как и sbr r, о) вообще ничего не делает, и такая запись бессмысленна.
Глава 7. Система команд AVR 177_ Разумеется, этими командами, как и логическими операциями, можно за один раз установить хоть все биты в регистре— в этом отличие команд sbr/cbr от sbi/cbi, которые устанавливают только по одному биту. Совместно с указанными команда- ми можно употреблять и выражения — так же, как с командой idi в примере из разд. «Числа и выражения» главы 6. Но, в отличие от idi, команды sbr/cbr не тро- гают остальные биты, потому их удобнее использовать для установки битов в тех РВВ, которые выходят за пределы первых 32 адресов. Для этого значение регистра извлекается командой in, модифицируется, а затем записывается обратно коман- дой out: in temp,MCUCR sbr temp, (1«SE) | (1«SM1) out MCUCR,temp ;разрешение "спящего" режима Power Down Кроме упомянутых, к группе операций с битами также относят команды установки разрядов регистра sreg, которые обсуждались ранее. Более правильным будет так- же относить к группе битовых операций и команды сдвига (официальное руковод- ство [8,12] так и поступает, хотя [6,7] почему-то относит их к арифметическим опе- рациям). Самая простая такая операция — сдвиг всех разрядов регистра влево (lsi) или вправо (lsr) на одну позицию. Их приходится применять достаточно часто, по- тому что это равносильно умножению (соответственно, делению) на 2. Для того чтобы крайние разряды не терялись при сдвиге, используют разновидности этих операций «сдвиг через перенос»: roi и гог. Они учитывают флаг переноса с, и через него можно перенести в другой регистр значения крайних разрядов. На- пример, в результате выполнения последовательности команд: lsl rl rol r2 регистр rl будет умножен на 2, а старший его разряд (неважно, ноль он или едини- ца) окажется в младшем разряде г2, также умноженном на 2, — т. е. удвоение 16-битового числа r2:ri будет выполнено полностью корректным способом. Если последовательно применять команду rol к одному регистру, то мы получим т. н. циклический (кольцевой) сдвиг, когда биты некоего девятибитового (с учетом бита переноса) числа будут двигаться по кругу. Кроме упомянутых, есть еще команда «арифметического» сдвига as г, которая осу- ществляет деление на два, но не трогает старшего (седьмого) бита, — она применя- ется в случаях, когда этот бит несет знак числа. Интересно также, что команда сло- жения регистра с самим собой (add x,x) не только осуществляет ту же самую опе- рацию, что и команда lsi x, но даже совпадает с ней по коду операции, — еще один пример типичной ситуации для AVR, где более трети команд представляют собой синонимы. Команды арифметических операций Арифметические операции в 8-разрядном контроллере — развлечение для настоя- щих любителей трудностей. Это реально один из самых убедительных доводов за переход на языки высокого уровня, где компилятор все делает за вас. Но далее мы
178 Часть II. Программирование микроконтроллеров AVR на ассемблере увидим, что ассемблер позволяет делать все то же самое, только гораздо быстрее и экономичнее по отношению к затраченным ресурсам. Контроллеры, тяготеющие к CISC-архитектуре (вроде семейства *51), изначально предоставляют программисту встроенные команды всех операций вплоть до аппа- ратного деления, но на самом деле это мало помогает: кому требуется делить и перемножать числа, если и операнды и результаты ограничены скудным диапазо- ном в пределах одного байта, а с учетом знака — в пределах всего 7 двоичных раз- рядов? Требуется расширить диапазон хотя бы до 16 разрядов (хотя для операций с «плавающей точкой» и этого недостаточно). Подробнее о многоразрядной арифметике в AVR мы поговорим в главе S, а здесь остановимся лишь на стандартных командах и способах их применения. Арифме- тические операции для AVR на первый взгляд могут показаться пользователю, привыкшему к бытовому представлению об арифметике, реализованными несколь- ко странно, но на деле получается весьма стройная система. Не вызывают никаких возражений только очевидные операции: add ri,r2 (сложить два регистра, записать результат в первый) и sub ri,r2 (вычесть второй из первого, записать результат в первый). Но если вдуматься, то и тут вопросов возникает множество: а что будет, если сумма превышает 255? Или разность меньше нуля? Куда девать остатки? Оказывается, все продумано — для учета переноса есть спе- циальные команды: adc и sbc соответственно. Корректная операция сложения двух 16-разрядных чисел rhi : rli и rh2 : RL2 будет занимать две команды: add RL1,RL2 adc RH1,RH2 Здесь переменные rli и rl2 содержат младшие байты слагаемых, a rhi и RH2 — старшие. Если при первой операции результат превысит 255, то перенос запишется во все тот же флаг переноса с и учтется при второй операции. Общий результат окажется в паре rhi : rli. Совершенно аналогично выглядит операция вычитания. Постойте, но мы же вовсе не хотели складывать 16-разрядные числа! Мы хотели всего лишь сделать так, чтобы в результате сложения 8-разрядных чисел получился правильный результат, пусть он займет два байта. На что нам тогда старший байт второго слагаемого, если его вообще в природе не существует? Конечно, можно сделать его фиктивным, загрузив в некий регистр нулевое значение, но это только кажется, что регистров у AVR много — аж 32 штуки, на самом деле они очень бы- стро расходуются под разные надобности. И занимать целый регистр под фиктив- ную операцию, пусть даже на один раз — как-то некрасиво. Потому «экономная» операция сложения 8-разрядных чисел будет выглядеть так: add RL1,R2 brcc add_8 inc RHI add_8: Исходные слагаемые находятся в rli и R2, а результат будет, как и ранее, в rhi:rli. Отметим, что в старшем байте (rhi) в результате может оказаться только
Глава 7. Система команд AVR . 179_ либо о, либо 1 — сумма двух восьмиразрядных чисел не может превысить число 510 (255 + 255), именно потому флаг переноса с представляет собой один- единственный бит в регистре флагов. Команда brcc (BRanch if Carry Cleared) пред- ставляет собой операцию перехода по условию, что флаг переноса равен нулю. Таким образом, если флаг не равен нулю, выполнится операция inc rhi — увели- чение значения регистра rhi на единицу, в противном случае это действие будет пропущено. Можно придумать и другие способы программирования этой опера- ции. Внимательный читатель, несомненно, уже заметил неточность в программе — а чему равно значение rhi до выполнения нашей процедуры? Вдруг оно совсем не ноль, и тогда нельзя говорить о корректном результате. Поэтому правильней было бы либо дополнить нашу процедуру еще одним оператором, расположив его рань- ше всех остальных: cir rhi, либо вместо команды inc применить какой-нибудь из способов загрузки значения i в переменную rhi (например, idi rhi, i). Заметим, что во всех случаях процедура разрастается по времени— было две команды, стало четыре, причем тут имеется замедление еще и неявного порядка: если все представленные арифметические команды выполняются за один такт, то команда ветвления brcc может выполниться за такт (если с = 1), а может и за два (если с = о). Итого мы выиграли один регистр, а потеряли два или три такта. И так всегда— как мы уже говорили, есть процедуры, оптимизированные по времени, есть — по числу команд, а могут быть — и по числу занимаемых регистров. Идеа- ла, как и везде в жизни, тут не добиться, приходится идти на компромисс. Если вы посмотрите на сводную таблицу команд в руководствах, то можете обра- тить внимание, что из восьмибитовых арифметических операций с константой дос- тупно только вычитание (subi, sbci), напрашивающейся же операции сложения с константой нет. Ограничение легко обходится вычитанием отрицательного числа: команда subi temp,-10 прибавит 10 К temp, а пара команд: subi temp, Low (-500) ; sbci tempi, High (-500) — добавит к двухбайтовому числу tempi: temp двухбайтовую константу 500. Кроме того, разработчики семейства AVR решили облегчить жизнь пользователям, добавив для двухбайтовых операций с константами в перечень арифметических команд две очень удобные команды: adiw и sbiw. Единственный их недостаток — работают они только со старшими парами регистров, начиная с г24-г25. Три последние пары (г2б-г27, г28-г29 и r30-r3i) носят еще название х, y и z, и мы их будем «проходить» далее в этой главе — они задействованы в операциях обмена данными с памятью, так что операции adiw/sbiw явно нацелены на манипуляции с адресами в памяти. Но в рассматриваемом случае точно так же работает и пара г24-г25, которая более нигде вместе не употребляется, и это бывает очень удобно. Независимо от используемой пары, старшим регистром считается тот, что с боль- шим номером, а операцию нужно проводить с младшим, при этом перенос учтется автоматически. Например, в результате выполнения последовательности команд: clr r25 Idi r24,100 adiw r24,200
180 Часть II. Программирование микроконтроллеров AVR на ассемблере в регистрах г25:г24 окажется число зоо (в г25 будет записано $01, что эквивалент- но 256 для 16-разрядного числа, а в г24 - $2С = 44, что в сумме и даст 300). Анало- гично работает и процедура вычитания константы sbiw. Контроллеры семейства Mega снабжены операцией умножения, реализованной не- сколько неочевидным образом. Так как при умножении результат гораздо чаще оказывается двухбайтовым, чем при сложении, то он помещается в специально для этого выделенные первые два регистра общего назначения ri: го: mul Xdat,Ydat; Xdat - множимое, Ydat - множитель, /двухбайтовый результат в rl:r0 Подробнее об умножении и делении многоразрядных чисел мы, как уже обещали, поговорим в главе 8. Команды пересылки данных Здесь мы рассмотрим команды, которые переносят данные из одной области памя- ти в другую (память рассматривается тут в широком смысле этого слова, и в нее включаются также и регистры). Некоторые команды из этой группы нам уже зна- комы — это ldi и mov. Первая загружает в регистр непосредственно число- константу (но действует только для регистров, начиная с г 16), а вторая — значение другого регистра. Часто забывают, что, кроме простой mov, есть еще mow, которая за один такт позволяет выполнить пересылку сразу двухбайтового слова. При этом в ней указываются только младшие байты, которые должны находиться в регистре с четным номером (в следующем по порядку нечетном будет находиться старший байт): movw rl6,rO /скопирует пару rl:r0 в г17:г1б Повторим, что ldi очень часто записывают с использованием выражений, что мож- но пояснить следующим практическим примером: ldi temp, (1«RXEN| 1«TXEN) Напомним, что при указании функции Побитовое ИЛИ скобки ставить необяза- тельно (ее приоритет выше, чем у операции сдвига). Смысл приведенного выраже- ния в том, что мы устанавливаем в единицы в регистре temp только биты с указан- ными именами (естественно, последние должны быть где-то определены — будем считать, что это сделано в INC-файлах). В рассмотренном примере мы хотим ини- циализировать последовательный порт UART, и приведенная форма записи означа- ет, что устанавливается разрешение приема (rxen) и передачи (txen). Обратите внимание, что, в отличие от команды sbi, неуказанные биты будут в этой операции обнулены, а не останутся при своих значениях. Такая форма очень удобна для задания поименованных битов в процессе инициализации служебных регист- ров. Вместо имен могут быть указаны непосредственно номера битов, но записы- вать их в такой форме нецелесообразно, — здесь как раз удобнее указывать имена.
Глава 7. Система команд AVR 181_ Непосредственная установка через константу заставляет рыться в справочнике или в inc-файлах в поиске номеров поименованных битов: ldi temp,0b00011000 ;TXEN=1,RXEN=1 А при использовании «законной» команды sbi пришлось бы делать то же самое, но еще и предварительно обнулять регистр. Понятно, что установить биты в рабочем регистре temp для инициализации UART недостаточно. Потому следующей по тексту программы командой мы обязаны пе- ренести установленное значение в соответствующий РВВ, называющийся в таком случае ucsrb (cm. главу 15). Это делается традиционной для всех ассемблеров командой out: out UCSRB,temp В паре к out идет симметричная команда in— чтение данных из РВВ. Эти две команды также относят к командам переноса данных. В отличие от команд sbi/cbi, команды in/out действуют на все 64 «обычных» регистра — с номерами до $3F (ад- ресами до $5F, — см. рис. 2.2 в главе 2). Что происходит в контроллерах, имеющих «дополнительные» регистры (Extended I/O) сверх этого адресного пространства, мы рассмотрим далее. Очень важный момент касается чтения и записи регистров ввода/вывода, которые составляют пару, образующую двухбайтное число. Если они меняются в реальном времени, то во время чтения или записи таких регистров одного за другим возмож- ны коллизии. Например, если вы случайно «словите» в момент чтения прибавление 1 к числу $ooff (следующее значение $оюо), то, прочитав сначала старший, потом младший, получите $оооо, а если наоборот— получите $oiff. Потому в AVR для критичных к этому пар регистров имеется специальный механизм буферизации, который работает по следующему правилу: При чтении необходимо сначала прочесть младший байт ь, потом старший н, а при записи — сначала записать старший байт н, потом младший l. К счастью, таких регистров немного: это прежде всего содержимое счетчиков 16-разрядных таймеров tcntxH:tcntxl, регистры захвата этих таймеров icrxHiicrxl и сравнения ocrxxH:OCRxxl, а также регистры данных АЦП adchiadcl. Иными сло- вами, это те 16-битные регистры, изменение которых в реальном времени способно повлиять на результат операции их чтения или нарушить функционирование при их неверной записи. Обратим внимание, что в языке С для AVR (в том числе и в Arduino) эта проблема решена за вас: там регистры tcntil и tcntih обрабатываются как единый двухбай- товый tcnti. То же касается и других упомянутых здесь 16-разрядных регистров. Следующими по важности командами пересылки данных будут команды загрузки и чтения SRAM: id/ids и st/sts. С этими командами связаны такие «жуткие» по- нятия, как «прямая» и «косвенная» адресации, а также «относительная косвенная адресация» и т. п. (по моему убеждению, этот раздел в описаниях МК AVR спокой- но можно при чтении пропускать). Мы постараемся термин «адресация» вообще не
182 Часть II. Программирование микроконтроллеров AVR на ассемблере употреблять, и рассмотрим здесь три основных режима чтения/записи SRAM: про- стой, а также с преддекрементом и с постинкрементом. Все три встречаются очень часто, хотя два последних режима работают не во всех типах AVR. Команды для того чтобы записать/считать данные напрямую в память/из памяти носят название: sts и ids. Команда sts $oiFF,temp запишет в ячейку по адресу $oiff содержимое temp, а команда ids temp,$oiFF считает ее обратно. Именно с по- мощью этих команд удобно манипулировать содержимым РОН и РВВ, напрямую их записывая, если это зачем-то надо. Не забывайте только, что при доступе с по- мощью команд id/ids и st/sts указывать надо не номера регистров, а абсолютные адреса в памяти. Соответственно, для обычных регистров к мнемонической кон- станте, обозначающей имя, нужно добавить $20 (32). Например, содержимое 8-разрядного счетчика TimerO можно перенести в регистр temp так: in temp,TCNT0 или так: Ids temp,(TCNT0+$20) или так: Ids temp, ($52) ;$52 - абсолютный адрес TCNT0 в ATmega8 Об обращении к Extended I/O (Memory Mapped) Registers в более современных мо- делях AVR, которыми мы тут не пользуемся, рассказано во врезке «Подробности» далее. Команды sts/ids занимают два такта (и имеют размер 4 байта), и при большем, чем два раза подряд считывании/записи, выгодней и удобнее пользоваться командами id и st. При чтении и записи SRAM с помощью этих команд потребуются регистры х, y или z— т. е. пары г27:г2б, г29:г28 и r3i:r30 соответственно, которые по от- дельности еще именуют xh:xl, yh:yl, zh:zl— в том же порядке (т.е. старшим в каждой паре служит регистр с большим номером). Если обмен данными произво- дится между памятью и другим регистром общего назначения, то достаточно одной из этих пар (любой), если же между областями памяти — целесообразно задейство- вать две. Независимо от того, какую из пар мы используем, чтение и запись проис- ходят по идентичным схемам, меняются только имена регистров. Покажем основной порядок действий при чтении из памяти с использованием регистра z (гзигзо). Чтение одной ячейки с заданным адресом Address, коррекция ее значения и последующая запись выполняются так, как показано в листинге 7.8. ldi ZH,High(Address) ;старший байт адреса RAM ldi ZL,Low(Address) /младший байт адреса RAM Id temp,Z /читаем ячейку в temp inc temp /например, увеличиваем значение на 1 st Z,temp ;и снова записываем
Глава 7. Система команд AVR 183_ При всех подобных манипуляциях с абсолютными адресами с помощью команд id/ids и st/sts нужно внимательно следить за двумя моментами: во-первых, за тем, чтобы не залезть в уже занятую область памяти, во-вторых, чтобы не испор- тить стек. Как мы уже говорили (см. разд. «Память данных» главы 2), младшие ад- реса SRAM заняты регистрами, и запись, например, по адресу $оооо (zh = о, zl = о) равносильна записи в регистр г о. Во всех относительно старых моделях, имеющих 64 РВВ, регистровый файл заканчивается адресом в памяти $5F. Сюда относятся «наши» ATmega8, ATmega8535, ATtiny2313, а также популярный ATmegal6 и дру- гие. Поэтому начальный адрес, с которого начинается пользовательская SRAM, и к которому вы можете обращаться без опаски, равен $60 (десятичное 96, констан- та sramstart). Если объем SRAM составляет 1024 байта, как в ATmega8, то конеч- ный адрес будет $045F (1119). Тогда zh в нашем примере может иметь значения только 0, 1, 2, 3 или 4. Самое правильное, конечно, пользоваться константой sramstart, которая во всех моделях указывает на начало пользовательской области SRAM (так же, как на ее конец указывает ramend). В этом случае вместо заданной нами константы Address в листинге 7.8 просто подставляется заданная свыше sramstart. Причем, если ко- личество данных невелико (меньше, чем $ff -$бо = 159 байт), можно один раз в начале программы задать zh = High(SRAMSTART) (или, что в нашем случае без раз- ницы, просто равным нулю), а потом задавать только zl = Low (sramstart) . Но это не убережет вас от того, чтобы залезть в самый конец SRAM, где размеща- ется стек, — компилятор его размер предсказать не может, потому никаких указа- ний на ошибку не последует. Срыв стека — это типичная ошибка времени выпол- нения. Внимание! Обязательно нужно помнить, что если в программе имеются вызовы процедур и пре- рываний, то последние адреса SRAM использовать нельзя, — мы сами даем в начале программы команду выделить их под стек. Если мы не слишком хулиганим со всякими push/pop и ограничиваемся простыми вызовами подпрограмм с небольшой степенью вложенности (типа вызова какой- нибудь процедуры из обработчика прерывания, не более), то для стека с запасом достаточно пространства в десяток байтов. Иначе нужно внимательно смотреть, что и куда вы помещаете (еще раз напомним первую компьютерную аксиому: «За- кладывая что-то в память компьютера, помните, куда вы это положили»). Подведем итог, чтобы не запутаться. Самые младшие 32 адреса памяти (от от $оо до $if) занимает регистровый файл РОН. Младшие 32 РВВ, с номерами от $оо до $if (занимающие адреса памяти от $20 до $3f), позволяют напрямую устанавливать значения битов командами sbi/cbi. К следующим 32-м РВВ с номерами до $3f (адресами до $5f) применимы команды переноса in/out (они применимы ко всем 64-м РВВ, но практически используются преимущественно для старших 32-х). По- этому в старших РВВ значения битов устанавливают через перенос в обычный РОН и загрузку обратно. К первой (младшей) группе всегда относятся, например, реги- стры портов Portx и ddrx (а также и битовый массив Pinx), потому с ними проще
184 Часть II. Программирование микроконтроллеров AVR на ассемблере всего. Ко второй группе обычно принадлежат всяческие регистры установки режи- мов энергосбережения, таймеров, портов UART и т. д. Чтобы не тратить времени на разборки, вы можете для всех портов, в которых сомневаетесь, использовать пере- нос через in/out — они выполняются за один такт, потому намного это программу не задержит. Все здесь сказанное по поводу разного доступа к разным областям регистрового файла РВВ сводится к коротким примечаниям к таблице Register Summary, которые имеются в описании каждого контроллера. Подробности Так и видится, как два студента из Тронхейма, Альф Боген и Вегард Волен, разраба- тывая структуру AVR, искренне собирались облегчить жизнь пользователям, придумав весьма стройную структуру, в которой было 32 регистра общего назначения, и 64 ре- гистра ввода/вывода. Как им казалось, с большим запасом, потому что в первом AT90S1200 было всего 18 РВВ. И 64-х адресов действительно хватало в течение целого десятилетия, пока не уперлись в то, что регистры для все новых функций, без которых жить уже становилось неприлично, размещать некуда. Модернизировать ядро, слава богу, не стали: без беспрецедентно быстрого и простого ядра, которое является неубиваемым козырем архитектуры AVR, она скоро скатилась бы на периферию от- расли. Потому пришлось городить надстройки в памяти данных, захватывая регистра- ми все новые области. Уже в АТтеда8/16 64-байтовое адресное пространство РВВ оказалось забито почти полностью, а в модернизированных АТтеда88/168/328 оно захватывает область в 224 байта — от $20 до $ff (пользовательская SRAM, соответственно, начинается с адреса $юо). Регистрам, попавшим в область выше абсолютного адреса $5f, номера не присваивают, они характеризуются только адресами в памяти. Соответственно, на эти Extended I/O Registers не действуют уже и инструкции in/out — доступ к ним про- изводится, как к ячейкам памяти на общих основаниях, т. е. командами ld/ids и st/sts. Причем для Extended I/O Registers их названия в этих командах нужно указы- вать напрямую, без всяких +$20. В ATmega88 и его родственниках общее количество регистров еще относительно не- велико, всего 86, потому они разместились по всей этой памяти достаточно вольготно. В старших моделях вроде ATmega2560 регистров ввода/вывода еще больше — 183 штуки, и там они отхватывают еще больший кусок памяти (до адреса $iff). Пото- му при использовании этих моделей всегда проверяйте адрес начала пользователь- ской SRAM, а еще лучше используйте константу sramstart. Мы к таким моделям контроллеров здесь не обращаемся, но если вам придется их программировать на ассемблере, сразу глядите в /пс-файл— там Extended I/O Registers компактно помещены в начале перечня по убыванию адресов и помечены примечанием MEMORY MAPPED. Теперь, наконец, вернемся к обмену с памятью SRAM. Режимы с преддекрементом и постинкрементом используются, когда нужно прочесть/записать целый фрагмент из памяти (эти команды недействительны для ряда младших моделей МК семейства Tiny, но для упоминаемой в этой книге модели ATtiny2313 они работают). Схема действий аналогичная приведенной ранее, только команды выглядят так: st -Z,temp /*c преддекрементом, запись в ячейку с адресом Z-1, после выполнения команды регистр Z = Z-1*/ st Z+,temp /*c постинкрементом, запись в ячейку с адресом Z, после выполнения команды регистр Z = Z+1*/
Глава 7. Система команд AVR 185_ Аналогично выглядят команды чтения: Id temp,-Z /*с преддекрементом, чтение из ячейки с адресом Z-1, после выполнения команды регистр Z = Z-1*/ Id temp,Z+ /*с постинкрементом, чтение из ячейки с адресом Z, после выполнения команды регистр Z = Z+1*/ Листинг 7.9 иллюстрирует, как можно в цикле записать 16 ячеек памяти подряд одним и тем же значением из temp, начиная с нулевого адреса фрагмента памяти с адресами $ioo-$iff(zh = i). ldi ZH,1 clr ZL LoopW: st Z+,temp /сложили в память cpi ZL,16 ;счетчик до 16 brne LoopW To же самое можно делать с помощью регистров х и y (соответствующий пример приведен в главе 13 в программе вывода на дисплей с 12С-интерфейсом). Еще одна важная команда переноса данных — инструкция lpm, которая позволяет прочесть произвольный байт из памяти программ. Напомню, что большинство раз- новидностей МК, в том числе и AVR, имеют гарвардскую архитектуру, когда па- мять программ отделена от памяти данных, и в первую контроллер самостоятельно ничего писать не может (кроме случая самопрограммирования). Потому хранение в памяти программ тех констант, которые никогда не будут изменяться, прямо ре- комендуется разработчиками AVR, за много лет так и не сумевшими окончательно решить проблему безопасного хранения данных в EEPROM (см. главу 10). Вот типичная задача такого рода: пусть контроллер осуществляет управление се- мисегментным индикатором в динамическом режиме, когда в каждом такте прихо- дится выводить разные цифры. Выстраивать рисунки (битовые маски) этих цифр каждый раз — замучаешься, и программа получится очень громоздкая и совершен- но нечитаемая. Проще их «нарисовать» единожды и расположить по порядку (от нуля до 9) в любом месте программы (удобно— в начале сразу после векторов прерываний). Дать понять компилятору, что это особая область памяти, которая его не касается и должна быть перенесена без изменений, можно с помощью директи- вы . db, а чтобы потом можно было найти эту область, ее следует пометить обычной меткой: Njnask: ; маски цифр на семисегментном индикаторе .db ObOOllllll,ObOOOOOllO,ObOlOllOll,ObOlOOllll,ObOllOOllO,ObOllOllOl, ObOlllllOl,ObOOOOOlll,ObOlllllll,ObOllOllll Метка может располагаться и до, и после директивы . db. Для лучшего понимания, как размещаются байты данных в памяти программ, следует учесть, что они, как и команды, объединяются здесь в двухбайтовые слова. Если число байтов, указанных
186 Часть II. Программирование микроконтроллеров AVR на ассемблере в приведенной директиве .db, нечетное, то последнее выражение будет записано как слово со старшим байтом — последним в ряду, — равным нулю, даже если далее идет еще одна директива . db. Исходя из этого, можно в нужном месте использовать команду lpm следующим хитрым образом (листинг 7.10). ldi temp,5 ;цифра 5 ldi ZH,High(N_mask*2) /загружаем адрес начала маски ldi ZL,Low(N_mask*2) add ZL,temp /адрес маски цифры 5 lpm ;маска окажется в регистре гО ;или так, если цифра - константа: ldi ZH,High(N_mask*2) ;загружаем адрес начала маски ldi ZL,Low(N_mask*2) subi ZL,-5 ;адрес маски цифры 5 lpm ;маска окажется в регистре гО Здесь в регистр z (и только в z!) заносится адрес начала массива констант, причем учитывается, что память программ имеет двухбайтовую организацию, а в z нужно заносить побайтные адреса, отчего появляется множитель 2. По этому адресу, как мы договорились, располагается маска цифры о. Для загрузки цифры 5 прибавляем к адресу это значение и вызываем команду lpm. Результат окажется в самом первом регистре общего назначения го, так что при таких действиях его нежелательно за- нимать под какие-бы то ни было переменные. Команду lpm «понимают» абсолютно все AVR-контроллеры (даже забытого семейства Classic), но МК семейства Mega и многие Tiny (включая ATtiny2313) поддерживают и более понятные форматы команды lpm, аналогичные обычной id (с любым регистром, необязательно гО, но адресацией также через регистр z). Примеры этого подхода вы найдете в главе 9 (в программе вывода синуса с помощью ШИМ), а также в главах 13 и 16. Прежде чем перейти к следующей группе команд, следует пару слов сказать об ин- струкциях работы со стеком push и pop. В других архитектурах (вроде интеловских х51 и х86), где регистров общего назначения мало, это едва ли не самые употреби- тельные команды, т. к. они позволяют не лазать каждый раз в память, что долго, а быстро сохранять промежуточные результаты в стеке и извлекать их оттуда. А здесь мы постараемся ими пользоваться только при необходимости, поскольку использование стека для своих сиюминутных надобностей сильно усложняет логи- ку программы и служит главным источником трудноотлавливаемых ошибок вре- мени выполнения (отсылаю любителей всяких финтов опять же к [2]). Программа для AVR, где часто встречаются команды push и pop, — верный признак того, что ее автор учился на микроконтроллерах других семейств.
Глава 7. Система команд AVR 187 Команды эти могут, как мы говорили, потребоваться для сохранения sreg (см. лис- тинг 7.4). Далее в этой главе мы расскажем, как с помощью команд push/pop орга- низовать деление на локальные и глобальные переменные, если это потребуется (обычно по причине нехватки РОН). А здесь еще приведем удобный способ обмена содержимым между двумя РОН, который не требует промежуточного третьего ре- гистра (листинг 7.11). Пример подобного фокуса вы можете встретить в программе термометра на основе дисплея ТМ1637 в главе 16, где действительно наблюдается дефицит регистров. push rl6 push rl7 pop г1б pop r17 Команды управления системой Их всего три: пор (по operation, пустая команда), sleep— перевод МК в режим энергосбережения) и wdr — сброс сторожевого таймера. Последнюю команду мы рассмотрим подробнее в главе 14, а здесь кратко остановимся на двух остальных. Операция пор входит в набор команд всех ассемблеров и служит для заполнения ячеек памяти программ пустыми значениями, если это зачем-то требуется, — на- пример, чтобы выровнять адрес процедуры по определенному адресу в памяти или задержать выполнение на фиксированное количество тактов. Встретив эту команду (в AVR ее код — все нули двухбайтового слова), процессор выполнит единствен- ную операцию инкрементирования содержимого счетчика команд. При использовании команды sleep режим энергосбережения должен быть разре- шен предварительно и, если потребуется, установлен его тип. Разрешение осущест- вляется установкой бита se в регистре mcucr, как показано в разд. «Команды сдвига и операции с битами» этой главы. Если более никаких битов не устанавливать, то это означает наиболее щадящий режим Idle, другие режимы необходимо выбирать специально (подробнее об этом рассказано в главе 14). Отметим, что инструкция советует устанавливать бит разрешения непосредственно перед командой sleep. На самом деле это только механизм защиты от сбоев — в реальности просто следует избегать установки разрешения с самого начала (в процедуре инициализации), а делать это лишь по мере необходимости в процессе выполнения программы. Команда sleep сработает только, если она вызывается из основной программы, а не из прерывания. Если энергосбережение не разрешено, то команда sleep ничего не делает, и это позволяет «поиграть» возможностями. Например, при необходимости передачи последовательности байтов по относительно медленному UART, можно разрешить МК «заснуть», только когда будет передан последний байт. Тогда основной цикл может включать в себя единственную команду sleep, а разрешать «спящий» режим мы будем перед выходом из процедуры прерывания «передача завершена» (ТХ Complete) после передачи последнего байта.
188 Часть II. Программирование микроконтроллеров AVR на ассемблере Выполнение на ассемблере типовых процедур К типовым здесь мы отнесем процедуры, которые на языках высокого уровня (ЯВУ) записываются просто и понятно, а в ассемблере требуют специальных ухищрений. Пример такой процедуры, имитирующей сложный оператор выбора case, мы уже встречали в этой главе чуть раньше — в разд. «Команды проверки- пропуска». Из предыдущего материала также понятно, как реализовать простей- шую конструкцию типа if ... then с помощью команд условных переходов. По- дытожим пройденное, а также рассмотрим несколько других типовых конструкций (листинги 7.12-7.16). Листинг ? Л 2. Усповная конструкция типа tt ,. th-an ... else Pascal if temp < Const 1 then varl {процедура varl} else var2; {процедура var2} с «сметай том : С if (temp < Const_l) varl; //процедура varl else var2; //процедура var2 Ассемблер,зарионт' Ассемблер., if_then else: cpi temp,Const 1 brsh var2 varl: ;если < <код процедуры varl> ret ;выход из if_then_else var2: /если >= <код процедуры var2> ret ;выход из if then else if then else: cpi temp,Const_l brio varl var2: ;если >= <код процедуры var2> ret ;выход из if_then else varl: /если < <код процедуры varl> ret /выход из if then else По равенству переменной 1 шгт й Pmcai if temp=l then varl {процедура varl} (else ) if temp=0 then var2; {процедура var2} ; Ассемблер, вариант 1 ! (no значониш Chits 0-1) if then else: sbrs temp,0 rjmp var2 varl: ;если бит0=0 <код процедуры varl> ret /выход из if_then_else var2: ;если бит0=1 <код процедуры var2> ret ;выход из if then_else if (temp==l) varl; //процедура varl (else ) if (temp==0) var2; //процедура var2 Ассемблер, вариант 2 ; (no 2и&чеиу>ю бита О -™ 0) i if then else: sbrc temp,0 rjmp varl var2: ;если бит0=1 <код процедуры var2> ret ;выход из if then else varl: ;если бит0=0 <код процедуры varl> ret ;выход из if then else
Глава 7. Система команд AVR 189 В последних двух процедурах листинга 7.12 (по равенству 1 или о) соответствие ассемблера языкам высокого уровня, конечно, условное — в ЯВУ, как правило (за исключением специально адаптированных версий), отсутствуют средства для непо- средственной работы с битами. Разумеется, в Pascal или С можно выделить путем побитовых операций отдельный бит в байте и сравнивать потом этот байт с нулем или тем значением, которое получается, если бит на нужном месте равен единице (или вообще применить inline-ассемблер). Но здесь мы не стали загромождать таб- лицу-листинг — в приведенных примерах служебное слово else показано в скобках для наглядности, на самом деле оно здесь лишнее. Листинг 7.13. Простой оператор выбора смш: cpi temp,Const_l breq cont_l cpi temp,Const_2 breq cont_2 cpi temp,Const_3 breq cont_3 cpi temp,Const_4 breq cont_4 cont_l: rcall proc_l ;если temp = Const_l cont_2: rcall proc_2 ;если temp = Const_2 cont_3: rcall proc_3 /если temp = Const_3 cont_4: rcall proc_4 ;если temp = Const_4 В листинге 7.13 учитывается ограниченность действия команд Ъгхх в пределах 64-х команд: если процедуры ргосх, помеченные метками contx, занимают много места, то их выделяют в отдельные процедуры, а после меток ставят только их вызовы командой rcall. Если же процедура занимает всего пару команд, то без дополнительного структурирования можно, конечно, обойтись. count := 0; while countoConst 1 do begin inc (count); {что-то делаем} end; Вариант 1 clr count while jproc: inc count count = 0; while (count != Const 1) { count++; {что-то делаем} } Вариант 2 ldi count,Const_l while_proc: dec count
190 Часть II. Программирование микроконтроллеров AVR на ассемблере cpi count,Const 1 breq end while <что-то делаем, пока count < Const 1> rjmp while_proc /обратно end while: <что-то делаем, если цикл окончен> continue: /продолжаем программу breq end while <что-то делаем, пока не ноль> rjmp while_proc /обратно endjwhile': <что-то делаем, если цикл окончен> continue: /продолжаем программу Листинг 7.15, Цинп с постустяжеы гшпш гщ Pascal count := 0/ repeat {что-то делаем} inc (count) until count = Const_l/ Ассемблер, вариант 1 clr count Repeat jproc: <что-то делаем> inc count cpi count,Const_l brne Repeatjproc continue: /продолжаем программу >eat - . unt±i {Pascal! С ; count = 0/ while (count != Const 1) { {что-то делаем} count++; } Ассемблер, вариант 2 ldi count,Const 1 whilejproc: dec count breq end while <что-то делаем, пока не ноль> rjmp whilejproc /обратно end while: /если count=0 <что-то делаем, если цикл окончен> continue: /продолжаем программу FOR count = Nx TO 0 »что-то делаем NEXT ldi count,Nx for jproc: <что-то делаем> dec count brne forjproc FOR count = Nbeg TO Nend лчто-то делаем NEXT ldi count,Nbeg for jproc: inc count <что-то делаем> cpi count,Nend brne forjproc
Глава 7. Система команд AVR 191_ Отметим, что в Pascal также возможен цикл с произвольными границами, анало- гичный for ... next в Basic, даже две разновидности таких циклов: for ... to ... do ... и for ... downto ... do... А вот задание шага итераций for ... то ... step, как в Basic, в Pascal невозможно — такой цикл приходится организовывать с помощью while ... do. В С, конечно, все проще, потому что там цикл for допус- кает любые издевательства над параметрами. Но ассемблер тут тоже на высоте, т. к. с легкостью позволяет выстраивать произвольные циклические конструкции, ими- тируя любую из этих разновидностей. Здесь мы не приводим все возможные вари- анты только потому, что это заняло бы много места, но по приведенным образцам читатель, без сомнения, сам сумеет выстроить ту конструкцию, которая лучше всего подходит к задаче. Другие типовые процедуры, из которых наиболее важны различные арифметиче- ские действия, мы рассмотрим в последующих главах по ходу дела. Стоит обратить внимание на классическую книгу [14], в которой вы сможете найти реализации практически всех типовых математических алгоритмов с примерами (правда, ино- гда изложенных излишне «математично»). О стеке, локальных и глобальных переменных Как мы уже говорили, стек — одно из самых употребительных понятий в програм- мировании. Наличие программного стека позволяет, например, организовать при- вычное для языков высокого уровня разделение переменных на локальные и гло- бальные. Однако постарайтесь забыть о такой привычке, к которой вас, несомнен- но, приучила среда Arduino,— там это действительно оправданно по многим причинам, а здесь нас это деление будет лишь путать. Во всех последующих про- граммах в этой книге мы будем пользоваться только глобальными переменными и, если регистров общего назначения для них не хватает,— задействовать ячейки SRAM. (Привычку пользоваться преимущественно глобальными переменными ав- тор даже перенес в свой стиль программирования на Delphi, отчего неоднократно создавал себе там излишние сложности.) Однако в больших проектах без локальных переменных бывает просто не обойтись (например, когда одна и та же процедура вызывается в разных местах с исходными данными, хранящимися в различных регистрах). Наиболее просто организовывать передачу параметров в процедуру через стек, если ее оформить в виде макроса (листинг 7.17). .macro procedure pop var_loc2 /переменная var_loc2 со значением var_2 pop var_locl ;и переменная var_locl со значением var_l ;расчеты, расчеты... push var_locl /результат — в стек push var_loc2 ;результат — в стек .endm
192 Часть II. Программирование микроконтроллеров AVR на ассемблере ;в программе: push var_l ;переменная var_l в программе помещается в стек push var_2 /переменная var_2 в программе помещается в стек procedure; /вызывается макрос procedure pop var_2 ;после него результат извлекается из стека pop var_l /второй результат извлекается из стека В качестве переменных vari и var_2 допустимы любые регистры, при этом дейст- вия внутри процедуры всегда будут совершаться с заданными регистрами var_ioc. Обратите внимание, что когда таких переменных несколько, важно соблюдать пра- вильный порядок их помещения в стек и извлечения оттуда согласно принципу «первым вошел — последним вышел» (в программах на языках высокого уровня за порядком переменных в стеке следят специальные форматы вызова функций типа stdcall И ПОДОбные). Подробности Конечно, если вы хотите сэкономить регистры, то в коде процедуры еще должны быть команды промежуточного сохранения и затем восстановления содержимого регист- ров, соответствующих переменным var_ioc2 и var_ioc2, — иначе, понятно, никакой экономии не получится, наоборот. Сохранение/восстановление делается, конечно, не через стек, а через обычные ячейки памяти. Здесь эти команды опущены, чтобы со- средоточиться на логике использования стека. Но мы знаем, что макрос — все-таки не процедура и не функция, это просто такой способ структурирования текста программы с целью сокращения записи. Потому в приведенном примере мы, скорее, безосновательно раздули код на кучу команд pop и push, обеспечив себе только возможность временно использовать иные регист- ры (в результирующем коде после двух push перед текстом макроса всегда будут сразу идти два pop, аналогично и в конце макроса). В реальных программах хочется все-таки иметь настоящую процедуру-функцию, когда один и тот же код процеду- ры может вызываться несколько раз, и при этом действительно имеется экономия регистров. Тут все оказывается несколько сложнее, потому что при вызове про- цедуры на вершине стека оказывается адрес возврата, и потерять его— верный способ развалить программу в полный хлам. Есть два способа организации этого процесса. Оба основываются на картине рас- пределения памяти в стеке (рис. 7.1) до и после вызова подпрограммы, а также в начале ее выполнения Здесь для примера рассмотрен случай, когда в стеке содер- жатся две переменные: var i и var_2 (не адреса этих переменных, а их содержимое). На вершину стека указывает содержимое регистров spl и sph (в которые мы, если помните, в начале программы загружали константу ramend). Канонический способ доступа к переменной vari из подпрограммы заключается в том, чтобы к двухбай- товой переменной sph : spl прибавить 1 (стек растет в сторону уменьшения адресов) и по полученному адресу извлечь из памяти значение (для переменной var 2 приба- вить 2 и т. д., если их больше двух). Потом по тем же адресам сохраняются новые значения. При этом перед выходом из подпрограммы вам ничего делать не надо, поскольку адрес возврата остался на своем месте. Именно так и поступают в «больших» микропроцессорах [13].
Глава 7. Система команд AVR 193 до и после вызова в начале подпрограммы подпрограммы «•— SPL j ««^SPH SPL —+• \ SPH—+ | RAMEND " RAMEND Рис. 7.1. Схема содержимого памяти данных до и после вызова подпрограммы и в начале ее выполнения В 8-разрядном контроллере такой способ, однако, получается достаточно сложным: из-за двухбайтового характера величины адреса в сочетании с однобайтовыми переменными при адресации легко запутаться. Поэтому мы поступим проще — позволим себе испортить стек в начале подпрограммы, не забыв его восстановить перед выходом (листинг 7.18). push var_l /переменная var_l в программе помещается в стек push var_2 ;переменная var_2 в программе помещается в стек rcall procedure /вызывается процедура pop var_2 ;после нее результат извлекается из стека pop var_l /второй результат извлекается из стека procedure: pop ZL /сохраняем младший байт адреса возврата в ZL pop ZH /сохраняем старший байт адреса возврата в ZH pop var_loc2 /переменная var_loc2 со значением var_2 pop var_locl /и переменная var_locl со значением var_l /расчеты, расчеты... push var_locl /результат — в стек push var_loc2 /результат — в стек push ZH /восстанавливаем старший байт адреса возврата из ZH push ZL /восстанавливаем младший байт адреса возврата из ZL ret /возврат из процедуры Команды промежуточного сохранения и восстановления регистров var_ioc2 и var_ioc2 здесь тоже опущены. Как видите, способ довольно громоздкий и в более или менее сложных программах (где как раз подобное и требуется) легко наворо- тить ошибок, запутавшись, куда чего вы положили и откуда это надо извлекать. Нечто подобное йрименяется в мини-операционных системах для контроллеров, типа RTOS [2]. В наших компактных задачах мы такое не используем — тут реги-
194 Часть II. Программирование микроконтроллеров AVR на ассемблере стров обычно хватает на все про все, и деления на локальные/глобальные перемен- ные в массовом порядке можно избежать. Аналогичным образом можно было бы организовать функцию в строгом смысле этого слова — как подпрограмму, возвращающую некое значение (а не как в язы- ке С, где функциями называют все подряд). Но самом деле такой формат с исполь- зованием стека в ассемблере практически не используется — можно даже сказать так, что если в С одни функции, то в ассемблере одни процедуры. Это, конечно, чисто терминологический вопрос, потому что вы и ранее встречали и еще встретите (особенно в следующей главе) множество подпрограмм или макросов, которые фактически делают именно то, что положено делать функциям: т. е. в ответ на входные величины выдают результат некоей операции над этими величинами. Они передают величины напрямую через регистры или иногда через память. Можете самостоятельно поразмыслить, как то же самое можно сделать с помощью стека, и стоит ли этим заниматься. Ассемблерное представление символов и строк Из-за наличия поистине сумасшедщего количества национальных кодировок пута- ница в этом вопросе во всех языках программирования изрядная. Сразу скажем, что она не касается английского алфавита и базовых спецсимволов (в основном тех, что размещены на компьютерной клавиатуре, плюс еще некоторых служебных). За ис- ключением канонического двухбайтового Unicode (который в чистом виде практи- чески не употребляется) во всех кодировках эти символы представлены одинаково и имеют размер в один байт (т. н. ASCII-кодировка). Именно поэтому в синтаксисе ассемблера используются только указанные символы— это позволяет создавать тексты программ в любом редакторе с любой кодировкой, и максимум неудобства, когда кто-то другой не сможет прочесть комментарии, если они представлены на национальном языке. Давайте сначала разберемся с этой бесспорной частью проблемы. Представление англоязычных символов и строк здесь абсолютно аналогично принятому в язы- ке С — так что можете спокойно справляться с руководством по Arduino. Напри- мер, разместив во флеш-памяти программ с помощью директивы .аь строку: .db "This is a long string",'\n' вы можете ее посылать через последовательный порт или выводить на дисплей, из- влекая символы побайтно с помощью команды lpm, как описано в этой главе ранее. Разумеется, подсчет знаков в строке, включая перевод строки в конце, здесь пол- ностью лежит на плечах программиста (в чем, впрочем, язык С не очень далеко ушел от ассемблера). Пример вывода подобной строки через UART вы можете най- ти в программе установки часов, приведенной в главе 75. Заодно сможете оценить удобство нуль-терминированных (т. е. заканчивающихся нулем) строк, принятых в том числе и в языке С.
Глава 7. Система команд AVR 195 Можно размещать отдельные знаки (или строку в виде отдельных знаков) в форме символьного представления: .db 'H'f'e'j'l'f'l'f'o'f'Wf'n1 /последние два символа - то же, что '\п' Наконец, можно все представить просто в виде чисел, руководствуясь таблицей ASCII: .db $48,$65,$6C,$6C,$6F,$0A Здесь $48 (72)— ASCII-код заглавной «Н», $65 (101)— ASCII-код строчной «е» и т. д., а символ с кодом $оа (10) есть ASCII-символ перевода строки. Таблицу ко- дов спецсимволов (вроде «\п») можно найти в руководстве по AVR-ассемблеру. Но все так просто, пока мы находимся в пределах 127 символов таблицы ASCII. В ней нет уже, например, значка градуса, а с национальными алфавитами наступает полный мрак. Если вы написали свою программу приема через последовательный порт (см. главу 75), то можете воспользоваться на обоих концах какой-нибудь определенной таблицей русских кодировок (например, Windows-1251 или ср866) и быть тогда уверенным, что русский текст отобразится адекватно. Но даже в этом случае в ассемблерном тексте нельзя будет изобразить русский текст напрямую, как в двух первых примерах. Тут применим только способ прямого указания ко- дов — можно так, как в последнем примере — просто цифрами, а можно в виде шестнадцатеричных (\хсо = 192 = русская буква «А», \хво .= 176 = значок градуса «°») или восьмеричных (\зоо, \2бо) кодов: db. "Г1, '\355', "ар", ' = ' ,' ',"30", '\260', 'С Эта последовательность воспроизведет на приемном конце строку «Тнар = 30 °С» в кодировке Windows-1251. Для экономии записи здесь русские буквы заменены английскими, если они совпадают по написанию, т. е. в нашем случае все кроме буквы «н» (восьмеричный код \355). Подробности В Arduino, кстати, при передаче через последовательный порт проблема ровно такая же — при попытке передать через Serial строку, записанную в тексте русскими буква- ми, в мониторе порта отобразятся «кракозябры». Этой проблемы не было в первых версиях Arduino (до 1.0 включительно), потому что текстовый редактор там работал в однобайтовой кодировке Windows-1251, и через контроллер русские символы пере- давались адекватно. Потом все текстовые редакторы дружно перешли на Unicode- стандарт, точнее, на его более экономичную версию UTF-8, в которой, как уже говори- лось, символы из таблицы ASCII остались однобайтовыми и одинаковы в любой коди- ровке, а вот русские буквы представляются уже двухбайтовыми символами. Но 8-раз- рядные контроллеры ничего о двухбайтовых символах не знают, и попытка передать русский текст напрямую в львиной доле случаев накрывается медным тазом. Конечно, никто не мешает вам послать вместо одного символа два байта подряд (таблица UTF-8 не секрет), но никогда не знаешь, как на это отреагируют на приемном конце: то ли правильно интерпретируют, как единый двухбайтовый символ, то ли просто как два отдельных символа, которые ничего не означают. По этим причинам для передачи через последовательный порт русских символов приходится либо в каждом отдельном случае изобретать свое решение, либо в це- лях универсальности вовсе отказаться от русского алфавита. Кстати, редактор
196 Часть II. Программирование микроконтроллеров AVR на ассемблере AVRasm поддерживает две однобайтовые русские кодировки: Windows-1251 (она же ANSI) и ср866 (она же DOS). Так что, если вы уверены, что на приемном конце поддерживается какая-нибудь из этих кодировок, можете рискнуть и записывать строки прямо по-русски. Иной гранью поворачивается та же самая проблема, когда речь идет о выводе сим- волов на дисплеи. О выводе текста на графические дисплеи мы здесь говорить не будем, потому что это отдельная история и не про ассемблер, — точнее, не про ас- семблер нашего уровня, — там можно запросто забить все 8 килобайт памяти про- грамм ATmega8 одними только таблицами шрифтов. А в текстовых дисплеях шрифт «зашит» в память контроллера дисплея в виде таблицы символов, и, чаще всего, не одной, а нескольких — обычно двух или четырех. В дисплеях, которые продаются в России (а не привезены откуда-нибудь из Китая в кармане), одна из этих таблиц содержит кириллические символы. Понятно, что в дисплеях, изготов- ленных в России (фирмы МЭЛТ, например), кириллическая таблица будет стоять по умолчанию, а в импортных ее придется, скорее всего, специально подключать отдельной командой. Подробности об обращении с дисплеями мы обсудим в главе 16, а здесь только остановимся на некоторых общих особенностях этих таблиц. Прежде всего — все они, хоть русские, хоть японские, одинаково (или почти одинаково — некоторые второстепенные символы могут различаться) устроены в части, касающейся ASCII, потому посылать на дисплей английские символы и знаки препинания можно, даже не глядя в описание дисплея. А основная кириллическая таблица там обычно орга- низована не в соответствии с какой-либо конкретной кодировкой, а по тому же принципу, по которому мы составляли строку в последнем примере: отдельно зако- дированы только русские буквы, не совпадающие по написанию с английскими. Поэтому вывод на такой дисплей русского текста всегда будет осуществляться ли- бо напрямую числовыми кодами, либо по принципу нашего последнего примера.
ГЛАВА 8 Арифметические операции и операции в двоично-десятичном формате Как мы уже говорили, выполнение арифметических операций в 8-разрядном МК связано с некоторыми трудностями, т. к. размер операндов простого сложения или вычитания ограничен целым значением 255 (или, для чисел со знаком, значением от -128 до +127). А если учитывать, что результат этих операций (не говоря уж об умножении или делении) может легко выйти за пределы восьми разрядов, то огра- ничения становятся еще сильнее. Поэтому для работы с многоразрядными и веще- ственными числами приходится изобретать разные приемы, а для этого необходи- мо, как минимум, вспомнить школьную арифметику, и не только ее. Заметки на полях Арифметические операции с многоразрядными числами наряду с их преобразованием в BCD-формат для индикации (см. далее) — самое узкое место в практическом при- менении AVR-ассемблера. Именно такие процедуры служат основанием для утвер- ждений, что ассемблерные программы разрабатывать очень долго и муторно. Если каждый раз составлять такие процедуры «с нуля», то это мнение будет совершенно оправданным: то, что в языке Arduino спрятано в функциях, выполняющихся в один оператор, здесь иногда приходится «размазывать» на несколько десятков команд. Обратите внимание: самая долгая операция в Arduino — деление чисел типа long — выполняется примерно за 670-680 тактов, и вы поймете, сколько всего упаковано в такие на вид простые операторы. Но не так страшен черт, как его малюют, — по- скольку мы здесь вольны делать с кодом все, что угодно, то совершенно необязатель- но применять все такие процедуры в общем виде. Если постараться и заранее рас- смотреть наиболее часто встречающиеся частные случаи, то можно соорудить некую «библиотеку» операций, которые будут значительно ускорять разработку ассемблер- ных программ. Конечно, это не настоящая программная библиотека — просто образцы проведения типовых операций, которые можно переносить в свою программу. Эта глава и посвящена рассмотрению примеров для подобной «библиотеки», которые мы будем не раз использовать в дальнейшем. Подумаем сначала: а с какими числами приходится работать на практике? Если го- ворить о целых числах, то большинство реальных нужд вполне укладывается в трехбайтовое значение (224 или 16 777 216). Этот же диапазон дает достаточное для практики значение точности (семь десятичных разрядов), большие числа обыч- но округляют и записывают в виде «мантисса-порядок». При этом следует учесть,
198 Часть II. Программирование микроконтроллеров AVR на ассемблере что любая арифметическая операция дает погрешности округления, которые могут накапливаться. Углубляться в этот достаточно сложный вопрос мы не станем — нам достаточно того факта, что, оперируя с трехбайтовыми числами, как результа- том обычных операций деления и умножения, мы не выходим за пределы погреш- ности в шестом знаке, что значительно превышает разрешение рядовых 10-раз- рядных АЦП с числом градаций 1024, означающим ошибку уже в третьем — мак- симум (в реальности так не бывает!) — в четвертом десятичном знаке. Потому, хотя в типовых приложениях для микропроцессоров оперируют 8-, 16- и 32-разрядными двоичными числами (так удобнее укладывать числа в регистры и ячейки памяти), мы в большинстве случаев ограничимся трехбайтовыми (24-раз- рядными) числами — нет смысла занимать дефицитный регистр (причем в арифме- тических операциях— и не один), если он все равно всегда будет равен нулю. Однако возможность получения полных 32-х разрядов также не следует упускать из виду— во многих случаях это может понадобиться (например, как результат промежуточных операций), и большинство описанных далее процедур мы будем рассчитывать как на 24 разряда, так и на 32. Подробности По прочтении этой книги читатель, возможно, заметит, что мы крайне редко и только по серьезной необходимости будем пользоваться действиями с числами со знаком. Для корректной работы с отрицательными числами в AVR есть специальные команды умножения (сложение и вычитание, вообще говоря, таких специальных команд не тре- буют), но в практические приемы работы с отрицательными числами мы здесь углуб- ляться не станем. Дело в том, что с отрицательными числами (как и с вещественны- ми) иметь дело напрямую в 8-разрядных МК неудобно, — их, например, приходится специальным образом преобразовывать при обмене с внешними устройствами, имеющими свою разрядность и тем самым свое представление об отрицательных числах. О том, как проще в 8-разрядном AVR обращаться с числами с плавающей точ- кой, пойдет речь далее, в разд. «Операции с вещественными числами» этой главы, а в конце главы мы также скажем несколько слов о том, как работа с отрицательными и вещественными числами может выполняться в принципе. Интересную отдельную проблему представляет вопрос о возможности производства вычислений с целыми числами большей разрядности, чем это допускают ограничения архитектуры МК. В 8-разрядном контроллере даже числа длиной 8 байтов уже «воро- чать» затруднительно. Прочитав следующие разделы этой главы, вы сможете себе представить, во что выльется простая операция умножения чисел размером, скажем, в 256 байтов каждое. А такие числа (2048 битов) встречаются сегодня, например, в криптографии, в различных связных системах и т. д. Конечно, этот вопрос выходит далеко за рамки нашего повествования (и, положа руку на сердце, за рамки 8-разряд- ных систем тоже), но если вы заинтересуетесь, то как это делается в принципе, може- те прочесть в блоге математика Максима Дерябина (http://mderyabin.ru/ru). Модуляр- ная арифметика (арифметика в остаточных классах, СОК, RNS), которую он там разъ- ясняет, подробно рассмотрена также во втором томе классического труда Дональда Кнута «Искусство программирования» [14]. Все показанные далее реализации типовых операций мы приводим в виде либо просто фрагмента кода, либо в виде законченной подпрограммы — за исключением нескольких небольших процедур, которые целесообразно оформить в виде макро- сов. Остальные имеют слишком большой объем, чтобы их представлять в виде
Глава 8. Арифметические операции и операции в двоично-десятичном формате 199_ макросов — результирующий код существенно раздуется, а преимуществ никаких не получите. Если кто-либо из читателей будут со мной не согласны, и им нравится оформление именно в виде макросов, то, несомненно, они уже вполне смогут спра- виться с этим самостоятельно. Стандартные арифметические операции О том, как выполнять сложение и вычитание 8-разрядных чисел с учетом переноса в следующий разряд, мы уже говорили в главе 7 при обсуждении штатных команд AVR (см. там разд. «Команды арифметических операций»). Эту методику легко распространить на числа любой разрядности, например: add datal0/data20 /младшие разряды adc datall,data21 ;байт1 + С adc datal2,data22 ;байт2 + С adc datal3,data23 /старшие разряды + С Здесь складываются 32-разрядные числа, представленные побайтно в регист- рах datal3:datal2:datall:datalO И data23:data22:data21:data20, результат ОКазыва- ется в datai3— dataio. Эту процедуру несложно оформить в виде макроса (лис- тинг 8.1). .macro Add32_32 add 03,07 /младшие разряды adc 02,06 ;байт1 + С adc 01,05 ;байт2 + С adc 00,04 /старшие разряды + С .endmacro При вызове такого макроса операнды указывают в «арабском» порядке, начиная со старшего: сначала все байты первого слагаемого, затем все байты второго слагае- мого, результат займет байты первого слагаемого. Например, такой вызов: Add32_32 rl6, rl7,rl8,rl9,rl,г2,гЗ,г4 предполагает, что в регистрах г1б, ri7, ri8, ri9 находятся байты первого слагаемого (старший в rl, младший в г4), а в ri,r2,r3,r4— байты второго (старший в г 16, младший в г19), результат будет записан в ri6, ri7, ri8, ri9. Аналогично записывается операция вычитания — с заменой add на sub, a adc на sbc. Для 24- или 16-разрядных исходных чисел достаточно из этих процедур убрать лишние строки и байты в исходных данных. Если предполагается, что результат сложения двух, например, 24-разрядных чисел займет более трех байтов, то первое слагаемое (оно же результат) должно по-прежнему занимать четыре байта (лис- тинг 8.2).
200 Часть II. Программирование микроконтроллеров AVR на ассемблере .macro Add32_24 add 03,@б /младшие разряды adc 02,05 ;байт1 + С adc 01,04 ;байт2 + С adc 00,0 ;С -> старший разряд .endmacro Соответственно, вызов такого макроса может, например, выглядеть, так: Add32_24 rl6,rl7,rl8,rl9,rl,г2,гЗ где в регистрах ri6,ri7,ri8,ri9 по-прежнему находятся байты первого слагаемого (старший в г 16, младший в г 19), в ri,r2,r3— байты второго (старший в rl, млад- ший в гЗ), а результат будет записан, как и ранее, в г1б, ri7, ri8, ri9. Умножение многоразрядных чисел Несколько сложнее с умножением и, особенно, делением. Ассемблерный текст универсальных (т. е. пригодных для всех моделей контроллеров) процедур умно- жения можно найти в Application note AVR200 (на новом сайте Microchip эта «апп- нота» переименована в AN0936), представленной в виде ASM-файла («аппноту» AVR200 в asm-виде можно найти в архиве по адресу, который указан во введении. Учтите, что в архивном варианте некоторые алгоритмы подправлены в сравнении с оригинальным текстом). Заметки на полях На старом сайте atmel.com найти что-то в ворохе различных Application notes было сложно, но там они, по крайней мере, лежали по порядку номеров. В Microchip, мало того, что сочли нужным переименовать «аппноты» (сохранив, правда, старое название в подзаголовке), но и разместили их внавал без особой системы, так что нельзя даже отфильтровать «аппноты», относящиеся к AVR8, от остальных разновидностей МК. Если вы доберетесь до полного списка «аппнот» (что получается не с первого раза: Главная страница microchip.com —► Products —► Resources —► Арр Notes), то единствен- ная возможность там что-то найти — набрать в строке поиска или известный номер (avr200), или характерное ключевое слово (multiply в данном случае). Для больших величин мы будем пользоваться своими процедурами. На практике, как мы говорили ранее, 32-разрядное число (максимально 4 294 967 296, т. е. более девяти десятичных разрядов) с точки зрения точности в большинстве случаев из- быточно. Если мы ограничимся 24 разрядами результата, то нам придется пожерт- вовать частью диапазона исходных чисел так, чтобы сумма двоичных разрядов со- множителей не превышала 24. Например, можно перемножать два 12-разрядных числа (в пределах (МЮ95 каждое) или 10-разрядное (скажем, результат измерения АЦП) на 14-разрядный коэффициент (до 16 383). Так как при умножении точность не теряется, то этого оказывается более чем достаточно, чтобы обработать боль- шинство практических величин.
Глава 8. Арифметические операции и операции в двоично-десятичном формате 201 Подпрограмму перемножения двух величин, представленных в исходном 16-раз- рядном виде, с представлением результата в трехбайтовой форме можно легко по- лучить из слегка подправленной процедуры MPY16U по «аппноте» 200 (листинг 8.3). Мы ею будем часто пользоваться в процедурах перевода данных в физические ве- личины для отображения на дисплее (комментарии сохранены из исходного текста, регистры переименованы, номера их здесь указаны только для ориентировки). /Универсальная процедура умножения двух 16-разрядных величин /множимое в DataH:DataL, множитель KoeffH:KoeffL /результат в tempi:temp:ResH:ResL .def Мр1бх1б: /умножение 16x16 = 32 разряда .def KoeffL = гЗ /multiplier low byte .def KoeffH = r4 /multiplier high byte .def ResL = r9 /result byte 0 (LSB) .def ResH = rlO /result byte 1 .def temp = rl6 /result byte 2 .def count = rl7 /loop counter .def DataL = rl9 /multiplicand low byte .def DataH = r20 /multiplicand high byte .def tempi = r21 /result byte 3 (MSB) /clear 2 highest bytes of result /init loop counter /if bit 0 of multiplier set /add multiplicand Low to byte 2 of res /add multiplicand high to byte 3 of res /shift right result byte 3 /rotate right result byte 2 /rotate result byte 1 and multiplier High /rotate result byte 0 and multiplier Low /decrement loop counter /if not done, loop more clr clr ldi ml6u 1:lsr ror brcc add adc noad8: ror ror ror ror dec brne ret temp2 ;cle temp count,16 KoeffH KoeffL noad8 temp,DataL temp2,DataH temp2 temp ResH ResL count ml6u 1 А вот универсальное перемножение двух однобайтовых величин (процедура mpysu в «аппноте») нам потребуется только один раз — в последнем разделе главы 16, и от оригинала она ничем, кроме имен регистров, не отличается, потому ее текста я здесь не привожу. Можно воспользоваться тем обстоятельством, что для контроллеров семейства Mega определены аппаратные операции умножения. Тогда алгоритм сильно упро-
202 Часть II. Программирование микроконтроллеров AVR на ассемблере щается, причем он легко модифицируется как для 32-разрядного, так и для 24-разрядного результата. Ради универсальности мы в примерах далее в основном будем пользоваться обычными процедурами из «аппноты» (исправленными), но для Mega может быть пригоден алгоритм, приведенный в листинге 8.3, а (в назва- ниях исходных переменных отражен факт основного назначения такой процеду- ры — для умножения неких данных на некий коэффициент, часть комментариев сохранена из английского текста «аппноты»). Надо помнить, что результат отдель- ной команды mul всегда помещается в первую регистровую пару ri: го, поэтому ее приходится дополнять командами обмена между регистрами mov или mow. .def dataL = r4 ;multiplicand low byte .def dataH = r5 /multiplicand high byte .def KoeffL = r2 /multiplier low byte .def koeffH = r3 /multiplier high byte .def temp = rl6 /result byte 0 (LSD1) .def tempi = rl7 /result byte 1 .def temp2 = rl8 /result byte 2 (MSD) /умножение двух 16-разрядных величин, только для Меда /исходные величины dataH:dataL и KoeffH:KoeffL /результат 3 байта temp2:tempi:temp Mul6x16: clr temp2 /очистить старший mul dataL,KoeffL /умножаем младшие movw temp,rO /результат операции из rl:rO в tempi:tempO (rl7:rl6) mul dataH,KoeffL /умножаем старший на младший add tempi,rO /в гО младший результата операции mul adc temp2,rl /в rl старший результата операции mul mul dataL,KoeffH /умножаем младший на старший add tempi,rO /в гО младший результата операции mul adc temp2,rl /в rl старший результата операции mul mul dataH,KoeffH /умножаем старший на старший add temp2,rO /4-й разряд нам тут не требуется, но он — в rl ret Как видите, эта процедура легко модифицируется для любой разрядности результа- та — если нужно получить полный 32-разрядный диапазон, просто добавьте еще 1 LSD (MSD) — Least (Most) Significant Digit, младшая (старшая) значащая цифра. В дальнейшем нам встретятся также LSB и MSB, означающие Least (Most) Significant Bit — младший (старший) знача- щий разряд, по-русски МЗР и СЗР соответственно.
Глава 8. Арифметические операции и операции в двоично-десятичном формате 203 один регистр для старшего разряда (temp3, к примеру) и одну строку кода перед командой ret: adc temp3,rl Естественно, можно просто обозначить ri через temp3, тогда и добавлять ничего не придется. Подробности Так как процедура линейная, без ветвлений, то очень просто подсчитать, сколько она займет времени: 4 команды mui по 2 такта и 7 остальных по одному такту, итого 15 тактов + возврат (ret). Для сравнения, упомянутая процедура mpyi6u из «аппноты» 200 занимает 153 такта, и при этом дает только двухбайтный результат. Хороший пример того, почему всегда предпочтительно пользоваться аппаратными командами. Деление многоразрядных чисел Самый простой алгоритм деления — определить, сколько раз можно вычесть дели- тель из делимого, однако в фирменных «аппнотах» он выглядит несколько слож- нее. Деление — значительно более громоздкая процедура, чем умножение, требует больше регистров и времени (mpyi6u из «аппноты», как мы говорили, занимает 153 такта, а аналогичная операция деления двух 16-разрядных чисел— от 235 до 251). Аппаратных команд в AVR для этого не предусмотрено, потому приходит- ся привлекать смекалку. Операции деления двух чисел (и 8-разрядных, и 16-разрядных) приведены в той же «аппноте» 200, и на этот раз без сложностей, но они не всегда удобны на практике: часто нам приходится делить результат какой-то ранее проведенной операции умножения или сложения, а он нередко выходит за пределы двух байтов. Поэтому пришлось разрабатывать свои операции. Например, часто встречается необходи- мость вычислить среднее значение для уточнения результата по сумме отдельных измерений. Если даже само измерение укладывается в 16 разрядов, то сумма не- скольких таких результатов уже может занимать три или четыре байта. В то же время делитель — число измерений — может быть относительно небольшим и укладываться в один байт. Здесь я привожу процедуру деления 32-разрядных чисел на однобайтовое число, которая представляет собой модификацию оригинальной процедуры из Application notes 200 (листинг 8.4). Названия переменных отражают назначение процедуры — деление состояния некоего 4-байтового счетчика на чис- ло циклов счета (определения регистров-переменных не приводятся, комментарии сохранены из оригинального текста «аппноты», они соответствуют блок-схеме алгоритма, размещенной в pdf-файле). ;div32x8 — 32/8 деление беззнаковых чисел /Делимое и результат в count_HH (старший), countTH, ;countTM, countTL (младший) ; делитель в cykl
204 Часть II. Программирование микроконтроллеров AVR на ассемблере /требуется четыре рабочих регистра dremL — dremHH ;из диапазона г16-г31 для хранения остатка ;а также переменная cnt для подсчета div32x8: clr dremL ;clear remainder Low byte clr dremM ;clear remainder clr dremH ;clear remainder sub dremHH,dremHH /clear remainder High byte and carry flag ldi cnt,33 ;init loop counter d_l: rol countTL ;shift left dividend rol countTM rol countTH rol count_HH dec cnt /decrement counter brne d_2 ;if done ret /return d_2: rol dremL /shift dividend into remainder rol dremM rol dremH rol dremHH sub dremL,cykl /remainder = remainder — divisor sbci dremM,0 / sbci dremH,0 / sbci dremHH,0 / brcc d_3 /if result negative add dremL,cykl /restore remainder clr temp adc dremM, temp adc dremH, temp adc dremHH,temp clc /clear carry to be shifted into result rjmp d_l /else d_3: sec /set carry to be shifted into result rjmp d_l .*****•***************конец 32x8 Отдав таким образом дань профессионализму программистов Atmel, заметим, что эта подпрограмма весьма громоздка и построена по довольно-таки «мутному» алгоритму. Процедуру деления можно значительно упростить (хотя в среднем она будет выполняться заметно медленнее), реализовав впрямую упомянутый ранее алгоритм подсчета числа вычитаний делителя из делимого. Код программы приве- ден в листинге 8.5 (регистры countxx и cykl здесь означают то же самое, что и в листинге 8.4, результат же окажется в регистрах resuitHH — resuitTb).
Глава 8. Арифметические операции и операции в двоично-десятичном формате 205 clr resultTL clr resultTM clr resultTH clr resultHH div32x8: sub countTL,cykl sbc countTM,0 sbc countTH,0 sbc countHH,0 brcs end_div32x8 ;если перенос в минус, то на выход inc resultTL ;иначе увеличиваем результат на 1 — младший байт brne div32x8 /если не было переноса, то обратно inc resultTM ;иначе следующий байт на 1 brne div32x8 ;если не было переноса, то обратно inc resultTH ;иначе следующий байт на 1 brne div32x8 ;если не было переноса, то обратно inc resultHH ;иначе старший байт на 1 rjmp div32x8 /обратно end_div32x8: ret ;====конец процедуры div32x8 Как видите, число задействованных регистров меньше, и процедура получилась значительно короче. Недостаток этого способа, кроме большей длительности вы- полнения, — здесь мы напрямую не получаем остатка. Его все же можно получить, если прибавить к тому отрицательному числу, что осталось в регистрах countxx, значение делителя (как бы вернуться на одну итерацию назад) либо, если оборвать цикл за одну итерацию до того, как выполнится условие переноса в минус (каждый раз сравнивая значение делимого с делителем: как только первое станет меньше второго, цикл можно обрывать). Другой недостаток — здесь приходится выделять специальные регистры под результат, а делимое при этом все равно окажется ис- порченным. Но зато этот алгоритм легко масштабируется — для того, чтобы вместо деления 32-битового числа на 8-битовое получить деление 32-битового на число большей разрядности, достаточно в командах sbc вместо нуля подставить соответствующий разряд делителя. При меньшей разрядности исходных чисел и результата из про- цедуры просто удаляют строки с участием ненужных старших регистров. Многие подобные задачи на деление удается решить еще более простым и менее громоздким методом, если заранее подгадать так, чтобы делитель оказался кратным степени двойки. Тогда все деление сводится, как мы знаем, к сдвигу разрядов впра- во столько раз, какова степень двойки. Для примера предположим, что мы некую величину измерили 64 раза и хотим узнать среднее. Пусть сумма укладывается в два байта, тогда вся процедура деления будет такой, как в листинге 8.6.
206 Часть II. Программирование микроконтроллеров AVR на ассемблере ;деление на 64 clr count_data ;счетчик до 6 div64L: Isr dataH ;сдвинули старший вправо ror dataL /сдвинули младший с переносом inc count_data cpi count_data,6 ;всего б раз (сдвиг на б разрядов) brne div64L Не правда ли, гораздо изящнее и понятнее, чем деление «в лоб»? Операции с вещественными числами Усвоив такой прием, попробуем на радостях решить задачку, которая на первый взгляд требует, по крайней мере, знания высшей алгебры — умножить некое число на вещественный коэффициент (число с «плавающей запятой»). Теоретически для этого требуется представить исходные числа в виде «мантисса-порядок», сложить порядки и перемножить мантиссы (см. разд. «Отрицательные и вещественные числа в МК» в конце этой главы). Нам же неохота возиться с этим представлением, т. к. мы не проектируем универсальный компьютер, и в подавляющем большинстве реальных задач все конечные результаты у нас являются целыми числами (запятая в электронных схемах с индикацией результатов устанавливается на нужном месте принудительно). На самом деле эта задача решается очень просто, если ее свести к последователь- ному умножению и делению целых чисел, представив реальное число в виде целой дроби с оговоренной точностью. Например, число 0,48576 можно представить как 48 576/100 000. И если нам требуется на такой коэффициент умножить, к примеру, результат какого-то измерения, равный 976, то можно действовать, не выходя за рамки диапазона целых чисел: сначала умножить 976 на 48 576 (получится заведо- мо целое число 47 410 176), а потом поделить результат на 105, чисто механически перенеся запятую на пять разрядов. Получится 474,10176 или, если отбросить дробную часть, 474. Большая точность нам и не требуется, т. к. исходное число 976 имело три десятичных разряда. С числами в десятичном виде хорошо работать «руками», просто отсчитывая раз- ряды. Нам же делить на сто тысяч в 8-разрядном МК крайне неудобно — представ- ляете, насколько громоздкая процедура получится? Наше ноу-хау состоит в том, что мы, чтобы «вогнать» вещественное число в целый диапазон, воспользуемся не десятичной дробью, а двоичной, — деление тогда сведется к только что описанной механической процедуре сдвига, аналогичной переносу запятой в десятичном виде. Итак, чтобы умножить 976 на коэффициент 0,48576, следует сначала последний «вручную» умножить, например, на 216 = 65 536, и таким образом получить числи-
Глава 8. Арифметические операции и операции в двоично-десятичном формате 207 тель соответствующей двоичной дроби (у которой знаменатель равен 65 536) — он будет равен 31834,76736, или, с округлением до целого, 31 835. Такой точности хватит, если исходные числа не выходят, как у нас, за пределы трех-четырех деся- тичных разрядов. Теперь мы в контроллере должны умножить исходную величину 976 на 31 835 (как это делается, мы уже знаем) и полученное число 31 070 960 (оно оказывается 4-байтовым— $oidaiafo) сдвинуть на 16 разрядов вправо (лис- тинг 8.7). ;в ddHH:ddH:ddM:ddL число $01DAlAF0, ;его надо сдвинуть на 16 разрядов clr cnt divl6L: /деление на 65536 lsr ddHH ;сдвинули старший ror ddH ;сдвинули 3-й гог ddM /сдвинули 2-й ror ddL /сдвинули младший inc cnt cpi cnt,16 brne divl6L /сдвинули-поделили на 2А16 В результате, как вы можете легко проверить, старшие байты обнулятся, а в ddM:ddL окажется число 474 — тот же самый результат. Но и это еще не все — такая про- цедура приведена, скорее, для иллюстрации общего принципа. Ее можно еще больше упростить, если обратить внимание на то, что сдвиг на восемь разрядов есть просто перенос значения одного байта в соседний (в старший, если сдвиг вле- во, и в младший— если вправо). Итого получится, что для сдвига на 16 разрядов вправо нам нужно всего-навсего отбросить два младших байта и взять из исходно- го числа два старших ddHH:ddH— это и будет результат. Проверьте — $oida и есть 474. Никаких других действий вообще не требуется! Пример использования этого метода вы можете увидеть в программе ультразвукового дальномера, приве- денной в главе 16. Если степень знаменателя дроби, как в рассмотренном случае, кратна 8-ми, то, дей- ствительно, не нужно никакого деления, даже в виде сдвига, но чаще всего это не так. Однако и тут приведенный принцип может помочь: например, при делении на 215 вместо пятнадцатикратного сдвига вправо результат можно сдвинуть на один разряд влево (фактически, умножив число на два), а потом уже выделить из него старшие два байта. Итого процедура будет состоять из четырех операций и займет четыре такта. А в виде циклического сдвига на 15, как ранее, нам необходимо в каж- дом цикле выполнить четыре операции сдвига и одну увеличения счетчика, итого 15 • 5 = 75 простых однотактных операций и еще 15 сравнений, из которых 14 зай- мут два такта, — всего 104 такта. А решение «в лоб» — на основе целочисленного деления — еще в несколько раз превышало бы и эту величину. Существенная раз- ница, правда? Вот такая специальная арифметика в МК.
208 Часть //. Программирование микроконтроллеров AVR на ассемблере Генератор случайных чисел Настоящая генерация случайных чисел в МК требуется не так уж часто, потому что, например, достаточно зафиксировать значение счетного регистра таймера (при счете с переполнением) в произвольный момент времени, определяемый действия- ми пользователя (например, по нажатию им кнопки). Случайность здесь определя- ется тем, что таймер считает намного быстрее, чем человек успевает подготовиться к нажатию, и при всем желании попасть в определенное число невозможно. Но если отказаться от участия человека с его непредсказуемостью и медленностью реакции, то проблема генерации случайных чисел становится достаточно сложной. Ведь компьютер — система полностью детерминированная, в которой любое со- стояние однозначно выводится из предыдущих. Заметки на полях Над созданием хорошего компьютерного генератора случайных чисел бились лучшие математические умы, и эта проблема до сих пор однозначно не решена. Насколько она актуальна, можно судить по такому примеру: в конце 2007 года израильские уче- ные из университета Хайфы обнаружили дефект в коде генератора случайных чисел (ФУНКЦИЯ CryptGenRandom) ОПвраЦИОННОЙ СИСТвМЫ WJndOWS, ИЗ-ЗЭ КОТОРОГО ЗЛОуМЫШ- ленник при желании может не только просчитать, какие шифровальные ключи будут создаваться системой, но и выяснить, какие из них генерировались ею в прошлом. Обратите внимание— дефект этот существовал со времен Windows2000, т.е., по крайней мере, восемь лет до того, как его нашли. В свое время крупнейший математик и теоретик программирования Алан Тьюринг, отчаявшись найти чисто математическое решение проблемы, предложил использо- вать для генерации случайных чисел природные процессы, которые имеют истинно случайный характер. Один из таких процессов — тепловой шум, который приводит к небольшим флуктуациям тока в любом проводнике. На практике для этой цели используют резистор или специальный диод, спонтанные колебания тока в котором усиливаются и оцифровываются. Но такой достаточно сложный генератор приме- няется лишь в специальной аппаратуре — городить его в составе микроЭВМ, ко- нечно, нецелесообразно. Поэтому в компьютерах распространен следующий прием. Есть математические методы, которые позволяют получить т. н. псевдослучайную последовательность чисел. Если начинать счет по такому алгоритму каждый раз с одного и того же чис- ла, то полученная последовательность чисел будет детерминированной — одной и той же во всех случаях, но сами числа окажутся случайными, т. е. равномерно рас- пределенными по вероятности в заданном диапазоне значений. Этой определен- ностью также широко пользуются— например, сигналы со спутника системы навигации GPS имеют именно такой псевдослучайный характер и для «посторон- него» выглядят, как случайный шум. При этом каждый приемник «знает», с какого момента следует начинать отсчет, в результате чего псевдослучайные последова- тельности у приемника и источника совпадут, и сигнал легко расшифровывается. Оказалось, что такая операция служит весьма эффективным средством защиты от помех, позволяющим выделить полезный сигнал на фоне всех остальных источни-
Глава 8. Арифметические операции и операции в двоично-десятичном формате 209 ков радиоволн, даже если их мощность многократно превышает мощность полезно- го сигнала. Отсюда и метод — для генерации истинно случайных чисел достаточно произволь- но выбрать число, с которого начинается отсчет псевдослучайной последователь- ности. Такое начальное число можно сгенерировать различными способами. В раз- личных системах программирования часто встречается инициализация по систем- ному таймеру, из которого берется число, например, миллисекунд в произвольный момент времени. В AVR семейства Mega с асинхронно работающим таймером для отсчета истинно случайных чисел можно использовать его счетный регистр, если запустить его от независимого источника (например, от RC-цепочки, и чем более нестабильны ее параметры, тем лучше). Но мы вопрос инициализации генератора случайных чисел оставим за кадром, а сейчас сосредоточимся на собственно алгоритме генерации псевдослучайной последовательности. Самый простой и широко распространенный алгоритм носит название линейно- конгруэнтного метода Лехмера [14] и сводится к формуле: Хп+\ = (аХп + с) mod т. Здесь Хп+\ — следующее псевдослучайное число последовательности, а и с — кон- станты, называемые множителем и приращением, соответственно, mod — опера- ция нахождения остатка от деления на число w, которое называется модулем. От выбора модуля зависит длина последовательности, после которой она начинает по- вторяться. Для наших целей удобно (и достаточно) выбрать модуль, равный 256 — размеру одного байта. Тогда операция mod будет выполняться автоматически в процессе арифметических операций — в самом деле, для результата, меньшего величины 256, его значение уже само по себе будет остатком от его деления на 256, а большее значение автоматически усекается на величину 256, если не учитывать перенос (например, сумма 240 и 20 даст 260, от которого в регистре останется зна- чение 4). Именно с этим явлением мы боролись в главе 7, когда рассматривали пе- реполнение регистра при сложении, а здесь оно нам может пригодиться. Теория гласит, что выбор величины приращения с достаточно произволен (в том числе оно может быть равно и нулю, но Кнут [14] утверждает, что лучше, если с — нечетное число, например, равное единице). А множитель а в нашем случае, когда модуль представляет собой степень двойки более 4, должен, согласно тому же ис- точнику, быть равным 3 или 5. Пусть а = 5, тогда весь алгоритм генерации сведется к выбору начального числа Nrandom (любым из указанных ранее методов — на- пример, чтением регистра таймера в случайный момент времени) и подсчету сле- дующих чисел (листинг 8.8). mov temp,N_random /временно сохраняем значение N_random Isr N_random ; умножили на 2 Isr N_random ; умножили на 4 add N_random, temp ; умножили на 5 inc N_random /прибавили с = 1
210 Часть II. Программирование микроконтроллеров AVR на ассемблере Естественно, для получения достаточно длинной последовательности такой алго- ритм следует выполнять в цикле. «Искусственное» умножение на 5 в МК семейства Mega можно заменить, разумеется, командой mul, причем старший байт результата (он окажется в регистре ri) при этом следует игнорировать (листинг 8.9). ldi temp,5 mul N_random,temp /умножили на 5 mov N_random/ rO inc N_random /прибавили с = 1 Операции с числами в двоично-десятичном формате (BCD) Операции с числами в двоично-десятичном формате (binary-coded decimal, BCD) — это важная группа операций, ведь значительная часть устройств на основе МК предназначена для демонстрации чисел в том или ином виде. Это, естественно, можно делать только в десятичном формате, в то время как внутреннее представле- ние чисел в регистрах — двоичное. Напомним (см. приложение 7), что числа в двоично-десятичном формате могут существовать в двух видах: упакованном и неупакованном. Неупакованный формат попросту означает, что мы тратим на каждую десятичную цифру не тетраду, как необходимо, а целый байт. Зато при этом не возникает разночтений: $05 = 05ю, и никаких проблем. Однако ясно, что это крайне неэкономично: байтов требуется в два раза больше, а старший полубайт при этом все равно всегда ноль. Потому BCD-числа при хране- нии в регистрах всегда упаковывают, занимая и старший разряд второй десятичной цифрой, — скажем, число 59 при этом и запишется как просто 59. Однако это не шестнадцатеричные $59! 59 в шестнадцатеричной форме запишется как $зв, а наше 59 процессор прочтет как 5 • 16 + 9 = 89, что вообще к нашим значениям не имеет отношения. Поэтому перед проведением операций с упакованными BCD-числами их распаковывают, перемещая старший разряд в отдельный байт и заменяя в обоих байтах старшие полубайты нулями. В некоторых микропроцессорных системах (в их число входит интеловское семей- ство х51 и, кстати, х86) имеется специальная инструкция для т. н. двоично- десятичной коррекции, которая позволяет получить верный результат при сложе- нии двоично-десятичных чисел в упакованном формате. Но в системе команд AVR такой инструкции нет, и она все равно не очень-то полезна, поскольку математиче- ские операции в любом случае удобнее производить в «родной» двоичной форме, а для представления числа на дисплее так или иначе приходится распаковывать. В Arduino этим незаметно для пользователя занимаются функции вывода и ввода чисел (да так успешно, что приходится скорее озадачиваться обратной пробле- мой — представлением десятичных чисел в двоичной/шестнадцатеричной форме),
Глава 8. Арифметические операции и операции в двоично-десятичном формате 211 ну а на уровне ассемблера десятичные преобразования приходится делать, что называется, ручками. Заметки на полях Традиционная область применения команд двоично-десятичной коррекции, в том чис- ле и в процессорах х86, — манипуляции со значением времени, полученным из мик- росхем часов реального времени RTC, в которых часы, минуты и секунды традицион- но хранятся в упакованном BCD-формате. Как вы увидите далее, такой формат хра- нения вполне удобен на практике. Однако область применения микроконтроллерных систем далеко не исчерпывается подсчетом и демонстрацией времени, потому нам придется выйти за рамки однобайтовых кодов, для которых, собственно, инструкция коррекции и создавалась. Уже для двухбайтовых чисел ее применение вызывает только лишние сложности. В области BDC-преобразований есть четыре основные задачи: □ упаковка распакованного и распаковка упакованного формата BCD; □ преобразование двоичного/шестнадцатеричного числа в упакованный BCD-фор- мат; □ распаковка упакованного BCD-формата для непосредственного представления десятичных чисел, например, с целью их вывода на дисплей; □ обратное преобразование упакованного BCD-формата в двоичный/шестнадцате- ричный с целью, например, произведения арифметических действий над ним. Некоторые процедуры для работы с BCD приведены в фирменной Application notes AVR204. Она также доступна в архиве по адресу, указанному во введении. Однако пользоваться процедурами упаковки и распаковки оттуда для самых простых слу- чаев я не советую — они там чересчур усложнены и непредсказуемы по длительно- сти. Упаковка двух отдельных десятичных чисел вообще делается в две команды (листинг 8.10). ; Упаковка распакованного 8-разрядного BCD ;BCD - в datal:dataO, выход - dataO swap datal /меняем тетрады местами в старшем or dataO,datal ;складываем Распаковку выполнить также несложно применением битовой маски поочередно к обоим тетрадам байта (листинг 8.11). Листинг 3.11 /Распаковка упакованного 8-разрядного BCD ;BCD - в data, выход - datal:dataO
212 Часть II. Программирование микроконтроллеров AVR на ассемблере mov dataOfdata andi dataO, ObOOOOllll /выделяем младший mov datal,data andi datal, 0Ы1110000 /выделяем старший swap datal /меняем тетрады местами /Экономичный вариант с замещением исходного упакованного BCD /BCD - в dataO, выход - datal:dataO mov datal,dataО andi datal, 0Ы1110000 /выделяем старший swap datal /меняем тетрады местами andi dataO,ObOOOOllll /выделяем младший Каноническая процедура преобразования произвольного числа в BCD — последо- вательное нахождение остатков от деления на 10. Для ряда практических случаев она может быть существенно упрощена. Так, процедура bin2BCD8 для преобразова- ния однобайтового числа в BCD, приведенная далее, работает только для чисел от О до 99 = $63. В «аппноте» процедура представлена в универсальном виде, пригодном (при небольшой модификации) и для получения упакованного BCD, и для сразу распакованного (результат в двух отдельных байтах). Здесь приведен ее вариант, более экономно задействующий регистры (лис- тинг 8.12). Исходное НЕХ-число содержится в регистре temp, распакованный результат — в tempi: temp. Как и в предыдущих случаях, комментарии сохранены из исходного текста. Процедура выполняется за время от 5 до 50 тактов, в зависимо- сти от исходного числа. /преобразование 8-разрядного hex в неупакованный и упакованный BCD /вход hex = temp, выход BCD tempi — старший, temp — младший /эта процедура работает только для чисел от 0 до 99 bin2bcd8: clr tempi /clear result MSD bBCD8_l: subi temp,10 /input = input — 10 brcs bBCD8_2 /abort if carry set inc tempi /inc MSD rjmp bBCD8_l /loop again bBCD8_2: subi temp,-10 /compensate extra subtraction /* /для получения упакованного (в temp) раскомментировать: swap tempi /меняем тетрады местами в старшем or temp,tempi /складываем */ ret
Глава 8. Арифметические операции и операции в двоично-десятичном формате 213 А вот одно из решений обратной задачи — преобразования упакованного BCD (на- пример, тех же значений часов, минут и секунд из RTC) в НЕХ-число, после чего с ним можно производить арифметические действия (листинг 8.13). По сравнению с «фирменной» BCD2bin8 эта процедура хоть и немного длиннее, но понятнее и бо- лее предсказуема по времени выполнения («фирменная» может занимать от 3 до 48 тактов, наша — всегда 10 тактов). ;на входе в temp упакованное BCD-значение ;на выходе в temp hex-значение ; tempi - вспомогательный регистр /•действительна только для семейства Меда bcd2bin8: ldi multl0,10 ;если mult10 из числа регистров гО-г15, то заменяется на пару ; ldi tempi,10 ; mov mult 10,tempi mov tempi, temp andi temp, 0Ы1110000 ; выделяем старший swap temp /старший в младшей тетраде mul temp,mult10 ;умножаем на 10, в г0 результат умножения mov temp,tempi /возвращаемся к исходному andi temp,0Ь0 0001111 ;выделяем младший add temp,r0 /получили hex ret Решение задачи преобразования в BCD-формат 8-разрядного числа в общем случае (т. е. для чисел от 0 до 255) существенно сложнее, т. к. результат может иметь три десятичных разряда. Заметим, что такая процедура в «аппноте» 204 не представле- на, но ее несложно составить самостоятельно. Можно сначала выполнить один раз деление на 10 (по штатной процедуре div8u из «аппноты» AVR200), получив част- ное и остаток. Частное представляет старшие два десятичных разряда, оно будет укладываться в диапазон 0-99 и преобразовывается далее с помощью приведенной процедуры, а остаток — младший, он и так будет заведомо в диапазоне от 0 до 9. При необходимости выполнить арифметические действия с BCD-числом, проще его преобразовать в обычное двоичное, выполнить действие (не забывая про огра- ничение сверху величиной 99 для одного байта), а затем преобразовать обратно. В «аппноте» AVR204 приведены процедуры сложения и вычитания 16-разрядных чисел с двоично-десятичной коррекцией, но они ничем не лучше такой очевидной последовательности действий. Для вывода чисел через UART, когда текст и числа идут вперемешку, может потре- боваться еще один вариант преобразования чисел— конвертация BCD-значения в строку текстовых символов в стандартной кодировке ASCII (пример вы встретите в программе установки часов, приведенной в главе 15). Если одна цифра в диапазо-
214 Часть II. Программирование микроконтроллеров AVR на ассемблере не от 0 до 9 преобразуется в символьный код простым прибавлением к ее значению числа $зо (десятичное 48— код символа цифры 0 в таблице ASCII), то для числа необходимо уже преобразование в строку. Требуется для наших целей это доста- точно редко, но вывести что-то понятное в такие программы, как монитор порта Arduino, можно только таким способом (нормальные программы-терминалы, в от- личие от монитора порта, принимают числа в любой форме). Процедура (лис- тинг 8.14) переводит упакованные BCD-значения, а для перевода обычных чисел (понятно, что только в диапазоне от 0 до 99) их предварительно надо перевести В BCD процедурой bin2bcd8. ;на входе в temp упакованное BCD-значение ;на выходе в tempi:temp ASCII-строка bcd2txt: ;bcd -> текст максимум 2 разряда mov tempi,temp andi tempi, 0Ы1110000 /выделяем старший swap tempi /меняем тетрады местами в старшем subi tempi,-$30 /старший в tempi andi temp, ObOOOOllll /выделяем младший subi temp,-$30 /младший в,temp ret Есть, конечно, и другие способы преобразования. В языке С, например, иногда ис- пользуют такие хитрые процедуры в одну строку: uint8_t bcd2bin (uint8_t val) {return val - 6 * (val » 4)/} uint8_t bin2bcd (uint8_t val) {return val + 6 * (val / 10)/} Если вы, восхищенные подобной лаконичностью, попытаетесь перевести это на ассемблер, то получите в результате код, не сильно отличающийся по объему от того, что мы здесь привели, и тоже содержащий операции аппаратного умножения, причем в обоих случаях. Потому мы не станем терять время на рассмотрение раз- ных вариантов, а двинемся дальше. Более громоздкая задача— преобразование многоразрядных чисел. Преобразовы- вать BCD-числа, состоящие более чем из одного байта, обратно в НЕХ-формат приходится крайне редко, зато задача прямого преобразования возникает на каж- дом шагу — при выводе на дисплей или при преобразовании в ту же ASCII-строку цифровых символов для передачи через UART. Преобразование 16-разрядного числа в общем случае займет 5 десятичных разря- дов, и соответствующая процедура (bin2BCDi6) приведена в «аппноте» AVR204. Если числа укладываются в диапазон 3-х десятичных разрядов (от 0 до 999 = $03Е7), то можно упростить эту процедуру, сократив количество используемых регистров. Такой вариант процедуры (назовем его bin2bcdio) может потребоваться достаточно часто, т. к. результаты различных измерений: температуры, давления, расстояния, напряжения — чаще всего удобно представлять именно в виде трехразрядного де-
Глава 8. Арифметические операции и операции в двоично-десятичном формате 215 сятичного числа. Примеры его использования для отображения чисел на дисплеях можно найти в главах 14 и 16. В архиве с примерами по адресу, указанному во введении, имеется файл (ariphmetic.asm) с текстами всех описанных здесь процедур, включая и bin2bcdio. В конце файла также размещен весьма объемный пример процедуры bin2BCD24, осуществляющей конвертацию чисел, выходящих за рамки 16-разрядного диапазо- на, — такая, например, задача может возникнуть при конструировании многораз- рядного счетчика или частотомера. Пример ограничивается диапазоном в 7 деся- тичных знаков (9 999 999), т. е. исходное число будет укладываться в три байта (24 разряда). В целях универсальности на выходе получаются отдельно: неупако- ванный (сразу для индикации) и упакованный десятичный формат. В примере упа- кованное число сразу складывается в SRAM, но экономией регистров там не зани- мались, и их требуется аж 14 штук. Сократить количество необходимых регистров (но при этом увеличить число команд) можно, если и исходное число, и все резуль- таты сразу записывать в SRAM. Обратите внимание, что для экономии часть данных в этих процедурах размещает- ся в регистрах из первой половины регистрового файла, которые обычно мы не ис- пользуем из-за невозможности проведения операций с константами, также надо учитывать некоторые адреса в SRAM, чтобы случайно что-то не испортить. Обе процедуры: bin2bcdlO И bin2BCD24 — Сделаны На ОСНОВе «фирменной» bin2BCD16. Как и в других случаях, в текстах процедур сохранена часть оригинальных коммен- тариев из исходной процедуры. Отрицательные и вещественные числа в МК Мы уже упоминали в начале главы о том, что отрицательными и действительными числами пользоваться по возможности не будем. Но ради общего образования надо представлять, что именно Arduino делает, когда мы задаем отрицательное число или тип данных float. Все сложности с этим вопросом возникают от того, что сам микропроцессор знает только целые двоичные числа размером, соответствующим его разрядности, и про все остальное приходится договариваться людям, которые его эксплуатируют. Они очень стараются, чтобы эти договоренности были едины- ми и для внутреннего представления чисел в инструкциях процессора, и для ком- пиляторов всех языков программирования, но по объективным причинам их нико- гда не получается соблюсти до конца. Представление отрицательных чисел Отрицательное число — это чистая абстракция человеческого разума, в природе никаких знаков минуса не существует. Потому их представление в понятном чело- веку виде — предмет договора, не более. Правила, которым подчиняются отрица- тельные числа в математике, — и есть такой договор, который позволяет непроти- воречиво распространить на отрицательные числа почти все операции, которые
216 Часть II. Программирование микроконтроллеров AVR на ассемблере определены для обычных положительных чисел. И представление отрицательных чисел в машине должно этим правилам соответствовать. Напрашивающийся метод представления отрицательных чисел — отвести один бит (логичнее всего — старший) для хранения знака. По причинам, которые вы пой- мете далее, значение 1 в этом бите означает знак «минус», а о — знак «плюс». Как будут выглядеть двоичные числа в таком представлении? В области положительных чисел ничего не изменится, кроме того, что их диапазон сократится вдвое, — например, для числа в байтовом представлении вместо диапа- зона 0..255 мы получим всего лишь 0..127 (оооо оооо ... от ни). А отрица- тельные числа будут иметь тот же диапазон, только старший бит у них будет равен единице (128..255, или юоо оооо ... ни ни). Все просто, не правда ли? Нет, неправда. Такое представление отрицательных чисел совершенно не соответ- ствует обычной числовой оси, на которой влево от нуля идет минус единица, а за- тем числа по абсолютной величине увеличиваются. Здесь же мы получаем, во-первых, два разных нуля («обычный» оооо оооо и «отрицательный» юоо оооо), во-вторых, оси отрицательных и положительных чисел никак не стыкуются, и вы- полнение арифметических операций превратится в головоломку. Поэтому поступим так: договоримся, что -1 соответствует число 255 (ни ни), -2 — числу 254 (ни ню) и т. д. вниз до 128 (юоо оооо), которое будет соответст- вовать -128 (и общий диапазон всех чисел получится от -128 до +127). Очевидно, что если вы в таком представлении хотите получить отрицательное число в обыч- ном виде, то нужно из абсолютного значения числа в двоичной записи (например, оьниоооо = 240) вычесть максимальное значение диапазона (оьиинн = 255) и прибавить единицу (что равносильно вычитанию из 256). Если отбросить знак, то результат такого вычитания (оьоооони + 1 = оьоооюооо = 16 в нашем случае) на- зывается еще дополнением до 2-х (или просто дополнительным кодом) для исход- ного числа (а само исходное число 240 тогда будет дополнением до 2-х для 16-ти). Название «дополнение до 2-х» не зависит от разрядности числа, потому что верх- ней границей всегда служит степень двойки (в десятичной системе аналогичная операция называется «дополнение до 10»). Что произойдет в такой системе, если вычесть, например, 2 из 1? Запишем это дей- ствие в двоичной системе обычным столбиком: 00000001 -00000010 В нулевом разряде результата мы без проблем получаем единицу, а уже для перво- го нам придется занимать единицу из старших, которые сплошь нули, поэтому представим себе, что у нас будто бы есть девятый разряд, равный единице, из кото- рого заем в конечном итоге и происходит: (1)00000001 - 00000010 11111111
Глава 8. Арифметические операции и операции в двоично-десятичном формате 217 На самом деле девятиразрядное число 1 оооо оооо есть не что иное, как 256, т. е. то же самое максимальное значение диапазона + единица, и мы здесь выполнили две операции: прибавили к уменьшаемому эти самые 256, а затем выполнили вычита- ние, но уже в положительной области для всех участвующих чисел. А что резуль- тат? Он будет равен 255, т. е. тому самому числу, которое, как мы договорились, представляет собой -1. То есть вычитание в такой системе происходит автоматиче- ски правильно, независимо от знака участвующих чисел. Немного смущает только эта самая операция нахождения дополнения до 2-х, точ- нее, в нашем случае, до 256 — как ее осуществить на практике, если схема всего имеет 8 разрядов? В дальнейшем мы увидим, что на практике она не нужна, — при вычитании и в микроконтроллерах, и в обычных электронных счетчиках все осу- ществляется автоматически. Впрочем, в микропроцессорах есть и отдельная команда, которая возвращает до- полнение до 2-х. В большинстве ассемблеров она называется neg, от слова «нега- тивный», потому что просто-напросто меняет знак исходного числа, если мы дого- вариваемся считать числа «со знаком», и определяется как операция вычитания из нуля. Рассмотрим из любопытства, как ее можно было бы осуществить «вручную», не обращаясь в действительности к девятому разряду. Для этого выпишем столби- ком какие-нибудь числа (далее для примера: 2 и 240), результаты операции нахож- дения их дополнения до 2-х, и результат еще одной манипуляции, которая пред- ставляет собой вычитание единицы из дополнения до 2-х, или, что то же самое, просто вычитания исходного числа из наивысшего числа диапазона (255): Исходное число Дополнение до 2-х Дополнение до 1 2 256 - 2 = 254 255 - 2 = 253 00000010 11111110 11111101 Исходное число Дополнение до 2-х Дополнение до 1 240 256-240 = 16 255-240 = 15 11110000 00010000 00001111 Если мы сравним двоичные представления в верхней и нижней строках в каждом случае, то увидим, что их можно получить друг из друга инверсией каждого из би- тов. Эта операция называется нахождением обратного кода или дополнения до 1 (потому что число, из которого вычитается, содержит единицы во всех разрядах). Для десятичной системы аналогичная операция называется дополнением до 9-ти. Для нахождения дополнения до 1 девятый разряд не требуется, да и схему можно построить так, чтобы никаких вычитаний не производить, а просто переворачивать биты. Но не ищите в микроконтроллерах специальной операции инверсии битов — для этого вызывается именно команда нахождения дополнения до 1 (в AVR- ассемблере она обозначается com и определяется как операция вычитания из $ff).
218 Часть II. Программирование микроконтроллеров AVR на ассемблере Иначе говоря, для полного сведения вычитания к сложению нужно проделать три операции: 1. Найти дополнение до 1 для вычитаемого (инвертировать его биты). 2. Прибавить к результату 1, чтобы найти дополнительный код. 3. Сложить уменьшаемое и дополнительный код для вычитаемого. Первые две операции и объединены в AVR-ассемблере в одну под именем neg, ко- торая сразу возвращает дополнительный код 8-разрядного числа1. Для корректной работы с отрицательными числами в AVR есть специальные команды умножения (сложение и вычитание, вообще говоря, таких специальных команд не требуют), но в практические приемы работы с отрицательными числами мы здесь углубляться не станем. С отрицательными числами (как и с веществен- ными, о чем рассказано далее) иметь дело напрямую в 8-разрядных МК неудоб- но — их, например, приходится специальным образом преобразовывать при обмене с внешними устройствами, почти всегда имеющими свою разрядность и тем самым свое представление об отрицательных числах. Так, в 8-разрядной системе число 255 есть минус единица, а в 16-разрядной минус единице соответствует число 65 535, в 32-разрядном — число 2 147 483 647 и т. п. В этих случаях число 255 бу- дет интерпретировано как положительное 255, что приводит к излишней путанице. Стоит, например, иметь в виду, что при посылке данных в компьютер некоторые не очень совершенные приемные программы (как, например, монитор порта Arduino) могут интерпретировать большие положительные числа как числа со знаком минус. В электронике небольшой разрядности вообще всегда проще «вогнать» числа в по- ложительный диапазон, а знак «минус» (например, для температуры в градусах Цельсия при отображении на дисплее) учесть отдельно, — все равно число прихо- дится раскладывать побайтно (соответствующие примеры приведены в главах 14 и 16). Представление вещественных чисел Как мы можем при необходимости представить вещественные числа в компьютере, если регистры ничего о таковых «не знают» и могут воспроизводить только целые числа? Мы не станем рассматривать расширение формулы для представления чисел в позиционной системе (см. приложение 7), включающее в себя не только целые, но и все действительные числа, поскольку в электронике такой вариант хождения не имеет. В электронике и компьютерной технике используют другой способ пред- ставления действительных чисел — с помощью мантиссы и порядка, в так назы- ваемом нормализованном виде. При этом место запятой фиксируется так, чтобы перед запятой всегда был один ненулевой десятичный разряд (т. е. число от 1 до 9): 1 Казалось бы логичным поменять названия команд com и neg местами — так они больше соответство- вали бы смыслу производимых ими действий. .Но так принято, потому что именно операция neg соот- ветствует изменению знака однобайтового числа (в диапазоне -128 до +127). В языке С операция по- битового НЕ (~) также имеет синоним compl.
Глава 8. Арифметические операции и операции в двоично-десятичном формате 219 0,0125 = 1,25-10~2, 1254,81 = 1,2548Ы03. Разумеется, в микропроцессорах все это лучше делать в двоичной форме, записы- вая порядок как степень двух (скажем, операция выравнивания порядков при сло- жении таких чисел сведется просто к сдвигу мантиссы). Легко заметить, что и саму запятую, и основание степени тут можно опустить, за- писывая в память лишь мантиссу и порядок, — конечно, если всегда помнить, что мы имеем в виду. Например, можно сделать так: отвести в памяти четыре байта, из которых первые три байта хранят величину и знак мантиссы, а четвертый отведен под порядок. Если старший бит порядка равен единице, то порядок отрицательный, а если нулю— положительный, итого двоичный порядок составит диапазон от -128 до +127, а весь диапазон чисел в десятичном виде составит примерно от -3,4-1038 до 3,4-1038. Число значащих десятичных цифр будет составлять около 6- 7 десятичных знаков (оно будет соответствовать целому числу 223 = 8388608). 8 обычных языках программирования такие числа называют числами ординарной точности (в языке Pascal это тип single, в стандартном С — float), и на персо- нальных компьютерах давно уже не употребляют, поскольку все персоналки, как минимум, 32-разрядные, и там базовый вещественный тип — double длиной 8 бай- тов. Но для 8-разрядных контроллеров именно вещественные числа ординарной точности являются основными, что хорошо видно на примере компилятора AVRGCC, на котором базируется Arduino: там float и double совпадают по размеру и занимают четыре байта. В некомпьютерной же электронике вещественными числами не пользуются вовсе. При необходимости их переводят в целые и обратно, умножая на соответствующую степень десяти (или двойки, как мы делали ранее), при этом все остальные участ- вующие в расчетах величины также масштабируются в нужное число раз. Затем при выводе, к примеру, на цифровой дисплей, запятая просто устанавливается в нужном месте. Иными словами, для цифровой схемы не существует значения температуры, равного 30,81 градуса, а есть число 3081 в BCD-формате. Именно из этого мы будем исходить при работе с AVR-контроллером, в котором конечно же нет никаких команд для вещественных чисел, — их не было даже в первом IBM PC, к которому пришлось потом пристраивать специальный матема- тический сопроцессор, чтобы аппаратно ускорить выполнение операций с вещест- венными числами. Действия с числами с плавающей запятой (с плавающей точкой, как их называют в США) сложнее и занимают намного больше времени, чем дейст- вия с целыми числами: при сложении, например, нужно выровнять порядки, сло- жить мантиссы, потом результат привести к нормализованной форме и при всех этих действиях постараться не потерять в точности. И вот еще интересный истори- ческий факт: в советской вычислительной машине БЭСМ-6, которая в 1960-70 годы считалась основным конкурентом американским суперкомпьютерам, не было пре- дусмотрено, наоборот, целочисленной арифметики — там все действия производи- лись только с действительными числами.
ЧАСТЬ III Практическое программирование микроконтроллеров AVR Глава 9. Глава 10. Глава 11. Глава 12. Глава 13. Глава 14. Глава 15. Программирование таймеров Использование EEPROM Аналоговый компаратор и АЦП Интерфейс SPI Интерфейс TWI (I2C) и его применение Режимы энергосбережения и сторожевой таймер Программирование UART и обмен данными с персональным компьютером Глава 16. Некоторые Arduino-задачи на ассемблере
ГЛАВА 9 Программирование таймеров С таймером-счетчиком, одним из важнейших компонентов микроконтроллеров, мы уже в общих чертах познакомились в главе 3. Из главы 6 мы узнали, как реализо- вать простые задержки с помощью таймера и без него. В этой главе мы резюмиру- ем полученную информацию и познакомимся с некоторыми более сложными функциями, которые позволяют осуществлять таймеры. 8- и 16-разрядные таймеры Таймер в МК — это, по сути дела, двоичный счетчик. 8-разрядный таймер может считать от 0 до 255, а 16-разрядный — от 0 до 65 535. Результат счета можно про- честь в любой момент (в том числе и не прекращая работу таймера) из счетного регистра, носящего общее наименование tcntx, где х— номер таймера (для 8-разрядных это номера о и 2, для 16-разрядных— нечетные 1, з и т. д.). В 16-разрядных таймерах счетный регистр состоит из двух 8-разрядных регистров с общим наименованием tcntxh (старший) и tcntxl (младший). В счетные регистры также в любой момент можно записывать любое значение, что позволяет начинать счет не с нуля и регулировать интервалы счета. Мы в дальнейшем будем иметь дело с моделями, в которых один (ATtiny2313) или два (ATmega8, ATmega8535) 8-разрядных таймера: TimerO и Timer2, и один 16-разрядный Timerl. Ровно те же таймеры имеются и в базовом Arduino. Большее количество доступно в старших Mega, но мы их здесь, как договорились, не рас- сматриваем. Поэтому далее в наименовании регистров вместо буквы х будем ука- зывать номер конкретного таймера, но надо иметь в виду, что те же закономерно- сти в основном действуют и для таймеров с большими номерами. При чтении регистров 16-разрядных таймеров их содержимое может измениться в промежутке между чтением отдельных 8-разрядных «половинок». Например, ес- ли содержимое младшего байта составляет $ff, to мы прочтем именно это значе- ние, но до выполнения команды на чтение старшего байта общее состояние тайме- ра может стать на единицу больше, а младший байт в это время обнулится. В ре- зультате при реальном числе, записанном в счетных регистрах, равном $01 о о, мы
224 Часть III. Практическое программирование микроконтроллеров AVR прочтем $oiff, что, конечно, далеко от действительности. Аналогичная история может произойти и при записи в таймер какого-либо числа. Во избежание таких ситуаций в 16-разрядных таймерах AVR, как мы уже говорили, предусмотрен специальный механизм чтения и записи парных 16-разрядных регистров, который требует некоторой внимательности от программиста (см. разд. «Команды пересыл- ки данных» главы 7). Механизм этот следующий. При записи первым загружается значение старшего байта, которое автоматически помещается в некий (недоступный для программи- ста) буферный регистр. Затем, когда поступает команда на запись младшего байта, оба значения объединяются, и запись производится одновременно в обе «половин- ки» 16-разрядного регистра. Наоборот, при чтении первым должен быть прочитан младший байт, при этом значение старшего автоматически фиксируется помещени- ем в тот же буферный регистр, и при следующей операции чтения старшего байта его значение извлекается оттуда. Таким образом, и при чтении значения байтов со- ответствуют одному и тому же моменту времени. Неправильный порядок чтения либо записи может привести к непредсказуемым результатам — например, если прочесть младший байт tcntil последним, то в этот момент старший tcntih буферизуется и так и останется в буфере. И при следующем чтении в том же порядке, когда бы оно ни производилось, сначала будет прочитано это значение, которое, естественно, никак не соответствует прочитанному значе- нию младшего регистра. Повторим! Чтобы манипуляции с 16-разрядными регистрами были успешными, при чтении необ- ходимо сначала прочесть младший байт (tcntil), потом старший (tcntih), а при запи- си — сначала записать старший байт (tcntih), потом младший (tcntil). При чтении и записи регистра захвата icrihiicril и при записи регистров сравне- ния ocrixH:ocrixl (где х— А или В, о чем далее) также следует придерживаться этого правила. Оно не действует на регистры управления tccria и tccrib (которые по сути есть два раздельных регистра, а не один 16-разрядный) и может не учиты- ваться при операции чтения из регистров сравнения ocrixh и ocrixl, которые мож- но читать в произвольном порядке (хотя сама по себе такая операция требуется крайне редко). Формально говоря, при чтении содержимого этих регистров также должны быть запрещены прерывания, но на практике, если соблюдается правильный порядок чтения, то «вклинившаяся» процедура прерывания приведет лишь к небольшой от- срочке получения результата. Единственное исключение составляет случай, если в самом прерывании также происходит обращение к любому из 16-разрядных реги- стров таймера, изменяющее буфер (для всех регистров он один и тот же), — тогда на время чтения или записи такие прерывания следует запрещать. При запуске таймеров в работу следует также учитывать следующее обстоятельст- во: несмотря на формальную установку в ноль всех РВВ при включении питания, опыт показывает, что перед запуском таймера в работу для получения точного пер-
Глава 9. Программирование таймеров 225 вого отсчета регистры tcntx следует принудительно обнулить. Например, для 16-разрядного Timer 1 эта процедура выглядит так: clr temp out TCNT1H,temp ;не забываем про порядок записи out TCNT1L,temp Если далее используется прерывание по переполнению (или с обнулением счетчика по достижении заданного числа), то каждый раз обнулять таймер необязательно. Однако при этом следует учесть, что вовремя не остановленный таймер продолжа- ет считать (в том числе и в течение времени, необходимого для его остановки). По- тому для точного измерения интервалов времени между событиями необходимо таймер остановить, очистить его счетные регистры и по наступлению нужного начального события запустить «в работу» заново. При необходимости следует также очищать предделитель таймеров — иначе может возникать ошибка в пределах одного периода заданной частоты на входе таймера (например, при коэффициенте 1:8 может возникнуть ошибка в пределах 8 периодов тактовой частоты). Очистка осуществляется записью логической единицы в биты PSR2 или psrio регистра sfior, в некоторых моделях (ATtiny2313) называемого так- же gtccr (бит psrio относится к Timer 1 и TimerO, а бит PSR2 — к Timer2, который может работать в асинхронном режиме отдельно от первых двух). Подробности Заметим, что в старших моделях (ATmega88/168/328, например) останавливать тай- меры можно не только очисткой соответствующих битов регистра управления (о чем далее), но и остановкой предделителя, — при этом прекратят счет все таймеры, кото- рые в текущий момент работают от предделителя. Для остановки нужно записать ло- гическую единицу в бит tsm того же регистра gtccr, для запуска — в этот бит записы- вается логический нуль. Таким способом таймеры можно синхронизировать: при сбро- се tsm аппаратно сбрасываются psr2 и psrio (которые в этих моделях зачем-то переименованы в psrasy и psrsync), и все таймеры начинают работать одновременно. В «наших» контроллерах такой удобной «фичи», к сожалению, не имеется. Регистры управления (tccro и tccr2 для 8-разрядных таймеров и tccria и tccrib для 16-разрядного) используются, как ясно из их названия, для установки нужного режима таймера. В простейшем случае для 8-разрядного TimerO регистр управле- ния tccro только запускает таймер и устанавливает тактовую частоту счета. Все нули в разрядах этого регистра (по умолчанию) означают остановленный таймер, а комбинация трех младших битов cso2-csoo от ooi до ш запускает таймер, одно- временно задавая источник тактового сигнала. Значения от i до 5 этого регистра означают, что таймер будет работать от тактовой частоты МК с учетом преддели- теля (коэффициент деления которого может принимать величину 1, 78, 764, V256 и 7ю24* в зависимости от значения cso2-csoo). Значения tccro, равные б и 7 (но и ш в младших разрядах) означают работу TimerO от внешнего источника импульсов, подаваемых на вывод ТО МК, — от падающего или нарастающего фронта этих им- пульсов, соответственно. Таким простейшим режимом обходится в некоторых моделях семейства Tiny, а также в ATmega8, только TimerO. Во всех остальных моделях AVR, как и в до-
226 Часть III. Практическое программирование микроконтроллеров AVR полнительных 8- и 16-разрядных таймерах, режимов значительно больше. Среди них возможности работы в режиме «захвата» внешнего события (удобная для по- строения периодомеров или подсчета редких событий) и счета до определенного значения (или даже нескольких таких значений) с возникновением прерывания по достижении заданного числа, несколько режимов широтно-импульсной модуляции (PWM), возможность функционирования в асинхронном режиме от отдельного так- тового генератора и т. п. В некоторых старших Mega (ATmega64 и др.) есть еще и третий регистр управления (tccrxc), с помощью которого можно программно управлять выводом ОСпх (об этом рассказано далее). Интересно, что в ATtiny2313 TimerO — аналогично старшим моделям Mega — также имеет два регистра управ- ления: TCCR0A И TCCR0B. Все варианты мы тут рассматривать не станем, чтобы не переписывать «инструк- цию»: подробное описание всех функций таймеров-счетчиков AVR займет много места, к тому же большинство этих возможностей редко требуются на практике. Остановимся мы лишь на некоторых практических аспектах использования тай- меров. Совет Чтобы не запутаться в многообразии режимов, следует иметь в виду общее правило: если вы какие-то режимы не используете, то соответствующие им биты регистров управления следует оставлять в нулевом («сброшенном») состоянии, в которое они установлены по умолчанию. Например, для простого запуска таймеров (режим Normal) достаточно установить режим делителя тактовой частоты в нужное состояние с помощью битов cso2-csoo. Они для TimerO и Timer2 находятся в младших разрядах регистров tccro/tccr2 (tccrob для ATtiny2313) или регистра tccrib— для Timer 1. Для выключения эти биты следует обнулить. Обычно еще устанавливается либо состояние бита пере- ключения внешнего вывода ООс, либо режим прерывания таймера, что также дела- ется одинаково для всех моделей. Больше никаких дополнительных действий неза- висимо от модели контроллера предпринимать не требуется. Разумеется, в большинстве случаев использования таймеров (за исключением фор- мирования простых задержек, как в главе 7) нет смысла оставлять тактовую часто- ту, задаваемую от внутреннего RC-генератора, обеспечивающего эту функцию по умолчанию, — он слишком неточен, и к тому же частота 1 МГц недостаточна для корректного выполнения многих действий. Поэтому большинство задач далее мы ориентируем на подключение внешнего кварца с компромиссной частотой 4 МГц. При такой частоте и потребление еще не слишком увеличивается, и в то же время она достаточна для корректного выполнения многих действий. Напомним (см. гла- ву 5), что при этом необходимо переключить контроллер на работу от внешнего кварцевого резонатора, для чего fuse-биты cksel3 .. о необходимо установить в зна- чения mi (а для ATtiny2313 еще дополнительно установить сброшенный по умол- чанию бит ckdiv8, чтобы отключить режим деления частоты на 8).
Глава 9. Программирование таймеров 227 Формирование заданного значения частоты Надо сказать, что в Arduino такая функциональность реализована достаточно удоб- но — в виде единственной функции tone (), которая позволяет формировать любое значение частоты (в пределах от 30 Гц до по крайней мере сотен килогерц) на про- извольном выводе платы Arduino. На практике это применяется для генерации зву- ка, но, конечно, может быть использовано для любых целей, — правда, точность задания частоты и ее стабильность здесь нуждаются в отдельной проверке. Есть и множество альтернативных Arduino-библиотек, которые преодолевают те или иные ограничения функции tone (), но все они, во-первых, не годятся для произвольного значения частот (а что делать, если нужна стабильная «мигалка» 0,5 или 0,1 гер- ца?), во-вторых, их точностные характеристики также никто не указывает и не исследует. Поэтому давайте рассмотрим, что может в этом плане предложить ас- семблер. В примере, приведенном в главе 6, мы рассматривали функционирование 8-разряд- ного таймера в самом простом режиме — непрерывного счета тактовых импульсов с подсчетом числа переполнений его счетного регистра для формирования нужной задержки. При этом выбор значений частоты следования прерываний (а значит, и значения задержки) ограничен комбинациями тактовой частоты на входе таймера с допустимыми коэффициентами предделителя, да еще полученная величина ока- зывается поделенной на «некруглое» число 256, соответствующее объему счетного регистра TimerO (для 16-разрядного Timer 1 это число, соответственно, будет равно 65 536). А как быть, если нам требуется получить точную частоту, например, 1 кГц, 100 или 1 Гц? Есть два способа достижения такого результата. Первый способ при- меним ко всем таймерам, независимо от их разрядности и особенностей, и мы его сейчас рассмотрим. Способ заключается в том, что каждый цикл счета мы начинаем не с нуля, а с опре- деленного заранее рассчитанного числа. Обратим внимание, что счетчики-таймеры AVR, вообще говоря, могут только суммировать (реверсирование счета также мо- жет производиться, но только в режимах PWM, к которым мы сейчас обращаться не станем). Исходя из этого, нам и нужно рассчитать предварительно записываемое число. Опять будем ориентироваться на простейший TimerO (в 16-разрядных таймерах це- лесообразнее выбрать другой способ, о котором далее) и предположим, что нам требуется сформировать частоту 1 кГц. Рассмотрим типовой порядок рассуждений в подобном случае. Частоту тактовых импульсов необходимо выбирать так, чтобы она была больше 1 кГц, но не превышала значение 1 кГц х 256 = 256 кГц. Если, как мы договорились, тактовая частота контроллера равна 4 МГц, то можно выбрать коэффициент предделителя, равный 1:64, — тогда частота на входе таймера будет 62 500 Гц. В этом случае, чтобы получить частоту ровно 1 кГц, число 62 500 нужно поделить на 62,5. Нецелое число отсчетов в таймере организовать сложно, но теоретически возможно. Для этого можно, например, начинать с числа 194, что до переполнения (значения 256, т. е. 0 в счетном регистре) даст 62 отсчета. Еще пол-отсчета можно
228 Часть III. Практическое программирование микроконтроллеров AVR сделать, если сменить частоту на входе таймера на более высокую — например, при частоте предделителя V8 вместо 764 каждый «старый» отсчет будет соответст- вовать по длительности 8 «новым», и нам нужно будет отсчитать 4 «новых» интер- вала (для чего придется предварительно записать в счетный регистр число 252). При этом следует еще учесть количество тактов, необходимое для перестройки таймера. Другой способ точной подгонки частоты — организовать простую задержку, и мы его рассмотрим позднее. А пока зададимся вопросом: нельзя ли избежать всех этих сложностей? Для этого можно, например, выбрать подходящее значение тактовой частоты МК — так, «кварц» с частотой 4096 кГц даст при делении на 64 ровно 64 кГц, и делить придется уже на целое число. Более общий способ — применить тот же метод, что и в примере из главы 5, — с дополнительным счетчиком пере- полнений (переменная counttime в листинге 5.13). Тогда мы не ограничены часто- той 256 кГц, а можем выбрать более высокую частоту на входе счетчика. Напри- мер, если установить коэффициент предделителя Vg, то мы из 4 МГц получим 500 кГц, которые поделим на 250 описанным ранее методом, и отсчитаем два таких цикла переполнения. Теперь посмотрим, как это может выглядеть в конкретной задаче. Предположим, нам нужно с частотой 1 кГц переключать внешний вывод МК (для определенности пусть это будет вывод PD6). Чтобы частота переключения была 1 кГц, нам надо за один период переключить вывод дважды (от высокого к низкому уровню и обрат- но). Листинг 9.1 иллюстрирует, как это может выглядеть в реальности. .device ATmega8 .include "m8def.inc" /частота 4 Мгц .equ K_div = 250 /коэффициент деления Кдел .def rK_div = rl6 /рабочая ячейка для Кдел .def count = г17 /счетчик до 2 .def temp = rl8 /рабочая переменная /===== прерывания rjmp Reset /вектор сброса .org OVFOaddr /no адресу OVFOaddr прерывание переполнения TimerO rjmp TIM0_OVF .org INT_VECTORS_SIZE /начинаем с адреса после таблицы векторов /====== начало программы TIM0_OVF: /прерывание TimerO inc count sbrs count,0 /если счетчик нечетный, пропустить sbr temp, ОЬОЮООООО /иначе установить бит б sbrc count,0 /если счетчик четный, пропустить cbr temp, ОЬОЮООООО /иначе сбросить бит 6
Глава 9. Программирование таймеров 229 out PortD,temp ;вывести в порт D out TCNTO,rK_div ;"заряжаем" таймер к следующему разу reti ;конец прерывания таймера Reset: ldi temp,low(RAMEND) ;загрузка указателя стека out SPL,temp ldi temp,high(RAMEND) ;загрузка указателя стека out SPH,temp ldi temp,0b0100D000 /шестой разряд порта D на выход out DDRD,temp clr count /очищаем clr temp /регистры ldi temp, (l«TOIE0) /разр. прерывания TimerO Overflow out TIMSK,temp ldi rK_div,K_div /значение к. деления (250) neg rK_div /256-K_div, т. к. счетчик суммирующий out TCNTO,rK_div /"заряжаем" таймер на первый раз ldi temp,0b00000010 /TimerO включить 1:8 out TCCR0,temp sei Cykle: rjmp cykle Заметки на полях При отсутствии под рукой осциллографа или мультиметра с частотомером, для про- верки функционирования программы можно временно установить коэффициент деле- ния входной частоты таймера 1/ю24 (вместо 1/8) и подключить к выводу PD6 (вывод 12 корпуса) светодиод. Он должен замигать с видимой глазом частотой около 8 герц. По- том не забудьте опять установить правильный коэффициент предделителя. В этом примере нам требуется переключать вывод дважды за период (один раз в 1 и один раз обратно в 0). Если же за период выполнять только одно действие, то можно переписать прерывание следующим образом (листинг 9.2). ldi count,2 TIM0: /прерывание TimerO dec count sbrc count,0 ;если счетчик четный, пропустить rjmp exit ;на выход <производим нужное действие> exit: out TCNTO, rK_div /"заряжаем" таймер reti /конец прерывания таймера
230 Часть III. Практическое программирование микроконтроллеров AVR При необходимости точной подстройки частоты по такому способу время, уходя- щее на вызов прерываний и выполнение команд, можно не учитывать, т. к. таймер считает независимо от работы ядра, и частота прерываний будет точно равна за- данной. Но вот сама эта заданная частота отличается от точного значения 1 кГц на величину нестабильности «кварца», которая может достигать заметных величин. Для миниатюрного часового «кварца» 32768 Гц типа РК206 разброс номинальной частоты составляет порядка 210"5 (плюс еще не менее 410"5 ухода в интервале тем- ператур от -40 до +70 °С), для обычного в корпусе HC-49U он несколько меньше, и может составлять около 15-10'6. Следовательно, номинальная частота «кварца» 4 МГц может оказаться в диапазоне от 3 999 940 до 4 000 060 Гц. Это усредненные величины, а реально ошибка может оказаться еще больше, т. к. на корпусе тип «кварца» чаще всего указывается не полностью, и определить допуск для куплен- ного экземпляра, особенно импортного, нередко крайне затруднительно. Самые «плохие» кварцевые резонаторы дают разброс до 510'4 (0,05%, т.е. 4000 кГц ± 2 кГц). Такой разброс может показаться небольшим, но, между прочим, его величина в 2-10"5 дает ошибку хода часов около 1 с в сутки или даже несколько больше (на- верняка вам надоело корректировать дешевые часы, уходящие на минуту-другую каждые два-три месяца). Поэтому при необходимости частоту срабатывания тайме- ра приходится подстраивать индивидуально. Как это можно сделать, мы поговорим позже, а сейчас остановимся на более «прогрессивном» способе формирования точных временных интервалов и частот с помощью 16-разрядного таймера. Заметим, что уже рассмотренный способ получения частоты— универсальный, и годится для всех таймеров всех моделей контроллеров, причем частоту можно сформировать на любом выводе МК. На практике же куда удобнее использовать специальные выводы ОСпх (где п — номер таймера, а х может отсутствовать или принимать значение А, В или, в некоторых случаях, С — в зависимости от того, какой регистр сравнения задействован), которые в МК AVR семейств Tiny и Mega имеют все 16-разрядные таймеры (Timer 1), а в большинстве моделей и 8-разрядные (TimerO и Timer2). Выводы ОСпх аппаратно привязаны к прерыванию сравнения с заданным числом, потому для их активации достаточно один раз установить нуж- ный вывод порта на выход, установить соответствующее прерывание по сравнению и завести таймер. Режим этот организован так, что вывод переключается один раз за установленный период («туда» и в следующем периоде «обратно»), потому на выходе мы имеем строгий меандр, частота которого равна половине от частоты прерываний. Посмотрим, как с использованием этого режима можно построить часы. Отсчет времени Способ отсчета времени мы рассмотрим на примере формирования частоты в 1 Гц для отсчета секунд — это базовая функция для построения часов на основе МК. Идея состоит в том, чтобы использовать режим сравнения (compare) значения счет- чика с наперед заданным числом. Причем 16-разрядные таймеры имеют таких режимов по два или иногда по три. Из двух 8-разрядных таймеров в ATmega8
Глава 9. Программирование таймеров 231 TimerO таких режимов не имеет (это практически исключение среди AVR, если не считать уже не выпускающегося семейства Classic), зато их имеет Timer2 (соответ- ствующий вывод— ОС2). В ATtiny2313 единственный 8-разрядный TimerO в этом плане полностью аналогичен 16-разрядному и имеет два таких режима: А и В. Самый классический в этом плане из «наших»— ATmega8535, который имеет по одному режиму сравнения для каждого из 8-разрядных таймеров. Потому, чтобы меньше путаться между моделями, мы рассмотрим использование этих выводов применительно к Timer 1 (соответствующие выводы: ОС1А и ОС 1В), что годится для всех контроллеров, кроме некоторых младших Tiny, в которых Timer 1 вовсе отсутствует. Подсчитаем, какие параметры нам следует обеспечить. Как мы уже выяснили ра- нее, если тактовая частота контроллера равна 4 МГц, а коэффициент предделителя составляет 7б4, то частота на входе таймера будет 62 500 Гц. Если бы мы считали до переполнения счетчика Timer 1, как ранее, при таких параметрах частота на выходе составила бы 62500/65536 = 0,9537 Гц. Для того чтобы получить 1 Гц, достаточно поделить частоту на входе на 62 500, что укладывается в 16 разрядов. Значит, если мы запишем в 16-разрядный регистр сравнения число 62 499 и будем досчитывать до этой величины (обнуляя счетный регистр по ее достижении), то как раз получим нужную частоту следования переключающих импульсов (частота на выводе ocix при этом, как мы говорили, будет вдвое меньше). Чтобы такой режим работал, для таймера есть специальный режим обнуления счет- чика при совпадении с числом в регистре сравнения A. Timer 1 имеет два вывода: ОС1А и ОС 1В, но здесь нам достаточно одного. Листинг 9.3 иллюстрирует процесс инициализации Timerl для работы в таком ре- жиме (пример годится для ATmega8, ATtiny2313, ATmega8535 и многих других моделей, если изменить строки, относящиеся к инициализации вывода ОС1А). ldi temp,high(62499) out OCRlAH,temp ;62499 для длительности 1 сек, ;31249 для частоты 1 Гц при к. дел 1/64 ldi temp,low(62499) out OCR1AL,temp ; ;62499 для длительности 1 сек, ;31249 для частоты 1 Гц при к. дел 1/64 ldi temp, (1«СОМ1А0) out TCCR1A,temp /переключающий режим для выхода ОС1А ldi temp,ОЬООООООЮ ;вывод РВ1 (ОС1А) на выход для mega8 out DDRB,temp ;ldi temp,0b00001000 ;вывод РВЗ (ОС1А) на выход для tiny2313 ;out DDRB,temp ;ldi temp,0b00100000 /вывод PD5 (OC1A) на выход для mega8535 ;out DDRD,temp ldi temp,0b00001011 /включить Timerl 1/64 out TCCRlB,temp
232 Часть III. Практическое программирование микроконтроллеров AVR Закомментированные строки относятся к другим моделям контроллеров, в которых размещение вывода ОС1А, разумеется, отличается от Mega8. В регистр сравнения нужно загружать число, на единицу меньшее заданного числа отсчетов таймера (почему — пояснено во врезке «Подробности» далее). Бит з в регистре tccrib, называемый WGM12 (в некоторых моделях Mega он носит другое название — ctci), необходимо устанавливать в 1 — это означает, что по достижении записанного в регистрах сравнения числа таймер обнулится и начнет отсчет заново. В противном случае переключение вывода произойдет, но таймер будет считать до заполнения регистра и далее опять до заданного числа — факти- чески прерывания просто сдвинутся по фазе относительно прерываний по перепол- нению, но будут происходить с той же частотой. Установка бита comiao в регистре tccria задает режим работы вывода ОС1А. Этот вывод может переключаться в заданное состояние каждый раз, когда таймер дости- гает предустановленного значения в регистре сравнения ocriah:OCRial. Установка битов comiai : comiao (биты 7 и б в регистре tccria) в состояние 01 означает, что этот вывод (если он сконфигурирован на выход) будет автоматически переключаться каждый период «туда-обратно», формируя меандр. Тогда к выводу ОС1А можно подключить, например, светодиод, который в этом случае будет загораться или гаснуть раз в секунду (обратите внимание, что в предыдущем случае с использова- нием простейшего TimerO нам приходилось вывод переключать «вручную»). При- чем работа этого вывода не зависит от того, задействовано ли соответствующее прерывание для обработки таких событий или нет. Иными словами, просто для получения частоты на одном выводе ничего больше делать не требуется. Оформите текст в виде законченной программы, не забыв оп- ределить temp и вставить в конец бесконечный цикл (см. текст программы timerMc.asm в архиве по адресу, указанному во введении), и загрузите ее в контрол- лер. Если это модель, отличная от Mega8, раскомментируйте или заново впишите соответствующие строки инициализации вывода ОС1А и замените наименование INC-файла. Вся программа займет 22 байта, причем она легко адаптируется под иное значение частоты на выходе ОС 1. Еще раз обратим внимание, что переключение светодиода на выводе происходит каждое совпадение, т. е. период собственно мигания вдвое больше секунды, а частота фактически равна 0,5 Гц, а не 1 Гц. Промежуток между сравнениями (длительность импульса) ровно в 1 секунду нам понадобится далее, когда мы будем задействовать прерывание. Если же нам требуется именно частота 1 Гц на выводе, а не промежуток между сравнениями в 1 секунду, то нужно заме- нить загружаемое в регистры сравнения число на вдвое меньшее — 31 249. Изменение частоты возможно как с помощью изменения числа сравнения, так и с помощью установки иного коэффициента деления входной частоты таймера. При той же тактовой частоте 4 МГц, установив максимально возможный коэффициент 7ю245 вы можете получить максимальную длительность импульса более 16 секунд, а установив все в минимум (коэффициент деления 1:1 и число сравнения = 1) ми- нимальную длительность импульса в 0,5 мкс (частоту 1 МГц). В последнем случае можно еще увеличить частоту вдвое, если загружать в регистр сравнения нулевое
Глава 9. Программирование таймеров 233 значение, но в таком режиме таймеры, естественно, эксплуатировать бессмыс- ленно. Подробности Следует учитывать, что число тактов (таймера, а не контроллера), которое отсчитает таймер до наступления события сравнения, на единицу больше заданного в регистрах сравнения числа. Это хорошо иллюстрируется последним примером — загрузив 1, вы считаете до 2-х (первый такт: счетный регистр = 0, второй: счетный регистр = регистру сравнения = 1). Поэтому и надо задавать число сравнения на единицу меньше необ- ходимого числа отсчетов. Для величин в регистре сравнения порядка десятков тысяч, задаваемых в рассмотренном только что примере, ошибка будет сравнима с погреш- ностью кварцевого резонатора, потому там это можно и не учитывать (см. далее разд. «Точная коррекция времени»). Подчеркнем: загружая в регистры сравнения какое-либо число, вы задаете именно длительность импульса на выходе ОС1А, а не частоту переключения. Так, в послед- нем случае, загружая 1, вы считаете до двух и таким образом увеличиваете период исходной частоты (0,25 мкс) вдвое, получая длительность импульса 0,5 мкс. По- скольку период частоты складывается из двух длительностей, то и выходит частота не 2, а 1 МГц. Это нужно учитывать при подсчетах — если вызывать прерывание сравне- ния (как показано далее), то частота его возникновения будет обратной величиной именно длительности (в нашем случае 2 МГц), а если рассматривать все это как гене- ратор частоты на выводе, то частота определяется периодом переключения и будет равна 1 МГц. (Конечно, прерывания, возникающие с частотой 2 МГц, при 4-х МГц так- товой частоты на практике использовать невозможно, потому это уточнение приведе- но просто в качестве иллюстрации). Однако применение вывода ОС1А в автоматическом режиме решает лишь одну достаточно узкую задачу генерации частоты для внешнего потребителя. Для того чтобы выполнять еще какие-либо действия, необходимо разрешить соответствую- щее прерывание, которое в таком случае носит название timicompa (Timer 1 Compare Match А). Это можно сделать установкой бита ocieia в регистре timsk: ldi temp, (1«OCIE1A) ;разрешение прерывания TIM1_COMPA out TIMSK,temp Теперь сформулируем задачу создания электронных часов. Мы имеем прерывание timicompa, возникающее раз в секунду (для этого, еще раз напоминаю, в регистр сравнения загружается значение 62 499). Для отсчета минут достаточно создать от- дельный счетчик до 59, а часов — еще один до 23. Однако значение времени обыч- но представляют в BCD-формате (причем для индикации нужен распакованный формат). Тут напрашиваются два варианта: или при необходимости вызывать каж- дый раз процедуру преобразования hex-значения в BCD (она описана в главе 8\ или усложнить процедуру подсчета времени, заранее разместив единицы и десятки часов, минут и секунд в отдельных регистрах. Первый способ целесообразнее в тех программах, где счет времени— не основная функция, и наблюдается дефицит свободных регистров, — тогда для подсчета времени потребуются всего три гло- бальные переменные: для часов, минут и секунд, а распакованные значения можно хранить в SRAM. Второй способ проще (хотя сама процедура подсчета и сложнее), и его следует применять тогда, когда функция подсчета времени— основная, и других громоздких действий (кроме, возможно, индикации, на которой мы оста- новимся далее) МК не производит.
234 Часть III. Практическое программирование микроконтроллеров AVR Теперь предположим, что значения секунд индицироваться не будут (их заменит «мигалка» по выводу ОС1А), так что для них достаточно одного глобального счет- чика, а единицы-десятки минут и часов мы будем располагать в отдельных пере- менных. В листинге 9.4 показано, как можно реализовать этот второй способ — по прерыванию timicompa (разумеется, оно должно быть не только разрешено, но и в соответствующем месте таблицы прерываний должен стоять переход rjmp timicompa). .def temp = rl7 ;рабочая переменная .def sek = rl8 ;счетчик секунд .def emin = rl9 /единицы минут .def dmin = r20 /десятки минут .def ehh = r21 /единицы часов .def dhh = r22 /десятки часов TIM1_COMPA: /прерывание по сравнению 1 сек inc sek /увеличиваем число секунд на 1 cpi sek,60 /сравнить со значением 60 brne Texit /если не равно, на выход clr sek /если уже 60, очистить секунды inc emin /и увеличить ед. минут cpi emin,10 /сравнить ед. минут с числом 10 brne Texit /если еще не равно, на выход clr emin /иначе очистить ед. минут inc dmin /увеличить дес. минут cpi dmin,б /сравнить с числом б brne Texit /если еще не равно, на выход clr dmin /иначе очистить дес. минут inc ehh /увеличить ед. часов cpi ehh,4 /сравнить ед. часов с числом 4 brio Texit /если меньше, на выход cpi dhh,2 /иначе сравнить дес. часов с числом 2 brne mhh /если не равно 2, то на метку mhh clr ehh /если равно, то очистить ед. часов clr dhh /и десятки часов rjmp Texit /и на выход mhh: /если дес. часов меньше 2, то cpi ehh,10 /сравнить ед. часов с числом 10 brne Texit /если меньше 10, то на выход clr ehh /иначе очистить ед. часов inc dhh /увеличить дес. часов Texit: reti
Глава 9. Программирование таймеров 235 Разумеется, чтобы этот алгоритм правильно заработал с самого начала, в секции Reset требуется предварительно обнулить все регистры отсчета времени. Затем придется установить в них реальные значения текущего времени (число минут не должно быть больше 59, часов — больше 23 и т. д.). Последнее можно делать вруч- ную — как обычно на часах, с помощью нажатия кнопок. Расписывать такой алго- ритм в силу его громоздкости полностью мы здесь не станем. Для более удобной установки и коррекции времени применяются различные автоматизированные ме- тоды, на которых мы остановимся позже (см. главу 15). Вернемся к подсчету времени. Другой (точнее, первый из упомянутых) метод под- счета времени состоит в том, что счет времени мы ведем в обычной шестнадцате- ричной форме, а значения разрядов в BCD-формате храним в SRAM. Причем могут понадобиться значения времени как в распакованном виде (для индикации), так и в упакованном (для обмена с «внешним миром», — например, для установки вре- мени в отдельной микросхеме часов реального времени, RTC). Так что реализацию этого способа мы начнем с резервирования памяти под все указанные нужды. При этом для удобства положим старший байт адреса SRAM навсегда равным единице, что равносильно использованию адресов, начиная с 256-й ячейки, и страхует нас от случайного обращения к области регистров (для моделей вроде ATtiny2313, где объем SRAM менее 256 байтов, адреса придется менять, — об этом рассказано далее, в конце главы). Такой фрагмент программы представлен в листинге 9.5. /SRAM старший байт адреса SRAM = 0x01 .equ Sek = 00 ;текущие секунды в упакованном формате .equ Min =01 ;текущие минуты в упакованном формате .equ Hour = 02 ;текущие часы в упакованном формате ;распакованные .equ DdH = 0x06 .equ DeH = 0x07 .equ DdM = 0x08 .equ DeM = 0x09 .def temp = rl6 .def tempi = rl7 .def cSek = rl8 .def cMin = rl9 часы старший BCD часы младший BCD минуты старший BCD минуты младший BCD рабочая переменная /рабочая переменная счетчик секунд счетчик минут .def cHour = r20 ; счетчик часов Т1М1_СОМРА: /прерывание по сравнению 1 сек ldi ZH,0x01 /старший адреса SRAM для сохранения в памяти inc cSek /увеличиваем число секунд на 1 cpi cSek,60 /сравнить со значением 60 brne Tsek_exit /если не равно, на запись в память
236 Часть III. Практическое программирование микроконтроллеров AVR clr cSek /очистить секунды inc cMin /увеличиваем число минут на 1 cpi cMin,60 /сравнить со значением 60 brne Tmin_exit /если не равно, на запись в память clr cMin /очистить минуты inc cHour /увеличиваем число часов на 1 cpi cHour,24 /сравнить со значением 24 brne Thour_exit /если не равно, на запись в память clr cHour /очистить часы / запись новых упакованных значений: Thour_exit: mov temp,cHour /часы rcall bin2bcd8 /на выходе в tempi десятки, в temp единицы час ldi ZL,DdH /адрес десятков часов st Z+,tempi /сохраняем десятки st Z,temp /сохраняем единицы swap tempi /десятки в старшую тетраду add temp,tempi /упакованные часы ldi ZL,Hour /адрес упаков. часов st Z,temp /сохраняем часы Tmin_exit: mov temp,cMin /минуты rcall bin2bcd8 /на выходе в tempi десятки, в temp единицы мин ldi ZL,DdM /адрес десятков минут st Z+,tempi /сохраняем десятки st Z,temp /сохраняем единицы swap tempi /десятки в старшую тетраду add temp,tempi /упакованные минуты ldi ZL,Min /адрес упаков. минут st Z,temp /сохраняем минуты Tsek_exit: mov temp,cSek /секунды rcall bin2bcd8 /на выходе в tempi десятки, в temp единицы сек swap tempi /десятки в старшую тетраду add temp,tempi /упакованные секунды ldi ZL,Sek /адрес упаков. секунд st Z,temp /сохраняем секунды reti По этому алгоритму все три величины будут сохраняться в памяти каждый час, ми- нуты и секунды — каждую минуту, только секунды — каждую секунду. Как види- те, алгоритм получился даже длиннее предыдущего, зато тут мы имеем три значе- ния времени, готовые к употреблению каждое в своей области: с обычными hex- значениями легко производить всякие арифметические и логические операции (на- пример, сравнивать моменты времени между собой), упакованные значения можно посылать во «внешний мир», а распакованные использовать для вывода на индика- торы в независимом от подсчета времени цикле (чем мы и займемся чуть позже).
Глава 9. Программирование таймеров 237 Заметки на полях Следует обратить внимание, что в МК с часовыми функциями отдельный подсчет времени обычно ведется даже тогда, когда в схеме присутствуют автономные часы реального времени, RTC (ими мы займемся в главе 13). Вроде бы в таком случае де- лать это незачем — RTC все равно считают «внутри себя», и притом не только время, но и дату, и день недели. Однако обмен данными с RTC каждую секунду займет вре- мени значительно больше, чем собственный подсчет. Из-за этого RTC целесообразно использовать, как генератор внешних прерываний, по которым и происходит подсчет времени (вместо прерываний таймера, как у нас), а синхронизировать значения можно один раз при включении системы (RTC, как правило, имеют собственную резервную батарейку) и можно еще периодически для подстройки (это при условии, что выбран- ные часы точнее нашего кварца, а то может оказаться и наоборот). Иное дело, если требуются дни недели-месяцы-годы — ведь алгоритм календаря весьма громоздкий, сложен в отладке и организовывать его самостоятельно замучаетесь. Но учтите, что тогда придется менять и все решение полностью: очевидно, семисегментные индика- торы (о которых далее в этой главе) с этим удовлетворительно не справятся. О других решениях мы также поговорим в главе 16. В «больших» компьютерах реализован еще более простой алгоритм взаимодействия с RTC. Системное время Windows хранится в длинном многоразрядном счетчике и преобразуется в реальные значения даты-времени каждой программой самостоятель- но по готовым функциям, а синхронизация с RTC происходит один раз при включении питания. RTC также могут там выполнять функцию «будильника» для системы. Точная коррекция времени Остановимся на вопросе о том, как можно точно корректировать ход таких часов. Относительно грубую подстройку (чтобы устранить основную часть ошибки из-за погрешности частоты «кварца») можно осуществить подгонкой коэффициента, за- писываемого в регистры сравнения. Например, если при номинальном значении 62 500 часы отстают на 10 с в сутки, то это означает, что частота прерываний тай- мера на 1О/8б4оо= И,7105 долей меньше, чем необходимо (86 400— число секунд в сутках). Чтобы ускорить ход, надо увеличить частоту, т. е. уменьшить секундный интервал на эту величину. Для этого нужно записать в регистр сравнения число 62500 - 62500-11,6-10"5 « 62493. Как легко подсчитать, подстройка получается дос- таточно грубая — изменение коэффициента на единицу дает изменение хода часов на «1,4 с в сутки, т. е. примерно на 40 с в месяц. Если же требуется более точная подстройка, то решать проблему путем еще каких- то программных манипуляций нецелесообразно. Увеличением частоты прерываний таймера и формированием секундного интервала с помощью дополнительного регистра, как мы это делали в простейшем примере в главе 6, не получится — «раз- решающая способность» наших манипуляций в любом случае ограничена 16-раз- рядным числом в регистрах сравнения. Можно отказаться от удобного способа формирования секундного интервала с помощью сравнения и вести его примитив- ным методом подсчета достаточно большого числа. Но на практике же это мало чего даст: если ввести в регистр сравнения число 1, то таймер запускать все равно придется с частотой не более 7б4 от тактовой. Если прерывания будут происходить чаще, чем каждые 64 такта, то, скорее всего, часть прерываний «потеряется» — нам
238 Часть III. Практическое программирование микроконтроллеров AVR ведь требуется еще и выполнять какие-то действия, а их нужно успеть совершить в промежутке между прерываниями (поэтому и увеличение тактовой частоты не поможет). Есть также довольно-таки сложный способ подгонки значений времени путем до- бавления рассчитанных по величинам фактического убегания часов программных задержек в определенное время (ежесуточно, например). Но все такие «костыли» сложны в реализации, отвлекают от главного и, главное, с легкостью при необхо- димости могут быть заменены аппаратными усовершенствованиями. Для этого достаточно завести контроллер от внешнего точного генератора. Фирма Epson выпускает такие генераторы с точностью от ± 25 ррм (миллионных долей) в диапазоне температур -20 -s- 70 °С. Продукция китайских производителей не- сколько похуже, но тоже подтягивается к этим величинам1. Генераторы эти не смертельно дороги, так что в критичных случаях их можно использовать вместо обычного кварца в тактировании контроллера. Это равно касается и обсуждаемых часовых функций, и применения МК в качестве периодомера и частотомера (см. далее), и просто как генератора частоты или импульсов заданной длительно- сти. Точности атомных часов вы, разумеется, таким способом не достигнете (и МК для подобного уровня решений вообще мало приспособлены), но огромное боль- шинство задач даже из области научного эксперимента вполне решаются. А что касается внешних RTC, то вопросы их погрешности мы обсудим в главе 13. Частотомер и периодомер Частота может измеряться, как известно, двумя способами: либо подсчетом числа импульсов измеряемой частоты за определенный промежуток времени, либо, на- оборот, подсчетом числа импульсов известной частоты за период (или несколько периодов) измеряемого сигнала. В первом случае мы получаем именно значение частоты (если промежуток времени равен 1 с, то сразу в герцах), а во втором — об- ратную величину, значение периода. Первый способ удобнее для измерения высо- ких частот, второй — низких. Заметим, что в Arduino операции, аналогичные функциям частотомера и периодо- мера, отсутствуют, имеется только измерение длительности импульса в виде функ- ции puiseino. Если вам любопытно узнать, как к задачам измерения периодов и частот можно пристроить Arduino, то можете ознакомиться со статьей автора [15]. С помощью контроллеров МК частоту можно измерять несколькими путями. И сначала мы займемся методом прямого измерения (по способу частотомера) дос- таточно высокой частоты, причем с подстройкой измерительного интервала для получения более точного результата прямо в физических величинах — герцах. 1 Поистине безбрежный выбор таких генераторов разных фирм имеется в интернет-магазине «Electronic component» (electronic-component.org), а также в магазине «Кварц» (quartzl.com).
Глава 9. Программирование таймеров 239 Частотомер Предположим, измеряемая частота находится в диапазоне около 4 МГц, и нам же- лательно измерить ее с разрешением до 1 Гц. Прежде всего, напомним, что такто- вая частота контроллера должна превышать измеряемую не менее, чем в два раза, — таково требование руководств. Обнаружение изменения внешнего сигнала производится по фронту тактового, и если период измеряемого сигнала слишком короткий, то в регистрации могут быть пропуски. Так что нам следует ориентиро- ваться на МК с тактовой частотой не менее 8 МГц. Выберем опять ATmega8 (при необходимости легко модифицировать алгоритм для любого AVR) с кварцевым резонатором 8 МГц. Для измерения мы задействуем два таймера: один 16-раз- рядный Timer 1 — для отсчета собственно внешней частоты и второй 8-разрядный TimerO — для отсчета измерительного интервала. Заметки на полях Регистрация перепада уровней внешнего сигнала производится автономной асин- хронной схемой. Потому из руководства не следует, что тактовая частота должна быть выше измеряемой именно в два раза, — достаточно простого превышения. Однако окончательное суждение по этому вопросу я оставляю на усмотрение читателей — изложение в руководстве не очень толковое (не до конца прояснен вопрос с задерж- ками регистрации фронта сигнала), и, конечно, лучше «на всякий случай» следовать фирменным рекомендациям. Результат измерения частоты до 4 МГц с точностью до герца в общем случае займет не менее трех 8-битовых регистров. Но реальное их число, которое требуется задей- ствовать, будет зависеть от диапазона изменения измеряемой частоты. В самом деле -— предположим, что частота может меняться не более, чем на 256 Гц относи- тельно номинальной величины 3 МГц. Тогда старшие два регистра всегда будут пока- зывать одно и то же число (и точно известно, какое), а все изменения будут регистри- роваться только в самом младшем регистре самого таймера. Если же частота 3 МГц не меняется более чем на 65 кГц, то можно оставить только два регистра (тоже собст- венные счетчики таймера). Важно только, чтобы в процессе изменений частота не переваливала за границу, когда старший регистр тоже должен меняться (что в нашем случае произойдет, например, если средняя частота колеблется около значения 22 = 2 097 152 Гц), иначе возникнет неоднозначность (которую, впрочем, также в не- которых случаях можно учесть). Иногда (например, при измерении частоты термочув- ствительных «кварцев», вариации которой невелики) эти соображения позволяют экономить регистры. Здесь мы будем рассматривать общий случай и «тупо» предпо- ложим, что частота в пределах емкости трех регистров может быть любой (т. е. с большим запасом — до 16,7 МГц). Для измерения нам потребуется ввести прерывание Timer! по переполнению, в ко- тором третий регистр (назовем его count3) будет всякий раз увеличиваться на еди- ницу. Входной сигнал подадим на вход внешнего тактового сигнала Т1 (вывод 11 для ATmega8), с которого внешние импульсы поступают прямо на счетчик таймера, если ему задать соответствующий режим. Теперь разберемся с формированием измерительного интервала. При 8 МГц такто- вой частоты и коэффициенте предделителя для TimerO, равном V256? прерывания переполнения будут происходить с частотой 122,07 Гц. Нам же требуется 1 с (1 Гц), потому мы введем счетчик (countsek) и станем его с каждым прерыванием увели- чивать, пока он не отсчитает ровно 122 таких прерывания. После этого можно фик-
240 Часть III. Практическое программирование микроконтроллеров AVR сировать число импульсов, сосчитанное к тому времени в регистрах Timer 1. Но если кварцевый резонатор идеально точный, то секунда получится чуть меньше настоящей (неучтенные 0,07 Гц дадут ошибку 576 мкс в сторону уменьшения). Для компенсации этой недостачи перед чтением значений мы введем задержку, с по- мощью которой наш частотомер можно еще дополнительно калибровать (т. е. учесть исходную неточность кварца). В начале и в конце интервала будем пере- ключать разряд 6 порта D (вывод 12), чтобы контролировать измерительный интер- вал в процессе калибровки. На рис. 9.1 представлен МК ATmega8 с обозначением выводов для нашей цели. _ - - - - - - 1 II г Ml 1 8 МГц | | h~ll < " г 1 II измеряемая i частота 0-4 МГц 1— - - 1 Reset 2RXD 3TXD 4 INTO 5INT1 6PD4 7Vcc 8GND 9 XTAL1 10XTAL2 11 PD5(T1) 12PD6 13PD7 PCS 28 PC4 27 PC3 26 PC2 25 PC124 PC0 23 GND22 AREF 21 AVCC 20 SCK19 MISO 18 MOS117 PB216 14 PB0(ICP1) (OC1A) PB1 15 ATmega8 г J L контроль интервала Рис. 9.1. Подключение ATmega8 для измерения частоты Листинг 9.6 содержит фрагмент кода программы частотомера, относящийся к ини- циализации таймеров (определение регистров я опускаю, в нашем случае их потре- буется всего три: temp, count3 и countsek). В секции прерываний введем прерыва- ния TimerO и Timerl по переполнению (по меткам: timoovf и timiovf). Инициали- зация таймеров в секции начальной загрузки сводится к разрешению прерываний и запуску, но обязательно здесь же нужно очистить счетные регистры таймеров и все дополнительные регистры, а также предцелитель TimerO и Timerl, и только потом запускать TimerO (для ATtiny2313 почему-то регистр управления предцелителем sfior решили переименовать в gtccr, но в «правильном» INC-файле присутствуют оба эти названия как синонимы). ldi temp, (l«TOIE0) | (1«TOIE1) out TIMSK,temp ;разр: прер. TimerO и Timerl
Глава 9. Программирование таймеров 241 clr temp out TCNTlH,temp out TCNTlL,temp /очищаем Timerl out TCNTO,temp ;очищаем TimerO ldi temp,(1« PSR10);для очистки предделителя надо записать лог. 1 out SFIOR,temp ;в бит PSR10 регистра SFIOR (GTCCR) clr count3 clr count_sek /очищаем счетчик прерываний ldi temp,0b00000100; out TCCROB,temp;запускаем TimerO div 1:256 ldi temp,ObOOOOO111/внешний сигнал Tl (вью. 9) по фронту out TCCRlB,temp /запускаем Timerl sei Листинг 9.7 иллюстрирует прерывание Timerl, которое состоит из единственной команды. TIM1_OVF: inc count3 reti Самое главное прерывание TimerO показано в листинге 9.8. TIM0_OVF: /переполнение TimerO 122,07 Гц inc count_sek cpi count_sek,122 /получаем 0.999424 с breq corr_l ;если секунда прошла, то на коррекцию счета reti ;иначе на выход согг_1: ;1 сек + коррекция clr temp out TCCR0,temp /останавливаем TimerO ;==== задержка на 600 мкс для коррекции интервала ldi ZH,high(1200) ;8 МГЦ, 4 такта по 125 не, 600 мкс = 1200 тактов ldi ZL,low(1200) loop: sbiw ZL,1 ;2 такта brne loop ;2 такта cbi PortD,б ;выключение контр, выв 12 PD6 длит. 1 с ;=== конец измерения clr temp out TCCRlB,temp /останавливаем Timerl /читаем данные in temp,TCNTlL <пишем младший байт в память>
242 Часть III. Практическое программирование микроконтроллеров AVR in temp,TCNTlH <пишем второй байт в память> <пишем старший байт count3 в память> ;=== очищаем все регистры clr temp out TCNT1H,temp out TCNTlL,temp ;очищаем Timerl out TCNTO,temp /очищаем TimerO clr count3 clr count_sek ;очищаем счетчик прерываний ;=== запускаем таймеры опять ldi temp,0b00000100; out TCCRO,temp /запускаем TimerO div 1:256 ldi temp,0b00000111 /внешний сигнал Tl (выв. 11) по фронту out TCCRlB,temp /запускаем Timerl sbi PortD,6 /включение контр, выв 12 PD6 длит. 1 с reti Здесь TimerO отсчитывает ровно 1 секунду, за это время Timerl считает все фрон- ты, поступающие на вход Т1. После остановки обоих таймеров мы сохраняем ре- зультаты и запускаем таймеры на измерение опять. Обратите внимание, что в нача- ле работы (в секции Reset) нужно запустить оба таймера, иначе первое измерение будет ошибочным, причем сделать это нужно после очистки регистров. Запись данных в память не расшифровывается, потому что это может быть и запись в SRAM с последующим выводом на индикацию, и запись во внешнюю энергоне- зависимую память для последующего чтения из компьютера, как описано в гла- вах 12 и 13, или одновременно и то, и другое. Простейший частотомер можно сде- лать, если организовать автоматическую передачу данных через UART в компью- тер, который и будет заниматься отображением и записью информации. Из-за инструкции sbiw, которая занимает два такта (а не один, как инструкция dec, встречавшаяся у нас в процедуре delay ранее), здесь один цикл задержки равен четырем тактам, или 0,5 мкс при тактовой частоте 8 МГц. Меняя число циклов задержки, можно откорректировать длительность секундного интервала отно- сительно номинального значения от 576 мкс в сторону уменьшения (задержка равна нулю) до целых 32-х миллисекунд в сторону увеличения (задержка равна 65 535 циклов по 0,5 мкс). Этого достаточно для подстройки стандартного кварца (в крайнем случае можно отобрать экземпляр из нескольких). Калибровка осущест- вляется измерением длительности импульса на контрольном выводе 12 МК с по- мощью точного частотомера (лучше использовать профессиональный лаборатор- ный прибор, а не мультиметр, который имеет недостаточную точность). . Периодомер В МК AVR имеется специальный режим работы таймеров с «захватом» (capture) внешнего перепада уровней и генерацией прерывания по этому поводу. Он удобен для измерения периода низких (с точки зрения МК) частот по второму способу —
Глава 9. Программирование таймеров 243^ с помощью определения периода. Этот же режим подходит для фиксации редких событий (например, прохождения частиц через счетчик Гейгера) — тогда подсчет времени между событиями пригодится, например, для статистики. Пример исполь- зования этого режима приведен также в программе работы с ультразвуковым дат- чиком в главе 16. Рассмотрим, как это можно осуществить на практике для измерения периода. В ре- жиме работы с «захватом» может функционировать 16-разрядный Timer 1. Внешний импульс следует при этом подавать на специальный вывод ICP1 (совпадающий в ATmega8 с выводом РВО, в ATtiny2313 — с выводом PD6). «Захват» фронта или спада выбирается установкой бита icesi (бит б) в регистре управления tccrib. При этом в системе «захвата» действует нечто вроде «антидребезга» — установкой бита icnci (бит 7) в том же регистре можно включить схему «подавления шума», кото- рая после прихода фронта или, соответственно, спада, 4 раза определяет уровень сигнала и принимает решение о «захвате» только в том случае, если все четыре из- мерения одинаковы. Заметим, что для Timerl всех моделей AVR источником сиг- нала «захвата» вместо вывода ICP1 может служить аналоговый компаратор (см. главу 77). Собственно событие «захвата» состоит в том, что в момент, когда оно происходит, содержимое счетных регистров Timerl переносится в специальный 16-разрядный регистр icrih:icril. Поэтому для таймера режим «захвата» специально включать не требуется (только установить режим по фронту или спаду) — ни на счет, ни на другие функции таймера он никак не влияет. Для того чтобы можно было обнару- жить, что «захват» произошел, имеется соответствующее прерывание Timerl Capture, которое разрешается установкой бита ticiei регистра timsk. При проведении точных измерений следует учесть, что между «захватом» и копи- рованием счетного регистра имеется задержка около трех тактов («захват» проис- ходит синхронно с тактовым сигналом, потому разброс может составлять один такт). Включение схемы «подавления шума» увеличивает задержку еще на четыре такта. Следует отметить, что на практике такая схема малоэффективна: «настоя- щий» дребезг длится десятки микросекунд, так что схема его не «отловит», и таким образом можно фильтровать только очень короткие «иголки», встречающиеся до- вольно-таки редко. В результате схема действий при измерении периода такова: разрешается прерыва- ние «захвата», и таймер запускается на счет с нужной частотой. Измеряемый сиг- нал подается на вывод ICP1 (у ATmega8 это вывод 14, его необходимо оставить сконфигурированным на вход). В прерывании с содержимым регистров «захвата» icrih и icril производятся нужные операции. Вопрос, однако: что при этом делать с самим таймером? Ответ зависит от необхо- димой методики измерения. Если необходимо измерение не периода (т. е. проме- жутка времени между фронтами или между спадами), а интервала времени (т. е. промежутка времени между фронтом и спадом или между спадом и фронтом, что более частая задача), в прерывании «захвата» нужно переключать активный уро- вень с помощью установки или сброса бита icesi в регистре tccrib. Тогда упро- щаются также и последующие действия— мы имеем запас времени до прихода
244 Часть III. Практическое программирование микроконтроллеров AVR следующего фронта/спада, и за это время можем совершить достаточно много опе- раций (в предположении, что частота внешних событий невелика). В этом случае порядок действий такой (предположим, что мы «ловим» положи- тельные импульсы): сначала устанавливаем «захват» по фронту (установкой бита icesi), разрешаем прерывание «захвата», но таймер не запускаем, только обнуляем его счетные регистры. В наступившем прерывании немедленно запускаем таймер и переключаем «захват» на реакцию по спаду (сбросом бита icesi). В следующем прерывании фиксируем содержимое регистра захвата icrih:icril, производим с ним необходимые действия, останавливаем таймер, очищаем его и переключаем опять «захват» на реакцию по фронту. При измерении именно периода переключать фронт/спад не нужно, а вот регистры таймера очищать все равно требуется. Поскольку кроме задержки собственно «за- хвата» имеется еще задержка возникновения прерывания (6-7 тактов, в зависимо- сти от модели), и на выполнение команд очистки также необходимо время (не ме- нее двух тактов на каждую), то общая ошибка может составлять около 12 тактов, что при точных измерениях бывает недопустимо. Эту ошибку можно учесть (про- сто прибавляя 12 тактов к числу, зафиксированному в счетчике таймера), а для пе- риодических сигналов поступить более радикально: проводить измерения лишь каждый второй период. Разумеется, если частота заполнения счетчика Vg от такто- вой и ниже, то эта ошибка становится незначимой (чтобы ее снизить дополнитель- но, следует использовать возможность очистки регистров предделителя, имею- щуюся в большинстве Mega). Листинг 9.9 иллюстрирует процесс измерения периода в простейшем случае «лов- ли» редких событий без учета всех этих нюансов («подавление шума» также не включаем — редкие события могут проявлять себя достаточно короткими импуль- сами). Листинг SJ RESET: ldi temp, (1« TICIE1) | (1«ТО1Е1) ;разр. прер. "захвата" и /переполнения Timerl out TIMSK,temp clr temp out TCNTlH,temp out TCNTlL,temp ;очищаем Timerl clr countO ;регистры для сохранения числа в счетчике clr countl clr count2 /старший разряд счетчика ldi temp,0b01000010; "захват" по фронту, частота 1/8 ldi temp,(1« PSR10);для очистки предделителя надо записать лог. 1 out SFIOR,temp ;в бит PSR10 регистра SFIOR (GTCCR) out TCCRlB,temp /запускаем Timerl sei
Глава 9. Программирование таймеров 245 Прерывание переполнения Timer 1 будет таким же простым, как и ранее (лис- тинг 9.10) TIM1OVF: inc count2 reti И прерывание «захвата» timicapt (листинг 9.11) будет на первый взгляд выглядеть даже проще, чем обработчик переполнения TimerO в предыдущем случае: Т1М1_САРТ: clr temp out TCNT1H,temp out TCNT1L, temp /первым делом очищаем Timerl in countO,ICR1L /младший счетчика in countl,ICRlH /старший счетчика <что-то делаем с числом count2:count1:count0> reti Вся громоздкость будет заключаться в процедуре, обозначенной в листинге 9.11 как «что-то делаем...». Иногда и делать почти ничего не требуется — если стоит задача измерения периода или временного интервала, то по этому алгоритму мы сразу получим время в микросекундах (при частоте «кварца» 8 МГц и делителе 1:8 частота счета будет ровно 1 МГц). Его можно временно сохранить в SRAM, а в промежутках между прерываниями, например, посылать «наружу» по UART или записывать во внешнюю память. Если же наша задача, как и ранее, измерение частоты внешних событий, то нам придется ее вычислять: делить некое заданное число (входную частоту счетчика) на измеренный период. В общем случае это выльется в одну из модификаций про- цедур деления многобайтовых чисел, описанных в главе 8, Но это еще не все. Если тактовая частота, как и ранее, 8 МГц, то на входе таймера будет 1 МГц, и для полу- чения частоты в герцах на полученный интервал придется делить число 106. При измеряемой частоте порядка единиц герц в результате деления получатся числа около единицы с большим и очень информативным «хвостом» после запятой. От- бросить этот «хвост» нельзя — разрешающая способность метода будет такова, что не стоило и затевать все эти «захваты», а можно было просто ограничиться обыч- ным подсчетом частоты по способу частотомера. Как же вывернуться? Для этого нужно сдвинуть запятую (десятичную! — прием с манипулированием двоичными числами здесь не проходит) вправо, т. е. осущест- вить дополнительное умножение результата, например, на 1000, чтобы получить три дополнительные значащие цифры после запятой при диапазоне в единицы герц
246 Часть III. Практическое программирование микроконтроллеров AVR (саму запятую на индикаторах придется поставить принудительно). Чтобы опять не возиться с «плавающей» арифметикой, следует, как мы и делали в главе 7, заранее умножить делимое на нужный коэффициент, — т. е. мы будем делить на получен- ный период число 109, а не 106. В общем-то, это не так уж и страшно (109 займет четыре байта: $звэасаоо — это число нужно делить на трехбайтовый счетчик). При указанных параметрах минимальная измеряемая частота может составить одну тысячную герца. Закончим на этом с использованием таймеров по прямому назначению — т. е. для измерения времени, и остановимся на других их применениях. Управление динамической индикацией Типов индикаторов существует великое множество (по сути компьютерный дис- плей — тоже индикатор). Мы пока не станем углубляться в тонкости управления ЖК-индикаторами — обычно такие дисплеи (и семисегментные и матричные гра- фические) используются совместно со встроенным контроллером, и мы рассмотрим их применение в ассемблерных программах позже (в главах 13 и 76). Здесь мы остановимся на самой простой разновидности — семисегментных цифровых инди- каторах. Заметки на полях Отметим, что прямого аналога такой функциональности в Arduino нет — там попросту маловато выводов для управления хотя бы четырьмя семисегментными индикаторами напрямую, а программа получается сложная и объемная. Никто, конечно, не мешает вам самостоятельно организовать подключение, аналогичное описываемому далее, но вы быстро убедитесь, что это неудобно. Поэтому семисегментный индикатор там обычно используется в единственном числе лишь в качестве учебного примера, а для индикации нескольких цифр применяют либо готовые семисегментные индикаторные модули, которые управляются через последовательные порты, либо дисплеи других типов со встроенным контроллером. Ассемблерные решения для таких случаев пока- заны в главах 13 и 16. LED-индикаторы и их подключение Здесь и далее мы будем рассматривать обычные светодиодные (LED) семисегмент- ные индикаторы (рис. 9.2 и 9.3), в которых управление сегментами осуществляется напрямую, каждым по отдельности. Существуют разновидности с общим анодом (когда вывод положительного питания общий, а зажигаются сегменты коммутаци- ей их к «земле» через резистор) и с общим катодом, где все наоборот (общий — отрицательный вывод). Многоразрядные индикаторы можно собирать из отдель- ных разрядов (таких, как показаны на рис. 9.3), устанавливаемых на плате вплот- ную друг к другу. Также выпускают индикаторы с несколькими разрядами в сбо- ре — сдвоенные, строенные и изредка счетверенные, в которых отдельные разряды управляются по питанию независимо. Индикаторы с большим числом разрядов в сборе обычно также содержат встроенные контроллеры, и мы на них здесь оста- навливаться не станем.
Глава 9. Программирование таймеров 247 DEC 0 1 2 3 4 5 6 7 8 9 BIN 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 а 1 0 1 1 0 1 1 1 1 1 b 1 1 1 1 1 0 0 1 1 1 с 1 1 0 1 1 1 1 1 1 1 d 1 0 1 1 0 1 1 0 1 1 е 1 0 1 0 0 0 1 0 1 0 f 1 0 0 0 1 1 1 0 1 1 9 0 0 1 1 1 1 1 0 1 1 Рис. 9.2. Обозначение выводов сегментов семисегментных индикаторов и таблицы формирования цифр Рис. 9.3. Внешний вид семисегментного индикатора В применении LED-индикаторов тоже есть свои схемотехнические тонкости — па- дение напряжения на светодиодах составляет от 1,8 до 2,5 В. Если подключать их напрямую к выводу порта, то к этому нужно еще прибавить падение напряжения на выходном сопротивлении порта (порядка 100 Ом), при этом еще необходимо учесть, что суммарный ток через порты ограничен величиной порядка 200 мА (см. приложение 2), так что много индикаторов непосредственно к МК не подклю- чишь. Если подключать их через транзисторные ключи, как мы будем делать да- лее, — придется учесть падение напряжения на ключах (не менее 0,5-1 В). Причем, в случае рассматриваемой далее динамической индикации, эти падения напряжения нужно удваивать, т. к. там присутствуют два транзисторных ключа, включенные последовательно. Потому применять в схемах с питанием 3 В достаточно пробле- матично даже индикаторы малого размера, а большие (с размером цифры 18 мм и более) обычно в каждом сегменте имеют по два включенных последовательно све- тодиода, и их сложно подключать и к питанию 5 В — светиться сегменты при про- стом подключении еще будут, а вот управлять ими в схемах с динамической инди- кацией через ключи, имеющие собственное падение напряжения, практически не- возможно. По этой причине схема подключения светодиодных индикаторов к МК обычно ока- зывается более громоздкой, чем ЖК-индикаторов с контроллерами, зато выглядят они гораздо красивее и лучше видны, чем «слепые» жидкокристаллические (учтите еще значительное разнообразие LED-индикаторов по цвету). Альтернатив такому простейшему решению с динамическим управлением семисегментными индикато- рами не так уж и много: либо готовые четырехразрядные модули с управлением
248 Часть III. Практическое программирование микроконтроллеров AVR сдвиговым регистром или микросхемой ТМ1637, с весьма ограниченным выбором по внешнему виду (см. врезку «Подробности» далее), либо самостоятельное реше- ние на тех же управляющих элементах. Прежде чем перейти к практической схеме, рассмотрим идею динамической инди- кации. Заключается она в том, что в каждый момент времени питание подается только на один из разрядов, и одновременно на выводах сегментов, которые для всех разрядов объединены вместе, формируется нужный код цифры. В следующем такте питание подается на следующий разряд, а код сегментов синхронно меняется. Динамическая индикация выгодна при числе разрядов более двух. Например, при четырех разрядах непосредственное (статическое) управление индикацией (когда всеми сегментами всех разрядов управляют совершенно независимо и асинхронно) потребовало бы 28 линий управления, не считая разделительной точки, а динами- ческое — всего 11 (семь — управления сегментами и четыре — разрядами). Пример схемы подключения четырех разрядов (часы, минуты и разделительное двоеточие) в расчете на динамический режим приведен на рис. 9.4. Здесь выбраны индикаторы с общим анодом, потому управление разрядами должно производиться в положительной логике (подача высокого уровня зажигает разряд), а управление сегментами — в отрицательной (подача низкого уровня — через резистор — зажи- гает сегмент). В схеме, приведенной на рис. 9.4, показано подключение больших индикаторов желтого свечения SA10-21Y с высотой цифры равной дюйму, на сегментах которых 3,8 3,8 Н1 ОА О LJ a bcde fg ОА Н2 О и a b с d e f g 4 2 19 10 VD1.VD2 L132Y 3,8 3,8 6 4 2 1 9 10 H1-H4SA10-21Y ОА НЗ П D a bcde fg ОА H4 П D a bcde fg 4 2 19 10 6 4 2 1 9 10 Х1 (PLD-16) I.' i.i,г Рис. 9.4. Схема индикации для часов
Глава 9. Программирование таймеров 249 падение напряжения достигает 4-4,5 В. Если учесть еще падение напряжения на транзисторных ключах (порядка 2 В), то напряжение питания всей схемы должно быть не менее 7-8 В, а лучше — больше. В нашем случае мы выберем 12 В, на ко- торые будут рассчитаны токоограничительные резисторы в схеме управления (см. далее). Отметим, что динамическая индикация не допускает пульсирующего питания индикаторов выпрямленным сетевым напряжением 50 Гц, что нормально для типовых схем на распространенном АЦП 572ПВ2, где управление индикацией статическое. Здесь напряжение питания может быть нестабилизированным, прямо с обмотки трансформатора через выпрямитель, но должно сглаживаться фильтром с конденсаторами достаточной емкости, иначе индикаторы неизбежно будут мер- цать из-за возникновения биений между частотой сети и частотой переключения разрядов. Пример схемы управления таким индикатором показан на рис. 9.5. Она сделана на этот раз на основе ATmega8535 по одной простой причине— в этом контроллере все четыре 8-разрядных порта: А,В,С и D — полные, и есть возможность выбрать нужные разряды подряд, не пересекаясь с другими функциями. Удобнее применять битовую маску цифр к одному регистру порта, чем вразбивку по разным выводам. В общем-то можно сделать такое управление и на ATmega8, и на ATtiny2313 (на последней даже удобнее, чем на Mega8, т. к. на ней есть полный порт В), но лучше всего применять для подобных целей контроллеры в 40-выводных корпусах. Ведь мы здесь показали только индикацию, но даже просто часам понадобятся дополни- тельные функции, которые здесь для наглядности опущены— прежде всего это кнопки режима установки часов (для чего нужны свободные выводы прерываний INT1 и INTO). Кроме того, нужен контроль резервной батарейки (об этом рассказа- но в главе 77), и, возможно, UART— на случай, если вы соберетесь установку часов делать через компьютер (что, конечно, несравненно удобнее, чем тыкать кнопки зубочисткой). Подробности Если же вам непременно нужен миниатюрный контроллер (40-выводной DIP-корпус — еще та «дура» по размерам), то укажем еще на одну возможность — управление раз- рядами с помощью внешних дешифраторов или демультиплексоров1. Скажем, 561ИД5 (CD4056) или 561КП1/2 (CD4052/1) позволяют управлять разрядами семисегментного индикатора трех-четырехразрядным двоичным кодом, и это решение при большом числе разрядов может пригодиться даже, если у вас 40-выводной контроллер. Кроме того, демультиплексор — удобный способ осуществлять управление яркостью многих индикаторов через единственный выход ШИМ (о режиме ШИМ рассказано далее). Есть также многочисленные готовые решения на основе регистра сдвига 74НС595 с воз- можностью «защелкивания» данных, что позволяет организовать вполне статическую индикацию для большого количества разрядов с управлением всего по трем выводам. Применение таких модулей ограничено куцым выбором по внешнему виду и фанта- стическим разнобоем схемотехнических решений при отсутствии какой-либо внятной документации, потому схему с 74НС595 проще построить самостоятельно, чем разби- раться в том, что предлагается в продаже. 1 Название «мультиплексор» относится к случаю коммутации «много входов —* один выход», назва- ние «демультиплексор» к случаю «один вход —* много выходов».
С1 1000,0 16 В VT1-VT4, VT9-VT16 ВС337 VT5-VT8 ВС327 1 РВО(ТО) 2 РВ1 (Т1) 3 РВ2 (Т2) 4 РВЗ (ОСО) 5 РВ4 (SS) 6 РВ5 (MOSI) 7 РВ6 (MISO) 8 РВ7 (SCK) 9 Reset 10Vcc 11GND 12XTAL2 13XTAL1 14PD0(RxD) 15PD1 (TxD) 16 PD2 (INTO) 17PD3(INT1) 18PD4(OC1B) 19PD5(OC1A) 20PD6(ICP1) (ADCO) РАО 40 (ADC1)PA1 39 (ADC2) PA2 38 (ADC3) РАЗ 37 (ADC4) PA4 36 (ADC5) PA5 35 (ADC6) PA6 34 (ADC7) PA7 33 AREF 32 6ND31 AVcc 30 (T0SC2) PC7 29 (TOSC1)PC6 28 PCS 27 PC4 26 PC3 25 PC2 24 (SDA) PC1 23 (SCL) PCO 22 (OC2) PD7 21 ATmega8535 Рис. 9.5. Схема управления для индикации времени
Глава 9. Программирование таймеров 251 Более универсальное решение — готовый модуль с управлением через некий после- довательный порт на основе специальной микросхемы ТМ1637, и это решение мы рассмотрим в главе 16. Оно'позволяет существенно упростить программу, и единст- венный его недостаток, пожалуй, заключается в скудном ассортименте модулей на основе ТМ1637 — 9 из 10 дисплеев, представленных в продаже, имеют конфигурацию «для часов» (4 цифры + двоеточие), причем весьма убогого размера 0,36 дюйма (9 мм). Так что при желании сделать красивую и удобную вещь вам также придется самостоятельно строить схему, которая в общем случае может оказаться даже слож- нее, чем непосредственное управление по рис. 9.5. Рассмотрим работу динамического режима индикации подробнее. Индикаторы фактически оказываются включенными только часть времени, и чтобы обеспечить достаточную яркость, через них нужно пропускать больший ток (при четырех ин- дикаторах — вчетверо больший, чем для статического режима). Отсюда выбирают номиналы ограничивающих резисторов — в нашей схеме на рис. 9.5 они равны 470 Ом, что даст при 12-вольтовом питании около 12-13 мА в импульсе, или при- мерно Ъ-А мА среднее значение, — для нормальной яркости этого достаточно. Источник питания можно рассчитывать на среднее значение, т. е. максимальный потребляемый ток составит при горящих цифрах «8» на всех индикаторах не более 110-120 мА. Некоторые уточнения к расчету этой схемы см. в конце приложения 1. Подробности Вам, возможно, захочется объединить часы с термометром и барометром, что доста- точно распространенная задача в Arduino. Ассемблерные решения для случаев ис- пользования дисплеев с контроллером мы, как уже говорили, рассмотрим в главах 13 и 16, а здесь остановимся лишь на вопросе вывода такого большого количества данных на семисегментные индикаторы. Для отображения часов, температуры и давления по- надобится минимум 10 разрядов. 6-7 десятичных разрядов также понадобятся для частотомера или других подобных приборов. По этому поводу возникает вопрос о ко- эффициенте заполнения — времени, в течение которого каждый разряд светится на продолжении периода. Если на четырех разрядах этот вопрос вас не очень беспокоит, то на десяти вам придется каждый сегмент «гонять» на максимально допустимом токе и даже более того, чтобы не было заметно снижения яркости. Это не очень страшно, т. к. все диоды (в том числе и светодиоды) боятся в разумных пределах не превыше- ния тока, а перегрева, а его здесь вы избегаете, т. к. большую часть времени сегмент выключен. Логика коммутации разрядов здесь обеспечивается схемотехнически — при подаче логической единицы на вывод управления разрядами комбинация из двух разнопо- лярных транзисторов не инвертирует уровень напряжения, и на нужный разряд по- дается питание 12 В. Наоборот, одиночные транзисторы управления сегментами инвертируют уровень, и наличие логической единицы на выводе МК заставляет коммутироваться нужный сегмент на «землю» (через токоограничивающий рези- стор). Двухкаскадное усиление по управлению разрядами сделано для преобразо- вания уровня от 5 до 12 вольт. Вместо первых п-р-п транзисторов (VT1-VT4) мож- но применить повышающий преобразователь уровня— например, микросхему CD40109 с двумя питаниями от 3 до 18 вольт каждое, имеющую как раз четыре ка- нала. Если на ее выход подключить р-п-р транзистор, как на схеме (обязательно с резисторами в цепи базы, аналогичными R6-R10 на рис. 9.5), то логику управле- ния разрядами необходимо будет поменять на обратную — разряд включается при
252 Часть III. Практическое программирование микроконтроллеров AVR низком уровне на выходе. Сохранить положительную логику можно, если на выход микросхемы подключить n-p-п транзистор в качестве эмиттерного повторителя, тогда базовые резисторы можно и не включать. Программирование динамической индикации При программировании режима динамической индикации необходимо учесть, что переключение между разрядами должно происходить достаточно быстро — с час- тотой, не меньшей, чем 50-60 Гц, иначе индикаторы будут мерцать заметно для глаза. Очень большую частоту при этом задавать не рекомендуется, т. к., во- первых, из-за инерционности ключей будут подсвечиваться выключенные сегмен- ты в соседних разрядах, во-вторых, не следует сильно перегружать контроллер — как мы увидим, даже в нашем простейшем случае процедура индикации достаточно громоздкая. Предположим, что время у нас отсчитывается посредством Timer 1, тогда с по- мощью TimerO удобно управлять индикаторами. Подадим на него частоту, равную, например, 764 от тактовой (4 МГц), тогда прерывания по переполнению будут про- исходить с частотой около 244 Гц, что для четырех индикаторов приемлемо (часто- та переключения составит 244/4 = 61 Гц). Перед тем, как «изобретать» процедуру управления индикаторами, определимся с тем, как формировать рисунок цифр. Фактически для этого необходимо в про- грамму занести таблицу, приведенную на рис. 9.2. Это можно сделать несколькими способами: просто расположить ее в памяти (в EEPROM или, что проще, прямо в программе) или сформировать 10 процедур (по одной на каждую цифру от 0 до 9), в которых непосредственно устанавливаются нужные разряды портов. Последний вариант, хотя и громоздок, но вполне приемлем в ситуации, когда биты управления сегментами «разбросаны» по разрядам разных портов, — тогда все равно прихо- дится фактически управлять этими разрядами индивидуально. Мы же здесь рассмотрим более универсальный способ с представлением маски цифр в виде констант в памяти и последующим приложением этой маски к регист- ру данных порта С. Как записать такую маску в память программ и прочесть ее командой lpm, описано ъразд. «Команды пересылки данных» главы 7. Расположим распакованные значения часов и минут в оперативной памяти подряд, начиная со старшего разряда так же, как мы делали ранее (см. листинг 9.5). Кроме того, зададим специальную глобальную переменную— счетчик разрядов cRazr, изменяющийся по кругу: 0-1-2-3-0-1... Сказанное иллюстрирует листинг 9.12. /адреса распакованных часов в SRAM .equ DdH = 0x06 ;часы старший .equ DeH = 0x07 ;часы младший .equ DdM = 0x08 ;минуты старший .equ DeM = 0x09 ;минуты младший
Глава 9. Программирование таймеров 253 .def temp = rl6 ;рабочая переменная .def cRazr = г17 ;счетчик разрядов ;в начале программы N_mask: ;маски семисегментных цифр .db ObOOllllll,ObOOOOllO,ObOlOllOll,ObOlOOllll,ObOllOOllO,ObOllOllOl,ObOlllllOl, ObOOOOOlll,ObOlllllll,ObOllOllll He забудем предварительно обнулить счетчик разрядов, разрешить прерывание пе- реполнения TimerO с коэффициентом 1:64 (не забудьте, что у ATmega8535 таблица прерываний отличается от ATmega8) и установить разряды PD0-PD3, а также все разряды порта В на выход. Листинг 9.13 иллюстрирует прерывание переполнения TimerO: TIM0_OVF: ;динамическая индикация inc cRazr ;счетчик разрядов cpi cRazr,4 ;всего 4 разряда brne Set_razr clr cRazr ;если равен 4, очищаем Set_razr: ldi YH,1 ;старший разряд адреса SRAM = 1 mov YL,cRazr+DdH /установка тек. адреса Id temp,Y ;в temp — значение дес. цифры ldi ZH,HIGH(N_mask*2) ;адрес констант в памяти — в Z ldi ZL,LOW(N_mask*2) add ZL,temp ;адрес маски цифры, равной temp adc ZH, 0 ;на всякий случай учитываем перенос lpm ;в гО — маска out PortC,rO /установили сегменты ;установка разряда биты РВО-РВЗ cpi cRazr,О brne Setl sbr temp,l /устанавливаем разряд РВО out PortB,temp reti /выход Setl: cpi cRazr,1 brne Set2 sbr temp,2 /устанавливаем разряд РВ1 out PortB,temp reti /выход Set2: cpi cRazr,2 brne Set3 sbr temp,4 /устанавливаем разряд РВ2
254 Часть III. Практическое программирование микроконтроллеров AVR out PortB,temp reti ;выход Set3: ;если не 0, 1 или 2, значит 3 sbr temp,8 ;устанавливаем разряд РВЗ out PortB,temp reti ;выход - конец прерывания Обратите внимание, что устанавливать непосредственно нужный бит в PortB командой sbi здесь нельзя — нам надо обнулить все остальные, потому приходится использовать маску, приложенную к обычному регистру temp через команду sbr {си. разд. «Команды сдвига и операции с битами» главы 7). Как видите, получается не так уж и громоздко — сама операция подсчета времени в часах (см. листинг 9.5) была сложнее. Но если вы вдруг захотите перенести это решение в Arduino, его все равно следует разместить в прерывании — нельзя по- зволить, чтобы еще что-то серьезное вклинивалось в эту процедуру, достаточно длинная пауза на почти любом этапе может привести к видимым на глаз сбоям в индикации. Подробности У вас непременно возникнет вопрос: нельзя ли подобным образом подключить ЖК- дисплей? Большинство ЖК-дисплеев на прилавках имеют встроенный контроллер, ко- торый обычно много потребляет и тем самым в значительной степени сводит на нет главное преимущество ЖК-дисплеев — малое потребление. ЖК-дисплеи, лишенные контроллера, потребляют исключительно мало — микроамперы или даже доли микро- ампер, и на их основе можно делать круглосуточно включенные приборы с батарей- ным питанием. Но управляются они сложнее, чем LED-индикаторы, и напрямую орга- низовать динамическую индикацию в них нельзя, т. к. общим выводом всех сегментов служит единая подложка. Описание проблемы, а также один из вариантов управления ЖК-индикатором без встроенного контроллера представлены, например, в статье [21]. Другой вариант мы рассмотрим в главе 13— экономичный ЖК-дисплей с семисег- ментными цифрами, управляющийся по 12С-интерфейсу. Таймеры в режиме ШИМ Областей применения таймеров в режиме широтно-импульсной модуляции (по- английски— PWM, Pulse-Wide Modulation) множество, и, соответственно, в МК AVR таких режимов тоже более чем достаточно, и различаются они способами обеспечения ШИМ, разрядностью и другими нюансами (есть режимы, где число разрядов задано жестко, а есть такие, когда разрядность определяет содержимое регистра «захвата»). Перебирать все на этот счет подробности мы, естественно, не станем — в описаниях контроллеров, а также в литературе различные варианты ШИМ достаточно подробно представлены (рекомендую, например, [2], где режимы ШИМ описаны довольно-таки внятно, хотя пример там приведен не слишком удач- ный). По сути, ШИМ представляет собой один из вариантов аналого-цифрового или циф- роаналогового преобразования. В аналоговом варианте преобразование синусои- дального сигнала в ШИМ-последовательность и обратно в синусоиду схематически
Глава 9. Программирование таймеров 255 выглядит так, как показано на рис. 9.6. Для прямого преобразования на один из входов компаратора подается сигнал опорной частоты fon, который имеет треуголь- ную форму, на второй — исходный аналоговый сигнал С/вх. При совпадении уров- ней на входах выход компаратора переключается «туда-обратно», и в результате формируется последовательность прямоугольных импульсов с несущей частотой Топ? в длительности которых оказывается закодирован уровень исходного аналого- вого сигнала. Если требуется потом опять выделить аналоговый сигнал, то такую последовательность подают на ключевой усилитель (на схеме он обозначен парой комплементарных транзисторов) и пропускают через фильтр для отсеивания опор- ной частоты. С/пит + RC »1/fn (Упит- Рис. 9.6. Схема аналоговой ШИМ Для успешного преобразования без потерь в спектре аналогового сигнала опорная частота должна быть вдвое выше самых высокочастотных гармоник исходного сигнала. Так, для речи с запасом достаточно 8 кГц, можно было бы ограничиться и величиной 4 кГц (обычный разговор укладывается в полосу частот 300 Гц — 3 кГц). Однако тогда для фильтрации опорной частоты (она также оказывается в звуковом диапазоне) понадобится достаточно качественный фильтр. Чтобы упро- стить конструкцию, выбирают опорную частоту выше порога слышимости, кото- рый составляет около 16-20 кГц. Отметим, что для более эффективного использо- вания спектра опорная частота может быть переменной (аналог такого режима в AVR также присутствует). В чисто цифровом виде ШИМ реализуется немного иначе, чем в аналоговом, — в роли компаратора и одновременно формирователя опорной частоты выступает счетчик-таймер в совокупности с регистром сравнения. Рассматривать генерацию звука с помощью режимов ШИМ мы не станем— AVR-контроллеры для этого плохо приспособлены, и эти режимы в них предназначены для других целей. Мы сначала рассмотрим управление ШИМ-сигналом для выполнения задачи построе- ния инвертора — преобразователя постоянного напряжения в переменное. В этой области режим ШИМ просто незаменим — никаким другим способом не удается получить практически идеальную синусоиду. Такую сравнительно сложную для новичков задачу я выбрал потому, что она дает универсальное решение, после несложных модификаций пригодное для почти лю- бых применений ШИМ. Как вы увидите далее, все сложности в конкретном случае инвертора кроются не в собственно реализации двухканального ШИМ-режима, а в
256 Часть III. Практическое программирование микроконтроллеров AVR схемотехнике мощных ключевых схем и правильном подборе параметров транс- форматора и дросселя. Поскольку начнем мы не прямо с инвертора, а с привычного мигания светодиодом, то вам достаточно изучить принцип, а приложить его най- дется куда. Заметим, что в Arduino выполнение подобной задачи стандартными методами не решается (уже только по причине низкой частоты встроенных ШИМ-сигналов). Там пришлось бы все равно все делать так же, как описано далее, разве что на С, а не на ассемблере. Заметки на полях Следует отметить, что с удешевлением микросхем-драйверов и мощных MOSFET- ключей ШИМ стали пихать куда угодно: от экономичных усилителей звука в режиме D или управления яркостью светодиодных светильников до электроинструмента. Кое- где это уместно, а кое-где не очень. Так, следует предостеречь читателя от попыток , замены традиционных редукторов на ШИМ-регуляторы — они абсолютно не взаимо- заменяемы. Вспомните коробку передач автомобиля: переключение на низ- кие/высокие обороты с помощью редуктора преследует цель не только и не столько снижения/увеличения собственно оборотов, сколько изменения крутящего момента, т. е. усилия, подводимого к колесам. Если вы помните школьную физику, то знаете, что то же самое правило рычага относится ко всем редукторам: чем ниже обороты, тем выше передаваемое усилие. А ШИМ-регулирование скорости вращения делает ровно обратную операцию: при снижении среднего напряжения снижается и мощ- ность, следовательно, крутящий момент падает. Как приспособление для плавного запуска, например, электродрели — при постепенном нажатии клавиши включения — ШИМ-регулятор как раз очень удобная штука, но для точной установки числа оборотов в подобных механизмах он непригоден. Есть типы двигателей (например, шаговые), скорость вращения которых зависит от частоты, что позволяет менять скорость в ши- роких пределах, но при этом сохраняя постоянной и мощность на валу привода, но ШИМ-управление к этому не имеет отношения. Расчет режима ШИМ для инвертора На рис. 9.7 показан принцип работы инвертора по схеме симметричного четырех- плечевого моста на основе двухканального режима ШИМ. Подробности о таком построении можно узнать из статьи [16], а также из других статей того же автора на Habr.com. Как видите, ШИМ-сигнал, скважность которого изменяется по закону синуса, попеременно поступает на противоположные MOSFET-ключи моста: или VT1-VT4, или VT2-VT3. Когда открыта первая пара VT1 и VT4, ток через нагруз- ку /А идет слева направо, когда вторая VT2 и VT3 — наоборот. Итого мы каждый раз получаем полпериода переменного тока синусоидальной формы. Если длитель- ность каждой такой пачки импульсов с переменной скважностью будет равна 10 мс, то общая частота составит ровно 50 Гц. Подробности о драйверах и ключах мы рассмотрим далее, а пока попробуем получить такой сигнал с помощью PWM- режимов таймера. На рис. 9.8 показан принцип работы в контроллерах AVR режимов ШИМ: Fast PWM и Phase Correct PWM (а также Phase and Frequency Correct PWM — об их раз- личиях рассказано во врезке «Подробности» далее). Из описания в документации очень нелегко понять, в чем их особенности и различия, и перевод [6,7] тут не по-
Глава 9. Программирование таймеров 257 Рис. 9.7. Принцип функционирования инвертора по схеме симметричного «моста» Fast PWM Phase Correct PWM модуль счета (top) phase and Frequency Correct PWM 0CR1A / 0CR1B ! j/ 11 \ A A h * A _h_ / ^\ 0CR1A i yS\ I ^N. 0CR1B сдвиг фаз сдвиг фаз * 0 Рис. 9.8. Принцип работы режимов ШИМ в контроллерах AVR могает. В основном это получилось из-за того, что на графиках в описании кон- троллеров попытались представить сразу все возможные варианты, в том числе и режим с включенным битом «сброса по совпадению» wgm12 (ctci), который мы ра- нее использовали для генерации частоты, а вовсе не для ШИМ (см. листинг 9.3 и пояснения к нему). В режиме PWM этот бит может использоваться для генерации ШИМ с переменной частотой, что относится к высшему пилотажу из области гене- рации звука, и в обычных задачах не применяется. Из упрощенных графиков на рис. 9.8 сразу ясно, что к чему, и, главное, чем отличаются режимы: выражение Phase Correct относится к фазам сигналов на выводах ОС1А и ОС 1В, если их ис-
258 Часть III. Практическое программирование микроконтроллеров AVR пользовать оба и притом с различными значениями 16-разрядных регистров срав- нения 0CR1A И OCR1B. Подробности В реализации режимов PWM есть такой нюанс — при задании новых значений порога сравнения через пару ocrixh:ocrixl происходит двойная буферизация: сначала при записи пары регистров по очереди (старший байт, как мы говорили, записывается первым и при этом помещается в буфер). Затем записанное значение уже полностью помещается в буфер и обновляется только в момент достижения счетчиком заданного максимального значения (модуля счета, ТОР). Благодаря этому, у нас есть достаточно времени на запись новых значений регистров сравнения. Но если вы поразмыслите над временными диаграммами на рис. 9.8, то поймете, что в режиме Phase Correct PWM такой алгоритм приведет к появлению несимметричных импульсов при смене порога, и вся эта самая Phase Correct окажется не такой уж и Correct..Для исправле- ния этой ситуации служит третий режим Phase and Frequency Correct PWM, диаграм- мы для которого ничем не отличаются от диаграмм для Phase Correct, но момент сме- ны регистра сравнения приходится на минимальное значение в счетчике (т. е. равное $оооо), а не на максимальное, как в остальных двух режимах. Однако наша задача— получить на двух выводах ОС1А и ОС 1В одинаковые по- следовательности ШИМ. А если значения порога сравнения одинаковые, то и фаза во всех случаях одинаковая, хотя и это нас на самом деле беспокоить не будет, т. к. мы будем получать наши пачки импульсов для каждого полупериода синуса в разное время и на разных выводах. Потому мы воспользуемся режимом Fast PWM, который позволяет получить более высокую частоту ШИМ при той же так- товой частоте на входе таймера. Формула для расчета частоты ШИМ в режиме Fast PWM такая: где Fdk — тактовая частота контроллера, N— коэффициент предделителя таймера, а ТОР — модуль счета (установленное максимальное число). Для общего образова- ния отметим, что для двух других режимов частота будет вдвое ниже. Сначала разберемся со значением ТОР. От установленного модуля счета зависит разрешение сигнала ШИМ (то же самое, что разрешение АЦП), т. е. число градаций выходного сигнала. Для 16-разрядного таймера его можно выбирать из трех значе- ний: $ff (8-битовое разрешение), $iff (9-битовое) и $3ff (10-битовое), а также можно делать переменным вплоть до 16 разрядов (чем мы заниматься не будем). Мы выберем банальное 8-битовое разрешение просто потому, что нам еще надо будет где-то размещать таблицу значений синуса, а величины размером не больше байта легче извлекать из памяти. 256 градаций вполне хватит, чтобы получить при- личный синус. Сразу учтем: для того чтобы нормально работал ферритовый трансформатор ин- вертора (см. далее), частоту нам желательно иметь порядка 20-40 кГц. Из приве- денной ранее формулы получаем, что при тактовой частоте 8 МГц и коэффициенте предделителя, равном 1, частота ШИМ на выходе будет равна 31 250 Гц, что нас устроит. Теперь вычислим, сколько периодов такой частоты надо отсчитать, чтобы
Глава 9. Программирование таймеров 259_ получить искомые 10 миллисекунд (полпериода частоты 50 Гц): 10-2 • 31250 = = 312,5. Если округлим до целого 312, то получим частоту 50,08 Гц, что укладыва- ется в требования ГОСТ (50 ±0,2 -s- ±0,4 Гц). Теперь посмотрим, как все сказанное реализовать программно. Заметки на полях Кстати, а как сделан ШИМ-сигнал в Arduino? Если вы заглянете в описание функции anaiogwrite () на любом из официальных Arduino-сайтов, то узнаете, что в современ- ных версиях, где стоит контроллер ATmega328, ШИМ-сигнал работает на цифровых выводах 3, 5, 6, 9, 10 и 11, а на первой версии, где еще применялся ATmega8, он ра- ботал только на выводах 9, 10 и 11. Теперь посмотрим на картинку соответствия вы- водов Arduino и контроллера ATmega328, ссылка на которую имеется на всех странич- ках с описанием, например, Arduino Uno. Из этой картинки мы узнаем, что выводы 9, 10 и 11 соответствуют выводам ОС1А (РВ1), ОС1В (РВ2) и ОС2А (РВЗ, в ATmega8 это просто ОС2). То есть здесь применяются те же функции Timeii, которые используем и мы, а также и соответствующая функция Timer2 (которая у ATmega8 в единственном числе). А выводы Arduino 3, 5, 6 соответствуют аналогичным выводам ОС0А и ОСОВ для TimerO, а также ОС2В для Timer2, т. е. функциям, отсутствующим у ATmega8, но введенным в АТтеда 168/328. Отсюда, кстати, становится понятно, почему к функции tone о сделано примечание, что она будет «влиять на ШИМ-сигнал на выводах 3 и 11» (на русскоязычных сайтах вы встретите «может влиять», что просто неверный перевод английского «will inter- fere»). Функция задействует Timer2 для генерации частоты, и по идее anaiogwrite () на этих выводах вообще не должна включаться одновременно с tone (). Но при одновре- менном обращении она все-таки как-то включается, и обе функции при этом, понятно, работать перестают. Разберемся также с частотой и разрешением ШИМ, которые в Arduino для упрощения фиксированные и равны «approximately 490 Hz» (а на выводах 5 и 6 — «approximately 980 Hz») и числу градаций 255. Очевидно, мы имеем дело с 8-битовым разрешени- ем — для удобства установки значения и унификации, т. к. TimerO и Timer2, в отличие от Timeri, иначе не умеют. Для Timer2 и TimeM это режим Phase Correct PWM, а для TimerO — обычный Fast PWM с коэффициентом предделителя таймеров, равным 64. Соответственно, точные значения частот ШИМ равны при «ардуиновских» 16 мегагер- цах 976,5625 Гц для выводов 5 и 6 и 488,28125 Гц для остальных. Программная реализация ШИМ Нам осталось решить по сути только одну задачу — в какой момент менять содер- жимое регистра сравнения? Большая часть примеров ШИМ в Сети делает это с по- мощью программных задержек, никак не синхронизированных с работой ШИМ. Так можно получить низкие частоты смены сигнала ШИМ — порядка долей или единиц герц, причем по какому-нибудь простому закону, потому что с увеличением частоты и сложности закона изменения вместо искомого вы рискуете получить не- весть что. Поэтому здесь мы просто будем загружать новое значение по прерыва- нию сравнения — оно будет происходить каждый период сигнала ШИМ (см. диа- грамму на рис. 9.8), причем до начала нового отсчета, т. е. к нему оно уже будет подготовлено. Отсчитав 312 таких сравнений, мы переключаемся на другой вывод, и все повторяем сначала. Иными словами, нам нужно реализовать следующий алгоритм:
260 Часть III. Практическое программирование микроконтроллеров AVR О при начальной инициализации Reset: • выводы ОС 1А и ОС 1В — на выход; • задать режим Fast PWM на выводе ОС1А при ТОР = $ff (8 разрядов), и ре- жим вывода ОС1А — установка в о при совпадении (неинверсная ШИМ); • вывод ОС 1В установить в низкий уровень; • включить таймер с предделителем 1:1; • разрешить прерывание по сравнению Timer I Compare A; □ в прерывании Timer I Compare A: • отсчитать 312 прерываний, меняя каждый раз значение регистра OCR1A в соответствии с таблицей в памяти; • вывод ОС 1А установить в низкий уровень; • задать режим Fast PWM на выводе ОС 1В при ТОР = $ff (8 разрядов), и ре- жим вывода ОС 1В — установка в о при совпадении (неинверсная ШИМ); • запретить прерывание по сравнению Timer I Compare А, разрешить прерыва- ние по сравнению Timerl Compare В; □ в прерывании Timerl Compare В: • отсчитать 312 прерываний, меняя каждый раз значение регистра OCR IB в соответствии с таблицей в памяти; • вывод ОС 1В установить в низкий уровень; • задать режим Fast PWM на выводе ОС1А при ТОР = $ff (8 разрядов), и ре- жим вывода ОС1А — установка в о при совпадении (неинверсная ШИМ); • запретить прерывание по сравнению Timerl Compare В, разрешить прерыва- ние по сравнению Timerl Compare A. Таблицу из 312 значений функции 255sin(x) в пределах полупериода (от 0 до я) можно рассчитать в Excel или любым другим способом, который вам по вкусу. Мы ее разместим в памяти программ и будем добывать значения с помощью одного из вариантов команды lpm, подобно тому, как это делалось для маски цифр в процеду- рах индикации. Заметим, что в Arduino такая операция соответствовала бы дирек- тиве progmem, и большие таблицы (шрифтов, например) там тоже хранят в памяти программ, — а куда денешься? Подробности С канонической точки зрения таблицу следовало бы разместить в EEPROM, но есть у нас любимый ATtiny2313, который вполне справится со всеми операциями управле- ния ШИМ, но, увы, имеет всего по 128 байтов и SRAM, и EEPROM. Так что пойдем наиболее универсальным путем — читать байт из флеш-памяти программ даже на один такт быстрее, чем из EEPROM (см. главу 10). Прерывание будет длиться какое- то время, и по идее мы должны иметь небольшое искажение синуса вблизи макси- мальных значений из-за того, что в этом случае прерывание может перекрывать мо- мент достижения максимума счета, и обновление регистров сравнения не успеет про-
Глава 9. Программирование таймеров 261 изойти (напомним, что счет в таймере идет независимо от программы). На практике такое искажение все равно осциллографом не обнаруживается, так что можно быть спокойными. Если внутри у вас зудит от такого недостатка — выбирайте контроллер пошустрее (STM32) или вообще специализированный, как советует в своих статьях автор [16]. Полностью рабочую программу, осуществляющую эти функции, вы можете найти в архиве к этой книге, адрес которого указан во введении. А здесь мы приведем ее отдельные фрагменты. Сначала определения переменных (обратите внимание, что двухбайтовый счетчик countircounto размещается в регистрах г25:г24, чтобы потом его можно было уве- личивать одной командой adiw): .def temp =rl6 .def countO = r24 /счетчик байтов .def countl = r25 .def temp2 =rl9 Процедура инициализации Reset (листинг 9.14). Reset: ldi out ldi out temp, low (RAMEND) SPL,temp temp,high(RAMEND) SPH,temp ;указатель стека .***************•*** PortB *********************** ldi temp,ObOOOOOllO; PortB.1 (OC1A) PortB.О (ОС1В) - Output out DDRB, temp .***•*************** TIMER1 *********************** ldi temp, 0Ы0000001; COMlAl=bit7; WGM10=bit0 неинв. FastPWM выв А out TCCR1A, temp ldi temp, ObOOOOlOOl ;bit3=WGM12, CS12:CS10= 001 = 1:1 out TCCR1B, temp ldi temp, ObOOOlOOOO ;OCIE1A = bit4,TIMl_COMPA начинаем с А out TIMSK, temp .******************* Registers ****************** clr count0 ;младший байт счетчика циклов clr countl /старший байт счетчика циклов clr temp2 /старший байт величины синуса всегда 0 ldi ZH,HIGH(N_sin*2) ;адрес таблицы sin в памяти - в Z ldi ZL,LOW(N sin*2)
262 Часть III. Практическое программирование микроконтроллеров AVR sei ;разрешаем прерывания Gcykle: /основной цикл пустой rjmp Gcykle Таблица 312 значений полупериода синуса в масштабе 0..255 градаций — сразу после таблицы прерываний (листинг 9.15). Листинг 0/1Ъ | .org INT_VECTORS_SIZE ;начинаем с адреса после таблицы векторов N_sin: ;312 точек = 0138h .db 0,3,5,8,10,13,15,18,21,23,26,28,31,33,36,38,41,43,46,48,51,54,56,59,61,64, 66,68,71,73,76,78,81,83,86,88,90,93,95,98,100,102,105,107,109,112,114,116,119, 121,123,125,128,130,132,134,136,138,141,143,145,147,149,151,153,155,157,159,161, 163,165,167,169,171,173,175,177,178,180,182,184,186,187,189,191,193,194,196,198, 199,201,202,204,205,207,208,210,211,213,214,216,217,218,220,221,222,223,225,226, 227,228,229,230,231,233,234,235,236,237,238,238,239,240,241,242,243,243,244,245, 246,246,247,248,248,249,249,250,250,251,251,252,252,252,253,253,253,254,254,254, 254,255,255,255,255,255,255,255,255,255,255,255,255,254,254,254,254,253,253,253, 252,252,252,251,251,250,250,249,249,248,248,247,246,246,245,244,243,243,242,241, 240,239,238,238,237,236,235,234,233,231,230,229,228,227,226,225,223,222,221,220, 218,217,216,214,213,211,210,208,207,205,204,202,201,199,198,196,194,193,191,189, 187,186,184,182,180,178,177,175,173,171,169,167,165,163,161,159,157,155,153,151, 149,147,145,143,141,138,136,134,132,130,128,125,123,121,119,116,114,112,109,107, 105,102,100,98,95,93,90,88,86,83,81,78,76,73,71,68,66,64,61,59,56,54,51,48,46, 43,41,38,36,33,31,28,26,23,21,18,15,13,10,8,5,3,0 Прерывание Timer I Compare А (листинг 9.16). Т1М1_СОМРА: cpi countO,$38 /сравниваем с 312 ldi temp,$01 срс countI,temp brio continueA ;если меньше 312, на продолжение ldi ZH,HIGH(N_sin*2) ;адрес таблицы sin в памяти - в Z ldi ZL,LOW(N_sin*2) clr count0 clr count1 /переключаем на В ldi temp, 0b00100001;COMlBl=bit5 WGM10=bit0 неинв. FastPWM выв В out TCCR1A, temp ldi temp, ObOOOOlOOO ;OCIE1B = bit3, TIM1_COMPB включаем out TIMSK, temp cbi PortB,l;Ha всякий случай сброс ОС1А reti ;выход из прерывания continueA: ;продолжаем счет
Глава 9. Программирование таймеров 263 lpm temp,Z+ ;в temp - число из таблицы out OCRIAH, temp2 /выводим старший out OCR1AL, temp ; выводим младший adiw countO,l /увеличиваем счетчик reti Прерывание Timer 1 Compare В идентично, только все а (и соответствующие пози- ции битов) меняются на в и наоборот. Обратите внимание на два в общем-то лиш- них усложнения: во-первых, отсчет позиций в таблице ведется отдельным двухбай- товым счетчиком countiicounto, хотя у нас уже есть регистр z, который так и так отсчитывает позицию, начиная с удвоенного адреса метки Nsin (здесь мы приме- нили модификацию команды lpm с постинкрементом). Мы могли бы сравнивать непосредственно значение регистра z с нужным числом вместо отдельного счетчи- ка, но здесь это сделано специально, чтобы не запутаться в сравнениях удвоенного двухбайтового адреса метки, к которому прибавляется двухбайтовое число текуще- го адреса. Если сократить количество значений в таблице до величины меньше бай- та (например, вдвое— до величины 156, при этом установку следует менять не в каждом прерывании, а в каждом втором), то процедура сравнения упростится, но не забудьте, что адресный регистр z все равно останется двухбайтовым. Во-вторых, мы отводим целый лишний регистр temp2 для хранения нуля, который записывается в старший байт регистра сравнения ocriah. Дело в том, что из-за бу- феризации, о которой мы говорили ранее, всегда следует записывать регистр ocriah первым, даже если он равен нулю или другой константе. И в нашем случае жестких лимитов времени желательно загрузку обоих регистров делать строго друг за дру- гом: пока мы будем извлекать байт из таблицы, счетчик продолжает считать, и чем дольше мы возимся, тем вероятнее, что момент перезагрузки счетчика вклинится между записью старшего и младшего байтов. Программа занимает 466 байтов в памяти, из которых две трети — таблица значе- ний синуса. Она будет работать почти без изменений в любом контроллере с па- мятью 8 кбайт и менее, в котором имеется Timer 1 (или другой таймер с двумя кана- лами). Изменения коснутся только строк инициализации и обнуления выводов пор- та, соответствующих ОС1А и ОС 1В,— в других контроллерах они находятся на других местах (см. листинг 9.3). Частота контроллера 8 МГц выбрана универсаль- ная, на ней работают все модификации контроллеров, даже семейства Classic. Для проверки загрузите программу из архива (файл PWM_OC1_Sin.asm) в контрол- лер, поменяв в ней предварительно младшие три бита csi2:csio байта, загружаемо- го в регистр tccrib, — вместо значения ooi (предделитель 1:1) подставьте значение юо (1:256) или 101 (1:1024), и подключите к выводам 15 и 16 ATmega8 светодиоды. Они будут по очереди медленно менять яркость свечения от нуля до максимума и обратно. Если вы будете применять эту программу для инвертора, как описано далее, то не забудьте восстановить значение ooi и проверьте значение частоты на каждом из выводов — оно должно быть ровно 50 Гц. Как изменять значение часто- ты синуса для других применений, вы уже поняли: можно менять тактовую частоту контроллера, можно задавать другой коэффициент деления на входе таймера, мож-
264 Часть III. Практическое программирование микроконтроллеров AVR но, наконец, внести изменения в программу, пересчитав количество значений в таблице. Подробности При компиляции программы вы можете получить от компилятора avrasm2 предупреж- дение (warning) о том, что .csed .db missaligment - padding zero byte. Это компиля- тор напоминает, что в памяти программ (.csed) к массиву .db добавляется нулевой байт, чтобы выровнять (aligment) содержимое по двухбайтовому слову, если число байтов нечетное. Иными словами, это означает, что вы ошиблись при расчете табли- цы, и число значений либо 313, либо 311. Пересчитайте таблицу с числом значений меньше или больше на единицу — число значений в ней должно быть четным. Кстати, нет особых проблем повысить разрешение синуса сверх 256 градаций, если задать, например, режим ТОР = $3ff (1024 градации), а в таблице разместить рассчи- танные двухбайтовые величины для старшего и младшего байтов регистра сравнения. Единственное, что при этом емкость таблицы увеличится вдвое, и чуть удлинится процедура извлечения из памяти. Я предлагаю читателю самому поразмыслить над таким усовершенствованием — это будет очень хорошая тренировка сразу по многим темам применения различных команд контроллера. О схемотехнике инвертора Поскольку эта книга не посвящена схемотехнике, то подробно мы обсуждать схему инвертора со всеми ее многочисленными нюансами не станем. На рис. 9.9 приведен вариант базовой схемы, построенный на основе схемы включения, рекомендован- ной в описании драйвера ШР4082 фирмы Intersil. Конечно, приведенная здесь схе- ма далеко не единственно возможная, т. к. эта тема на сегодняшний момент чрез- вычайно популярна, а разнообразие выпускаемых драйверов и ключевых MOSFET- транзисторов быстро стремится к бесконечности. +12 В, 10 А к контроллеру ОС1А ОС1В R1 30-100 кОм VD1.VD21N4934 VT1-VT3IRF540 С1,С2 1,0мкФ Рис. 9.9. Базовая схема инвертора напряжения на основе драйвера HIP4082
Глава 9. Программирование таймеров 265 Подробности Не углубляясь в подробности работы схемы драйвера (все это есть в «даташите» на HIP4082), сделаем только пару замечаний. Вы, наверное, слышали, что транзисторы плеча моста не полагается включать одновременно с выключением противоположного плеча, чтобы избежать сквозных токов. Подобно другим современным драйверам ключей, микросхема HIP4082 все делает сама: для регулирования задержек включе- ния/выключения служит резистор R1, который подбирается исходя из конкретных условий (в частности, быстродействия ключей). Опять же, все необходимые данные приведены в «даташите» на HIP4082. Транзисторы IRF540 можно заменить неисчислимым множеством других типов, для которых, кроме предельного тока, нужно смотреть на сопротивление канала. Для IRF540 оно равно 27 миллиом, следовательно, при токе в 10 А падение напряжения будет где-то 0,3 вольта, а выделяющаяся мощность — 3 Вт. То есть эти транзисторы необходимо установить на небольшие радиаторы порядка 20-30 см2. В этой схеме не приведены необходимые для построения настоящего инвертора узлы, такие как цепь стабилизации напряжения, ограничения по току, защиты от короткого замыкания. Всю схему можно выключить, если подать на вывод DIS вы- сокий уровень (выше 2,7 вольта), что также не отражено на схеме. Мы отсылаем читателя к соответствующим ресурсам в Сети, а здесь лишь остановимся на паре моментов, которые на этих ресурсах чаще всего отражены не очень внятно, хоть они и не имеют прямого отношения к тематике этой книги. Прежде всего, это выбор магнитопровода трансформатора и дросселя для сглажи- вания высокочастотных пульсаций выходного напряжения, а также расчет числа витков для них. Если не вдаваться в подробности, то для магнитопровода подходит импортный феррит марки N27 или N87, а для дросселя, ничтоже сумняшеся, выби- раем отечественный пермаллоевый сердечник типа МП-140. Примем, что мы про- ектируем простенький инвертор мощностью порядка 100 Вт (как следует из значе- ния тока для входного напряжения, взятого с небольшим запасом, — см. величину на рис. 9.9). Пусть у нас для трансформатора есть кольцо N87 размерами 29,5x19x14,9 мм. До- пустимая мощность, которую оно может перекачать, равна примерно 300-400 Вт, т. е. взята с запасом (ее можно повысить, если сделать зазор в сердечнике, но мы в это вдаваться не станем). Самый простой расчет числа витков первичной обмотки можно произвести по формуле: п = 0,7/SK, где п — число витков на вольт, a SK — площадь поперечного сечения кольца в см2. Считаем: (29,5-19)/2 = 5,25 мм — тол- щина кольца, умножаем ее на высоту 5,25* 14,9 ~ 78 мм2 = 0,78 см2 — площадь се- чения. Получаем: п = 0,7/0,78 ~ 0,9 витков на вольт. Ничего не испортим, если округлим до одного витка на вольт, итого получаем, что для 12 вольт на первичную обмотку надо намотать 12 витков. Диаметр провода тут считается по обычной формуле: d = 0,8 Vl, где I — ток в амперах, a d получается в миллиметрах. Для 10 ампер можно принять стандартный провод 2,5 мм2, причем будет правильно взять кусок силового провода прямо в изоляции и намотать его на кольцо (при условии, что еще и влезет вторичная обмотка). Вторичная обмотка, казалось бы, из этих величин считается элементарно: 220 вит- ков дадут 220 вольт. На самом деле все несколько сложнее — так мы на вторичной
266 Часть III. Практическое программирование микроконтроллеров AVR обмотке получим амплитуду пульсирующего напряжения с частотой ШИМ (т. е. 32 кГц). А нам надо действующее значение 50 Гц после сглаживания пульсаций. То есть полученное число витков надо еще умножить на 1,41 (V2), тогда результи- рующее число витков, равное 310, будет давать как раз такую амплитуду, чтобы действующее значение получалось равным 220 вольт. При 100 ваттах ток через вторичную обмотку будет равен: 100/220 = 0,45 А, т.е. диаметр провода нужен примерно 0,5 мм. Я предоставляю читателю самостоятельно рассчитать, влезет ли такое количество витков на выбранное нами кольцо, или нужно где-то ужиматься (а может быть, взять кольцо побольше?). Теперь о дросселе. В принципе, если нагрузка позволяет, то можно сглаживающий фильтр и не ставить, но таких нагрузок, о которых это можно сказать уверенно, не- много (разве что лампочка накаливания), потому лучше его установить. Расчет дросселя в учебниках излагается еще мутнее, чем расчет трансформатора, хотя он на самом деле проще и однозначней. В характеристиках сердечников из ферритов, пермаллоев и прочих альсиферов есть такая величина, которая называется коэффи- циентом индуктивности и обозначается AL. Из нее сразу получается необходимое количество витков N для получения нужной индуктивности L: N = Vb/AL. Для кольца с размерами 24x13x7 из пермаллоя МП-140, AL будет равна примерно 100 мкГн, а необходимая нам индуктивность равна 5 мГн. Из приведенной форму- лы получаем: N = V(5 мГн/0,1 мГн) = л/50 ~-7 витков. Сильно не напортачим, если намотаем до 10 витков, причем провод здесь рассчитан на вторичное напряжение, и можно брать любой прямо в изоляции, чем толще, тем лучше. Теперь проверим емкость конденсатора. Для сглаживающих фильтров в случае достаточно высокочастотной ШИМ ее можно найти из условия: FmHM»l/(2WLC)»FBbIX, где l/(27rVLC) — резонансная частота LC-контура, Ршим — частота ШИМ (в нашем случае 32 кГц), FBbIX — частота синуса 50 Гц. Смысл неравенств в том, чтобы сгла- дить ШИМ, но не ослаблять выходной сигнал 50 Гц. То есть нам надо оказаться где-то посередине между 32 кГц и 50 Гц, так что резонансная частота в районе 1-3 кГц нас устроит. Из формулы для нее получаем при С =1,0 мкФ: FLc = = l/(6,28-V(510"3110"6)) ~ 2200 Гц. Откуда делаем вывод, что емкость 1-2 микро- фарады как раз подойдет. Конечно, конденсатор нужно рассчитывать на выходное напряжение— для 220 вольт предельное напряжение должно быть не менее 400 вольт, т. е. его выбирают из пленочных типов — вроде К73-17. На остальных многочисленных нюансах, касающихся построения инвертора, мы здесь останавливаться не будем. Заметки на полях Сделаем еще только одно замечание, касающееся идеи стабилизации напряжения. Сигнал для него следует брать непосредственно с высоковольтного выхода, и его можно через резисторный оптрон завести на АЦП контроллера, а получившуюся ве- личину после соответствующего масштабирования вычитать из табличного значения синуса. Для успешной реализации этой идеи необходимо иметь запас по регулирова- нию, для чего можно просто увеличить количество витков (но не слишком, чтобы не
Глава 9. Программирование таймеров 267 искажать сигнал на холостом ходу схемы). Все это требует точных и тонких расчетов, поэтому мы не станем углубляться в подробности, тем более что такой принцип, ко- нечно, не единственный. Другие применения ШИМ На рис. 9.10 показано подключение нескольких групп светодиодов, объединенных по четыре штуки (при питании 12 вольт), к выходу ШИМ через ключевой MOSFET-транзистор. Транзистор IRLU8259 в малогабаритном корпусе ТО-251 хо- рошо приспособлен для подобных целей: при максимально допустимом напряже- нии 25 вольт он способен потянуть ток до нескольких десятков ампер, управляясь непосредственно с 5-вольтового выхода контроллера (чего нельзя сказать про мно- гие более мощные MOSFET-ключи, обязательно требующие управляющего драй- вера). Так как собственное сопротивление канала у него составляет около 9 милли- ом, то вплоть до 10-амперного потребления можно не очень беспокоиться о пере- греве. Конечно, при небольших токах до пары сотен миллиампер MOSFET-ключ можно заменить на обычный п-р-п транзистор типа ВС337 или ВС557, с резистором в базе порядка 1 кОм. +12 В 0С1А выв. 15 ATmega8 20-51 Ом Рис. 9.10. Подключение групп светодйодов к выходу ШИМ Если снизить частоту ШИМ на выходе любым способом — можно увеличение ко- эффициента деления таймера совмещать со снижением тактовой частоты контрол- лера, — то получим медленно загорающуюся и гаснущую гирлянду. Вместо само- дельной группы светодиодов можно, конечно, подключить и фирменную ленту или линейку из тех, что используют в рекламе. Однако тут есть нюанс, который заклю- чается в том, что частота ШИМ не должна быть слишком низкой, — уже установка коэффициента 1:1024 при 8 МГц тактовой частоты даст частоту ШИМ примерно в 30 Гц, что при малых скважностях в начале и в конце периода даст заметное глазу мигание. При этом период собственно синуса будет составлять примерно 20 секунд (0,05 Гц), а для плавного перелива из одного цвета в другой хочется еще больше. Для устранения мигания можно использовать банальный аналоговый RC-фильтр, который, однако, придется как-то пристраивать к самой гирлянде. В случае, пока- занном на рис. 9.10, можно установить конденсатор в несколько сотен микрофарад от точки соединения резистора 330 Ом к «земле». Ставить фильтр между выходом контроллера и затвором (базой) транзистора в общем случае нельзя, т. к. транзи- стор тогда перестанет работать в ключевом режиме, и уже при токах более 20-
268 Часть III. Практическое программирование микроконтроллеров AVR 30 мА придется заботиться о перегреве ключа, и половина энергии источника пита- ния уйдет на бесполезный нагрев окружающей среды. Пристраивать фильтр к светодиодной линейке не всегда возможно (скажем, это не- применимо к фабричным светодиодным полосам, которые питаются от напряже- ния, а не от тока), и конденсатор будет к тому же занимать много места. Потому целесообразно все-таки применить какие-то универсальные программные меры, благо контроллер тут в нашем полном распоряжении. В этом случае стоит растягивать период синуса, не слишком снижая частоту собст- венно ШИМ (величины более 60-100 Гц будут незаметны глазу). Это просто: если мы, ничего не меняя в алгоритме, будем выводить новые значения в регистры срав- нения OCRIjc не каждое прерывание, а каждое, например, четвертое, то увеличим период синуса в четыре раза. При делителе 1:256, когда мигание на глаз еще не заметно, мы получим в этом случае те же 20 секунд периода, которые получились бы при делителе 1:1024. Отслеживать каждое и-е мигание в общем-то очень просто: для этого нужно реали- зовать функцию mod, возвращающую остаток от деления на п какого-нибудь счет- чика. Если остаток равен нулю, то выводим новое значение, если нет — пропуска- ем. Но то, что просто в языке С или в ассемблере, в котором есть аппаратное деле- ние (х;86, jc51), в нашем случае выливается в неоправданно долгую процедуру. В общем случае проще завести счетчик до нужного числа, который после его дос- тижения каждый раз обнуляется. Но для частных случаев выделения моментов, когда счетчик кратен степени двойки (2, 4, 8 и т. д.) в AVR-ассемблере можно при- менить более изящную процедуру. В листинге 9.17 выделяется каждое четвертое прерывание timicompa (cm. его текст в листинге 9.18 далее). Т1М1_СОМРА: inc t_count ;увеличиваем счетчик прерываний andi t_count,ObOOOOOOll /выделяем младшие два бита breq set_OCR ;если результат равен 0 - на установку OCRlx reti ;если нет - ничего не делаем set_OCR: < продолжаем прерывание > Здесь в результате операции andi над значением счетчика прерываний tcount вы- деляются младшие два бита. Если они равны о о, то устанавливается флаг нуля z и происходит переход на продолжение прерывания, иначе ждем до следующего раза. При выполнении операции andi, конечно, сбрасываются остальные биты счетчика, но нас они не волнуют— счет до четырех (01, ю, и, оо) обеспечен. Точ- но так же можно обеспечить счет до 2-х, до 8-ми, до 16-ти и т. д., выделяя соответ- ствующее количество битов (пример с выделением каждого 8-го значения показан в программе ультразвукового дальномера в главе 16).
Глава 9. Программирование таймеров 269 Теперь о том, как можно обеспечить эффект плавного переливания цветов друг в друга. Это гораздо красивее их раздражающего внезапного переключения, и мо- жет быть применено и в рекламе, и в быту — например, в елочных гирляндах. При прямом подключении к каналам А и В двух разных гирлянд светодиодов они будут медленно гаснуть и загораться по очереди, но в середине возникнет пауза, когда обе гирлянды не горят. Чтобы от нее избавиться, хорошо бы сделать так, чтобы по- ка одна гирлянда гаснет, вторая уже начинала загораться. Для этого надо включить оба вывода на непрерывную, без пауз, генерацию синуса, но инвертировать их друг относительно друга — так, чтобы второй вывод начинал сразу с максимума (т. е. генерировал бы косинус, а не синус). Инвертировать можно вывод В, если установить оба бита: comibi : comibo и comibi в регистре tccria. Установим это прямо в секции Reset и сведем установку обоих регистров сравнения в одно прерывание timicompa. Прерывание при этом упро- стится. Изменения в программе (файл в архиве PWMJDC1 J_ed.asm) будут следую- щими (листинги 9.18 и 9.19). В секции Reset заодно устанавливаем предделитель на 1:256. ldi temp, 0Ы0110001 ;COMlAl=bit7;COMlBl=bit5,COMlBl==bit4,WGM10=bitO out TCCRIA, temp ldi temp, ObOOOOllOO ; bit3=WGM12, CS12:CS10=100=l:256 out TCCR1B, temp Прерывание timicompb вычеркиваем, прерывание timicompa начинаем с выделе- ния каждого четвертого (листинг 9.19). Т1М1_СОМРА: /каждое 4-е прерывание inc t_count /увеличиваем счетчик прерываний andi t_count,ObOOOOOOll /выделяем младшие два бита breq set_OCR /если результат равен 0 - на установку OCRlx reti /если нет - ничего не делаем set_OCR: cpi countO,$38 /сравниваем с 312 ldi temp,$01 срс countl,temp brio continueA /если меньше 312, на продолжение ldi ZH,HIGH(N_sin*2) /адрес таблицы sin в памяти - в Z ldi ZL,LOW(N_sin*2) clr countO
270 Часть III. Практическое программирование микроконтроллеров AVR clr countl reti continueA: lpm temp, Z+ ;в temp - число из таблицы out OCR1AH, temp2 out OCR1AL, temp out OCR1BH, temp2 out OCR1BL, temp adiw count0,1;увеличиваем счетчик reti Для улучшения эффекта можно также поиграть с таблицей синуса, исказив его форму «по месту», чтобы переход цветов был более выраженным, — у светодиодов нелинейная характеристика зависимости яркости от тока, и визуально нарастание и спад яркости могут казаться происходящими слишком быстро. Размножить этот алгоритм на более, чем два цвета, чисто программным путем можно, если ввести дополнительные выводы с нужным сдвигом фазы, которыми придется уже управ- лять вручную. Для этого вводят второе прерывание по переполнению (оно в ШИМ- режиме срабатывает в момент достижения значения ТОР) и включают светодиод в нем (т. е. в момент, когда счетчик сбрасывается в ноль), а выключают по старому прерыванию сравнения. Конечно, можно и подключить второй таймер (в ATmega8 это Timer2, в ATtiny2313 — TimerO), но тогда для сдвига фазы на 90 (четыре цвета) или 120 градусов (три цвета) придется, скорее всего, рисовать отдельные таблицы синуса со сдвигом. Пока заканчиваем с таймерами и попытаемся в следующей главе понять, как обра- щаться с энергонезависимой памятью EEPROM, а заодно рассмотрим еще пример использования ШИМ (попроще, чем задача с генерацией синуса).
ГЛАВА 10 Использование EEPROM Энергонезависимая память данных, которая в архитектуре AVR традиционно име- нуется EEPROM, в практической реализации не полностью соответствует этому названию. В реальности это всего лишь специально выделенный кусок флеш- памяти, который EEPROM эмулирует, — т. е., как и положено, имеет организацию с индивидуальной линейной адресацией каждого байта. Видимо, из-за этого при чтении, которое, в отличие от записи, в EEPROM по идее происходит очень быст- ро, здесь наблюдается задержка в сравнении с чтением из SRAM. При программи- ровании памяти по параллельному интерфейсу приходится еще учитывать, что у некоторых моделей EEPROM организована постранично (только страницы эти очень маленькие, всего по 4 байта). При программировании как «снаружи» (по по- следовательному интерфейсу), так и «изнутри» (из программы), страничная струк- тура EEPROM не учитывается, и доступ к ней во всех моделях осуществляется одинаково — побайтно. Размер EEPROM колеблется от 64 байтов в младших моделях Tiny до 4096 байтов в старших Mega. В ATtiny2313 объем EEPROM составляет 128 байтов, и адресация, как и в других моделях с объемом EEPROM 256 байтов и менее, происходит через единственный регистр адреса eear. В подсемействах, основанных на одинаковой структуре, но с разным объемом памяти,— таких, как ATtiny24/44/84 или ATmega48/88/l68/328, адресация осуществляется одинаково, также через пару ре- гистров eearh:eearl, но в младших моделях подсемейства, где объем EEPROM ме- нее 512 байтов, старший из регистров eearh не учитывается и в процедурах адреса- ции может не участвовать. В ATmega8 и ATmega8535 с памятью программ 8 кбайт, на которые мы в основном ориентируемся в этой книге, объем EEPROM — 512 байтов, и адресация производится через пару регистров eeaph:eearl, причем в eearh имеет значение только младший бит. Еще раз о сохранности данных в EEPROM EEPROM и flash-память программ принципиально не различаются и предназначе- ны для хранения данных в отсутствие питания. Однако между ними есть карди- нальное различие — EEPROM может быть перезаписана в любой момент програм-
272 Часть III. Практическое программирование микроконтроллеров AVR мой самого МК. В этом состоит и ее слабость — как мы уже говорили (см. главу 2), при снижении питания до определенного уровня МК начинает совершать непред- сказуемые операции, и, если не принимать меры, EEPROM с большой вероят- ностью может быть повреждена. Для защиты от этой напасти (и вообще от выпол- нения каких-либо операций, которые иногда могут навредить внешним устройст- вам) в AVR предусмотрена система мониторинга питания BOD (см. главы 2 и 5), которая при снижении напряжения питания до некоторого порога (4 или 2,7 В) «загоняет» МК в состояние сброса. Это помогает, но, как показывает опыт, для обеспечения абсолютной защиты данных в EEPROM встроенной системы BOD, к сожалению, недостаточно. Впрочем, нахождение в любом из режимов энергосбережения исключает выполне- ние контроллером недопустимых операций и потому может служить способом обеспечения сохранности EEPROM. В этом смысле наиболее удобен режим Idle (см. главу 14), который не требует никакой предварительной настройки и выключа- ется многими прерываниями: и внешними, и внутренними. Однако применение его не всегда возможно и целесообразно — так, в программе управления яркостью све- тильников, которую мы рассмотрим в конце этой главы, вводить контроллер в ре- жим Idle нельзя, т. к. при этом остановится главный цикл программы, в котором мы отслеживаем состояние кнопок. Самый надежный и проверенный способ предохранения данных в EEPROM — до- бавить к схеме внешний монитор питания (супервизор). Это небольшая микросхе- ма, которая при снижении питания ниже допустимого закорачивает свой выход на «землю». Если питание в норме, то выход находится в состоянии «разрыва» и ни- как не влияет на работу схемы. Присоединив этот выход к выводу RESET, мы по- лучаем надежный предохранитель. Вопрос о том, какие именно мониторы питания предпочесть, мы уже обсуждали в главе 2, а здесь немного добавим в плане практи- ческого применения. При сетевом питании и обычном стабилизаторе типа LM2931 (см. главу 4) или го- товом стабилизированном 5-вольтовом адаптере вообще-то можно обойтись и встроенной схемой BOD. Для ее включения служит ячейка BODEN (см. рис. 5.7- 5.9 и комментарии к ним в главе 5). В такой ситуации схему BOD включают на по- роговое напряжение 2,7 вольта и больше об этом не думают. Можно включить и на 4,1 вольта (дополнительно BODLEVEL = 0), но до 2,7 вольта любой AVR все равно будет работать стабильно, и мы застрахованы от случайных колебаний питания. Но так можно поступать, только если у вас: а) в EEPROM не хранятся данные, существенные для выполнения программы с самого начала (например, калибровоч- ные коэффициенты датчиков), б) если у вас питание от стационарной электриче- ской сети, а не от батареек или аккумуляторов. Потому что в первом случае даже доли процента вероятности нарушения целостности данных в EEPROM недопусти- мы, т. к. могут нарушить работу устройства до полной его неработоспособности. А во втором случае важно то, что электрохимические источники тока «умирают» медленно, причем даже с точки зрения человека, а не только микроконтроллера с его микросекундами. И длящееся часами колебание напряжения вблизи порога стабильной работы устройства может разрушить данные в EEPROM почти гаран- тированно, и никакие BOD не помогут.
Глава 10. Использование EEPROM 273 Подробности Если подходить к вопросу более тщательно, то отсутствие команд записи в EEPROM в программе должно давать дополнительную гарантию целостности данных (о поряд- ке осуществления записи в энергонезависимую память рассказано во врезке «Под- робности» в следующем разделе). На практике это проверить почти невозможно — нужны длительные целенаправленные исследования. Но обратить внимание на этот момент стоит — если вы в программе используете, например, только заранее рассчи- танные индивидуальные калибровочные коэффициенты, то лучше их загружать в кон- троллер сразу вместе с программой (в сегмент .eseg, — см. главу б), а не городить для их загрузки через последовательный порт отдельный программный интерфейс. Это, правда, приводит к вырожденной ситуации, потому что тогда и EEPROM вро- де бы использовать незачем, — коэффициенты можно разместить и править прямо в тексте программы. Потому такой рецепт на практике малоприемлем, особенно когда у вас одинаковых устройств больше одного: не хранить же для каждого свои отдель- ные загрузочные файлы и не таскать с собой программатор повсюду? Так что и в этих случаях все равно приходится заботиться о сохранности данных в EEPROM. Следует еще отметить один факт, о котором забывают упомянуть разработчики ин- тегральных стабилизаторов, — а что происходит, если напряжение на входе стаби- лизатора снижается ниже порога стабилизации (т. е. величины, складывающейся из номинального напряжения стабилизации и минимального проходного напряже- ния)? На самом деле ровным счетом ничего страшного, только стабилизатор, ко- нечно, теряет свои стабилизирующие свойства, и напряжение на выходе начинает повторять напряжение входа. В случае питания от электрохимических источников это очень удобно: стабилизатор служит для ограничения напряжения сверх допус- тимого (допустимые 6,0 вольта для AVR при четырех свежих щелочных батарейках превышаются почти на полвольта), а ниже порога стабилизации сами батарейки вкупе со все равно установленными конденсаторами по питанию служат неплохим буфером для, например, колебаний нагрузки, не допуская резких всплесков и про- валов напряжения. Осталось соорудить препону снижению напряжения ниже допустимого для ста- бильной работы. Супервизоры именно это и делают. Чтобы их выбирать осознанно, сначала рассмотрим, что случается с питанием, если батарейка близка к разряду (в случае аккумуляторов все аналогично, только величины там будут свои для каж- дой разновидности). На рис. 10.1 показаны разрядные кривые для щелочных и литиевых элементов ти- пов АА и ААА по данным фирмы Energizer. Фирмы-производители всегда приво- дят такие кривые для сравнительно больших мощностей разряда— в рассматри- ваемом случае, чтобы подчеркнуть преимущество литиевых элементов, которые при больших токах действительно ведут себя гораздо лучше щелочных. Но нас сейчас интересует не энергоемкость, а форма кривых, которая от тока почти не зависит: при малых токах кривые растянутся по горизонтальной оси и только. Здесь мы видим, во-первых, что истощение щелочных элементов идет полого, и их можно спокойно эксплуатировать вплоть до напряжения около одного вольта на элемент— никаких неожиданностей не возникает. При четырех батарейках (со стабилизатором) можно выбирать упомянутый в главе 2 супервизор DS1813-15 с порогом срабатывания 4,1 В, при трех (без стабилизатора)— DS1818-10 с поро-
274 Часть III. Практическое программирование микроконтроллеров AVR гом 2,9 вольта. При этом вы не очень промахнетесь, если выберете для четырех щелочных элементов из более распространенных стабилизаторов с напряжением 4,35 вольта, а для трех элементов с напряжением 3,1 или 3,3 вольта — немного не- дорасходуете ресурс, и все. 1.6 1.5 > ЛА О 13 |12 > 1.0 0.9 0.8 100 mW Constant Power Discharge Typical Service @ 21 °C -—«^ AAA Alkaline —■' , — v I AA Aikaline 1 J AAA 1 ithium 10 15 Service, Hours 20 25 30 Рис. 10.1. Разрядные кривые для щелочных (Alcaline) и литиевых (Lithium) элементов (по данным фирмы Energizer) Подробности А почему бы не применить вместо внешнего монитора систему BOD, у которой есть нужный порог около 4,1 вольта? Напомним, что схема BOD работает от встроенного ИОН с напряжением 1,1-1,3 вольта, который в Меда имеет разброс примерно 6-7% (подробно об этом рассказано в главе 3). То есть установленный порог 4,1 вольта мо- жет составлять в реальности и 4,4 вольта, и 3,8 — что представляет собой недопус- тимую погрешность даже для щелочных батареек, не говоря уже о литиевых. Индиви- дуальная калибровка, в отличие от применений этого источника в АЦП и аналоговом компараторе, тут ничего не даст, т. к. регулировать порог BOD мы не можем. Вот в ATtiny2313 опорный источник, как мы говорили, почему-то точнее, чем в семействе Меда, и имеет разброс всего 0,9% — там можете попробовать, но я предпочитаю не рисковать (мало ли что там в документации понапишут) и во всех таких случаях при- меняю проверенное решение с внешним монитором. Литиевые батарейки требуют несколько иного подхода. Так как они держат напря- жение, близкое к номиналу, до последнего (см. кривую на рис. 10.1), то выбирать порог сброса нужно тщательнее — чуть выше нужного, и вы отрубите практически неизрасходованную батарейку, а чуть ниже— введете схему в непредсказуемый режим работы с полностью израсходованной батарейкой, готовой обрушиться в ноль при малейшем увеличении потребления (что не очень полезно и без всяких EEPROM). При четырех батарейках (со стабилизатором) можно ориентироваться на распространенный тип супервизора с порогом 4,6-4,65 вольта (DS1813-5), при трех (без стабилизатора) — на порог 3,45-3,6 вольта (не менее!). Последняя разно- видность с фиксированным порогом в продаже практически не встречается, потому придется выбирать супервизор с настраиваемым уровнем срабатывания.
Глава 10. Использование EEPROM 275 Отметим, что даже в доработанных клонах Arduino никакого супервизора, конечно, нет. И тут мы имеем все преимущества, которые особенно выявляются в случае конструирования устройств с автономным батарейным питанием. Подключение монитора питания (супервизора), как мы уже говорили, не представ- ляет проблем — в простейшем случае он ставится просто вместо RC-цепочки по входу RESET (как типы DS1813-1818), в других случаях иногда требуется отдель- ный подтягивающий резистор в несколько килоом. Отметим еще, что можно применить рекомендуемый в фирменных описаниях спо- соб защиты EEPROM переводом МК в состояние пониженного энергопотребления. Это можно сделать, например, если подсоединить монитор питания к выводу внешнего прерывания и все время отслеживать соответствующий бит в регистре gifr, при установке которого в единичное состояние немедленно уводить МК в «спящее» состояние (штатным способом — через обработчик прерывания — это сделать сложнее, т. к. в прерывании команда sleep будет просто проигнорирова- на, — см. главу 14). Но, кроме относительной сложности этого процесса, следует еще учитывать, что при резком снижении потребления напряжение источника не- медленно опять повысится (особенно в случае батарейного питания). И если у вас предусмотрена процедура обратного вывода из этого состояния (например, чтобы схема не «защелкивалась» при случайных бросках напряжения), то при относи- тельно малом значении гистерезиса монитора питания это обязательно вызовет «дребезг» схемы, и неизвестно, что хуже. Увеличить гистерезис можно включени- ем еще нескольких резисторов, но, на взгляд автора, лучше при наличии внешнего монитора обойтись вводом в режим сброса, как более простым и надежным спосо- бом, не требующим программного вмешательства и к тому же автоматически пре- дусматривающим защиту от «защелкивания». Запись и чтение EEPROM Запись и чтение данных в EEPROM осуществляется через специальные регистры ввода/вывода: регистр данных eedr, регистр адреса eear (если объем EEPROM бо- лее 256 байтов, то последний состоит из двух регистров: eearh и eearl) и регистр управления eecr. Основная особенность этого процесса — большая продолжитель- ность процедуры записи, которая для разных моделей AVR может длиться пример- но от 2 до 9 мс, в тысячи раз дольше, чем выполнение обычных команд. Обратим внимание, что, в отличие от записи, чтение формально осуществляется всего за один машинный цикл, даже быстрее, чем из обычной SRAM, — это типичная осо- бенность всех EEPROM, имеющих структуру NOR (в отличие от flash-памяти, ко- торая сейчас сплошь делается на NAND-структурах). Поскольку в AVR это лишь область флеш-памяти, эмулирующая функциональность EEPROM, то в реальности чтение длится несколько дольше (подробно об этом чуть позже), хотя, конечно, да- леко не так долго, как запись. При инициализации записи байта в EEPROM в регистре управления eecr устанав- ливается флаг разрешения записи eewe, по окончании записи он автоматически сбрасывается. Для удобства почти во всех моделях AVR предусмотрено соответст-
276 Часть III. Практическое программирование микроконтроллеров AVR вующее прерывание (отметим, что в семействе Classic его не было). Прерывание EEPROM, если оно разрешено, генерируется по окончании очередного цикла запи- си, когда память освобождается. Использовать его удобно, если нам требуется за- писывать достаточно большие массивы,— например, запись 100 байтов может длиться порядка секунды, и тормозить МК на весь этот период было бы неразумно. Следует быть внимательными: если установить флаг eewe в начале программы, то прерывание сгенерируется немедленно, т. к. условие для его возникновения — ну- левое значение флага eewe, и если ничего не делать, то прерывание (разрешено же!) будет генерироваться постоянно. Следует разрешить прерывание EEPROM где-то по ходу программы, и внутри него осуществлять запись каждого очередного байта. Когда массив заканчивается, прерывания EEPROM запрещают. Такой способ можно назвать «правильным», но он заметно сложнее в применении, чем простой «лобовой» метод, рекомендуемый, кстати, и в фирменном описании. Простой алгоритм состоит в том, что мы запускаем бесконечный цикл ожидания сброса флага eewe и тогда записываем очередной байт (и, кстати, производим чте- ние тоже — если запись не закончилась, читать нельзя). В этом случае, если нам нужно записать всего один байт, МК вообще не будет затормаживаться (перед пер- вой записью доступ к EEPROM свободен), и лишь при записи нескольких байтов подряд будет возникать упомянутая задержка. Факт задержки следует учесть, когда приходится стыковать запись в EEPROM с асинхронными процедурами пересылки данных,— например, если при приеме данных из последовательного порта они поступают быстрее, чем успевают записываться в EEPROM, то некоторые можно потерять. Чтобы такого не случилось, следует записывать данные через буфер в SRAM. Но при достаточно редком обращении к этой процедуре в форме, напри- мер, обновления в EEPROM небольшого количества данных, проще подождать не- сколько лишних миллисекунд, чем путаться в прерываниях без прямой необходи- мости. Процедура записи в EEPROM, которую мы будем использовать, ничем не отлича- ется от приводимой в фирменных описаниях контроллеров, — я только изменил некоторые обозначения регистров (листинг 10.1). WriteEEP: ;в ZH:ZL — адрес EEPROM куда писать ;в temp — записываемый байт sbic EECR,EEWE ; ждем очистки бита rjmp WriteEEP /разрешения записи EEWE out EEARH,ZH ;старший адреса out EEARL,ZL ;младший адреса out ЕЕDR,temp ;данные sbi EECR,EEMWE /установить флаг разрешения записи sbi EECR,EEWE /установить бит разрешения ret ;(конец WriteEEP) Отметим, что процедура разрешения записи двухступенчатая: «лишний» флаг eemwe (официально он называется флагом управления разрешением записи) введен, оче-
Глава 10. Использование EEPROM 277 видно, для дополнительного предохранения EEPROM от несанкционированной записи при сбоях, когда МК может выполнять произвольную последовательность команд. Устанавливаемый программно, флаг eemwe независимо ни от чего сбрасы- вается аппаратно через четыре такта, и если в течение этих тактов флаг eewe не бу- дет установлен, то далее его установка не окажет никакого влияния. Расчет сделан на то, что при выполнении произвольных операций каждый из этих флагов может быть установлен случайно, но случайное совпадение таких действий в нужной по- следовательности практически исключено. Именно поэтому при отсутствии в про- грамме процедуры записи в EEPROM вероятность порчи данных в ней практически равна нулю. А вот при наличии такой процедуры мы своими руками вписываем нужную последовательность, и данные при выключении питания достаточно часто оказываются испорченными. Установленный нами бит разрешения eewe в регистре управления сбросится авто- матически, когда запись закончится, — этого сброса мы и ожидаем в начале проце- дуры. На всякий случай, как мы говорили, то же самое рекомендуется делать и при чтении, но практически всегда (если только мы не читаем непосредственно после записи) это не будет задерживать программу дольше, чем на время выполнения команды sbic, т. е. на два машинных цикла. Сама процедура получается даже коро- че (листинг 10.2). ReadEEP: ;в ZH:ZL — адрес откуда читать ;возврат temp — прочтенный байт sbic EECR,EEWE /ожидание очистки флага записи rjmp ReadEEP out EEARH,ZH ;старший адреса out EEARL,ZL /младший адреса sbi EECR,EERE ;бит чтения in temp,EEDR /чтение ret ;конец ReadEEP В этих процедурах регистр z не играет никакой выделенной роли, — он просто служит удобной парой регистров, которую можно заменить на любую другую. При чтении следует учитывать, что после установки бита eere в единицу контрол- лер пропускает 4 такта перед выполнением следующей команды. То есть сама по себе команда чтения in temp,EEDR выполняется за один такт, но вся вместе про- цедура занимает минимум 5 тактов, без учета ожидания очистки флага и установок адреса. Отметим еще, что если запись производится в основном цикле, то на время записи следует запрещать прерывания, — если между установкой флага eemwe и флага eewe «вклинится» стороннее прерывание, то первый окажется сброшен, и никакой запи- си не произойдет. Если мы используем процедуру записи внутри какого-либо пре- рывания, то, разумеется, об этом можно не думать, поэтому в процедуре, приведен-
278 Часть III. Практическое программирование микроконтроллеров AVR ной в листинге 10.1, команд запрета/разрешения прерываний нет, но в общем слу- чае об этом забывать не следует. Учитывая еще ограниченное числом 100 000 допус- тимое количество обращений к этой памяти, воистину не захочешь лишний раз обращаться к такой капризной сущности, как EEPROM. И это правильно — лиш- ний раз к ней обращаться и не следует, только по необходимости. Подробности Вообще говоря, запись в любую разновидность энергонезависимой памяти осуществ- ляется в два приема: сначала очистка, потом запись нового значения. В ряде моделей контроллеров (в «нашем» ATtiny2313, в «ардуиновских» ATmega88/168/328 и др.) в ре- гистре eecr есть дополнительные биты eepmi и еермо, которые могут делить единую операцию записи в EEPROM на эти отдельные операции. При этом установка этих би- тов в нули (по умолчанию) означает как раз единую т. н. «атомарную» операцию (т. е. неделимую на части), как и во всех остальных контроллерах, где эти биты отсутству- ют. То есть вы можете ни о чем не думать и делать все точно так же, за исключением того, что бит eewe может носить название еере, а бит eemwe — еемре (впрочем, в некото- рых случаях в /пс-файлах указаны оба как синонимы). Процедуры очистки и записи по отдельности протекают вдвое быстрее, чем совмещенная атомарная операция, и их можно разнести по времени: например, перед записью новых значений, когда про- грамма может быть заторможена безболезненно, очистить всю EEPROM и уже потом по очереди заносить в нее новые значения из буфера в SRAM или прямо из последо- вательного порта. Здесь также стоит отметить (см. главу 14\ что инициализация режима Power Down в момент записи в EEPROM не приведет к обрыву этой операции и порче данных, т. к. EEPROM управляется собственным RC-генератором: сначала закончится за- пись, и лишь потом МК перейдет в состояние «сна». Однако при этом RC-гене- ратор может продолжить работу, и, следовательно, режим Power Down будет вве- ден неполностью. Во избежание такого случая следует сначала закончить запись в EEPROM и только потом устанавливать режим энергосбережения. Регулируемый светильник с запоминанием состояния Пример чтения и записи EEPROM мы рассмотрим на основе практического приме- ра использования ШИМ, освоенного нами в предыдущей главе. Для разнообразия построим схему с применением контроллера ATtiny2313, который более уместен в малогабаритном устройстве. Здесь стопроцентная гарантия сохранности данных в EEPROM, которую мы так долго обсуждали в начале главы, нам не требуется, потому будет достаточно включения штатной системы BOD на порог 2,7 вольта (в ATtiny2313 для этого нужно установить одну ячейку BODLEVEL1). Регулятор для светодиодного светильника мы составим из двух кнопок: «больше» и «меньше», подключенных к прерыванию into и inti (PD2 и PD3 — выводы мик- росхемы ATtiny2313 6 и 7). Схема подключения кнопок и контрольного светодиода к ATtiny2313 приведена на рис. 10.2. Выход управления светодиодом— по- прежнему ОС1А (РВЗ в нашем случае, вывод 15 микросхемы), подключение осве- тительных светодиодов или гирлянды — через ключ, как на рис. 9.10 в главе 9 (на схеме, приведенной на рис. 10.2, этот ключ не показан).
Глава 10. Использование EEPROM 279 +5 В «• Кн2 1 Reset 2RXD(PD0) 3TXD(PD1) 4XTAL2(PA1) 5 XTAL1 (РАО) 6 INTO (PD2) 7 INT1 (PD3) 8 PD4 (TO) 9PD5(T1) 10GND Vcc20 SCK19 MISO 18 MOS117 (OC1B)PB416 (OC1A)PB3 15 PB214 (PCINT1)PB1 13 (PCINTO)PBO 12 PD6 11 ATtiny2313 = T •*+5В С2 •» и светильнику V Ud1 Рис. 10.2. Схема соединений ATtiny2313 для управления светильником Из материала предыдущей главы, где активно использовались прерывания, у вас могло сложиться ложное впечатление, что они необходимы для успешной реализа- ции ШИМ. Это неправильное впечатление: точно так же, как в примере с генера- цией частоты (см. листинг 9.3), ШИМ вполне работает и безо всяких прерываний. Так как контроллер в нашем случае ничего не делает, кроме вывода ШИМ-сигнала, который после задания режима генерируется аппаратно, то можно реагирование на кнопки вынести в основной цикл. Реагирование будет происходить так: если нажа- та кнопка «больше», то некая переменная porog с задержкой в 20 мс увеличивается на единицу, записывается в регистр сравнения таймера, и программа возвращается к началу цикла. То же самое происходит, если нажата кнопка «меньше», — только в сторону уменьшения. Дойдя до крайних значений ($ff при увеличении и о при уменьшении), переменная больше не изменяется. При таком подходе за 256*20 мс ~ ~ 5 секунд каждая кнопка, если ее не отпускать, «пробегает» весь диапазон (вместо двух кнопок, конечно, можно установить одну перекидную клавишу без фиксации). Но у нас есть еще задача сохранять текущее значение порога в EEPROM — как это сделать так, чтобы не тратить дефицитный ресурс энергонезависимой памяти по количеству перезаписей? Это только кажется, что 100 тыс. перезаписей— много. Если мы будем записывать каждое изменение счетчика на одну ступеньку из 256-ти, и в сторону увеличения, и в сторону уменьшения, то один прогон обеих кнопок «болыце»/«меныпе» от начала до конца и обратно даст нам 512 перезаписей. То есть контроллер имеет шанс отказать после 200 таких циклов, что вполне обозри- мое количество. Можно использовать прием, который с целью увеличения ресурса применяется в хороших флеш-картах, — текущие адреса записи время от времени сдвигаются по кристаллу в случайном, порядке, чтобы равномерно использовать ресурс. Но можете себе представить, во что выльется этот прием, и целесообразно ли применять его в таком простом устройстве?
280 Часть III. Практическое программирование микроконтроллеров AVR Поэтому мы поступим проще: будем записывать не каждое изменение на ступень- ку, а только то значение порога, на котором пользователь в конце концов остано- вился. Зафиксировать его очень просто— по моменту отпускания кнопки. Для фиксации момента отпускания мы при первом же нажатии для обеих кнопок объ- явим прерывание по фронту (перепаду из нуля в единицу) и, выполнив в нем запись в EEPROM, тут же его запретим. Независимо от того, сколько вы будете держать кнопку нажатой, такое прерывание произойдет только один раз при отпускании. Этот подход снизит количество необходимых перезаписей раз в сто, что уже впол- не приемлемая величина с точки зрения сохранности EEPROM. Сохраненное значение будет прочитано при следующем запуске и сразу записано в регистр срав- нения. Все указанное реализовано в следующей программе (листинг 10.3). Обратите вни- мание, что мы вернулись от жутких мегагерцев с внешним кварцем к спокойному тактированию от внутреннего генератора 1 МГц (не забудьте переустановить fuse- биты CKSEL, а для ATtiny2313 еще нужно включить бит CKDIV8, — см. главу 5). .device ATtiny2313 ; встроенный 1 MHZ .include fltn2313def.inc" .def temp =rl6 .def RazrO = rl7 /разряды задержки .def Razrl = rl8 .def porog = r20 /============ прерывания ============ rjmp RESET /Reset Handle rjmp EXT_INT0 /External InterruptO Vector Address rjmp EXT_INT1 /External Interruptl Vector Address .org INT_VECTORS_SIZE /начинаем с адреса после таблицы векторов /========== программа ============ .macro Delay /процедура задержки ldi Razrl,00 /старший байт N ldi RazrO, (§1/младший байт N R_sub: subi RazrO,1 sbci Razrl,0 brcc R_sub .endm EXT_INT0: /кнопка "больше" отпущена cbi PortD,6 / PD6 = low гасим rcall WriteEEP /пишем текущее значение porog clr temp /запрещаем прерывание out GIMSK,temp reti
Глава 10. Использование EEPROM 281 EXT_INT1: /кнопка "меньше" отпущена сЫ PortD, б ; PD6 = low гасим rcall WriteEEP ;пишем текущее значение porog clr temp /запрещаем прерывание out GIMSK,temp reti WriteEEP: ;в Z#:ZL - адрес EEPROM куда писать ;в porog - записываемый байт sbic EECR,EEWE ; ждем очистки бита rjmp WriteEEP /разрешения записи EEWE out EEAR,ZL ; адрес в tiny2313 однобитовый out EEDR,porog /данные sbi EECR,EEMWE /установить флаг разрешения записи sbi EECR,EEWE /установить бит разрешения ret /(конец WriteEEP) ReadEEP: /в ZH:ZL - адрес откуда читать /возврат porog - прочтенный байт sbic EECR,EEWE /ожидание очистки флага записи rjmp ReadEEP out EEAR,ZL / адрес в tiny2313 однобитовый sbi EECR,EERE /бит чтения in porog,EEDR /чтение ret /конец ReadEEP Reset: ldi temp, low (RAMEND) out SPL,temp /y tiny2313 один байт адреса стека ldi temp,ObOOOOlOOO /PB3=OC1A, - Output out DDRB, temp ldi temp,0b01000000 /PD6 - Output out DDRD, temp ldi temp,0b00001100 /для разряда 2 и 3 порта D INTO,INT1 out PORTD,temp /подтягивающие резисторы на всякий случай ldi temp,0b00001111 /npep. INTO и INT1 на отпускание out MCUCR,temp ldi temp, OblOOOOOOl/ COMlAl=bit7/ WGM10=bit0 8бит FastPWM выв А out TCCR1A, temp ldi temp, ObOOOOlOll / bit3=WGM12, CS12:CS10= 11 = 1:64 out TCCR1B, temp clr ZL /нулевой адрес в EEPROM rcall ReadEEP /извлекаем запомненное значение porog clr temp
282 Часть III. Практическое программирование микроконтроллеров AVR out 0CR1AH, temp out OCRlAL,porog ;заносим в регистр сравнения sei /разрешаем прерывания Pincykle: ;цикл отслеживания кнопок sbis PinD,2 /пропустить, если "больше" не нажата rjmp push_pinO sbis PinD,3 /пропустить, если "меньше" не нажата rjmp push_pinl rjmp Pincykle /вернуться обратно, если не нажата push_pinO: /кнопка "больше" нажата cpi porog,$FF breq Pincykle /если равно $FF не увеличиваем sbi PortD,6 /PD6 = High зажигаем для контроля Delay $13,$88 /$1388=5000 для задержки 20 мс clr temp inc porog /увеличиваем счетчик каждые 20 мс out OCR1AH, temp out OCR1AL,porog /заносим в регистр сравнения ldi temp, (l«INT0) /разрешаем прерывание INTO out GIMSK,temp rjmp Pincykle /вернуться обратно - новый цикл push_pinl: /кнопка "меньше" нажата tst porog breq Pincykle /если равно 0 не уменьшаем sbi PortD,б / PD6 = High зажигаем для контроля Delay $13,$88 /$1388=5000 для задержки 20 мс clr temp dec porog /уменьшаем счетчик каждые 20 мс out OCR1AH, temp out OCR1AL,porog /заносим в регистр сравнения ldi temp, (1«INT1) /разрешаем прерывание INT1 out GIMSK,temp rjmp Pincykle /вернуться обратно - новый цикл Контрольный светодиод на выводе PD6 будет показывать моменты нажатия и отпускания кнопок. Если программа отлажена, то PC0 можно не использовать и удалить или закомментировать соответствующие строки. Заметки на полях Здесь у нас расчет на то, что подобный регулятор встраивается в некий светильник или размещается поблизости от него. В этом случае питание схемы не представляет проблемы. Если же регулятор переделать в автономный пульт, питающийся от бата- реек, то программа так и просит дополнить ее режимом энергосбережения. Это не за- предельно сложно, но тогда придется принципиально поменять алгоритм работы, т. к. из «сна» контроллер надо выводить, а сделать это можно в нашем случае только пре- рываниями (подробно об этом рассказано в главе 14). Красивый замкнутый цикл оп- роса кнопок заменяется на работу в прерываниях, и логика обработки нажатий-отпус-
Глава 10. Использование EEPROM 283_ каний усложнится. Причем для ATtiny2313 внести такие изменения будет проще из-за наличия у него прерываний типа pcint, но дела это принципиально не меняет. Текст этой программы, как обычно, можно найти в архиве по адресу, указанному во введении (файл Bright_control.asm). В тексте закомментированы изменения, которые надо внести в случае использования ATmega8. Аналогичным образом можно реализовать процедуру установки яркости дисплея, например, при динамической индикации на семисегментных линейках. В часах, которые мы показывали в главе 9, можно для этого применить Timer2, который не задействован в индикации и подсчете времени, подключив к его выводу OC2jc клю- чи управления разрядами через сдвиговый регистр или демультиплексор. Ввиду громоздкости такого проекта мы его здесь не рассматриваем. Хранение констант в EEPROM Рассмотрим практический пример хранения в EEPROM калибровочных констант, необходимых для выполнения расчетов, — например, при переводе «сырых» дан- ных, получаемых по каналам измерения, в физические величины. Особенность ра- боты с такими константами в том, что их окончательная величина обычно выясня- ется лишь при калибровке готового прибора, однако при первом включении кон- троллера необходимо иметь какие-то ориентировочные величины, иначе он может просто не заработать. Потому нужно обеспечить и первичную загрузку значений по умолчанию и в дальнейшем возможность замены этих значений на «настоящие». При первичной загрузке можно пойти двумя путями: самый простой заключается в том, чтобы создать ЕЕР-файл по методике, описанной в главе б, и загружать его в EEPROM отдельно. При отладке программы в дальнейшем можно выключить fuse-бит EESAVE, отвечающий за стирание EEPROM в процессе программирова- ния flash-памяти (см. разд. «Конфигурационные ячейки (fuse-биты)» главы 5), и первоначально введенные значения констант будут сохраняться при последую- щих перезаписях программы. Недостаток такого подхода— при каких-то манипу- ляциях в процессе отладки схемы можно, не заметив того, запросто испортить капризную EEPROM (например, случайно подав на какие-то выводы повышенное напряжение) и потом долго недоумевать, почему все перестало работать. Конечно, при отлаженных схеме и программе загрузка данных в EEPROM через отдельный файл предпочтительна с точки зрения простоты. Но за счет усложнения программы можно сделать иначе— тогда содержимое EEPROM по умолчанию будет обеспечиваться автоматически, и вам не придется об этом заботиться даже при случайной ее порче. В таком случае загрузку нужно делать при запуске МК, в процедуре Reset. Но за- писывать константы каждый раз при включении питания не только не имеет смыс- ла (тогда проще их хранить прямо в тексте программы), но и крайне неудобно для пользователя: нет ничего хуже устройства, заставляющего себя инициализировать при каждом сбое питания (не идите на поводу у горе-разработчиков бытовых при- боров, в которых при каждом включении приходится заново устанавливать
284 Часть III. Практическое программирование микроконтроллеров AVR часы, — лучше бы тогда часов не было вообще). Если схема спроектирована верно, и EEPROM надежно защищена от сбоев, автоматическая запись должна произво- диться один-единственный раз при первом запуске контроллера. Кроме того, она должна осуществляться при случайной порче данных. Для этого нам потребуется как-то узнавать, есть ли уже в EEPROM какие-то дан- ные или нет, и правильно ли они записаны. Можно учесть тот факт, что в пустой EEPROM всегда записаны одни единицы (любой считанный байт будет равен $ff), но в общем случае это ненадежно. Наиболее универсальный способ— выделить для этого один какой-нибудь байт в EEPROM и всегда придавать ему определенное значение, а при загрузке МК его проверять. Это не гарантирует 100%-ной надежно- сти при сбоях (т. к. данные в незащищенной EEPROM могут меняться произвольно, в том числе и с сохранением значения отдельных байтов), но мы будем считать, что какую-то весомую вероятность распознавания сбойной ситуации это дает, и нам такой эшелонированной защиты достаточно. Опыт автора показывает, что спроек- тированные таким образом приборы работают годами в режиме 24x7 без единого сбоя. Итак, общая схема алгоритма такова: читаем контрольный байт из EEPROM, если он равен заданной величине (обычно я выбираю чередование единиц и нулей: $аа), то это значит, что коэффициенты уже записаны. Если же нет, то записываем значе- ния «по умолчанию», в том числе и этот контрольный байт. Пусть у нас есть неко- торые значения двухбайтовых коэффициентов ко, ki и т. д. (в отдельных байтах: kohikol, kihikil и т. п.), которые записываются в EEPROM с самого начала (с адре- са 0:0, по которому располагается старший байт первого коэффициента кон), а по адресу $ю записывается контрольный байт, равный $аа. Тогда в программе в конце процедуры начальной загрузки по метке Reset до команды sei (обязательно перед ней, а не после) добавляется текст, приведенный в листинге 10.4. /чтение коэффициентов из EEPROM =========== clr ZH ;ст. адрес =0 ldi ZL,$10 ;адрес контрольного байта rcall ReadEEP cpi temp,$AA ;если он равен $АА breq ram_RK ;то на чтение в ОЗУ rcall ZapisK ;иначе запись значений по умолчанию ram_RK: /извлечение коэфф. из EEPROM в SRAM clr ZL /начальный адрес EEPROM 0:0 ldi YL,AddrK0H /начальный адрес SRAM LoopRK: rcall ReadEEP /читаем байт st Y+,temp /складываем в ОЗУ inc ZL /следующий адрес cpi ZL,8 /всего 4 коэффициента, 8 байт brne LoopRK
Глава 10. Использование БЕРНОМ 285^ Процедура записи коэффициентов по умолчанию, обозначенная как zapisK, может быть вставлена в любом месте программы (листинг 10.5). ZapisK: /запись предварительных коэффициентов по умолчанию clr ZH ;с нулевого адреса в EEPROM clr ZL ;К0 ldi temp,High(КО) ;ст rcall WriteEEP inc ZL ldi temp,Low(КО) ;мл rcall WriteEEP inc ZL ;K1 ldi temp,High(Kl) ;ст rcall WriteEEP inc ZL ldi temp,Low(Kl) ;мл rcall WriteEEP inc ZL ldi ZL,$10 ldi temp,$AA ;все Ok, записьгоаем rcall WriteEEP /контрольный байт ret Манипулируя значением контрольного байта, можно даже определить, предвари- тельные у нас коэффициенты записаны или уже окончательные после калибровки, если вдруг возникает такая задача. Кроме записи констант, наиболее часто EEPROM служит для хранения, например, заводского номера и названия прибора, фамилии конструктора или программиста, названия фирмы-изготовителя и всякой другой полезной информации (сравните с данными, которые извлекает операционная система ПК при подсоединении, на- пример, через USB, устройства plug&play, в спецификациях которого наличие энергонезависимой памяти небольшого объема специально предусмотрено). Мож- но заполнять поле серийного номера и вести базу выпущенных экземпляров. Записывать все эти данные при начальной загрузке не всегда удобно. Кроме того, не забудем, что у нас осталась задача замены калибровочных констант после про- ведения калибровки. Во всех этих случаях данные для записи нужно получать извне (например, через последовательный порт UART) и записывать по мере их поступления. Напомним, что запись в EEPROM может протекать медленнее, чем получение данных через UART, поэтому правильная организация такой процедуры предусматривает буферизацию: полученные данные сначала всем массивом запи- сываются в SRAM, а потом переносятся в EEPROM.
ГЛАВА 11 Аналоговый компаратор и АЦП В AVR достаточно много встроенных возможностей для выполнения операций с аналоговыми величинами: это аналоговый компаратор, который неизменно вхо- дит во все без исключения модели AVR (а в «продвинутом» семействе XMega их даже несколько), и 10-разрядный многоканальный аналого-цифровой преобразова- тель (АЦП)— в семействе XMega он стал 12-разрядным. Преобразование в обрат- ную сторону — цифрового значения в аналоговое — без дополнительных усилий можно осуществлять только с помощью ШИМ-режима таймеров (см. главу 9), хотя, опять же, семейство XMega обладает «настоящими» цифроаналоговыми преобра- зователями (ЦАП), а в Arduino функции ЦАП имеются в основанной на 32-раз- рядном микроконтроллере ARM-типа плате Arduino Due. Справедливости ради заметим, что на практике задача цифроаналогового преобразования возникает зна- чительно реже аналого-цифрового. В этой главе мы рассмотрим аналого-цифровые преобразования с помощью и ана- логового компаратора, и АЦП. Но сначала познакомимся с принципом аналоговых операций, «подводными камнями», которые могут нас поджидать на этом пути, а также с основной терминологией по этому вопросу. Вообще-то, проведение изме- рений с помощью электронных приборов — совершенно отдельная и большая тема, не имеющая прямого отношения к контроллерам, как таковым, но мы тут вынуж- дены изложить хотя бы ее самые-самые основы, иначе к АЦП будет обращаться незачем. Аналоговые операции: понятие погрешности и построение градуировочных уравнений Такое впечатление, что современные инженеры-цифровики о погрешностях ника- кого понятия иметь не должны. Arduino для проведения аналоговых измерений фактически требует всего лишь одной строчки программного кода — вызова функ- ции anaiogRead (). Это порождает мнимую уверенность в том, что все произойдет само по себе, и никаких знаний об погрешностях тут не требуется. Такой подход был бы еще простительным в отношении программистов, которые об аналоговых сигналах знают только, что они измеряются в вольтах и милливольтах, а до осталь-
Глава 11. Аналоговый компаратор и АЦП 287 ного им нет никакого дела. Но совершено непростительно, когда речь идет о произ- водителях различных датчиков, которые, чтобы не перенапрягать утомленные про- граммистские мозги излишними подробностями, с большой охотой либо вообще «забывают» указать, в каких пределах и с какой реальной точностью их продукция работает, либо приводят заведомо ложные данные. В результате, например, из по- пулярного в среде Arduino семейства датчиков температуры-влажности с общим названием DHT/SHT (DHT — вариант SHT с однопроводным интерфейсом) в ре- альных устройствах можно применять только дорогие и редкие SHT-75, остальные (популярные DHT-11, DHT-22 и т. д.) в лучшем случае можно использовать в каче- стве комнатного термометра, но и при этом требуется дополнительная калибровка «по месту». Здесь мы, конечно, не станем излагать основы метрологии со всей полагающейся матстатистикой (интересующихся отсылаю к книге [10]), остановимся только на смысле основных понятий. Эти моменты следует учитывать, приобретая датчики и прочие аналоговые компоненты, а также принимать во внимание в расчетах, разра- батывая свои устройства. Необходимость элементарных знаний в области метроло- гии для любителя можно пояснить на примере инструкции к мультиметру: пусть там записано, что погрешность измерения напряжения составляет 0,5% на пределе 2 вольта. Если вы сходу правильно ответите на вопрос, насколько в абсолютных единицах (вольтах или милливольтах) конкретная величина, показываемая прибо- ром (например, 1,000 В), может отличаться от истинной, значит, этот раздел вам можно не читать. Измерительные схемы характеризуются тремя основными параметрами: точ- ностью, разрешающей способностью и стабильностью (временным дрейфом). Что такое точность или обратная ей величина — погрешность, понятно интуитив- но. Разрешающая же способность (иногда говорят о чувствительности) — это по- просту минимальная разница в значениях измеряемого параметра, которую мы еще можем различить. Для аналоговых приборов (стрелочных или, например, жидкост- ных термометров) это половина самого мелкого деления шкалы, а для цифровых — единица самого младшего разряда. Естественно, повышать точность сверх разре- шающей способности бессмысленно. В описаниях датчиков и приборов вам могут встретиться следующие три вида по- грешностей: □ абсолютная погрешность — в единицах измеряемой величины; □ относительная погрешность— абсолютная, но выраженная в процентах от значения измеряемой величины; □ относительная приведенная погрешность — абсолютная, но выраженная в про- центах от всего диапазона измерений. Последняя величина, если она соответствует стандартному ряду (например, 1,0; 0,75; 0,5; 0,25; 0,1 и т. п.), еще называется классом точности и обычно указывается в технических описаниях приборов. Зная это, мы можем грамотно ответить на поставленный вопрос: если погрешность мультиметра на пределе 2 В составляет 0,5% (без дополнительных оговорок это
288 Часть III. Практическое программирование микроконтроллеров AVR всегда означает относительную приведенную погрешность), то любое показывае- мое им значение на этом пределе (в том числе указанное нами ранее значение 1,000 В) отклонится от истинного значения не более, чем на ±10 мВ. Достаточно часто для импортных мультиметров вы можете встретить и такую запись в инст- рукции: «точность указывается как ±% от измеренного ± количество единиц млад- шего разряда», т. е. погрешность в этом случае просто относительная (не приведен- ная). Тогда правильный ответ при указании точности на пределе 2 В, равной ±0,5% ±1, будет звучать иначе: измеренное значение равно 1,000 В ±5 мВ ±1 еди- ница младшего разряда. В случае АЦП погрешность всегда указывается абсолютная, т. е. не в процентах, а в этих самых единицах младшего разряда (LSB). В главе 3 мы говорили, что погреш- ность по всей шкале для встроенного АЦП МК AVR может составлять до ±2 LSB плюс погрешность нелинейности, которая составляет ±0,5 LSB. Суммарная по- грешность ±2,5 младшего разряда— это много, фактически она снижает разряд- ность АЦП с десяти разрядов до менее чем восьми. Как видите, даже если мы при- мем все меры подавления «дребезга» на выходе АЦП, все равно относительную погрешность лучше, чем 7256 ~ 0,4%, не получим. То есть фактический класс наших приборов на основе встроенного АЦП не поднимется выше 0,5. Это не так уж и плохо, поскольку в большинстве случаев для бытовых нужд такой точности вполне достаточно. Но не пытайтесь сделать на основе этого АЦП научный или производ- ственный прибор для профессиональных целей— там такой точности может не хватить, и никакая супертщательная калибровка не поможет. Заметки на полях Насколько вообще целесообразно стремиться к высокой абсолютной точности изме- рений? Я вас могу удивить, но буду утверждать, что в большинстве практических слу- чаев точное значение абсолютной величины — в определенных пределах, разумеет- ся, — не представляет особого интереса. При измерении температуры единственное исключение для бытовых приборов — точка замерзания воды, потому что она прове- ряется непосредственно. Есть еще медицинские термометры, которые обязаны иметь достаточно высокую абсолютную точность,— не хуже 0,1 градуса в пределах своей шкалы (от примерно 34 до 43 градусов). Но в других случаях обычно нам неважно, 9 градусов на улице или 11, главное — весна, и можно снимать шубу. Для комфортно- го восприятия измеряемой величины стабильность (воспроизводимость) важнее абсо- лютной точности. С другой стороны, обычно нет никакого смысла конструировать вы- сокоразрешающие, но неточные приборы — если мы очень сильно увеличим разре- шающую способность по сравнению с точностью, то рискуем попасть в ситуацию, когда десятые градуса просто будут мельтешить на дисплее, что еще хуже, чем если бы их не было вовсе. Среднее значение и градуировочные уравнения Другая типовая задача из области метрологии в электронике — вычислить среднее из ряда измерений одной и той же величины. Среднее арифметическое из массива измерений вычисляется по всем хорошо известной со школы формуле, в которой сумма отдельных измерений х делится на число измерений т
Глава 11. Аналоговый компаратор и АЦП 289 Эта формула хорошо работает для фильтрации случайного шума с небольшими ам- плитудами («дребезга» показаний), который всегда имеет место в любых измерени- ях, и в цифровых в особенности. Производя измерение некоего параметра в одной определенной точке, вы выполняете его несколько раз без изменения условий и вы- числяете среднее, что и принимается за истинное значение измеряемой величины в этой точке. Подробности Для быстро и непредсказуемо меняющихся величин есть специальный способ осред- нения, который позволяет сгладить резкие скачки показаний на дисплее, — так назы- ваемый «метод скользящего среднего» (от англ. moving average, MA). Для его реали- зации вы создаете в памяти массив емкостью, например, 8 или 16 значений измеряе- мой величины, и на начальном этапе заполняете его текущими значениями. После заполнения можно приступать к собственно осреднению: выполнив каждое измерение, вы сдвигаете массив «влево», теряя самое старое из значений, и помещаете на осво- бодившееся место новое измеренное значение. Затем вычисляете среднее из обнов- ленного массива и посылаете результат, например, на индикацию. За число измере- ний, равное объему массива, значения в нем обновляются полностью. Существует ряд модификаций этого метода, отличающихся способами формирования массива, вычисления текущего значения, взвешивания исходных данных и т. п. Метод можно совмещать с вычислением среднего арифметического из группы измерений, помещая в массив уже усредненные величины. Подчеркнем, что речь не идет о случайном «дребезге» показаний, который всегда со- провождает цифровые измерения: если величина сама по себе меняется плавно, то для сглаживания мелких флуктуации вполне достаточно обычного среднего арифме- тического. МА-метод используется для сглаживания случайных выбросов данных, час- тота появления которых относительно мала, а амплитуды велики, из-за чего результа- ты простого осреднения все время «дребезжат», и увеличение периода осреднения уже не меняет картину. Излишне говорить, что метод весьма громоздок в реализации даже на языках высокого уровня, и в обычных случаях фильтрации случайного шума с относительно небольшими амплитудами выбросов его никто не применяет. Но бы- вают ситуации, когда без метода скользящего среднего не обойтись — пример дает непростая задача усреднения значений направлений, которые выдает флюгер, мечу- щийся на ветру. Если измеряемых точек много по всему интервалу измерений, то по ним строится уравнение градуировочной кривой,— некое обобщение понятия среднего, только в интервале значений. По этому уравнению можно вычислить нужные коэффици- енты пересчета для какого-либо датчика, чтобы прибор показывал физические величины, — выполнение этой задачи и называется калибровкой. С построением такого уравнения (оно еще называется регрессионным) сложнее, чем со средним арифметическим, — принцип там простой и носит название метода наименьших квадратов (провести кривую так, чтобы сумма квадратов отклонений значений, вычисленных по уравнению, от экспериментальных была минимальной), но весьма громоздкий в реализации. В основе его лежит подбор коэффициентов ах некоего полинома вида: Причем для простых линейных датчиков обычно первой степени достаточно, и по- лином вырождается во всем знакомое уравнение прямой линии: у = а0 + ахх,
290 Часть III. Практическое программирование микроконтроллеров AVR где а0 — значение у при х9 равном нулю (нулевой коэффициент), а а\ часто называ- ют крутизной преобразования, В подробности реализации метода наименьших квадратов для построения градуи- ровочных уравнений по экспериментальным данным вдаваться бессмысленно, т. к. его в современных условиях обычно представляют в виде ютовой программы (та- кую программу под названием RegrStat, специально приспособленную для нужд калибровки электронных датчиков, вы можете скачать из раздела Программы мо- ей домашней странички, расположенной по адресу http://revich.lib.ru). Умеет строить простейшие регрессионные зависимости (правда, только первой степени) и Microsoft Excel, и, конечно, всяческие «матлабы». В ряде случаев измерения уже бывают проведены за вас, и по крайней мере усредненные значения коэффициентов для ориентировочных расчетов вы можете получить из документации на датчики. Заметки на полях Построение удобного для расчетов уравнения, описывающего кривую, проходящую через экспериментальные точки, называется аппроксимацией. Упрощенной разновид- ностью аппроксимации является интерполяция (расчет промежуточных значений ме- жду заданными — обычно в виде таблицы — точками). Если у вас имеется достаточно большой массив экспериментальных данных, равномерно распределенных по рабо- чей области датчика, а зависимость имеет выраженный нелинейный характер, то при- менение интерполяции может оказаться с точки зрения вычислений выгоднее расчета полинома, аппроксимирующего все точки сразу: вы просто располагаете таблицу зна- чений в памяти и на ее основе всякий раз рассчитываете интерполированное значе- ние в нужной точке. Следует предостеречь читателя от применения еще одной разновидности аппрокси- мации — экстраполяции, т. е. расчета на основе аппроксимирующего полинома зна- чений вне области, в которой производилась калибровка. Для полиномиальной ап- проксимации методом наименьших квадратов экстраполяция принципиально не ра- ботает— вне расчетной области полученный полином может давать совершенно ложные значения. Поэтому, если у вас нет объективных причин полагать, что вне об- ласти, в которой производилась калибровка, данные будут описываться той же зави- симостью, в калибровку следует обязательно включать крайние значения диапазона измерений. Иногда в документации прямо указывают коэффициенты ах, иногда приводят гра- дуировочные таблицы соответствия физической величины электрическому сигналу на выходе, по которым эти коэффициенты можно вычислить. Для самых простых линейных датчиков указывают две крайние величины: значения сигнала у0 при х = 0, которое равно коэффициенту а0, и утах при jcmax — максимальном значении шкалы, из которых коэффициент а\ получается простым делением на калькуляторе: (Утах -.Уо)/*тах. Но не забывайте, что вы таким образом получите только зависимость для электрического сигнала на выходе аналогового датчика, а для получения физи- ческих величин на выходе контроллера его еще нужно «пропустить» через коэффи- циенты преобразования АЦП, и в целом задача оказывается не такой уж и простой (о чем далее).
Глава 11. Аналоговый компаратор и АЦП 291 Аналого-цифровые операции и их погрешности Основной принцип оцифровки любых сигналов очень прост (рис. 11.1, а). В неко- торые моменты времени: t\912, h мы берем мгновенное значение аналогового сигна- ла и как бы «прикладываем» к нему некоторую меру, линейку, проградуированную в двоичном масштабе. Обычная линейка у нас содержит крупные деления (метры), поделенные на десять частей (дециметры), каждая из которых также поделена на десять частей (сантиметры), и т. д. Двоичная линейка содержала бы деления, поде- ленные пополам, затем еще раз пополам и т. д. (насколько хватит разрешающей способности)1. Если вся длина такой линейки составляет, допустим, 2,56 м, а самое мелкое деление 1 см (т. е. мы можем измерить длину с точностью не хуже 1 см, точнее, даже половины его), то таких делений будет ровно 256, и их можно пред- ставить двоичным числом размером 1 байт, или 8 двоичных разрядов. Принцип не изменится, если мы измеряем не длину, а напряжение, ток или сопротивление, только смысл понятия «линейка» будет каждый раз иной. Так мы получаем последовательные отсчеты величины сигнала: х\9 jc2, х$. Причем заметьте, что при выбранной разрешающей способности и числе разрядов мы мо- жем измерить аналоговую величину не больше некоторого значения, которое соот- ветствует выбранному масштабу. Иначе придется или увеличивать число разрядов (длину линейки), или менять разрешающую способность в сторону ухудшения (растягивать линейку). Все изложенное и есть сущность работы аналого-цифрового преобразователя (АЦП). t,c (с Рис. 11.1. Операции с аналоговыми сигналами: а — основной принцип АЦП; б — обратная операция, ЦАП Если мы оцифровываем какую-то меняющуюся во времени величину, например звуковой сигнал, то приходится производить измерения регулярно. В этом случае можно говорить о временном разрешении преобразования с определенным бит- 1 Любопытно отметить, что дюймовые линейки также традиционно используют двоичный принцип: на них дюйм поделен на части в полдюйма, четверть дюйма, одну восьмую дюйма и так далее.
292 Часть III. Практическое программирование микроконтроллеров AVR рейтом и об искажениях частотного спектра сигнала. С помощью встроенных средств AVR подобные преобразования осуществимы лишь для относительно мед- ленно меняющихся сигналов (для качественной оцифровки звука АЦП и аналого- вый компаратор в AVR недостаточно скоростные), потому мы остановимся лишь на задаче, когда требуется преобразовывать статический сигнал при измерении аналоговой величины (т. е. когда измерения проводятся достаточно редко, и сама по себе их регулярность роли не играет). Обратная задача (цифроаналоговое преобразование) проиллюстрирована на рис. 11.1,6. Сущность ЦАП в том, что мы выражаем двоичное число в виде про- порциональной величины напряжения, т. е. занимаемся, с точки зрения теории, все- го лишь преобразованием масштабов или, иначе, физическим моделированием аб- страктной величины — числа. Вся аналоговая шкала поделена на кванты — града- ции, соответствующие разрешающей способности нашей двоичной «линейки». Как мы видим из рис. 11.1, правый график представляет левый, мягко говоря, весьма приблизительно. Чтобы повысить степень достоверности полученной кривой, сле- дует увеличить разрядность преобразования. Тогда ступеньки будут все меньше и меньше, и есть надежда, что при некотором достаточно высоком разрешении кри- вая станет, в конце концов, неотличимой от исходной непрерывной аналоговой линии. Ясно, что сколь угодно увеличивать разрешение нельзя — число разрядов АЦП ог- раничено. Поэтому в любом АЦ-преобразовании обязательно присутствует по- грешность квантования, связанная с разрядностью АЦП. Но это не единственная составляющая общей погрешности преобразования. Кроме нее есть случайная по- грешность— абсолютная погрешность по всей шкале, которая для встроенного АЦП МК AVR может составлять, согласно документации, до ±2 младших разрядов (LSB), погрешность нелинейности шкалы (в АПЦ AVR она достаточно мала: ±0,5 LSB) и дополнительная случайная погрешность, связанная с наводками и шу- мами. Для борьбы с последней принимают специальные меры. На сигнальном входе сле- дует фильтровать сигнал, устанавливая небольшой конденсатор. Если аналоговая часть МК питается от того же источника, что и цифровая, то аналоговое питание следует также фильтровать установкой LC- или RC-фильтров. Не рекомендуется использовать свободные выводы того же порта, к которому подсоединен АЦП (обычно это PortC или PortA), для обработки цифровых сигналов во время преобра- зования. Кроме того, имеется возможность вообще остановить МК на время преоб- разования через режим ADC Noise Reduction, о котором далее. Заметки на полях Не следует забывать и о том, что для более полной реализации возможностей имею- щегося АЦП нужно стремиться к тому, чтобы пределы изменения измеряемой величи- ны были как можно ближе к диапазону АЦП — длине нашей двоичной линейки. При- чем именно пределы изменения, которые несут информацию, а не полный размах аналогового сигнала: если сигнал изменяется, к примеру, от 4 до 4,5 В, и мы его будем измерять с помощью АЦП с диапазоном от 0 до 5 В и разрешением 10 двоичных раз- рядов (что соответствует минимальному делению линейки в 5 В/1024 « 5 мВ), то мы ухудшим результат с точки зрения разрешения ровно в 10 раз: на 0,5 В реального
Глава 11. Аналоговый компаратор и АЦП 293_ диапазона изменения измеряемой величины придется лишь 100 градаций. А неизмен- ную «подставку», соответствующую величине 4 В, ниже которой исходная величина никогда не опускается, в процессе расчета физического значения всегда можно про- сто прибавить, получив таким образом результат с наибольшим возможным разреше- нием. Чаще всего эта величина постоянной «подставки» роли не играет — например, медный термометр сопротивления всегда имеет определенное значение сопротивле- ния при минимальной температуре диапазона, и оно в общем случае не несет никакой информации. Однако для того, чтобы такую подгонку диапазонов осуществить, неред- ко приходится идти на заметное усложнение предварительной аналоговой схемы, да и не всегда задача получения именно наивысшего разрешения стоит так уж остро. При- мером может служить рассматриваемый далее датчик атмосферного давления, диа- пазон которого в мм рт. ст. как раз примерно соответствует десятибитовой шкале, даже с некоторым запасом, поэтому вычитать «подставку» в 600 или 700 мм рт. ст., ниже которой давление в той или иной местности не опускается, оказывается нецеле- сообразно. На практике для борьбы со случайными флуктуациями результата измерений, ко- торые, как показывает опыт, «вылезут» обязательно, невзирая ни на какие приня- тые меры, можно применить еще один прием — усреднение нескольких измерений, и о нем мы также поговорим далее. У неопытных (а иногда и у опытных) пользователей часто возникают проблемы с пересчетом кодов АЦП в значения напряжения на входе. Проблема заключается в том, что шкала 10-разрядного АЦП по определению имеет 1024 градации, а мак- симальное значение кода, которое АЦП может выдать, равно 1023. Пользователь предполагает, что именно максимальный код 1023 должен соответствовать величи- не опорного напряжения (£/ref), откуда ошибочно выводит, что цена одного разряда равна f/ref/1023. Если вы заглянете в «даташит» любого AVR, имеющего АЦП в своем составе, то найдете там формулу пересчета напряжения в код, которая в наших обозначениях выглядит так: ADC = (С/вх- 1024)/f/ref. Из нее вытекает правильная формула для цены разряда: £/ref/1024. Как это совместить с тем фактом, что максимальный возможный код равен 1023? А очень просто — максимальное измеряемое значение напряжения Umax (соответствующее этому коду) будет не равно опорному, а на цену одного раз- ряда меньшей: Umax = Uon-1023/1024 (в «даташите» это написано прямо: «...and 0x3FF represents the selected reference voltage minus one LSB»). Ситуация точно та- кая же, как если бы мы с помощью только одноразрядных десятичных цифр попы- тались бы разметить сантиметровую линейку: с помощью десяти цифр от 0 до 9 получается максимальная длина линейки в девять сантиметров, десятое деление будет одиннадцатым по счету, и для него требуется число за пределами диапазона. Но от того, что мы не можем его вывести, например, на одноразрядный десятичный индикатор, ничего не меняется: так, середина десятисантиметровой линейки все равно придется на деление номер 5. И в нашем случае 10-разрядного кода середина измеряемого диапазона будет равна коду 512 (а не 511 и, боже упаси, не 1023/2 = 511,5!), который точно соответствует f/ref/2. Приведем еще для лучшего за- поминания аналогичные цифры для часто встречающегося случая 8-разрядного АЦП: цена деления £/ге/256, максимальное измеряемое напряжение f/ref 255/256, середина соответствует числу 128 (а не 127!). Для 16-битового АЦП (65 536 града- ций) середина соответствует числу 32 768 и т. д.
294 Часть III. Практическое программирование микроконтроллеров AVR В примере с АЦП цена одной градации при опорном напряжении ровно 1 вольт (1000 мВ) будет равна 1000/1024 = 0,9765625 мВ, максимальное значение будет равно 1023 0.9765625 = 999,0234375 мВ, а середина диапазона— 5120.9765625 = = 500 мВ. Чтобы лучше понять, представьте, что у вас есть опорный источник напряжением 4,096 вольта (такие реально существуют). Разумеется, цена одной градации 10-битного АЦП при этом будет 4 мВ ровно, а максимальное значение, которое может выдать такой АЦП, равно 1023*4 = 4092 мВ. При этом середина диапазона будет соответствовать числу 512, т. е. в нашем примере ровно 2,048 В. Заметки на полях Заметим в скобках, что все эти цифры есть результат соглашения проектировщиков АЦП в целях облегчения расчетов. Им ничего не стоит подкрутить коэффициенты преобразования под любой требуемый масштаб, в том числе и такой, когда макси- мальное возможное значение кода точно соответствует опорному напряжению. Но во что бы тогда превратились расчеты? Как мы говорили, есть специально выпускаемые источники опорного напряжения с точным значением 4096 или 2048 мВ. В нашем слу- чае 10-разрядного АЦП цена разряда тогда будет ровно 4 или 2 мВ, а максимальное измеряемое напряжение — 4,092 и 2,046 вольта соответственно. А в «подкрученном» варианте цена разряда оказалась бы не круглым числом, и преобразования кодов в физические величины неимоверно бы усложнились, учитывая еще и ограниченные возможности 8-разрядных контроллеров. И смысла в приобретении таких относитель- но дорогих источников не было бы. До сих пор мы говорили в основном о встроенном АЦП. Но в AVR есть еще анало- говый компаратор. Давайте рассмотрим типовые применения компаратора, а потом уже опять вернемся к АЦП и рассмотрим его более подробно. Работа с аналоговым компаратором Аналоговый компаратор сравнивает две величины напряжения, установленные на его входах, и, в зависимости от их соотношения, устанавливает выход в одно из двух логических состояний. По сути это элементарный АЦП — однобитный преоб- разователь аналогового напряжения в цифровой сигнал. Во всех без исключения схемах АЦП любого типа (интегрирующих, дельта-сигма, последовательного при- ближения, непосредственного преобразования, в том числе и в самых наворочен- ных DSP-контроллерах) компараторы всегда служат основой. Нет особых проблем построить собственное АЦП, используя аналоговый компаратор контроллера, и когда-то так и делали, но вряд ли кто-то этим сейчас будет заниматься. Ввиду дос- тупности встроенных и внешних АЦП самого разнообразного типа, а также спе- циализированных DSP-контроллеров для обработки аналоговых сигналов, само- стоятельное изобретение аналого-цифровых преобразователей потеряло всякий смысл. Из-за этого про существование встроенного аналогового компаратора обычно за- бывают, и, например, в Arduino нет даже встроенных функций или стандартных библиотек для работы с ним. Тем не менее и сейчас аналоговый компаратор может пригодиться для выполнения каких-то элементарных действий. В аналоговой элек- тронике на основе компараторов обычно строят простые релейные регуляторы или
Глава 11. Аналоговый компаратор и АЦП 295 используют их в автоматических устройствах для слежения за превышением какой- либо величиной порогового значения. Рассмотрим функциональность встроенного компаратора, а потом приведем пример его простейшего использования. Устройство компаратора Входы компаратора обычно маркируются знаками «плюс» и «минус» (и называют- ся положительным и отрицательным, или, иначе, неинвертирующим и инверти- рующим). Если напряжение на положительном входе превышает напряжение на отрицательном, то выход компаратора устанавливается в логическую единицу, и наоборот — если напряжение на отрицательном входе больше, чем на положитель- ном, то выход устанавливается в логический ноль. В AVR эти входы называются AIN0 (положительный) и AIN1 (отрицательный). На схемах я в дальнейшем для ясности обозначаю их AIN+ и AIN-. Для успешной работы они должны быть уста- новлены в состояние по умолчанию: сконфигурированы на вход, а подтягивающий резистор отключен (т. е. его нельзя использовать в качестве одного из резисторов делителя по входу, если такой требуется в схеме). Ошибка аналогового компаратора AVR (напряжение смещения) — не более 40 мВ во всем диапазоне температур (реально гораздо меньше), время отклика — не более 0,5 мкс. Входной ток по каждому из входов не более 50 нА, поэтому к ним можно смело подключать резистивные делители в сотни килоом и даже единицы мегаом, не боясь слишком сильно увеличить ошибку смещения (вот у АЦП входное сопро- тивление из-за наличия устройства выборки-хранения гораздо ниже, и сопротивле- ния более 10 кОм там подключать не рекомендуется, — см. далее). Только, если вы используете совместно с компаратором такие значения, не забудьте зашунтировать вход дополнительным керамическим конденсатором, чтобы не вводить лишнюю составляющую дребезга из-за наводок на высокоомных резисторах. Напряжения на входах не должны выходить за пределы напряжения питания. Аналоговый компаратор может также работать совместно с АЦП, если он имеется в вашей модели МК,— инвертирующий вход может подключаться к одному из входов АЦП. Подробности приведены в техдокументации, а здесь отметим, что эта функция может пригодиться, например, для определения знака какой-то физиче- ской величины или для сигнализации о ее выходе за допустимые пределы. Еще одну интересную возможность предоставляет функция подключения компаратора ко входу захвата Timer 1, что позволяет с его помощью построить формирователь входных импульсов для измерения длительности временных интервалов или для подсчета событий (см. главу 9). Во всех моделях МК AVR компаратор управляется одинаково — через единствен- ный регистр acsr. Бит 7 (acd) этого регистра управляет включением компаратора, причем нулевое его состояние (по умолчанию) означает, что компаратор включен. Поэтому для энергосберегающих режимов его нужно специально выключать. Положительный (неинвертирующий) вход компаратора путем установки бита acbg может подключаться ко все тому же внутреннему источнику опорного напряжения величиной 1,2-1,3 В (в ATmega8/8535) или 1,1 В (ATtiny2313), от которого работа-
296 Часть III. Практическое программирование микроконтроллеров AVR ет и система BOD (подробности о ней приведены в главе 3). Тогда напряжение на отрицательном входе будет сравниваться с этой величиной, что позволяет упро- стить внешнюю схему. Как мы помним, источник имеет неприятный недостаток: разброс его значения от экземпляра к экземпляру для семейства Mega может дости- гать ±0,08 В (около 6-7%). В некритичных к таким ошибкам задачах внутренний источник подойдет без дополнительных усилий, но обычно приходится схему калибровать индивидуально. При подключении неинвертирующего входа компара- тора к внутреннему источнику вывод порта, соответствующий AIN0, можно задей- ствовать для других целей. При смене состояния выхода аналоговый компаратор может генерировать преры- вание. Для этого нужно задать бит разрешения прерывания acie (бит з регистра acsr). Два младших бита: asisiiasiso регистра acsr устанавливают событие, вызы- вающее прерывание, — когда оба равны нулю (по умолчанию), то прерывание вы- зывает любой перепад уровней на выходе компаратора, как из 0 в 1, так и обратно. При этом состояние выхода компаратора в любой момент можно узнать, прочитав бит АСО (бит 5). Система контроля батарейки Ориентируясь на эти сведения, попробуем решить простейшую задачу слежения за напряжением резервной батарейки с сигнализацией состояния, когда она требует замены. Предположим, у нас имеется батарея 4,5 В, составленная из трех щелочных ААА-элементов. Признаком неработоспособности батареи считаем падение напря- жения ниже 1,1 В на элемент (в сумме 3,3 В — см. разрядные кривые на рис. 10.1). Контролировать состояние батареи мы будем тогда, когда схема подключена к се- тевому источнику, — когда МК переключается на батарею, следить за ее своевре- менной заменой уже поздно. Схема для этого случая приведена на рис. 11.2. Батарея Б1 и внешний источник пи- тания на стабилизаторе LM2931 соединены по типовой схеме с развязкой на диодах Шоттки (КД922) с малым собственным падением напряжения (~0,2 В), обеспечи- вающей автоматическое переключение при исчезновении сетевого питания. Погрешность от разброса значений встроенного источника опорного напряжения (ИОН) имеет достаточно большую для наших целей величину: 6-7% ошибки дадут разброс срабатывания компаратора от примерно 3,1 до 3,5 вольта. Внешний преци- зионный источник для такой цели использовать как-то уж слишком круто, потому остается либо подгонять пороги срабатывания «по месту», либо наплевать на раз- брос и оставить все, как есть. Заметки на полях Обычный стабилитрон в качестве внешнего ИОН, как это часто делают, ставить не следует: в стабильности вы даже проиграете в сравнении со встроенным, а разбросы у них сравнимы, и к тому же они требуют достаточно большого тока для поддержания номинального напряжения на выходе. Есть еще недорогие малопотребляющие преци- зионные опорные диоды (вроде LM385), но они ничем не отличаются от встроенного ИОН (даже напряжение такое же), и ставить их бессмысленно. Поэтому мы остано- вимся все-таки на встроенном ИОН.
Глава 11. Аналоговый компаратор и АЦП 297 +12-14 В +5 В Вход Выход LM2931 Общий 100,0 Т VD1-VD2 КД922А PCS 28 РС4 27 РС3 26 РС2 25 РС124 РС0 23 GND22 AREF 21 AVCC 20 SCK19 MISO 18 MOS117 РВ216 РВ1 15 Рис. 11.2. Схема отслеживания напряжения резервной батареи на примере контроллера ATmega8 В изначальном состоянии, когда питание в порядке, напряжение батареи, посту- пающее через делитель R2/R3 на инвертирующий вход AIN1 (AIN- на схеме), вы- ше напряжения ИОН, выход компаратора находится в состоянии логического нуля (вход «+» компаратора отключен от вывода AIN+, напряжение ИОН поступает на внутренний вывод компаратора непосредственно). Светодиод, подсоединенный к выходу РВО, включен. Когда напряжение батареи уменьшится ниже -3,3 В, ком- паратор перебросится в состояние логической единицы, и одновременно возникнет прерывание. В этом прерывании мы выключим светодиод. Если мы заменим бата- рею, не выключая питания, то компаратор перебросится в обратную сторону, и в прерывании опять включится светодиод. Таким образом мы все время будем отслеживать состояние батареи в реальном времени. Эффект, аналогичный снижению напряжения батареи ниже допустимого, даст так- же отключение батареи тумблером S1. Его можно использовать для проверки рабо- ты схемы или для временного отключения батареи. Когда тумблер включен, к вы- ходу батареи подсоединен делитель R2/R3, который потребляет очень маленький ток около 0,7-0,8 мкА, что практически не скажется на потреблении схемы во вре- мя работы (даже с использованием энергосбережения контроллера), но все-таки будет зазря разряжать батарейку во время хранения при выключенном приборе. Заметки на полях Избавиться от делителя не так просто: теоретически можно подобрать внешний ИОН с нужным напряжением около 3,3 В, и если хотите повозиться с программируемыми ИОН (вроде TL431), то вперед. Но учтите, что это приведет к усложнению логики ра- боты схемы, потому что придется подключать ИОН к питанию 5 В (программируемые ИОН потребляют много больше фиксированных), и при отключении от сети компара- тор перестанет контролировать батарейку— в любом состоянии будет показывать, что она исправна. В общем, хлопот больше, чем выгод.
298 Часть III. Практическое программирование микроконтроллеров AVR Программа, отвечающая за функцию слежения по такой схеме, очень проста. В ATmega8 нужный вектор носит название anacomp (17-й по счету), и его необхо- димо сначала задействовать, затем в секции Reset сконфигурировать компаратор и установить светодиод в соответствии с начальным состоянием компаратора (лис- тинг 11.1). ;===== прерывания rjmp Reset ;вектор сброса .org ACIaddr ;ACIaddr (АТтеда8=$010) прерывание компаратора rjmp ANA_COMP .огд INT_VECTORS_SIZE ;начинаем с адреса после прерываний Reset: ldi temp,low(RAMEND) ;указатель стека out SPL,temp ldi temp,high(RAMEND) out SPH,temp ldi temp,0b00000001 ;PB0 - Output out DDRB,temp ldi temp, (1«ACIE) | (1«ACBG) ;разр. прерывания компаратора ;и подключение внутреннего ИОН out ACSR,temp sei /устанавливаем РВО в начальное состояние : sbis ACSR,ACO ;если бит АСО =1 то сброс LED rjmp Setb__l /иначе на установку LED cbi PortB,0 /сбрасываем LED rjmp Gcykle Setb_l: sbi PortB,0 /включаем LED Gcykle: rjmp Gcykle Затем вставим очень простой обработчик прерывания компаратора, в котором мы только включаем или выключаем светодиод (листинг 11.2). ANA_COMP: /прерывание компаратора sbis ACSR,ACO /если бит АСО =1 то сброс LED rjmp COMP_0 /иначе на установку LED cbi PortB,0 /сбрасываем LED reti /на выход
Глава 11. Аналоговый компаратор и АЦП 299 СОМР_0: sbi PortB,0 /включаем LED reti /выход из процедуры компаратора Как вы убедились на примере этой программы, не всегда простота кода подразуме- вает простоту схемотехнического решения. В программе даже не задействовано ни одного РОН, кроме необходимого временного регистра temp для начальных устано- вок! Зато нам пришлось тщательно продумывать каждый узел схемы, прежде чем перейти к собственно написанию программы, которая в результате имеет объем всего 46 байтов полезного кода (но занимает в памяти 80 из-за инструкции .org INT_VECTORS_SIZE). Мало того, мы можем упростить программу предельно, если перепишем ее в Arduino-стиле безо всяких прерываний (листинг 11.3). Это тот случай, когда принципы только мешают,— ведь событие исчерпания ресурса батарейки про- изойдет хорошо, если раз в год, и если его обнаружение отложится на несколько миллисекунд от того, что в это время обрабатывается какое-то длинное прерыва- ние, мир определенно не рухнет. ;Программа слежения за напряжением резервной батарейки .include "m8def.inc" .def temp = rl6 ldi temp,ObOOOOOOOl ;PortB.0 - Output out DDRB,temp ldi temp, (1«ACBG) ;подключение внутреннего ИОН out ACSR,temp Gcykle: sbis ACSR,ACO ;если бит АСО =1 то сброс LED rjmp Setb_l ;иначе на установку LED cbi PortB,0 /сбрасываем LED rjmp Gcykle Setb_l: sbi PortB,0 ;включаем LED rjmp Gcykle Правда, на этом наши мучения еще не заканчиваются: систему надо проверить и откалибровать. Для этого подключите временно вместо батареи регулируемый ис- точник или просто движок потенциомера сопротивлением 1 кОм, подсоединенного к внешнему источнику 5 В. Покрутите напряжение, имитирующее батарею, в пре- делах 2-4 вольт — светодиод должен гаснуть и загораться. Точно поймайте момент переключения при убывании напряжения и измерьте его величину (лучше это сде- лать несколько раз и вычислить среднее). Рассчитайте точное значение резистора R3, замените его (возможно, придется его составить из нескольких резисторов, т. к.
300 Часть III. Практическое программирование микроконтроллеров AVR однопроцентных резисторов из ряда Е192 у вас наверняка не найдется) и проверьте напряжение переключения опять. Полностью обе программы вы найдете в архиве по адресу, указанному во введении (файлы AnalogjDatt.asm и Analog_batt_simple.asm). Эти программы легко переносятся практически на любой контроллер в любом режиме тактирования без изменений, т. к. компаратор устроен одинаково везде (не в пример иным узлам, для которых приходится выискивать нюансы реализации и наименования регистров в той или иной модели). Процедуры по такому образцу взаимодействия с компаратором можно также задействовать в любых других целях, где необходимо определение некоего аналогового порога. Но при этом надо помнить, что компаратор ничем не отличается от любого другого порогового устройства и точно так же выдает дребезг при плавном снижении на- пряжения на входе, осложненного помехами. Здесь нас этот дребезг не волнует, т. к. мы всего только переключаем светодиод. Но в других случаях применения компаратора от дребезга придется избавляться. К сожалению, здесь нельзя приме- нить стандартный метод для настоящих аналоговых компараторов, который заклю- чается во введении гистерезиса— разницы между порогами срабатывания при уменьшении и увеличении входной величины (точнее, в принципе можно, но это усложнит схему подключения и программу непропорционально достигнутым результатам). Поэтому придется прибегать к методам фильтрации дребезга, анало- гичным использованным для кнопки в главе 6. Встроенный АЦП Одно из реальных преимуществ Arduino — простота реализации функций аналого- цифрового преобразования с помощью единственной функции anaiogRead (). Впро- чем, запустить АЦП на ассемблере и получить результат преобразования совсем несложно — не намного сложнее, чем через ардуиновскую anaiogRead (). Трудности начинаются далее, когда мы решаем, что с полученным результатом делать. В ре- зультате программа раздувается, и сравнение в удобстве применения оказывается не в пользу ассемблера, даже с учетом большей гибкости в манипулировании ре- жимами АЦП. Вероятно, АПЦ в контексте его применений для измерения различных величин — вообще самая сложная тема в изучении AVR-контроллеров, куда сложнее пугаю- щей новичка темы таймеров в режиме ШИМ. Начальное освоение АЦП не в по- следней степени осложняется тем, что разработчики умудрились насадить различий в АЦП даже близких по архитектуре контроллеров (как ATmega8 или ATmegal6 и ATmega8535). Управление АЦП настолько загромождено различными необяза- тельными опциями, вариантами и условиями, что с первого взгляда разобраться в нем кажется совершенно невозможным. Из-за этого при переносе на другой кон- троллер приходится проверять каждую мелочь. Но после того, как вы немного освоитесь в этом деле, запуск и чтение АПЦ уже не покажется сложным делом, а вот обработка результатов... Но все по порядку.
Глава 11. Аналоговый компаратор и АЦП 301 Подробности Неоценимую помощь в освоении любой периферии оказывает одно положительное качество архитектуры AVR: если вы освоили какой-то определенный режим любого устройства, входящего в состав конкретной модели, то в любой другой модели этот режим управляется теми же самыми регистрами и битами, находящимися в них на тех же самых местах. Различия бывают в наименованиях регистров и битов, не более (но при этом часто действуют и оба варианта имени). Именно поэтому я зачастую, как вы могли заметить, устанавливаю биты в регистрах через двоичное представление, а не заданием имени бита. Хотя последний способ лучше читается, но имя бита можно указать в комментарии вместо того, чтобы рыться в описаниях: а не изменилось ли оно? Исключения из этого правила случаются, но достаточно редкие, и при необходимости их несложно запомнить. Одно из них в рассматриваемой теме относится к заданию режима запуска АЦП по прерыванию от некоторых периферийных устройств, который задается битами adtds2 : о, находящимися в зависимости от модели в разных регист- рах (sfior или adcsrb) на разных местах. Мы такой режим использовать не будем (а в ATmega8 его и вовсе нет), потому для нас это несущественно. Но, к сожалению, именно АЦП ярко демонстрирует еще одну разновидность несовместимости моделей AVR — различие в характеристиках некоторых устройств. В нашем случае — это раз- ная величина внутреннего опорного напряжения у разных моделей. Если в случае компаратора ИОН использовался напрямую, и его величина 1,1-1,3 вольта приблизи- тельно одинакова для любого AVR, то для АЦП в одних моделях она может усили- ваться до 2,56 вольта, а в других остается равной напряжению ИОН. Иначе говоря, прежде чем перейти к практическому применению АЦП, сначала придется разобраться в управлении режимами. Рекомендую также ознакомиться с весьма толковым описанием работы АЦП в [2] — там вы найдете некоторые под- робности, которые я здесь буду опускать. АЦП есть во всех Mega и в некоторых Tiny (в «нашем» ATtiny2313 АЦП, к сожалению, отсутствует), но мы здесь будем говорить преимущественно о Mega, иначе совсем закопаемся в подробностях. Питание и опорное напряжение Общие сведения об устройстве АЦП в МК AVR приведены в главе 3. Прежде всего следует напомнить, что у АЦП отдельное питание через вывод AVCC. Мы уже го- ворили, что в некоторых пособиях отдельный вывод GND, находящийся рядом с выводом AVCC, называют AGND (подразумевая некую «аналоговую землю»). На самом деле теоретически никакой разницы между одноименными выводами GND на разных сторонах кристалла (в ATmega8 это выводы 8 и 22) нет, в цифровых функциях вы можете спокойно применять оба. Практически же второй вывод GND действительно физически находится рядом с АЦП, и при аккуратной работе с АЦП его не стоит привлекать к выполнению цифровых функций, а использовать для подключения реальной аналоговой «земли» — общего провода внешнего аналого- вого питания, которое подается на AVCC (к той же «земле» при необходимости подключаются и внешние аналоговые устройства). Аналоговая «земля» платы (не сам по себе второй вывод GND, а тот общий провод, к которому подключаются аналоговые компоненты схемы помимо контроллера) по правилам должна соединяться с цифровой (обязательно!), но только в одной точке, причем вблизи внешних выводов платы. В реальности это достижимо лишь когда
302 Часть III. Практическое программирование микроконтроллеров AVR аналоговое питание подается от отдельного источника, но обычно это не так, и та- кое требование можно не учитывать. Питание AVCC не должно отличаться от пи- тания цифровой части более, чем на 0,3 В в большую или меньшую сторону, и обычно это то же самое питание VCC, только дополнительно развязанное отдель- ным конденсатором 0,1 мкФ непосредственно между вторым выводом GND (к ко- торому подключена аналоговая «земля» платы) и выводом AVCC. Фирменные опи- сания контроллеров рекомендуют дополнительно к конденсатору ставить между VCC и AVCC еще и дроссель индуктивностью 10 микрогенри. Напомним, что если АЦП не используется, то подключать AVCC необязательно. Подробности На входе АЦП установлено устройство выборки-хранения, которое интегрирует на- пряжение входного сигнала с помощью RC-цепочки (оно призвано не допускать резких изменений сигнала во время преобразования). Из-за этого источник сигнала не дол- жен иметь слишком высокое сопротивление — документация рекомендует не более 10 кОм. Но не следует воспринимать эту величину как предельно допустимое значе- ние— при медленных изменениях сигнала и с более высокоомными источниками существенной ошибки возникать не будет. Не стоит только увлекаться и подключать ко входу мегаомные делители напряжения: АЦП все-таки не компаратор, и создавать таким образом лишние источники возможных погрешностей не следует. В связи с АЦП встает вопрос об источнике опорного напряжения. Выбор источника производится битами refsi:0 регистра admux (старшие биты 7 и б), причем их нуле- вое значение (по умолчанию) соответствует внешнему ИОН, подключенному к вы- воду AREF. Напряжение этого внешнего источника может лежать в пределах от 2 В до напряжения питания аналоговой части AVCC. По умолчанию этот режим вы- бран для безопасного включения контроллера, т. к. при подключенном внутреннем ИОН (единичное состояние обоих битов refs) к выводу AREF нельзя подключать какое-то напряжение. В случае выбора внутреннего источника к этому выводу сле- дует также подключить керамический конденсатор 0,1-1,0 мкФ, что должно суще- ственно снизить «дребезг» результатов преобразования. Из-за запрета на одновре- менное подключение внутреннего и внешнего ИОН чисто программное переклю- чение по ходу работы между ними невозможно. С вывода AREF можно измерить напряжение внутреннего ИОН, что полезно для проведения предварительных рас- четов при индивидуальной калибровке, но в общем случае не рекомендуется к нему подключать какие-то внешние схемы. Напряжение внутреннего источника для «наших» ATmega8 и ATmega8535 в случае АПЦ усиливается отдельным усилите- лем до 2,56 В (с разбросом приблизительно от 2,4 до 2,7 В), а в других моделях (в ардуиновском ATmega328, например) составляет все те же самые 1,1-1,2 вольта. Самый простой путь, по которому пошли и в Arduino, — выбрать в качестве опор- ного питание самой аналоговой части AVCC. Причем это можно сделать двумя способами: либо просто соединить выводы AREF и AVCC микросхемы, оставив настройки «по умолчанию», либо установить биты refsiiO в состояние 01 (тогда соединение осуществляется внутренними схемами). Конечно, второй способ удоб- нее и надежнее, и к тому же позволяет безболезненно переключаться между AVCC и внешним или внутренним ИОН.
Глава 11. Аналоговый компаратор и АЦП 303 Подробности Использование внутреннего источника опорного напряжения часто не столько неудоб- но из-за его разброса, сколько из-за заданной величины напряжения 2,56 или 1,1 вольта. Входное напряжение аналогового сигнала ограничено сверху величиной опорного напряжения Vreu которой соответствует максимальный код согласно разряд- ности АЦП (см. разд. «Аналого-цифровые операции и их погрешности» в этой главе). При этом, как мы уже знаем, нежелательно сильно занижать диапазон входного сиг- нала в сравнении с Wet, чтобы не терять в разрешении. То есть, чтобы «выжать» из АЦП максимум, следует подогнать напряжение ИОН под имеющийся датчик (обратное чаще всего недоступно, если только мы не возьмемся самостоятельно городить мас- штабирующие усилители). В основном, это и есть причина того, что нередко прихо- дится искать подходящий внешний источник. Конечно, возникает искушение повысить точность преобразования с помощью внешнего калиброванного прецизионного ИОН (вроде семейства REF19x фирмы Analog Devices или его аналогов), но, поверьте, что прецизионность в этом случае на втором плане — в большинстве случаев важнее со- ответствие выходного напряжения датчика и ИОН (соответствующий пример приведен в главе 14). Задание режима работы АЦП может функционировать в непрерывном режиме (когда по окончании преоб- разования тут же начинается следующее) или в одиночном (когда каждый раз по- ступает команда на запуск). Кроме того, запуск режима ADC Noise Reduction ини- циирует начало преобразования с одновременным выключением всех остальных цифровых узлов контроллера, а с его окончанием МК автоматически «просыпает- ся» и переходит к обработчику прерывания АЦП. Режим ADC Noise Reduction останавливает все схемы МК (кроме асинхронного таймера и WDT) до окончания преобразования (т. е. минимум примерно на 20 тактов). Для моделей, где режима ADC Noise Reduction нет, можно использовать режим Idle, в котором, правда, оста- навливаются не все устройства (см. главу 14). В любом случае возиться с наворо- ченным алгоритмом управления этими режимами целесообразно лишь тогда, когда по крайней мере и питание, и опорное напряжение АЦП подаются от отдельных прецизионных источников — иначе эти меры все равно не дадут нужного эффекта. АЦП включается в работу установкой бита aden (бит 7) в регистре управления АЦП, который в большинстве моделей Mega и Tiny называется adcsra. Режим не- прерывных измерений включается установкой бита adfr (бит 5) этого же регистра. В ряде моделей Mega (к ним относится, например, ATmega8535) этот бит носит на- именование adate, и управление режимом работы производится сложнее: там до- бавляются несколько режимов запуска через различные прерывания (в т. ч. преры- вание от компаратора, при наступлении различных событий от таймера и т. п.), и выбирать их следует, задавая биты adtsx регистра sfior (в некоторых моделях эти биты размещены в дополнительном регистре adcsrb). Установка бита adate при этом разрешает запуск АЦП по этим событиям. Так как нулевые значения всех битов adtsx (по умолчанию) означают режим непрерывного преобразования, то в случае, когда вы их значения не трогали, функции бита adate и бита adfr в других моделях будут совпадать.
304 Часть III. Практическое программирование микроконтроллеров AVR Если выбран режим запуска не от внешнего источника, то преобразование запуска- ется установкой бита adcs (бит б того же регистра adcsra). В непрерывном режиме (adfr/adate = 1) установка бита adcs запустит первое преобразование, затем они бу- дут автоматически повторяться. В режиме запуска через прерывания (в тех моде- лях, где это возможно) установка бита adcs происходит автоматически, и, соответ- ственно, запуск преобразования производится при запуске соответствующего пре- рывания. Отметим, что преобразование начинается по фронту первого тактового импульса (тактового сигнала АЦП, а не самого контроллера!) после установки бита adcs. Само преобразование занимает 13 (или 14 — для дифференциального входа) периодов тактового сигнала АЦП (опять же — не контроллера!), кроме первого по- сле включения АЦП преобразования, которое займет 25 тактов. По окончании одиночного преобразования бит adcs аппаратно сбрасывается. Кроме того, по окончании любого преобразования (и в одиночном, и в непрерывном ре- жиме) устанавливается флаг прерывания adif (бит 4 регистра adcsra). Чтобы оно реально произошло, необходимо прерывание АЦП разрешить, что осуществляется установкой бита adie (бит з) все того же регистра adcsra. Для работы с АЦП необходимо еще установить его тактовую частоту. Это делается тремя младшими битами ADPS2: о регистра adcsra. Коэффициент деления частоты тактового генератора МК устанавливается по степеням двойки, при этом состояния ооо и 001 соответствуют коэффициенту 2, далее все, как обычно, вплоть до всех трех единиц — 128. Напомним, что оптимальная частота преобразования лежит в диапазоне 50- 200 кГц, откуда вычислены допустимые значения коэффициента деления для раз- ных тактовых частот контроллера (табл. 11.1). Таблица 11.1. Тактовые частоты АЦП для разных тактовых частот МК Тактовая частота МК 1 МГц 4 МГц 8 МГц 16 МГц Коэффициент деления 8, 16 32,64 64; 128 128 Значения битов ADPS2:0 он; юо 101, НО но; in in Тактовая частота АЦП 125 кГц; 62,5 кГц 125 кГц; 62,5 кГц 125 кГц; 62,5 кГц 125 кГц Из данных таблицы следует, что при тактовой частоте АЦП в указанных диапазо- нах преобразования в 13 тактов каждое займут ~100 или ~200 мкс — в зависимости от установленной частоты. При «ардуиновской» частоте 16 МГц в допустимый диапазон укладывается только коэффициент 128, при этом преобразование займет примерно 100 мкс, что хорошо коррелирует с максимальной допустимой частотой опроса с помощью функции anaiogRead (), равной 10 000 раз в секунду. Результат преобразования АЦП размещается в регистрах adchiadcl. Поскольку ре- зультат 10-разрядный, то по умолчанию старшие 6 битов в регистре adch оказыва-
Глава 11. Аналоговый компаратор и АЦП 305 ются равными нулю. Чтение этих регистров производится, начиная с младшего adcl, после чего регистр adch блокируется, пока не будет прочитан. Следовательно, даже если момент между чтением регистров попал на фронт 14-го такта АЦП, ко- гда данные в них должны меняться, значения прочитанной пары будут соответст- вовать друг другу, пусть и результат текущего преобразования пропадет. В проти- воположном порядке читать эти регистры не рекомендуется. Но бит adlar (бит 5 регистра admux) предоставляет интересную возможность — если его установить в 1, то результат преобразования в регистрах adch:adcl выравнивается влево: бит 9 результата окажется в старшем бите adch, а незначащими будут младшие 6 битов регистра adcl. В этом случае, если хватает 8-разрядного разрешения результата, можно прочесть только значение adch. Выбор каналов и режимов их взаимодействия в АЦП производится пятью битами михо.. 4 в регистре admux. В некоторых моделях (семейство Tiny) этих битов всего три (михо.. 2), а, например, в ATmega8 — четыре (михо.. з), в зависимости от обще- го числа каналов. В любом случае их значения от 0 до максимального номера кана- ла (которых в большинстве случаев 8, так что значения оказываются в пределах от ооо до in, а старшие биты, если они есть, равны 0) выбирают нужный канал в обычном (недифференциальном) режиме, когда измеряемое напряжение отсчиты- вается от «земли» (аналоговой). А последние два значения этих битов для семейст- ва Mega (mo и mi для ATmega8) выбирают режимы, когда вход АЦП подсоеди- няется к опорному источнику компаратора (1,22 В) или к «земле», соответственно, что может использоваться для автокалибровки устройства. В имеющих АЦП моде- лях Tiny такого режима нет. Наконец, остальные комбинации разрядов мих предназначены для установки раз- личных дифференциальных режимов— в тех моделях, где они присутствуют (ATmega8535), в других случаях эти биты зарезервированы (в ATmega8 и, между прочим, в «ардуиновских» ATmega88/l68/328). В дифференциальном режиме АЦП измеряет напряжение между двумя выбранными выводами (например, между ADC0 и ADC1). В том числе дифференциальные входы АЦП можно подключать к одному и тому же входу для коррекции нуля — в дальнейшем значение выхода при соединенных входах можно просто вычесть из результата преобразования. Среди дифференциальных режимов АЦП есть такие, когда к некоторым парам вы- водов подключается встроенный усилитель с коэффициентом 10х и 200х. Входы и их режимы допускается переключать в любой момент, потому что эффект это даст все равно только после окончания текущего преобразования, — так можно менять каналы «на ходу» в режиме непрерывного преобразования. Только при этом следует учесть, что в дифференциальном режиме из-за наличия усилителя показа- ния установятся только через 125 мкс, результаты до истечения этого срока ока- жутся недостоверными. Для недифференциального режима АЦП, когда напряжение отсчитывается от «зем- ли», результат преобразования определяется формулой: Ка = 1024£/BX/£/ref, где Ка — значение выходного кода АЦП, UBX и f/ref— входное и опорное напряжения.
306 Часть III. Практическое программирование микроконтроллеров AVR Подробности Дифференциальному измерению соответствует такая формула: Ка = 512(l/pos- Цюд)/1Лег. где L/pos и L/neg — напряжения на положительном и отрицательном входах соответст- венно. Если напряжение на отрицательном входе больше, чем на положительном, то результат в дифференциальном режиме становится отрицательным и выражается в дополнительном коде от $200 (-512) до $3ff (-1)1. Реальная точность преобразова- ния в дифференциальном режиме не превышает 8 разрядов. Зачем нужен дифференциальный режим? Типовое его применение — преобразование сигнала с измерительного моста. Мост можно представить, как два резистивных дели- теля напряжения, при этом два входа дифференциальной схемы измерения подклю- чаются к средним точками делителей, и результат измерения представляет собой разность напряжений в этих точках. Если некоторые сопротивления резисторов в де- лителях зависят от какой-нибудь физической величины, то сигнал меняется в положи- тельную или отрицательную сторону. Мостовая схема известна еще с XIX века и по- зволяет осуществлять наиболее точные измерения, но не сама по себе, а при соблю- дении определенных условий. Теория мостовых измерений — достаточно сложный предмет, и некоторые ее азы читатель может узнать из моей книги [10]. Мне, как бывшему метрологу, непросто представить себе, зачем нужно применять не- посредственное подключение мостовых схем к АЦП контроллера в условиях, когда максимально достижимая абсолютная точность преобразования все равно ограничена 8-битовым числом (и то только теоретически — на практике всегда меньше). В случае, когда действительно нужно что-то измерить достаточно тщательно, проще заранее обеспечить гарантированную точность и диапазон входного сигнала АЦП, сформиро- вав его с помощью внешних прецизионных операционных усилителей, — и стабиль- ность обеспечена, и все ошибки точно просчитываются. Собственно говоря, так и по- ступают конструкторы различных датчиков физических величин, иногда выдавая на- гора готовый аналоговый сигнал, а иногда и сразу цифровой. Последний вариант в общем случае хуже, т. к., за редкими исключениями, в датчик прецизионный АЦП за- совывать никто не будет, но крайне удобен на практике, — для сравнения см. далее разд. «Перевод результатов в физические величины». В общем, чтобы у вас окончательно не уехала крыша, давайте в общем обзоре воз- можностей АЦП на этом остановимся. Хотя мы еще далеко не все нюансы доста- точно подробно рассмотрели, уже можно понять, почему в начале раздела говори- лось о реальном преимуществе Arduino в вопросе доступа к АЦП. На самом деле, для элементарной практической реализации в стиле функции anaiogRead () из опи- санных вариантов нам понадобится совсем немного: режим непрерывного или од- нократного запуска, смена каналов и дополнительно возможность менять источник опорного напряжения. Еще мы обсудим вариант с запуском прерывания готовности АЦП — он может пригодиться для удобного набора статистики без лишней нагруз- ки на прерывания таймера. Прочие варианты мы оставим за кадром. И сначала, чтобы привыкнуть, попробуем использовать АЦП в самом простом варианте циф- рового компаратора. Простейшее использование АЦП Давайте на основе АЦП соорудим автомат для регулирования освещения — будем выключать лампочку, когда освещенность вырастает до определенной величины, и включать ее снова, когда освещенность падает. В общем-то ничего не стоит соору- 1 В книгах [6,7] в этом вопросе допущена неточность.
Глава 11. Аналоговый компаратор и АЦП 307_ дить такую схему на отдельном аналоговом компараторе (типа LM311, например). Однако на контроллере — в сравнении с простым компаратором — мы сможем со- орудить любой алгоритм, а для такой капризной величины, как освещенность, оп- тимальный порядок надежной работы может оказаться довольно-таки заковыри- стым. Освещенность мало того, что меняется крайне медленно, она еще и регуляр- но колеблется в достаточно широких пределах, причем отклонения могут быть как долгие (вечернее солнце вдруг вышло из-за туч), так и относительно короткие (человек прошел мимо датчика, или в датчик попал блик от лобового стекла проез- жающего автомобиля). Применение Arduino для такой простой функциональности будет явно избыточ- ным — конечно, тут сгодится любая самая простая «тинька», лишь бы в ней был встроенный АЦП (ATtinyl3 в 8-выводном корпусе, например). Но чтобы не отвле- каться на обсуждение особенностей реализации АЦП еще и в этой группе контрол- леров, смоделируем программу на обычном ATmega8 с тактированием «по умолча- нию» 1 МГц (не забудьте установить опять «фьюзы», как показано на рис. 5.8, если они у вас переустанавливались на время работы с таймерами). По тому же принципу строятся релейные или даже пропорциональные регуляторы любых величин — термостаты, например. Использовать для этой цели встроенный аналоговый компаратор, как мы уже говорили, неудобно: в нем нелегко ввести гис- терезис (разность между порогами срабатывания «вверх» и «вниз»), что является обязательным атрибутом любой схемы регулирования. Для регулятора освещения гистерезис должен быть особенно велик из-за вероятности очень плавного, в тече- ние часов, изменения освещенности в совокупности с возможностью ее временного возврата на прежний уровень. Поэтому мы привлечем АЦП, где порогами срабаты- вания очень просто манипулировать, и, кроме того, введем задержку на переклю- чение для фильтрации коротких бросков освещенности. Схема регулятора освещения приведена на рис. 11.3. Датчик освещенности— фо- торезистор Ldrl. Его тип GL5516 показан на схеме условно — сойдет любой фото- резистор с величиной сопротивления примерно 10-20 кОм при 10 люксах освещен- ности, их продают в интернет-магазинах, торгующих аксессуарами для Arduino, по копейке пучок в базарный день. В любом случае резисторы делителя R2 и R3 при- дется подбирать по месту, в зависимости от конкретного экземпляра фоторезистора и, главное, от условий освещенности, при которых вы хотите, чтобы датчик сраба- тывал. Подробности В параметрах фоторезисторов приводится логарифмический показатель чувствитель- ности Y- Эта величина позволяет прикинуть, насколько изменится сопротивление фо- торезистора при изменении освещенности в 10 раз по формуле: R100/R10 = 10Y. Для обычных фоторезисторов у равен примерно 0,6-0,8, т. е. изменение будет находиться в пределах 4-6 раз. Порог, при котором приходится включать освещение, составляет несколько десятков люкс (50 люкс— это примерно как на освещенной автостоянке ночью), отсюда сопротивление фоторезистора, равное 10 кОм при 10 люксах, вблизи порога срабатывания окажется равной нескольким килоомам. Эта величина находится в рамках, допустимых для выходного сопротивления источника сигнала АЦП (10 кОм), и не может послужить источником дополнительных ошибок. Учитывая еще исключи- тельно медленно меняющийся уровень освещенности, можно позволить себе не осо- бенно обращать внимание на тип и параметры фоторезистора.
308 Часть III. Практическое программирование микроконтроллеров AVR R1 5,1 к ♦5 В * 1 I » 4=d - Хо,1 . +5 В =т=С2 h - - - R4330 " Led1 V? 1 Reset 2RXD 3TXD 4 INTO (PD2) 5INT1 (PD3) 6 PD4 (TO) 7Vcc 8GND 9 XTAL1 10XTAL2 11 PD5(T1) 12PD6 13PD7 14PBO (ADC5) PC5 28 (ADC4) PC4 27 (ADC3) PC3 26 (ADC2) PC2 25 (ADC1) PCI 24 (PCO)ADCO 23 6ND22 AREF 21 AVCC 20 SCK19 MISO 18 MOS117 (OC1B)PB216 (OC1A)PB1 15 ATmega8 Хсз n-100,0 • в i я=го,л C5 0,1 ={= I ♦5 В +5 В jL ^M Ldr1 M 6L5516 I T I flJ R2* Li зок MR3 M1 к I Рис. 11.3. Схема автоматического регулятора осёещения Схеме соответствует программа (листинг 11.4), которую вы также можете найти в архиве по адресу, указанному во введении (файл ADCJightness.asm). \ Листинг 11.4 /Программа регулятора освещенности .device ATmega8 /встроенный 1 MHZ .include "m8def.inc" .def temp = rl6 /рабочий регистр .def RazrO = rl7 /разряды задержки .def Razrl = rl8 .def Razr2 = rl9 .macro Delay80 /процедура задержки до 80 с ldi Razr2,@0 /старший байт задержки ldi Razrl,01 /средний байт задержки ldi RazrO,02 /младший байт задержки R_sub: subi RazrO,1 sbci Razrl, О sbci Razr2,0 brcc R_sub . endm .macro ReadADC in temp, ADCL/получаем младший ADC cpi temp, low (@0) /сравниваем с порогом in temp,ADCH /старший ADC
Глава 11. Аналоговый компаратор и АЦП 309 ldi razrO,high(@O) срс temp,razrO /сравниваем с порогом .endm ldi teinp,0b00000001 ; PortB.O - Output out DDRB,temp ;ADC === ;start ADC 1/8 = 125 кГц, непрерывный режим ADFR=1; ldi temp, 1«ADFR 11«ADEN 11«ADPS111«ADPSO out ADCSRA, temp ldi temp, (1«REFSO) ;Vref = AVCC,, канал ADCO out ADMUX,temp sbi ADCSRA,ADSC /запуск первого преобразования Gcykle: sbic PortB,0 /если горит, то на выключение rjmp test_On_led Test_Off_led: /если Led выключен, надо включать при падении ReadADC 400 /сравниваем с нижним порогом =400 brsh Test_Off_led /если больше, ничего не делаем /если меньше, проверяем заново Delay80 $09, $27, $С0 /задержка 3 с ReadADC 400 /опять сравниваем с нижним порогом =400 brsh Test_Off_led /если опять больше, ничего не делаем /если меньше, включаем sbi PortB,0 /зажигаем Led rjmp Gcykle /и назад Test_On_led: /если Led включен, надо выключать при возрастании ReadADC 500 /сравниваем с верхним порогом =500 brio Test_On_led /если меньше, ничего не делаем /если больше, проверяем заново Delay80 $09, $27, $С0 /задержка 3 с ReadADC 500 /опять сравниваем с верхним порогом =500 brio Test_On_led /если опять меньше, ничего не делаем /если больше, выключаем cbi PortB,0 /гасим Led rjmp Gcykle ; и назад Программа построена в Arduino-стиле: поскольку мы здесь никуда не торопимся, и нигде нет узких мест по скорости выполнения отдельных участков, то весь алго- ритм сосредоточен в главном цикле, никакие прерывания не задействованы, необ- ходимые задержки — чисто программные. АЦП изначально запускается на непре- рывные преобразования фоном к выполнению основной программы. Механизм защиты от ложных срабатываний двухступенчатый: сначала мы читаем АЦП и сравниваем его с порогом (см. текст макроса ReadADC). Если обнаруживаем,
310 Часть III. Практическое программирование микроконтроллеров AVR что порог был пересечен (вверх или вниз, в зависимости от текущего состояния), то выжидаем паузу 3 секунды и проверяем состояние опять. Если пересечение под- твердилось, то переключаем светодиод на выходе и возвращаемся к отслеживанию. Паузу в 3 секунды по окончании отладки стоит в рабочем варианте программы уве- личить до максимума, который позволяет трехбайтовая задержка, загрузив в макрос число $ffffff, — при 1 МГц это более 80 секунд. Поскольку освещенность меняет- ся медленно, то такая длительность будет в самый раз, чтобы отфильтровать слу- чайные блики, засвечивающие датчик. Второй эшелон защиты от случайного срабатывания: большая величина гистерези- са, которая здесь установлена равной 100 единиц кода— около 10% от диапазона АЦП. И сами величины порогов (500 и 400 единиц) и разность между ними, конеч- но, придется подстраивать «по месту». Я советую при настройке схемы сначала подобрать переменный резистор R2 так, чтобы при нужном уровне освещенности светодиод на выходе PB0 переключался примерно в среднем положении его движ- ка, и установить подстроечный («под отвертку») резистор подобранного номинала. На это время разницу между порогами переключения, которая у нас составляет примерно 10% от диапазона АЦП, можно уменьшить, оставив сами значения поро- гов близкими к числу 500. В готовом изделии вместо светодиода, конечно, подключается какой-то исполни- тельный механизм для управления осветительным прибором — в простейшем слу- чае это реле, подключенное через транзисторный ключ. У меня подобное устройст- во управляло включением вентиляторов солнечной сушилки, которые должны работать только при ярком солнце. Рекомендую подумать над алгоритмом, позво- ляющим превратить выключатель в пропорциональный регулятор (для этого при- дется обратиться к сведениям из разд. «Таймеры в режиме ШИМ» главы 9), и затем пристроить это к автоматическому регулированию яркости светодиодных дисплеев по какой-нибудь из схем, приведенных в этой книге. Как видите, с самим АЦП тут все достаточно просто. И в дальнейшем вы увидите, что само по себе обращение с АЦП — не такой уж трудный предмет: куда сложнее будет правильно организовать обработку результатов. Схема измерений с помощью АЦП Теперь поговорим на более актуальные темы — как с помощью АЦП организовать съем показаний аналоговых датчиков. Сначала остановимся на выборе контролле- ра. Предположим, у нас имеется обычный датчик температуры типа ТМР36 в кор- пусе ТО-92 с аналоговым сигналом на выходе. Сигнал нужно оцифровать и как-то выдать результат во внешний мир. Если требуется выводить полученное значение на индикацию, стоит использовать ATmega8535, учитывая большое число его вы- водов. Но, во-первых, не всегда требуется именно много выводов (в главе 13 мы рассмотрим управление индикацией по последовательному порту 12С), во-вторых, не всегда вывод осуществляется именно на индикатор — вполне может быть, что вы посылаете измеренное значение в компьютер через последовательный порт, записываете на флешку или через передатчик отправляете в мировой эфир.
Глава 11. Аналоговый компаратор и АЦП 311_ Мы все-таки будем рассматривать вариант ATmega8535, как наиболее универсаль- ный, но учтите, что созданная нами программа без изменений (кроме, возможно, наименования регистров) перенесется на ATmega8, ATmegal6 и ряд других мо- делей. При выборе контроллера следует учесть еще и нюанс с опорным напряжением. У датчика ТМР36 выходной сигнал при О °С равен 500 мВ и имеет крутизну 10 мВ/°С (с достаточно большим разбросом, соответствующим ошибке примерно 2-3°, т. е. датчик все равно необходимо калибровать). Таким образом, в диапазоне от -40 до +100 °С (ниже -40 °С датчик не работает, но не больно-то и хотелось) напряжение будет меняться от 0,1 до 1,5 В, при 0 °С оно будет равно -0,5 В. Чтобы не терять в разрешении, нам удобно выбрать встроенный источник, напря- жение которого у ATmega8 и ATmega8535 равно примерно 2,56 вольта. Тогда диа- пазон датчика будет соответствовать изменению выходного кода в пределах от -40 до -600 с крутизной около 4 единиц кода на 1°С и значением при 0 °С примерно 200. При таком выборе вы ограничены моделями AVR с таким же напряжением ИОН и не сможете без изменений перенести код на значительное число других мо- делей, у которых напряжение ИОН равно 1,1-1,2 вольта (включая «ардуиновские» ATmega88/l68/328) — там либо будет ограничен диапазон сверху (значением при- мерно +60°), либо вы потеряете в разрешении вдвое, вынужденно переключив опорное напряжение на AVCC. Подробности Если вам все-таки придется переносить нашу методику на контроллеры с напряжени- ем ИОН, равным 1,1-1,2 вольта, то проще взять внешний опорный источник с нужным напряжением. В качестве внешнего ИОН подойдут микромощные источники опорного напряжения REF191 или REF192 (фирма Analog Devices) с номинальным напряжени- ем 2,048 или 2,5 В соответственно. Источники очень точные (±2 мВ отклонения при изменениях питающего напряжения в пределах 3-15 вольт, в том же порядке цифры отклонения при изменении температуры) и потребляют всего 45 мкА даже без вклю- чения имеющегося у них режима Sleep. Потому, хотя REF192 в корпусе DIP-8 и отно- сительно дорог (a REF191 в таком корпусе еще и трудно найти), но так будет лучше, чем терять разрешение вдвое, подгоняя диапазон под 5-вольтовое Ц-ef. В том числе такой прием может пригодиться и в Arduino. На платах Uno и Nano есть вывод AREF, а в арсенале языка есть функция anaiogReferenceo. Плохо там только то, что в самой подходящей для подобных конструкций плате Arduino Mini почему-то AREF забыли вывести (хотя контактов платы хватает — там есть лишний Reset). В од- ном из своих проектов мне пришлось подпаиваться непосредственно к контактной площадке — выводу конденсатора, подключенного к AREF ATmega328, но это, согла- ситесь, и неудобно, и ненадежно. Схема для такого применения АЦП в контроллере ATmega8535 приведена на рис. 11.4. От дребезга значений, которые получаются с АЦП, ни аппаратными методами, ни даже с помощью режима ADC Noise Reduction полностью не избавишься, потому придется усреднять значения за какой-то промежуток времени. Его надо выбирать достаточно тщательно — не слишком маленьким, чтобы уйти от дребезга по мак- симуму, и не настолько большим, чтобы он превысил постоянную времени датчи-
312 Часть III. Практическое программирование микроконтроллеров AVR ка, в результате чего измеренное значение будет меняться редкими скачками. По моему опыту для датчиков, аналогичных ТМР36, значение температуры следует обновлять не реже, чем один раз в несколько секунд. За это время надо набрать ста- тистику, т. е. померить как можно больше значений и вывести среднее. I+5 В +5 В ТС1 ■ ^R1S,1K J0'1 " = C2 iJL^—h 4 МГц! 1 1 || 1 * UART(RxD) ■ - V 1 PBO(TO) 2 PB1 (T1) 3 PB2(T2) 4 РВЗ (ОС0) 5 PB4 (SS) 6 PB5 (MOSI) 7 PB6 (MISO) 8 PB7 (SCK) Q RAftAt 4 A Wrr IU VW» 11 QND 12XTAL2 13XTAL1 14RxD(PD0) 15TxD(PD1) 16 PD2 (INTO) 17PD3(INT1) 18PD4(OC1B) 19PD5(OC1A) 20PD6(ICP1) (ADCO) РАО 40 (ADC1)PA1 39 (AOC2) PA2 38 (ADC3) РАЗ 37 (ADC4) PA4 36 (ADC5) PA5 35 (ADC6) PA6 34 (ADC7) PA7 33 ARCC %9 After Oa {«МП 1Л V3>rf L/ v 1 AVcc 30 (T0SC2) PC7 29 (TOSC1) PC6 28 PC5 27 DfM 9fi rw4 «О PC3 25 PC2 24 (SDA) PC1 23 (SCL) PCO 22 (OC2) PD7 21 ATmega8535 LL 2 T - C5=*= Д TMP36 . • XX J~| сз T°'1 I === C4 0,1 L1 ЮмкГн *+5B Рис. 11.4. Схема измерения температуры Давайте подсчитаем — что означает в таком случае «как можно больше»? Значения у нас 10-битовые, значит, в два байта мы упакуем сумму не менее 64 значений. Та- кого количества для усреднения обычных величин более чем достаточно. Пусть период обновления составляет примерно 3 секунды, за которые мы должны отсчи- тать эти 64 значения, отсюда отдельные измерения должны проводиться с перио- дом примерно 50 мс (частота около 20 Гц). Каждое среднее значение будет заме- нять предыдущее в памяти SRAM, откуда его можно извлекать для других нужд, — например, для индикации, которая осуществляется в отдельном временном цикле (так, как мы это делали в главе 9). Измерения будем проводить по прерываниям TimerO. Если он у вас окажется занят, например, в процедурах динамической индикации, то постараемся, чтобы измере- ния ей не помешали. При желании можно заменить TimerO на Timer2 — при этом ничего не изменится, кроме названий битов и регистров. Тактирование контроллера для совместимости с процедурами динамической индикации, описанными в главе 9, произведем от кварца 4 МГц — для других частот тактирования придется только
Глава 11. Аналоговый компаратор и АЦП 313 пересчитать режим таймера и задать другие коэффициенты частоты АЦП в соот- ветствии с табл. 11.1. В соответствии с процедурами динамической индикации TimerO у нас работает с частотой 244 герца. Чтобы обеспечить частоту измерений около 20 Гц, надо их проводить примерно каждое 12-е прерывание переполнения. Создадим сначала проверочную программу (листинг 11.5), которая будет выдавать результат через UART на компьютер (перед тем, как подключать адаптер UART, прочтите соответ- ствующий раздел в главе 15). /Тестовая программа АЦП с усреднением ;кварц 4 МГц .device ATmega8535 .include lfm8535def.inc" ;==== переменные ==== .def AregH = rl /результат измерения, старший байт .def AregL = r2 /результат измерения, младший байт .def temp = rl6 /рабочий регистр .def countCyk = г18/счетчик прерываний - до 12 .def count64 = г19;счетчик преобразований - до 64 /==== прерывания ==== rjmp RESET .org OVFOaddr rjmp TIM0_OVF /переполнение таймераО .org INT_VECTORS_SIZE ; Конец таблицы прерываний - начало кода out_com: /посылка байта из temp с ожид. готовности sbis UCSRA,UDRE /ждем готовности буфера передатчика rjmp out_com out UDR, temp ret TIM0_OVF: inc countCyk cpi countCyk,11 breq readADC /12-е прерывание, читаем reti /иначе выход readADC: clr countCyk inc count64 cpi count64,64 /если прошло 64 чтения, то сразу на обработку breq endADC in temp, ADCL/получаем младший ADC add AregL,temp /суммируем
314 Часть III. Практическое программирование микроконтроллеров AVR in temp,ADCH /старший ADC adc AregH,temp ;суммируем sbi ADCSRA,ADSC /запуск нового преобразования reti endADC: /расчет clr count64 /очистили счетчик div64L: /деление на 64 Isr AregH /сдвинули старший ror AregL /сдвинули младший inc count64 cpi count64,6 brne div64L /сдвинули-поделили на 64 clr count64 /очистили счетчик /посылаем во внешний мир mov temp, AregL /младший результата гcall out_com mov temp, AregH /старший результата rcall out_com clr AregL /очистили младший к след. разу clr AregH /очистили старший sbi ADCSRA,ADSC /запуск нового преобразования reti RESET: ldi temp,low(RAMEND) out SPL,temp ldi temp,high(RAMEND) /указатель стека out SPH,temp /=== UART ldi temp,25 /9600 out UBRRL,temp /скор, передачи ldi temp, (1«RXEN| 1«TXEN) out UCSRB,temp /разреш. приема/передачи 8 битов /====TimerO ldi temp, (l«TOIE0) /разр. прер. Timer0 out TIMSK,temp ldi temp,255 /очищаем прерывания out TIFR,temp ldi temp,0b00000011 out TCCR0,temp /TimerO вкл. 1:64 -244 Гц ;==== АЦП /start ADC 1/32 = 125 кГц/ ldi temp, 1«ADEN 11«ADPS2 | l«ADPS0 out ADCSRA, temp ldi temp, (l«REFS0) | (1«REFS1) /внутренний ИОН, канал ADC0 out ADMUX,temp sbi ADCSRA,ADSC /запуск первого преобразования
Глава 11. Аналоговый компаратор и АЦП 315 clr count64 ;очистили счетчик чтений clr countCyk ;очистили счетчик прерываний clr AregL /очистили младший результата clr AregH ;очистили старший результата sei /разрешили прерывания Gcykle: rjmp Gcykle Вся кажущаяся сложность этой программы обусловлена осреднением и наличием процедуры вывода результата через UART для контроля. К собственно работе с АЦП относится лишь десяток строк программы. Однако без усреднения результа- тов применять АЦП для измерений чаще всего бессмысленно— необязательно брать для этого так много значений, но пяток-десяток с последующим вычислением среднего лучше накопить. Причем в Arduino это едва ли не более актуально, чем в ассемблерном исполнении, — там мы не властны над схемотехническими мето- дами подавления помех. Исключением могут являться задачи вроде описанного ранее регулятора освещения, аналогичного по построению регулятора температуры и других схем регулирования — в таких решениях всегда должен присутствовать гистерезис, который сам по себе является наилучшим методом подавления дре- безга. Наблюдать за результатом работы программы можно, если подключить внешний монитор последовательного порта. 10-разрядное число передается каждые три се- кунды побайтно, причем сначала младший, потом старший байт (последний может иметь значения только от о до з). Монитор порта Arduino в этом случае не подой- дет, т. к. он воспримет принятые значения, как символы, а символов со значением менее 32 не существует (кроме служебных). В Arduino это преодолевается наличи- ем форматного вывода в функции print, нет особых проблем наладить символьный вывод и с помощью ассемблера, если это зачем-то потребуется. Все эти вопросы, в том числе и подбор удобного для наших целей монитора порта, обсуждаются в главе 15. Программа не очень нагружает прерывание TimerO, даже если оно параллельно ис- пользуется для динамической индикации или еще для каких-нибудь целей. Данные АЦП читаются лишь каждое 12-е прерывание и добавляют к его длительности с десяток тактов. Дольше длится расчет среднего (около 40 тактов), но он происхо- дит еще намного реже (каждые три секунды) и все равно не вызовет заметных глазу нарушений в индикации, т. к. 50 тактов — это при 4 МГц всего-навсего 12,5 микро- секунд. Даже процедуры посылки значений через UART, которые занимают при- мерно миллисекунду каждая, вряд ли скажутся на динамической индикации, если зачем-то их нужно будет выполнять параллельно. Причем на посылку через UART можно затрачивать меньше процессорного времени, если привлечь прерывание (см. главу 75), но мы сейчас не об этом. Все равно хочется для таких случаев до- полнительно разгрузить таймер, и это можно сделать, если работать через прерыва- ние готовности АЦП. Я не буду приводить тут всю программу (она— по листингу 11.5 и по листин- гам 11.6-11.8— доступна целиком в архиве по адресу, указанному во введении:
316 Часть III. Практическое программирование микроконтроллеров AVR файлы ADCjnesuareJest.asm и ADCjnesuareJNTJest.asm), укажу лишь на изменения. Для начала мы подключаем прерывание готовности АЦП— указываем вектор в таблице прерываний и разрешаем его при включении АЦП в секции Reset (лис- тинг 11.6). ;==== прерывания ==== rjmp RESET .org OVFOaddr rjmp TIM0_OVF /переполнение таймераО .org ADCCaddr rjmp ADC_INT ;готовность ADC .org INT_VECTORS_SIZE ; Конец таблицы прерываний - начало кода ;start ADC 1/32 = 125 кГц + ADC interrupt; ldi temp, 1«ADEN 11«ADIE 11«ADPS2 11«ADPSO out ADCSRA, temp Потом переносим в обработчик adcint всю функциональность по подсчету суммы из 64 значений и вычислению среднего. Прерывание adcint здесь будет возникать с частотой около 10 тыс. раз в секунду (время вызова и обработки прерывания мало в сравнении с длительностью преобразования), и управлять частотой этого процес- са в должной мере мы не можем. Поэтому согласимся, что готовый результат будет у нас возникать примерно 150 раз в секунду (10 000/64). Требуется он нам, однако, гораздо реже. Чтобы не заниматься ненужной синхронизацией таймера и момента получения среднего, мы просто помещаем каждый раз результат в SRAM, а кто его оттуда извлечет и когда — это уже его дело. То есть вычисление среднего в преры- вании АЦП будет заканчиваться так (листинг 11.7) — старший байт адреса zh ра- вен о. ADC_INT: ;частота ~10 кГц <чтение АЦП и вычисление среднего 64 раза> ;записываем в память ~150 раз в секунду ldi ZL,Low(SRAM_START) ;младший байт адреса RAM st Z+,AregH /запоминаем старший st Z,AregL ;запоминаем младший clr AregL /очистили младший к след. разу clr AregH /очистили старший sbi ADCSRA,ADSC /запуск нового преобразования reti
Глава 11. Аналоговый компаратор и АЦП 317 Тогда прерывание таймера будет значительно короче (листинг 11.8). TIM0_OVF: ;244 герца inc countCyk cpi countCyk,244 breq readADC /каждую секунду читаем reti ;иначе выход readADC: clr countCyk ldi ZL,Low(SRAM_START) ;младший байт адреса RAM Id temp,Z+ /загрузка из памяти - старший rcall out_com /посылаем во внешний мир старший Id temp, Z /младший rcall out_com /посылаем во внешний мир младший reti Здесь оно посылает данные через UART раз в секунду просто для упрощения кода. В реальных задачах, конечно, можно это делать и реже, и чаще — вплоть до часто- ты 150 герц. Можно даже еще ускорить, если это зачем-то потребуется, просто уменьшив количество значений для осреднения. Прерывание АПЦ мешать таймеру не будет, т. к. длится несколько микросекунд в сравнении с четвертью миллисекун- ды между прерываниями таймера, и в худшем случае прерывание таймера просто отложится на эти микросекунды. А вот долгая процедура вывода через UART, длящаяся около 2 мс, конечно, заста- вит контроллер терять прерывания АЦП. Передачу значений через UART можно, как уже говорилось, облегчить, если воспользоваться прерыванием, но в этом месте могут стоять другие длительные процедуры: перевод в физические величины (см. далее) и затем в распакованные BCD-значения для индикации, запись во внеш- нюю память или EEPROM и т. д. В любом случае — ну, потеряем десяток-другой прерываний, и что, если их частота для нас все равно избыточна? Главное, что при этом измерения не собьются, т. к. в потерянных прерываниях ничего не происхо- дит, даже АЦП на выполнение лишний раз не запускается. Можно даже включить непрерывный режим преобразований, как в предыдущем разделе, но это уже при том условии, что за -400 тактов основной частоты (-100 мкс— столько занимает преобразование при коэффициенте 1:32, поскольку 13 • 32 = 416) вы успеете обра- ботать очередные результаты, т. е. не станете внутри прерывания АЦП выполнять те длительные процедуры, о которых мы говорили ранее. Изложенные схемы использования АЦП можно сколько угодно модифицировать и усложнять. Например, подключение датчика атмосферного давления МРХ4115АР, который выдает, согласно спецификации, 4,69 вольта при предельном для него зна- чении давления 115 кПа (874 мм рт. ст.) следует производить при напряжении пи- тания 5 вольт в качестве опорного. Примерно такую же величину (около 4 вольт) выдает на выходе аналоговый датчик влажности HIH-4000 при максимальном зна-
318 Часть III. Практическое программирование микроконтроллеров AVR чении влажности 99%. Для их подключения вместе с температурным датчиком можно, например, после каждого чтения показаний переключать АЦП на другой канал и одновременно переключать на другое опорное напряжение, примерно так (листинг 11.9). /внутренний ИОН, канал ADC0: ldi temp, (1«REFSO) | (1«REFS1) out ADMUX,temp ;Vref = AVCC, канал ADC1: ldi temp, (1«MUXO) | (1«REFSO) out ADMUX,temp ;Vref = AVCC, канал ADC2: ldi temp, (1«MUX1) | (1«REFSO) out ADMUX,temp ;внутренний ИОН, канал ADC3: ldi temp, (1«MUXO) | (1«MUX1) | (1«REFSO) | (1«REFS1) out ADMUX,temp Но при этом следует учитывать, что после переключения опорного источника пер- вое измерение, как сказано в описаниях контроллеров, «may be inaccurate», и его следует отбрасывать. То есть при необходимости переключения источника опорно- го напряжения целесообразнее не переключать его каждый раз (при этом придется произвести измерений ровно вдвое больше), а применить немного иную схему: сначала выполнить серию измерений для одного канала, потом для другого и т. д. — тогда отбросить придется только одно первое измерение в каждой серии. Как видите, программа проведения измерений с помощью АЦП получается весьма навороченная. И это мы еще не дошли до темы перевода в физические величины с целью вывода на индикацию — об этом в следующем разделе. Перевод результатов в физические величины Тема перевода в физические величины уже обсуждалась в начале этой главы, а не- обходимая арифметика, включая и последующий перевод в распакованный BCD с целью вывода на индикацию, — в главе 8 в связи с реализацией различных ариф- метических процедур на ассемблере. Здесь мы постараемся соединить эти разроз- ненные сведения в единую методику, а также обсудить те нюансы, которые под- робно не обсуждались ранее. Для начала нам следует иметь ориентировочные коэффициенты градуировочного уравнения. Почти все датчики — линейные, т. е. для них хватит двух коэффициен- тов: а0 (значение при нуле параметра) и а\ (крутизна преобразования). Нелинейные
Глава 11. Аналоговый компаратор и АЦП 319 датчики— отдельная тема и не всегда про АЦП. В градуировочном уравнении у = по + п\Х применительно к АЦП под х всегда имеется в виду величина получен- ного кода, а вот у будет иметь размерность физической величины, но подогнанной под удобный масштаб для вывода, например, на индикацию. Имея такие коэффициенты, несложно на основе материала разд. «Операции с веще- ственными числами» главы 8 построить алгоритмы вычисления физических вели- чин. Все физические величины следует представлять в целом виде с нужным коли- чеством разрядов, отбрасывая запятую, которая при индикации устанавливается на своем месте отдельными методами, — чаще всего ее стараются просто зафиксиро- вать на определенном разряде и засвечивают «навсегда» простым подключением к питанию. Все действия следует производить с избыточным числом разрядов, что- бы не терять в точности: в конце оставляете ровно столько, сколько нужно для ото- бражения на индикаторе, не заморачиваясь процедурами правильного округления. Заметим еще, что калибровку мы проводим с обычными физическими величинами, когда, например, температура имеет запятую («23,1»), но нам нужно, чтобы в ре- зультате получалось целое число («231»). Для этого проще всего при расчете гра- дуировочных коэффициентов указать температуру в нужном виде (без запятой) или потом коэффициент а\ умножить на 10. Полученный коэффициент может иметь все-таки недостаточное число целых разрядов, чтобы можно было производить действия в области целых чисел, потому следует сразу применить прием, описан- ный в разд. «Операции с вещественными числами» главы S, и записать в контрол- лер коэффициент аи уже умноженный на нужное двоичное число. О представлении нулевого коэффициента ао мы поговорим далее. После отработки программы и схемы надо провести индивидуальную калибровку, подправив коэффициенты «по месту». Иногда это очень непростое занятие: если температуру несложно откалибровать по положенному рядом термометру, а атмо- сферное давление — просто по сведениям из Интернета о погоде для вашего рай- она, то средства калибровки влажности у рядового пользователя отсутствуют. Влажность в помещении будет разная при открытом или закрытом окне, над бата- рей отопления и под ней, при переносе на пол с поверхности стола, на улице — меняется при выглянувшем солнце или на ветру и т. д. Рецептов тут нет — разве что постараться купить датчик, которому можно верить без калибровки. Бытовые метеостанции тут не помогут— они для всех измеряемых параметров требуют калибровки, а для влажности особенно: совершенно не исключительный случай, когда две бытовых метеостанции, поставленные рядом, показывают влажность с разницей в 10%. Для упрощения расчетов при калибровке можно временно вывести на дисплей зна- чение кода АЦП вместо физической величины — так будет гораздо проще пере- считать коэффициенты. Для расчета, как уже говорилось, удобно пользоваться про- граммой RegStat, но можно и рассчитать новые коэффициенты вручную по двум точкам. Обсудим также тему представления отрицательных значений, которая в первую очередь касается температуры. Программист бы не задумался о том, как представ- лять отрицательные числа на дисплее: помня о договоренности, согласно которой
320 Часть III. Практическое программирование микроконтроллеров AVR единица в старшем разряде означает знак минус, он бы вывел сначала этот знак, а потом применил бы к отрицательному числу операцию neg, получив абсолютное значение, и уже его вывел бы на дисплей. Но электронщик не хочет связывать себя никакими договоренностями, принципиально зависящими от количества разрядов, которыми мы оперируем (см. главу 8). У него изначально есть положительная вели- чина— код АЦП, и все расчеты он предпочитает производить в положительной области. К знаку минус электронщик обращается только при отображении чисел на дисплее, и делает это, например, следующим образом. Пусть для датчика ТМР36 и опорного напряжения 2,56 вольта величина кода АЦП будет лежать в указанных ранее пределах: от 40 до 600 с крутизной 4 единицы кода на 1°С и значением при 0 °С, равном 200. При этом в обе стороны от значения 200 абсолютное значение температуры станет увеличиваться, значит, мы получим два градуировочных уравнения с одинаковыми коэффициентами, но отличающимися знаками. При изменении кода в меньшую сторону придется еще где-то отдельно засвечивать знак минус. В программном исполнении это может выглядеть примерно так. В начале мы для удобства преобразуем наше градуировочное уравнение следующим образом: □ у = а0 + а\Х —► у = а\(х - ко) — для положительной области (х >= к0); □ у = а0 + а\Х —► у = а\(ко -х) — для отрицательной области (х < к0). Очевидно, что к0 здесь просто равно коду АЦП х в точке, где функция у (в нашем случае — температура) обращается в 0. Значение этого коэффициента, который мы назовем «подставкой», очень просто вывести из изначального градуировочного уравнения: ко= - а^1а\. На самом деле в уравнении прямой, у которой аргумент (код) лежит в положительной области, а функция (температура) переходит через ноль, коэффициент а0 должен быть отрицательным, так что «подставка» к0 всегда будет иметь положительное значение. Иногда при калибровке удается непосредст- венно измерить ко, положив температуру у равной нулю, но чаще его приходится вычислять из градуировочного уравнения по приведенной формуле. Теперь алгоритм получается простой и понятный: сначала мы сравниваем текущее значение кода х с «подставкой» ко, и если оно больше или равно ему, то вычисляем температуру по первому уравнению и гасим знак минус, если меньше — вычисляем по второму и зажигаем знак минус. В листинге 11.10 в переменные KoeffH:KoeffL предварительно загружается из EEPROM значение «подставки» ко (в общем случае она может иметь 16 разрядов), к выводу PortB, б подключен знак минус. ;учет знака: ср AregL,KoeffL сравниваем подставку с данными срс AregH,KoeffH brsh Ь0 ;если данные больше, то переход, знак + sub KoeffL,AregL ; иначе -, вычитаем из подставки данные sbc KoeffH,AregH
Глава 11. Аналоговый компаратор и АЦП 321 mov AregL,KoeffL ;и загружаем их обратно mov AregH,KoeffH ;в регистры AregH:AregL sbi PortB,6 ;зажигаем знак минус rjmp mO ;к дальнейшему расчету ЬО: ;если данные больше подставки, то знак + sub AregL,KoeffL /вычитаем из данных подставку sbc AregH,KoeffH cbi PortB,6 ;гасим знак mO: ;умножение на коэффициент крутизны al Процедуру умножения на п\ мы расписывать здесь не станем: все процедуры при- ведены в главе 8. Если вы думаете, что поступить по-программистски и работать с отрицательными числами в дополнительном коде было бы проще, то ошибае- тесь,— в ассемблерном исполнении при числах с очень разной разрядностью в общем случае это сложнее. Пример законченной программы с пересчетом значе- ния температуры и выводом на дисплей приведен в главе 16. Аналогичным образом к AVR можно подключать любые датчики физических ве- личин с аналоговым выходом. Придется, конечно, повозиться с пересчетом в физи- ческие величины, но это не так страшно, как кажется, и в этой книге есть образцы всех необходимых процедур. Куда сложнее провести калибровку, которая обяза- тельно потребуется, если вы хотите, чтобы датчик показывал не «погоду на Мар- се», а истинные величины. Причем в Arduino калибровка требуется не меньше, чем при таком самостоятельном подключении в отсутствие всяких библиотек, и это касается почти любых измерителей физических величин. Потому в целом задачи получаются близки по сложности. Точно так же легко вписываются в ассемблерные проекты различные измерители величин с выходами в виде логического перепада уровней (пироэлектрический дат- чик движения HC-SR501), интервала времени (ультразвуковой датчик HC-SR04 — пример приведен в главе 16), частоты (датчик вибрации AHT-801S) и т. д. Что каса- ется популярных в Arduino модулей датчиков с готовым цифровым выходом, то применять их в ассемблерных проектах нецелесообразно, — почти всегда имеется громоздкий и нестандартный протокол передачи данных (датчики DHT, SHT, АМ2320), часто сложно произвести расчеты физической величины по имеющимся коэффициентам (барометры BMP 180 или ВМР280). Убив кучу времени на перевод алгоритмов чтения таких датчиков на ассемблер, вы ничего не выиграете, — тут язык С или Arduino, в сочетании с готовыми для использования библиотеками, без- условно, вне конкуренции. Заметки на полях Возможно, из дешевых датчиков единственным примером грамотного метрологическо- го подхода может служить восхитительное — без иронии! — творение фирмы Bosch: барометр на основе микросхем (точнее, микромодулей) ВМР180 или ВМР280 с 12С-ин- терфейсом. Абсолютная точность их измерений (порядка 0,1% от шкалы -800 мм рт. ст.) не вызывает никаких нареканий, причем весьма тщательная индивидуальная калиб- ровка проведена при изготовлении, и коэффициенты зашиты прямо в датчик. Столь
322 Часть III. Практическое программирование микроконтроллеров AVR аккуратный подход к точностным показателям, не характерный для дешевых датчиков бытового назначения, объясняется просто: ВМР180/ВМР280 предназначены для ши- рокого рынка устройств, от смартфонов до квадрокоптеров, где они должны с доста- точной достоверностью показывать высоту над уровнем моря. Но для ассемблерного подхода этот датчик недоступен почти абсолютно — если вы заглянете в описание его микромодулей и ознакомитесь с самым простым вариантом алгоритма расчета конеч- ной величины на основе как минимум 11 коэффициентов по 2 байта каждый, то пой- мете, что ковырять на ассемблере 8-разрядного контроллера этот алгоритм будет сродни попытке пешком добраться от Москвы до Санкт-Петербурга, демонстративно игнорируя «Сапсан». Заметьте, что я намеренно не нагружал примеры программ из этой главы процеду- рами собственно вывода на индикацию. Кроме самых простых случаев двухразряд- ных десятичных чисел, такие процедуры требуют громоздких преобразований в BCD-значения (см. главу 8) и будут только затемнять суть приведенных методов. Приемы работы с дисплеями для индикации показаний — совершенно отдельная и большая тема, т. к. правильно подобранный дисплей в значительной степени опре- деляет удобство работы с прибором. Один более или менее универсальный вариант подключения дисплея на основе динамической индикации мы уже рассматривали в главе 9, а ряд альтернативных (более простых, но и более ограниченных) спосо- бов обсудим еще в главах 13 и 16. И основные методы преобразования чисел для индикации также будут приведены там на конкретных примерах.
ГЛАВА 12 Интерфейс SPI Сразу скажем, что применение интерфейса SPI в любительских проектах — доста- точно редкая экзотика (если не считать, конечно, применения ISP-программатора: код загружается в контроллер именно по SPI), разве что иногда он встречается в контроллерах различных дисплеев. С точки зрения пользователя, SPI имеет только одно преимущество перед другими последовательными портами — он более скоро- стной. Зато он требует лишнего провода (а в случае нескольких устройств на одном SPI еще и дополнительно по проводу на каждое устройство) и существует во мно- жестве разнообразных реализаций, нередко предполагающих ручное управление дополнительными нестандартными функциями. Поэтому SPI обычно применяется там, где он абсолютно необходим: например, при общении с быстродействующими устройствами вроде АЦП высокого разрешения или скоростной внешней памяти. На примере общения с такой памятью мы и продемонстрируем работу с SPI. С ее помощью вы можете получить в своем устройстве компактный буфер для хране- ния, например, данных измерений. Здесь будет идти речь о памяти сравнительно небольшой емкости (не более 1 Мбит, т. е. 128 кбайт), которая, однако, существен- но добавляет функциональности AVR-контроллерам. В конце главы мы коснемся вопроса о записи больших объемов данных на флеш-карты, которые также способ- ны работать в режиме SPI. Общие принципы обмена по трехпроводному интерфейсу SPI мы уже рассматрива- ли в главе 3 и там же выяснили, что для различных по назначению устройств могут не совпадать не только названия выводов или самого интерфейса, но и протоколы обмена. На самом деле SPI, несмотря на все эти тонкости, самый простой из всех последовательных интерфейсов, т. к. реализует в чистом виде главную их идею: передавать в каждый момент времени один бит по одной последовательной линии. Идея восходит еще к телеграфу Морзе — наиболее помехоустойчивой линии связи из всех придуманных. SPI отличается тем, что это синхронный протокол: в нем по отдельной линии передаются тактовые импульсы, которые служат для точного оп- ределения момента отсчета бита данных. Простота его обуславливается еще и тем, что, как правило, SPI ведомых устройств могут работать на тактовых частотах от единиц мегагерц и выше, что сравнимо с тактовой частотой МК, поэтому в процес- се передачи никаких специальных задержек не требуется.
324 Часть III. Практическое программирование микроконтроллеров AVR Использование интерфейса SPI описано, например, в «аппноте» 151 (правда, с при- мерами на языке С, а не на ассемблере). В принципе любой последовательный про- токол обмена может быть имитирован программно, и нередко это оказывается це- лесообразнее, чем применение штатных средств, даже если они присутствуют. UART имитировать без крайней необходимости не стоит: штатные средства управ- ления им достаточно удобны и просты в использовании (см. главу 75), а более од- ного UART нам почти никогда не требуется — в отличие от Arduino, где основной UART занят под программирование. В случае SPI программная имитация конкури- рует по простоте с использованием аппаратных средств, и зачастую она даже удоб- нее из-за совпадения у большинства контроллеров выводов интерфейсов SPI про- граммирования и SPI обмена с устройствами, что может, например, помешать внутрисхемному программированию при отладке системы. Поэтому тут мы рас- смотрим и тот, и другой способы. Еще интереснее в этом плане случай интерфейса TWI, который мы будем рассматривать в следующей главе, — там программная имитация почти во всех случаях оказывается удобнее. Основные операции через SPI В большинстве случаев МК выступает при обмене через SPI в роли ведущего (Master). Основная операция через SPI крайне проста (см. рис. 3.3 в главе 3) и за- ключается в том, чтобы передать через выход MOSI ведущего восемь битов данных синхронно с восемью импульсами на выводе SCK в нужной фазе. Фаза зависит от режима SPI: в режиме 0 (mode 0) на SCK изначально низкий уровень, в режиме 3 (mode 3) изначально высокий, а чтение бита производится в обоих случаях по на- растающему фронту (режимы 1 и 2 с чтением по падающему фронту, встречаются значительно реже). Одновременно с передачей через MOSI ведомый (Slave), если это предусмотрено в текущей операции, ведет передачу своих данных, которые в ведущем (Master) могут быть прочитаны сразу по окончании передачи. По сути чтение и запись через SPI — одна и та же операция. Аппаратный вариант Для работы с аппаратным SPI его нужно сначала, естественно, инициализировать. Посмотрим, как это будет выглядеть для ATmega8535 (а также для многих других МК того же класса, например, ATmega8, ATmegal6 и пр., у которых выводы аппа- ратного SPI совпадают с интерфейсом SPI-программирования и занимают старшие биты порта В из имеющихся). Заодно инициализируем и вывод для управления «выбором кристалла» — в большинстве случаев он в интерфейсе SPI играет важ- ную роль. Часто для этого выбирают вывод SS, который в режиме «мастера» не задействован (не забудем, что его нужно сконфигурировать на выход!), но мы для разнообразия выберем вывод РВО— часто требуется управлять несколькими устройствами, одного вывода SS тогда все равно недостаточно, и младшие биты порта В, идущие подряд, оказываются более удобными (листинг 12.1).
Глава 12. Интерфейс SPI 325 И истин г .equ CS = РВО .equ MOSI = РВ5 ;РВЗ для ATmega8 .equ MISO = РВб ;РВ4 для ATmega8 .equ SCK = РВ7 ;РВ5 для ATmega8 - разряды порта В для интерфейса SPI Скорость работы задается битами spi2x в регистре spsr и spri:spro в регистре spcr и может меняться от 1:2(spi2x = i, spri:spro = оо) до 1:128 (spi2x= о, spri:spro = и) тактовой частоты контроллера, что внушает уважение: даже при тактировании 1 МГц частота обмена может достигать 500 килобит в секунду в сравнении с жал- кой сотней у UART, причем достижимой при значительно более высоких тактовых частотах. Что немаловажно, это чистые информационные биты, без добавки каких- либо служебных «стартов» и «стопов». Заметки на полях Из последующих листингов понятно, почему SPI не очень уважают в любительской среде — мало того, что четыре разных режима, так еще в каждом могут быть нюансы: старший бит первым или последним и плюс еще куча дополнительных условий, зави- сящих от конкретного устройства. Приходится, как выражаются «в этих ваших интер- нетах», долго «курить мануал» ведомого устройства (что в переводе означает внима- тельно изучать его документацию), чтобы понять, в каком именно режиме следует инициализировать SPI в каждом конкретном случае. И, судя по всему, нет даже выде- ленного режима, который бы применялся чаще остальных. В этой главе вы встретите на два примера два разных режима. Листинг 12.2 иллюстрирует инициализацию в режиме 0, который устанавливается по умолчанию, если не трогать биты cpol и срна в регистре spcr: «мастер», биты регистра spcr: dord = о — старший бит первым, spri и spro = о, бит spi2x = о — СКОРОСТЬ Угакт/4. ldi temp, (1«SPE)+ (1«MSTR) out SPCR,temp ldi temp, (1«SCK) + (1«MOSI) + (1«CS) + (1«PB4) out DDRB,temp ;SCK,MOSI,CS, PB4 — выходы sbi PORTB,CS ;сразу устанавливаем в 1 Листинг 12.3 иллюстрирует инициализацию в режиме 3: «мастер», биты регистра spcr: dord =o — старший бит первым, spri и spro = о, бит SPI2X (регистр spsr) = о — ldi temp, (1«SPE) + (1«MSTR) + (1«CPOL) + (1«CPHA) out SPCR,temp
326 Часть III. Практическое программирование микроконтроллеров AVR ldi temp, (1«SCK) + (1«MOSIJ + (1«CS) + (1«PB4) out DDRB,temp ;SCK,MOSI,CS,SS - выходы ser temp ;0xFF out PORTB, temp ;PB7..O — высокий уровень весь порт В Процедура записи/чтения у «мастера» тогда будет выглядеть очень просто (лис- тинг 12.4). WR_spi: ;запись/чтение SPI, в temp данные на входе и на выходе out SPDR,temp ;начать передачу wait_spi: sbis SPSR,SPIF /ожидаем конца передачи rjmp wait_spi in temp,SPDR ;чтение данных ret При работе с большинством устройств через SPI, к сожалению, этой простой про- цедурой дело не ограничивается — чаще всего требуется, по крайней мере, вовремя правильно установить «выбор кристалла», причем иногда (если интерфейс у ведо- мого устройства недостаточно скоростной) с формированием искусственных задержек и прочими сложностями, сильно загромождающими программу. Подробности Если используется аппаратный SPI, то его выводы, как мы не раз говорили, в боль- шинстве случаев совпадают с последовательным интерфейсом программирования. Если ISP-разъем для программирования установлен прямо в схеме, то я рекомендо- вал устанавливать внешние «подтягивающие» резисторы для повышения помехо- устойчивости. В подавляющем большинстве случаев наличие этих резисторов никак не скажется на работе SPI. Однако следует учитывать, что формируя выводы MISO и SCK на выход и оставляя на них низкий уровень (как в режиме 0), тем самым мы обеспечиваем дополнительное потребление через «подтягивающие» резисторы на этих выводах, что может быть важно в устройствах с энергосберегающими режимами. В некоторых случаях наличие резистора на выводе Ml SO необходимо — например, если ведомое устройство работает от питания 2,7 В, а МК — от 5 В, то уровня логиче- ской единицы на этом выводе может не хватить для нормальной работы МК, и рези- стор поможет решить эту проблему (кстати, МК семейства х51 этого недостатка лише- ны). Однако в таком случае неизбежна утечка через этот резистор и ограничивающий диод на входе ведомого устройства. По всем этим причинам в аппаратуре с примене- нием режимов энергосбережения в общем случае устанавливать «подтягивающие» резисторы по выводам SPI не рекомендуется (как и во многих других случаях, когда выводы программирования служат обычными портами на выход). В процессе обмена возможно использовать и прерывания SPI. Хотя это и разгрузит контроллер на время посылки байта, но менее удобно — сильно усложняется логи- ка построения программы. Прерывание возникает всякий раз, когда заканчивается передача очередного байта, а связанные с этой передачей действия существенно различаются, и обработчик прерывания состоял бы из сплошной «лапши» услов-
Глава 12. Интерфейс SPI 327_ ных переходов. В доступной литературе я ни разу не встречал примеров с реализа- цией прерывания SPI ни на С, ни на ассемблере, т. к. обычная последовательная процедура куда проще в отладке — учитывая особенно, что в протоколе обмена по SPI с конкретными устройствами сама по себе посылка-прием байта протекает быстро. И в большинстве случаев посылка — еще не самое главное в программе, т. к. бывает обставлена всякими дополнительными условиями. Программный вариант Программный вариант процедур чтения/записи более громоздок, но зато позволяет задействовать любые удобные выводы портов МК (в том числе у таких контролле- ров, которые вообще аппаратного SPI не имеют, как многие Tiny, включая и «лю- бимую» ATtiny2313). Здесь не требуется особой инициализации, кроме формирова- ния направления работы соответствующих выводов. Листинг 12.5 иллюстрирует, как будет выглядеть чтение/запись для «мастера» в варианте, соответствующем режиму 0. .equ CS = 0 /выводы PortB .equ MOSI = 1 .equ MISO = 2 .equ SCK = 3 /инициализация SPI /установка MOSI, SCK, CS на выход ldi temp, (1«CS) I (1«MOSI) I (1«SCK) out DDRB,temp cbi PORTB,SCK cbi PORTB,MOSI sbi PORTB,CS ;чтение/запись RW_spi: ;запись/чтение через SPI посылаемый байт в data_out, /принимаемый в data_in cli ;на случай, если в программе есть длинные прерывания, /иначе можно удалить ldi temp, 8 /счетчик битов clr data_in spi_loop: lsl data_out /старший бит в перенос brcc put_0 /если в переносе 0 — перейти sbi PORTB,mosi пор /задержка на один такт rjmp r_bit
328 Часть III. Практическое программирование микроконтроллеров AVR put_O: cbi PORTB,mosi пор /задержка на один такт r_bit: sbi PORTB,sck ;вьщали строб и подождали пор ;задержка на один такт sbic PINB,miso ;читаем бит с miso rjmp rl_bit clc /если 0 — сбросим перенос rjmp rend_bit rl_bit: sec ;если 1 — установим перенос rend_bit: rol data_in /перенос во входной байт cbi PORTB,sck ;вьщали строб и подождали пор dec temp brne spi_loop sei ;если команды cli нет, то тоже можно удалить ret При тактовой частоте МК 4 МГц такая процедура обеспечит скорость передачи по- рядка 0,5 МГц. Пустые операции (пор) нужны, чтобы сформировать задержку (-250 не при 4 МГц) между формированием данных на линиях MISO и MOSI и мо- ментом их чтения (перепадом на SCK). Если быстродействие ведомого позволяет, то эти операции можно убрать. В качестве достаточно простого примера использования SPI рассмотрим обмен с flash-памятью серии AT45DB. Но сначала попробуем подробнее разобраться в различных типах памяти, которые имеются в продаже. О разновидностях энергонезависимой памяти Напомним, что изначально существовала программируемая энергонезависимая па- мять PROM, в том числе и однократно программируемые кристаллы (ОТР PROM) — по-русски ППЗУ. Добавка буквы Е (Erasable, стираемая) означала, что такие микро- схемы позволяют стирать и программировать информацию заново несколько раз. ОТР EPROM и УФ-стираемые версии EPROM давно не применяются, и все совре- менные ППЗУ являются электрически перепрограммируемыми— ЭСППЗУ, или, по-английски, EEPROM. Эти две буквы: ЭС/ЕЕ, в сущности, вывели такие микро- схемы из-под определения ROM (Read Only Memory, т. е. «память только для чте- ния»), т. к. позволяют многократно менять информацию прямо в устройстве, но название сохранилось, позволяя надежно отличить такие микросхемы от обычной быстродействующей памяти RAM. Модная ныне flash-память тоже, вообще говоря, относится к EEPROM, но чтобы подчеркнуть разницу в технологиях, чаще всего под EEPROM имеют в виду разновидность с произвольным доступом к байтовым
Глава 12. Интерфейс SPI 329 (обычно) ячейкам, в то время как flash-память может программироваться только блоками. В табл. 12.1 приведены некоторые наиболее распространенные типы микросхем памяти EEPROM, спроектированные в Atmel. Отметим, что номера, обозначающие серию (а также в большинстве случаев и цоколевка выводов), чаще всего сохраня- ются для тех же разновидностей памяти и у других производителей. Таблица 12.1. Некоторые серии микросхем памяти производства Atmel Номер серии 27 28 25 24 45 Емкость, биты 256 К + 8 М 64 К + 4 М 1 К-М М 1 К-М М 1 М + 64 М Тип и интерфейс Параллельная ОТР EPROM Параллельная EEPROM Последовательная (SPI) EEPROM Последовательная (I2C) EEPROM Последовательная (SPI) Flash Питание, В 4.5+5.5 4.5-5.5 1.8+5 5 1.7+5.5 2.7+3.6 Отметим, что микросхем flash-памяти серии 45 в номенклатуре Atmel (точнее, со- ответствующего подразделения Microchip) больше не числится. Однако эти микро- схемы доступны в продаже и выпускаются другими фирмами (Adesto), потому слегка остановимся на разнице между EEPROM и Flash. Ее легко понять, если вни- мательно рассмотреть технические характеристики той и другой: например, в серии АТ25 (EEPROM) запись одного байта занимает 5 мс, в то время как в серии АТ45 (Flash) те же 3-6 мс займет запись целой страницы (512 или 528 байтов), но предва- рительно придется ее стереть, что потребует 40 мс. Время стирания целой микро- схемы АТ45 объемом 16 Мбит потребует около 25 секунд. Но даже с учетом таких долгих операций, среднее время на перепрограммирование одного байта во Flash окажется примерно в 100 раз меньше. Отсюда и различие в применении: при боль- шой емкости последовательно заполняемой памяти целесообразнее Flash, если же требуется стохастическое выборочное заполнение памяти побайтно, без перепро- граммирования других ячеек, то EEPROM оказывается удобнее. Доступ к «чистой» EEPROM проще и заключается по сути в двух очевидных опе- рациях: послать команду— записать/прочесть байт (см. главу 10). Зато такая память в целом медленнее и к тому же, как видно из табл. 12.1, имеет небольшую емкость. Работу с EEPROM серии АТ24 мы рассмотрим в следующей главе, когда будем говорить о функционировании интерфейса 12С. Здесь же мы опишем flash- память серии AT45DBjccjcB, которая отличается встроенным SRAM-буфером и операциями записи, объединяющими стирание страницы в памяти с собственно записью. Серия работает при напряжении питания 2,7-3,6 В и может иметь емкость до 64 Мбит (8 Мбайт). Обратите внимание, что, как и любая flash-память, микросхемы серии AT45DB достаточно много потребляют в режиме записи/стирания: до 20- 25 мА. Это присуще всем микросхемам энергонезависимой памяти, причем в об- щем случае потребление с увеличением емкости кристалла растет.
330 Часть III. Практическое программирование микроконтроллеров AVR В версии «В» значение емкости страницы (и соответственно, емкости промежуточ- ного буфера) — 264 байта. Модели емкостью более чем 8 Мбит имеют больший размер буфера, но столь же «некруглое» его значение. Версия 45DBxxxD отличает- ся от версии «В» внутренней организацией — в ней, в частности, можно переклю- чать объем страницы (и буфера) на размер 256 байтов. Принципиально это значе- ния не имеет — заполнять буфер целиком, естественно, необязательно. Если вели- чина порции данных критична, то можно просто проигнорировать «лишние» 8 байтов в каждой странице или (как, очевидно, подразумевается) использовать их для каких-нибудь служебных целей— например, для хранения контрольной суммы. Наличие буфера позволяет при необходимости модифицировать всего один или несколько байтов, быстро переписать страницу из памяти в буфер, заменить в нем нужные ячейки и записать обратно обновленную страницу целиком. Отметим, что микросхемы версии «В», начиная с емкости 2 Мбит (а микросхемы «D», начиная с емкости 4 Мбит), имеют по два таких буфера, что позволяет вести непрерывную запись, не останавливаясь на время долгих операций стирания и записи в основную память, которые тогда могут протекать в фоновом режиме. Количество страниц в памяти зависит от емкости кристалла — так, в 1-мегабитовой версии таких страниц 512 (а общая емкость, стало быть, 135 168 байтов или 1 081 344 битов). На примере микросхемы 45DB011B (емкостью 1 Мбит) мы далее и продемонстрируем базовые операции чтения и записи через интерфейс SPI. Запись и чтение flash-памяти через SPI Мы используем для AT45DB раздельный режим записи и чтения, когда сначала данные пишутся в SRAM-буфер, а затем он целиком переносится в страницу ос- новной памяти (возможен и объединенный режим, когда запись и чтение произво- дятся прямо в основной памяти «сквозным» методом, но он более громоздкий). На- помним, что flash-память перед записью требуется стирать отдельной операцией, но в серии AT45DB, как и в большинстве других разновидностей, эти операции мо- гут быть объединены, причем суммарного времени на объединенную операцию уходит даже меньше. Операции с микросхемой памяти 45DB011B Сама по себе память 45DB011B допускает тактовые частоты обмена до 20 МГц (a 45DB011D, кстати, даже до 66 МГц), но мы, конечно, себе такое позволить не можем и скромно обойдемся величиной 1 МГц— максимально допустимой для аппаратного SPI в обычном режиме (без удвоения частоты) при частоте тактового генератора 4 МГц. Отметим, что после включения питания сразу начинать опера- ции с микросхемой 45DB011B нельзя— необходимо выждать не менее 20 мс (в программе далее это не отражено, т. к. запуск процедур будет осуществляться вручную).
Глава 12. Интерфейс SPI 331 Конкретная микросхема выбирается аппаратно через контакт «выбор кристалла» CS (напомним, что активный уровень на этом выводе — низкий, потому обычно он обозначается с инверсией). На рис. 12.1 приведены диаграммы записи и чтения для памяти 45DB011B, кото- рыми мы будем руководствоваться в написании программы. Операции записи и чтения в буфере специального ожидания не требуют и происходят в реальном вре- мени. Процедура записи страницы из буфера в основную flash-память с одновре- менным стиранием занимает до 20 мс (по отдельности эти операции заняли бы 15 и 10 мс, соответственно). Обратная операция переноса страницы из основной памяти в буфер выполняется гораздо быстрее (250 мкс), но также требует ожидания. Так что после посылки команды на запись или на чтение страницы нужно либо выждать заведомо большее время, либо проверять старший бит rdy/buzy регистра статуса микросхемы, который сбрасывается на время чтения или записи/стирания. Установка в единичное состояние этого бита означает, что микросхема готова к следующей операции. Второй способ, естественно, более корректен, и мы пойдем именно этим путем. Запись в буфер Перенос буфера в страницу flash-памяти cs Ч Перенос страницы flash-памяти в буфер cs~~\ Чтение буфера СХХХХХХХХ so- Рис. 12.1. Диаграммы записи и чтения данных через встроенный буфер для flash-памяти серии 45DBxxxB (X — любое значение, п — последовательный номер байта данных) Первый байт в каждом случае — код операции, для наглядности эти коды приведе- ны непосредственно на диаграммах рис. 12.1. В адресации буфера и страницы памяти имеются определенные нюансы. Так, адрес в буфере всегда предваряется пустым байтом с произвольным значением (на диаграммах обозначен, как X), остальные биты адреса располагаются в двух байтах (полный адрес буфера объемом
332 Часть III. Практическое программирование микроконтроллеров AVR 264 байта будет содержать 9 битов bfao. .bfa8, поэтому в первом байте размещен только 9-й бит — bfas). При чтении, кроме того, необходимо послать пустой байт после адреса. Адресация основной памяти в страничных операциях (чтение в буфер из основной памяти и запись из буфера в нее) производится несколько иначе: адрес страницы также 9-битовый (рао. . раэ), эти биты располагаются также в двух байтах, но со сдвигом на одну позицию влево: младший бит младшего байта адреса остается пус- тым и может иметь любое значение. Пустой байт при адресации страниц требуется послать только после адреса. Буквами г на диаграмме обозначены биты адреса страницы, зарезервированные для старших моделей памяти. Организовать обмен мы попробуем штатным способом на основе аппаратного SPI. На рис. 12.2 приведена схема соединений на основе ATmega8535 для этого случая. Отметим, что серия АТ45 не допускает 5-вольтового напряжения питания (единст- венная среди прочих в табл. 12.1), поэтому для простоты мы тут питаем всю схему от напряжения 3,3 вольта (если внешний источник выдает обычные 5 вольт, то сле- дует установить 3-вольтовый стабилизатор— например, типа LP2950-3.3). В реальных применениях вам, возможно, понадобится подключать контроллер к 5 вольтам, тогда 3-вольтовый стабилизатор следует установить отдельно на мик- росхему памяти, а управляющие выходы контроллера (РВО, MOSI и SLK) под- 1 РВО (ТО) 2 РВ1 (Т1) 3 РВ2 (Т2) 4 РВЗ (ОС0) 5 РВ4 (SS) 6 РВ5 (MOSI) 7 РВ6 (MISO) 8 РВ7 (SCK) 9 Reset 10Vcc 11GND 12XTAL2 13XTAL1 14PD0(RxD) 15PD1(TxD) 16 PD2 (INTO) 17PD3(INT1) 18PD4(OC1B) 19PD5(OC1A) 20PD6(ICP1) (ADCO) РАО 40 (ADC1) PA1 39 (ADC2) PA2 38 (ADC3) РАЗ 37 (ADC4) PA4 36 (ADC5) PA5 35 (ADC6) PA6 34 (ADC7) PA7 33 AREF 32 GND31 AVcc 30 (TOSC2) PC7 29 (TOSC1)PC6 28 PC5 27 PC4 26 PC3 25 PC2 24 (SDA) PC1 23 (SCL) PCO 22 (OC2) PD7 21 ATmega8535 Рис. 12.2. Схема для тестирования процедур записи и чтения flash-памяти 45DBxxxB по последовательному интерфейсу SPI
Глава 12. Интерфейс SPI 333^ ключить через резистивные делители 3:2 (например, 3,3 кОм + 6,8 кОм) или микро- схему-согласователь уровней (о них говорилось в главе 4). Как вы знаете, выводы аппаратного SPI практически во всех моделях МК AVR сов- падают с SPI-интерфейсом программирования. Поэтому запускать даже в прове- рочном режиме работу SPI сразу после включения было бы неправильно — нужно хотя бы дать возможность отключить программатор. В нашем случае мы сделаем так, чтобы работа начиналась при нажатии кнопки Кн1. Отметьте, что выводы WP (Write Protection, защита от записи) и RESET микро- схемы 45DB011B в рассматриваемом случае не задействованы и присоединены к высокому уровню напряжения. Программа обмена с памятью 45DB011B по SPI Полностью текст программы обмена с памятью 45DB01 IB no SPI вы можете найти в архиве, адрес которого указан во введении (файл rw45DB011B.asm). Программа на- писана на основе примера (модифицированного и с исправленными неточностями), описанного в [17]. Программа громоздкая, потому приводить ее здесь даже фраг- ментами я не буду, рассмотрим только основные моменты, остальное вы найдете в комментариях в тексте программы. По прерыванию от кнопки Кн1 мы запускаем TimerO, который будет служить сред- ством антидребезга и одновременно защиты от слишком частых нажатий на кноп- ку: прерывания от кнопки будут сразу запрещаться, а в прерывании таймера по ис- течении времени в 1 с разрешаться опять (для отсчета одной секунды мы заведем счетчик переполнений, который при тактовой частоте 4 МГц и коэффициенте 1:256 должен считать до 61). В этом же прерывании будем запускать процедуры записи (write) и чтения (read) с последующей выдачей результата «наружу» через UART (операция outcom, она рассмотрена в главе 75). Процедуры эти здесь в значительной степени демонстрационные: мы будем запол- нять 256 байтов в буфере, начиная с нулевого адреса, значением счетчика (count) этих же байтов, т. е. последовательными возрастающими значениями, начиная с нуля. На схеме рис. 12.2 указаны для подключения к адаптеру оба вывода UART (RxD и TxD)— на случай, если вы захотите попробовать модифицировать про- грамму так, чтобы она работала в более приближенном к практике режиме: записы- вала во флеш-память данные из последовательного порта. После операции записи в буфер всех 256 байтов следует операция переноса буфера в страницу памяти (для примера произвольно выберем страницу с адресом юо). Для проверки бита 7 (rdy/busy) в регистре статуса микросхемы 45DB011В мы орга- низуем замкнутый цикл чтения этого регистра до момента установки этого бита в единичное состояние. Заметки на полях Это весьма опасный момент— если связь с памятью прервется, то программа «по- виснет», — но, как мы говорили ранее, такое часто встречается в практике програм- мирования МК. Ведь если память окажется с дефектом, то, скорее всего, и все уст-
334 Часть III. Практическое программирование микроконтроллеров AVR ройство можно считать неисправным, так что дальнейшая работа программы уже не понадобится. В критичных случаях в цикл ожидания ответа от памяти можно, напри- мер, «вклинить» то же самое прерывание таймера, которое в случае неудачи по исте- чении некоторого времени прервет цикл ожидания и отправит через UART условный код, сигнализирующий о неисправности памяти. Другой более распространенный при- ем — запуск сторожевого таймера, который попросту перезапустит контроллер при «зависании» (см. главу 14). Записанные значения мы будем читать (процедура read): сначала следует перенос страницы в буфер, потом ожидание окончания операции, как и в случае записи, за- тем последовательное побайтное чтение из буфера с последующей посылкой через UART на внешний компьютер, чтобы убедиться в правильности чтения/записи на- глядно. Запись и чтение flash-карт Существует множество разновидностей карт на основе flash-памяти— только основных семейств около полудюжины. Хотя все они уже практически вытеснены компактными Secure Digital, на прилавках все еще можно встретить Compact Flash, Multi Media Card, Memory Stick и xD-picture, иногда даже попадаются полузабытые Smart Media и совсем забытые PC Card. В рамках каждого из семейств имеется зна- чительное число модификаций, иногда несовместимых между собой. Сами семей- ства различаются в первую очередь интерфейсом доступа— так, Compact Flash (как и совместимые с ними PC Card) совместим со старой параллельной шиной подключения жестких дисков IDE/AT А, остальные обладают различными последо- вательными интерфейсами. В самодельных устройствах чтения и записи лучше всего подходят flash-карты Multi Media Card (MMC), отличающиеся тем, что интерфейс у них менее запутан- ный, чем у остальных, и у всех модификаций совместим с SPI. Существует не- сколько разновидностей ММС. Кроме оригинальных карт длиной 32 мм, изготав- ливают специальные укороченные (18 мм длиной, их часто называют RS-MMC) — за исключением размера корпуса, они ничем не отличаются от обычных. Кроме простых 7-контактных ММС, чаще выпускают высокоскоростные High Speed ММС, у которых не 7, а 13 контактов. Эти выводы служат дополнительными ли- ниями обмена данными (фактически превращая интерфейс из последовательного в полупараллельный), в режиме доступа по SPI они игнорируются. Оригинальная ММС работает при напряжении питания от 2,7 до 3,6 В, но также появились карты с добавкой DV (Dual Voltage) в названии, которые могут работать при напряжении питания 1,8 и 3,3 В. Карты с добавками к названию Plus и Mobile являются одно- временно High Speed и DV (и отличаются друг от друга размером). Наиболее распространенные сейчас карты Secure Digital (SD) представляют собой по сути расширенную версию ММС, хотя производители всячески от этого откре- щиваются, — например, устройства, совместимые с SD, могут читать ММС, а вот наоборот — не всегда. SD, кроме всего прочего, сделана толще. SD были ориенти- рованы на шифрование контента, подобно тому, как это делается на DVD, хотя в итоге эта функция применяется очень редко — рынок продажи защищенного кон- тента на картах SD умер, не успев возникнуть. SD (кроме версии Secure Digital High
Глава 12. Интерфейс SPI 335 Capacity — SDHC, емкостью выше 2 Гбайт, которая, в отличие от ММС и «просто- го» SD, имеет не побайтовую, а блочную адресацию) совместима с ММС по основ- ным семи контактам, но имеет на 2 контакта больше, что, вместе с зарезервирован- ным в протоколе ММС седьмым контактом, образует три лишних линии для пере- дачи данных. В SPI-режиме SD, как и ММС, работают через одну линию, что делает их физически совместимыми. Протоколы доступа по SPI для SD и ММС практически не различаются, однако есть некоторые нюансы, которые не позволя- ют без доработки программы подключать SD вместо ММС. Если захотите попро- бовать — в публикации [18] очень подробно и доходчиво рассказывается о доступе к SD-картам через SPI (с примерами, правда, на Arduino, а не на ассемблере), и, ру- ководствуясь сведениями оттуда, программу можно будет доработать. Заметки на полях Хочу сразу подчеркнуть, что с помощью методики, излагаемой, как здесь, так и в пуб- ликации [18], доступ к картам производится на низком уровне, т. е. минуя файловую систему (а если карта была отформатирована, то ее файловая система может ока- заться испорченной, и карту придется форматировать заново). Карты здесь использу- ются, как расширенная версия энергонезависимой флеш-памяти, которую мы рас- сматривали в предыдущем разделе, — карточку нельзя будет вставить в компьютер и считать с нее данные обычным способом, через Проводник, для этого придется пи- сать специальную программу. Понятно, что такое ограничение резко сужает область применения карт какими-нибудь особыми случаями. Я использовал эту методику для изготовления секретных ключей доступа к электронным замкам или закрытым от постороннего использования про- граммам —- тут невозможность прочесть карту стандартным способом играет на руку. Вы записываете в произвольную область карты ключ любой желаемой длины, а если его еще и зашифровать, то разобраться будет непросто даже специалистам. Можно даже записать на карту для маскировки какую-нибудь музыку обычным способом, а ключ записать по одному из последних адресов карты, чтобы не нарушать имею- щуюся файловую систему. Если вы хотите узнать, как можно своими руками реализовать файловую систему, то отсылаю вас к публикации [19]. Возможно, ознакомление с этой статьей убедит вас в том, что самостоятельно ковыряться в недрах системы FAT16 (а она ведь еще и са- мая простая из применяемых сегодня) не имеет смысла, тем более, что там опять же приведены примеры для Arduino, а мы имеем в виду ассемблер. Для любой работы должен быть адекватный инструмент, и если возня с этим инструментом усложняет, а не облегчает вам жизнь, значит его нужно сменить. В Arduino есть стандартная биб- лиотека SD (далеко не идеальная, но пригодная для практических целей), которая по- зволяет писать на SD-карту в нормальном режиме, так что эти данные потом можно будет прочесть где угодно. А в профессиональной области таких библиотек еще больше. Мы остановимся на оригинальной 7-контактной ММС с достаточно простым ин- терфейсом, полное техническое описание которой можно разыскать по ссылкам в статье «MultiMediaCard» английской «Википедии». Подключение карт ММС Выводы карты ММС отсчитывают от скоса на корпусе (для 13-контактных карт это ряд, ближний к краю) — если расположить ее контактами к себе и вверх, то первый вывод будет слева. Назначение контактов в режиме SPI приведено в табл. 12.2.
336 Часть III. Практическое программирование микроконтроллеров AVR Таблица 12.2. Назначение выводов ММС в режиме SPI Номер вывода 1 2 3 4 5 6 7 Название CS Dl (Dataln) GND Vcc CLK GND DO (DataOut) Назначение Выбор карты Вход данных «Земля» Питание Тактовые импульсы «Земля» Выход данных Предельно допустимая тактовая частота SPI-интерфейса ММС (по крайней мере по стандартам версий 2jc и 3.x)— 20 МГц, что находится далеко за пределами воз- можностей нашего контроллера. Максимально допустимое напряжение питания карты составляет 3,6 В, с напряжениями питания от 2,7 В работают все современ- ные карты, ниже 2,7 В — только некоторые. Заметим, что имеется механизм, позволяющий установить возможность работы карты с конкретным напряжением питания — специальный 32-битовый регистр ocr (Operation Conditions Register) со- держит сведения об этом. Его можно прочесть специальной командой (readocr с кодом 58 — о том, как посылать команды в ММС, рассказано далее) и по таблице, имеющейся в фирменном руководстве, установить рабочее напряжение питания карты: старший и младший байты могут принимать любое значение, а биты двух средних байтов, начиная со старшего, обозначают допустимые напряжения питания с разбросом 0,1 В, начиная с 3,6 В (например, бит номер 24 регистра, установлен- ный в 1, означает допустимое напряжение питания в пределах 3,5-3,6 В). Биты с 15-го по 24-й, установленные в 1, сигнализируют о том, что рабочее напряжение питания не менее 2,7 В. Чем ниже допустимый минимум напряжения питания, тем больше младших битов установлено в состояние 1. Схема для проверки процедур обмена с картой (рис. 12.3, слева) в нашем случае ничем не отличается от приведенной на рис. 12.2 для микросхемы AT45DB. Конеч- но, не стоит портить карту пайкой непосредственно к ее выводам. У выводов карты шаг 2,5 мм, поэтому самодельный разъем для отладки программы доступа можно сделать, например, из двухрядного игольчатого разъема PLD, если загнуть контак- ты одного ряда внутрь (рис. 12.3, справа). Отметим, что на линиях DO, DI и CLK здесь рекомендуется инструкцией устанав- ливать «подтягивающие» резисторы (на схеме не показаны), хотя минимальное значение этих резисторов, согласно рекомендациям, должно быть 50 кОм (явный расчет на применение встроенных резисторов в микроконтроллерах). На практике карта нормально работает и с обычными для наших схем резисторами 3-5 кОм (но, конечно, потребление при этом будет больше).
Глава 12. Интерфейс SPI 337 1 3 SCK со Т о MISO § Рис. 12.3. Подключение flash-карты ММС к схеме, показанной на рис 12.2; самодельный разъем для присоединения карты (справа) Подача команд и инициализация ММС Общий принцип работы интерфейса SPI в картах немного отличается от привычно- го нам побайтного доступа. Если помните, в главе 3 было сказано, что в интерфейсе SPI нет никакого маркера, разделяющего байты, — это попросту бесконечный по- ток битов, синхронизированных фронтами по линии SCL. У карт памяти, которые и сами могут иметь самое разное устройство и применяться с различными контрол- лерами, между информационными посылками могут присутствовать достаточно большие промежутки, необходимые контроллеру или карте на то, чтобы «перева- рить» нужную информацию. Поэтому промежуток до команды, или от команды до получения отклика, или до начала массива данных (см. далее описание команд) может быть произвольным. Поскольку внутреннего тактового генератора у карты нет, то работа ее в таких пау- зах обеспечивается тактами по линии CLK, при этом на линии DI должен быть вы- ставлен высокий уровень (что равносильно посылке байтов, равных $ff). На линии DO в большинстве случаев при этом также присутствует высокий уровень, за ис- ключением состояния, когда карта занята, например, записью блока в память (buzy), — тогда здесь низкий уровень. Стандарт устанавливает для большинства таких промежутков максимальное значение в 8 восьмибитовых посылок (мини- мальное может быть в разных случаях как 0, так и 1). Первый (старший) бит любой команды (либо байта отклика, о чем далее) всегда равен нулю, так что по сбросу в нулевое состояние на линии можно определить начало информационного байта. Аналогично, массив данных всегда предваряется байтом, в котором последний (младший) бит равен нулю ($fe, о чем далее), так что устройство «знает», когда на- чинать отсчитывать данные. Общий принцип подачи команд и данных иллюстрируется приведенной на рис. 12.4 диаграммой, отображающей пример процедуры записи блока данных.
338 Часть III. Практическое программирование микроконтроллеров AVR Длина команды в картах ММС фиксированная и составляет 6 байтов (48 битов). Первые два бита (47 и 4 б), посылаемые «мастером»— в терминологии описания ММС он называется хостом (host), — всегда равны 01. Следующие шесть битов старшего байта представляют собой код команды. Таким образом, любая команда формируется из ее кода (который может лежать в пределах от о до 63) прибавлени- ем числа $40. После команды должны идти четыре байта (32 бита) аргумента, для команд без аргументов на этом месте должны стоять нули. Для рассматриваемых далее операций чтения или записи это — адрес начального байта (а не блока!). CS 0 1 di 1x|x1h|c|a|a|a|a|cr)h|h1h|fe|d|d| jd|d|h|h|h|h| jh|h|x|x| 510 511 |—команда 1 |—данные—| |—BUZY—| DO |ZiZlZ|H|H|H|H|HiH|H|O|H|H|H|Hl|pHkRicR|LlLl 1Щ Рис. 12.4. Временная диаграмма подачи команд на карту ММС: X —любое состояние; Z — «третье» состояние; Н — высокий уровень ($FF); L — низкий уровень ($00); С — байт кода команды (ObOlnnnnnn, где п — биты кода команды); А — байты аргумента (адреса), CR — код CRC; FE — байт со значением $FE Заметки на полях Собственно, с тем фактом, что 32-битовое число адресует каждый байт индивидуаль- но, и была связана необходимость принятия нового стандарта карт SD под названием SDHC, т. к. максимальная емкость по рассматриваемому нами стандарту ММС и SD может составить теоретически лишь 4 Гбайт, а на практике, учитывая ограничения файловой системы FAT16, — лишь 2 Гбайт. Зато такая адресация позволяет читать информацию побайтно, начиная с любого места в памяти, что, учитывая назначение флеш-карт, — явный анахронизм. Наконец, последний байт в команде должен нести значение циклического кон- трольного кода CRC (Cyclic Redundancy Code), причем собственно CRC занимает семь старших битов этого последнего байта, а младший (самый последний бит команды)— всегда единица. CRC— не контрольная сумма LRC, о которой мы говорили в главе б в связи с НЕХ-файлами, а более «крутая штука», на которой здесь останавливаться нет смысла, — в Интернете найдется множество ресурсов, где это понятие подробно разъясняется, а в техническом описании карт ММС пред- лагается весьма «навороченный» алгоритм его расчета. Нам здесь важно, что в режиме SPI для карты ММС по умолчанию механизм про- верки CRC отключен, и почти для всех команд можно посылать значение последне- го байта, равное $ff. Это сильно облегчает задачу, т. к. проверка значения CRC в общем-то ориентирована на реализацию в аппаратной, а не программной форме (где она получается достаточно громоздкой), а такого модуля у нас, естественно, нет. Потому вычислять CRC нам не придется — за одним важным исключением: самой первой должна идти команда сброса карты goidlestate (с кодом, равным нулю), в которой наличие CRC обязательно. К счастью, его уже подсчитали за
Глава 12. Интерфейс SPI 339_ нас — значение последнего байта для этой команды с учетом CRC будет равно $95, а вся команда полностью тогда будет представлять последовательность байтов: $40, $00, $00, $00, $00, $95. Перед любой командой, как показано на рис. 12.4, лучше подать не менее восьми импульсов по линии CLK при высоком уровне на линии DI — проще говоря, вы- дать $ff. После подачи любой команды записи или чтения карта посылает отклик (в SPI-режиме в большинстве команд — один байт), который иногда полезно про- анализировать: отдельные биты в нем сигнализируют, например, об ошибке, если достигнут конец памяти, или о неправильно принятой команде. Перед посылкой отклика карта выжидает как минимум в течение восьми тактов, посылая единицы (байт, равный $ff), старший бит отклика всегда равен нулю. Если все нормально, отклик должен содержать нули во всех разрядах, за исключением рассматриваемой команды сброса, где отклик должен быть равен $01. На практике все проверенные мной карты (опыт некоторых авторов публикаций в Сети это также подтверждает) выдавали ровно один пустой байт перед откликом. Таким образом, процедура посылки любой команды в карту, кроме goidlestate, будет выглядеть, как показано в листинге 12.6. ;====== Передача команды в карту ======= ;в CMD — команда, в AddrHH:AddrLL аргумент Send_command: ser temp ;$FF rcall WR_spi /пустой байт mov temp,CMD rcall WR_spi /команда mov temp,AdrHH rcall WR_spi /старший байт аргумента mov temp,AdrHL rcall WR_spi ;3 байт mov temp,AdrLH rcall WR_spi /2 байт mov temp,AdrLL rcall WR_spi /младший байт аргумента ser temp ;$FF вместо CRC rcall WR_spi ser temp ;$FF rcall WR_spi /пустой байт ser temp /$FF rcall WR_spi /в temp — отклик ret Для возврата карты в исходное состояние и перевода ее в SPI-режим необходимо сбросить линию CS в состояние логического нуля и подать команду goidlestate
340 Часть III. Практическое программирование микроконтроллеров AVR (без этих действий карта будет после включения работать в режиме ММС Protocol). Байт отклика после этого должен быть равен $01 (младший бит, равный 1, сигнали- зирует о том, что карта находится в состоянии ожидания inidie state). Потом следует выждать некоторое время (инструкция рекомендует не менее 74 циклов на линии CLK) и послать команду инициализации sendopcond (ее код равен l), после подачи которой убедиться в том, что байт отклика равен нулю, — т. е. карта готова к работе. Если отклик отличен от нуля, то следует подать команду sendopcond несколько раз. Перед подачей команды после сброса линии CS следует послать пустой байт $ff, чтобы дать карте возможность «прийти в себя», — линия DO при этом может еще оставаться в третьем состоянии. Если сброс был осуществлен ранее, пустой байт не помешает. На практике процедура сброса и инициализации карты ММС может выглядеть так, как показано в листинге 12.7 (описание процедуры WRspi приведено ъразд. «Основные операции через SPI» этой главы). Если используется аппаратный SPI, то инициализировать порт можно в режиме 0. ;-===== Инициализация карты ========= MMC_ini: cbi PORTB,CS ;CS = 0 ser temp ;$FF rcall WR_spi ;пустой байт ldi temp,$40 ;CMD0 rcall WR_spi /команда GO_IDLE_STATE clr temp rcall WR_spi /нулевой байт clr temp rcall WR_spi ;нулевой байт clr temp rcall WR_spi /нулевой байт clr temp rcall WR_spi /нулевой байт ldi temp,$95 /CRC rcall WR_spi ser temp /$FF rcall WR_spi /пустой байт ser temp /$FF rcall WR_spi /в temp — отклик 01, что означает In_idle state /rcall out_com /можно послать наружу через UART для контроля /задержка на 80 тактов ldi count,8 delay_80: ser temp /$FF rcall WR_spi /пустой байт dec count
Глава 12. Интерфейс SPI 341 brne delay_80 clr AdrHH clr AdrHL clr AdrLH clr AdrLL . /посылаем команду SEND_OP_COND сколько надо раз sd_ini: ldi CMD,$41 ;CMD1 rcall Send_coinmand cpi temp,0 brne sd_ini ;rcall out_com ;можно послать наружу через UART для контроля ;конец инициализации /окончание процедуры ser temp ;$FF rcall WR_spi /пустой байт — пауза sbi PORTB,CS ;CS = 1 ret Поскольку эта программа демонстрационная, то средств предотвращения зависания тут не предусматривается, однако в рабочих проектах следует ограничить число циклов посылки команды sendopcond (например, значением несколько сотен) и при неудаче инициализации карты (она, например, может быть просто не встав- лена в устройство) принимать соответствующие меры. Запись и чтение ММС Карты ММС внутри устроены аналогично любым микросхемам flash-памяти — все пространство поделено на страницы, размер которых равен сектору на жестком диске (512 байтов), и которые в документации называются блоками (или сектора- ми). Как и на жестком диске, величину блока для операции чтения можно изменить (от 1 до 2048 байтов), но эта операция имеет ограничения для операций записи, и притом для карт разных производителей несколько разные, потому проще сми- риться с тем, что читать и писать придется одинаково массивами по 512 байтов (можно, правда, читать и писать сразу несколько таких массивов). Отличие ММС от рассмотренной ранее микросхемы AT45DB в том, что базовые операции чтения и записи производятся без явного участия буфера— непосредственно в память, поэтому для записи на карту в МК придется либо иметь буфер данных такого объ- ема, либо получать данные извне (а при чтении — посылать «наружу»). Емкости встроенной в контроллер SRAM не всегда хватает для организации буфера разме- ром 512 байтов (напомним, что даже если емкость SRAM равна 512 байтов, как в большинстве младших моделей Mega, то часть ее занята под стек). Потому млад- шие AVR — не очень удобное устройство для «общения» с картами памяти. Однако положение облегчается следующим нюансом. Задав команду чтения или записи блока, ее всегда приходится выполнять до конца, подав нужное число так-
342 Часть III. Практическое программирование микроконтроллеров AVR товых импульсов для обмена блоком данных соответствующей длины. Но, соглас- но основному принципу работы интерфейса SPI, нет необходимости читать (или писать) все байты без перерывов, — карта вполне может «повисеть», пока на линии CLK отсутствуют импульсы. Потому передачу данных можно прерывать на время обработки уже принятых байтов. Среди команд ММС есть операции чтения и записи сразу нескольких блоков под- ряд, но мы будем применять только чтение и запись одного блока: writeblock (код 24) и readsingleblock (код 17). Блок данных и при чтении, и при записи предваряется, как мы говорили, байтом $fe, перед которым (уже после подачи ко- манды) может идти несколько пустых байтов $ff. После нулевого бита данные должны начинаться сразу— например, если вы подадите байт со значением $fd или $fc, то весь 512-байтный массив окажется сдвинутым на один или два бита влево. Отметим, что при записи это соблюдается в случае команды для одного бло- ка данных, для команды writemultipleblock (код 25) этот байт должен быть равен $fc. По окончании блока данных карта выдаст отклик из двух байтов, содержащих CRC, который в нашем случае может быть проигнорирован. Перед подачей любой команды необходимо сбросить линию CS. При записи сле- дует учесть, что после передачи массива, если линия CS находится в активном (сброшенном) состоянии, на время ожидания нельзя прекращать выдачу импульсов по линии SCK до тех пор, пока операция, которая может продолжаться, как обыч- но — несколько миллисекунд, не будет закончена. Во время этой операции линия DO карты находится в нулевом состоянии, по завершении на этом выводе устанав- ливается высокий уровень. Можно следить просто за состоянием вывода (только следует пропустить байты CRC, которые также могут содержать нули), а можно — за принимаемыми байтами, значение которых должно смениться с $оо на $ff. После этого карта готова к дальнейшей работе— можно опять установить линию CS в единичное состояние. Далее мы поступим в соответствии с этим алгоритмом, но в общем случае после приема байтов CRC можно установить линию CS в единич- ное состояние, заняться своими делами и потом, сбросив CS опять (иначе DO ока- жется в третьем состоянии), проверить состояние линии DO. Ввиду объемности этих процедур, я их здесь не привожу. Демонстрационная про- грамма целиком имеется в архиве по адресу, указанному во введении (файл rwMMC- SPl.asm). По этой программе устройство должно выдать через UART сначала под- ряд числа от 256 до нуля (вторая половина записанного блока), затем числа от нуля до 256 (первая половина блока, которую мы при чтении сохранили на время в SRAM). Еще раз отметим, что отформатированная (обычно в файловой системе FAT 16) карта после такого издевательства будет, конечно, непригодна для обычных при- менений. Однако восстановить ее не составляет труда: можно либо просто отфор- матировать ее заново средствами Windows, либо применить одну из утилит форма- тирования, которые доступны на сайтах производителей.
ГЛАВА 13 Интерфейс TWI (I2C) и его применение Интерфейс TWI (иное наименование стандартизированной фирмой Philips интер- фейсной шины 12С) устроен так же, как UART, но в нем есть несколько различаю- щихся состояний: «старт», «стоп», передача от «мастера» или к «мастеру» и т. д. В результате протокол обмена все равно приходится строить, что называется, «руч- ками», подобно разным случаям использования SPI. Кроме того, наличие в TWI всего одной линии «туда и обратно» (да еще и с поддержкой многих устройств, подключенных к ней) вынуждает организовывать протокол так, чтобы исключить электрические конфликты, и процедура в целом оказывается весьма сложной, так что «аппаратность» тут отходит на второй план. Программная имитация TWI в большинстве простых случаев удобнее, чем штатные средства. Она, кроме всего прочего, не привязана к модели МК, позволяет приме- нять интерфейс в любых контроллерах, даже самых простых Tiny, и задействовать любые удобные выводы, а не только штатные SDA и SCL. К тому же они у разных, даже близких, моделях AVR привязаны к различным выводам портов, что при пе- реносе может добавить немало проблем, вплоть до необходимости переделывать всю схему,— например, штатные выводы TWI у ATmega8535 (РСО и РС1) будут мешать динамической индикации в конфигурации, показанной в главе 9. В реальности аппаратный способ для TWI все равно сложнее аналогичных про- цедур для других портов и имеет лишь одно теоретическое преимущество перед программным— прерывание TWI может «будить» контроллер при нахождении в любом из режимов энергосбережения. Но в наших схемах контроллер всегда вы- ступает в качестве «ведущего», и получается так, что будить его оказывается некому. Так что это преимущество все равно на практике оказывается невостребованным. Заметки на полях В качестве примера: входящая в комплект Arduino библиотека Wire задействует аппа- ратный порт TWI по всем правилам, включая использование прерываний. По идее это должно было бы разгружать контроллер для выполнения других задач. Но в действи- тельности этого не происходит— 16-мегагерцевый контроллер смиренно тормозит все операции, ожидая окончания обмена по TWI со скоростью 100 кГц. Это иллюстра- ция к тому утверждению, что программная имитация порта TWI в большинстве случа- ев ничуть не хуже официального способа.
344 Часть III. Практическое программирование микроконтроллеров AVR Аппаратный UART за полвека своего существования в отношении простоты использо- вания вылизан до предела — как вы уже видели (см. главу 11), даже безо всяких пре- рываний процедура отсылки байта состоит из трех команд, потому применять его про- граммную имитацию особого смысла не имеет. SPI достичь таких же высот мешает разве что некоторый разнобой в реализациях и отсутствие аппаратного порта в ряде случаев. А вот TWI слишком различается нюансами в различных случаях применения протокола, из-за чего и приходится все время разбираться в результатах его деятель- ности «вручную». Нет, в каких-то особо сложных случаях имеет смысл использовать именно аппаратный порт TWI — например, когда несколько «мастеров» и «ведомых» подключены к одной шине (см. [2]). Но у нас таких задач не встретится, и мы пойдем самым простым и удобным путем. Базовый протокол I2C Предположим, у нас есть несколько устройств, подключенных параллельно к двум линиям (не считая, естественно, «земли»). По одной из них (SCL) всегда передают- ся синхронизирующие импульсы, а по второй — собственно данные (SDA). Ин- формация в каждый момент времени передается только одним устройством в одну сторону. С помощью 12С можно (теоретически) соединить до 128 устройств— как показано на рис. 13.1. «Подтягивающие» резисторы R1 и R2 должны иметь номи- нал порядка единиц, максимум десятков килоом (чем больше скорость обмена, тем меньше). Точный расчет оптимальных номиналов этих резисторов достаточно сло- жен (он учитывает емкость выхода, емкость шины, установленную скорость обмена и т. п.), и в реальности номиналы обычно устанавливаются подбором «по месту» так, чтобы получить «приличную» форму сигнала. Для скорости обмена 100 кГц и менее при напряжении 5 В можно принять «среднепотолочное» значение около 4,7-5,1 кОм. При этом стандарт диктует максимальное значение тока через выход шины около 3 мА, откуда минимально допустимое значение сопротивлений R1 и R2 равно примерно 1,5 кОм при 5 вольтах питания. ~пит устройство 1 SDA SCL устройство 2 устройство 3 устройство й- о R2 4,7 КОМ Рис. 13.1. Соединение устройств по интерфейсу I2C (общий провод не показан)
Глава 13. Интерфейс TWI (12С) и его применение 345 Заметки на полях Это надо иметь в виду при подключении готовых модулей с 12С-интерфейсом — они обычно имеют встроенные «подтягивающие» резисторы в пределах 4,7-10 кОм, при- чем чаще встречается сопротивление, равное нижнему пределу 4,7 кОм. И подключе- ние более трех модулей к одной шине не рекомендуется. Обычно более трех и не требуется, но об этом надо помнить и стараться вместо готовых модулей (например, часов или каких-нибудь датчиков) устанавливать соответствующую «голую» микро- схему, что обычно и удобнее, и существенно дешевле. Обратите внимание, что все устройства в этом случае обязаны иметь выход с «от- крытым коллектором», а привязка к шине питания обеспечивается парой этих са- мых внешних резисторов. Как мы знаем, выходы портов AVR построены иначе — по симметричной КМОП-схеме с третьим состоянием. Чтобы обеспечить совмес- тимость с «открытым коллектором» и в программной имитации 12С, и в аппаратном TWI предусмотрен хитрый прием: состояние «разрыва» (т. е. выключенного тран- зистора на выходе) имитируется установкой выхода в третье состояние, т. е. факти- чески — в режим работы вывода порта на вход, а включенное состояние — уста- новкой вывода порта на выход и при этом обязательно в состояние логического нуля. Заметки на полях При такой постановке дела в качестве «подтягивающих» использовать встроенные резисторы портов неудобно — приходится каждый раз при переключении на вход сле- дить за их подключением через регистр данных portx. Кроме того, номинал встроен- ных «подтягивающих» резисторов чересчур велик и годится только для самых мед- ленных скоростей обмена. Потому в схемах с интерфейсом 12С принято всегда ставить внешние «подтягивающие» резисторы. Нужно только не забывать о том, что при пере- ходе в «спящий» режим выводы, подсоединенные к этим резисторам, нельзя остав- лять в состоянии выхода с логическим нулем. Вообще-то сам протокол 12С такого не позволяет (о чем далее), но лишний раз убедиться в этом никогда не вредно. Чтобы несколько соединенных по интерфейсу 12С устройств отличались друг от друга, каждое из них обязано иметь индивидуальный адрес. Он задается 7-битовым кодом (восьмой — младший — бит байта адреса служит для других целей, как мы увидим далее), потому всего таких устройств на одной линии может быть 127. Этот адрес обычно задает изготовитель (в некоторых случаях его можно изменить про- граммно или внешними соединениями). За право присвоения индивидуального ад- реса изготовитель, возможно, по сей день платит лицензионные отчисления фирме Philips — так или иначе, но найти два разных устройства с одинаковыми адресами мне не удалось. В самом AVR он, разумеется, задается программно, но для наших целей этого не потребуется, т. к. «мастер» (МК) у нас один, и «ведомые» устройст- ва не будут к нему обращаться. Типовой вариант обмена информацией по интерфейсу 12С показан на рис. 13.2. Кратко расшифруем эту диаграмму. Любой сеанс передачи по протоколу 12С начи- нается с состояния линии, именуемого START (когда сигнал на линии SDA меняет- ся с логической 1 на логический 0 при высоком уровне на линии SCL). START мо- жет выдаваться неоднократно — тогда он называется «повторный старт». Заканчи- вается сеанс сигналом STOP (состояние линии SDA меняется с логического 0 на
346 Часть III. Практическое программирование микроконтроллеров AVR логическую 1 при высоком уровне на линии SCL). Между этими сигналами линия считается занятой, и только «ведущий» (тот, который выдал сигнал START) может управлять ею1. Сама информация передается уровнями на линии SDA (в обычной положительной логике, старший разряд первым), причем смена состояний может происходить только при низком уровне на SCL, а при высоком уровне на ней про- исходит считывание значения бита. Любая смена уровней SDA при высоком уровне SCL будет воспринята либо как START, либо как STOP. SCL MSB ппсстгл /ттт:< > адрес ведомого R/W АСК - повтор для каждого байта - данных АСК АСК START STOP Рис. 13.2. Обмен информацией по интерфейсу I2C Процесс обмена всегда начинается с передачи ведущим байта, содержащего адрес устройства (также начиная со старшего разряда), который содержится в семи стар- ших битах. Последний (младший!) бит этого байта называется r/w и несет инфор- мацию о направлении обмена: если он равен 0, то далее ведущий будет передавать информацию, т. е. писать (w), если равен 1 — читать (r), т. е. ожидать данные от ведомого. Все посылки (и адресные, и содержащие данные) сопровождаются девя- тым битом, который передается последним и называется битом квитирования (аск). Во время действия этого девятого импульса адресуемое устройство (т. е. ве- домый, который имеет нужный адрес после посылки адреса ведущим, или веду- щий, если данные направлены к нему) обязано сформировать ответ (аск) низким уровнем на линии SDA. Если такого ответа нет (nack), to можно считать, что дан- ные не приняты, и фиксировать сбой на линии. Иногда устройства не требуют от- сылки бита аск (или игнорируют его), и это учтено в процедурах, которые рассмот- рены далее. Заметим, что сигналы SCL необязательно должны представлять собой равномер- ный меандр со скважностью 2 — период их следования в общем случае ничем не ограничен, кроме «терпения» приемника, который, естественно, ждет сигнала какое-то ограниченное время (иначе при нарушении протокола программа может зависнуть). Более подробно мы рассматривать протокол 12С не станем, т. к. вы лег- ко можете найти его описание в документации на любое устройство, которое этот 1 Хотя это и не совсем так, но здесь мы не будем углубляться в подробности читателя к [6,7]. за ними я отсылаю
Глава 13. Интерфейс TWI (12С) и его применение 347_ протокол поддерживает (в том числе и в описаниях AVR, изложенных по-русски в [6,7]). Как видим, организовать обмен по протоколу 12С непросто, но это плата за универ- сальность и простоту электрической схемы. Большинство современных устройств с интерфейсом 12С могут работать с тактовой частотой до 400 кГц и более, но из-за не слишком высокой помехоустойчивости такой линии, максимальные частоты це- лесообразны только, когда микросхемы установлены на одной плате недалеко друг от друга. При соединении проводами (например, МК с каким-нибудь датчиком) лучше ограничиться частотами до 100 кГц, а при длинных линиях связи (провода в полметра длиной и более) частоту обмена следует снизить до 10-30 кГц. Мы, вслед за разработчиками библиотеки Wire для Arduino, будем ориентироваться на величину 100 кГц, как наиболее универсальную. Программная эмуляция протокола 12С Мы рассмотрим только программную эмуляцию этого протокола, и свою задачу сформулируем так: есть контроллер, и есть некое (одно или более) внешнее устрой- ство. Нужно прочесть/записать данные. Контроллер тут всегда будет выступать как Master, а устройство — как Slave. Для того чтобы программно эмулировать прото- кол 12С, нам тогда придется сначала решить вопрос о том, как формировать такти- рующую последовательность на линии SCL. Это, конечно, можно сделать с помощью таймера, что на самом деле неудобно — и таймеры обычно заняты более полезными делами, и нет тут у нас каких-либо жестких требований ни к стабильности, ни к конфигурации сигнала. Потому мы воспользуемся способом формирования временной задержки на основе пустого цикла (см. главу 6) и задействуем для формирования импульса счетчик cnt (пусть это будет регистр из числа старших: от г1б до г31). Тогда простейший пустой цикл, повторенный nn раз, запишется так: ldi cnt,NN cyk_delay: dec cnt brne cyk_delay Подсчитаем, чему должно равняться число nn. Пусть мы хотим обеспечить такто- вую частоту 12С около 100 кГц, тогда длительность одного импульса (полпериода тактовой частоты) должна равняться примерно 5 мкс. Сам цикл занимает три такта (команда dec один такт + команда brne с переходом — два такта), т. е., например, при частоте кварцевого генератора 4 МГц цикл будет длиться 0,75 мкс. Итого, что- бы получить при этой частоте импульс в 5 мкс, нам нужно повторить цикл 6-7 раз. Выбираем меньшее число из этих двух, поскольку вызов процедуры и возврат из нее занимают сами по себе не менее 7 тактов. Точно подогнать частоту не удастся, но это и не требуется, — работоспособность 12С от частоты не зависит, главное — не превышать максимально допустимую скорость обмена для выбранного устрой- ства.
348 Часть III. Практическое программирование микроконтроллеров AVR Код процедуры задержки иллюстрирует листинг 13.1. Сохранение в стеке регистра cnt осуществляется с тем, чтобы не испортить его значение в задержке, т. к. вне ее он будет использоваться для отсчета обращений к TWI. delay: ;~5mks (кварц 4 MHz) push cnt ldi cnt,6 cyk_delay: dec cnt brne cyk_delay pop cnt ret Используя эту процедуру задержки, можно шаг за шагом сформировать весь прото- кол. Чтобы не загромождать текст этой главы, я вынес полный текст процедур об- мена по 12С в отдельный файл i2c.prg, который доступен в архиве по адресу, указан- ному во введении (этот файл представляет собой нечто вроде «библиотеки» в язы- ках высокого уровня, только не надо воспринимать этот термин в данном контексте всерьез). Подробно рассматривать его мы не станем, т. к. он полностью соответст- вует описанию протокола. В этом файле принято, что тактовая частота 4 МГц, а для приема-передачи используются младшие два вывода порта D (PD6 и PD7). При другой тактовой частоте и при желании изменить выводы или порт целиком, нужно внести в файл исправления (подробные указания по этому поводу— в начале файла). Файл можно включать в текст своей программы обычной директивой #include, но в определенном месте — сразу после таблицы прерываний, если она используется. Параметры передаются через регистры: в г1б (data)— данные, в yl— 12С-адрес устройства, в yh — адрес внутреннего регистра, из которого производится чтение или в который осуществляется запись. Внутри еще используется регистр ri7 (cnt), который лучше не занимать для других нужд в основной программе. Беда только в том, что в разных устройствах могут иметься различия, — приведен- ные в файле процедуры read_i2c и write_i2c годятся для устройств с однобайтовым адресом внутренних регистров (часов, датчиков). А, например, для доступа к EEPROM-памяти с 12С-интерфейсом типа АТ24 эти процедуры придется править в расчете на двухбайтовый адрес. В таких отдельных случаях можно употреблять имеющиеся там же общие для всех случаев элементарные процедуры посылки и приема байта (бесхитростно названные write и read), а что именно посылается и куда — указывать в своей программе конкретно. Мы сейчас приведем несколько примеров, чтобы было понятнее, что к чему. И начнем с классической задачи чте- ния и записи времени из часов DS1307. Для примеров мы воспользуемся привыч- ным ATmega8, но код можно перенести на ATmega8535 или на ATmegal6, имею- щие большее число выводов, без каких-либо изменений. Для ATtiny2313 код при- годен ограниченно из-за необходимости изменения выводов порта и иногда некоторых других ограничений.
Глава 13. Интерфейс TWI (12С) и его применение 349 Заметки на полях В рассматриваемых далее программах процедуры, приведенные в файле i2c.prg (и ад- ресное ЧТение/запИСЬ read_i2c/write_i2c, И ПРОСТО ЧТвНИв/ОТСЫЛКа байта read/write), сопровождаются командой ьгсс для проверки состояния бита с в регистре флагов sreg с отсылкой на процедуру индикации ошибки, если этот флаг в результате опера- ции оказался установленным неправильно. Отметим, что такая проверка для выпол- нения протокола 12С необязательна, а в случаях, когда сообщение об ошибке отсы- лать некуда, и вовсе оказывается лишней. В большинстве рассматриваемых далее программ эта проверка сохранена по формальным причинам, но может быть удалена без ущерба для функциональности процедур, подобно тому, как это сделано в случае инициализации дисплея МТ-10Т11 (о чем рассказано далее). Часы с интерфейсом I2C Моделей часов реального времени (Real Time Clock, RTC) существует множество. Все пользователи ПК с ними знакомы заочно — именно микросхема RTC питается от резервной батарейки, находящейся на любой материнской плате. Такие часы, кроме собственно функций счета времени и календаря, имеют небольшую встроен- ную SRAM, в которой записаны установки BIOS. Хранить их именно в энергозави- симой памяти удобно, т. к. часы реального времени все равно требуются, а в случае чего установки легко сбросить в исходное состояние, просто лишив микросхему питания (или замкнув специальные контакты). Встроенную SRAM имеют не все такие микросхемы, но в остальном RTC внутри устроены примерно одинаково: ведут счет времени и календарь, имеют функции будильника и/или таймера, обязательную возможность автономной работы от бата- рейки в течение длительного времени. Такие часы обычно снабжают кварцем на 32 768 Гц, иногда даже встроенным в микросхему. Кроме этого, значительная часть моделей имеет дополнительный выход (иногда и не один), на котором формируется некая частота, задаваемая программно. Этот выход можно использовать для управ- ления прерыванием микроконтроллера, и таким образом организовать счет времени и его индикацию. Еще одна особенность микросхем RTC — единицы времени в них традиционно представлены в десятичном виде (в упакованном BCD-формате). Именно так вы- дают значения времени RTC, встроенные в ПК. Например, число минут, равное 59, так и выдается— как байт со значением 59, но, как уже мы говорили (см. гла- ву 8)— это не 0x59, что в десятичной системе будет равно 89, а именно 59 в при- вычной нам десятичной записи. BCD-представление удобно для непосредственной индикации, но при арифметических операциях (или, например, при сравнении) его приходится преобразовывать к обычному двоичному виду. На самом деле это поч- ти не доставляет неудобств, скорее наоборот. Для наших целей выберем простейшую модель RTC под названием DS1307. Я с 1990-х годов пользовался этой микросхемой и, познакомившись с Arduino, с удивлением узнал, что там она тоже принята за базовую, — мне казалось, что можно было бы подыскать посовременней. Однако функциональности DS1307 вполне хватает, а придумать что-либо новое трудно, потому ее используют до сих
350 Часть III. Практическое программирование микроконтроллеров AVR пор. Это простейшие часы с 12С-интерфейсом, в 8-выводном корпусе с внешним резонатором на 32 768 Гц, 5-вольтовым питанием и возможностью подключения резервной батарейки на 3 В (т.е. обычной литиевой «монетки» 2032 или 2016). Схема переключения питания на батарейку— встроенная и не требует внешних элементов. Часы допускают максимальную тактовую частоту интерфейса 12С 100 кГц. Отметим, что DS1307 практически без изменений в программе (кроме 12С- адреса) можно заменить на модель DS1338, допускающую напряжения питания 3,3, 3,0 или 1,8 вольта (в зависимости от разновидности), а также повышенную ско- рость передачи по 12С. Подробности Часы DS1307 имеют лишь один недостаток — точность их хода полностью зависит от используемого кварца, и обычно минимум каждые несколько месяцев их показания приходится корректировать. Популярные модели DS3231/DS3232 с интегрированным кварцем и повышенной стабильностью совместимы с DS1307 по базовым функциям, и хотя по управлению они немного отличаются, приведенные далее программы должны работать и для них без существенных изменений. Часы DS3231/DS3232 также рабо- тают с напряжением питания 1,8-3,3 В. Для 12С-устройств с разным напряжением пи- тания имеется специальная микросхема-согласователь уровней РСА9306, которую часто ставят в готовые модули часов на их основе, а «подтягивающие» резисторы включаются с каждой стороны такой микросхемы. Отметим, что включение простого резистивного делителя для сопряжения уровней оказывается невозможным в этой си- туации, когда одно устройство (с 3-вольтовым питанием) имеет выход с открытым коллектором, а второе (5-вольтовый контроллер) — выход с тремя состояниями. Тут нужен, как минимум, развязывающий транзистор по схеме, приведенной на рис. 4.6 в главе 4. В DS1307 имеется вывод для прерывания МК, который может программироваться с различным коэффициентом деления частоты кварца. Мы запрограммируем его на выдачу импульсов с периодом 1 с, чтобы по внешнему прерыванию от этих им- пульсов можно было считать секунды, обновлять значение времени и выполнять другие полезные действия — т. е. использовать часы вместо таймера. В отличие от счета времени самим контроллером (см. главу 9), здесь мы можем быть уверены, что при любых сбоях в МК время у нас будет отсчитываться верно, а также получа- ем возможность проведения длинных процедур (вроде чтения из внешней памяти) без боязни сбить отсчет времени. Но в этом случае, если вы вместо «голой» микро- схемы захотите использовать готовый модуль часов реального времени, каких пол- но в магазинах, торгующих аксессуарами для Arduino, то он должен иметь допол- нительный вывод этой частоты, обычно обозначаемый как SQW (в некоторых RTC- модулях он выведен отдельно от основного разъема). В остальном для нашего слу- чая безразлично, в каком варианте вы подключаете часы: как отдельную микросхе- му с кварцем и батарейкой или как единый модуль, все это объединяющий в одно целое. Схема подключения DS1307 к ATmega8 приведена на рис. 13.3. На схеме не пока- зан основной кварцевый резонатор контроллера, т. к. он в этом случае не очень-то и нужен. Напоминаю, что процедуры в файле 12с.ргд рассчитаны на тактовую частоту 4 МГц, но для нашей задачи необязательно получать именно точное значение от кварца: для отсчета времени у нас есть часы, а внутренний генератор большинства
Глава 13. Интерфейс TWI (12С) и его применение 351 +5 в Xci R1 5,1 к -у 0>1 к адаптеру UART(RxD) +5 В C2 Q1 JC- 327681I £ зв- 1 Reset 2RXD 3TXD 4 INTO (PD2) 5INT1 (PD3) 6 PD4 (TO) 7Vcc 8GND 9 XTAL1 10XTAL2 11PD5(T1) 12PD6 13PD7 14PBO (ADC5) РС5 28 (ADC4) РС4 27 (ADC3) РСЗ 26 (ADC2) РС2 25 (ADC1)PC124 (ADC0) РСО 23 GND22 AREF 21 AVCC 20 SCK19 MISO 18 MOS117 (ОС1В)РВ216 (ОС1А)РВ1 15 Рис. 13.3. Подключение часов DS1307 к ATmega8 моделей AVR может частоту 4 МГц обеспечить при соответствующей настройке бита cksel (см. [6,7] или документацию на контроллеры). Серым цветом обведено содержимое готового модуля часов и показаны его внеш- ние выводы, если вы подключаете часы именно таким способом. Выход програм- мируемой частоты SQW (который мы должны будем запрограммировать на выдачу сигнала с периодом 1 с) у нас подсоединен к выводу внешнего прерывания INTO и «подтянут» к питанию резистором R2. Выводы SDA и SCL «подтянуты» резисто- рами R3 и R4. Если используется готовый модуль, то все три «подтягивающих» резистора уже установлены на плате этого модуля. К линиям, показанным на схеме стрелочками, можно подключать другие устройства с 12С-интерфейсом, при этом дополнительные «подтягивающие» резисторы уже не требуются. Подробности Следует помнить, что если среди всех подключенных устройств к шине 12С имеется хоть один готовый модуль, в нем должны уже присутствовать «подтягивающие» рези- сторы, и тогда резисторы R3-R4 устанавливать не следует (исключение представляет дисплей МТ-10Т11, рассматриваемый далее в этой главе). Проверить это просто: по- меряйте мультиметром сопротивление между каждым из выводов SDA и SCL и выво- дом питания Vcc модуля — оно должно лежать в пределах 4-10 кОм. Если сопротив- ление существенно отклоняется от этой величины в большую сторону, то отдельные резисторы R3-R4 необходимы. Не следует также забывать, что при отключении питания схемы часы не отключаются, а вывод SQW представляет собой вывод с открытым коллектором, — т. е. при пере- ключении на резервную батарейку он продолжит работу. При извлечении микросхемы
352 Часть III. Практическое программирование микроконтроллеров AVR или модуля из схемы или полном отключении внешнего питания ничего не должно случиться, даже если вы не станете отключать батарейку, т. к. потреблять ток выводу SQW оказывается неоткуда. А вот при переключении в энергосберегающий режим вы- ход SQW продолжит работу и будет потреблять от внешнего источника через R2 в моменты нулевого состояния. При обычном номинале резистора R2 -5 кОм это не такой уж маленький ток — в среднем порядка 0,5 мА, что сильно превышает допусти- мые значения для режима энергосбережения. Потому в режимах энергосбережения нужно принимать дополнительные меры (см. главу 14). Основное неудобство обращения с часами DS1307 — то, что у них нет состояния «по умолчанию», и внутренние регистры при первом подключении резервной бата- рейки могут иметь произвольные значения. В частности, в этих часах в одном из регистров (том же, что хранит значения секунд) предусмотрен бит сн, который может погружать часы в «спячку», — если он установлен в 1, то не работает гене- ратор, и даже невозможно определить правильность подключения. Поэтому после первого включения (если батарейка подсоединена — то только после первого) часы приходится инициализировать. Логика разработчиков проста— зачем кому-то нужны часы, которые не установлены на правильное время? Ну, а если корректиро- вать показания, то нетрудно установить и эти биты. Рассмотрим сначала простейший случай— проверку состояния часов. Если они были когда-то запущены, и батарейка не отключалась, то скорее всего бит сн сбро- шен, часы показывают какое-то время, но необязательно при этом запущен вывод внешнего прерывания. Вот определить все это мы и попытаемся с помощью сле- дующей программы (листинг 13.2). ; тактовая частота 4 МГц ;I2C PortD, выводы 13 (SDA, PD7) и 12 (SCL, PD6), см. i2c.prg .device ATmega8 .include "m8def.inc" .def temp = rl6 /рабочий регистр ;регистр DATA=teinp определен в i2c.prg /регистр г17 занят в i2c.prg rjmp RESET ; Reset Handler rjmp EXT_INT0 ; IRQO Handler .org INT_VECTORS_SIZE ; начало кода .include "i2c.prg" /процедуры i2c out_com: /посылка байта из temp с ожид. готовности sbis UCSRA,UDRE ;ждем готовности буфера передатчика rjmp out_com out UDR, temp ret
Глава 13. Интерфейс TWI (12С) и его применение 353 EXT_INTO: /каждую секунду rcall ReadSek; чтение секунд reti ReadSek: ;чтение значений секунд sbis PinD,pSDA ;линия занята rjmp stopW ldi YL, 0Ы1010000 ; адрес device DS1307 ldi YH,0 ;адрес регистра секунд = О rcall read_i2c ;секунды - в temp rcall out_com ;отсылаем brcs stopW ;если флаг переноса С =1 - ошибка ret stopW: ldi temp,$EE ;отсылаем $ЕЕ - ошибка rcall out_com ret RESET: ldi temp,low(RAMEND) out S PL, temp ldi temp,high(RAMEND) /указатель стека out SPH, temp ;=== UART ldi temp,25 ;9600 out UBRRL,temp ;скор. передачи ldi temp, (1«RXEN| 1«TXEN) out UCSRB,temp ;разреш. приема/передачи 8 битов ;=== INTO ldi temp, (l«ISC01) ;npep. INTO по спаду out MCUCR,temp ldi temp,l«INT0;pa3p. INTO out GICR,temp sei /разрешили прерывания rcall ReadSek; чтение секунд - один раз Gcykle: rjmp Gcykle Текст этой программы можно найти в архиве по адресу, указанному во введении (файл rtc1307Jest_sek.asm). При компиляции вы получите предупреждение (warning) о том, что регистр г1б у нас определяется дважды: как temp в начале текста про- граммы и затем как data в файле i2c.prg. Все правильно — это сделано специально для улучшения читаемости программы. Такое двойное определение позволит нам не править текст отлаженных процедур или не привлекать еще один регистр для временного использования в основной программе с лишней операцией обмена дан- ными при отправке через порт. От такого предупреждения можно избавиться с по- мощью директивы undef (позволяющей отменить ранее сделанное определение), но в нашем случае это только запутает программу.
354 Часть III. Практическое программирование микроконтроллеров AVR По этой программе контроллер один раз в начале запросит величину, хранящуюся в регистре, секунд часов (процедура ReadSek). После загрузки надо выключить пи- тание схемы, запустить монитор порта, настроить его на нужный порт и скорость передачи, прием чисел в hex-формате (см. главу 15) и включить питание снова. Если часы идут, то в монитор порта сразу после включения вернется какое-то зна- чение секунд. Если дополнительно запущено прерывание на выводе SQW с нужной скоростью, то в монитор порта будет поступать каждое новое значение секунд. Причем секунды в hex-формате будут нормально читаться, как обычное число от 00 до 59, а при попытке переключить на прием в десятичной форме — как не имею- щее смысла расшифрованное BCD-значение, т. е. после 9 ($09) будет идти 16 ($ю) после 25 ($19) — 32 ($20) и т. д. Если часы не запущены вовсе (бит сн оказался установленным в единицу), то про- грамма сразу после запуска выдаст единственное значение $ее («ошибка»). В этом случае диагноз однозначный: батарейка отключалась, и часы необходимо заново инициализировать и установить в них время. Если не запущен выход SQW, то они выдадут единственное значение из регистра секунд сразу после запуска, и в этом случае может быть достаточно часы инициализировать, не устанавливая время «по полной программе». Но, как уже говорились, при полном сбросе биты регистров часов могут принимать произвольные значения, потому не исключено, что часы будут идти и даже выдавать правильные прерывания раз в секунду, но вместо вре- мени показывать ерунду. По значению выдаваемых секунд это можно легко уста- новить — неустановленные часы могут выдавать даже величины секунд более 59. Мы сейчас сосредоточимся на том, как часы инициализировать и прочесть из иду- щих часов значения времени. А установкой значений текущего времени займемся в главе 15 — эта тема больше относится к тому, как правильно посылать и прини- мать числа через UART, а не к собственно порту TWI. Заметки на полях Конечно, никто не мешает организовать классическую процедуру установки часов по нажатию кнопок. Этот вариант был бы обязательно необходим, если бы часы не име- ли резервной батарейки и их приходилось бы устанавливать при каждом сбое пита- ния, — не будешь же каждый раз тащить часы к ноутбуку, включать его, запускать нужную программу и т. д. Но для часов с резервной батарейкой, которые в самом худшем случае приходится раз в полгода немного подкорректировать, в нудной про- цедуре установки кнопками никакой нужды нет— проще все-таки это делать через компьютер. Сначала о том, как часы просто «завести» — запустить в работу в нужном нам ре- жиме. С помощью такой программы можно проверить правильность соединений, невзирая на начальные установки, — даже если часы не показывают верное время, они будут исправно отсчитывать секунды, начиная с нуля, и мы это увидим. Для инициализации нужно дополнить программу процедурой inicik (листинг 13.3). IniClk: /завести кварц, установить выход и обнулить секунды sbis PinD,pSDA ;если линия занята - на error rjmp stopW
Глава 13. Интерфейс TWI (12С) и его применение 355 ;обнуление секунд, ldi YL, 0Ы1010000 ; адрес device DS1307 clr YH /адрес регистра секунд = О ldi DATA, 0 ;обнуляем секунды rcall write_i2c brcs stopW ;если флаг переноса С = 1 - ошибка ldi temp,$AA ;все отлично rcall out_com ldi YL, 0Ы1010000 ;адрес device DS1307 ldi YH,0x07 ;адрес регистра управления = 07 ldi DATA, ОЬОООЮООО ;бит SQWE = 1 rcall write_i2c brcs stopW ldi tenp,$AA ;все отлично - отсылаем $АА rcall out_com ret Вызов процедуры inicik размещается в конце установок, непосредственно перед командой sei. В ней, во-первых, обнуляется регистр секунд, причем целиком, включая и бит сн, если он вдруг оказался установленным, т. е. часы заведомо долж- ны заработать. Во-вторых, устанавливается бит sqwe в регистре управления для включения вывода SQW (если два младших бита регистра управления равны нулю, то период переключения будет равен 1 секунде). Выполнение обеих операций кон- тролируется: если все прошло удачно, то в UART выдается значение $аа, иначе вы- зывается общая для обеих процедур отсылка кода ошибки по метке stopw (см. лис- тинг 13.2). После этого часы должны, как и ранее, равномерно выдавать в UART секунды, начиная с нулевого значения сразу после запуска. Программу можно найти в архиве по адресу, указанному во введении (файл rtc1307jni.asm). Обратите внимание, что значения секунд в ней также выводятся без преобразований, т. е. в BCD-форме, и в приемной программе их надо отображать как hex-числа. Для чтения показаний часов нужно вместо инициализации добавить в программу достаточно длинную процедуру, которую мы назовем Readcik. По ней мы читаем последовательно секунды, минуты, часы, день недели (число от 1 до 7 — см. врезку «Подробности» далее), дату, месяц, год (в виде числа, равного последним двум цифрам года), преобразуем полученные значения из BCD в обычные числа и от- правляем через UART (листинг 13.4). ReadClk: /чтение часов ldi YL, ОЫ1010000 ; адрес device DS1307 ldi YH,0 ;адрес регистра секунд sbis PinD,pSDA
356 Часть III. Практическое программирование микроконтроллеров AVR rjmp stopW rcall start mov DATA,YL ;адрес device DS1307, r/w=0 rcall write brcs stopW ;C=1 ERROR mov DATA,YH ;адрес регистра секунд rcall write brcs stopW ;C=1 ERROR rcall start sbr YL,1 ;r/w=l mov DATA,YL ;адрес device DS1307, r/w=l rcall write brcs stopW ;C=1 ERROR set ;put ACK rcall read ;sek brcs stopW ;C=1 ERROR rcall bcd2bin8 ;секунды - в число, см. главу 8 rcall out_com /отправляем секунды < далее аналогично минуты, часы, день недели, дату, месяц > clt ;no put ACK rcall read ;god brcs stopW ;C=1 ERROR rcall bcd2bin8 ;год - в число rcall out_com /отправляем год rcall stop ret stopW: ldi temp,$EE /отсылаем $ЕЕ - ошибка rcall out_com ret Еще раз отметим, что многочисленные проверки того, что посылка сработала (строки, помеченные комментарием ;C=i error) с отсылкой по UART кода ошибки, для работы процедур не требуются и после отладки могут быть удалены или закомментированы. Полностью программу (файл rtc1307_read.asm) можно найти в архиве по адресу, указанному во введении. Также еще раз подчеркнем — в отли- чие от предыдущих программ, в этой программе все значения времени выводятся в нормальной, десятичной форме. Процедура основана на том факте, что однажды запущенным часам в режиме чтения с указанием номера начального регистра (ре- гистр о — секунды) в дальнейших обращениях номер регистра можно не указывать. Часы будут выдавать последовательно значения остальных регистров, пока вы не закончите процесс. Для этого надо в последнем чтении выдать бит «неподтвержде- ния» («поАСК») и затем закончить сессию выдачей состояния stop. Ясности у вас прибавится, если вы внимательно изучите таблицу регистров DS1307 (см. табл. 13.1, а также описание регистров в русском переводе «даташита» [20]).
Глава 13. Интерфейс TWI (12С) и его применение 357 Таблица 13.1. Регистры часов DS1307 № 00 01 02 03 04 05 06 07 Бит 7 СН 0 0 0 0 0 Бит 6 Бит 5 Бит 4 десятки секунд десятки минут 12(1) 24(0) 0 0 АМ/РМ старшие десятки 0 млад- шие десятки 0 десятки даты десятки месяца десятки лет OUT 0 0 SQWE БитЗ Бит 2 Бит1 БитО единицы секунд единицы минут единицы часов 0 день недели единицы даты единицы месяца единицы лет 0 0 RS1 RS0 Диапазон 00-59 00-59 01 12 00-23 1-7 01-31 01-12 00-99 Подробности Обратите внимание на бит в в регистре значений часов — он переключает часы с при- нятого, например, в США и Канаде 12-часового режима на обычный 24-часовой. При 12-часовом режиме бит 5 будет показывать текущую половину суток (AM или РМ). В показаниях цифровых часов в 12-часовом режиме можно с непривычки запутаться (о неоднозначностях формата см. статью «12-часовой формат времени» в Википе- дии), но по счастью специально следить за этим не надо: нулевое значение бита б оз- начает 24-часовой режим, и при установке значения часов (см. главу 15) мы автома- тически сбрасываем и этот бит. Более серьезная проблема — раздражающая неопределенность с нумерацией дней недели, она характерна для всех микросхем RTC. Неопределенность связана с тем, что во многих культурах первым днем недели до сих пор считается воскресенье (пер- вый день творения, согласно Ветхому завету), а в светских календарях, включая оте- чественный,— понедельник (стандарт ISO 8601 от 1988 года, которому, между про- чим, не подчиняются США, Израиль и Канада, но подчиняется Европа). Поэтому соз- датели DS1307 и других моделей RTC разумно не стали привязывать номер дня недели к календарю, а предоставили этот выбор пользователям. Иными словами, ка- кой номер вы присвоите текущему дню недели при установке часов, такой и будет принят за основу автоматического отсчета в дальнейшем. Заметим, что при выводе на цифровые дисплеи день недели можно игнорировать — в быту численная величина никому ничего не скажет, и выводить день недели имеет смысл только, если есть воз- можность отобразить его в текстовой форме. При этом еще больше, как говорят интернетчики, «лулзов» добавляет факт, что счет дней недели можно вести по-человечески (от 1 до 7), а можно «по-компьютерному» (от 0 до 6 — так принято во многих «больших» языках программирования). В резуль- тате в программах для Arduino, например, воскресенье может иметь номер 0,1,6 или 7— в зависимости от предпочтений автора выбранной библиотеки. Создатели DS1307 были в этом отношении гуманнее: они указали в документации, что счетчик дней недели ведет счет от 1 до 7, оставив на выбор пользователя только начало от- счета. Но на всякий случай этот момент следует уточнять в документации на выбран- ную вами модель RTC. Заметим, что длительность процедуры чтения по 12С со скоростью 100 кГц, не- смотря на ее кажущуюся «навороченность», не так уж и велика: для каждого байта
358 Часть III. Практическое программирование микроконтроллеров AVR это примерно по 100 мкс плюс необходимые операции в начале, итого где-то 1 миллисекунда на 7 байтов данных. Само выполнение команд добавляет к дли- тельности совсем немного. В рассматриваемом случае время дополнительно за- тягивается необходимостью посылки через UART каждого следующего байта, который может быть послан не ранее окончания передачи предыдущего. При ско- рости 9600 это добавляет примерно по миллисекунде на каждое значение, кроме последнего, т. е. общая длительность составит примерно 7 мс. Но UART — не показатель, тем более что его легко ускорить в несколько раз (см. главу 75), а в ос- тальных случаях такая длительность чтения вполне приемлема — она даже не со- бьет динамическую индикацию так, чтобы было заметно для глаза. То есть у нас все готово для построения вполне достойных настольных часов. В главе 75, как мы уже говорили, будет показано, как устанавливать часы через по- следовательный порт. Из вариантов индикации у нас пока только обычная динами- ческая (см. главу 9), которая при всех ее неудобствах позволяет зато использовать семисегментные цифры любого желаемого размера. В главе 16 мы покажем, как для той же цели использовать готовый 4-разрядный семисегментный светодиодный ин- дикатор, а также рассмотрим вывод значений часов и других величин на строчные (знакосинтезирующие) индикаторы. Использовать для этой цели описываемый да- лее дисплей МТ-10Т11 можно только в каких-то сугубо утилитарных целях, т. к. в нем невозможно вывести разделительное двоеточие. Особенности записи и чтения внешней памяти с 12С-интерфейсом Энергонезависимая память типа АТ24Сххс с 12С-интерфейсом имеет структуру на- стоящей EEPROM т. е. с индивидуальной адресацией каждого байта. Потому про- цедуры обращения с этой памятью существенно отличаются от обычной flash- памяти, которую мы рассматривали в предыдущей главе. Последнее число в обо- значении микросхемы означает объем памяти в килобитах — например, АТ24С256 имеет емкость 256 кбит или 32 768 байтовых ячеек (32 кбайт). Объем памяти в 32 кбайт кажется смешным в сравнении с современными разновидностями flash- памяти, которые достигают гигабайтных объемов, но для наших целей, как вы уви- дите, этого будет достаточно. Максимальная емкость памяти серии АТ24 составля- ет 1 Мбит (128 кбайт), но удобно применять кристаллы от 2 до 512 кбит включи- тельно, т. к. они имеют двухбайтовую адресацию и не требуют лишней траты вре- мени на посылку еще одной составляющей адреса. Память принципиально больших объемов с интерфейсом 12С не выпускают — слишком он медленный. Заметим, что современные типы АТ24 (с буквой «С» и выше) допускают скорость обмена по 12С до 1 МГц при питании 2,7+5 В и 400 кГц при питании 1,7 В. В режиме записи это не имеет особого значения: как и положе- но энергонезависимой памяти, процедура записи медленная, и сигнал аск после посылки очередного байта следует ожидать не ранее, чем через 5 мс, т. е. общая скорость записи ограничена величиной примерно 200 байт/с, и быстродействие ин- терфейса перестает играть тут решающую роль. Иное дело при чтении — в режиме
Глава 13. Интерфейс TWI (12С) и его применение 359 с автоинкрементом адреса можно выжать все, что позволяет интерфейс I С. При «нашей» частоте 100 кГц это будет 10-11 кбайт в секунду. Но вся прелесть EEPROM именно в том, что она позволяет адресовать индивидуальные байты, и процедура чтения займет не менее четырех обращений к памяти: «мастер» посы- лает 3 байта (адрес устройства и два байта адреса памяти), и «ведомый» отвечает байтом данных. Получается что-то около 2,5 кбайт/с, т. е., чтобы прочесть все 32 кбайта микросхемы АТ24С256, потребуется примерно 12 секунд. Однако в рас- чете на один байт это все равно быстрее, чем аналогичная процедура для flash- памяти, из которой приходится извлекать целый блок в 256 байтов, менять в нем одну ячейку и записывать обратно. Отсюда и главный способ использования внеш- ней EEPROM с 12С-интерфейсом — с ней удобно работать, когда необходима по- байтные запись и чтение данных. Базовый 12С-адрес АТ24 равен $ао (оыоюоооо). При необходимости можно увели- чить емкость памяти, поставив несколько микросхем параллельно. Современные типы (с буквой «В», «С» или «D» в конце наименования) имеют по три вывода А0- А2, комбинацией логических единиц на которых можно задавать последние три бита адреса,— 12С-адрес устройства тогда будет равен ioioa2aiao. Так можно со- единять до восьми микросхем параллельно. Старые типы имели две таких линии — соответственно, можно было подсоединять до четырех микросхем. Отметим, что специальное подсоединение выводов А0-А2 к логическому нулю не требуется, и если применяется всего одна микросхема, то эти выводы можно оставить «вися- щими в воздухе», — логический ноль формируется внутренней схемой. То же самое относится к выводу запрещения записи wr (который здесь имеет положитель- ную логику— при нуле запись разрешена). Поэтому показанная на рис. 13.4 базовая схема подключения очень простая: достаточно выводы 5 и 6 подключить к линиям, обозначенным на рис. 13.3 как SDA и SDL. +5 j В 8 SCL SDA АТ24С256 X4 6 5 [ и 5>1 R2 к Рис. 13.4. Подключение памяти типа АТ24 Заметки на полях Давайте прикинем, на сколько нам может хватить одной микросхемы памяти АТ24С256. Пусть базовый кадр данных состоит из четырех байтов значений давления и темпера- туры, полученных с датчиков по одному из способов, описанных ранее. Тогда в наши 32 килобайта мы сможем вместить 8192 измерения. Разумеется, писать их все подряд не нужно: у нас измерения производятся каждые несколько секунд, но за это время в погоде вряд ли что изменится. Стандартный синоптический интервал, на который мы
360 Часть III. Практическое программирование микроконтроллеров AVR ориентируемся, подразумевает трехчасовой цикл (8 измерений в сутки), тогда памяти нам хватит на 1024 суток, или почти на 3 года записей! Как видите, даже объем памя- ти в 32 кбайта в такой ситуации вполне приемлем. Мы можем поставить несколько микросхем параллельно и, не снижая максимального времени записи, дополнить кадр данных другими значениями, в том числе временем, когда производилась запись. Для того чтобы обращаться к такой памяти, стандартные процедуры записи и чте- ния TWI: write_i2c и read_i2c (см. файл l2C.prg) надо дополнить еще одной (в слу- чае памяти до 256 килобит) или двумя (для памяти большей емкости) операциями посылки адреса памяти. Я приведу здесь без особых комментариев только подле- жащие изменению фрагменты процедур (листинги 13.5 и 13.6). В обычных про- цедурах у нас в yl хранился адрес устройства, а в yh — адрес регистра. Здесь адрес устройства задан, а в пару yh:yl загружается двухбайтовый адрес памяти емкостью до 256 килобит включительно. ;универсальная write_i2c - было (см. файл I2C.prg) 1оор120: push DATA rcall cbr YL start ,1 mov DATA,YL ;addr device,r/w=0 rcall brcs mov rcall brcs pop rcall write rt_write ;O1 ERROR DATA,YH ;set register address write rt write DATA write ;C=1 ERROR ;set data to DATA /запись в АТ24 write_12c_EEPROM - стало: Ioopl20ew: push DATA rcall start ldi DATA,OxAO ;addr device=0,r/w=0 rcall write brcs rt_write_ew ;C=1 ERROR mov DATA,YH ;set HI address rcall write brcs rt_write_ew ;C=1 ERROR mov DATA,YL ;set LO address rcall write brcs rt_write_ew ;C=1 ERROR pop DATA ;set data to DATA rcall write
Глава 13. Интерфейс TWI (12С) и его применение 361 /универсальная read_i2c - было (см. файл I2C.prg) loop_read : rcall cbr YL, mov rcall brcs mov rcall brcs rcall sbr YL, start 1 DATA, YL write rt DATA,YH ; write rt start 1 ;addr device,r/w=0 ;C=1 ERROR tset register address ;C=1 ERROR mov DATA,YL ;addr device,r/w=l rcall write ;чтение из АТ24 read_12c_EEPROM - стало: loop_read_er: rcall start ldi DATA,OxAO ;addr device=0,r/w=O rcall write brcs rt er ;C=1 ERROR mov DATA,YH ;set HI address rcall write brcs rt er ;C=1 ERROR mov DATA,YL ;set LO address rcall write brcs rt er ;C=1 ERROR rcall start ldi DATA,OxAl ;addr device=0,r/w=l rcall write Измененные процедуры можно разместить в отдельном включаемом файле или добавить к файлу 12С.ргд, но в любом случае названия меток и самих процедур не должны совпадать с уже имеющимися. Поэтому эти названия в текстах изменены. Дисплей МТ-10Т11 ЖК-дисплей МТ-10Т11 российской фирмы МЭЛТ имеет 10 семисегментных сим- волов с десятичными точками, расположенных в одну строку. Он выгодно отлича- ется от обычных многострочных ЖК-дисплеев, с которыми мы познакомимся в главе 16, достаточно крупными (8 мм в высоту) и контрастными символами, хо-
362 Часть III. Практическое программирование микроконтроллеров AVR рошо различимыми и без подсветки. Есть модификация МТ-10Т12 с цифрами 13 мм в высоту, но только в вариантах с 3-вольтовым питанием (судя по отзывам на сайте МЭЛТ, от 5 вольт такие дисплеи работают даже лучше). Дисплеи имеют ин- терфейс 12С и спроектированы на одном из вариантов специально предназначенных для подобных целей драйверов семисегментных индикаторов— микросхеме PCF8576 (и даже не совсем на ней, а на ее белорусском аналоге). У этих дисплеев есть аналоги МТ-10Т7/МТ-10Т9 с непосредственным доступом к каждому сегмен- ту, но они управляются иначе (по 4-проводному параллельному интерфейсу), и здесь мы их рассматривать не будем. PCF8576 — не полноценный контроллер, а по сути сдвиговый регистр, снабженный дополнительными функциями. Таким выбором обуславливаются и недостатки, и достоинства дисплея МТ-10Т11. Главный недостаток— в усложненном управле- нии: на дисплей можно выводить только сразу целую строку из 10 знаков (по от- дельности позиции знаков недоступны). Но главное достоинство все перекрывает: дисплей потребляет около 30 мкА, потому отлично подходит для энергосберегаю- щих приборов на батарейном питании. Еще один аналог (MT-10S1) на основе на этот раз стандартного контроллера рассмотрен в главе 16, но он характеризуется примерно в 20 раз большим потреблением, чем упомянутые типы. Заметки на полях Продукция фирмы МЭЛТ ничуть не хуже импортной, а в некоторых областях даже су- щественно лучше. Плохо только то, что действует фирма по извечному российскому принципу «можете же, когда захотите». Например, устанавливать кодировку сегментов для МТ-10Т11 пришлось экспериментально, потому что подробного описания этих дисплеев просто нет в природе. Есть многочисленные жалобы на стабильность и на- дежность работы дисплеев МЭЛТ, а в результате выясняется, что кто-то что-то не так подключал, и при этом нигде не объясняется, как правильно. Претензий к продукции МЭЛТ было бы гораздо меньше, если бы фирма хотя бы потрудилась для каждой раз- новидности своей продукции составить более или менее подробную документацию и внятные рекомендации по применению. Еще одним недостатком дисплея можно считать слишком миниатюрные и потому «слепые» десятичные точки, которые совершенно теряются при разглядывании с расстояния более метра. Но этот недостаток общий для многих разновидностей семисегментных индикаторов— дисплей МТ-10Т11 тут, увы, не исключение. Учтите также, что библиотек на языке С для МТ-10Т11 никаких не существует, и при желании подключить его к Arduino вам придется изобретать свои процедуры по той же методике, по которой составлена тестовая ассемблерная программа, опи- санная далее. Подключение к контроллеру показано на рис. 13.5, и оно ничем не отличается от подключения любого другого 12С-устройства (не забывайте про «подтягивающие» резисторы— дисплей в своем составе их не имеет!). Тактирование от внешнего кварца тут в принципе не требуется, и поэтому он на схеме не показан, — вполне достаточно внутреннего генератора, только настроенного на частоту 4 МГц, на которую рассчитаны наши 12С-процедуры. 5-й и 6-й выводы дисплея служат для подачи питания подсветки. Мы в своих конструкциях на основе дисплея МТ-10Т11 ее не используем, потому выводы оставлены неподключенными.
Глава 13. Интерфейс TWI (12С) и его применение 363 +5 В *— МТ-10Т11 ПООЗООООеО Led+ Led- R1 5,1 -СП к т =f=C1 - J-0,1 . - +5 В C2 = - = 1- + 5B «*-•—1 5,1 к M M SDA SCL Vcc GND 1 1 1 2 1 i+» +58 Ч 1 Reset 2RXD 3TXD 4 INTO (PD2) 5 INT1 (РОЗ) 6 PD4 (TO) 7 Vcc 8 GND 9 XTAL1 10XTAL2 11 PD5(T1) 12PD6 13PD7 14PB0 (ADC5) PC5 28 (ADC4) PC4 27 (ADC3) PC3 26 (ADC2) PC2 25 (ADC1)PC1 24 (PCO)ADCO 23 Г2КЩ о*» AREF 21 AVCC 20 SCK19 MISO 18 MOS117 (OC1B)PB216 (OC1A) PB1 15 ATmega8 Рис. 13.5. Подключение дисплея МТ-10Т11 к контроллеру ATmega8 Подробности Если будете подключать подсветку, учтите, что внутри там просто два последова- тельных светодиода, которые в сумме дают падение напряжения около 4,3-4,5 воль- та. Исходя из этого, следует рассчитывать токоогранчивающий резистор так, чтобы ток подсветки составлял около 30 мА (для 5 вольт получается всего 20 Ом, и, тем не менее, без него подсветку включать нельзя). Почему-то так же неудобно устроена подсветка у всех ЖК-дисплеев: единственное известное мне исключение представля- ет дисплей ВСВ1602-05, который мы будем рассматривать в главе 16. Тестовую программу подключения дисплея МТ-10ТН можно разыскать в архиве по адресу, указанному во введении (файл MT-10T11_test.asm). Она также использует включаемый файл «библиотеки» l2C.prg, так что при компиляции он должен нахо- диться в том же каталоге, что и программа. В этой программе мы избавимся от многочисленных проверок того, что посылки по интерфейсу 12С сработали, — нам все равно некуда их посылать, пока дисплей еще не работает. Программа содержит единственную достаточно длинную процедуру для общения с дисплеем: writestr. Пользователи, привычные к дисплеям с контроллерами, ко- торые надо долго и правильно инициализировать, прежде чем что-то на них удастся вывести (см. главу 16), будут удивлены: здесь установку режима необходимо про- изводить каждый раз заново после того, как ранее на линиях 12С было выставлено состояние «стоп». И каждый раз после этого подавать всю строку из 10 символов целиком. Конечно, можно ограничиться только необходимым количеством знаков, начиная с нулевой позиции, но, во-первых, в остальных позициях тогда может ото- бражаться невесть что, во-вторых, каждый раз изобретать процедуру заново не- удобно. То есть методика работы с дисплеем такая: сначала вы формируете где-то в памяти строку из десяти символов. Здесь удобно применить способ формирования массива
364 Часть III. Практическое программирование микроконтроллеров AVR в SRAM с использованием директивы .byte, о котором упоминалось в главе 6. Можно сразу подготовить стандартный формат строки для последующего вывода, содержащий все необходимые символы, в котором потом по ходу дела менять только цифры. Строка должна содержать номера символов в соответствии с масси- вом кодов под меткой Digits, размещенным в программной памяти (см. текст про- граммы). Для цифр от 0 до 9 эти номера совпадают со значением цифры, номера остальных символов (пробела, знака минус, значка градуса и некоторых букв) при- ведены в программе. Если хотите дополнить массив своими символами (возможно отображение букв «A», «b», «S», «L» и т. п.), то это можно сделать по образцу закомментированной в тексте программы таблицы, где указано соответствие битов и сегментов (см. также рис. 9.2). Подготовленную строку вы выводите на дисплей указанной процедурой writestr. Процедура посылает необходимые команды установки режима и потом выводит припасенные десять символов из строки, находящейся в памяти. Если вы при фор- мировании строки добавите к номеру символа число $80 (т. е. установите старший бит), то в процедуре writestr это расшифруется как необходимость добавить к выводимому знаку десятичную точку. Рис. 13.6. Результаты работы тестовой программы на индикаторе МТ-10Т11. а — вывод 10 установленных знаков, б — образец вывода отрицательной температуры с десятыми градуса и дополнением в виде символов «°С» Результаты работы тестовой программы с выводом 10 установленных знаков пока- заны на рис 13.6, а. А на рис 13.6, б вы видите образец вывода отрицательной тем- пературы с десятыми градуса и дополнением в виде символов «°С». Поскольку в любом обращении к дисплею здесь через интерфейс 12С всегда выво- дится одинаковое количество байтов, то подсчет времени, которое занимает вывод, упрощается. Процедура writestr выводит сначала 6 служебных байтов установки, а потом 10 байтов символов из строки. Так как вывод одного байта через 12С со скоростью 100 кГц занимает около 100 мкс, то общее время будет равно примерно 1,6 мс. В следующей главе мы построим на основе МТ-10Т11 экономичный термо- метр на батарейках, где этот параметр пригодится для расчетов энергосбережения.
ГЛАВА 14 Режимы энергосбережения и сторожевой таймер В различных моделях МК AVR имеется от 2-3 (семейство Tiny) до 5-6 (старшие Mega) режимов энергосбережения. Отметим, что к режимам энергосбережения иногда относят специальный режим ADC Noise Reduction, который мы упоминали в главе 11, хотя он не совсем связан именно с энергосбережением. При его выборе, как только контроллер «уснет», АЦП заработает, сделает преобразование и «про- снется» на прерывании АЦП, т. е. преобразование теоретически произойдет в от- сутствие цифровых шумов. Так что цель этого режима совсем другая. При практическом использовании режимов энергосбережения следует учесть, что команду sleep нельзя вызывать из процедуры прерывания — только из основной программы (подробнее см. описание команды в главе 7). Есть два базовых режима энергосбережения, общие для всех моделей AVR: Idle mode и Power Down mode. В большинстве моделей МК AVR имеются и другие разновидности режимов энер- госбережения, рассмотренные далее. Изо всех без исключения режимов контроллер может выводиться некоторыми прерываниями, в том числе прерыванием готовно- сти TWI, которое мы не применяли (см. главу 73), потому далее оно среди прочих не упоминается. В Idle mode (режиме ожидания) останавливается GPU, а также устройство управ- ления выборкой команд из памяти. Все периферийные устройства: таймеры, АЦП, порты — продолжают функционировать. Поэтому значительной экономии не по- лучается — потребление снижается лишь на 30-50%. Для ввода в режим Idle mode в программе достаточно просто вызвать команду sleep, больше ничего делать не требуется. Из этого состояния контроллер выводится прерыванием от многих источников: Timer2, внешних выводов INT0/INT1, готовности АЦП и др. Очевидно, что с точки зрения экономии энергии режим Idle имеет смысл использо- вать тогда, когда контроллер обязательно должен находиться в состоянии постоян- ной готовности, а общее потребление устройства лимитируется именно им. Кроме того, это самый простой способ предохранить контроллер от выполнения «недо- пустимых» операций при снижении питания (жаль только, не во всех случаях воз- можный). Во всех остальных ситуациях следует выбирать режимы «глубокого» энергосбережения, когда собственное потребление МК снижается до единиц или десятков микроампер.
366 Часть III. Практическое программирование микроконтроллеров AVR К таким режимам относится, в первую очередь, общий для всех моделей Power Down mode. В нем останавливаются все узлы МК, за исключением сторожевого таймера (если он включен), системы обработки внешних асинхронных прерываний и модуля TWI. Соответственно, выход из этого режима возможен либо по сбросу контроллера, в том числе и от сторожевого таймера, либо от внешнего прерывания, причем только того, которое обнаруживается асинхронно (см. разд. «Разновидно- сти прерываний» главы 3). Потребление в этом режиме может составить до 50- 60 мкА при включенном сторожевом таймере и около 1 мкА при выключенном (при условии, что выключены все остальные потребляющие устройства контролле- ра: система BOD, аналоговый компаратор, АЦП, внутренний источник опорного напряжения). Важная особенность Power Down mode — в этом режиме останавливается тактовый генератор. Это означает, что при выходе из спящего режима тактовый генератор потратит время на «раскрутку», причем задержка включения может задаваться конфигурационными ячейками suti. .0. Мы о них упоминали в главе 5: при работе от внутреннего генератора 1 МГц задержка составит около 65 мс, от внешнего кварца — 4 мс + от 1 до 16 мс, в зависимости от его частоты. Эти задержки следует учитывать при разработке программы. Заметки на полях В режиме Power Down есть одна тонкость — если в момент перехода в этот режим не закончилась операция записи в EEPROM (которая длится дольше всех других в AVR и независима от системного тактового генератора), то сама запись закончится успешно, и контроллер только после этого перейдет в Power Down Mode. Однако в этом случае продолжит работу собственный RC-генератор EEPROM, так что режим Power Down будет инициализирован не полностью. Во избежание такого случая, следует сначала закончить запись в EEPROM и только потом устанавливать режим энергосбережения. Power Save mode отличается от Power Down тем, что если в МК имеется таймер, способный работать в асинхронном режиме от отдельного тактового генератора (см. разд. «Таймеры-счетчики» главы 5), и этот таймер включен, то он и в режиме Power Save продолжит свою работу. Это несколько увеличивает потребление, зато выход из режима Power Save возможен по прерываниям от таймера-счетчика. Практически это удобно при реализации экономичных часов реального времени. Standby mode отличается от Power Down тем, что в этом режиме продолжает рабо- тать тактовый генератор (только при установке внешнего резонатора внутренний генератор точно так же отключается). Режим полезен тем, что позволяет выходить из «спящего» состояния всего за 6 тактов контроллера. В некоторых моделях есть еще режим Extended Standby, который объединяет в себе Standby и Power Save. Потребление при этом зависит от частоты кварца: при выключенном сторожевом таймере и кварце 1 МГц ток потребления равен 50- 60 мкА, каждый мегагерц добавит к потреблению примерно 20 мкА (т. е. при квар- це 16 МГц потребление составит уже более 350 мкА), ну а включенный сторожевой таймер добавит к этому свои 50-60 мкА. И, тем не менее, режим Standby mode в некоторых случаях может оказаться выгоднее Power Down из-за ускоренного запуска.
Глава 14. Режимы энергосбережения и сторожевой таймер 367 Стоит еще упомянуть, что Standby mode может работать и при тактировании от внешнего генератора, но при этом потребление с каждым мегагерцем будет расти еще больше. Так что в этом режиме общее потребление на максимальных частотах может вплотную приближаться к отметке 0,5 мА, и использовать его при таких час- тотах нецелесообразно. Теперь вам более понятно, почему я в этой книге все время стараюсь использовать минимально необходимые частоты тактирования,— при- строить к приведенным ранее алгоритмам режимы энергосбережения будет много проще, если нам ничего не придется в этих алгоритмах пересчитывать. Давайте подведем итог: режим Power Down mode дает максимальное энергосбере- жение, но максимальное же и время выхода из сна; в режиме Standby mode кон- троллер потребляет куда больше, но время выхода в рабочий режим намного меньше. Потому нельзя однозначно сказать, какой из режимов лучше в том или ином случае. Далее мы ориентируемся на универсальный режим Power Down. В каком случае нужен режим энергосбережения? Из разд. «Потребление МК A VR» главы 4 мы знаем, что в простейшем режиме работы от встроенного генератора 1 МГц потребление среднего по характеристи- кам контроллера не превышает 1,6-1,8 мА. Может быть, этого достаточно, чтобы без проблем работать от батареек и не надо заморачиваться каким-то энергосбере- жением? Давайте посчитаем. Предположим, у нас есть устройство с током потребления 1-2 мА. Элементы типо- размера АА (типа alcaline, т. е. щелочные) имеют емкость порядка 2000-3000 мАч, т. е. устройство с указанным потреблением от этих элементов проработает не менее 1-2 тыс. часов или 40-80 суток (реально даже больше, т. к. чем меньше нагрузка, тем больше емкость электрохимических элементов). Время работы от батарей ти- поразмера D с энергоемкостью порядка 15-18 000 мАч составит более года, чего для большинства практических применений достаточно. Заметки на полях Выбирать для питания подобных устройств следует именно щелочные элементы, т. к. они обладают большой емкостью, не текут при переразряде и, главное, имеют доста- точно большой срок хранения (порядка 7 лет). Литиевые элементы типоразмера АА и ААА, как мы знаем, держат напряжение практически на номинальном уровне до по- следнего момента, после чего оно быстро падает до нуля (см. рис 10.1 в главе 10), и срок хранения у них еще больше, чем у щелочных — до 15 лет. Но следует учесть, что из-за низкого внутреннего сопротивления литиевые элементы лучше всего себя про- являют при работе на мощную или импульсную нагрузку. В таком режиме они покажут гораздо большее время работы, чем щелочные, и практически сравняются с послед- ними по стоимости в расчете на каждый ватт-час, в то время как в низкопотребляющих приборах щелочные элементы по емкости от них почти не отличаются, зато гораздо дешевле. Еще одна область, где литиевые батарейки вне конкуренции: при низких температурах их емкость падает меньше других типов (в противоположность литий- ионным и особенно литий-полимерным аккумуляторам, которые морозов не переносят вовсе).
368 Часть III. Практическое программирование микроконтроллеров AVR Из приведенных цифр следует, что в условиях дефицита питания мы все время вы- нуждены балансировать между тремя показателями: функциональностью устройст- ва, его потреблением и приемлемыми габаритами батареек. Огромные (и дорогие) батарейки типа С или D в случае переносных устройств мы отметаем сразу — этот вариант приемлем для автономного датчика, который где-то устанавливается ста- ционарно. Потому устройство с потреблением более 1 мА— это всегда прибор, который включается периодически, остальное время батарейки в нем просто хра- нятся. В этом случае, действительно, можно было бы и не усложнять алгоритм энергосбережением, учитывая еще то, что в нем могут быть компоненты, потреб- ляющие куда больше контроллера. А вот обеспечить потребление менее 1 мА бывает очень непросто, и опять же не из- за энергосбережения контроллера, а от сложности подбора остальных компонентов. Датчики обычно потребляют только в момент запроса, потому с ними все относи- тельно просто, а вот дисплей в режиме непрерывной работы уже приемлем только семисегментный ЖК-типа. МТ-10Т11 потребляет около 30 мкА, тогда как переход к строчным (знакосинтезирующим) типам сразу повышает потребление почти на два порядка, — контроллер любого двухстрочного дисплея сам по себе потребляет примерно 1-2 мА, т. е. они съедят батарейку за пару месяцев. Далее мы покажем, как использовать МТ-10Т11 совместно с энергосберегающими режимами, а в гла- ве 16 опишем управление и более потребляющими разновидностями, но на работу от батарейки в круглосуточном режиме они уже не рассчитаны. А если потребле- ние вообще перестает играть серьезную роль, то можно себя побаловать куда более эргономичными и эстетичными OLED-дисплеями. Потому случаи необходимости применения режимов энергосбережения ограниче- ны по сути только схемами, в которых все компоненты основное время потребляют токи порядка десятков, в крайнем случае сотен микроампер. Тогда становится зна- чимым различие между Power Down mode и Standbye mode, и имеет смысл заняться тонкими расчетами необходимой и достаточной тактовой частоты. Если вы не сумеете обеспечить такой режим, то можете с энергосбережением не возиться — не поможет. Программирование режима энергосбережения Само по себе программирование режима энергосбережения — без учета всех отме- ченных нюансов — очень простое. Управляется этот режим битами se (sleep enable) и smx (sleep mode), которые в большинстве младших моделей Mega, включая «на- ши» ATmega8 и ATmega8535, а также ATtiny2313, находятся в регистре mcucr (том же, который отвечает за внешние прерывания). Некоторую сложность здесь пред- ставляет только перенос кода на другие модели из-за того, что в разных моделях МК AVR управляющие биты бывают «разбросаны» по разным другим регистрам, и приходится смотреть в описание, чтобы не ошибиться. Например, в «ардуинов- ском» ATmega328 и его младших братьях ATmega88/168 для управления режимом sleep имеется отдельный регистр smcr (жалко что ли регистров, если там область РВВ в памяти расширена и все равно использована не полностью?) А в традицион-
Глава 14. Режимы энергосбережения и сторожевой таймер 369 ном регистре mcucr там зато есть биты управления отключением системы BOD — в обход обычно управляющего этим процессом fuse-бита boden (о чем авторы реко- мендаций по части энергосбережения Arduino почему-то благополучно забывают и рекомендуют лезть в конфигурационные ячейки). Мы будем здесь рассматривать только классический случай управления через mcucr. По умолчанию все биты установки Sleep Mode, сколько их есть (три smo .. 2, два smo. .1 или один sm) установлены в нулевое состояние, что означает режим Idle. Тогда достаточно установить бит разрешения se и вызвать команду sleep. Для ус- тановки режима Power Down надо дополнительно установить бит smi: ldi temp, (1« SMI) | (1«SE) /разрешение Sleep, режим Power Down out MCUCR,temp Как мы помним из главы 7, команду sleep нельзя вызывать из прерывания (она по- просту не сработает). Напомним также, что инструкция рекомендует устанавливать бит разрешения непосредственно перед вызовом команды sleep, — фактически это предохранитель, как и в некоторых других случаях (см., например, описание записи в EEPROM или обращения со сторожевым таймером далее), обеспечивающий дуб- лирование одной и той же операции во избежание самопроизвольного «ухода» МК в спящее состояние при наличии помех. Обратите внимание, что первым делом по- сле выхода из режима «сна» выполнится обработчик прерывания, «разбудившего» компьютер, а после него — команда, следующая после sleep. Дополнительно снизить потребление может отключение системы BOD. В боль- шинстве контроллеров отключить ее можно только программированием «фьюзов» (вернуть boden в состояние 1, — см. главу 5). В моделях с пикопотреблением (ATmega88P/168P/328P) в регистре mcucr есть биты bodse и bods, позволяющие от- ключать BOD программно. Отключение BOD должно экономить около 20-30 мкА, в зависимости от контроллера. Мы уже знаем, что в любом из Sleep-режимов ника- ких непредсказуемых операций контроллер совершать не может, потому система BOD не требуется. Но прежде чем уводить контроллер в спящий режим, нужно озаботиться его спосо- бом вывода оттуда, иначе он просто заснет навсегда, и ничего полезного сделать уже не сможет. При всей видимой скудности средств вывода из глубокого сна в режиме Power Down, они предоставляют достаточно богатый набор возможно- стей. Начнем с классического пробуждения по внешнему импульсу, пришедшему на вывод внешнего прерывания. Выход по внешнему прерыванию Напомним, что вывести из режима Power Down можно только прерыванием, про- исходящим асинхронно, т. е. не связанным с наличием тактовых импульсов. Уни- версальный способ состоит в объявлении прерывания по уровню INTO или INT1, которые имеются во всех моделях контроллеров (разве что в самых младших Tiny имеется только INTO). Внешний импульс может поступить от банального нажатия
370 Часть III. Практическое программирование микроконтроллеров AVR кнопки, а может — от внешнего таймера, часов реального времени или любого дру- гого устройства, например, какого-нибудь датчика, закончившего преобразование. В тестовой программе мы по нажатию кнопки будим контроллер, зажигаем свето- диод на выводе РВО (вывод 14 ATmega8), выдерживаем паузу и даем контроллеру заснуть снова (листинг 14.1). Рисовать схему не станем— мы уже неоднократно обсуждали, как подключить кнопку к выводу INTO ATmega8 (PD2, вывод 4),— см., например, рис. 6.1 в главе 6. Из этой же главы мы возьмем и основу для про- цедуры пробуждения по прерыванию. .device АТтедаЭ /частота тактирования 4 МГц (для определенности) .include "m8def.inc" .def temp = rl6 .def RazrO = rl7 /разряды задержки .def Razrl = rl8 .def Razr2 = r20 ; ;============ прерывания ============ rjmp RESET ;Reset Handle rjmp EXT_INT0 /External InterruptO Vector Address ;========== программа ============ .macro Delay20 /процедура задержки до 20 с при 4 Мгц ldi Razr2,@0 /старший байт задержки ldi Razrl,01 /средний байт задержки ldi RazrO,02 /младший байт задержки R_sub: subi RazrO,1 sbci Razrl,0 sbci Razr2,0 brcc R_sub . endm EXT_INT0: /кнопка нажата ldi temp,0b00000001 /порт В контакт РВО = 1 out PortB,temp /зажигаем светодиод Delay20 $0C,$35,0 /задержка 1 с при 4 МГц clr temp out PortB,temp /гасим светодиод ldi temp,$FF out GIFR,temp /сбросить флаги внешних прерываний reti /конец прерывания INTO Reset: ldi temp, low(RAMEND) /устанавливаем указатель на стек out SPL, temp
Глава 14. Режимы энергосбережения и сторожевой таймер 371_ ldi temp, high(RAMEND) out SPH,temp ldi temp,ObOOOOOlOO /для второго разряда порта D out PORTD,temp /подтягивающий резистор на всякий случай ldi temp,ObOOOOOOOl ;порт В контакт РВО на выход out DDRB,temp ldi temp, (1« SMI) | (1«SE) /разрешение Sleep, режим Power Down out MCUCR,temp ldi temp, (1«INTO) /разрешаем прерывание INTO по уровню out GICR,temp sei /разрешаем прерывания Gcykle: /цикл отслеживания кнопки sleep /уходим в сон rjmp Gcykle /вернуться обратно, Эту тестовую программу вы найдете в архиве по адресу, указанному во введении (файл SleepJNTOJest.asm). Напоминаю, что при пробуждении после выполнения прерывания, в котором све- тодиод зажигается на одну секунду, контроллер выполнит как минимум одну команду после sleep. Это команда rjmp, возвращающая опять к sleep. Но так как у нас и прерывание по уровню (которое захочет повторяться, пока этот самый уро- вень висит), и еще и неизбежный дребезг кнопки, то к моменту возврата флаг прерывания INTO почти наверняка будет опять установлен, и прерывание захочет выполниться повторно. Чтобы этого не произошло, мы перед выходом из прерыва- ния чистим регистр флагов внешних прерываний, предотвращая возможность ново- го срабатывания. Измеренный мной ток потребления контроллера ATmega8A в таком режиме, без каких-либо дополнительных мер по снижению потребления, составил около 150 мкА. Его можно дополнительно снизить (отключением системы BOD, аналого- вого компаратора, АЦП и т. д.), но и без того это вполне приличная величина для устройств с автономным питанием. Если бы можно было удержать потребление всего прибора на таком уровне, то от комплекта батареек ААА (емкость около 1200-1500 мА-ч) он бы проработал примерно год, от АА — года два с половиной, а то и три. Потому стоит сосредоточиться на минимальном потреблении при подборе остальных компонентов реальной схемы. Например, как мы уже говорили, ЖК-дисплей МТ-10Т10-11 (см. далее) с потреблением 30 мкА вписывается в такую схему, а куда более богатые по возможностям строчные (знакосинтезирующие) ЖК-дисплеи со встроенными контроллерами типа HD44780 — нет, поскольку та- кой контроллер или любой его аналог потребляют не менее 1 мА. Во всем этом деле неприятной занозой остается только прерывание по уровню. В нашем случае все просто: после нажатия кнопки контроллер целую секунду ни- чего не делает, причем застывает он в процедуре прерывания, куда другие преры- вания вмешаться не могут. А потом мы сразу чистим флаги, даже если они устано-
372 Часть III. Практическое программирование микроконтроллеров AVR вились, и ничего плохого от жизни больше не ждем. Но вполне могут быть случаи, когда возможность повторных срабатываний так легко предотвратить не удается. Да и здесь тоже возникнет неприятная ситуация, если вы задержите палец на кноп- ке дольше секунды. В этом деле могут помочь возможности, которые предоставляют другие контролле- ры. В ATmega8535 и ATmegal6 (как и во многих других) есть прерывание INT2, которое выполняется асинхронно. Потому в них можно смело объявлять прерыва- ние INT2 на нажатие, т. е. по перепаду из единицы в ноль (режим специально уста- навливать не надо — как и режим по уровню для INT0/INT1, он действует по умол- чанию). И дальше в нашем случае делать все то же самое. Отдельную тестовую программу для этого случая вы найдете в архиве по адресу, указанному во введении (файл Sleep_INT2_test.asm). Она мало чем отличается от предыдущей — различие только в настройках прерывания и выводов: кнопка под- ключается к выводу РЕО (вывод 31 корпуса ATmega8535), на котором здесь нахо- дится INT2, светодиод— к тому же выводу РВО (вывод 1 ATmega8535). Здесь в нашем случае (только при использовании кнопки!) может возникнуть другая за- сада — если вы задержите палец дольше секунды, то при отпускании, скорее всего, прерывание возникнет повторно из-за дребезга. И с этим бороться здесь непро- сто — придется как-то отслеживать отпускание и только потом разрешать уходить в sleep. Еще интереснее возможность, которую предоставляет прерывание PCINT. Ни в од- ной из «наших» моделей Mega оно не представлено — в этом семействе прерыва- ние появилось, начиная с ATmega88. Зато оно есть в ATtiny2313, на примере кото- рого мы сейчас его и продемонстрируем. Преимущество прерывания PCINT, кроме его асинхронности, в том, что его можно поймать на любом из семи выводов (в на- шем случае — порта В), недостаток — в том, что оно происходит по любому изме- нению логического уровня на выбранном выводе (потому и называется Pin Change Interrupt). To есть определять, что это было: нажатие или отпускание, придется про- граммным путем. Но мы ведь это уже умеем, правда? На рис. 14.1 показано подключение кнопки и светодиода к ATtiny2313 для иллюст- рации применения прерывания PCINT. Подключение кварца на схеме не показа- но — хотя приведенная далее программа рассчитана на 4 МГц, но это несущест- венно для обсуждаемого случая. Чтобы не пересчитывать настройку задержки, вы можете выбрать внутреннее тактирование с этой частотой, установив конфигура- ционные ячейки cksel3:0 в состояние оою или ООН (и обязательно не забыв вы- ключить ячейку ckdiv8). Кстати, в ATtiny2313 различные варианты тактовой часто- ты можно установить из программы, предварительно выключив только ячейку ckdiv8 (встроенный генератор начнет тактирование на частоте 8 МГц) и затем уста- новив биты делителя тактовой частоты clkps3:0 в регистре clkpr (clkps3:0 = oooi дает коэффициент деления 2). Программа с использованием PCINT согласно схеме, показанной на рис. 14.1, при- ведена в листинге 14.2. Ее также можно найти в архиве по адресу, указанному во введении (файл Sleep_PCINT_test.asm).
Глава 14. Режимы энергосбережения и сторожевой таймер 373 +5 В 1 Reset 2RXD(PD0) 3TXD(PD1) 4XTAL2(PA1) 5 XTAL1 (РАО) 6 INTO (PD2) 7 INT1 (PD3) 8 PD4 (TO) 9PD5(T1) 10GND Vcc20 SCK19 MISO 18 MOSI17 (OC1B)PB416 (OC1A) PB3 15 PB214 (PCINT1)PB1 13 (PCINTO)PBO 12 PD6 11 -►+5B + 5B ATtiny2313 Kh1 Рис. 14.1. К использованию прерывания PCINT для вывода из «сна» ATtiny2313 .device ATtiny2313 ;4 МГц (для определенности) .include fftn2313def.inc" .def temp =rl6 .def RazrO = rl7 /разряды задержки .def Razrl = rl8 .def Razr2 = r20 ; /============ прерывания ============ rjmp RESET ;Reset Handle .org PCIaddr rjmp EXT_PCINT ;External Interrupt Vector Address ;========== программа ============ .macro Delay20 ;процедура задержки до 20 с при 4 Мгц ldi Razr2,@0 /старший байт задержки ldi Razrl,@1 /средний байт задержки ldi RazrO,02 /младший байт задержки R_sub: subi RazrO,1 sbci Razrl,0 sbci Razr2,0 brcc R_sub .endm EXT_PCINT: /кнопка нажималась или отпускалась пор пор пор
374 Часть III. Практическое программирование микроконтроллеров AVR пор ; - пропускаем 4 такта = 1 мкс, чтобы устоялся уровень sbic PinB,I ;пропустить, если нажата reti ldi temp,0b00000001 ;порт В контакт РВО = 1 out PortB,temp /зажигаем светодиод Delay20 $0С,$35,0 ;задержка 1 с при 4 МГц clr temp out PortB,temp ;гасим светодиод ldi temp,$FF out GIMSK,temp ;сбросить флаги внешних прерываний reti ;конец прерывания PCINT Reset: ldi temp,low(RAMEND) /устанавливаем указатель на стек out SPL,temp ;для tiny2313 только SPL ldi temp,ObOOOOOOlO ;для первого разряда порта В out PORTB,temp /подтягивающий резистор на всякий случай ldi temp,ObOOOOOOOl ;порт В контакт РВО на выход out DDRB,temp ldi temp, (1« SMI) | (1«SE) /разрешение Sleep, режим Power Down out MCUCR,temp ldi temp, (1«PCIE) /разрешаем прерывание PCINT out GIMSK,temp ldi temp, (1«PCINT1) /прерывание PCINT на выводе PCINT1 (PB1) out PCMSK,temp sei /разрешаем прерывания Gcykle: /цикл отслеживания кнопки sleep /уходим в сон rjmp Gcykle /вернуться обратно, если не нажата Программа, как видите, при возникновении прерывания выжидает около микросе- кунды, проверяет уровень на выбранном выводе, и если он низкий (кнопка нажата), то делает то же самое, что и программы, приведенные ранее, включая очистку фла- гов прерываний, а затем снова уходит в «сон». Применение сторожевого таймера Сторожевой таймер (Watchdog Timer, WDT) — одно из самых полезных устройств в составе микроконтроллеров. Причем в описаниях его полезность часто сводится к примитивной возможности автоматического перезапуска контроллера в случае за- висания программы. Есть даже fuse-бит wdton, который запрещает выключать WDT вообще, — его можно только периодически сбрасывать, не давая контроллеру уйти в перезагрузку. Конечно, это сильно ограниченный подход.
Глава 14. Режимы энергосбережения и сторожевой таймер 375 Заметки на полях Наверное, можно представить реальный случай, когда такой перезапуск может пона- добиться при нормальной работе: если, скажем, есть вероятность, что очередной бес- конечный цикл ожидания, которых у нас предостаточно по разным поводам, не закон- чится ничем, а продолжать работу надо. Но как на страховку от нештатной ситуации проще, на мой взгляд, на перезапуск не надеяться. Нештатная ситуация, требующая перезагрузки контроллера, в огромном большинстве случаев означает, что либо схе- ма, либо программа составлены с ошибками, и их надо просто отладить как следует. Аналогия с ранними Windows, которые уходили в перезагрузку по каждому чиху (зна- менитый «синий экран смерти»), тут не катит: не бывает в контроллерах близких по сложности программ, а если вдруг бывает, то чего-то тут не додумали. У контроллеров другое назначение, чем у традиционных микропроцессоров: схема вместе с програм- мой представляют единый комплекс, и если в нем что-то сбоит, то это означает нера- ботоспособность прибора в целом. И тут уж безразлично, перезагрузится он и снова зайдет в тупик или просто повиснет. Так что обычно периодическая принудительная перезагрузка — это в лучшем случае отладочный режим, но никак не нормальная ра- бота. Зато для многих других целей сторожевой таймер весьма полезная вещь — прежде всего потому, что он выводит контроллер из любого режима энергосбережения. Представим себе прибор, который основное время бездельничает, и только редко- редко должен что-то делать (датчик, измеряющий какую-то медленно меняющуюся величину). Тогда мы просто загоняем контроллер в «сон», периодически просыпа- емся, выполняем работу и опять засыпаем. Если WDT будет «будить» контроллер каждые две секунды, а выполняемая операция занимает, допустим, 4 миллисекунды (длинная операция типа отсылки данных по UART или записи в EEPROM), то доля активного состояния во времени составит 75оо- Соответственно, общее потребление в активном состоянии нужно тоже поделить на 500, и если оно составляет, допус- тим, 15 мА, то в среднем эта доля потребления составит 30 мкА — совсем неболь- шая добавка к потреблению контроллера в «спящем» режиме (не забудем, что сам включенный WDT также потребляет, потому общая добавка может превысить 100 мкА). Классический WDT, который унаследован многими относительно старыми моде- лями контроллеров (включая «наши» ATmega8 и ATmega8535) еще от семейства Classic, только это и умеет делать: перезагружать контроллер через заданные про- межутки времени (как если бы мы нажали на кнопочку Reset), если его вовремя не сбросить. В последующих моделях появился расширенный WDT, который допол- нительно приобрел одно крайне полезное свойство — он может при пробуждении входить в режим специального прерывания WDT. При этом все регистры и память остаются в неприкосновенности — как будто мы вообще ни в какой сон не уходи- ли, а начинаем выполнять действия просто по обычному таймеру или другому со- бытию. Это, повторимся, крайне полезное свойство позволяет решить кучу разных проблем: поскольку мы теперь можем хранить данные хоть в регистрах, хоть в па- мяти, то очень просто строить те же датчики с любым периодом измерения и осреднением измеряемой величины за любой произвольный срок, причем с эко- номным расходом энергии. В семействе Mega такой расширенный WDT начинается с модели ATmega88 и да- лее, а вот в семействе Tiny все проще: его, подобно прерыванию PCINT, имеет
376 Часть III. Практическое программирование микроконтроллеров AVR в том числе и «наш» ATtiny2313 (теперь вы начинаете понимать, почему я называл этот контроллер любимым?). Давайте разберемся сначала с процедурами запуска WDT. Эта, в общем, несложная задача крайне бестолково изложена как в документации на контроллеры, так и в переводных пособиях [6,7], потому постараемся изложить ее человеческим язы- ком. Инициализация, запуск и сброс WDT Сначала договоримся, что никаких ftise-битов wdton мы не устанавливаем — только ручной запуск и тогда, когда нам это потребуется. Так мы снимаем часть путанных вариантов, изложенных в упомянутых источниках. Тогда у нас остаются три состояния WDT, с которыми будем разбираться: запуск (с нужным временным интервалом), периодический сброс по ходу выполнения программы и остановка насовсем. Проще всего со сбросом — для этого есть, как мы помним (см. главу 7), отдельная команда, которая называется wdr. Ее просто расставляют в нужных местах по коду, чтобы временной промежуток между вызо- вами этой команды не превысил установленного времени, и контроллер перезагру- зится только, если что-то задержало выполнение программы. Инициализация и запуск WDT с нужным временным интервалом сложнее, и вы- полняется немного по-разному для классического (обычного) и расширенного сто- рожевых таймеров. Для обычного (ATmega8, ATmega8535) это выполняется так (листинг 14.3). cli wdr ldi temp, (1«WDCE) | (1«WDE) ; разрешение изменений + запуск out WDTCR,temp ldi temp, (1«WDE) | (1«WDP2) I (1«WDP1) I (1«WDPO) ;внесение изменений (WDP2:0 = 111 =2,2 секунды) + запуск out WDTCR,temp sei Классический WDT, как видите, имеет три бита задания времени: wdp2:0, с по- мощью которых максимальное время срабатывания может быть установлено рав- ным около 2,1 с. В ATmegal6, как ни странно, WDT еще «более классический» (напоминает WDT из давно забытого семейства Classic), и процедура может быть еще упрощена — там можно обойтись одной второй строкой, разрешать изменения при этом не требуется. Прерывания (команда cli) нужно на время установок WDT запрещать обязатель- но — перерыв между двумя out (см. листинг 14.3) абсолютно недопустим. Это еле-
Глава 14. Режимы энергосбережения и сторожевой таймер 377 дует делать, даже если у вас никаких прерываний в программе не предусмотре- но, — просто на всякий случай. И обязательно не забыть восстановить разрешение прерываний в конце процедуры (команда sei), иначе после установок контроллер может оказаться неработоспособным. Заметим в скобках, что Arduino этот момент особенно касается— там неявные прерывания возникают постоянно. Поэтому, если здесь мы можем специально ничего не запрещать, поскольку будем произво- дить инициализацию режима в начале программы, когда прерывания запрещены заведомо, то в Arduino мы ни в чем не уверены, и там правило запрета прерываний нужно соблюдать во всех случаях. В расширенном WDT (ATtiny2313, «ардуиновские» ATmega88/l68/328) простой запуск с целью последующего сброса осуществляется точно так же, только для задания времени в регистре wdtcr имеется четыре бита: wdp3:0, а не три. Но нам интереснее режим прерывания, который можно включить следующим способом (листинг 4.14). cli wdr ldi temp, (1«WDCE) | (1«WDE) ; разрешение изменений + запуск out WDTCR,temp ldi temp, (1«WDIE) | (1« WDP3) | (1«WDPO) /внесение изменений: прерывание, (WDP3:0 = 1001 = 8 сек) + запуск out WDTCR,temp sei Разумеется, прерывание WDT нужно инициализовать еще и обычным способом через таблицу векторов. Если вы одновременно с разрешением прерываний (бит wdie) еще и установите обычный запуск (бит wde, как в листинге 14.3), то у вас кон- троллер окажется в комбинированном режиме прерывания+сброса. В этом режиме первый раз контроллер просыпается на выполнение только прерывания, в конце которого он переключается на сброс, и в следующий раз окажется в режиме сброса. В обычных программах такое не требуется — режим может пригодиться, чтобы куда-то сохранить ценные данные перед тем, как сбросить контроллер полностью, и затем опять начать с нуля. В приведенном примере (см. листинг 4.14) мы установили максимальное время срабатывания расширенного WDT, равное 8 с. Тактирование WDT происходит от отдельного RC-генератора, который у классического таймера «шлепает» с частотой 1 МГц, а у расширенного — с частотой 128 кГц (тем же генератором отсчитывается задержка запуска после сброса, — см. описание fuse-битов suto и suti в главе 5). Разумеется, это величины приблизительные и зависят как от напряжения питания, так и от температуры кристалла (в модернизированном расширенном WDT эта за- висимость несколько меньше, чем в классическом). Значения временного интервала в зависимости от состояния битов WDP2: о и WDP3: о приведены в табл. 14.1 и 14.2 для разных вариантов таймеров.
378 Часть III. Практическое программирование микроконтроллеров AVR Таблица 14.1. Задание интервалов срабатывания классического WDT Классический сторожевой таймер, freH = 1 МГц БИТЫ WDP2:0 К. деления /ген Интервал БИТЫ WDP2:0 К. деления freH Интервал 000 214 (16 384) 16 мс 100 218 (262 144) 0,26 с 001 215 (32 768) 32,5 мс 101 219 (524 288) 0,52 с 010 216 (65 536) 65 мс 110 220(1 048 576) 1,0с 011 217 (131 072) 130 мс ill 221 (2 097 152) 2,1 с Таблица 14.2. Задание интервалов срабатывания расширенного WDT БИТЫ WDP3:0 К. деления freH Интервал БИТЫТОЭРЗ:О К. деления freH Интервал Расширенный сторожевой таймер, /ген 0000 211 (2048) 16 мс 0101 216 (65 536) 0,5 с 0001 212 (4096) 32 мс ОНО 217 (131 072) 1,0с 0010 213(8192) 64 мс 0111 218 (262 144) 2,0 с = 128 кГц ООН 214 (16384) 125 мс 1000 219 (524 288) 4,0 с 0100 215 (32768) 250 мс 1001 220(1 048 576) 8,0 с Наконец, остановить запущенный WDT во всех случаях позволяет следующая про- цедура (листинг 14.5). cli wdr ldi tmp, (1«WDCE) | (1«WDE) out WDTCR,tmp ldi tmp, (0«WDE) out WDTCR,tmp sei Подробности Если вдруг вам понадобится менять временнбй интервал уже включенного WDT по ходу программы, то для этого в описаниях рекомендуется более сложная процедура, когда сначала содержимое wdtcr извлекается в регистр общего назначения, потом в нем устанавливаются нужные биты, а затем результат опять выгружается в wdtcr: in temp, WDTCR ori temp, (1«WDCE) | (1«WDE) out WDTCR, temp
Глава 14. Режимы энергосбережения и сторожевой таймер 379 Смысл этой процедуры в том, чтобы wdtcr никогда не оставался с обнуленными бита- ми wdpx, — это означает минимальное время срабатывания (около 16 мс), и если WDT был близок к срабатыванию, то теоретически не исключена ситуация, что он успеет сбросить систему до окончательного выключения. Для расширенного варианта WDT при его остановке рекомендуется тем же способом очистить бит прерывания (wdrf в регистре mcusr), чтобы оно случайно не возникло после выключения. На практике это, однако, чистая перестраховка — все равно WDT перед внесением каких-то изме- нений нужно сбрасывать командой wdr. Возможно, так надо делать в ответственных приборах на всякий случай, но если мы следим за временами в своей программе, не- сложно вероятность такого совпадения исключить. Составляя программу, о включенном WDT нужно помнить: если встречаются дли- тельные процедуры, которые нельзя прерывать (например, запись в EEPROM не- скольких байтов подряд, отправка по UART большого массива, запись/чтение внешней памяти и т. п.), то надо не забывать WDT периодически сбрасывать командой wdr, а если это по какой-то причине невозможно, то перед выполнением таких процедур сторожевой таймер нужно принудительно запрещать, а потом опять разрешать. Примеры использования WDT Рассмотрим сначала простой классический вариант на примере ATmega8. Подклю- чите к его выводу 14 (РВО) светодиод. Больше можно ничего не подключать — за- вести контроллер можно в нашем случае от любой частоты любого источника. Программа будет такой (листинг 4.6). .device ATmega8 ;4 МГц (важно только для задержки) .include "m8def.inc" .def temp =rl6 .def RazrO = rl7 ;разряды задержки .def Razrl = rl8 .def Razr2 = rl9 ; ;============ прерывания rjmp RESET ;Reset Handle ;========== программа === .macro Delay20 /процедура задержки до 20 с при 4 МГц ldi Razr2,@0 ;старший байт задержки ldi Razrl,01 /средний байт задержки ldi RazrO,@2 /младший байт задержки R_sub: subi RazrO,1
380 Часть III. Практическое программирование микроконтроллеров AVR sbci Razrl,0 sbci Razr2,0 brcc R_sub . endm Reset: ldi temp,low(RAMEND) ;устанавливаем указатель на стек out SPL,temp ldi temp,high(RAMEND) out SPH,temp ldi temp,0b00000001 ;порт В контакт РВО на выход out DDRB,temp ;==== установка Watchdog, прерывания запрещены! wdr /сбрасываем WDT ldi temp, (1«WDCE) I (1«WDE) ; разрешение изменений + запуск out WDTCR,temp ldi temp, (1«WDE) I (1« WDP2) | (1«WDP1) I (1«WDPO) /внесение изменений: прерывание, (WDP3:0 = 0111 = 2 сек) + запуск out WDTCR,temp ldi temp, (1« SMI) | (1«SE) /разрешение Sleep, режим Power Down out MCUCR,temp Gcykle: /главный цикл sbi PortB,0 /зажигаем светодиод Delay20 $03,$0D,40 /задержка 0,25 с при 4 МГц cbi PortB,0 /гасим светодиод sleep /уходим в сон rjmp Gcykle / По этой программе контроллер будет каждые две секунды подмигивать светодио- дом, сигнализируя, что он «проснулся», загрузился и что-то делает. Причем от так- товой частоты здесь будет меняться только задержка, т. е. время нахождения кон- троллера в активном режиме, о чем сигнализирует включенный светодиод. Период между перезапусками, независимо от того, что контроллер в это время делает, от частоты тактирования не зависит и будет всегда равным примерно 2,1 секунды. Не забывайте только, что по умолчанию время выхода из режима Power Down при так- тировании контроллера от внешнего кварца 4 МГц составит около 8 мс, а от внут- реннего генератора — почти одну десятую секунды (о конфигурационных ячейках suto и suti, определяющих эти моменты, рассказано в главе 5), что может оказаться критичным в некоторых ситуациях. Повторим еще раз про ограничение на длительность работы при включенном WDT (задаваемое в рассматриваемом случае задержкой Deiay20): время пребывания в активном состоянии не должно превысить установленного интервала WDT, иначе контроллер уйдет на перезагрузку, не окончив работу. Но можно, конечно, посту- пить и иначе — не дожидаясь очередной перезагрузки, своевременно сбрасывать WDT командой wdr.
Глава 14. Режимы энергосбережения и сторожевой таймер 381 Расширенный вариант WDT с использованием прерывания мы покажем на примере ATtiny2313 (листинг 14.7). Чтобы убедиться, что значения регистров сохраняются после выхода из «сна», мы включим сторожевой таймер на пробуждение каждые 2 секунды и в прерывании, кроме включения светодиода на четверть секунды, бу- дем посылать через UART последовательные значения счетчика от 0 до 255 (перед подключением UART ознакомьтесь с материалом главы 15). Светодиод подключен также к выводу РВО (вывод 12 ATtiny2313), а к TXD (вывод 3 ATtiny2313) нужно подключить вывод RxD адаптера UART (см. разводку выводов ATtiny2313 на рис. 14.1). Тактирование в нашем случае производится от внешнего кварца 4 МГц, иначе UART будет сложно настроить. .device ATtiny2313 ;4 МГц кварц (важно для задержки и UART) .include "tn2313def.inc" .def temp =rl6 .def RazrO = rl7 /разряды задержки .def Razrl = rl8 .def Razr2 = rl9 / .def count = r20 ;счетчик ;============ прерывания ============ rjmp RESET ;Reset Handle .org WDTaddr rjmp WDT_over ;External Interrupt Vector Address ;========== программа ============ out_com: /посылка байта из count с ожид. готовности sbis UCSRA,UDRE ;ждем готовности буфера передатчика rjmp out_com out UDR,count ;count!!! а не temp ret .macro Delay20 /процедура задержки до 20 с при 4 Мгц ldi Razr2,@0 /старший байт задержки ldi Razrl,01 /средний байт задержки ldi RazrO,02 /младший байт задержки R_sub: subi RazrO,1 sbci Razrl,0 sbci Razr2,0 brcc R_sub . endm WDT_over: /пробуждение по Watchdog sbi PortB,0 /зажигаем светодиод
382 Часть III. Практическое программирование микроконтроллеров AVR inc count ;увеличиваем счетчик к следующему разу rcall out_com /посылаем во внешний мир счетчик Delay20 $03,$0D,40 ;задержка 0,25 с при 1 МГц cbi PortB,0 ;гасим светодиод reti ;конец прерывания PCINT Reset: ldi temp,low(RAMEND) /устанавливаем указатель на стек out SPL,temp ;для tiny2313 только SPL ldi temp,ObOOOOOOOl ;порт В контакт РВО на выход out DDRB,temp ;=== UART ldi temp,25 ;9600 при 4 МГЦ + U2X out UBRRL,temp ;скор. передачи ldi temp, (1«RXEN| 1«TXEN) out UCSRB,temp ;разреш. приема/передачи 8 битов ;==== установка Watchdog, прерывания запрещены! wdr /сбрасываем WDT ldi temp, (1«WDCE) I (1«WDE) ; разрешение изменений + запуск out WDTCR,temp ldi temp, (1«WDIE) I (1« WDP2) | (1«WDP1) I (l«WDP0) /прерывание, время (WDP3:0 = 0111 = 2 сек) + запуск out WDTCR,temp ldi temp, (1« SMI) | (1«SE) /разрешение Sleep, режим Power Down out MCUCR,temp clr count /обязательно устанавливаем при сбросе sei /разрешаем прерывания Gcykle: /главный цикл sleep /уходим в сон rjmp Gcykle Программа будет через каждые 2 секунды выдавать через UART последовательные состояния счетчика (1, 2, 3, ...)• Если вы сбросите контроллер (например, выключи- те и включите питание), то программа опять начнет считать с нуля. Вот здесь очень важно, что в начале программы инициализируется счетчик (коман- да cir count перед разрешением прерываний). Закомментируйте ради эксперимента команду cir count и загрузите программу заново. Можете убедиться, что теперь даже сброс не повлияет на значение count — после выключения и включения про- грамма продолжит считать с того момента, на котором остановилась перед выклю- чением. Еще интереснее варианты вы можете получить, если включите комбиниро- ванный режим, о котором шла речь ранее (для этого нужно добавить к строке запи- си в wdtcr еще и (1«wde)) — в зависимости от наличия или отсутствия начальной инициализации count, программа либо будет всякий раз выдавать единицу, либо опять же считать с того значения, на котором остановилась перед выключением питания.
Глава 14. Режимы энергосбережения и сторожевой таймер 383 Все, что сказано ранее касательно соотношения периода «пробуждения» контрол- лера по WDT и времени его работы в активном состоянии, действительно и в этом случае: либо позаботиться о том, чтобы время работы не превышало установлен- ный интервал WDT, либо, не дожидаясь его истечения, сбрасывать таймер коман- дой wdr. В расширенном варианте WDT это дело, правда, облегчается увеличенным максимальным интервалом, который здесь равен 8 секундам. Обе рассмотренные в этом разделе программы вы найдете в архиве по адресу, ука- занному во введении (файлы WDT_ sleepjest.asm и WDTjnt__sleep_test.asm). О правильном построении малопотребляющих схем Правильно организовать работу схемы в режиме энергосбережения не так-то про- сто. Начнем с того, что в спящем режиме у контроллера отключается буфер вывода порта (т. е. входная логика), но в документации не поясняется, что при этом само состояние выводов остается неизменным. Потому, если к выводу подсоединен включенный светодиод, который потребляет 10 мА, то и после «засыпания» он будет гореть и потреблять те же 10 мА. Вы можете долго ловить блох (учитывать возможность дребезга на аналоговых входах, отключать аналоговый компаратор и BOD вместе с АЦП и т. п.) и при этом намного больше потерять из-за забытого в нулевом состоянии входа с подключенным внутренним подтягивающим резисто- ром— он ведь тоже так и останется подключенным. Забота о предотвращении таких ситуаций — целиком на совести программиста и разработчика схемы. Очевидный прием с переключением оптом всех выводов на вход может не дать нужного эффекта не только в случае подтягивающего резистора, но и в случае, если часть из них «висит в воздухе», — наводки будут переключать входную логику и тем самым повышать потребление. Да, я знаю, что отключение логики происходит автоматически при вводе в режим энергосбережения, но это действительно не для всех контактов: остаются включенными, например, выводы внешних прерываний, если они разрешены в программе, а также некоторые другие, если они используют- ся в режиме альтернативных функций. Если же при включенном входном буфере вывод микроконтроллера останется неподключенным, или на нем будет присутст- вовать аналоговый сигнал с напряжением, близким к половине питания, то буфер будет потреблять чрезмерный ток. Потому нужно внимательно просмотреть все режимы, в которых остаются выводы контроллера при вводе его в «сон». Кроме того, надо не забывать об остальных компонентах схемы. Некоторые микро- схемы (как, например, память серии АТ24) в неактивном режиме сами практически не потребляют тока. Со многими другими удается справиться, если они имеют режим sleep, инициируемый обычно через определенный уровень на отдельном выводе. Но надо не забывать, что бывают и неочевидные случаи. Так, продолжат по- треблять наши часы реального времени, если у них выход частоты SQW подключен к выводу RESET контроллера с подтягивающим резистором (см. рис. 13.3 в гла- ве 13), — ведь, в отличие от простого выключения, мы питание 5 вольт не снимаем.
384 Часть III. Практическое программирование микроконтроллеров AVR Заметки на полях Особенно беспечны бывают разработчики устройств, где имеется узел, работающий от батарейки, тогда как остальная часть схемы обесточена. Самый распространенный пример— компьютерная материнская плата с CMOS-часами. Там часы (несколько другой конструкции, чем наши DS1307) подключены к выводам управляющих логиче- ских микросхем. А обесточенная КМОП-логика, в отличие от нормального режима ра- боты, может потреблять через входы при наличии на них высокого уровня значитель- ный ток — он обусловлен защитными диодами, которые при отсутствии питания ока- зываются подключенными к низкому потенциалу в прямом направлении. Это приводит к ускоренной разрядке резервной батарейки, если вы компьютер долго не включаете. Нельзя сказать, что так случается со всеми материнскими платами (в ноутбуках, на- пример, я с этим вообще не сталкивался), но такую вероятность надо иметь в виду. В более очевидных случаях, когда компонент (например, аналоговый датчик) по- требляет много, а встроенной возможности отключения не имеет, питание этой части схемы можно отключать при уходе в «спящий» режим с помощью транзи- сторных ключей или реле (разумеется, ключи предпочтительнее, т. к. любое реле много потребляет само и долго включается). Но при таком способе надо учитывать, что многие датчики имеют достаточно большое время выхода на режим, которое нередко составляет несколько миллисекунд или десятков миллисекунд (датчик влажности серии НШ-4000 фирмы Honeywell — 70 мс), а в худших вариантах мо- жет достигать и секунд. Это время надо с запасом учитывать при такой организа- ции питания. Экономичный термометр на батарейках Давайте попробуем применить полученные сведения на практике — построим тер- мометр на датчике ТМР36 и дисплее МТ-10Т11, который мы рассматривали в пре- дыдущей главе (рис. 14.2). С точки зрения энергосбережения 12С-порт ничего нам напортить не может: хотя он и имеет «подтягивающие» резисторы, но основное время оба порта установлены на вход с «третьим» состоянием вывода, потому ни- чего не потребляют. Что немаловажно, это отключенное состояние соблюдается с обеих сторон, и ничего специально тут предпринимать не требуется. Датчик ТМР36 потребляет 50 мкА, и эта цифра еще может быть уменьшена в сто раз, если задействовать режим Shutdown, который доступен, если использовать вариант датчика в корпусе с планарными выводами. Мы таких корпусов не приме- няем, потому обойдемся обычным потреблением — нам этого хватит. Дисплей МТ- 10Т11 потребляет, как мы говорили, около 30 мкА. Программу этого термометра вы найдете в архиве по адресу, указанному во введе- нии (файл MT-10T11_TMP36.asm). Тактирование здесь предполагается от внешнего кварца 4 МГц по причине, о которой мы упоминали ранее: задержка выхода из «сна» при тактировании от внутреннего генератора может достигать 65 мс, что нам невыгодно. Если очень надо избавиться от кварца, то лучше снизить задержку, пе- реключив «фьюзы» sutiisuto в состояние 01. В момент пробуждения зажигается контрольный светодиод, подключенный к выводу РВО, перед уходом в «сон» мы его гасим, в результате светодиод будет коротко «подмигивать» раз в две секунды.
Глава 14. Режимы энергосбережения и сторожевой таймер 385 Перед выходом из процедуры расчета показаний датчика и вывода результатов не забываем полностью выключить АЦП (установкой всех битов регистра adcsra в нули) — включенный АЦП потребляет примерно 300-350 микроампер даже в по- кое, и в режиме «сна» они будут совершенно лишние. R1 5,1 к +5 В <* Г~Т- +5 В 4.7 (AOC5) PC5 28 (ADC4) PC4 27 (ADC3) PC3 26 (ADC2) PC2 25 (ADC1)PC1 24 (PC0)ADC0 23 GND 22 AREF 21 AVCC 20 SCK19 MISO 18 MOS117 (OC1B)PB216 (OC1A) PB1 15 ATmega8 T C3 XL 1+5 В 2 =±=04 C5 0,1 +5 В TMP36 Рис. 14.2. Экономичный термометр на дисплее МТ-10Т11 В части чтения датчика предлагаемая программа аналогична программе тестирова- ния прерывания АЦП из главы 11. Перевод в физические величины данных с дат- чика ТМР36 мы также уже обсуждали. Ориентировочные калибровочные коэффи- циенты можно вывести из указанных в этой главе условий: для датчика ТМР36 и опорного напряжения 2,56 вольта величина кода АЦП будет лежать в пределах от 40 до 600 с крутизной 4 единицы кода на 1 °С и значением при 0 °С, равном 200. Все эти величины приблизительные и должны быть уточнены при калибровке. Величина «подставки» ко, равная в нашем случае 2 о о, — это значение кода, при ко- тором характеристика «ломается»: выше его температура положительная, ниже — такая же, но отрицательная. Как показано в главе 11, мы сначала должны сравнить код с этой «подставкой». Если он больше или равен к0, то из величины кода «под- ставка» вычитается, если меньше — из «подставки» вычитается код. Полученный результат мы должны умножить на а\ — коэффициент крутизны, который вычис- ляется из второго условия: он в нашем случае будет равен 2,5 градуса на единицу кода. Чтобы совершить целочисленное умножение, поступим так, как рекомендуется в главе 8: передвинем это число в целочисленный диапазон, умножив его на подхо- дящую степень двойки. В нашем случае достаточно умножить на 256, получив
386 Часть III. Практическое программирование микроконтроллеров AVR коэффициент, равный 640,— после умножения и удаления младшего байта мы будем получать как раз трехзначное число, равное числу десятых долей градуса, — запятую мы выставляем вручную (если в этом рассуждении что-то непонятно, пе- речтите еще раз разд. «Операции с вещественными числами» главы S). Плохая подгонка под опорное напряжение (диапазон датчика составляет менее чет- верти от опорного напряжения) скажется в том, что разрешение нашего термометра не будет превышать 0,25 °С, и при возрастании, например, от 2 до 3 градусов мы на дисплее увидим ряд чисел: 2,0, 2,2, 2,5, 2,7, 3,0. Для улучшения разрешения в на- шем случае придется либо выбирать внешний опорный источник с напряжением около 1 вольта (например, AD1580), либо усиливать сигнал вдвое-втрое с помощью внешнего операционного усилителя. Ну, или перейти на «продвинутые» модели AVR, в которых опорное напряжение АЦП напрямую подается от встроенного опорного источника (ATmega88 и далее). Теоретически длительность рабочего состояния складывается из приблизительно 4 мс на пробуждение, 7 мс на считывание данных с осреднением и около 2 мс на вывод результатов: в сумме менее 15 мс. 15 мс от 2 секунд составит V133 часть, т. е. общее потребление схемы в активном состоянии, составляющее примерно 10 мА, нужно поделить на 133. К полученным 75 мкА надо прибавить потребление в ре- жиме «сна», которое, согласно моим измерениям, составляло 150 мкА, и потребле- ние дисплея с датчиком (30 + 50 = 80 мкА). Всего получается примерно 300 мА. В реальности от схемы термометра на макете с контроллером ATmega8A при пита- нии от источника 5 вольт мне удалось добиться потребления примерно в 350— 400 мкА, причем следует ожидать, что при работе от пониженного напряжения ба- тареек эта цифра еще снизится. Это позволит работать от трех батареек АА при- мерно в течение круглого года, но учтите, что ниже -10 градусов дисплей МТ-10Т11 все равно не работает, а батарейки при минусовых температурах сильно проседают в емкости. Потому эксплуатировать эту конструкцию зимой на улице может быть затруднительно. Можно пристроить к этому термометру и часы DS-1307, теоретических проблем для этого нет, и на экономичности это не скажется, даже улучшит ситуацию, т. к. мы сможем «будить» контроллер от прерывания SQW, выключив WatchDog и не тратя времени на полную загрузку. Но на семисегментных дисплеях без двоеточия время отображается плохо, потому от мысли добавить к температуре время здесь придется отказаться. К законченным часам с дисплеем мы обратимся в главе 16 при рассмотрении дис- плеев со встроенными контроллерами.
ГЛАВА 15 Программирование UART и обмен данными с персональным компьютером Все контроллеры AVR, кроме самых младших Tiny, содержат в себе модули асин- хронного приемопередатчика UART или синхронно-асинхронного USART. При работе USART в асинхронном режиме он ничем не отличается от обычного UART (и по умолчанию дополнительные функции USART отключены). В дальнейшем, чтобы не путаться, мы будем обобщенно называть этот модуль UART, подчерки- вая, что речь идет об асинхронном режиме обмена данными. Дополнительные функции расширенного порта USART в теории должны приблизить этот интерфейс к SPI и TWI, позволяя организовать обмен данными многих устройств между со- бой, но на практике эти функции так и не прижились, и в огромном большинстве случаев этот порт эксплуатируется как простой UART. Как уже говорилось ранее (см. главу 5), за десятилетия существования этот интер- фейс «вылизан» до возможного идеала. UART— самый беспроблемный из всех интерфейсов, и если вы его правильно включаете, то он никогда не сбоит и не отка- зывает без существенных поводов. По сути, принципиальное ограничение на ис- пользование UART только одно: в силу своей асинхронной природы он требует «кварцованной» частоты. Использование UART при тактировании контроллера от встроенного RC-генератора в принципе еще возможно при очень низких скоростях передачи (600-1200 битов в секунду), но и в этом варианте от сбоев вы не застра- хованы. А при стандартной для наших целей скорости 9600 тактирование возможно только от внешнего кварца, причем желательно не менее 2-4 МГц (подробнее об этом далее). Заметки на полях UART— основной канал для связи контроллеров AVR с внешним миром. Большая часть последовательных интерфейсов, как проводных, так и беспроводных (RS-232, RS-485, USB, Wi-Fi, Bluetooth и т. п.), все равно в конечном итоге чаще всего сводится именно к использованию UART с дополнением в виде того или иного аппаратного адаптера. Причем нередко эти адаптеры бывают устроены намного сложнее, чем AVR, и обладают развитой собственной функциональностью, так что в таких случаях возникает вопрос: а зачем там AVR вообще нужен? Например, с помощью модулей Wi-Fi на основе популярной микросхемы ESP8266 вполне можно построить беспро-
388 Часть III. Практическое программирование микроконтроллеров AVR водную метеостанцию, не привлекая никаких других контроллеров. Потому такие слу- чаи нам здесь не очень интересны, и мы рассмотрим только то, что непосредственно касается функций UART. Мы также уже упоминали в главе 3, что интерфейс UART двухпроводный (не счи- тая «земли»), причем линии имеют разное назначение: одна (TxD) — для передачи данных от модуля, другая (RxD) — для приема данных в модуле. Отметим, что не- смотря на это, регистры данных у модуля UART и на прием, и на передачу распо- ложены по одному адресу, который носит наименование udr или udrx, где к — О, 1,..., если модулей UART более одного (в «наших» контроллерах модуль всего один, потому этот регистр всегда будет именоваться udr). Однако физически реги- стры данных приемника и передатчика разделены. Кроме того, эти регистры по адресу udr являются лишь буферами, а собственно передача/прием ведется с по- мощью отдельных сдвиговых регистров. Потому интерфейс UART считается пол- нодуплексным, т. е. в каждый момент времени может вестись передача и прием данных одновременно, хотя пишутся и читаются эти данные по одному адресу. В протоколе UART (см. рис. 3.2 в главе 3) не предусматривается никаких особых состояний «Старт» или «Стоп», как в 12С, — просто каждая посылка всегда сопро- вождается стартовым и стоповым битами для синхронизации (при оговоренной за- ранее скорости обмена). Следующая посылка может прийти через произвольный промежуток времени, потому протокол и называется асинхронным. Посылка в большинстве случаев состоит из 8 битов (как обычный байт), но может быть и 9-битовой (с битом контроля четности), а в модулях USART ее длина может со- ставлять от 5 до 9 битов. В промежутке между посылками линия TxD (если UART инициализирован) нахо- дится в состоянии логической единицы, а на линии RxD должна присутствовать логическая единица, установленная внешним передатчиком. Поэтому, если UART включен, не рекомендуется эти линии использовать еще для каких-то функций, иначе приемопередатчики могут вас «неправильно понять». Исключением является ситуация, когда по этому интерфейсу контроллеры обмениваются данными друг с другом, — обычно предполагается, что линии RxD и TxD подсоединены к адап- теру, конвертирующему логические уровни UART в сигналы соответствующего интерфейса (например, RS-232). Этим определяется основное назначение UART, в отличие от SPI или 12С — связь удаленных устройств между собой. Давайте сна- чала остановимся на том, как организовать обмен данными с персональным ком- пьютером, т. к. отлаживать прием и передачу с помощью собственно UART удоб- нее в режиме двухстороннего обмена. Но прежде всего следует затвердить главное правило при любом соединении UART или RS232-nopTOB между собой: Линии передачи и приема всегда соединяются перекрестно! То есть RxD (RX) с одной (первой) стороны подключается к TxD (TX) с другой (второй) стороны, и наоборот. Никаких исключений из этого правила не существу- ет — применение Arduino в качестве USB-адаптера (см. главу 4) противоречит это- му правилу лишь по видимости, т. к. перекрестное соединение уже выполнено внутри платы Arduino.
Глава 15. Программирование UARTu обмен данными с персональным компьютером 389 Способы обмена данными с ПК Подобрать адаптер и подключить его для простого обмена данными с персональ- ным компьютером (ПК) совсем не сложно — нам здесь требуется всего две линии: RxD и TxD, никаких дополнительных выводов RS-232 не задействовано. Поэтому адаптер годится абсолютно любой, лишь бы он воспринимал 5-вольтовые уровни и был правильно промаркирован в отношении линий RX и ТХ (а то, по слухам, среди продукции китайских товарищей одно время попадались адаптеры, в которых с контроллером необходимо было соединять одноименные выводы). Самый простой способ— применить один из адаптеров USB-UART (USB-TTL), описанных в главе 4, Как вариант, можно использовать и более простой, и даже са- модельный преобразователь UART-COM (UART-RS232), но для него понадобится дополнить ПК вставной PCI-платой с СОМ-портами. Популярные одно время ка- бель-адаптеры, содержащие встроенный преобразователь USB-COM или USB- UART, почему-то работали из рук вон плохо. Адаптеры USB-UART, кроме сигнальных выводов и общего провода GND, обычно имеют свой вывод питания. В отличие от ISP-программаторов, питающихся от про- граммируемой схемы, этот вывод адаптеров подключать к вашей схеме не следует. Он подключен к питанию USB-шины (иногда через промежуточные стабилизато- ры), а у вашей схемы есть собственное питание, и они будут конфликтовать с не- предсказуемым исходом, о чем уже упоминалось в главе 4. Внимание! USB-UART-адаптеры подключаются к схеме только сигнальными проводниками RX и ТХ, а также общим проводом, выводы питания соединять нельзя! Преимущество второго, более сложного способа — с оборудованием современного ПК настоящими СОМ-портами, также уже упоминалось применительно к ISP- программаторам (см. главу 5) и состоит в том, что для аппаратного СОМ-порта не требуется программный Windows-драйвер. Следовательно, два таких адаптера, включенные в ПК одновременно, по крайней мере в теории никак не могут кон- фликтовать друг с другом, и можно без опаски налаживать одновременный обмен по двум портам. Как мы говорили, это важно в случае отладки программ с по- мощью UART, когда одновременно с адаптером подключен и ISP-программатор. Поэтому в случае ноутбуков и прочих всяких mini-PC, которые снабдить дополни- тельными портами невозможно, при отладке программ с UART-обменом приходит- ся чаще всего их подключать попеременно: загружать программу при отключенном UART и проверять обмен при отключенном программаторе. Правила техники безопасности при подключении к ПК При использовании аппаратных СОМ-портов на ПК следует учесть, что, в отличие от USB, «горячего» подключения в общем случае СОМ-порт не допускает. Под- ключать к ПК адаптеры, особенно самодельные, можно только при выключенном питании с обеих сторон. Конечно, выключать ПК, а потом ждать его загрузки каж-
390 Часть III. Практическое программирование микроконтроллеров AVR дый раз, когда потребуется отключить программатор или 118232-адаптер, невоз- можно и не нужно. ISP-программатор с Л8232-интерфейсом можно отключать от тестируемой платы, а не от СОМ-порта, причем во всех случаях следует подклю- чать к макету первым и отключать от макета последним общий провод GND (опе- рация носит название «выравнивание земель»). Но программируемая или тести- руемая схема обычно не подключена ни к какому потенциалу, гальванически свя- занному с бытовой сетью или электротехнической землей (т. е. «третьим» проводом в розетке), потому обычно все подобные операции проходят без послед- ствий. Подробности В любом сигнальном кабеле, в том числе и в фирменных удлинителях RS-232 или нуль-модемных (связных) кабелях, экранирующая оболочка никогда не является токо- ведущей жилой, хотя и формально имеет потенциал «земли». Общий провод идет от- дельно, а «экран» по правилам должен соединяться с ним только в одной точке с од- ной стороны парного соединения. В этом случае его экранирующие способности мак- симальны. На практике общий провод коммуникационного порта соединен с корпусом компьютера (а следовательно, и с «экраном») где-то в источнике питания. При под- ключении к схеме с батарейным питанием или с изолированным от электрической сети источником вся конструкция оказывается «подвешенной» к потенциалу корпуса компьютера. Наихудший случай представляет собой соединение двух компьютеров, у которых не выровнены потенциалы корпусов: «горячее» соединение СОМ-портов ведет в этом случае к их почти гарантированному выходу из строя (контакты «земель» или «корпусов» могут войти в соприкосновение первыми только случайно). Потому всегда стоит озаботиться тем, чтобы розетки в помещении были оснащены контактом «земли», или эти «земли» у них, как минимум, были бы соединены между собой, если уж настоящее заземление в здании отсутствует. Неприятности начинаются, если положение «подвешенности» схемы относительно электротехнической «земли» нарушается либо вынужденно (как, например, в про- стейших схемах регулирования сетевой нагрузки), либо случайно (общий провод оказался замкнутым на какую-то массу, связанную с электротехнической «зем- лей»). В этом случае нарушение порядка подключения, когда первыми входят в контакт сигнальные проводники, а не общий провод, почти гарантированно при- ведет порт к выходу из строя. Причем «горит» всегда именно порт со стороны ПК, который починить гораздо сложнее (если это вообще возможно), чем заменить адаптер или программатор. У вас может фокус с «горячим» подключением СОМ- порта пройти раз сто безболезненно, но на сто первый вы все-таки нарветесь. С ав- тором этой книги такое произошло только один раз в жизни, но запомнилось надолго — мне пришлось тогда компенсировать покупку нового ноутбука редкой мини-разновидности. USB в этом отношении намного безопаснее, по крайней мере в отношении сохран- ности портов, т. к. USB-разъемы спроектированы так, что общий провод (металли- ческое обрамление разъема) всегда входит в контакт первым. Но с подключением по любому проводному интерфейсу, в том числе и USB, связана еще и другая опас- ная особенность— корпус ПК оказывается соединенным с «землей» вашего уст- ройства. А если персональный компьютер подключен к сети вилкой без надежного заземления (как чаще всего и бывает, ибо если у вас в розетке присутствует на-
Глава 15. Программирование UART и обмен данными с персональным компьютером 391 стоящее заземление, то вам очень повезло), то у него на корпусе всегда висит опре- деленный потенциал, равный примерно половине сетевого питания, т. е. около 110- 120 вольт переменного тока. Спасти от различных неприятностей, которые потен- циально в этом кроются, может только адаптер с оптической развязкой сигнальных цепей, когда «земли» с обеих сторон изолированы друг от друга. В любительской практике оптическая развязка— редкость, и обычно ее отсутствие не причиняет проблем по крайней мере в случае USB, — слишком много «если» должно совпасть одновременно, чтобы возникли неприятности. А вот в аппаратуре бытового, осо- бенно медицинского назначения, требование опторазвязки в USB- или СОМ- адаптерах должно соблюдаться неукоснительно, причем вне зависимости от того, используете вы готовую плату Arduino или собственноручно спаянную конструк- цию. Программы для связи ПК с контроллером Адаптер, как мы говорили, подобрать просто, но на этом проблемы не заканчива- ются. Куда сложнее подобрать программу-монитор, с помощью которой можно общаться со стороны ПК с контроллером. Монитор порта Arduino для этой цели годится только теоретически: он воспринимает данные лишь в виде текстовых сим- волов и символьных строк (прозрачное для пользователя преобразование чисел в текст и обратно в Arduino осуществляют функции класса serial). Если посылать на монитор порта обычные числа, то вы получите полную несуразицу, и тем более это касается отправки со стороны ПК в контроллер. В определенных задачах это бывает даже удобно, но класса Serial у нас нет, так что в этом случае приходится конвертацию чисел в символы и наоборот осуществлять внутри контроллера. Далее мы соорудим программу установки часов DS1307, специально адаптирован- ную под монитор порта, т. к. с его помощью установку делать проще и не требует никаких дополнительных настроек. Но в общем случае нам со стороны ПК требу- ется совсем не это. Мы хотим посылать числа «as is», причем побайтно, и получать на экране уже готовое число в текстовой форме в одной из заданных разновидно- стей: как минимум, в обычном десятичном или в шестнадцатеричном виде. Бывает полезно и отобразить текстовые символы, если контроллер посылает именно тек- стовое сообщение. Неплохо сохранять обмен в лог-файле, а также иметь возмож- ность копирования результатов с экрана. Обязательной функцией должна быть возможность остановки приема без закрытия программы — это полезно, когда ва- лится большое количество данных, и вы не успеваете их рассмотреть. Обязателен также весь набор возможных номеров СОМ-портов со всеми стандартными скоро- стями, начиная по крайней мере от 1200 (чего, кстати, не допускает монитор порта Arduino). А вот такие функции, как 9-битовый обмен или подсчет контрольной суммы, для наших целей, в общем, необязательны. Различных программ для работы с СОМ-портом существует огромное количество, но указанным требованиям соответствуют считанные единицы. Особенно неприят- но то, что большинство подобных программ представляют собой либо жутких мон- стров, собравших в себе все функции всех возможных коммуникационных портов, либо специализированные утилиты, «заточенные» под какую-то частную задачу.
392 Часть III. Практическое программирование микроконтроллеров AVR Вы можете сами развлечься поиском и испытанием разных вариантов этих про- грамм, а я для себя давно написал свою собственную, назвав ее СОМ2000. Прото- типом ей послужила когда-то существовавшая в составе пакета Norton Commander для DOS программа эмулятора терминала под названием Term90, почти идеально подходившая для подобных целей. Программу СОМ2000 можно скачать с моего сайта по адресу http://revich.lib.ru/ comcom.zip. Устанавливать ничего не требуется — просто распакуйте содержащий два файла архив в любую папку. Сама программа содержится в файле com2000.exe. Файл помощи help2000.htm можно открыть как изнутри программы (через меню со знаком вопроса или клавишей <F1>), так и обычным способом в браузере, что удобнее. Собственно, в этом файле все о программе рассказано. Заключается файл помощи двумя полезными табличками кодирования: для основных символов ASCII и русских символов в различных кодировках. Большую часть испытаний и отладки программ для этой книги я провел именно с помощью СОМ2000. Тем, кого эта программа не устроит по каким-то причинам, могут посоветовать программу Realterm, которая также ориентирована на работу с микроконтроллера- ми, но содержит много отсутствующих в моей программе возможностей. Из-за это- го она сложнее в начальной настройке, и я сам ей пользуюсь редко. Страничка раз- работчиков Realterm находится по адресу https://realterm.sourceforge.io, но про- грамма доступна из множества других источников. Если будете ее пробовать, скачивайте версию не выше 2.0.0.70 — как это часто бывает, далее разработчики увлеклись наполнением программы всякими дополнительными функциями, отчего пользоваться и без того перегруженной настройками программой стало совсем не- возможно. В любой подобной коммуникационной программе требуется указывать номер СОМ-порта, через который будет осуществляться связь. Если вы подключаете адаптер UART-COM к аппаратному СОМ-порту, то уже знаете номер порта, к ко- торому его подключили, — этот порт и надо потом указывать в программе. Адап- теры USB-UART создают себе виртуальный СОМ-порт в момент подключения к ПК. Самый надежный способ его узнать — запустить Диспетчер устройств из па- нели управления Windows и обратиться к разделу Порты (СОМ и LPT). Многие коммуникационные программы (в том числе и Arduino IDE, из которой запускается монитор порта) могут определять существующие СОМ-порты, из списка которых остается только выбрать нужный. Дистанционная связь через UART Обмен данными через UART — самый простой и надежный способ связи между двумя устройствами. Организовать проводную связь между двумя контроллерами в пределах одного прибора или на небольшом расстоянии — до одного-двух мет- ров — можно простым соединением сигнальных линий UART. Для больших рас- стояний и был придуман интерфейс RS-232, а также его более надежные разновид- ности (RS-485, RS-422), и с их помощью тщательно продуманная схема может обеспечить проводную связь между устройствами на расстояниях в километры.
Глава 15. Программирование UART и обмен данными с персональным компьютером 393 А нельзя ли соединить контроллеры по UART с помощью беспроводной связи, причем не прибегая к сложным и избыточным решениям вроде модулей Wi-Fi? Оказывается, это совсем несложно, и для этого ничего сверхъестественного не тре- буется. Для подключения по беспроводному каналу есть много разных способов, из которых можно выбрать те, что не требуют каких-либо специальных ухищре- ний,— получается просто радиоудлинитель UART, который никак не влияет на сам процесс обмена. Заметки на полях Насколько мне известно, первым подобное решение выставила фирма Digi в виде мо- дулей ХЬее. Модули получили широкое распространение, но на поверку это оказалось не лучшим вариантом. Во-первых, частота 2,4 ГГц (та же самая, на которой работают Wi-Fi и Bluetooth) плохо подходит для цели простого удлинения UART — она избыточ- на с точки зрения информационной емкости, и в то же время обладает малым радиу- сом действия и плохой проникающей способностью (грубо говоря, чем ниже частота радиоволны, тем ее радиус действия будет выше). Во-вторых, сами Xbee-модули ока- зались дорогими, ненадежными и капризными в настройке. Все это привело к тому, что их вытеснили альтернативные решения, которые если и не намного дешевле, то гораздо лучше с точки зрения эксплуатационных качеств. Одним из наилучших вариантов для организации такого обмена могут служить мо- дули под названием МВее-868, работающие на частоте 868 МГц. Настроенные мо- дули напрямую подключаются к UART контроллера, только следует учесть, что номинальное питание у них 3,3 вольта (1,8-3,6 В), так что при пятивольтовом пи- тании контроллера требуется какой-то преобразователь уровня: либо специализи- рованная микросхема (вроде SN74LVC1T45 или ADG3301), либо хотя бы простой резистивный делитель по входу RX со стороны модуля (подробнее об этом расска- зано в главе 4). Для настройки потребуется еще специальный преобразователь UART-USB MB-USBridge, так что в целом комплект из двух модулей и преобразо- вателя обойдется достаточно дорого. Но эта дороговизна с лихвой окупается ис- ключительно внятной и обильной документацией на русском языке, в которой рас- смотрены все возможные случаи подключения и настройки (скачать документацию можно, например, с сайта известного интернет-магазина «Чип и дип»). Согласно уверениям разработчиков, на открытом пространстве модули МВее-868 при не слишком большой скорости обмена (1200 битов в секунду) могут обеспе- чить дальность до 4 километров. Разумеется, для таких экстремальных дальностей необходимо приобретать направленные антенны диапазона 900 МГц (со стандарт- ным для таких случаев разъемом SMA). Ну а для нескольких десятков метров хва- тит и отрезка обычного жесткого — одножильного! — провода диаметром 0,5 мм и длиной 34-35 см, вставленного в гнездо SMA-разъема на модуле. Немаловажно и то, что модули имеют специальный вывод для включения режима энергосбереже- ния, что позволяет с их помощью строить дистанционные датчики на батарейках. Управление режимом Sleep, вероятно, единственная функция, которую приходится специально включать со стороны контроллера, в остальном же модули МВее-868 могут работать совершенно прозрачно для пользователя.
394 Часть III. Практическое программирование микроконтроллеров AVR Программирование UART Перед использованием UART его нужно включить, установить в нужный режим, а также задать скорость обмена. В листинге 15.1 приведена простейшая последова- тельность действий для современных типов контроллеров, содержащих модуль USART (вы с ней уже знакомы по случаям применения UART в предыдущих гла- вах). ;асинхронный режим UART, тактовая частота 4 МГц ldi temp,25 /скорость передачи 9600 при 4 МГц BAUD=25 out UBRRL, tetnp ldi temp, (1«RXEN) I (1«TXEN) /разрешение приема /передачи out UCSRB,temp Число ubrr для делителя частоты, размещающегося в регистрах ubrrhiubrrl (в на- шем случае— 25), определяется из таблиц, которые имеются в описании соответ- ствующего контроллера (там же приводится и ошибка для выбранного значения частоты), или рассчитывается, исходя из заданной скорости обмена BAUD: BAUD =/pe3/16(UBRR + 1) В старшей части ubrrh, неиспользованной в нашем случае, работают только млад- шие четыре разряда, итого значение ubrr в расширенном модуле USART может быть 12-разрядным (т. е. от 0 до 4095). На практике в большинстве случаев доста- точно устанавливать значение лишь младшего регистра (ubrrl) — например, при тактовой частоте 4 МГц его максимально возможное значение 255 даст нестандарт- ную скорость обмена 976 бит/с (ближайшая стандартная 1200 обеспечивается зна- чением UBRR, раВНЫМ 208). Подробности Во всех описаниях AVR-контроллеров есть раздел под названием «Examples of Baud Rate Setting», в котором приводятся таблицы необходимых значений ubrr для стан- дартных скоростей обмена в зависимости от тактовой частоты контроллера, потому подсчитывать что-то по приведенной формуле самостоятельно необходимо лишь в случае использования кварца, частота которого в таблицах отсутствует. Из этих таблиц можно также узнать максимальную скорость обмена, доступную при заданной тактовой частоте. Приемлемы все значения, при которых ошибка составляет 0,2% и менее (в таблицах они выделены жирным шрифтом). Так мы можем узнать, что при «круглых» значениях тактовой частоты (1 МГц, 2 МГц, 4 МГц и т. д.) точные значения скорости обмена получаются далеко не во всех случаях. Для тактовой частоты 1,000 МГц в обычном режиме максимальная скорость обмена составит 4800, для 4,000 МГц— 19 200 (а для «ардуиновских» 16 МГц, между прочим, не более 76 800). Как видно из таблиц, эти ограничения в ряде случаев можно обойти, если дополни- тельно установить специальный бит и2х (он находится в регистре ucsra), удваивающий скорость обмена. Таким образом для 1,000 МГц можно достичь привычного значения 9600, а для 4,000 МГц повысить максимальную скорость до 38 400. Кроме того, можно видеть, что для «круглых» значений тактовой частоты, начиная с 2 МГц, работают не-
Глава 15. Программирование UARTи обмен данными с персональным компьютером 395 стандартизированные повышенные скорости обмена от 250 килобит в секунду. Но бу- дут ли на таких скоростях корректно работать адаптеры, соединительные кабели и все прочее, — это большой вопрос, потому в обычных условиях их никто не применяет. USART по умолчанию совместим с самым простым UART (формат кадра 8nl) и везде устроен одинаково, потому в любых контроллерах приведенные процедуры инициализации заработают. Единственное, что может потребоваться, — замена имен регистров (в старших моделях портов может быть несколько, потому к реги- страм прибавляются номера о, 1, 2 и т. д.). И, к сожалению, для старших моделей (ATmega88/l68/328) регистры USART размещены в области memory mapped, пото- му для них придется менять способ загрузки значений (см. главу 7). И у собственно UART, и тем более у расширенного USART, есть еще много всяких разных настроек, которые мы здесь не рассматриваем. Единственное, что стоит рассмотреть дополнительно, — прерывания USART, которых аж три штуки: «при- ем завершен» (RX Complete), «регистр данных передатчика пуст» (ТХ UDR Empty), а также «передача завершена» (ТХ Complete). Все эти прерывания разрешаются специальными битами: rxcie, udrie и txcie соответственно, в том же регистре ucsrb, потому их можно подключать одной командой совместно с разрешением приема/передачи. Последние два прерывания: «регистр данных передатчика пуст» и «передача завершена» — в известной степени дублируют друг друга, только воз- никают в разное время: «регистр данных передатчика пуст» возникает сразу, как только текущие данные из буфера передатчика UDR переданы в регистр передат- чика (т. е. в момент начала передачи), а «передача завершена» — в момент оконча- ния передачи, когда регистр передатчика опустеет. Порядок и целесообразность применения этих прерываний в различных случаях мы рассмотрим на конкретных примерах. Примеры использования UART в разных режимах Пример простейшего приема и отправки без использования прерываний приведен в листинге 15.2. Здесь принятый байт сразу же посылается обратно. /Простой прием и отправка байта через UART .device ATmega8 .include flm8def.inc" .def temp = rl6 /рабочий регистр .org 0 rjmp Reset out_com: /посылка байта из temp с ожид. готовности sbis UCSRA,UDRE ;ждем готовности буфера передатчика rjmp out_com out UDR,temp ;отсылаем ret
396 Часть III. Практическое программирование микроконтроллеров AVR in_com: ;прием байта в temp с ожид. готовности sbis UCSRA,RXC ;ждем готовности буфера приемника rjmp in_com in temp,UDR /принимаем ret Reset: ldi temp,low(RAMEND) /указатель стека out SPL,temp ldi temp,high(RAMEND) out SPH,temp ;=== UART ldi temp,25 ;9600 при 4 МГц out UBRRL,temp ;скор. передачи ldi temp, (1«RXEN|1«TXEN) out UCSRB,temp ;разреш. приема/передачи 8 битов Gcykle: rcall in_com ;ждем любого байта г call out_com /отсылаем обратно rjmp Gcykle ;в начало цикла Эту программу вы найдете в архиве по адресу, указанному во введении (файл UART__test__simple.asm). Загрузите ее в какой-нибудь контроллер из «наших» — не- обязательно в указанный ATmega8 (для ATtiny2313 придется удалить строки, отно- сящиеся к регистру SPH). Подключите адаптер USB-UART, запустите на компью- тере любой монитор порта (можно даже монитор порта Arduino) и попробуйте по- слать какой-нибудь байт в контроллер— он вернется обратно без изменений. Давайте рассмотрим подробнее, что тут происходит, и в каких случаях такими про- стыми мерами можно обойтись. Непрерывный опрос бита rxc, реализованный в процедуре приема incom, равноси- лен просто замкнутому циклу. При размещении такой простейшей процедуры приема в главном цикле контроллер на ней тормозится и зацикливается в ожидании байта, который должен прийти на UART. Вклиниться в этот цикл может только прерывание, причем и дня прерывания, и для приема это в большинстве случаев безопасно — с принятым байтом в регистре udr ничего не случится, если посторон- нее прерывание не выполняется дольше миллисекунды (порядка тысяч команд кон- троллера). Но даже если обработчик прерывания выполняется ну очень долго (на- пример, выводит данные на LCD-дисплей, — см. главу 16), то потерять что-то вы рискуете только если «снаружи» послано более одного байта. Один принятый байт вы не сможете потерять ни в каком случае, когда бы ни обратились к проверке бита rxc. Единственное ограничение при наличии посторонних прерываний мы рассмот- рим далее. • С процедурой outcom вы также знакомы по программам в предыдущих главах. Передачу модуль UART осуществляет аппаратно в два приема: как только данные
Глава 15. Программирование UART и обмен данными с персональным компьютером 397 записаны вами в буфер передатчика по адресу udr, они автоматически передаются в сдвиговый регистр передатчика и начинается собственно передача. В этот момент буфер передатчика уже свободен для записи новых данных. Если при этом было включено прерывание готовности буфера (о чем рассказано далее), то оно возника- ет сразу же, как только вы записали первые данные. Иными словами, независимо от того, включено ли это прерывание, или запись данных происходит по «ручному» опросу состояния бита udre как в процедуре outcom, передача двух байтов подряд происходит, с точки зрения программы, непосредственно друг за другом (с задерж- кой в 4 или, в случае использования прерывания, 8 тактов, необходимых для вы- полнения соответствующих действий). То есть при одном-двух посылаемых байтах отсылка через UART любым способом практически не задержит выполнение про- граммы. Важная особенность процедур incom и outcom — то, что их можно вызывать в лю- бой момент в любом месте программы, внутри обработчиков прерываний или вне их, безразлично (более того, в прерываниях это даже безопаснее, — см. врезку «Подробности» далее). А так как отсылка одного-двух байтов почти не нарушает течения основной программы, то процедуру outcom удобно применять при отладке программного кода, временно вставляя ее в те места кода, где необходимо прокон- тролировать содержимое того или иного регистра. Нужный регистр перегружается в temp, и сразу вызывается outcom. Программа с такой добавкой в большинстве случаев будет работать как обычно, только в нужный момент в UART будет выда- ваться значение контролируемого байта (в отличие, между прочим, от штатных средств отладки в Atmel Studio или Proteus, которые капитально нарушают выпол- нение исследуемой программы в реальном времени). Подробности Наличие внешних прерываний меняет дело только в одном отношении: экономя реги- стры, мы в этой простейшей программе осуществляем прием и передачу через рабо- чий регистр temp. А он у нас задействован практически везде, и при наличии посторон- них прерываний может быть испорчен, если одно из них вклинится между приемом и отсылкой. Поэтому при размещении простейших процедур incom и outcom в главном цикле, возможно, потребуется заменить temp на какой-то другой регистр, который в прерываниях не используется. Об этом не приходится заботиться, если сами эти процедуры вызываются изнутри прерывания, — тогда можно оставить temp, потому что внутри прерывания он случайно испорчен быть не может. Недостаток у такой организации обмена тоже имеется — он состоит в том, что в момент перезагрузки контроллера, подключения-отключения адаптера от схемы и при других подобных действиях контроллер склонен осуществлять фиктивный об- мен, принимая и отправляя несанкционированные байты с нулевым значением или, реже, со значением $ff. Если такая ситуация может что-то нарушить в функциони- ровании контроллера или внешней программы, то следует принимать меры для фильтрации приема произвольных значений. Можно придумать разные способы избежать такой ситуации (например, очищать регистр udr при выходе из сброса и т. п.), но самый простой и надежный из них — посылать/принимать не один байт, а два, первый из которых является командой с определенным фиксированным зна-
398 Часть III. Практическое программирование микроконтроллеров AVR чением. Команда служит для распознавания того, что следующим придет байт, с которым нужно производить конкретную операцию. Теперь о том, когда и какие прерывания UART применять целесообразно. Вариан- тов постановки задачи тут тьма-тьмущая, все случаи, когда именно вот такой вари- ант выгоден и необходим, мы перебрать все равно не в состоянии, потому опишем только пару-тройку самых распространенных. Канонический вариант, при котором применяются прерывания и на прием, и на передачу, максимально разгружает про- грамму, основной цикл при этом можно оставить пустым (см. файл UART_test_ RX_TXjnt.asm в архиве по адресу, указанному во введении). Здесь мы ждем от ком- пьютера байта конкретной команды — в нашем случае символа q (question), что равносильно десятичному числу 81 (разыщите в Интернете таблицу символов ACSII, чтобы узнать соответствие символов и их кодов). Если такая команда при- шла, мы выдаем наружу строку «Hello all!» («Привет всем!»), которую заранее за- гружаем в SRAM. На прием задействовано прерывание RX Complete («прием окон- чен»), а на передачу — прерывание ТХ UDR Empty («регистр данных передатчика пуст»). Этот пример описывает ситуацию, когда байт команды может прийти «сверху» в любое время, и мы должны тут же отослать обратно целую строку, причем не затормаживая остальные функции программы (типичный случай — значения теку- щего времени и даты из часов в то время, когда программа продолжает их перио- дическое чтение и вывод на индикацию). Если два байта подряд отправятся без за- держки в любом случае, то третий и последующий байты при скорости 9600 будут передаваться уже с задержкой примерно в 1 миллисекунду перед отсылкой каж- дого, и не тормозить программу можно только с использованием прерывания пере- дачи. В реальности могут быть промежуточные случаи — так, в длинной и громоздкой программе часов с красивым форматированным выводом на строчный дисплей (она рассмотрена в главе 16) основной цикл у нас, тем не менее, остался совершенно пустым. И мы вполне можем вставить в него простую процедуру incom из первого примера для приема внешних команд по ходу дела — т. к. больше байта за один раз мы не ждем, то и потерять ничего не рискуем. А отправлять значения часов в про- грамму можно уже с помощью прерывания освобождения регистра данных, как во втором примере. Эта ситуация моделируется в файле UART_test_TXJnt.asm из архива, расположенного по адресу, указанному во введении. И еще два случая, когда целесообразно применять прерывание только на прием. Первый случай — обратный предыдущему примеру (файл UART_test_RXJnt_vlasm из архива, расположенного по адресу, указанному во введении). Тут команда прини- мается своевременно и без задержек, а время отправки нас не лимитирует (напри- мер, потому что происходит крайне редко). Основной цикл мы тут не трогаем, а все выполняем в прерывании приема. И последний случай представляет файл UARTJest_RX_jnt_v2.asm из архива, располо- женного по адресу, указанному во введении. Он порадует сторонников концепции, что в прерываниях нужно делать минимум необходимого, а потом как можно быст- рее сбежать в основной цикл. Здесь мы по прерыванию приема только принимаем
Глава 15. Программирование UART и обмен данными с персональным компьютером 399 байт, а анализ его для выявления команды и собственно отправку строки произво- дим не спеша в основном цикле. Пример использования подобного подхода — про- грамма тестирования управления серводвигателем, рассмотренная в разд. «Управ- ление серводвигателем» главы 16, только там принятый байт анализируется не в основном цикле, а в других прерываниях. К этому случаю относится замечание по поводу возможной порчи регистра temp в других прерываниях, потому в примере он для наглядности заменен на регистр command, который после распознавания команды мы больше не используем. Но не забывайте, что в этом случае вы все равно посланный байт не получите, если в это время обрабатывается другое постороннее прерывание, до тех пор, пока его обра- ботка не закончится. И может быть надежнее зациклить контроллер в ожидании этого позарез необходимого байта, чем усложнять логику программы лишними прерываниями. По приведенным здесь соображениям в программах этой книги вы использования прерываний UART почти не встретите. Самая громоздкая здесь программа по передаче через UART— установка часов DS1307, когда туда-сюда передаются по 7-8 байтов (она рассмотрена далее). И даже если эти процедуры использовать в другой программе как составную часть, то все равно обращаться к ним придется далеко не каждый месяц. И при надлежащей организации такого обращения основ- ная функциональность программы не будет нарушаться. Рассмотрим подробнее фильтрацию произвольного значения в момент запуска на примере упомянутой программы тестирования управления серводвигателем (см. разд. «Управление серводвигателем» главы 16). Там используется прерывание RX на прием значения угла поворота. В прерывании просто принимается один байт: In_Rx: ;прием окончен in degree_b,UDR /принимаем байт угла поворота reti Предположим, что мы хотим установить какое-то отличное от нуля значение угла по умолчанию, загрузив его заранее в переменную degreeb. В существующей про- грамме ничего не выйдет, т. к. при включении контроллера (или при подключении- отключении адаптера UART) через UART будет принят несанкционированный байт с нулевым значением, которое заменит в переменной degreeb любое ранее существовавшее. Потому следует модифицировать программу так, чтобы заменять значение угла только в случае, если перед этим была легитимная команда, на такую замену. При приеме через прерывание RX это можно организовать, например, так (значение команды не должно пересекаться с множеством значений углов, иначе для хранения команды нужно отвести отдельный регистр): In_Rx: ;прием окончен cpi degree_b,$EE ;если ранее была команда со значением $ЕЕ brne in_command in degree_b,UDR ;то принимаем новый байт угла поворота reti
400 Часть III. Практическое программирование микроконтроллеров AVR in_command: in degree_b,UDR /иначе принимаем байт команды reti По приему последовательности из двух байтов подряд, первый из которых равен $ее, запишется новое значение угла. Без прерываний то же самое выглядит нагляд- нее, и значение команды может быть любым без траты лишних регистров: G_cykle: rcall in_com ;прием байтов в temp с ожиданием cpi temp, $A0 ; в temp команда $А0 ? brne G_cykle ;иначе возврат к опросу rcall in_com ;прием байтов в temp с ожиданием mov degree_b,temp ; принимаем байт угла поворота Вывод и ввод символов через UART В Arduino операции форматированного вывода функция serial.print о делает прозрачно для пользователя (иногда даже чересчур прозрачно, так что добиться нужного формата вывода бывает нелегко). Здесь мы, как обычно, все вынуждены «делать ручками», зато и владеем ситуацией полностью. Самое простое — послать через UART одну десятичную цифру в виде ее символа. Как можно узнать из таблицы ASCII, для этого к значению цифры нужно приба- вить число 48 ($30). При приеме, соответственно, от полученного кода надо отнять то же самое число, получив значение цифры. Когда мы переходим к числам в не- сколько десятичных разрядов, то поступать приходится точно так же, как при вы- воде чисел на дисплей: переводить их в распакованную BCD-форму и затем уже к каждой цифре добавлять по числу 48 ($30). При приеме таких многозначных чи- сел в виде строки придется принимать отдельно каждый разряд, переводить его в цифру, а затем либо упаковывать в BCD-значение, либо переводить в обычное шестнадцатеричное число, в зависимости от задачи. Все типовые процедуры для таких действий мы уже рассматривали в главе 5, а примеры их использования при- ведены далее, ъразд. «Программа установки часов DS1307». Символы выводятся еще проще — просто их указанием в ординарных кавычках. Ничего не изменится, если вместо прямого указания символа вы будете указывать его код согласно таблице ASCII. Несложно также организовать вывод и целых строк (подробно это рассматривалось в разд. «Ассемблерное представление симво- лов и строк» главы 7). Во всех таких случаях важно, чтобы в приемной программе также был установлен прием в виде текста, а не численного кода (в мониторе порта Arduino это получается автоматически, в остальных коммуникационных програм- мах настраивается).
Глава 15. Программирование UART и обмен данными с персональным компьютером 401 Программа установки часов DS1307 Для иллюстрации приведенных ранее положений воспользуемся давно обещанной программой установки часов DS1307 (файл rtc1307_set.asm из архива, расположен- ного по адресу, указанному во введении). Она специально ориентирована на ис- пользование монитора порта Arduino, приспособленного к текстовому выводу и вводу. Прерывания UART в ней не используются, т. к. программа, кроме установки часов, больше ничего не делает. После включения программы контроллер с подключенными часами (см. рис. 13.3 в главе 13) и адаптером USB-UART (по обеим линиям RxD и TxD) ожидает вашей команды. Получив от монитора порта символ r в качестве команды, программа на- чинает выдавать каждую секунду строку значений времени и даты в том порядке, в каком они хранятся в регистрах часов: Секунды, Минуты, Часы, День недели, Дата, Месяц, Год. Остановить вывод можно, подав в качестве команды символ т (английский). Чтобы установить часы, необходимо сначала сформировать строку установочных значений, предварив ее командой s. Все значения вводятся в том же порядке, начи- ная с минут (секунды будут автоматически установлены на 00), через пробел, еди- ничная цифра дополняется ведущим нулем. День недели будем вводить по россий- ско-европейскому стандарту из диапазона от 01 (понедельник) до 07 (воскресенье). Например, такая строка: S 23 19 03 10 04 19 установит часы на 10.04.19, среда (03 день недели), время 19:23. Для установки удобно использовать часы Windows, предварительно уточнив их показания через Интернет. Строку следует сформировать заранее на минуту-другую раньше указан- ного в ней времени и отправить ее нажатием клавиши <Enter> в момент, когда секундная стрелка на часах Windows дойдет до начала указанной минуты. Вместе с установкой всего времени секунды сбросятся в нулевое значение, и часы пойдут с начала установленной минуты. Как с помощью UART организовать выход из режима энергосбережения? Есть приборы, в которых контроллер основное время проводит в режиме «сна», лишь изредка отвлекаясь на проведение измерений и запись результатов куда- нибудь во внешнюю память. Но мы хотим работу этих приборов контролировать, настраивать их, считывать накопленные показания и т. д., причем делать это в про- извольный момент времени. Встает вопрос о том, как вывести контроллер из режи- ма «сна» при подключении к UART, например, компьютера через какой-либо адап- тер? Ни сам по себе последовательный порт UART, ни его прерывания из режима энергосбережения контроллер вывести не может. Но есть несколько методов, по- зволяющих «разбудить» контроллер по такому случаю.
402 Часть III. Практическое программирование микроконтроллеров AVR Самый очевидный из них — тот же самый, что используется для сброса Arduino при загрузке скетчей через Bootloader. Для этого в адаптере должна транслировать- ся одна из дополнительных линий расширенного порта RS-232, направленных от компьютера к контроллеру: RTS или DTR. В нормальном состоянии эти линии «ви- сят» в логической единице (отрицательный потенциал на выходе СОМ-порта, по- ложительный на TTL-выходе адаптера). Подключив одну из этих линий к выводу внешнего прерывания INTO или INT1, вы можете перед началом коммуникаций сбросить ее в ноль, инициировав таким образом пробуждение контроллера. Не за- будьте только выждать паузу, необходимую для установления после «пробужде- ния» (от 4 до 20 мс, в зависимости от тактовой частоты, — см. главу 14). Когда кон- троллер загрузится, источник пробуждения легко определяется по прерыванию, которое привело к выводу из «сна», и контроллер вместо рутинной работы перехо- дит в режим ожидания команд извне. Второй способ не требует отдельного вывода, но менее надежен. Пока обмена нет, на линии TxD со стороны адаптера (т. е. компьютера) также «висит» логическая единица, которая сбрасывается в ноль первым же стартовым битом. То есть соеди- нив между собой линии RxD и внешнего прерывания INTO (или INT1) контроллера, вы первым посланным байтом с произвольным значением «будите» контроллер, а настоящий обмен затем начинаете спустя некоторое время, необходимое кон- троллеру для выхода из «сна». Ненадежность тут в том, что на вывод RxD, а следо- вательно, и на вывод внешнего прерывания контроллера при этом могут попадать ложные стартовые импульсы: в моменты подключения и отключения адаптера от компьютера или от контроллера, запуска внешней программы, просто из-за наводок и т. п. В общем-то подобное не исключено и в первом способе, хотя и гораздо ме- нее вероятно. Потому оба этих способа требуют тщательной проверки на практике, в конкретной схеме и с конкретным адаптером.
ГЛАВА 16 Некоторые Ard u i no-задач и на ассемблере В этой главе мы остановимся на том, как использовать в ассемблерном проекте не- которые стандартные для Arduino возможности. Не всегда это проще делать имен- но на ассемблере, но без таких возможностей ваше изучение языка будет непол- ным — многие проекты окажутся недоступными. И, кроме того, такое применение, как всегда, позволит лучше понять, как функционирует то или иное устройство, что именно происходит в недрах Arduino-библиотек, и как это оптимизировать для наилучшего применения устройства в конкретном проекте. Начнем мы с дисплеев, как очень часто применяющихся компонентов многих за- конченных приборов. Задача, которую мы ставили в начале, — создать удобный, эстетичный и экономичный прибор, — предполагает, что вы с дисплеями знакомы накоротке. Универсальный метод динамической индикации, изложенный в главе 9, имеет понятные ограничения, и хочется иметь в запасе методы и не столь громозд- кие в реализации, с одной стороны, и побогаче возможностями, с другой. С их по- мощью мы доведем до конца некоторые проекты, ранее показанные только в виде тестов. Дисплеи Дисплеев, как устройств для перевода электрических сигналов в визуальную фор- му, существует очень много самых разных типов. Можно даже сказать, что дисплеи были самой первой разновидностью электронных приборов, появившейся задолго до самой отрасли под названием «электроника». Электроскоп и стрелочный гальва- нометр— первые устройства для визуализации процессов, протекающих в элек- трических цепях, были изобретены еще тогда, когда о самом существовании элек- трона и не подозревали. А сейчас только цифровых дисплеев существует с десяток основных разновидностей, и хорошо вам знакомые жидкокристаллические и свето- диодные индикаторы представляют собой лишь вершину этого айсберга. Мы здесь остановимся только на нескольких основных типах, причем отфильтруем их из всей доступной массы по принципу целесообразности использования этих разновидностей именно в ассемблерных проектах. Скажем, цифровые графические матрицы, особенно небольших размеров, подключить к МК не сложнее, чем рас-
404 Часть III. Практическое программирование микроконтроллеров AVR сматриваемые далее матричные (знакосинтезирующие) строчные дисплеи, тем более что зачастую они управляются одними и теми же встроенными контроллера- ми. Но управление ими — дело настолько громоздкое даже на С, что перевод на ассемблер станет только лишним и ничем не оправданным усложнением. Наиболее универсальный способ прямого управления семисегментными цифровы- ми дисплеями с помощью динамической индикации мы уже рассмотрели в главе 9, а экономичный ЖК-дисплей — в главах 13 и 14. Здесь мы рассмотрим несколько типов дисплеев со встроенными контроллерами. Это, с одной стороны, позволяет экономить в числе необходимых выводов управляющего микроконтроллера и упрощать программы управления, с другой стороны — вынуждает нас полагаться на готовые конструктивные решения, которые по определению не могут быть оптимальными и универсальными, годящимися на любой случай. 4-разрядный цифровой дисплей на основе ТМ1637 4-разрядных цифровых индикаторов на основе ТМ1637 выпускается много (загля- ните на ru.aliexpress.com по ключевому слову ТМ1637), и общих недостатков у них два. Первый недостаток обусловлен большой величиной прямого падения напря- жения на светодиодном сегменте: при 5-вольтовом питании дисплей с цифрами более 14 мм найти в продаже невозможно. Распространить это решение на индика- торы большего размера можно только самостоятельно с использованием транзи- сторных ключей, наподобие того, как это делалось в схеме динамической индика- ции (см. рис. 9.5 в главе 9). Второй недостаток — в некотором разнобое схемотехнической реализации готовых модулей, имеющихся в продаже. Девять из десяти таких модулей выпускаются в варианте «для часов» с разделительным двоеточием (Arduino-библиотека тм1б37 .h). Оставшиеся (с десятичными точками у каждой цифры) могут иметь существенно отличающееся управление. Его во многих случаях приходится изобретать само- стоятельно «наощупь», т. к. документацию найти невозможно, а с обычными Arduino-библиотеками эти модули работать отказываются. Мы не станем, конечно, лезть в такие дебри, а рассмотрим только управление готовым модулем, но ничто не мешает распространить полученное решение на самостоятельно спроектирован- ные схемы на основе ТМ1637, что может оказаться проще, чем заниматься реинжи- нирингом продукции безымянных китайских товарищей. Быстро проверить гото- вый дисплей на соответствие стандартному управлению можно, если подключить его к Arduino с примером из комплекта библиотеки ТМ1367 .h. ТМ1637 имеет последовательный порт, похожий на 12С, но все-таки только похо- жий, отчего с ней нельзя работать через штатный протокол, как мы это делали в главе 13. ТМ1637 не имеет 12С-адреса, и каждая такая микросхема подключается через два отдельных вывода контроллера. В «даташите» на ТМ1637 показана схема подключения максимального количества семисегментных цифр в количестве шести разрядов, при этом восьмой (старший) бит в каждом разряде, как и положено, управляет десятичной точкой. Разряды нумеруются от 0, при этом номер 0 соответ- ствует старшему (крайнему левому разряду). В часовых 4-разрядных модулях точ-
Глава 16. Некоторые Arduino-задачи на ассемблере 405 ки у цифр обычно отсутствуют, а восьмой бит второго разряда (разряда номер 1 — и только его) подключен к разделительному двоеточию. Другая особенность ТМ1637 — биты семисегментного кода, в отличие от обычного TWI, передаются по последовательному каналу младшим битом вперед, отчего код сегментов прихо- дится «переворачивать» относительно показанного на рис. 9.2 в главе 9. Заметки на полях Некоторый свет на происхождение особенностей дисплея на основе ТМ1637 пролива- ет тот факт, что он может быть подключен к клавиатуре 2x8 кнопок и непосредственно индицировать на подключенных индикаторах число, набранное на клавиатуре, одно- временно передавая его в контроллер. Очень вероятно, что изначально ТМ1637 пред- назначался для электронных замков или других подобных устройств, потому никто не старался приспособить его к стандартному 12С-протоколу. Я ни разу не встречал его использования в этом качестве, но это, конечно, не значит, что такая необходимость не может возникнуть. Управление 4-разрядным цифровым индикатором иллюстрирует простая програм- ма, которую можно найти в архиве по адресу, указанному во введении (файл 4-digit_display_test.asm). Модуль подключается к выводам РВО (CLK) и РВ1 (DIO) — это крайние выводы 14 и 15 у ATmega8 (рис. 16.2) и выводы 12 и 13 у ATtiny2313 (рис. 16.4). Код, рассчитанный на тактовую частоту 4 МГц, без изменений может быть перенесен в любой контроллер, имеющий порт В (для ATtiny2313 нужно уда- лить строки, относящиеся к регистру sph, который в нем отсутствует). А при необ- ходимости задействовать другие выводы и порты, надо только заменить номера битов порта CLK и DIO, а также названия регистров PortB и ddrb. Здесь тактовая частота может быть задана как от внутреннего генератора, так и от внешнего кварца. Программа выводит цифры «0123» и мигает двоеточием. В программе проиллюст- рированы оба способа вывода цифр в разряды: подряд все четыре цифры с автоин- крементом разрядов, или по одной в заданное знакоместо. В тексте программы раз- мещен массив кодов основных знаков, которые могут понадобиться в наших зада- чах: цифр 0-9, значка градуса (deg), знака минус, пробела (пустого места для очистки разряда), а также букв «Е» (error, ошибка), «Н» (humidity, влажность) и «Р» (pressure, давление). В этой программе массив не используется, но вы можете по- смотреть его применение в более «навороченной» демопрограмме из того же архи- ва (файл 4-digit_display_test_full.asm), где используются все эти символы. Добавление к символу на втором месте кода $80 (т. е. установка таким способом старшего бита) засвечивает двоеточие. Биты в коде символа тут, в отличие от МТ-10Т11, соответ- ствуют порядку сегментов «a»...«g» (см. рис. 9.2), только— в сравнении с представленной там табличкой — идут справа налево. Заметки на полях Если вы думаете, что я корпел над рис. 9.2, переворачивая биты в каждом символе для заполнения массива, то ошибаетесь— я просто скопировал кусок массива из файла Arduino-библиотеки ТМ1367.срр, а потом добавил по образцу несколько тре- бующихся мне символов. Мы тут вообще не делаем ничего такого, что бы выходило за рамки этой библиотеки, — только упрощаем и обходимся без излишних «наворотов» и посредников.
406 Часть III. Практическое программирование микроконтроллеров AVR Кроме всего прочего, ТМ1637 может управлять яркостью индикаторов. Для этого в команде включения (см. процедуру инициализации модуля в тексте программ) нужно установить младшие биты в соответствии с таблицей в руководстве. В ре- альности имеют значение только три варианта установки: ооо (яркость минималь- ная), ою (пониженная) и он (нормальная). Возможно, в каких-то случаях нормаль- ная яркость будет достигнута при значениях юо или 101 — все зависит от установ- ленных индикаторов, выше этого яркость уже практически не меняется. В первой тестовой программе установлена яркость он, во второй перебираются несколько вариантов. Часы на дисплее ТМ1637 Так вышло, что работа с часами DS1307 растянулась на три различные темы: при- менение интерфейса TWI (см. главу 73), установка часов через UART (см. главу 15) и вот теперь мы можем представить удобный вариант законченного настольного устройства, подключив часы к модулю 4-разрядного LED-индикатора на основе микросхемы ТМ1637. Дисплей здесь требуется с подключенным двоеточием. Программу простых часов на основе 4-разрядного индикаторного модуля с микро- схемой ТМ1637 вы можете найти в архиве по адресу, указанному во введении (файл 4-digit_display_cloGk.asm). Часы индицируют значения минут и часов, а также мигают двоеточием (рис. 16.1). В программе установлен вариант яркости индикатора ою. К программе также подключается файл i2c.prg, описанный в главе 13. Рис. 16.1. Дисплей часов на основе 4-разрядного индикаторного модуля Микросхема (модуль индикации DS1307) подсоединяется, как уже говорилось, к выводам РВО и РВ1 — это крайние выводы И и 15 у ATmega8 (см. рис. 16.2). Тактовая частота контроллера 4 МГц может быть установлена от любого источни- ка, в том числе от встроенного генератора контроллера. При желании ее изменить, нужно пересчитать числа в двух процедурах задержки: в конце текста программы и в файле i2c.prg. Код рассчитан на ATmega8, но может быть перенесен на другой контроллер, имеющий порты D и В, без изменений (для ATtiny2313 — как уже от- мечалось ранее— придется только удалить строки, относящиеся к регистру sph, который в нем отсутствует). Обратите внимание, что при работе с часами никаких BCD —> BIN преобразований не требуется, только распаковка — распределение тетрад по отдельным разрядам. Наоборот, при выводе на индикатор обычных чисел их придется преобразовать
Глава 16. Некоторые Arduino-задачи на ассемблере 407 в распакованный BCD, иначе никак. При работе с ардуиновской библиотекой TMi367.h зачастую приходится делать то же самое, особенно если вы хотите вывес- ти число не вообще как-то, а в определенную позицию. +5 в R1 5,1 к +5 В 32768I I 1 Reset 2RXD 3TXD 4 INTO (PD2) 5 INT1 (PD3) 6PD4(T0) 7Vcc (ADC5) PCS 28 (ADC4) PC4 27 (ADC3) РСЗ 26 (ADC2) PC2 25 (ADC1)PC124 (ADCO) PCO 23 GND22 AREF 21 AVCC 20 SCK19 MISO18 MOS117 (OC1B)PB216 (OC1A) PB1 15 ATmega8 Рис. 16.2. Схема часов на основе 4-разрядного индикаторного модуля Ультразвуковой дальномер на дисплее ТМ1637 Этот код — в отличие от программы простых часов, рассмотренной в предыдущем разделе и рассчитанной на ATmega8, — наоборот, рассчитан на ATtiny2313, но мо- жет быть перенесен на другой контроллер, имеющий порты D и В, без изменений (для ATmega8/l6/8535 придется только добавить строки инициализации стека, относящиеся к регистру sph, который в ATtiny2313 отсутствует). Здесь тактовая частота 4 МГц должна быть задана от внешнего кварца, т. к. мы имеем дело с час- тотными измерениями. Точки-двоеточия нам тут не потребуются, потому дисплей может быть любого типа, лишь бы он имел стандартное управление. Программу ультразвукового дальномера можно найти в архиве по адресу, указан- ному во введении (файл HC-SR04_disp4digit.asm). Несмотря на простоту самого дат- чика и не слишком объемную программу работы с ним, которая получится в ре- зультате ее доработки, о которой рассказано далее (основная часть почти 500- байтовой программы посвящена выводу на 4-разрядный дисплей, а собственно код обработки датчика там занимает около 150 байт), эта программа— одна из самых насыщенных в книге, потому что демонстрирует многие приемы, о которых ранее мы только упоминали.
408 Часть III. Практическое программирование микроконтроллеров AVR Ультразвуковой дальномер HC-SR04 показан на рис. 16.3, а его подключение к контроллеру— на рис. 16.4. HC-SR04 запускается импульсом длительностью не менее 10 мкс и по получении ультразвукового эха выдает перепад из единицы в нулевое значение. Импульсы мы формируем примерно каждую секунду с по- мощью TimerO. Для измерения интервала по окончании запускающего импульса включаем Timer 1. Так как скорость звука в воздухе при нормальных условиях око- ло 340 м/с, то каждая секунда до появления эха означает, что сигнал пробежал до цели 170 метров (340— туда и обратно). Соответственно, микросекунда означает расстояние 0,17 мм или 0,017 см, а один метр будет соответствовать 5800 мкс. Что- бы получить расстояние в сантиметрах, величину времени отклика нужно умно- жить на 0,017 (или поделить на 58, как это рекомендуется в Arduino). Рис. 16.3. Ультразвуковой дальномер HC-SR04 +5 к RX аоапш% UART длят* R1 5,1 « в «—I Xci 1" " ♦" 9ста .. 4 МГц [ЦЦ hi-J-j HC-SR04 Vcc Trig Echo GND D1 L_ i+5 В - H 1 i Keset vcc 20 2RXD(PD0) SCK19 3TXD(PD1) MISO18 4XTAL2(PA1) M0SM7 5 XTAL1 (РАО) (ОС1В) PB4 16 6 INTO (PD2) (OC1A) PB3 15 7 INT1 (PD3) PB2 14 8 PD4fT0) PB1 13 9PD5(T1) PB0 12 10 GND (PD6)ICP1 11 ATtiny2313 dz: C2 + 5B : т t GND Vcc ZBBBB Рис. 16.4. Подключение ультразвукового дальномера HC-SR04 к контроллеру ATtiny2313 Это действие мы организуем следующим образом. При частоте 4 МГц и коэффици- енте делителя Timer 1, равном 1:8, мы отсчитываем по 2 мкс в каждом такте тайме- ра, потому число в счетчике на 1 метр будет равно 2900. Для удобства расчетов сразу переведем результат в мкс, умножив его на 2 (т. е. сдвинув через перенос вле- во на 1 разряд). Умножим 0,017 на $ffff, перемножим полученное число 1114
Глава 16. Некоторые Arduino-задачи на ассемблере 409 ($045а) на величину микросекунд и возьмем из 32-разрядного результата два стар- ших байта (см. главу 8). Максимальное измеряемое расстояние при такой методике лимитируется числом микросекунд 65 535, что равносильно длине 11 м, а датчик HC-SR04 на такую дли- ну и не работает. Более того, при испытаниях этой программы вы увидите, что дат- чик HC-SR04 более или менее уверенно работает только примерно до метра, пото- му мы могли бы облегчить себе жизнь, ограничив число сантиметров одним байтом (тогда и умножение, и преобразования для вывода на дисплей упростились бы) и, соответственно, максимальной измеряемой дистанцией 2,55 м. Но ведь моделью HC-SR04 ассортимент подобных датчиков не ограничивается, потому не будем заранее отрубать потенциальные возможности, коли нам это ничего не стоит, — ограничим только отображаемое значение сантиметров числом 999, так будет про- ще производить BCD-преобразование. Поработав с этой программой, вы убедитесь, что показания ультразвукового датчи- ка очень сильно «дребезжат», поэтому их необходимо усреднять. Программу с ус- реднением показаний вы также найдете в архиве по адресу, указанному во введении (файл HC-SR04_disp4digi_avr.asm). Необходимость усреднения заставляет провести дополнительные предварительные расчеты, чтобы не выйти за диапазоны приме- няемых чисел. Измерение на максимальной дистанции 10 м занимает -60 мс, т. е. за секунду мы можем провести примерно 16 измерений. Мы ограничимся 8-ю изме- рениями в секунду (для этого надо частоту на входе TimerO сделать в восемь раз быстрее, т. е. коэффициент деления установить 1:8 вместо 1:64). Суммировать бу- дем рассчитанное число сантиметров, которое в каждом измерении может состав- лять, как мы говорили, не более 999 см ($03Е7). За восемь измерений мы накопим максимум 999 х 8 = 7992 ($1F38), т. е. за пределы двух байтов не вылезем. Полу- ченное после суммирования двухбайтовое число мы каждое восьмое измерение три раза сдвигаем вправо (перенос из старшего разряда только для младшего байта!), т. е. делим его на 23 = 8, и уже этот результат выводим на индикацию. В зависимости от назначения дальномера, время измерения, равное здесь примерно секунде, может быть скорректировано, — например, для ориентации робота в пространстве время реакции продолжительностью в целую секунду слишком велико. Но вы, наверное, из предыдущего подробного изложения уже и сами сообразили, какие методы тут можно применить, так что не будем больше на этом останавливаться. Термометр на дисплее ТМ1637 Для термометра на датчике ТМР36 (рис. 16.5) тактирование от внешнего кварца 4 МГц необязательно, но программа почти столь же сложная, как и в случае даль- номера, потому точная частота может понадобиться для отладки с помощью UART. Контроллер с отлаженной программой можно потом переключить на тактирование от внутреннего генератора. Как и в случае дисплея МТ-10Т11, программа (ее файл 4-digit_display_TMP36.asm вы найдете в архиве по адресу, указанному во введении) создана под ATmega8, и переносить ее на другие контроллеры можно с оглядкой на величину внутреннего опорного напряжения, которая в ATmega8 составляет 2,56 вольта (а у других контроллеров может отличаться — подробно об этом рас-
410 Часть III. Практическое программирование микроконтроллеров AVR +5 В «*- R1 5,1 к То" +5 В С2 1 Зт Н I Reset 2RXD 3TXD 4 INTO (PD2) 5 INT1 (PD3) 6 PD4 (ТО) 7Vcc 8GND 9 XTAL1 10XTAL2 II PD5(T1) 12PD6 13PD7 14PBO (ADC5) PCS 28 (ADC4) PC4 27 (ADC3) PC3 26 (ADC2) PC2 25 (ADC1)PC124 (PCO)ADCO 23 GND22 AREF 21 AVCC 20 SCK19 MISO 18 MOSi 17 (OC1B)PB216 (OC1A) PB1 15 ATmega8 t+5B L 2 C5 0,1 =±= +5 В TMP36 + 5B т t DIO CLK GND Vcc а в. в. а Рис. 16.5. Термометр на датчике ТМР36 и 4-разрядном дисплее на основе ТМ1637 сказано в главе 11). Кстати, на ATtiny2313 программа, понятно, не заработает — ввиду отсутствия АЦП. Дисплей здесь потребуется с подключенными разделительными точками (у нас точка всегда будет засвечиваться перед младшим разрядом). Но проверить про- грамму и подправить калибровочные коэффициенты датчика при необходимости можно с помощью любого дисплея на основе ТМ1637 со стандартным управлени- ем, только разделительная точка будет отсутствовать. Все остальное (перевод в фи- зические величины и затем в BCD-код), кроме, конечно, процедур вывода, такое же, как в программе для дисплея МТ-10Т11 в главе 13. Знакосинтезирующие дисплеи на базе HD44780 и его аналогов Контроллер знакосинтезирующих (строчных) дисплеев HD44780 фирмы Hitachi стал промышленным стандартом де-факто еще в 90-е годы прошлого века. Не слишком экономичный в отношении количества используемых выводов управ- ляющего контроллера (минимум 6, максимум 11-12), он зато несложен в програм- мировании и без лишних «наворотов» применим в комплекте с управляющим МК любого семейства, даже из самых архаичных. Сложность только в том, что за годы существования этого стандарта различные фирмы по всему миру наплодили неимоверное количество ЬЮ44780-совместимых контроллеров. Иногда это практически полные аналоги (KS0066 фирмы Samsung, КБ1013ВГ6 от российской фирмы «Ангстрем»), иногда расширенные и усовершен- ствованные (WS0010 фирмы Winstar, могущий управлять дисплеями в графическом режиме, ST7070 российской фирмы Sitronix, дополненный интерфейсами 12С и
Глава 16. Некоторые Arduino-задачи на ассемблере 411 SPI), но даже только в области стандартных функций все они имеют свои мелкие особенности, особенно в части инициализации. Разнообразие дисплеев на основе всех этих аналогов, притом с необходимостью поддерживать алфавиты нескольких языков, вносит свою долю неразберихи. Поэтому нередко можно натолкнуться на трудности с применением произвольно взятого дисплея даже в Arduino с его готовой библиотекой LiquidCrystal. Если вы попробуете «погуглить» по ресурсам, описывающим самостоятельное управление подобным дисплеем, то едва ли встретите две одинаковых процедуры инициализа- ции, — каждый для своего частного случая вынужден изобретать свою. Пытаясь когда-то подключить к Arduino OLED-дисплеи фирмы Winstar, я тоже не сумел ми- новать этой разновидности бега на месте. С тех пор мне постепенно удалось разра- ботать более или менее универсальную процедуру инициализации, годную по крайней мере для тех типов дисплеев, что прошли через мои руки. Заметим, что как раз из-за бешенного разнообразия вариантов программировать такие дисплеи на ассемблере, как говорится, сам бог велел: так при нужде гораздо проще «подрихто- вать» программу «по месту», чем ковыряться в недрах LiquidCrystal, разбираясь в ее запутанной структуре (попробуйте сами найти там с лету место, куда можно добавить, например, переключение таблиц знакогенератора, о котором рассказыва- ется далее, и вы поймете, в чем я вас пытаюсь убедить). Описываемые здесь типовые процедуры с гарантией применимы для самых рас- пространенных типов с конфигурацией до 20 символов в 2 строки (кроме рассмат- риваемых далее— это обычные ЖК-дисплеи Winstar типа WHxx, а также Data Vision и других фирм). Заметим, что HD44780 и его аналоги могут управлять толь- ко двухстрочным или однострочным дисплеем, причем с числом символов в каж- дой строке до 40. На практике более чем 20-символьные дисплеи встречаются ред- ко— лишние области ОЗУ иногда используются для эффектов типа «бегущая строка». Для дисплеев с четырьмя и более строками задействуются, соответствен- но, два и более контроллера, переключение между которыми происходит прозрачно для пользователя. При этом однострочные дисплеи чаще всего фактически все рав- но двухстрочные, только обе строки у них расположены в один ряд. Необходимо еще сказать пару слов о принципах вывода знаков у HD44780 и его аналогов. На рис. 16.6 показан внешний вид типового дисплея с конфигурацией Рис. 16.6. Внешний вид знакосинтезирующего ЖК-дисплея конфигурации 16x2
412 Часть III. Практическое программирование микроконтроллеров AVR 2 строки по 16 знакомест каждая. Одно знакоместо содержит матрицу 5x8 точек- квадратиков. Для вывода символов внутри контроллера имеется таблица знакоге- нератора, именуемая DDRAM (Display Data RAM). Обращаясь по адресу этой таблицы, вы инициируете вывод соответствующего символа в текущую позицию курсора. Таблица адресуется однобайтовым кодом и, соответственно, может содержать мак- симум 255 символов. Поэтому, как правило, дисплеи разделяются по поддерживае- мым языкам — при покупке нужно обращать на это внимание. Стандартная таб- лица конфигурации русско-английского знакогенератора HD44780 приведена на рис. 16.7. / старший полубайт £ 0 1 2 3 4 5 6 7 8 9 А В С D Е F 0 1 2 1 и # $ т4 & ' ;■ # + — ■ 3 0 1 2 3 4 5 6 7 8 9 ■ ■ < > 7 4 Э Я В С D Е F Q Н I J К L М N 0 5 Р Q R S Т и и 1.1.1 X V Z с ф ] Л — 6 а b с d е f g h i j k 1 Pi n о 7 P Ц r t u ',.■ X У z ID IB 15 £ 8 9 A Б Г Ё H-i з и й л п 1 i У ф ч ш ъ ы 3 в ю я б в г ё ж И Й К ■л м н п т с ч ш ъ ы ь э ю •й <•: :•[:• ее ?? с £ D , i и II ?• X ':>• I Е t Ф К -И Н ■ Е д ц щ д ф ц щ ■" Л.' ё ■;• у * ■■•■ о F Щ '4 Ч 1\ ш 1- t "iii t t S Щ 1 Рис. 16.7. Таблица конфигурации стандартного русско-английского знакогенератора HD44780 Из этой таблицы видно, что в таких дисплеях кодировка (т. е. адрес в таблице зна- когенератора) английских символов, знаков препинания, цифр и некоторых стан- дартных символов («$», «%», «*», «#», «-», «+», скобок и т. п.) устроена в полном соответствии со стандартной компьютерной кодировкой ASCII, что позволяет про- граммам обращаться к таблице с указанием просто символа (подробно об этом рас- сказано в главе 7). Но вот в части русского алфавита таблица устроена предельно экономично — приведены только кириллические символы, не совпадающие по на- чертанию с английскими. Потому обращаться к ним и другим символам из правой части таблицы в таком случае можно лишь через прямое указание адреса: старшая тетрада байта адреса указана в столбце, младшая — в строке.
Глава 16. Некоторые Arduino-задачи на ассемблере 413 Свободные места в стандартной таблице обычно заполняются разными производи- телями на свое усмотрение, иногда не без пользы, как мы увидим далее. За одним исключением — первые восемь символов (с адресами 00-07) всегда остаются заре- зервированными. Эти восемь мест программист может заполнить рисунками сим- волов на свое усмотрение. Процедура описана к «даташитах» крайне мутно, но в реальности все очень просто: нарисованный символ 5x8 сохраняется в специальной области памяти CGRAM (Character Generator RAM), содержащей всего восемь зна- комест. Рисунок, помещенный в определенное знакоместо в CGRAM, автоматиче- ски заменяет собой один из символов с соответствующим номером в DDRAM, после чего может быть адресован обычным способом. В некоторых дисплеях имеется не одна таблица знакогенератора, а несколько, меж- ду которыми можно переключаться по их номеру. Как правило, в русифицирован- ных дисплеях стандартная русско-английская таблица (с номером 0) включена по умолчанию, на что мы будем ориентироваться в дальнейших действиях. Но так не всегда— OLED-дисплеи с расширенным контроллером WS0010 фирма Winstar вознамерилась сделать многоязычными, потому они имеют четыре таких таблицы. Первой под номером 0 там идет включенная по умолчанию англо-японская табли- ца, а стандартная русско-английская размещается под номером 2 (оыо). Если ее не включить специально, то можно будет выводить надписи только по-английски (и, разумеется, по-японски). Подробности У дисплеев российской фирмы МЭЛТ обычно также несколько таблиц с различными языками и кодировками. По умолчанию там, как положено, стандартная русско- английская таблица HD44780 (№ 0), но вторая таблица (№ 1) может быть полезна при программировании управления такими дисплеями на языках высокого уровня, т. к. для русского языка там действует стандартная кодировка Windows-1251. AVR-ассемблер не воспринимает никаких символов, кроме английских, но в любой среде программи- рования на С (в т. ч. и в Arduino) русские буквы для таких дисплеев также можно ука- зывать напрямую (с некоторыми оговорками касательно путаницы между однобайто- выми и двухбайтовыми кодировками в современных Windows). У дисплеев на базе HD44780 предусмотрены два варианта подключения: по 8-про- водному интерфейсу и по более экономичному, но и более сложному с программ- ной точки зрения 4-проводному. 8-проводную схему, из-за большого количества соединений (минимум 11 проводников), кажется, уже почти все забыли. На рис. 16.8 показана пригодная для нашего случая схема подключения такого дис- плея к контроллеру ATmega8, на которую мы будем ориентироваться далее. Схема по экономичному 4-проводному интерфейсу (всего 6 выводов) выбрана с тем, что- бы оставить свободными выходы UART, прерываний INTO и INT1, задействован- ные в наших 12С-процедурах выводы PD6 и PD7, а также пару каналов АЦП (ADC5 и ADC4). Этот выбор оставляет достаточно свободы для маневра, не вынуждая об- ращаться к контроллерам с избыточно большим числом выводов. Единственный его недостаток — то, что задействованные в командах управления выводы дисплея RS и Е не размещаются в одном регистре с данными, отчего управление несколько усложняется.
414 Часть III. Практическое программирование микроконтроллеров AVR 1 Reset (ADC5) PC5 28 2 RXD (ADC4) PC4 27 3 TXD (ADC3) PC3 26 4 INTO (PD2) (AOC2) PC2 25 5 INT1 (PD3) (AOC1) PC1 24 6 PD4 (TO) (ADCO) PCO 23 7 Vcc GND 22 8 GND AREF 21 9 XTAL1 AVCC20 10 XTAL2 SCK 19 11 PD5 MISO 18 12 PD6 MOS117 13PD7 (OC1B)PB216 14PB0 (OC1A)PB115 ATmega8 4 3 2 1 I— I- I I I I 2 3 4 — Vcc (Vdd) Vo RS(AO) R/W с DBO DB1 DB2 DB3 DB4 DB5 DB6 DB7 Led* Led- Рис. 16.8. Схема подключения дисплеев на основе HD44780 к контроллеру ATmega8 Так как выводы у всех дисплеев на основе HD44780 имеют одинаковое назначение и большей частью носят одни и те же наименования (разночтения приведены в скобках), то схема, показанная на рис. 16.8, может считаться универсальной. На ней намеренно не показаны номера выводов — у разных типов дисплеев они могут отличаться (мы далее рассмотрим соответствующие примеры), а здесь для удобства распространенные варианты разводки выводов сведены в табл. 16.1. Отметим, что разводка в первой (и последней) колонке встречается наиболее часто, но. как види- те, это даже не общепринятый стандарт. У некоторых типов (в частности, у вклю- ченных в таблицу дисплеев MT-10S1 и ВСВ1602-05) названия выводов надписаны прямо на плате. Вывод Vo у ЖК-дисплеев предназначен для подключения напряжения регулировки контраста (его можно подавать с помощью потенциометра, как показано на схеме, приведенной на рис. 16.8). В большинстве случаев такая регулировка ничему не поможет и устанавливать ее не надо. «Умолчательный» контраст обычно установ- лен так, что представляет собой компромисс между оптимальным значением при работе на отражение (т. е. с внешним источником света) и при работе на пропуска- ние (т. е. с включенной подсветкой). Улучшить весьма убогое качество почти всех ЖК-дисплеев при работе без подсветки все равно не удастся. Встречаются и ис- ключения с улучшенным качеством изображения, но они тем более никаких регу- лировок не требуют. Некоторые дисплеи для нормального контраста требуют под- ключения Vo к напряжению питания, у большинства его можно оставлять непод- ключенным.
Глава 16. Некоторые Arduino-задачи на ассемблере 415 Таблица 16.1. Разводка выводов некоторых типов дисплеев на основе HD44780 Вывод GND (Vss) Vcc (Vdd) Vo (Vee) RS (АО) RW E DB0-DB3 DB4 DB5 DB6 DB7 Led+ Led- Назначение GND +5 В Контраст Data/Cmd Read/Write Enable Data 0..3 Data 4 Data 5 Data 6 Data 7 Подсветка+ Подсветка- ЖК-дисплеи Winstar, DataVision, MT-16S1 и т.д. 1 2 3 4 5 6 7-10 11 12 13 14 15 16 MT-10S1 12 14 NC(13) 5 6 7 1-4 11 10 9 8 15 16 BCB1602-05 14 13 12 11 10 9 1-4 5 6 7 8 15 16 Winstar OLED 1 2 NC 4 5 6 7-0 11 12 13 ' 14 NC NC Отметим, что вывод чтения/записи RW у дисплея можно подключать к нулю, т. к. в наших операциях он не задействован. Вывод разрешения Е, кроме всего прочего, может служить для параллельного управления несколькими дисплеями по тем же остальным линиям, если подключить вывод Е каждого дисплея к отдельному выво- ду внешнего контроллера. Инициализация и вывод символов По образцу подключаемого файла для интерфейса 12С (см. разд. «Программная эмуляция протокола 12С» главы 13) создадим «библиотеку», в которой распишем универсальные базовые процедуры доступа к строчным дисплеям. Чтобы не загро- мождать программу лишней условной компиляцией, как и в том случае, примем конкретную схему подключения согласно рис. 16.8 и тактовую частоту 4 МГц. Контроллер при этом необязательно должен быть именно ATmega8, можно взять любой другой с такими же портами, просто для контроллеров с большим числом выводов целесообразно программу упростить, подключив все управляющие входы к одному порту и устанавливая нужные биты в один прием. Полностью файл такой «библиотеки» (LCD1602.prg) вы найдете в архиве по адресу, указанному во введении. Отметим, что цифры 1602 в имени файла лишь отражают его применение для наиболее популярной конфигурации дисплеев: 16 позиций в 2 строки, но на самом деле он годится для любых дисплеев (именно строчных, а не графических) с числом знакомест до 20, — дисплеев с их большим количеством просто в продаже не встречается. При этом для однострочных дисплеев процедуры
416 Часть III. Практическое программирование микроконтроллеров AVR из этого файла тоже в большинстве случаев подходят— как мы говорили, чаще всего они все равно де-факто двухстрочные. Подробно используемые алгоритмы мы здесь разъяснять не станем (при желании читатель легко найдет информацию в многочисленных сетевых публикациях на эту тему или просто в описаниях дис- плеев). Здесь мы только поясним применение некоторых процедур и опций, кото- рые там доступны. В файле LCD1602.prg объявлен регистр г1б как привычный temp, и им можно пользо- ваться под этим именем в основной программе (чтобы не вызывать излишних пре- дупреждений, лучше его не именовать по второму разу). Кроме этого, для процедур задержки задействуются два регистра: г17 и ri8 (RazrO и Razri). При этом они со- храняются (кешируются) в стеке, потому их можно применять в основной про- грамме без каких-то ограничений. ПерВЫМ ДеЛОМ В Программе Необходимо С ПОМОЩЬЮ КОМаНДЫ . include "LCD1602 .prg" подключить файл нашей «библиотеки». Подключается он в начале программы, по- сле первой команды rjmp reset или таблицы прерываний. Затем в секции reset нужно, как обычно, установить указатель стека (и сделать другие необходимые в программе установки, если они требуются), после чего сразу вызвать процедуру инициализации командой rcaii LCDini. В этой процедуре, кроме всего прочего, делаются установки нужных битов рсо .. рсз и pbi .. 2, так что специально их уста- навливать не надо (остальные биты этих портов потом в программе можно исполь- зовать по своему усмотрению). Процедура LCDini устанавливает курсор в начальную позицию первой (верхней) строки (строка 0, позиция 0). Последовательный вывод символов будет каждый раз автоматически сдвигать курсор на одну позицию. Если вывод символов необходи- мо начинать с определенного места, то нужно переустановить курсор командой setcursor. Команда является макросом, потому оператор rcaii не требуется, а оформлять параметры надо по правилам для макросов, по образцу setcursor 1,5 (эта команда установит курсор в 5-ю позицию нижней строки). Первый параметр, означающий строку, может принимать значения о (верхняя строка) или 1 (нижняя), а второй параметр, означающий позицию: от о до максимальной длины строки ми- нус 1 (для дисплеев с 16-ю позициями это будет 15). Учтите, что выход за границы строк и позиций никак не проверяется, и попытка вывода, например, в строку 2 или позицию 16 (на дисплее 16x2) либо будет проигнорирована, либо превратит в хаос изображение на всем дисплее. К основной процедуре для вывода символов надо обращаться по следующему об- разцу: ldi temp,'2' ;вывод цифры 2 в текущую позицию курсора rcall LCD_data Как мы уже говорили, напрямую можно указывать цифры, английские буквы и другие символы из левой половины таблицы знакогенератора, приведенной ранее (см. рис. 16.7— такая таблица обязательно приводится в полном «даташите» на любой дисплей). Русские буквы выводятся по их кодам (адресам в таблице):
Глава 16. Некоторые Arduino-задачи на ассемблере 417 ldi temp,$C7 ;вывод русской буквы «я» в текущую позицию курсора гcall LCD_data Вывод одного символа занимает время около 150 микросекунд, потому обычно не мешает другим процедурам в программе. Но вывод целой строки в 16 символов займет уже 2,5 миллисекунды, и может появиться необходимость это учесть в ка- ких-то случаях. Если вам позарез потребуется сократить время выполнения вывода символов, то для конкретных дисплеев можно попробовать уменьшить задержку на 150 мкс в конце процедуры LCDdata, при каждом изменении экспериментально проверяя надежность работы дисплея на макете. Кроме символов из таблицы, можно выводить свои рисованные символы. В ориги- нальной таблице HD44780 по какой-то причине отсутствует нормальный значок градуса, и в файл LCD1602.prg включена процедура его отрисовки: Symboidegree. Вызывать ее надо обычным rcaii и до того, как вы соберетесь выводить этот значок. Очищать дисплей целиком, как правило, не требуется— просто новые символы выводятся на те же места, а ненужные забиваются пробелами. Потому отдельной процедуры для этого не предусмотрено, но при необходимости можно очистить дисплей следующей последовательностью команд: ldi temp,0b00000001 rcall LCD_coramand_4bit /дисплей clear rcall Del_5ms Set_cursor 0,0 /курсор в позицию 0,0 В процедурах файла LCD1602.prg предусмотрены три опции, которые задействуются в необходимых случаях (примеры мы увидим далее). Это подключение возможно- сти мигания символом в позиции курсора, подключение русско-английской табли- цы там, где она не подключена по умолчанию (ориентирована на OLED-дисплеи Winstar), а также замена символа «0» буквой «О». Подключаются эти опции указа- нием в начале программы следующих строк: #define Blink ;мигание символа в позиции курсора #define Rus_table /подключение русской таблицы №2 #define Zerosymb /подмена 0 на букву О Если какие-либо из опций не требуются, то соответствующую строку можно за- комментировать или просто не указывать. Последняя опция может вызвать недо- умение: зачем заменять «0» на букву «О»? Дело в том, что нормальный ноль на дисплеях с матричными шрифтами неотличим от буквы «О», и потому всегда изо- бражается перечеркнутым. Я уважаю производителей дисплеев за приверженность традициям, но смотрится этот архаизм в современном антураже, на мой взгляд, безобразно. Отсюда и отключаемая опция — для себя я ноль всегда заменяю на букву «О» безоговорочно, но может быть кому-то это не понравится (сравните изо- бражение нуля на рис. 16.6 и 16.7).
418 Часть III. Практическое программирование микроконтроллеров AVR Заметки на полях Корни у перечеркнутого нуля растут из эпохи монохромных дисплеев и АЦПУ, которые никаких шрифтов, кроме матричных, не знали. А нарисовать в ограниченном про- странстве матрицы 5x8 или даже 8x12 обычный ноль так, чтобы он уверенно отличал- ся от буквы «О» невозможно. В программных текстах, для вывода которых в основном использовались устройства отображения того времени, это могло приводить к трудно- исправляемым ошибкам. Потому и договорились изображать ноль перечеркнутым овалом. На современных дисплеях мы не выводим программные тексты, и ситуацию, когда идентичность изображений нуля и буквы «О» вызовет трудности, можно разве что попытаться сконструировать искусственно. А первая опция— мигание символом в позиции курсора— нам пригодится для того, чтобы мигать двоеточием в часах. К сожалению, в большинстве аналогов HD44780 для ЖК-дисплеев она реализована исключительно плохо, и мигать там придется искусственно. Пример управления ЖК-дисплеями конфигурации 16x2 Конкретный пример тестовой программы для типового ЖК-дисплея с конфигура- цией 16x2 вы найдете в архиве по адресу, указанному во введении (файл LCD16xO2_ proba.asm). На рис. 16.6 как раз и показан результат работы этой программы на при- мере дисплея ВСВ1602-05А фирмы Blaze при включенной подсветке. Как у боль- шинства других ЖК-дисплеев, нормального изображения на нем можно добиться только в этом режиме — при выключенной подсветке надписи трехмиллиметровым шрифтом становятся блеклыми, невыразительными и с трудом читаются на сером фоне. Мигание символа в позиции курсора у ВСВ 1602-05А, как и у других знакомых мне ЖК-дисплеев, реализовано из рук вон плохо (можете попробовать включить за- комментированную опцию Blink и убедиться в этом самостоятельно). Поэтому в тестовой программе мигание реализуется самым простым способом: каждую секунду мы заменяем двоеточие пробелом, а еще через секунду восстанавливаем. Из-за инерционности дисплея делать это чаще не рекомендуется. Дисплей без включенной подсветки потребляет 0,8 мА, что вполне приличная ве- личина для дисплеев на аналогах HD44780 (например, аналогичные дисплеи Winstar потребляют раза в два больше, правда, и символы у них на пару миллимет- ров покрупнее). Подсветка у ВСВ 1602-05А включается отдельно, подачей напря- жения +5 вольт на вывод 15 и «земли» на вывод 16. Ток потребления подсветки при этом равен 20 миллиамперам. Величиной напряжения можно регулировать яркость подсветки для разных условий внешнего освещения (подавая это напряжение, на- пример, от вывода ШИМ контроллера). Из-за слепой картинки в отсутствие подсветки область применения таких индика- торов при батарейном питании ограничена периодически включающимися прибо- рами. Потребление подсветки у ВСВ 1602-05А одно из самых малых в своей облас- ти— у упомянутых ЖК-дисплеев Winstar или рассматриваемых далее дисплеев МЭЛТ оно в несколько раз больше. Притом у многих других дисплеев, в отличие от ВСВ 1602-05А, не установлен встроенный резистор для светодиодов подсветки,
Глава 16. Некоторые Arduino-задачи на ассемблере 419 и их яркость задается током, а не напряжением, что в цифровом окружении не очень удобное качество. По этой программе должен без проблем работать любой двухстрочный дисплей, совместимый с системой команд HD44780, так что ее можно использовать для тес- та перед включением таких дисплеев в свой проект. Дисплей MT-10S1 фирмы МЭЛТ Второй тест мы устроим на примере однострочного 10-символьного дисплея МТ- 10S1 российской фирмы МЭЛТ— двоюродного брата рассмотренного в главе 13 дисплея с семисегментными цифрами МТ-10Т11. Дисплей MT-10S1, в отличие от многих других типов, однострочный и с десятью (а не с 16-ю) символами. Этот дисплей спроектирован на основе российского аналога HD44780— микросхемы КБ1013ВГ6 и отличается прекрасным контрастом символов безо всякой подсветки, большим размером знаков (почти 8,5 мм в высоту) и относительно небольшим по- треблением (0,7 мА). Заметим, что его более длинный аналог MT-16S1 от MT-10S1 ничем не отличается, кроме числа символов в строке и другой (стандартной) раз- водки выводов. А вот двухстрочные дисплеи фирмы МЭЛТ типа MT-10S2 уже ничем не лучше аналогичных импортных, за одним разве что исключением — их разновидности на основе контроллера ST7070 имеют сразу два последовательных интерфейса, кроме обычных параллельных. Подобные импортные аналоги на рос- сийском прилавке представлены крайне скудно. Рис. 16.9. Дисплей MT-10S1 конфигурации 10x1 10 символов на дисплее MT-10S1 (рис. 16.9) расположены в одну строку, но логи- чески состоят из двух строк, в первой из которых 8 символов, во второй — остав- шиеся 2. Потому при выводе символов в последние два знака нужно переключать курсор на вторую строку. Заметим, что 16-символьный MT-16S1 (как и многие дру- гие подобные — WH1601, например) тоже состоит из двух строк, только одинако- вых, по 8 символов в каждой, а вот аналогичный 20-символьный MT-20S1 почему- то из одной. Контакт Vo в описании дисплея помечен как NC (т. е. не подключаемый), и это правильно — контраст дисплея по умолчанию выверен наилучшим образом хоть для режима подсветки, хоть без нее. Пример управления дисплеем MT-10S1 вы
420 Часть III. Практическое программирование микроконтроллеров AVR найдете в архиве по адресу, указанному во введении (файл LCD_MT-10S1_proba.asm). Результат работы программы как раз и представлен на рис. 16.9. В дисплее — о, счастье! — имеется нормальный значок градуса (адрес $99 в таблице знакогене- ратора), потому рисовать его не требуется. А вот мигание двоеточием приходится также делать искусственно. OLED-дисплеи фирмы Winstar Дисплеи на основе органических светодиодов намного приятнее своих ЖК-ана- логов по внешнему виду, но обладают и рядом недостатков. Они, разумеется, больше потребляют при работе (до 40-50 мА), но важнее другое: при существенно большей стоимости (при прочих равных OLED раза в три-пять дороже, чем ЖК) они менее долговечны. Впрочем, на три-четыре года непрерывной работы их, по моим наблюдениям, вполне хватает. Разнообразие по размерам у них больше: OLED-дисплеи Winstar конфигурации 16x2 с буквой «В» в обозначении (WEH001602В) имеют символы высотой почти 9 мм (рис. 16.10). Рис. 16.10. OLED-дисплей WEH001602B с символами высотой 9 мм Эти дисплеи управляются контроллером типа WS0010, который в текстовом режи- ме практически ничем не отличается от других аналогов HD44780. Он легко может переключаться в графический режим, у HD44780 отсутствующий, и этот контрол- лер явно проектировали для подобных целей, но, увы, немного не доделали — мат- рицу более, чем 100x16, он не тянет (чего для многих целей, впрочем, достаточно). Видимо из-за ориентированности WS0010 на графический режим, в части инициа- лизации для работы в текстовом режиме такие дисплеи более капризны, чем обыч- ные ЖК. Процедуры в файле LCD1602.prg специально подогнаны так, чтобы и OLED-дисплеи запускались надежно. Общая тестовая программа для OLED-дисплеев представлена в архиве по адресу, указанному во введении (файл OLED16x02_proba.asm). Подключение дисплея стан- дартное (см. рис. 16.8 и табл. 16.1). Для этих дисплеев обязательно надо включать опцию Rustabie (иначе не получите русского шрифта). В них также нормально работает опция Blink, потому искусственно вызывать мигание не требуется — дос- таточно после вывода символов каждый раз устанавливать курсор на позицию двоеточия. Нормального значка градуса в этих дисплеях нет, и его приходится ри- совать, как и для обычных LCD-дисплеев.
Глава 16. Некоторые Arduino-задачи на ассемблере 421 Часы с календарем на OLED-дисплее Как вы видели на тестовом примере, вывести на такой дисплей можно время и дату одновременно. В случае четырех символов, доступных хоть для динамической ин- дикации, хоть для дисплея на основе ТМ1637, мы вынуждены ограничиваться только временем, а здесь можем выводить также дату, причем название месяца отображать буквами (и при желании менять дату на название дня недели). Подключить часы DS1307 к 12С-интерфейсу можно по схемам, показанным ранее (например, на рис. 16.2), дисплей подключается по стандартной схеме (см. рис. 16.8 и табл. 16.1). Программу индикации часов на OLED-дисплее 16x2 вы найдете в ар- хиве по адресу, указанному во введении (файл OLED16x02_clock.asm). Это самая объ- емная программа в книге— она занимает в памяти программ более 1300 байтов (красота требует жертв!). Существенную часть этого объема, однако, занимает предварительная загрузка в SRAM текстовых названий месяцев, которая произво- дится один раз в начале программы. Если месяцы записать отдельно в EEPROM, например, или помучиться с директивой . dseg, то можно сократить код собственно программы, но тут уместно вспомнить, что нас вообще-то объем памяти программ не лимитирует, а так получается нагляднее и проще для отладки. Вся программа вместе с названиями месяцев влезет даже в куцые объемы ATtiny2313, и выводов тоже должно хватить. Правда, ни на что другое их там уже не останется, и не за- будьте, что у ATtiny2313 порты другие, и еще придется менять текст процедур ра- боты с дисплеем, так что заниматься этим стоит разве что из спортивного интереса. Названия месяцев с учетом выравнивающих пробелов занимают по 10 байтов, т. е. в сумме получится 120 байтов. Если к ним добавить текстовые названия дней неде- ли (можно менять дату на отображение дня недели периодически), то, учитывая длинные названия «воскресенье» или «понедельник», это будет еще больший объ- ем, так что одним байтовым сегментом SRAM мы уже не обойдемся, и в процедуры вызова придется вносить коррективы. Попробуйте самостоятельно подключить через АЦП еще и измеритель температу- ры, как это описывалось ранее применительно к дисплею ТМ1637, — мы ведь спе- циально оставили два вывода АЦП свободными. Долгая процедура полного обнов- ления дисплея в программе производится только раз в минуту (когда обновляется значение секунд), а ежесекундно читаются только сами секунды, и у вас достаточно времени на любые другие операции. Температуру можно уместить на том же экра- не (см. упомянутую ранее тестовую программу из файла OLED16x02_proba.asm). Если вы справитесь с такой модификацией программы, считайте, что научились программировать AVR на ассемблере. * * * Итак, с дисплеями мы познакомились достаточно для того, чтобы выполнять за- конченные проекты с их применением на чистом ассемблере. Рассмотрим теперь еще пару познавательных проектов, типичных для Arduino.
422 Часть III. Практическое программирование микроконтроллеров AVR ИК-приемник Приспособить ИК-канал для управления каким-либо устройством не составляет особых проблем. В Arduino для этого есть соответствующая библиотека iRremote.h, только по неясной для меня причине она ориентирована на создание универсально- го пульта управления: в ней содержатся расшифрованные коды пультов разных производителей. Ясно, что удобней и компактней, чем фирменный ПК-пульт, все равно на коленке не сделаешь, а для получения универсальных пультов есть мно- жество более удобных способов (один их первых универсальных пультов, кстати, придумал еще в начале 1980-х Стив Возняк, сооснователь фирмы Apple). А вот об- ратная задача — приспособить имеющийся пульт для управления своей конструк- цией, — возникает довольно часто. Для задачи ИК-управления уже много лет выпускаются стандартные ИК-прием- ники разных размеров. Самые популярные производит фирма Vishay, название у них начинается с букв TSOP, а заканчивается значением базовой несущей часто- ты в килогерцах (рис. 16.11). Львиная доля пультов работает на частоте 38 кГц, но могут быть и другие частоты, причем приемники с частотами 36 и 40 кГц совмес- тимы с пультами 38 кГц (слегка падает только чувствительность), а пульты с часто- той 56 кГц мне ни разу не попадались (говорят, эту частоту применяет Sharp). В Сети масса информации по этому вопросу, так что всегда можно подобрать при- емник точно под имеющийся пульт. Рис. 16.11. Стандартные ИК-приемники фирмы Vishay ИК-приемник представляет собой чисто аналоговое устройство — узкополосный усилитель сигнала, получаемого с ИК-фотодиода. Приемник в своем диапазоне имеет достаточно большой коэффициент усиления, чтобы на выходе получить логические уровни с размахом от нуля до напряжения питания. Беда в том, что и полоса 38 кГц «вырезана» не идеально, и чувствительность вне ИК-диапазона тоже имеет место. Потому иногда рекомендуемое применение такого приемника в каче- стве простого ИК-фотодиода для приема одной команды (например, включить/ выключить свет в помещении с помощью любой кнопки любого оказавшегося под рукой пульта) есть решение очень плохое — приемник будет срабатывать от любо- го резкого перепада освещенностей. Свет в комнате при этом может внезапно вы-
Глава 16. Некоторые Arduino-задачи на ассемблере 423 ключиться ночью из-за того, что его включили в коридоре, или включиться днем от того, что мимо окна по улице проехала машина, отбросив на потолок солнечный зайчик от лобового стекла. Потому все такие приемники предполагают подключение к контроллеру с декоди- рованием команд. Опять же Сеть полнится кучей описаний разных протоколов пе- редачи ИК-команд, как более или менее стандартных (RC4/5/6), так и всякой раз- ной экзотики. По большому счету тут никто никому ничем не обязан, единых при- нятых стандартов не существует, и любая фирма может применять тот протокол и способ кодирования, который ей больше нравится. А нам надо в это вникать? Со- вершенно нет: задача точного декодирования и повторения команды (как это, по- вторим, сделано в библиотеке iRremote.h) возникает при желании построить свой пульт управления в пику уже готовым фирменным устройствам. Для нас актуальна совершенно иная задача — нам надо точно различать одну или несколько команд, посланных с конкретного пульта, чтобы они выполняли требуемое нам действие, и при этом устройство должно быть по максимуму защищено от ложных срабатыва- ний. Причем требуемое число команд обычно невелико и не превышает десятка — скажем, для роботизированной тележки или катера с дистанционным управлением в базовом варианте их всего пять: движение вперед-назад, поворот вправо-влево, экстренный стоп. «Настоящий» код команды нас при этом совершенно не волнует — главное, чтобы команды четко различались, потому заниматься муторной расшифровкой «офици- альных» протоколов нецелесообразно (их к тому же достаточно много разных, и не всегда точно известно, какой именно задействован в том или ином случае). Мы применим простой в повторении и достаточно универсальный способ декодирова- ния команд, который основан на использовании UART в качестве приемника по- следовательности битов. В большинстве протоколов биты (или в случае манчестер- ского кодирования — полубиты) имеют длительность порядка 400-600 мкс, и под них очень хорошо подходит стандартная скорость приема UART, равная 2400 бит/с. При этом полученные последовательности байтов окажутся далеки от «настоящих» кодов, но обязательно будут различаться для разных команд и точно воспроизводиться от раза к разу. Чем мы и воспользуемся для наших целей — 2400 бит/с можно воспроизвести на контроллере с любой частотой от 1 МГц и вы- ше. Единственное требование здесь такое же, как при любом использовании UART, — «кварцованная» тактовая частота. Но и оно в нашем случае не является абсолютным, если определить возможные отклонения и учесть их в программе (пример вы увидите далее). Или, что еще проще, просто пропускать ошибочно при- нятые команды: если вы потренируетесь с программой распознавания кодов, то увидите, что ситуации, когда от двух разных кнопок возникает одинаковый код, не бывает, а какое-то количество несрабатываний можно допустить. Мы рассмотрим пример с двумя командами с помощью пульта от китайского мас- сажера — я намеренно выбрал простейший пульт не от известных брендов. Пульт имеет всего три кнопки, из которых нам понадобится две: «Timer» и «On/Off», и работает на частоте 38 кГц. Выбираем достаточно большой и чувствительный при- емник TSOP1738, но можно применить любой другой на эту частоту. Подключение
424 Часть III. Практическое программирование микроконтроллеров AVR приемника показано на рис. 16.12, и там же дана разводка его выводов (учтите, что у приемников других типов разводка может отличаться). Кроме кнопки, к контрол- леру подключены два светодиода: Ledl и Led2, имитирующие исполнительные механизмы. +5 в к RX адаптера UART для пюста TSOP1738 OND +5 В 1 Reset 2RXD(PD0) 3TXD(PD1) 4XTAL2(PA1) 5 XTAL1 (РАО) 6 INTO (PD2) 7 INT1 (PD3) 8 PD4 (TO) 9PD5(T1) 10GND ATtiny2313 Led1 Led2 Рис. 16.12. Подключение ИК-приемника TSOP1738 к ATtiny2313 Сначала нам потребуется определение кодов выбранных кнопок. На каждое нажа- тие любой пульт выдает последовательность байтов, которая длится до нескольких десятков миллисекунд. Отдельную программу для определения кодов вы найдете в архиве по адресу, указанному во введении (файл IR_code.asm). Она предельно про- ста и состоит из бесконечного цикла приема и передачи по UART, настроенного на скорость 2400 бит/с. Контроллер подключается через UART-адаптер к упомянутой в предыдущей главе коммуникационной программе СОМ2000 (не забудьте настро- ить ее на нужную скорость приема). Направив пульт в сторону приемника, вы на- жимаете на выбранную кнопку, и полученная пачка кодов немедленно появляется в коммуникационной программе. Для надежности нажатие каждой кнопки повто- ряем несколько раз с паузой не менее секунды, чтобы разделить группы байтов друг от друга. Последовательности, полученные для двух кнопок пульта массажера, у меня выглядели следующим образом: "Timer" 00 4В 7В 7В CF 4F 4F 7В 4В 7В CF CF FF 00 FF 00 4В 7В 7В CF 4F 4F 7В 4В 7В CF CF FF 00 FF 00 4В 7В 7В CF 4F 4F 7В 4В 7В CF CF FF 00 FF 00 4В 7В 7В CF 4F 4F 7В 4В 7В CF CF FF 00 4В 7В 7В СЕ 4F 4F 7В 4В 7В CF CF FF 00 4В 7В 7В CF 4F 4F 7В 4В 7В CF СЕ FF 00 FF
Глава 16. Некоторые Arduino-задачи на ассемблере 425 "On/Off" 00 4В 7В 7В CF 4F CF СВ 4В 7В 7В CF FF 00 FF 00 4В 7В 7В CF 4Е CF СВ 4В 7В 7В CF FF 00 FF 00 4В 7В 7В CF 4F CF СВ 4В 7В 7В CF FF 00 FF 00 4В 7В 7В CF 4F CF СВ 4В 7В 7В CF FF 00 FF 00 FF 00 4В 7В 7В CF 4F CF СВ 4В 7В 7В CF FF 00 FF 00 4В 7В 7В CF 4F CF СВ 4В 7В 7А CF FF 00 FF Как видим, 7-й и 8-й байты в посылке от каждой кнопки различаются (для нагляд- ности они подчеркнуты). Теперь можно составить программу (файл IR_pult_ proba.asm в архиве по адресу, указанному во введении), которая будет по нажатию каждой из кнопок зажигать соответствующий светодиод (см. рис. 16.12). Программа предусматривает ситуацию ошибок при приеме байтов за счет флуктуации такто- вой частоты при работе от встроенного генератора, потому анализирует оба разли- чающихся байта и возможные их варианты. В простейшем случае «кварцованной» частоты определения одного из различающихся байтов для каждой из команд вполне достаточно. Управление серводвигателем Серводвигатель представляет собой моторчик с редуктором, вал которого можно поворачивать на заданное число угловых градусов относительно нулевого положе- ния (как правило, в пределах полуокружности). Более распространенные и универ- сальные шаговые двигатели могут делать то же самое и притом в произвольном диапазоне углов поворота, но принцип управления у них другой, и без специальной микросхемы-драйвера подключить их к контроллеру весьма затруднительно. А серводвигатели управляются через единственный вывод напрямую от сигналов логических уровней (правильное подключение серводвигателя показано на рис. 16.13). +5 В к адаптеру UART ТХ «* Hhr 4 МГц! 1 1 Reset 2RXD(PD0) 3TXD(PD1) 4XTAL2(PA1) 5 XTAL1 (РАО) 6 INTO (PD2) 7 INT1 (PD3) 8 PD4 (TO) 9PD5(T1) 10GND ATtiny2313 Vcc20 SCK19 MISO 18 MOS117 PB416 PB315 PB214 PB1 13 PB0 12 PD6 11 *»+5В С2 +5,0 B, 2,0 A '' C3 220,0 желт 1 (оранж) FS5109M Рис. 16.13. Подключение серводвигателя к контроллеру ATtiny2313
426 Часть III. Практическое программирование микроконтроллеров AVR Большое потребление в момент трогания может сбить контроллер, потому вывод питания достаточно мощного серводвигателя лучше подключать к отдельному ис- точнику. Это в меньшей степени касается маломощных сервомоторов с крутящим моментом 1,3-2,0 кГсм (типа SG90), но для более мощных с усилием до 10 кГсм (FS5106, FS5109M) становится обязательным условием стабильной работы— пи- ковое потребление у них может составлять до 2-3 А. Подробности В кратких описаниях сервомоторов на сайтах интернет-магазинов величину тока по- требления вы не найдете. Отсутствие такого важного параметра легко объяснимо — у сервомоторов потребляемый ток зависит от механической нагрузки на валу. Если сервомотор крутит лишь самого себя, то вся потребляемая энергия уходит на преодо- ление только трения в шестеренках, и ток минимален. А вот если он будет поворачи- вать какую-то внешнюю нагрузку, то ток возрастает. В установившемся режиме этот ток, очевидно, будет иметь какую-то определенную величину. Рассчитать ток, соответствующий номинальной нагрузке в установившемся режиме, несложно. Он, как правило, определяется максимальным усилием, которое может развить привод, т. е. величиной крутящего момента. Для сервопривода FS5109M, например, указано, что крутящий момент равен от 9 кГсм при 4,8 вольтах питания до 10,2 кГсм при 6 вольтах, т. е. примерно 10 кГсм при 5-вольтовом питании. Исходя из крутящего момента, можно рассчитать потребляемую мощность — она равна произ- ведению крутящего момента на угловую скорость вращения. А последняя тоже приво- дится в характеристиках: в рассматриваемом случае 60° за 0,18 сек при 4,8 В или 60° за 0,16 сек при 6 В. В привычных единицах это составит около 1 оборота в секунду или 60 оборотов в минуту. Если вместо положенных ньютонов на метр и радианов в се- кунду в произведение подставлять наши килограммы (точнее, килограммы силы) на сантиметр и обороты в минуту, то формула будет выглядеть так: Р (ватт) = М (кгсм) х п (об/мин) / 97,37 То есть 10 кГсм и 60 об/мин означают мощность около 6 ватт. Соответственно, по- требляемый при максимальной нагрузке и питании 5 вольт ток составит около 1,2 ам- пера. В момент трогания, как известно, все электродвигатели потребляют больше, чем в установившемся режиме, так что источник нужно рассчитывать на ток в 2-3 А. Это, кстати, существенно превышает возможности не только USB, но и встроенного стаби- лизатора Arduino, и служит главной причиной неработоспособности Arduino-макетов с серводвигателями. Принцип управления серводвигателями весьма заковыристый и неочевидный. Если шаговые двигатели просто сдвигаются на количество шагов, равное числу подан- ных импульсов, и затем останавливаются, то серводвигатели устанавливаются все- гда в определенное положение, задаваемое длительностью подаваемых импульсов, и не сдвинутся с места, пока эта длительность не изменится. Импульсы должны подаваться с частотой около 50 Гц. Сделано большинство серводвигателей так, что длительность около 500 мкс соответствует положению, принятому за начало отсче- та (0°), длительность 1500 мкс— среднему положению (90°), а длительность 2500 мкс— противоположному крайнему (180°). У разных двигателей эти величи- ны могут немного различаться, но не настолько существенно, чтобы привести к неработоспособности. Такой принцип управления называется «модуляцией длительности импульсов» (от англ. Pulse Duration Modulation) и по сути является разновидностью хорошо знако-
Глава 16. Некоторые Arduino-задачи на ассемблере 427 мой нам широтно-импульсной модуляции (ШИМ) с фиксированной на уровне 50 Гц несущей частотой. На этом принципе основана Arduino-библиотека servo.h, общая для разных моделей серводвигателей. Единственная функция этой библио- теки: servo.write (<угол поворота>) — использует ШИМ от Timerl, и устроена так, что с ее помощью можно управлять аж 12-ю серводвигателями, подключенными к разным выводам. Мы не будем связываться с ШИМ, а упростим себе задачу: сформируем нужные длительности с помощью задержек. Задержки будем вычислять, исходя из заданно- го угла. Согласно приведенным ранее правилам, длительность Тс углом поворота d будет связана соотношением Т= 500 + t/* 1000/90 = 500 + 11,1ч/. Чтобы не терять в точности при вычислениях по этой формуле, мы умножим коэффициент 11,1 на 16 (получив число 178) и результат умножения будем делить на 16, сдвигая на 4 раз- ряда вправо. Алгоритм формирования импульса приведен в листинге 16.1. ;в temp угол поворота градусов ldi RazrO,178 ;(2500-500)/180 = 178 коэффициент пересчета rcall mp8x8u ;умножение temp на RazrO /результат в Razrl:RazrO делим на 16 — 4 сдвига вправо lsr Razrl ror RazrO lsr Razrl ror RazrO lsr Razrl ror RazrO lsr Razrl ror RazrO /добавляем 500 subi RazrO,Low(-500) sbci Razrl,High(-500) ;b Razrl:RazrO — длительность в микросекундах sbi PortD,6 ;PD6 в 1 impuls: /формируем импульс длит Razrl:RazrO микросекунд subi RazrO,1 sbci Razrl,0 brcc impuls cbi PortD,6 ;PD6 в 0 subi RazrO,1 Приведенный алгоритм ссылается на универсальную процедуру перемножения двух байтов (с 16-битовым результатом) mp8x8u, которую мы ранее не использова- ли, а только упоминали в главе 8. Процедура в программе (см. далее) напрямую за- имствована из «аппноты» 200, изменены только имена регистров. Конечно, в Mega
428 Часть III. Практическое программирование микроконтроллеров AVR можно перемножать гораздо быстрее с помощью команды mul, но тогда в ATtiny2313 программа работать не будет. Приведенную в листинге 16.1 процедуру вставим в прерывание переполнения TimerO, с помощью которого будем формировать частоту 50 Гц. Тестовую про- грамму, в которой величина угла задается из компьютера через UART непосредст- венно в градусах, вы найдете в архиве по адресу, указанному во введении (файл Servojest.asm). По умолчанию сервомотор установится на нулевой угол. Здесь мы переключаем вывод PD6, но никто не мешает одновременно переключать сколько угодно других выводов, причем каждый по своему заданному значению угла. Удобно то, что для этого требуется всего одно прерывание на все подключенные выводы. Большая часть серводвигателей имеет диапазон углов поворота в пределах от 0 до 180°. Есть серводвигатели, которые могут вращаться на полный круг 360°, но это не то же самое, что обычные, с фиксированными положениями. У них отсутст- вует ограничитель, потому тот же принцип управления приводит к иным последст- виям: длительностью импульса задаются скорость и направление вращения. Ней- тральное положение (остановка вращения) обычно соответствует положению 90°. С помощью таких сервомоторов можно, например, управлять вращением колес са- модвижущегося робота, задавая повороты и кручение на месте. Но, конечно, обыч- ные серводвигатели с заданием положения имеют больше потенциальных приме- нений — например, на них можно построить вполне серьезную охранную систему с видеокамерами, автоматически следящими за движущимися объектами. Однако обсуждение таких тем увело бы нас далеко за пределы поставленной зада- чи— освоить программирование AVR-контроллеров на самом низком уровне с помощью ассемблера и самых простых программных инструментов. Будем счи- тать, что с этой задачей в первом приближении мы справились, а все остальное зависит от целей и желаний читателей.
ПРИЛОЖЕНИЕ 1 Ликбез В этом приложении кратко излагаются те необходимые основы электроники, кото- рые в многочисленных пособиях по Arduino не считают нужным изложить нович- кам. Сосредоточенные на составлении программ, авторы рекомендаций по Arduino часто даже не приводят принципиальных схем, видимо, считая это второстепенным делом, а основы систем счисления, очевидно, полагают известными каждому школьнику. Мы, конечно, не можем изложить тут все необходимые азы (в частно- сти, вынужденно обходим закон Ома и все с ним связанное), и тех, кому приведен- ных далее сведений окажется недостаточно, отсылаем к книге [10]. Десятичные, двоичные и шестнадцатеричные числа Для счета в позиционной системе сначала выбирается некоторое число р, которое носит название основания системы счисления. Тогда любое число в такой системе может быть представлено следующим образом: ап-рп + Яп-г/"1 + - + <*vpl + ао-р°. В самой записи числа степени основания обычно подразумеваются, а не пишутся (и для записи основания даже нет специального значка), поэтому запись будет представлять собой просто последовательность ап... а0. Отдельные позиции в запи- си числа называются разрядами. Обратим внимание на то, что запись производится справа налево (т. е. именно в таком порядке возрастают степени основания) — обычная математическая запись выглядела бы наоборот. Например, в десятичной системе (т. е. в системе с основанием 10) полное представление четырехразрядного числа 1024 таково: 1-Ю3 + 0-Ю2 + 2-Ю1 + 4-10°. Для любой системы с основанием р нужно ровно р различных цифр — т. е. значков для изображения чисел. Для десятичной системы их десять — это и есть известные всем символы от 0 до 9. Заметим, что выбор начертания этих значков совершенно произволен, и в разных языках они могут быть различными.
430 Приложение 1 В двоичной системе необходимо всего два различных знака для цифр: 0 и 1. Это и вызвало столь большое ее распространение в электронике: смоделировать два состояния электронной схемы и затем их безошибочно различить неизмеримо про- ще, чем три, четыре и более, не говоря уж о десяти. Число, например, 1101 тогда будет выглядеть так: 1-23 + Ь22 -h 0-21 + Ь2° = 13. Чтобы отличить запись числа в различных системах, часто внизу пишут основание системы: 11012= 13н>. Шестнадцатеричная система имеет, как ясно из ее названия, основание шестна- дцать. Для того чтобы получить шестнадцать различных знаков, изобретать ничего нового не стали, а просто использовали те же цифры от 0 до 9 для первых десяти и заглавные латинские буквы от А до F для знаков с одиннадцатого по шестнадцатый (часто вместо заглавных букв употребляют и строчные с теми же значениями). Та- ким образом, число 13ю выразится в шестнадцатеричной системе, как просто D]6. Соответствие шестнадцатеричных знаков десятичным числам следует выучить наи- зусть: А — 10, В — 11, С — 12, D — 13, Е — 14, F — 15 (см. также таблицу далее). Значения больших чисел вычисляются по обычной формуле, например: A2FC16 =10-163 + 2-162+15-161 +12-16° = 40960+ 512+ 240+12 = 4172410. Отдельные разряды двоичного числа называются битами, а отдельные разряды ше- стнадцатеричного— тетрадами (потому что в них четыре двоичных знака). Младший бит (разряд) двоичной записи числа называют сокращением LSB (Least Significant Bit), старший — MSB (Most Significant Bit). Английские сокращения не стоит путать с русскими, где буква «М» означает, естественно, «младший», так что LSB переводится как МЗР (младший знаковый разряд), a MSB — как СЗР (старший знаковый разряд). Самая употребляемая единица информации — байт, — состоит из двух тетрад, или восьми двоичных разрядов (отсюда и название: byte представляет собой сокраще- ние от BinarY digiT Eight, восемь двоичных цифр). Один двоичный разряд может представить два числа (0 и 1), одна тетрада— 16 чисел (от 0 до 15), один байт — 256 чисел (от 0 до 255). Емкость одного регистра 8-разрядного AVR-контроллера равна одному байту. В Arduino однобайтовому числу соответствуют типы данных char (диапазон от -128 до 127), unsigned char и просто byte (от 0 до 255, т. е. от 0 до 28-1). В шестнадцатеричной форме максимальное число, представляемое одним байтом, запишется как FFj6 (т. е. содержит единицы во всех двоичных разрядах). Более крупные единицы: килобайт, мегабайт и т. д. образуются последовательным умножением на 1024 (а не на 1000, как в десятичной системе), но непосредственно в электронике употребляются только для характеристики объема памяти или ско- ростей передачи данных. Вместо них в расчетах применяются числа, кратные од- ному байту: двухбайтовые или четырехбайтовые слова. Двухбайтовое слово соот- ветствует в Arduino типу int (от -32 768 до 32 767), а также unsigned int или word (от 0 до 65 535, т. е. от 0 до 216-1). Четырехбайтовые величины в Arduino соответству-
Ликбез 431_ ют типу long (0 до 4 294 967 295, т. е. от 0 до 232-1). Максимальное число в шестна- дцатеричной форме для двухбайтового числа будет выглядеть как FFFFi6, а для четырехбайтового— как FFFFFFFFi6. Для практических расчетов емкости двух- байтового числа часто недостаточно, а четырехбайтовое излишне велико, потому мы в этой книге еще употребляем нестандартный трехбайтовый формат, где мак- симальное число равно FFFFFFi6 = 16 777 215 = 224-1. Подробности Важная вещь, на которую стоит обратить внимание: конечная величина 8-битного диапазона в 256 градаций равна числу 255, 10-битного диапазона в 1024 градации — числу 1023 и т.д. Даже опытные электронщики часто теряются: например, если у вас есть 10-битный аналого-цифровой преобразователь (АЦП), у которого вся шкала соответствует одному вольту, то для определения цены одной градации всю шкалу надо делить на 1024 или 1023? Как минимум, на половине интернет-ресурсов, посвя- щенных тем или иным аспектам применения АЦП в AVR-контроллерах, этот момент отражен неправильно. Запомните наизусть, чтобы не плавать в таких случаях: всю шкалу надо делить на полные величины диапазона (256 и 1024 для этих примеров). В главе 11 этот вопрос рассматривается подробнее. Запись чисел в различных форматах Если система не указана, то имеется в виду обычно десятичная, но не всегда — час- то, когда из контекста понятно, что идет речь об электронных устройствах, не ука- зывают не только основание два, но и под словом «разрядность» имеют в виду количество именно двоичных, а не десятичных разрядов (таков, скажем, смысл терминов «24-разрядный цвет» или «8-разрядный контроллер»). Шестнадцатеричный формат записи часто еще обозначают как HEX (hexadecimal), двоичный— как BIN (binary), а десятичный— как DEC (decimal). Кроме этого, в ходу еще так называемый двоично-десятичный формат— BCD (binary-coded decimal), о котором далее. С помощью матричных шрифтов на компьютерах с тек- стовыми дисплеями воспроизводить индексы было невозможно, и вместо того, чтобы обозначать основания системы цифрой справа внизу, их стали обозначать буквами: «В» (или «Ь») — означает двоичную систему, «Н» (или «h») — шестна- дцатеричную, отсутствие буквы означает десятичную систему: 13 = 00001101b = 0Dh. Такая запись принята в языке ассемблера для процессоров Intel и одно время была общепринятой. Популярность языка С внесла в это дело некоторый разнобой: там десятичная система не обозначается никак, двоичная— буквой «Ь» (см. далее), а вот шестнадцатеричная — буквой «х», причем запись во всех случаях предваря- ется нулем (чтобы не путать запись числа с идентификаторами переменных, кото- рые всегда начинаются с буквы): 13 = 0Ь00001101 = OxOD. Такая запись также работает и в ассемблере для AVR. Шестнадцатеричные числа в ассемблере AVR можно обозначать также в «паскалевско-мотороловском» стиле, предваряя их знаком $ ($00), а вот «интеловский» способ (00h), напоминаем, в AVR
432 Приложение 1 не работает. В этой книге мы пользуемся преимущественно более лаконичным сти- лем языка Pascal, предваряя шестнадцатеричные числа знаком доллара. Создатели стандартов языка С в своем стремлении запутать пользователей как можно больше приняли еще запись в реже употребляемой восьмеричной системе, как числа с ведущим нулем, — например, 77 означает просто десятичное 77, а вот 077 будет означать 7-8 + 7 = 63ю. Это надо учитывать, потому что и среда Arduino, и AVR-ассемблер принимают такую запись, и это может привести к недоразуме- ниям. Следует учитывать, что стандартами языков С и C++ запись констант в двоичном виде не поддерживается, потому с этим вопросом существует большая путаница в языках на их основе. Созданный на основе C/C++ язык Arduino унаследовал от AVR-ассемблера поддержку такой записи, но, формально говоря, в нем определена другая форма представления двоичных чисел: с заглавной латинской В перед чис- лом (В00001101). В Arduino можно использовать обе этих формы, а вот в AVR- ассемблере — только первую. Нужно также учитывать, что во избежание неодно- значностей не следует записывать в двоичном виде числа более одного байта. В книге часто встречаются примеры, когда большое десятичное число нужно поде- лить на байты, записывая их в отдельные разряды. Это можно делать с помощью штатных средств AVR-ассемблера (функции low и high — см. главу 6). В явном ви- де (так, как мы задаем время задержки в многочисленных случаях применения про- цедуры Delay по всему тексту книги), десятичное число любой длины можно пре- образовать в шестнадцатеричную запись с помощью обычного калькулятора Windows, если выбрать для него вид «Программист». Отрицательными и дробными числами мы в этой книге не пользуемся (подробнее об этом см. главу 8\ потому рассказывать о них не будем. Двоично-десятичный формат BCD О существовании двоично-десятичного формата записи чисел (BCD) пользователи языков высокого уровня (в том числе и Arduino) часто не осведомлены вовсе. Но в электронике его поневоле приходится использовать, когда речь идет о выводе, например, на цифровой дисплей. Для этой цели приходится преобразовывать дво- ичные/шестнадцатеричные числа в десятичные и хранить их в таких же байтовых регистрах или ячейках памяти. Это можно делать двумя путями: в виде упакован- ного и неупакованного BCD. Неупакованный формат попросту означает, что мы тратим на каждую десятичную цифру не тетраду, как минимально необходимо, а целый байт. Зато при этом не возникает разночтений: 05i6= 05 ю, и никаких проблем. Ясно, что это крайне неэкономично — байтов для записи числа требуется в два раза больше, а старший полубайт при этом все равно всегда ноль. Потому BCD-числа при хранении и передаче по каналам связи всегда упаковывают, занимая и старший разряд второй десятичной цифрой. Скажем, число 59ю при этом запишется не как два байта: 05 и 09, а как просто 59i6. Однако обращаться с ним, как с обычным чис- лом, нельзя: записи 59!6 соответствует число 516 + 9 = 89ю, что в рассматриваемом
Ликбез 433 случае будет ошибкой. Поэтому в общем случае перед проведением операций с упакованными BCD-числами их распаковывают, перемещая старший разряд в отдельный байт и заменяя в обоих байтах старшие полубайты нулями. В качестве примера хранения чисел в упакованном BCD-формате можно привести значения часов, минут и секунд в микросхемах энергонезависимых часов RTC (см. главу 13). Arduino-библиотеки делают распаковку-упаковку незаметно для пользователя, а нам придется заниматься этим самостоятельно. Перевод из одной системы счисления в другую Для перевода шестнадцатеричной или двоичной записи в десятичную надо число просто разбить на тетрады и перевести каждую в отдельности. После этого деся- тичное число вычисляется по общей формуле, как последовательное умножение разрядов на степени числа 16 (пример см. в разд. «Десятичные, двоичные и шест- надцатеричные числа»). Для перевода тетрад в десятичную форму несложно за- помнить следующую табличку (см. табл. П1.1). Таблица П1.1 DEC HEX BIN DEC HEX BIN 0 0 0000 8 8 1000 1 1 0001 9 9 1001 2 2 0010 10 A 1010 3 3 0011 11 В 1011 4 4 0100 12 С 1100 5 5 0101 13 D 1101 6 6 0110 14 E 1110 7 7 0111 15 F 1111 Например, шестнадцатеричное число FF в двоичном виде будет выглядеть как во- семь единиц: 1111 1111. В десятичный вид это переводится как FFi6=Fx 16+ F или 15 х 16+15 = 25510. Сложнее переводить из десятичной системы, и для этого в учебниках рекомендует- ся устрашающая процедура, основанная на делении столбиком. При некотором на- выке небольшие числа можно научиться переводить в уме, руководствуясь этой же табличкой: их разбивают на сумму чисел, кратных тетраде, и переводят по отдель- ности — например, 59 есть сумма 3 16+11= $ЗВ. Для любого электронщика также полезно знать таблицу степеней двойки до 16 (см. табл. Ш.2). Таблица П1.2 21 2 29 512 22 4 2ю 1024 2з 8 2и 2048 2" 16 212 4096 2s 32 213 8192 2е 64 214 16384 27 128 2,5 32768 28 256 216 65536
434 Приложение 1 Эти значения в применениях цифровых электронных схем встречаются буквально на каждом шагу, потому их очень невредно выучить наизусть. Булевы операции Оперировать с логическими функциями приходится все время даже в Arduino. Но там на поверхностном уровне можно обойтись без особого вникания в сущность производимых действий, понятных на интуитивном уровне: «если нечто больше того, то сделать то-то, иначе другое». Единственное, что знают обыкновенные пользователи Arduino непосредственно о логических операциях,— есть такая переменная типа boolean, которая может принимать значение «правда» (true, или значение, отличное от нуля) или «ложь» (false, значение, равное нулю). Переменная типа boolean предназначена для удобного вычисления логических выражений, и в этом качестве весьма полезная вещь. В ассемблере же (как и в электронике вообще) больших логических выражений обычно вычислять не приходится, зато сплошь да рядом приходится иметь дело с манипулированием битами, а для этого абсолютно необходимо четкое понимание элементарных логических операций, для выполнения которых есть специальные команды. Базовые операции математической логики (называемой еще булевой алгеброй), с которыми нам придется имеет дело, следующие: □ операция логического сложения — операция объединения (дизъюнкция), опера- ция «ИЛИ» («OR») — в языках программирования, основанных на С, обознача- ется знаком вертикальной черты |; □I операция логического умножения (конъюнкция) — операция пересечения, опе- рация «И» («AND») — в языках программирования, основанных на С, обознача- ется знаком &; □ операция отрицания (инверсия)— операция «НЕ» («NOT»)— в языках про- граммирования, основанных на С, обозначается восклицанием ! или знаком ~. Инверсию также часто обозначают чертой над символом операнда (надстроч- ной чертой); □ операция «исключающее ИЛИ» — операция несовпадения («XOR») — вообще говоря, операция, производная от предыдущих трех основных, но в электронике встречается очень часто. В языках программирования, основанных на С, обозна- чается знаком А. Заметки на полях Заметим, что в языке С есть две разновидности логических операций: обычные («ло- гическое И» &&, «логическое ИЛИ» ||) и поразрядные («поразрядное И» &, «поразряд- ное ИЛИ» |), а также, соответственно, «логическое НЕ» (!) и «поразрядное НЕ» (~). Термин «поразрядные» означает, что они применяются к каждому двоичному разряду в отдельности. Все операции в электронике, как вы увидите дальше, соответствуют поразрядным операциям, и потому вдаваться в тонкости различий мы не будем.
Ликбез 435 Во всех этих операциях изначально операндами могут выступать только единичные биты. Для понимания их смысла служат так называемые таблицы истинности (см.табл. П1.3). Таблица П1.3 «ИЛИ» Вх1 0 0 1 1 Вх2 0 1 0 1 Вых 0 1 1 1 «и» Вх1 0 0 1 1 Вх2 0 1 0 1 Вых 0 0 0 1 «Исключающее ИЛИ» Вх1 0 0 1 1 Вх2 0 1 0 1 Вых 0 1 1 0 Словесно это можно выразить так: в результате операции «ИЛИ» единица на выхо- де возникает тогда, когда хотя бы один из операндов равен единице, в результате операции «И» единица на выходе возникает только тогда, когда все операнды рав- ны единице, в результате операции «исключающее ИЛИ» единица на выходе воз- никает тогда, когда операнды не равны друг другу. Операция отрицания «НЕ», примененная к одному биту, меняет 1 на 0 и наоборот. Заметьте, что смена положи- тельной логики на отрицательную (когда за активный уровень принимается ноль, а не единица) меняет все функции «ИЛИ» на «И» и наоборот. Логическая операция, примененная к многоразрядным числам («поразрядная»), бу- дет означать применение ее к отдельным битам операндов. Иными словами, коман- да or, соответствующая операции «ИЛИ», примененная к двум байтовым регист- рам, означает, что каждый двоичный разряд результата операции равен результату от применения функции «ИЛИ» к соответствующим разрядам первого и второго байта согласно таблице истинности этой функции. Существует еще много разных типов обозначений этих операций в различных об- ластях, но мы еще остановимся на тех, которые приняты для обозначения логиче- ских элементов интегральных микросхем, т. к. они будут вам встречаться на прин- ципиальных схемах (рис. П1.1). В интегральной электронике «чистые» логические функции «И» и «ИЛИ» встреча- ются редко, потому на рис. П1.1 приведены обозначения стандартных комбиниро- — ■J- -- И-НЕ ■\- ИЛИ-НЕ -о- НЕ — > п- Исключающее ИЛИ Рис. П1.1. Обозначения логических элементов на принципиальных схемах: вверху — отечественное; внизу— принятое в США
436 Приложение 1 ванных элементов «И-НЕ» и «ИЛИ-НЕ» (их выходы будут соответствовать инвер- сии последней колонки таблиц истинности, приведенных ранее). Заметьте, что функция «НЕ» здесь обозначается кружочком у соответствующего вывода, тогда как в обозначениях выводов контроллеров и других сложных микросхем чаще при- нято ставить черту инверсии над наименованием вывода, хотя иногда (как, напри- мер, в схемах триггеров) применяют и то и другое совместно. Об обозначениях на принципиальных схемах Обозначения на принципиальных схемах не представляют собой китайской грамо- ты, и любой может очень быстро научиться их читать, даже если не умел этого ра- нее. Излагать здесь все нюансы требований к компонентам и принципы их обозна- чения на схемах неуместно, потому интересующихся подробностями отсылаем к моей книге [10], а здесь только скажем пару слов по поводу обозначений типов и номиналов компонентов. И то, и другое в общем-то полагается приводить на схеме (или в отдельной спецификации к ней, если делать по стандарту) полностью, но на практике прибегают к ряду сокращений и упрощений, на которых мы сейчас и ос- тановимся. Прежде всего — о компонентах, для которых типы обычно не указываются. Рези- сторы, если не оговорено иное, подразумеваются самые обычные— С 1-4/0,25 Вт, конденсаторы неполярные— керамические К10-17, полярные— электролитиче- ские К50-53 (или их импортные аналоги). Все остальное, не оговоренное специаль- но, подразумевает «любого типа», конечно, в пределах обычных допусков, — на- пример, электролитические конденсаторы должны быть на напряжение не меньше, чем 1,3-1,5 напряжения питания и т. д. В двух словах о принципах указания номиналов резисторов и конденсаторов. В российской практике приняты следующие сокращения: □ для резисторов: омы (100 Ом)— целое число без указания единиц измерения («100»). На килоомы (100 кОм) указывает строчная буква «к» («100 к», нередко уточняется: «100 кОм»). На мегаомы (100 МОм)— прописная «М» («100 М» или полностью «100 МОм», не путать с миллиомами «100 мОм»!); □ для конденсаторов: просто «100» означает 100 пикофарад. Нанофарады обозна- чаются буквой «н» («100 н», иногда уточняется: «100 нФ»). Микрофарады пи- шутся в виде действительного числа с запятой («100,0», «0,1», иногда уточняет- ся: «100,0 мк», «0,1 мкФ»). Звездочкой («R1*») сопровождается обозначение компонента, если его величина требует индивидуального подбора. Еще один момент— вы, наверное, уже заметили, что почти все компоненты, не только микросхемы, имеют добавки к наименованию типа, означающие какие-то их разновидности. Если на схеме никакие такие добавки не указаны, то это также оз- начает, что подходит любая их разновидность (например, если указан ATmega8, то можно использовать и ATmega8A, и ATmega8L и другие модификации, причем в любом корпусном исполнении). Для светодиодов обычно не указывается даже тип, т. к. осветительные диоды ни- кому не придет в голову использовать в таких схемах, а абсолютно все сигнальные
Ликбез 437_ взаимозаменяемы и отличаются на уровне «красный»-«зеленый», «круглый»- «плоский» и т. п. Тем не менее, если есть подозрение, что имеющийся у вас свето- диод может оказаться на пределе возможностей (например, ток более 20-30 мА для одиночного светодиода или более 10 мА для сегмента индикатора), то следует про- верить его данные по техническому описанию. Вторая ситуация, когда требуется проверка светодиода по характеристикам: низкие напряжения питания и ситуации, когда желательно обеспечить минимальное по- требление. Обычно можно считать, что на светодиоде прямое падение напряжения составляет от 1,5 до 2,5 вольт, и исходя из этого обычно рассчитываются токоогра- ничивающие резисторы. Но некоторые светодиоды, особенно высокой яркости, мо- гут иметь прямое падение до 3,5—4 вольт, что уже очень существенно скажется на величине резистора при питании 5 вольт, а при питании 3 вольта они вообще не заработают. То же самое относится к семисегментным светодиодным индикаторам: у типов с размером знака больше, чем полдюйма, сегменты, как правило, составле- ны из двух и более последовательно включенных светодиодов, и они могут не за- работать даже от питания 5 вольт. Причем в таких ситуациях лучше не полагаться на приводимые в обобщенных таблицах максимальные значения, а внимательно изучить имеющийся в подробном описании график в координатах «прямой ток» — «напряжение». Если этого не сделать, то в оценке общего потребления схемы, со- держащей десяток-другой светодиодов, можно промахнуться в разы. Для семисегментных светодиодных индикаторов, кроме того, следует проверять общее количество выделяющегося тепла для конкретного типа. Так как светодиод в них залит компаундом, то отвод тепла от него плохой, и допустимое общее коли- чество тепла для этих индикаторов невелико: обычно оно в пределах 100 мВт или менее, в зависимости от размера. Превышение этой величины может и не сожжет индикатор, но заставит его светиться хуже, так как яркость светодиода падает с температурой, и долговечность может упасть. Рассчитать выделяющуюся тепло- вую мощность можно по формуле: W= £/„р-/„мп-7/и, где W— тепловая мощность, мВт; /ИМП9 мА— импульсный ток через сегмент; С/пр, В — прямое падение напряжения при токе /ИМп (точное значение проверяется по графику в описании); п — число разрядов дисплея при динамической индикации (для часового 4-разрядного индикатора равно 4). При статической индикации, когда каждый разряд горит все время, п = 1. Например, для красного индикатора SA56-11SRWA прямое напряжение, согласно графику в описании, при токе 20 мА составит 1,85 В, тогда по формуле выше W = 64,75 мВт, что меньше предельно допустимой величины для этого типоразме- ра, равной 75 мВт. Для такого же желтого индикатора SA56-11YWA, при том же токе 20 мА прямое напряжение будет равно 2,1 В, поэтому W = 73,5 мВт, что почти равно допустимому значению. А для зеленого SC56-11GWA при токе 20 мА прямое напряжение будет равно уже 2,2 В, поэтому W = 11 мВт, что пусть немного, но превышает предельно допустимое значение. В двух последних случаях придется буквально «ловить блох» — уточнять все падения напряжения на ключах и очень тщательно подбирать токоограничивающие резисторы, не доверяясь тому, что ука- зано на схеме.
ПРИЛОЖЕНИЕ 2 Основные параметры некоторых микроконтроллеров Atmel AVR Таблица П2.1. Параметры некоторых MKAtmelAVR Модель ATtinylO ATtiny13 ATtiny2313 ATtiny24 ATtiny25 ATtiny26 ATtiny28L* ATtiny44 ATtlny45 ATtiny84 ATtiny85 ATtiny861 ATmega8 ATmega8515 ATmega8535 ATmega88 ATmega16 ATmega168 ATmega32 ATmega329/3290** Flash (Кбайт) 1 1 2 2 2 2 2 4 4 8 8 8 8 8 8 8 16 16 32 32 EEPROM (байт) 64 64 128 128 128 128 - 256 256 512 512 512 512 512 512 512 512 512 1024 1024 SRAM (байт) 32 64 128 128 128 128 - 256 256 512 512 512 1024 512 512 1024 1024 1024 2048 2048 Линий ввода/ вывода 6 6 18 12 6 16 11 12 6 12 6 16 23 35 32 23 32 23 32 54/69 АЦП (каналов) 4 (8 bit) 4 - 8 4 11 - 8 4 8 4 11 6/8 * - 8 6/8 8 6/8 8 8 Преры- вания 4 9 8 17 15 11 5 17 15 17 15 19 18 17 20 26 20 26 19 25 Внешние преры- вания 4 6 2+8*** 1+12*** 7 1 2+8*** 12*** 7 12*** 7 2 2 3 3 26*** 3 26*** 3 17/32***
Основные параметры некоторых микроконтроллеров Atmel AVR 439 Таблица П2.1 (окончание) Модель ATmega64 ATmega640 ATmega2560 Flash (Кбайт) 64 64 256 EEPROM (байт) 2048 4096 4096 SRAM (байт) 4096 8192 8192 Линий ввода/ вывода 54 86 86 АЦП (каналов) 8 16 16 Преры- вания 34 57 57 Внешние преры- вания 8 32*** 32*** * Оптимизирована для использования в пультах ДУ. ** Имеет встроенный контроллер ЖК-дисплея 4x25 (4x40) сегментов. *** 1, 2 или 3 обычных INT, плюс фупповые прерывания на выводах портов (типа PCINT, либо Low level на порту В). Таблица П2.2. Корпуса МК Atmel AVR (см. также рис. 4.1 в главе 4) Модель ATtinylO ATtiny13 ATtiny2313 ATtiny24 ATtiny25 ATtiny26 ATtiny28L ATtiny44 ATtiny45 ATtiny84 ATtiny85 ATtiny861 ATmega8 ATmega8515 ATmega8535 ATmega88 ATmega16 ATmega168 ATmega32 ATmega329 ATmega3290 Варианты корпусов PDIP 8 SOT 6 PDIP 8 SO 8 QFN 20 PDIP 20 SOIC 20 QFN 20 PDIP 14 QFN 20 PDIP 8 SOIC 8 QFN 32 PDIP 20 SOIC 20 QFN 32 PDIP 28 TQFP 32 QFN 20 PDIP 14 QFN 20 PDIP 8 SOIC 8 QFN 20 PDIP 14 QFN 20 PDIP 8 SOIC 8 QFN 32 PDIP 20 SOIC 20 QFN 32 PDIP 28 TQFP 32 QFN 44 PDIP 40 TQFP 44 PLCC 44 QFN 44 PDIP 40 TQFP 44 PLCC 44 QFN 32 PDIP 28 TQFP 32 QFN 44 PDIP 40 TQFP 44 QFN 32 PDIP 28 TQFP 32 QFN 44 PDIP 40 TQFP 44 QFN 64 TQFP 64 TQFP 100 QFN 64 TQFP 64 TQFP 1Q0
440 Модель ATmega64 ATmega640 ATmega2560 Варианты корпусов QFN 64 TQFP 64 CBGA 100 TQFP 100 CBGA 100 CBGA 100 CBGA 100 TQFP 100 Приложение 2 Таблица П2.2 (окончание) Таблица П2.3. Максимально допустимые значения эксплуатационных параметров МК Atmel AVR Параметр Допустимая рабочая температура («индустриальный» диапазон) Температура хранения Напряжение на любом выводе (кроме вывода RESET) относительно вывода GND Напряжение на выводе RESET относительно вывода GND Максимальное напряжение питания Максимальный ток канала ввода/вывода Максимальный ток выводов питания (Vcc) и GND Предельные значения -40... +85 °С -65...+150°С -1,0...VCc + 0,5B -1,0...+13 В 6,0 В 40 мА 60 мА (ATtiny28x, вывод РА2) 200 или 300 мА Таблица П2.4. Статические параметры (DC) MK Atmel AVR Параметр Входное напряжение низкого уровня Входное напряжение высокого уровня Выходное напряжение низкого уровня линий портов ввода/вывода Обозначение V,L V,H VOL Условия Вывод XTAL1 Остальные выводы Вывод XTAL1 Вывод RESET Остальные выводы /ol = 20 мА, VCC=5B /ol= Ю мА, VCC=3B Модель Все модели Все модели ATtlny ATmega ATtlny ATmega Все модели Все модели Все модели min, В -0,5 -0,5 0,7 Vcc 0,8 Vcc 0,85 Vcc 0,9 Vcc 0,6 Vcc max, В 0,1 Vcc 0,3 Vcc Vcc + 0,5 Vcc+0,5 Vcc+0,5 Vcc+ 0,5 Vcc+0,5 0,6 0,5
Основные параметры некоторых микроконтроллеров AtmelAVR 441 Таблица П2.4 (окончание) Параметр Выходное напряжение высокого уровня линий портов ввода/вывода Обозначение Voh Условия /он = -3 мА, Vcc = 5B /он = -20 мА, Усе = 5 В /0„ = -1,5мА, Vcc = 3 В /он = -ЮмА, Vcc = 3B Модель ATtlny, ATmega 161, 163,323 Остальные ATtiny, ATmega 161, 163,323 Остальные min, В 4,2 2,3 max, В Таблица П2.5. Типовые эксплуатационные параметры МК Atmel AVR Параметр Ток утечки на входе (уровень лог. 0) Ток утечки на входе (уровень лог. 1) Сопротивление под- тягивающего рези- стора в цепи сброса Сопротивление под- тягивающего рези- стора линии порта ввода/вывода Ток потребления, семейство Tiny (без доп. буквы) Обозначение /.L /|Н Rrst R\o /ее Условия Усе = 5,5В Vcc = 5,5В Рабочий режим, Усе = 3 В, F < 4 МГц Рабочий режим, Vcc = 5B, Р>4МГц Режим Idle, Усе = 3 В, F < 4 МГц Режим Idle, Vcc = 5B, F > 4 МГц Режим Power Down WDT вкл , \/cc = 3B Режим Power Down WDT выкл., Усе = 3 В Модель ATmega 8515 Остальные ATmega8 Остальные Все модели Все модели Все модели Все модели Все модели Все модели Все модели Все модели min 100 35 typ 1,5 3,5 9,0 <1,0 max 3 8 3 8 500 120 3-5 5-10 15,0 2,0 Ед. изм. мкА мкА кОм кОм мА мА мкА
442 Приложение 2 Таблица П2.5 (окончание) Параметр Ток потребления, семейство Меда (без доп. буквы) Обозначение /ее Условия Рабочий режим, Vcc = 3 В, F = 4 МГц Рабочий режим, Усе = 5 В, F = 8 МГц Режим Idle, VCC = 3B, F = 4 МГц Режим Idle, V« = 5B, F = 8 МГц Режим Power Down WDT вкл., \/cc = 3B Режим Power Down WDT выкл., Vcc = 3 В Модель Все модели Все модели Все модели Все модели (кроме ATmegal61) Все модели Все модели min typ <1,0 max 6,0 15,0 2,5 8,0 30,0 8,0 Ед. изм. мА мА мкА Таблица П2.6. Основные параметры встроенного АЦП Параметр Разрешение [бит] Абсолютная погрешность [МЗР] INL DNL Интегральная нелинейность [МЗР] Дифференциальная нелинейность [МЗР] Ошибка смещения [МЗР] Время преобразования [мкс] /Adc AVbc Vref Тактовая частота [кГц] Напряжение питания [В] Опорное напряжение [В] Условия Несимметричный вход Дифференциальный вход, Ки = 1 х и 20х Дифференциальный вход, Ки = 200х Несимметричный вход, Vref = 4 В fADC = 200 кГц Vref = 4 В Vref = 4 В VREF = 4B Режим непрерывного преобразования Несимметричный вход Дифференциальный вход min 65 50 Vcc-0,3 2,0* 1,0 2,0* 1,0 typ 10 8 7 1 0,5 0,5 1 max 2 260 200 Vcc+0,3 Vcc Vcc-0,2
Основные параметры некоторых микроконтроллеров Atmel AVR 443 Таблица П2.6 (окончание) Параметр Vint ^REF ^AIN Напряжение внутреннего ИОН [В] Входное сопротивление канала опорного напряжения [кОм] Входное сопротивление аналогового входа [МОм] Условия min 2,3-2,4* 1,0 6 typ 2,56 1,1 10 100 max 2,7-2,9 1,2 13 * Для моделей ATmega8, ATmega8535, ATmega16 и некоторых других.
Литература 1. Монк С. Программируем Arduino. Профессиональная работа со скетчами. — СПб.: Питер, 2017. 2. DI HALT. AVR. Учебный курс. (http://paulfertser.info/AVRJ)I_HALT.pdf) 3. Справочник по AVR-ассемблеру (русский язык). (http://s-engineer.ru/DOWNOLO AD/Asm_A VR_rus.pdf) 4. Официальный справочник по AVR-ассемблеру (английский язык). (Онлайновая версия: https://www.microchip.com/webdoc/avrassembler/index.html9 PDF-версия: http://wwl.microchip.com/downloads/en/DeviceDoc/40001917A.pdf). 5. Параметрические таблицы AVR Mega и Tuny на русском языке (http://avr.ru/docs/d-sheet). 6. Евстифеев А. В. Микроконтроллеры AVR семейства Mega. Руководство пользователя. — ДМК-Пресс, 2015. 7. Евстифеев А. В. Микроконтроллеры AVR семейства Tiny. Руководство пользователя. — ДМК-Пресс, 2015. 8. 8-bit AVR Instruction Set Manual (английский язык). (Онлайновая версия: https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_instruction_ list.html, PDF-версия: http://wwl.microchip.com/downloads/en/devicedoc/atmel- 0856-avr-instruction-set-manual.pdf). 9. AVR-GCC: Совмещение С и ассемблера в одном проекте (https://embedderslife.wordpress.com/2012/02/19/avr-gcc-asm-and-c/). 10. Ревич Ю. Занимательная электроника. / 5-е изд. — СПб.: БХВ-Петербург, 2018. 11. Зубарев А. А. Ассемблер для микроконтроллеров AVR: Учебное пособие. — Омск: Изд-во СибАДИ, 2007. — 112 с. (http://bek.sibadi.org/fulltext/ED1519.pdf). 12. Хоровиц П., Хилл У. Искусство схемотехники, в 3 т. Пер. с англ. — М.: Мир, изд. 1983,2001,2003. 13. Зубков С. В. Assembler для DOS, Windows и UNIX. — М.: ДМК-Пресс, 2000.
Литература 445 14. Кнут Д. Э. Искусство программирования. Том 1. Основные алгоритмы. Том 2. Получисленные алгоритмы.: пер. с англ. — М.: Издательский дом «Вильяме». Любое из изданий 2001/2007/2008 гг. 15. Ревич Ю. Оценка методов измерения низких частот на Arduino (https://habr.com/ru/post/373213/). 16. NordicEnergy, DC/AC инвертор: принцип работы, схемотехника, встроенное ПО (https://habr.com/ru/post/358172/). 17. Лещинский С. Работа с аппаратным интерфейсом SPI микроконтроллеров семейств AVR и MCS51 на примере обмена данными с микросхемами энергонезависимой памяти семейства DataFlash. (http://www.fulcrum.ru/Support/art_Atmel_SPI.htm). 18. Микроконтроллер — работаем с SD-картой без файловой системы (http://s-engineer.ru/mikrokontroller-rabotaem-s-sd-kartoj-bez-fajlovoj- sistemoj). 19. Микроконтроллер — работаем на SD-карте с FAT 16 на низком уровне (http://s-engineer.ru/mikrokontroller-rabotaem-na-sd-karte-c-fatl6-na-nizkom- urovne). 20. DS1307 — 64x8 часы реального времени с последовательным интерфейсом. Техническое описание. — Перевод с англ. О PIClist RUS (http://piclist.ru/D-DS-DS1307-RUS/D-DS-DS1307-RUS.html). 21. Задорожный С. М. Самодельный контроллер ЖКИ на микроконтроллере (http://sezador.radioscanner.ru/pages/articles/sources/lcdctrl.htm).
Предметный указатель Arduino20, 156 Arduino IDE 107 AT45DBxxxB 329 AT90S2313 23 ATmegal6 92,324 ATmega328 25,92 ATmega8 24, 87, 92, 112, 131,305 ATmega8515 32,92, 133,324 ATmega8535 92,113,324 ATmega88 31,92 ATtiny2313 23,31,91, 112, 149,239 ATtiny26 24 ATtinyS2313 240 AVR 0 Mega 24 0 Tiny 23 0 потребление 87 0 семейства 23 в BOD См. Brown-out Detector Bootloader31,108, 115, 145 Brown-out Detector 23, 40-42,114, 272,274 С CRC 338 E EEPROM 29, 33, 127, 271, 275, 328, 358 Flash-карта 334 Flash-память 29, 329 Fuse-биты 109-116 н 1 1 Нех-файлы 140 1 1 I2C61.Gn.TWI L LRC 142, 338 P PWM 48. См. ШИМ R RESET 40, 136 RS-232 52-57 RTC См. Часы реального времени s SPI 22, 57, 323-28 0 аппаратный 324 0 программный 327 SRAM 29, 31,125, 136,182 0 сохранность данных 271
Предметный указатель 447 ТМР36 310,384,409 TWI 22, 61, 343-349 и UART22,52,53,387 О примеры 395 О программирование 394 О скорость обмена 394 UART-COM 392 USART См. UART USB-UART 392 USI62 W Watchdog 48, 374-79 Адаптер О USB-COM 76, 389 О USB-RS232 76, 389 О USB-TTL См. USB-UART О USB-UART 76-79, 389 Аналого-цифровые операции 291 Аналоговый компаратор 22,294 Арифметические операции 199 Ассемблер 119-23 О директивы компилятора 125 О числа и выражения 123 АЦП 22, 48,286, 291, 300-306 В Вектор прерывания 131, 134 Вещественные числа 206 Генератор сигналов 82 Генерация случайных чисел 208 Градуировочное уравнение 289, 318 Деление 203 Дизассемблер 143 Динамическая индикация 248-254 Дисплей 0 4-digit(TM1637)404 0 MT-10S1419 0 МТ-10Т11 361,384 0 OLED420 0 знакосинтезирующий (HD44780) 410-418 Дополнительный код 216 Запись в EEPROM 276 И Измерение 0 времени 233 0 периода 242 0 частоты 238 ИК-приемник 422 Индикаторы: семисегментные 246 Индикация: динамическая 248-254 Интерфейс 0 SPI22 О TWI22 О UART22 ИОН 50, 311 Источники питания 84 К Калибровка289,290,319 Класс точности 287 Кодировка символов 195 Команды 0 арифметических операций 177, 197 0 безусловного перехода 120, 164 0 логических операций 174 0 операций с битами 175 0 пересылки данных 180 0 проверки-пропуска 170 0 сравнения 166 0 условного перехода 165 КОП (код операции) 141 л Логическое 0 отрицание 434 0 сложение 434
448 Предметный указатель м Макетные платы 75 Макрос 138,199 Метка 122 Метод наименьших квадратов 289 Микроконтроллер О AVR18,20,21 О PIC 19 О STM8 19 О х51 18 Монитор О питания 23, 41, 272, 273, 275 О порта 391 Мультиметр 80 Опорное напряжение См. ИОН Опорный источник См. ИОН Осциллограф 81 Отрицательные числа 216 Отсчет 291 п Память 0 EEPROM17,22,29,33 0 Flash 17,22,29 0 SRAM 29 0 данных См. SRAM 0 программ 29-31, 127 Панельки 73 Периферийные устройства 43 Погрешность 286 0 абсолютная 287 0 относительная 287 0 относительная приведенная 287 Подпрограммы 120, 129 Подтягивающий резистор 70,149 Полином 289 Порты ввода/вывода 44-46 Последовательный порт 51 0 SPI См. также SPI 0 TWI См. также TWI 0 UART См. также UART 0 USI62 Прерывания 30, 62-68, 131, 158, 165 0 внешние 65, 158 0 внутренние 65 Разрешающая способность 287 РВВ (регистры ввода/вывода) 32, 43 Регистр SREG 166 Режимы энергосбережения 365-367, 401 0 программирование 368 РОН (регистр общего назначения) 32 Сброс 39 Семисегментные индикаторы 246 Серводвигатель 425 Символы 194 0 форматированный вывод 400 Система счисления: шестнадцатеричная 430 СОМ-порт 54. См. также RS-232 Среднее арифметическое 288 Стартовый бит 55 Стек 191 Стоповый бит 55 Сторожевой таймер 22, 39, 40. См. Watchdog Строки 194 Структура программы 120 Супервизор См. Монитор питания Схема генератора цифрового 82 Таймер-счетчик 23, 46-48 Таймеры-счетчики 223 Тактирование 35 Точность 287 У,Ц Умножение 200 ЦАП 292 Часы реального времени 235, 237, 349-358, 401,421 Чтение EEPROM 277 ш, э ШИМ 254-59, 278 Энергонезависимая память 328, 358