Занимательная микроэлектроника — страница 64 из 117

е его с упомянутым ASMEdit.

Для начала работы нам предварительно надо создать командный файл. Предположим, у вас файл avrasm32.exe находится в созданной вами папке c: \avrtools. Запустите Блокнот и введите в него следующий текст (соответственно измените путь, если папка другая): c: \avrtools\avrasm32 — fI %1.asm

Сохраните этот файл под именем, например, asm32.bat в той же папке, что и avrasm32.exe. Теперь запустите ASM Editor, выберите в меню пункт Service|Properties и в появившемся окне найдите вкладку Project, а в ней — строку Assemble ASM file (рис. 13.5). В эту строку, как и показано на рисунке, внесите путь к нашему bat-файлу. Больше ничего настраивать не требуется, остальные пункты можно оставить, как есть, или очистить — по желанию. Теперь по нажатию сочетания клавиш +, вне зависимости от того, где находится файл с редактируемой программой, он скомпилируется, и результат (т. н. hex-файл, о чем далее), если нет ошибок, окажется в той же папке, где и исходный текст.



Рис. 13.5.Настройка редактора ASM Editor


Одновременно с компиляцией отдельно возникает консольное (с белыми надписями на черном фоне) окно с сообщениями компилятора. Сразу смотрите на последние строки, где должна быть надпись «Assembly complete with no errors» («ассемблирование выполнено без ошибок»). Если такая надпись есть, ищите готовый hex-файл в папке с исходным текстом. В противном случае hex-файла не окажется (а предыдущий его вариант, если он был, будет стерт), а в этом окне появятся номера строк в исходном тексте с ошибками и примерным описанием проблемы в данной строке (например, сообщение «Undefined variable reference» — «ссылка на неопределенную переменную», означает, что у вас в данной строке идентификатор, который нигде не определен).

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


Структура программы AVR

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

Естественно, программу сначала нужно в память записать, причем записать совершенно определенным образом, так, чтобы МК «знал», откуда начинать при включении питания или после подачи импульса Reset. Это его «знание» в случае современных МК AVR также программируется, однако для простоты будем считать, что программа всегда начинает читаться с самой первой ячейки памяти программ, т. е. с нулевого адреса. (А вот для классического процессора семейства i86 это не так. По умолчанию он всегда начинал с команды, расположенной за 16 байт до конца первого мегабайта памяти, т. е. по адресу FFFFOh. К тому же, адрес этот можно изменять, но мы этим здесь заниматься не будем.) Исходя из этих обстоятельств, программа должна иметь определенную структуру.

По начальному (нулевому) адресу всегда располагается одна и та же команда безусловного перехода (по-английски jump — «прыжок»), которая записывается так:

rjmp RESET

Или так:

jmp RESET

Отметьте себе на память, что AVR-ассемблер, в отличие от канонического С, регистры букв не различает (одинаково правильной будет форма записи jmp, JMP и jmp, так же как Reset, RESET и reset).

Форма написания (jmp или rjmp) зависит от типа контроллера: если в нем объем памяти программ меньше или равен 8 кбайт, то всегда (и не только в этом случае) используется команда rjmp (relative jump, т. е. «относительный безусловный переход»). Она занимает в памяти два байта, как и практически все остальные команды AVR, о чем мы еще поговорим. Код самой команды в этих двух байтах занимает старшую тетраду старшего байта (т. е. четыре бита), остальные 12 бит представляют собой адрес, куда переходить. В данном случае компилятор подставит адрес команды, следующей сразу за меткой RESET, с которой и начнется собственно выполнение программы. Метка с этим именем, естественно, всегда должна присутствовать, но может быть расположена уже в любом другом удобном месте программы, за исключением еще нескольких первых адресов, о назначении которых далее. Метка, кстати, может называться и не RESET, а любым другим именем, просто так принято для удобства чтения: хотите найти в любой программе ее начало — ищите метку RESET.

Вернемся к форме записи команды. 12 бит адреса могут представлять 4096 различных адресов. Так как единицей объема памяти программ служит слово из двух байтов (а не отдельный байт), как раз из-за того, что любая команда занимает не менее чем два байта, то общий объем адресуемой таким образом памяти и составит 8 кбайт. А вот если памяти больше, то приходится использовать команду jmp (абсолютный безусловный переход). Она состоит из четырех байт, в которых адрес займет 22 бита, и потому с ее помощью можно адресовать до 4 М (8 Мбайт) памяти. Мы условимся, что старшие модели семейства Mega применять не будем, и потому ограничимся командой rjmp.

Подведем некоторый итог: мы уже узнали многое о структуре типовой программы для AVR. Она должна начинаться с безусловного перехода на метку RESET, а само начало этой программы будет располагаться сразу после этой метки где-то в другом месте программы. А почему так странно? Нельзя ли начать прямо сразу с нулевого адреса, строка за строкой, зачем нужны какие-то переходы? Можно, отвечают нам авторы описаний AVR. Простейшая программа может начинаться с нулевого адреса без переходов, но только в одном случае: если мы обязываемся отказаться от прерываний. А без них контроллер теряет 90 % своей функциональности. Конечно, можно придумать пример программы, которая бы делала что-нибудь полезное, но при этом работала совсем без прерываний. Но на практике без прерываний обходятся только тогда, когда штатных прерываний хватает (например, для отслеживания состояния большого количества кнопок), или когда без них программа получается компактнее (такие случаи мы еще встретим).


Обработка прерываний

AVR по умолчанию ожидает, что сразу после первой команды с адресом $0000 идет таблица т. н. векторов прерываний. Вектор — это просто отсылка по нужному адресу с помощью команды rjmp. Адрес обозначается меткой, может располагаться в любом месте программы, и содержит начало процедуры обработки прерывания. Первый вектор располагается по адресу $0001 (а для МК с памятью более 8 К — по адресу $0002, потому что по адресу $0001 находится вторая половина более длинной команды jmp), причем адрес этот означает номер двухбайтного слова в памяти, а не отдельного байта (оно в два раза меньше количества байт). Попутно заметим, адреса в SRAM (ОЗУ) именно байтовые, а не пословные (см. далее). На самом деле в режиме по умолчанию нам вообще не нужно думать про абсолютные адреса и их нумерацию: первая команда программы (rjmp RESET), которая еще называется вектором сброса (или вектором начальной загрузки), автоматически расположится по нулевому адресу, вторая — по адресу $0001 и т. д. Найдя какую-нибудь команду перехода по метке, компилятор автоматически подставит абсолютные адреса.

Порядок векторов и их количество в таблице жестко задано в соответствии с типом МК. Потому самое первое, что вы должны сделать, приступая к программированию, — открыть руководство по применению выбранного типа контроллеров и скопировать оттуда эту таблицу. Можно попытаться сделать это прямо через буфер обмена из PDF-описания или преобразовав его в другой формат, так меньше вероятность что-то пропустить, только придется потом удалить указанные там абсолютные адреса, стоящие в начале каждой строки. Вот как будет выглядеть заготовка начала программы для МК ATmega8 (листинг 13.1).

Листинг 13.1.

;=======прерывания=======

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  ;Timer1 Capture Handler

rjmp TIM1_COMPA  ;Timer 1 CompareA Handler

rjmp TIM1_COMPB  ;Timer1 CompareB Handler

rjmp TIM1_OVF  ;Timer1 Overflow Handler

rjmp TIM0_OVF  ;Timer0 Overflow Handler

rjmp SPI_STC  ;SPI Transfer Complete Handler

rjmp USART_RXC  ;USART RX Complete Handler

rjmp USART_UDRE  ;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

;========================

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