Персональный компьютер вместо паяльника



 

О программировании МК

 

 

– Чтобы найти дорогу в Лондон, надо уметь говорить по‑английски. По‑моему, дело это очень трудное.

А.Дюма. Три мушкетера

 

 

Внедрение любой новой технологии требует начальных затрат. Не составляет исключения и микропроцессорная технология. В данном случае прямые затраты будут состоять в том, что вам, во‑первых, придется приобрести программатор, во‑вторых, компьютер – если по какой‑то непостижимой случайности у вас его до сих пор нет. Затраты эти могут быть сведены к минимуму – программатор лучше приобрести специализированный, а он стоит на порядок меньше универсального, а компьютер для наших целей сгодится совершенно любой, лишь бы он был из семейства PC, т. е. умел бы работать с Windows (хотя есть программаторы, которые работают и с DOS, и, разумеется, с Linux). Большинство современных программаторов общаются с компьютером через универсальный порт USB (создавая через него виртуальный СОМ‑порт), так что в этом отношении проблем не ожидается.

 

 

Железо

Раз уж мы начали с потребного «железа», то закончим эту тему, а потом перейдем к собственно программированию. Программатор, о котором идет речь, называется ISP‑программатором (In System Programming , т. е. программирование осуществляется прямо в устройстве пользователя). В Интернете можно найти множество предложений самодельных программаторов такого рода (ибо интерфейс программирования AVR не составляет секрета), но их функциональность и удобство пользования часто оставляют желать лучшего[30]. Поэтому предпочтительно покупать фирменный – так, очень удобные выпускает фирма Argussoft (AS‑2/3/4).

Для того чтобы работать с ISP‑программатором, естественно, его надо куда‑то подключить. Для этого на программируемой плате специально устанавливают программирующий разъем. ISP‑программаторы используют один и тот же тип разъема – игольчатый PLD (PL double , т. е. двухрядный), который хорошо знаком всем, кто когда‑нибудь подсоединял жесткий диск с IDE‑интерфейсом к материнской плате. Естественно, для ISP‑программаторов требуется гораздо меньше контактов, чем для жесткого диска. Минимальное их количество равно 6 (именно столько их у программатора, рекомендуемого самой фирмой Atmel , такой же разъем предусмотрен на всех платах Arduino ) – это выводы SPI‑интерфейса программирования: MOSI, MISO и SLK, а также Reset и два вывода питания +5 В и «земля» (ISP‑программаторы обычно питаются от программируемой схемы). Указанные выводы SPI‑интерфейса присоединяются к одноименным выводам кристалла, которые есть у всех МК AVR, имеющих возможность SPI‑программирования.

Хлопоты по приобретению программатора можно дополнительно минимизировать, если воспользоваться платформой Arduino , в которую программатор попросту встроен, как неотъемлемая часть. Но эти возможности, о которых мы расскажем в последующих главах, по умолчанию предполагают наличие фирменных плат Arduino (или какой‑либо из ее клонов). Здесь нам важно, что необязательно пользоваться аппаратными средствами собственно Arduino и приобретать платы этой платформы, в которых заведомо имеется много лишнего. Программное обеспечение Arduino IDE включает утилиту AVRDUDE, позволяющую программировать МК AVR напрямую, не пользуясь ни средой Arduino , ни заранее встроенным в контроллер загрузчиком. Информацию о том, как обращаться с AVRDUDE, можно найти на множестве ресурсов в Сети[31]. Эта утилита и связанные с ней программные средства (вроде WinAVR), в частности, позволяют применять простейший самодельный программатор, подключаемый к порту LPT.

Упомянутые фирменные программаторы от Argussoft имеют больше контактов: между сигнальными линиями проложены дополнительные линии «земли» и всего контактов получается 10[32]. Разводка этого разъема показана на рис. 19.2 далее.

Отметьте, что каждый сигнальный вывод «подтянут» к питанию внешним резистором – так надежнее. При отсчете выводов имейте в виду, что нумерация контактов PLD‑разъемов отличается от обычной и делается не в обход, как у микросхем, а следующим образом: если глядеть на «папу» сверху (т. е. со стороны подсоединения проводов), то вывод номер 1 , как и положено, находится слева внизу, вывод 2 – над ним, вывод 3 – следующий за первым в нижнем ряду и т. д., т. е. нижний ряд – это все нечетные, а верхний – все четные контакты. Это сделано потому, что разъемы PL могут иметь не строго фиксированное количество контактов, но при любом их количестве имеющиеся контакты будут нумероваться одинаково.

Если не поняли, то поглядите на разводку любого PLD‑разъема на материнской плате компьютера, которая обычно приведена прямо на ней (а также, как правило, имеется в руководстве).

* * *

 

Подробности

Так как игольчатые разъемы типа PLD и IDC с шагом 2,54 мм встречаются в практике изготовления микроэлектронных устройств довольно часто, то стоит разобраться в их маркировке. Начнем с того, что наименование IDC в случае штыревых разъемов для установки на плату относится к разъемам в кожухе с ключом (именно такие используются для подсоединения жесткого диска в ПК). Бескорпусные подобные разъемы носят название PLD для двухрядных (или PLS для однорядных) типов и более удобны в радиолюбительской практике, т. к. длинные разъемы легко отломать в нужном месте, чтобы обеспечить необходимое количество выводов (правда, при этом лучше все же обозначать на плате первый вывод, чтобы не перепутать ориентацию при включении).

