AVR по умолчанию ожидает, что сразу после первой команды с адресом $0000 идет таблица т. н. векторов прерываний. Вектор — это просто отсылка по нужному адресу с помощью команды rjmp. Вообще-то вектор по нулевому адресу, на который программа переходит по сбросу (вектор сброса или вектор начальной загрузки), тоже считается прерыванием, хотя оно занимает особое место.
Адрес обозначается меткой, может располагаться в любом месте программы и содержит начало процедуры обработки прерывания. Первый вектор располагается по адресу $0001 (а для МК с памятью более 8 К — по адресу $0002, потому что по адресу $0001 находится вторая половина более длинной команды jmp), причем напомним, что для памяти программ адрес этот означает номер двухбайтового слова в памяти, а не отдельного байта. На самом деле по умолчанию нам вообще не нужно думать про абсолютные адреса и их нумерацию — первая команда программы (rjmp reset) автоматически расположится по нулевому адресу, вторая — по адресу $0001 и т. д. Найдя какую- нибудь команду перехода по метке, компилятор автоматически подставит абсолютные адреса. Нужно только быть внимательным при выборе модели: если вы подставите для МК ATmega8 в таблицу прерываний jmp вместо rjmp (а для ATmega16 и старше наоборот), то сброс у вас пройдет нормально, а вот все остальные прерывания выполняться не будут — скорее всего, программа просто "повиснет".
Порядок следования векторов и их число в таблице жестко заданы в соответствии с типом МК. Потому самое первое, что вы должны сделать, приступая к программированию, — открыть руководство по применению выбранного типа контроллеров и скопировать оттуда эту таблицу. Можно прямо через буфер обмена из PDF-описания (если вам позволят это сделать — в последних версиях описаний копирование текста через буфер обычно запрещено, что не делает чести менеджменту Atmel, но всегда можно вывернуться с помощью программ, удаляющих ограничения PDF, таких как Foxit Reader или платный Advanced PDF Password Recovery). Так меньше вероятность что-то пропустить, только придется потом удалить указанные там абсолютные адреса, стоящие в начале каждой строки. Начало программы для МК ATmega8.
Листинг ;=========прерывания================ гjmp 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 ; Timer1 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 ;==================================
Но постойте: мы что, обязаны использовать все прерывания? Конечно, нет. Для неиспользуемых прерываний в контроллерах с памятью программ менее 16 кбайт команду rjmp [метка] следует заменить на reti — выход из прерывания ("return interrupt"). На самом деле можно было бы указать и команду nop — пустую операцию. Мы будем ставить именно reti, т. к. тогда нам неважно — если прерывание случайно инициализировано, оно все равно не будет выполняться, а писать и отлаживать программы так удобнее. Я в своих программах просто дополняю стандартные строки командой reti и точкой с запятой, чтобы закомментировать команду rjmp.
Листинг
.include "m8def.inc" . . . . . . . . . . . .def temp = r16 ;рабочая переменная rjmp RESET ; Reset Handler reti ;rjmp EXT_INT0 ;IRQ0 Handler reti ;rjmp EXT_INTl ;IRQ1 Handler . . . . . . . . . . .
Теперь заготовка начала программы готова: при необходимости в дальнейшем использовать какое-то прерывание, мы удаляем из соответствующей строки фрагмент reti, а затем где-то в программе ставим нужную метку и пишем обработчик, заканчивающийся командой reti.
Листинг rjmp RESET ;Reset Handler rjmp EXT_INT0 ;IRQ0 Handler . . . . . . . . . . . EXT_INT0: ;процедура обработки прерывания INT0 . . . . . . . . . . .
reti ;окончание процедуры обработки прерывания INT0 Есть и более короткий способ оформления таблицы векторов прерываний (он особенно актуален для старших Mega, где число прерываний может достигать нескольких десятков, а из-за четырехбайтового формата команды jmp заменить ее на reti просто так не получается). Способ основан на использовании директивы org, которая устанавливает абсолютный адрес в памяти программ. В inc-файлах есть специальные определения констант для адресов прерываний, например (из файла 8515def.inc):
.equ INT0addr=$001; External Interrupt0 Vector Address . . . . . . . . . . . .equ URXCaddr=$009; UART Receive Complete Interrupt Vector Address .equ UDREaddr=$00a; UART Data Register Empty Interrupt Vector Address .equ UTXCaddr=$00b; UART Transmit Complete Interrupt Vector Address . . . . . . . . . . .
Тогда, если вам, к примеру, никакие иные прерывания не требуются, кроме прерываний URXC и UDRE для UART.
Листинг ;Установка векторов прерываний .org 0 ;начало программы после сброса rjmp RESET .org UDREaddr ;адрес прерывания UDRE rjmp TransUART .org URXCaddr ;адрес прерывания URXC rjmp ReceiveUART .org $0D ;только для 8515 Classic . . . . . . . . . . . [программа] . . . . . . . . . . .
Здесь TransUART и ReceiveUART — процедуры, которые выполняются при возникновении соответствующих прерываний. Обратите внимание на то, что при смене модели здесь не требуется что-либо исправлять (при условии, конечно, что новая модель будет поддерживать такие же прерывания), за исключением строки .org $0D. В модели 8515 всего 12 прерываний + сброс, а объем памяти 8 кбайт, поэтому таблица прерываний занимает 13 двухбайтовых ячеек, и программа может начинаться с ячейки номер $0D. В других моделях это обязательно будет другое число, т. к. количество прерываний отличается, причем для моделей с объемом памяти программ меньше или равной 8 кбайт к адресу последнего прерывания нужно добавить единицу, а в моделях с объемом памяти более 8 кбайт — двойку.
Если вы боитесь ошибиться, то более-менее универсальный метод заключается в том, чтобы пренебречь потерями пространства памяти и всегда начинать программу с адреса, заведомо большего следующего за вектором последнего прерывания, например, так: .org $40. В данном случае мы потеряем первые 64 ячейки памяти минус занятые под реально действующие векторы. Обратите внимание, что в старших моделях Mega таблица прерываний может занимать и больше места, чем 64 ячейки (так, в ATmega128 программа может начинаться лишь с ячейки $46), но для них, за небольшим исключением, и названия прерываний будут иными, так что все равно программу придется править (или использовать условную компиляцию).
|