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

June 27, 2010 by admin Комментировать »

Для того чтобы сделать все то же самое, но «по-настоящему», придется вос­пользоваться таймером, а значит — прерываниями. А значит — текст про­граммы придется оформить несколько по-иному. Если помните, я говорил, что при возникновении прерывания процессор обращается по некоторому фиксированному адресу. Для каждой модели МК количество и типы преры­ваний различаются, поэтому эти адреса фиксированы для каждой модели процессора.

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

rjmp RESET ; Reset Handler rjmp EXT__INTO ; External InterruptO Handler rjmp EXT__INT1 ; External Interruptl Handler rjmp TIM1_CAPT ; Timer 1 Capture Handler rjmp TIM1_C0MPA ; Timer 1 CompareA Handler rjmp TIM1_0VF ; Timer 1 Overflow Handler rjmp TIM0_OVF ; Timer 0 Overflow Handler rjmp USARTO_RXC ; USARTO RX Complete Handler rjmp USARTO__DRE / USARTO,UDR Empty Handler rjmp USARTO_TXC ; USARTO TX Canplete Handler rjmp ANA_COMP ; Analog Comparator Handler rjmp PCINT ; Pin Change Interrupt rjmp TIMER1_C0MPB ; Timer 1 Compare В Handler

rjmp TIMER0_COMPA ; Timer О Compare A Handler

rjmp TIMER0_COMPB ; Timer 0 Compare В Handler

rjmp USI_START ; USI Start Handler

rjmp USI_OVERFLOW ; USI Overflow Handler

rjmp EE_READY ; EEPROM Ready Handler

rjmp WDT__OVERFLOW ; Watchdog Overflow Handler

Подробности

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

Здесь rjmp— знакомая нам команда безусловного перехода, а reset, EXTiNTO и т. п. — метки в тексте программы, откуда начинается текст про­цедуры (подпрограммы) обработки прерывания. Метки могут быть обозна­чены и по-иному, тут полный произвол. Сам переход осуществляется автома­тически, если в процессе выполнения программы возникнут условия для возникновения соответствующего прерывания, для чего и его отдельно, и прерывания вообще надо еще разрешить. Все прерывания и особенности их вызова мы рассматривать, конечно, не будем, а некоторые их них разберем далее на практике.

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

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

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

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

reset: ;Старт главной программы

Idi rl6,low(ramend) /только для 2313 out spl,rl6 /установка указателя стека

<начальные установки и разрешение необходимых прерываний>

sei /общее разрешение прерываний

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

RESET: ;для моделей с SRAM более 256 байт

ldi temp,low(RAMEND) ;загрузка указателя стека out SPL, temp

ldi temp,high(RAMEND) ;загрузка указателя стека out SPH, temp

Подробности

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

2 комментариев(ия)

  1. Димас says:

    а пример на Си можно?

  2. Vadim says:

    Пример для таймера:

    #include “iom16.h”

    long unsigned int counter = 0; //Счетчик для формирования временных интервалов
    unsigned char B0Pressed = 0; //Здесь хранится состояние кнопки0 (0 – не нажата, 1 – нажата)
    unsigned char B1Pressed = 0; //Здесь хранится состояние кнопки1 (0 – не нажата, 1 – нажата)

    //Инициализация таймера2
    //Нужно каждые 11059 такта (1 мс) увеличивать counter. У нас получается каждые 1,001175 мс
    void init_timer2()
    {
    OCR2 = 173;
    TCCR2 = (1 << WGM21) | (1 << CS22);
    TIMSK = (1 << 7);
    }

    //Инициализация портов ввода/вывода
    init_io_ports()
    {
    DDRA =(1<< DDA0)|(1<< DDA1)|(1<< DDA2)|(1<< DDA3)|(1<< DDA4)|(1<< DDA5)|(1<< DDA6)|(1<< DDA7);
    DDRB =(1<< DDB0)|(1<< DDB1)|(1<< DDB2)|(1<< DDB3)|(1<< DDB4)|(1<< DDB5)|(1<< DDB6)|(1<< DDB7);
    DDRC =(0<< DDC0)|(0<< DDC1)|(1<< DDC2)|(1<< DDC3)|(1<< DDC4)|(1<< DDC5)|(1<< DDC6)|(1<< DDC7);
    DDRD =(1<< DDD0)|(1<< DDD1)|(1<< DDD2)|(1<< DDD3)|(1<< DDD4)|(1<< DDD5)|(1<< DDD6)|(1<< DDD7);

    PORTA =(0<< PA0)|(0<< PA1)|(0<< PA2)|(0<< PA3)|(0<< PA4)|(0<< PA5)|(0<< PA6)|(0<< PA7);
    PORTB =(0<< PB0)|(0<< PB1)|(0<< PB2)|(0<< PB3)|(0<< PB4)|(0<< PB5)|(0<< PB6)|(0<< PB7);
    PORTC =(1<< PC0)|(1<< PC1)|(0<< PC2)|(0<< PC3)|(0<< PC4)|(0<< PC5)|(0<< PC6)|(0<< PC7);
    PORTD =(0<< PD0)|(0<< PD1)|(0<< PD2)|(0<< PD3)|(0<< PD4)|(0<< PD5)|(0<< PD6)|(0<< PD7);
    }

    //формирование задержки в Pause_ms миллисекунд
    void delay(long unsigned int Pause_ms)
    {
    counter = 0;
    while (counter < Pause_ms)
    {}
    }

    void main()
    {
    SREG |= (1 << 7); //Разрешаем прерывания
    init_timer2(); //Включаем таймер2 на каждые 64 такта, считать до 173
    init_io_ports(); //Включаем порты ввода/вывода
    while(1)
    {
    //Обработка кнопки 0
    if (B0Pressed == 1) //Если произошло нажатие на кнопку,
    { // уведичивает PORTB, ждет отпускания
    PORTB++;
    B0Pressed = 0;
    while ((PINC & (1 << PC0)) == 1)
    {}
    }
    else
    {
    if ((PINC & (1 << PC0)) == 1) //Фиксирует нажатие
    {
    delay(50); //Устранение “дребезга клавиш”
    if ((PINC & (1 << PC0)) == 1) //Проверяет нажатие
    {
    B0Pressed = 1; //Устанавливает флаг “кнопка нажата”
    }
    }
    }
    //Обработка кнопки 1
    if (B1Pressed == 1) //Если произошло нажатие на кнопку,
    { // уменьшает PORTB, ждет отпускания
    PORTB–;
    B1Pressed = 0;
    while ((PINC & (1 << PC1)) == 2)
    {}
    }
    else
    {
    if ((PINC & (1 << PC1)) == 2) //Фиксирует нажатие
    {
    delay(200); //Устранение “дребезга клавиш”
    if ((PINC & (1 << PC1)) == 2) //Проверяет нажатие
    {
    B1Pressed = 1; //Устанавливает флаг “кнопка нажата”
    }
    }
    }
    }
    }

    //Прерывание по таймеру 2, прн этом увеличение счетчика counter
    #pragma vector = TIMER2_COMP_vect
    __interrupt void inc_delay_counter()
    {
    counter++;
    }

Оставить комментарий

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