Разметка на плате для обоих типов разъемов (и с кожухом и без) одинакова, поскольку все равно приходится учитывать место, которое займет кабельная розетка при ее подсоединении, и мы в этой книге вперемешку упоминаем IDС и PLD. Разумеется, розетка, которая используется для установки на плоский кабель, может иметь только фиксированное число контактов (из ряда 6, 10, 14, 16, 20, 22, 24, 26, 30, 34, 36, 40, 44, 50, 60…), что нужно учитывать при проектировании.

Цифра после обозначения разъема (IDC‑10 или PLD‑10) – это количество контактов разъема, а следующая буква символизирует его конфигурацию: М (male , «папа») для штыревой части и F (female , «мама») для гнездовой. Далее может следовать еще одна буква, которая обозначает ориентацию: S для прямых выводов (разъем перпендикулярен плате), R для повернутых под углом 90° (разъем параллелен плате). Таким образом, приведенное далее на схеме рис. 19.2 обозначение IDC‑10MS означает штыревой («папа») разъем в кожухе с ключом и с 10‑ю прямыми выводами. Соответствующая этому разъему кабельная часть обозначится, как IDC‑10F. Бескорпусные PLD‑разъемы бывают, естественно, только штыревые, потому для них буквы М и F не указываются (а повернутые под углом 90° дополняются буквой R ).

 

* * *

Вопрос, который при этом немедленно возникает: не жирно ли занимать как минимум три вывода портов (обычно это порт В контроллера) альтернативными функциями? Отвечаю: в большинстве случаев такие выводы можно использовать как обычные порты, программатору это не помешает. Единственная ситуация, в которой возникает конфликт между программатором и схемой, это если выводы MOSI, MISO и SLK используются как входные линии и подсоединены к активным выходам других микросхем, к коллектору открытого транзистора или к конденсатору большой емкости. Поэтому в схеме эти контакты лучше использовать в качестве выходных линий, а если хватает других портов – вообще оставить их только для программирования. На платах Arduino, где контакты штатного SPI‑порта совпадают с портом программирования, они выведены, как обычные линии (выводы D11, D12 и D13[33]), и в случае их использования для программирования к ним тоже не рекомендуется подключать низкоомную нагрузку. Естественно, при работе схемы программатор лучше отключать вовсе, только учтите, что по окончании цикла программирования в любом случае схема заработает сразу с начала, как после включения питания.

Все эти проблемы несущественны, если работать с универсальным программатором, где используется параллельное программирование. Тогда при программировании МК приходится вставлять в специальную колодку на корпусе программатора, а в схеме, естественно, для него тогда должна быть предусмотрена установка на панельку. Преимущество универсальных программаторов в том, что можно легко переходить с одного семейства процессоров на другое, причем, независимо от типа корпуса, а также программировать любые микросхемы памяти. Не нужен также и программирующий разъем на плате и связанные с ним резисторы. Но в процессе отладки сложной программы пользоваться универсальным программатором крайне неудобно – МК все время приходится вынимать и вставлять, что сильно замедляет процесс, к тому же есть риск повредить выводы. Поэтому универсальный программатор следует рекомендовать в случаях, когда программа уже более‑менее отлажена, например, при тиражировании конструкции.

 

 

Софт

Если мы имеем какой‑то программатор с прилагающимся к нему софтом, то, в сущности, нам нужна еще только одна специальная программа – ассемблер (assembler значит «сборщик»). Его можно бесплатно скачать с сайта Atmel в составе AVR Studio (папка avrassembler ), файл носит название avrasm2.exe . Практически все существующие ассемблеры запускаются из командной строки (хотя могут быть и упакованы в оболочку с графическим интерфейсом). В качестве параметров для компилятора Avrasm2 указывается имя файла с исходным текстом программы и имена выходных файлов, главным из которых является файл с расширением hex . Чтобы каждый раз не вводить длинную командную строку, пишется соответствующий bat ‑файл.

* * *

 

Подробности

Предположим, у вас файл avrasm2.exe находится в созданной вами папке c.\avrtools . Запустите Блокнот и введите следующий текст (соответственно измените путь, если папка другая):

c: \avrtools\avrasm2 – fI %1.asm

Строка эта может выглядеть и несколько иначе:

c: \avrtools\avrasm2 – е %1.еер – fI %1.asm

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

Сохраните созданный файл под названием, например, avrasm/bat . Пусть текст созданной вами программы находится в файле programm.asm , тогда достаточно в командной строке запустить avrasm.bat с параметром рrоgrаmm (если надо, то с путем к нему, а расширение добавится автоматически), и в той же папке, где находится последний, создастся файл рrоgrаmm.hex . При этом откроется DOS‑окно, в котором будут проанализированы ошибки, если они есть (тогда выходной файл не создастся), а если все в порядке – указан объем полученной программы в двухбайтных словах (учтите, что размер hex‑файла ни о чем не говорит).

 

* * *

Полученный в результате ассемблирования hex‑файл с программой представляет собой текстовый файл (а не бинарный, как обычные исполняемые компьютерные файлы), но содержащий только числа в байтовом представлении в шестнадцатеричной записи. Он имеет строго определенную структуру, разработанную в свое время фирмой Intel . Этот hex‑файл и есть та программа в процессорных кодах, которую мы загружаем в МК с помощью различных программаторов (в том числе среда Arduino тоже создает такой файл). При этом программатор автоматически располагает ее в памяти программ МК, начиная с нулевого адреса.

Исходные тексты ассемблерных программ можно создавать в любом текстовом редакторе (разве что к результатам деятельности Microsoft Word следует относиться с осторожностью). Но, несмотря на широкий выбор, есть по крайней мере две причины, по которым лучше все же использовать редакторы специализированные.

Первая причина – это так называемый highlighting или подсветка синтаксиса по‑русски. Те, кто пользовался любыми средами высокоуровневого программирования (от Turbo Pascal до Delphi или Visual Basic ), хорошо знают, что это такое – служебные слова, комментарии, разные типы выражений выделяются каждый своим цветом или шрифтом, что сильно облегчает чтение текста и служит заодно неплохим средством проверки правильности написания. Но если эту опцию предлагает множество фирменных и не очень редакторов, то вторая желательная функция есть лишь у считанных единиц. Я имею в виду возможность прямо из редактора с помощью горячих клавиш запускать процесс ассемблирования. В этом случае вы можете «не отходя от кассы», т. е. не покидая редактор, одним нажатием горячих клавиш сразу же ассемблировать написанный текст и ознакомиться с сообщениями об ошибках.

Еще одна причина для использования специализированных редакторов – они автоматически нумеруют строки. Причем пустые строки также входят в нумерацию – так проще считать. Если у вас есть ошибки в программе, то ассемблер укажет номер строки с ошибкой, так что нумерация строк принципиально важна. Один из рекомендуемых вариантов редакторов для AVR‑ассемблера – «самопальный» редактор ASM Editor (не путать с Asmedit !), который сделан на удивление профессионально, хотя и не без некоторых досадных огрехов.

Все сказанное относится к программированию на языке ассемблера, потому что программы для AVR‑контроллеров можно писать и на С, C++ , специальном языке Proccesing/Wiring (Arduino !) и многих других, включая даже специальные версии Pascal (mikroPascal for AVR [34]). Для тех, кто уже владеет программированием на языках высокого уровня, это может показаться более удобным способом, а такие варианты, как среда Arduino , специально «заточены» под начинающих, не имеющих представления ни о программировании, ни об электронике. Знакомиться со средой Arduino мы еще будем в дальнейшем, и там вы сами сможете пощупать руками все ее достоинства и недостатки. Сейчас же я скажу только, что для углубленного изучения контроллеров я бы не рекомендовал начинать не только со среды Arduino , но и вообще с программирования на С или других языках высокого уровня. Не столь важно то, что в результате на ассемблере получается более быстрый и компактный код, сколько то, что любой посредник (а компилятор С есть именно посредник) в этом деле только мешает понимать, что именно происходит в контроллере.

Следует, однако, учесть, что по мере усложнения программ и перехода к профессиональному программированию МК обойти освоение С уже невозможно, как минимум, по двум причинам. Во‑первых, большинство профессиональных библиотек подпрограмм существуют именно на С , и среди них есть вещи, которые повторить на ассемблере будет, как минимум, весьма затруднительно. Во‑вторых, потому что в ассемблере отсутствуют достаточно развитые средства структурирования, и отлаживать большую программу, содержащую более пары‑другой тысяч ассемблерных операторов, – мучение. Ну, а, конечно, когда углубленное изучение не предполагается, а стоит чисто утилитарная задача один раз воспроизвести ту или иную конструкцию – здесь такие среды, как Arduino , оказываются вне конкуренции.

Все, что сказано в этой главе далее, относится только к программированию МК AVR «в чистом виде», т. е. на ассемблере и в средах, ориентированных на его использование.

 

 

О конфигурационных битах

Эта напасть свалилась на нас с появлением семейств Tiny и Mega , в «классических» AVR ничего такого не было (точнее, было, но специально заботиться об установке этих битов не требовалось). В англоязычной инструкции конфигурационные биты называют fuse битами . Их появление привело к многочисленным проклятиям на голову фирмы Atmel со стороны армии любителей, которые стали один за другим «запарывать» кристаллы при программировании. Положение усугублялось тем, что в описании этих сущностей используется извращенная логика, – как мы знаем, ячейки любой чистой EEPROM (по принципу ее устройства) содержат единицы, и слово «запрограммированный» по отношению к такой ячейке означает, что в нее записали логический ноль. Термины запрограммированный и незапрограммированный как раз и применяются в фирменных описаниях AVR, и оттуда перекочевали в ряд самодельных программаторов – готовьтесь к тому, что в некоторых программаторах отмеченный галочкой в меню программы бит означает его равенство логической единице, а в других – запрограммированное состояние, т. е. логический ноль. Поэтому разработчики программаторов AS из фирмы Argussoft даже специально предусмотрели в окне программирования конфигурационных ячеек памятку на этот счет (рис. 19.1).

 

 

Рис. 19.1. Окно типового состояния конфигурационных ячеек в нормальном режиме работы ATmega8535  

 

В этом окне сейчас приведено безопасное рабочее состояние конфигурационных ячеек для ATmega8535, причем выпуклая кнопка означает единичное состояние ячейки, а нажатая – нулевое (и не путайтесь с этим самым «запрограммированным» состоянием!). Для разных моделей набор fuse битов различный, но означают они одно и то же, потому мы рассмотрим типовое их состояние на этом примере.

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

Самые необходимые пункты тут такие. По умолчанию любая микросхема семейств Mega или Tiny запрограммирована на работу от внутренней RC‑цепочки, за что разработчикам большое спасибо, иначе было бы невозможным первичное программирование по SPI – только через параллельный программатор. Для работы с обычным «кварцем», присоединенным по типовой схеме, как мы говорили в главе 18 , требуется установить все ячейки CKSELO‑3 в единицы (что согласно логике разработчиков означает незапрограммированное их состояние). Заметим, что это и ведет к критической ошибке – решив при поверхностном чтении написанного по‑английски руководства, что установка всех единиц означает запрограммировать все ячейки, пользователь смело устанавливает их на самом деле в нули, отчего микросхема переходит в состояние работы от внешнего генератора, и разбудить ее через SPI‑интерфейс уже невозможно. Легче всего в этом случае переустановить fuse биты с помощью параллельного программатора либо, за неимением такового, попробовать‑таки подключить внешний генератор, как описано в руководстве.

Это самая крупная ошибка, которую можно допустить, но есть и другие менее распространенные. Ячейка SPIEN разрешает/запрещает последовательное программирование по SPI и должна оставаться в нулевом состоянии, иначе МК не «разбудишь» никак, только опять же с помощью параллельного программатора (говорят, правда, через SPI ее и отключить невозможно). Ячейка S8535C (в других моделях она будет иметь другое название или вовсе отсутствовать) – очень важна и определяет режим совместимости с семейством Classic (в данном случае с AT90S8535).

Если ее установить в нулевое состояние, то МК семейства Mega перейдет в режим совместимости. В популярном ATtiny2313, потомке «классического» AT90S2313, такого бита совместимости нет (но, как мы увидим, это не очень существенно). При использовании режима совместимости следует учесть, что состояния МК нельзя перемешивать: если fuse бит совместимости запрограммирован (равен 0), то программа компилируется полностью, как для семейства Classic (в том числе с использованием соответствующего inc‑файла, см. далее), иначе она может не заработать.

Еще одна важная ячейка – EESAVE, которая на рис. 19.1 установлена в единицу (режим по умолчанию), но ее целесообразно перевести в нулевое состояние – тогда при программировании памяти программ не будет стираться содержимое EEPROM.

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

Наконец, для нас в дальнейшем будет иметь значение состояние ячеек BODEN и BODLEVEL. Первая, будучи установлена в ноль, разрешает работу так называемой схемы BOD (Brown‑out Detection ), которая сбрасывает контроллер при снижении питания ниже допустимого порога. Ячейка BODLEVEL и определяет этот самый порог – при установленной в 0 ячейке он равен 4 В, при установленной в 1–2,7 В.

При питании 5 В надо выбирать первое значение, при питании 3,3 В – второе. Это предохраняет контроллер от «недопустимых операций» при выключении питания, но для обеспечения полной сохранности содержимого EEPROM таких мер может оказаться недостаточно, и приходится производить дополнительные действия (см. также главу 18 ). Все остальные ячейки следует оставить по умолчанию (и тем более ни в коем случае не трогать Lock биты, при установке которых доступ к программе и fuse битам вообще отключается навсегда!). Только учтите, что в разных контроллерах одни и те же узлы могут программироваться по‑разному. Так, в рассматриваемом далее ATtiny2313 (и некоторых других, в основном из тех, что способны работать при напряжении питания до 1,8 В) схема BOD устроена иначе: ячеек BODLEVEL там целых три, причем упомянутая ранее BODLEVEL соответствует BODLEVEL0, а при тех же значениях порога ячейки BODLEVEL2: BODLEVEL1 должны быть в состоянии 10. Ячейки BODEN там нет, а выключенному состоянию схемы BOD соответствуют все единицы во всех трех ячейках. Зато там есть любопытный fuse бит CKOUT, который при программировании (установке в ноль) подключает к выводу 6 (PD2) выход тактового генератора, и еще бит CKDIVS, который в том же случае снижает тактовую частоту в восемь раз (например, при кварце 8 МГц МК заработает, как от 1 МГц, что снижает потребление). Поэтому для каждого конкретного контроллера следует сверяться с техническим описанием.

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

 

 

Примеры программирования

Написание программ – целое искусство, поэтому здесь можно на нескольких примерах попытаться дать только общее представление о том, как это делается, – заостряя внимание на специфических особенностях программирования для МК, а не программирования вообще. Полного обзора команд и даже комментариев по каждой встречающейся команде вы также здесь не увидите – это потребовало бы отдельной книги (см. [19, 20], а также книгу автора [21]).

 

Самая простая программа

Давайте напишем сначала самую простую программу, которая будет включать светодиод сразу при включении питания и больше ничего не делать. Для примера возьмем контроллер ATtiny2313, схема подключения которого вместе с программирующим разъемом показана на рис. 19.2.

 

 

Рис. 19.2. Схема подключения ATtiny2313  

* * *

 

Подробности

Обратим внимание на RC‑цепочку (R1 и С1), которая подсоединена к выводу Reset . Она служит для более надежного сброса контроллера при не слишком качественном источнике питания, если установление напряжения затягивается. В техническом описании указано, что по выводу Reset уже имеется встроенный фильтр дребезга с «подтягивающим» резистором, так что эта цепочка оказывается вроде бы и ненужной (ее установка рекомендовалась для семейства Classic, где фильтра не было). Тем не менее, для более надежной работы МК рекомендуется во всех случаях устанавливать резистор R1 с номиналом 3–5 кОм (встроенный резистор имеет слишком большой номинал). Что касается конденсатора С1, то если для управления сбросом применяется внешний монитор питания, как описано в главе 18 , то, разумеется, он будет только мешать, в остальных случаях он необязателен, но делает работу схемы более стабильной. Резисторы R2‑R4 при наличии программирующего разъема устанавливать обязательно, в противном случае надежность схемы снижается.

Заметим, что в схеме на рис. 19.2 показана только небольшая часть функциональности входных/выходных линий, которые все, включая даже Reset и выводы подсоединения кварца (кроме, конечно, выводов питания), имеют как минимум две, а большинство даже три функции. Подробно обо всех этих функциях можно узнать из технического описания, частично вы с ними познакомитесь из дальнейшего изложения.

 

* * *

Светодиод мы сразу взяли двухцветный – в целях дальнейшего усовершенствования схемы. Светодиод L56 (можно также взять L57 или малогабаритный L36) светится зеленым, если плюс питания находится со стороны ключа (скоса на корпусе), и красным – если наоборот. Здесь мы его подключили к выводам PD5 и PD6 порта D. Таким образом, если на этих выводах будет одинаковый уровень (неважно, единица или ноль), то светодиод погашен, если разный – то в зависимости от того, на каком выводе единица, светодиод будет гореть красным или зеленым. Токоограничивающий резистор R5 при логическом уровне на выходе порта примерно 4,5 В обеспечит ток через светодиод около 5 мА.

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

1. Конфигурирует нужные выводы порта D (PD5 и PD6) на выход.

2. Устанавливает на выводе PD6 логическую единицу (логический ноль на выводе PD5 устанавливается по умолчанию).

И что, программа будет состоять всего из двух команд? Увы, не все так просто.

В регистры портов записывать непосредственное значение нельзя, можно только командой out переносить информацию из какого‑нибудь рабочего регистра. Поэтому добавится третья команда – сначала мы установим нужные биты в некоем рабочем регистре, специально выбранном для этих целей из числа регистров общего назначения (РОН), затем загрузим весь полученный байт в регистр порта. Причем установить непосредственное значение можно также не в любом из РОН, а только в регистрах с номерами с 16 по 31, поэтому выберем себе регистр r16 в качестве рабочего. Тогда последовательность команд будет выглядеть так:

 

ldi r16, 0Ь0Н00000 ; устанавливаем биты номер 5 и 6 в регистре r16

out DDRD,r16 ; выводим это значение в регистр направления порта D

sbi PortD,6 ; устанавливаем в единицу бит 6 регистра данных порта D

 

Здесь ldi – команда загрузки непосредственного значения (load immediate ), out – команда вывода в какой‑либо регистр ввода/вывода (РВВ), sbi (set bit input/output ) – команда установки бита с выбранным номером в РВВ. А что означает запись 0Ь0Н00000 ? Это хорошо знакомое нам двоичное представление числа – 0b впереди как раз и означает, что это именно двоичная запись. Таким образом в данном случае удобнее отсчитывать биты, но если мы запишем это же число в hex‑форме 0х60 (или $60), или даже просто десятичное 96, ничего не изменится.

Заметим, что способ установки значений РВВ через команду out – не единственный, можно было бы установить биты 5 и 6 регистра DDRD двумя командами sbi (есть и другие способы).

* * *

 

Подробности

Обратите внимание на синтаксис команд – сначала пишется, куда писать, а потом, через запятую, откуда или что. Это справедливо для всех команд и для всех ассемблеров. Каждая команда пишется в отдельной строке. Весь текст после точки с запятой ассемблером игнорируется и представляет собой комментарии, которые нужно писать обязательно, иначе вы через пару месяцев и сами в своей программе не разберетесь. Если комментарий переходит на другую строку, то точку с запятой нужно ставить заново, в начале строки. В отличие от знака перевода строки, все пробелы и табуляции игнорируются (а там, где есть другой разделительный знак, в данном случае запятая, как видите, пробелов вообще может не быть), так что украшать текст отступами можно, а для удобства чтения и нужно. Строчные и прописные буквы не различаются: записи LDI , Ldi и ldi означают одно и то же – и это отличие AVR‑ассемблера от программных сред, основанных на C/C++. Вопреки распространяемым в Интернете сведениям (заимствованным из [22]), это правило соблюдается и для обновленных версий компилятора Avrasm2.

 

* * *

Ну, а теперь все? Можно скопировать это в Блокнот, сохранить с расширением asm и компилировать в hex‑файл? Ишь разбежались – нет, не все. Во‑первых, никаких таких DDRD и PortD AVR‑ассемблер не понимает. Если соответствия кодов команд и их мнемонических обозначений (ldi), а также обозначений рабочих регистров (r16) и их адресов зашиты в ассемблере, то адреса РВВ могут меняться от модели к модели, а их названия и вообще могут быть выбраны совершенно произвольным образом. Сам ассемблер понимает только конкретные числа, представляющие собой адреса этих регистров. Но писать программу без мнемонических обозначений было бы крайне неудобно, т. к. она оказалась бы совершенно нечитаемой (вторая команда, к примеру, тогда выглядела бы так: out $11, r16 ). Поэтому к нашей программе надо «пристегнуть» файл с мнемоническими обозначениями, который поставляется Atmel и в данном случае называется tn2313def.inc (при компиляции он должен находиться в одном каталоге с файлом программы). Это делается почти в точности, как в языке С , строкой:

 

.include "tn2313def.inc"; точка впереди обязательна!

 

* * *

 

Замечание

Файлы макроопределений (с расширением inc ), к сожалению, нельзя скачать с сайта Atmel в отдельности. Как и файл компилятора Avrasm2, эти файлы для каждого контроллера в отдельности проще всего добыть из пакета AVR Studio, где они находятся в папке avrassembler/include. Лучше, если это будет одна из последних версий AVR Studio, пятая или более поздняя, иначе при компилировании совместно с Avrasm2 возможны ошибки.

 

* * *

Если вы заглянете внутрь файла tn2313def.inc, то увидите, что он состоит из строк, начинающихся с директивы .equ . Мы могли бы не включать его в программу (хотя в память процессора он все равно не записывается, ибо не содержит команд, а только определения переменных), а лишь дописать к программе в начале текста строки:

 

.equ DDRD = $11

.equ PortD = $12

 

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

Если провести аналогии с языком Turbo Pascal или Turbo С , то директива .equ (от англ. equal – равно) полностью аналогична определению констант, а директива .def (от англ. define – определить) аналогична определению переменных, с единственным отличием: тип переменной здесь не указывается, ибо он один‑единственный – число размером один байт. А вот в директиве .equ может быть указано число любого размера, а также и отрицательное, но, естественно, только целое.

Неудобно каждый раз писать r16 и помнить, что это у нас рабочая переменная для всяких текущих надобностей, потому лучше дописать еще такую строку:

 

.def temp = r16 ;рабочая переменная, от слова temporary (временный)

 

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

 

;программа зажигания светодиода

;процессор Tiny2313, частота 4 МГц

.include "tn2313def.inc"

.def temp = r16 ;рабочая переменная

ldi temp,0b01100000 ;устанавливаем биты номер 5 и 6 в temp

out DDRD,temp ;выводим это значение в регистр направления порта D

sbi PortD,6 ;устанавливаем в единицу бит 6 регистра данных порта D

Sleep

Exit

 

Программа займет в памяти программ контроллера ровно 8 байтов. Последняя команда sleep означает остановку процессора и выход в режим экономии – ведь должен процессор что‑то делать по окончании программы? Директива .exit предназначена для ассемблера и означает конец программы, указывать ее необязательно.

А зачем мы в заголовочном комментарии указали тактовую частоту процессора?

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

 

Таймер без прерываний

Давайте теперь заставим наш МК управлять этим светодиодом так, чтобы он мигал с частотой примерно один раз в секунду из красного в зеленый. И сначала сделаем это самым простым способом – так, как это делали в те времена, когда микропроцессоры еще не были микроконтроллерами и не содержали никаких дополнительных узлов вроде таймеров. Для отсчета времени тогда пользовались тем фактом, что команды выполняются строго определенное время. Причем в AVR этот способ применять особенно удобно, поскольку большинство команд занимают один такт, за исключением команд передачи управления. Этим способом часто пользуются и по сей день для отсчета программных задержек (не станешь же заводить таймер по каждому случаю), потому урок окажется не совсем бесполезным. Заодно познакомимся с понятием процедур (подпрограмм) и с самими командами передачи управления.

Не вникая в подробности, сразу напишем «правильную» процедуру, позволяющую формировать заданные задержки без таймера. Назовем ее Delay , тогда она запишется так:

 

Здесь Razr0‑Razr2 – рабочие регистры. Отведем для них регистры r17, r18 и r19 . В начало программы тогда следует внести их определения через команду def (по образцу .def Razr0 = r17 ). Delay с двоеточием – метка, в данном случае обозначающая начало процедуры, команда ret – выход из процедуры (зачем она нужна, пояснено далее). Команда subi вычитает из регистра константу, в данном случае единицу. А команды sbci работают хитрее – они также вычитают константу, но с учетом переноса. Если переноса нет, то они просто ничего не делают (ибо вычитаемое значение равно нулю). Перенос же возникает тогда, когда в результате предыдущей команды вычиталась единица из нуля. Тогда значение регистра меняется с нулевого на все единицы (255), а перенос записывается в специальный бит переноса и учитывается следующей командой sbci . В этой схеме легко узнать принцип работы соединенных между собой двоичных счетчиков из главы 16 , в которых выход старшего разряда предыдущего счетчика соединен со входом переноса следующего. В данном случае счетчик состоит из трех отдельных байтовых регистров, т. е. всего имеет 24 двоичных разряда.

Нам не надо, чтобы счет продолжался до бесконечности, и для этой цели служит команда brcc , которая относится к группе команд передачи управления (branch ) и работает по очень простому правилу – если в момент ее исполнения бит переноса (он обозначается буквой С , от слова саnу , что и значит «перенос») равен нулю (clear , т. е. очищен), то далее выполняется возврат к команде по метке Delay . To есть название команды (brcc ) расшифровывается так: branch if carry clear (перейти, если перенос очищен). В противном случае управление передается на следующую команду – выхода из процедуры ret , счет заканчивается.

Легко сообразить, что в момент выполнения команды brcc перенос станет равен единице только тогда, когда все регистры в предыдущем такте были равны нулю. Поэтому вся процедура работает так: перед ее началом в счетчики Razr0‑Razr2 записывается некое заданное число, которое в каждом такте уменьшается на единицу, и вся процедура заканчивается при достижении нулевого значения во всех разрядах. Отсюда, зная тактовую частоту МК и время выполнения команд (по такту на вычитание и два такта на возврат к начальной метке), легко вывести формулу: записываемое в счетчики число N , соответствующее нужному интервалу времени T (с) при тактовой частоте F (Гц), рассчитывается, как T ·F /(M + 2), где М – число регистров‑счетчиков, в данном случае 3.

Число N в данном случае трехбайтовое. Старший байт записывается в Razr2 , младший – в Razr0 . При тактовой частоте 4 МГц мы можем получить задержку до 4,19 с, если запишем в регистры все единицы: число 16 777 215 = $FFFFFF. Если же нам требуется, например, задержка в 1 секунду, то придется записать число 800 000 или $0С3500, если в полсекунды – число 400 000 или $061А80.

Вооружившись этими знаниями, попробуем соорудить программу мигалки. Сначала давайте определимся с алгоритмом переключения из красного в зеленый. Наиболее универсальный метод здесь такой: каждый раз будем определять, в каком состоянии в данный момент находится светодиод (по состоянию какого‑нибудь из задействованных выводов порта), и переключать его в противоположное. Это может выглядеть таким образом:

 

 

Не углубляясь в подробности, заметим, что такой алгоритм (он, конечно, не единственно возможный) будет устойчив к начальным условиям – в каком бы состоянии к моменту его начала ни находились биты 5 и 6 (даже если светодиод был совсем погашен), максимум через один цикл они придут в противоположное состояние, и мигание начнется. Теперь осталось соединить все это в законченную программу – повторять процедуру мигания через промежуток времени, определяемый процедурой Delay . Программа будет выглядеть таким образом:

 

 

О назначении первых двух операторов программы мы поговорим позднее. Rcall – команда вызова процедуры (в данном случае Delay ). После этой команды всегда должна где‑то встретиться команда ret (возврата из процедуры), иначе программа к основной последовательности операторов вернуться не «сумеет». Сама программа представляет собой бесконечный цикл, т. к. заканчивается оператором безусловного перехода обратно в начало (rjmp Migbegin ). В данном случае мигание будет происходить с периодом в секунду (полсекунды красный, полсекунды зеленый), для другого периода нужно изменить записываемое в счетчики значение. Программа может служить хорошей основой для любимого развлечения радиолюбителей – конструирования елочных гирлянд.

* * *

 

Заметки на полях

Кстати, о елочных гирляндах. На мой вкус, самый приличный вариант построения такой гирлянды, который к тому же не встретишь в продаже, – это обеспечить вместо раздражающего мигания медленное изменение из одного цвета в другой. Его несложно построить, используя явление биений : если сложить два почти совпадающих по частоте колебания через элемент, например, «Исключающее ИЛИ», то на выходе возникнет колебание с меняющейся скважностью (см. рис. 15.8, а , аналогичный эффект можно получить и с другими логическими функциями). Если напряжение такой формы подать на лампочку, то высокочастотные составляющие не будут заметны, и лампочка станет медленно зажигаться и гаснуть. При очень близких, но все‑таки не равных частотах, период биений может составить минуты, а если взять две разноцветных лампочки или светодиода и включить их в противофазе, то гирлянда начнет медленно менять цвет с одного на другой. Причем в качестве одной из частот удобно взять частоту электрической сети, которая никогда точно не равна 50 Гц.

Для осуществления этого проекта на дискетной логике пришлось бы городить довольно громоздкую схему на счетчиках с предзагрузкой, причем долго подгонять коэффициент деления так, чтобы смена цветов не была слишком быстрой. А вот с помощью контроллера такая задача решается, что называется, в пять секунд. Для одной лампочки или светодиода почти не нужно даже менять схему по сравнению с рис. 19.2 – достаточно подключить светоизлучающий элемент не непосредственно к выводу контроллера, а через ключевой транзистор. Коллекторную цепь его, в которую и будет включена лампочка или светодиод, при этом нужно питать от источника пульсирующего напряжения с частотой сети (от отдельной обмотки трансформатора через один диод, без сглаживания). А частоту на выводе МК, управляющую открыванием транзистора, подогнать примерно под 50 Гц (т. е. длительность задержки должна составить примерно 0,01 с, для чего в счетчики придется записать число около 8000, или, в шестнадцатеричной системе, 1F40h – как видите, можно сократить число регистров задержки до двух). Частоты станут складываться прямо на транзисторе – лампочка будет гореть только тогда, когда и на коллекторе, и на его базе одновременно присутствует высокий уровень напряжения. Чтобы сделать эффект более заметным, можно поиграть со значением скважности, т. е. ввести разные задержки для красного и зеленого состояний. Поскольку у нас уже имеется два управляемых в противофазе вывода, можно добиться переливания из одного цвета в другой, а если усложнить программу, подключив еще несколько выводов МК, то можно получить весьма сложные и красивые эффекты. И, что приятно, экспериментируя с такой схемой, почти не придется браться за паяльник.

 

Применение прерываний

Для того чтобы сделать все то же самое, но «по‑настоящему», придется воспользоваться таймером, а значит – прерываниями. Заметим, что в большинстве проектов Arduino прерывания в явном виде не используются, что относится к одному из главных недостатков этой платформы. Наличие аппаратных прерываний – одно из главных преимуществ современных контроллеров, и если в Arduino мы этим подходом часто жертвуем ради простоты, то в обычном программировании такое недопустимо.

Если помните, я говорил, что при возникновении прерывания процессор обращается по некоторому фиксированному адресу. Для каждой модели МК количество и типы прерываний различаются, потому эти адреса фиксированы для каждой модели процессора. Это первые адреса памяти программ, начиная с адреса 0:0 и дальше, – столько адресов, сколько имеется прерываний.

В МК ATtiny2313 имеется 19 прерываний (в его классическом аналоге было всего 11), соответственно, они занимают первые 19 адресов памяти программ. Напомним: поскольку коды команд двухбайтовые, то память программ организована по 16‑битовым словам, поэтому адреса в памяти программ означают адреса слов, а байтовый адрес будет в два раза больше (т. е. не 0, 1, 2…., а 0, 2, 4…). Это вас ни в коей мере не должно волновать, потому что абсолютные адреса вам отсчитывать не придется, – достаточно правильно оформить первые 19 строк кода, после секции всех определений. Поэтому использующая прерывания программа для ATtiny2313 всегда должна начинаться с последовательности команд, представляющих векторы прерываний, по следующему образцу:

 

 

* * *

 

Подробности

Если сравнить таблицы прерываний «классического» AT90S2313 и ATtiny2313, то окажется, что первые 11 векторов у них полностью совпадают. Отсутствующие в «классическом» аналоге остальные 8 векторов можно просто проигнорировать: если соответствующие прерывания не задействованы, то к ним никогда не произойдет обращения. По этой причине программы, написанные для AT90S2313, почти без оговорок совместимы с ATtiny2313 (не требуется даже заменять файл определений констант 2313def.inc). В дальнейшем мы будем без пояснений употреблять программы для обеих версий этого МК.

 

* * *

Здесь rjmp – знакомая нам команда безусловного перехода, a RESET, EXT_INTO и т. п. – метки в тексте программы, откуда начинается текст процедуры (подпрограммы) обработки прерывания. Метки могут быть обозначены и по‑иному, тут полный произвол. Сам переход осуществляется автоматически, если в процессе выполнения программы возникнут условия для возникновения соответствующего прерывания, для чего и его отдельно, и прерывания вообще надо еще разрешить.

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

Сначала заметим, что подобным образом должна выглядеть программа, если вы используете все 19 прерываний, чего на самом деле, конечно, не бывает. Но каждой записи rjmp <метка> должно соответствовать наличие метки далее в тексте, иначе ассемблер укажет на ошибку (посылать по неизвестному адресу нехорошо!). Поэтому если некоторые прерывания не используются, то, вообще говоря, можно вместо безусловных переходов наставить в соответствующих строках команд nор (nо operation – ее код равен просто нулям во всех разрядах), однако на практике вместо них чаще ставят команду reti , которая означает возврат из процедуры прерывания к выполнению основной программы, если вдруг оно возникнет.

* * *

 

Заметки на полях

На самом деле в общем случае не имеет значения, какую именно команду в этих строках ставить – выполняться они никогда не будут, если соответствующие прерывания не активированы, нам только нужно занять память, чтобы команда rjmp используемого нами прерывания оказалась по нужному адресу, – например, можно поставить команду rjmp $0000. Есть и другие, более корректные способы размещения команд по конкретным адресам памяти (с помощью директивы .org ), но не будем усложнять. Кстати, надо учесть, что данный способ относится, строго говоря, лишь к МК с памятью не более 8 Кбайт, уже для ATmega16 команды будут другими: вместо 2‑байтовой rjmp там применяется 4‑байтовая jmp (а вместо команды вызова подпрограммы rcall – call ).

 

* * *

Особое место занимает вектор, расположенный по самому первому, нулевому адресу, у нас он именуется RESET. Вообще говоря, вектор по нулевому адресу – это даже не совсем прерывание, а так называемый вектор сброса – мы ведь неоднократно говорили, что МК начинает выполнение программы с нулевого адреса. Программа из предыдущего раздела с него прямо начиналась (т. е. при включении питания сразу выполнялся первый оператор, потом второй и т. д.), но если задействованы прерывания, мы не можем так поступить. Поэтому в самом первой строке программы обязательно должен стоять указатель на процедуру, которая осуществляется в самом начале работы, и обычно так и называется: RESET. Эта процедура должна начинаться со следующих обязательных строк:

 

 

Если указатель стека не установить, то прерывания не заработают. Установка указателя стека для ряда моделей Tiny (в которых отсутствует SRAM) не требуется, а вот для других, в том числе и всех Mega , где количество SRAM превышает 256 байтов, эта загрузка будет протекать иначе, т. к. константа RAMEND там размером больше байта:

 

 

* * *

 

Подробности

Как мы уже отмечали в главе 18 , стек – область памяти, куда будут записываться адреса точек возврата при вызове подпрограмм по команде rcall или перехода к обработчикам прерываний. По окончании процедуры обработки прерывания (по команде reti ) или обычной подпрограммы (по команде ret ) этот адрес считывается в программный счетчик, и выполнение основной программы продолжается. Если во время выполнения обработчика данного прерывания происходит другое прерывание с большим приоритетом (это нужно специально разрешать, по умолчанию в AVR любое прерывание будет ожидать окончания обработки предыдущего), то в стек записывается также и этот текущий адрес команды, таким образом ошибиться при последовательном возврате невозможно. Стек устроен по принципу «последним вошел – первым вышел», т. е. извлекается всегда последнее записанное туда значение. Программист может использовать стек и для своих целей (командами push и pop – например, для сохранения текущего значения рабочих регистров), но неопытным программистам лучше активно этим способом не пользоваться – вероятность допустить трудно обнаруживаемую ошибку значительно возрастает. Тем более что в AVR и без того достаточно РОН (в отличие, например, от х51, где без стека обойтись практически невозможно), а при необходимости можно еще и хранить текущие значения в SRAM.

 


Дата добавления: 2019-02-12; просмотров: 268; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!