Коллизии прерываний

Тема в разделе "Микроконтроллеры AVR", создана пользователем ostrov, 7 апр 2016.

  1. a1000

    a1000 Гик

    Не буду плодить новых тем, спрошу тут.
    Разбираясь с ассемблером, чисто в учебных целях решил объединить примеры работы с ADC и USART. Примеры были для ATmega16, переделывал на ATmega8. Заменил заголовочный файл и адреса прерываний. При компилировании результатов Atmel Studio упорно ругалась на строку с указателем вектора ADC Conversion Complete
    Код (C++):
    .org $00e // Указатель вектора ADC_Conv(Прерывание по выполнению конвертации АЦП)
    Долго не мог понять в чём причина, пока не обратил внимание на очерёдность записи векторов
    Код (C++):
    .org $00e // Указатель вектора ADC_Conv(Прерывание по выполнению конвертации АЦП)
    rjmp ADC_Conv // Переходим на ADC_Conv
    .org $00d // Адрес вектора прерывания по посылке байта
    rjmp USART_TXC ; // Переходим на USART_TXC
    Поменял местами
    Код (C++):
    .org $00d // Адрес вектора прерывания по посылке байта
    rjmp USART_TXC ; // Переходим на USART_TXC
    .org $00e // Указатель вектора ADC_Conv(Прерывание по выполнению конвертации АЦП)
    rjmp ADC_Conv // Переходим на ADC_Conv
     
    всё стало работать. Собственно вопрос - таблица векторов прерываний обязательно должна заполняться в порядке приоритета этих прерываний?
     
  2. AlexU

    AlexU Гуру

    В приведённых Вами исходниках ни чего крамольного нет -- всё должно работать.
    Надо чуть больше информации (касается примера, где прерывания декларируются в "неправильном" порядке).
     
  3. a1000

    a1000 Гик

    Вот весь код
    Код (C++):
    .include "m8def.inc" // Подключаем заголовочный файл ATMega16
    .def temp = r16 // Присваиваем символические имена регистрам
    .def razr1 = r17
    .def razr2 = r18
    .equ BAUD = 51  //задаём битрейт USART
    .dseg // Сегмент ОЗУ

    .cseg // Программный сегмент
    .org 0 // Указатель вектора Reset
    rjmp Reset // Переходим на Reset

    .org $00e // Указатель вектора ADC_Conv(Прерывание по выполнению конвертации АЦП)
    rjmp ADC_Conv // Переходим на ADC_Conv

    .org $00d // Адрес вектора прерывания по посылке байта
    rjmp USART_TXC ; // Переходим на USART_TXC

    //.org $00e // Указатель вектора ADC_Conv(Прерывание по выполнению конвертации АЦП)
    //rjmp ADC_Conv // Переходим на ADC_Conv

    Reset: // Метка Reset
    ldi temp, high(RAMEND) // Инициализируем стек
    out sph, temp
    ldi temp, low(RAMEND)
    out spl, temp
    //////////////////////////////// настройка АЦП ///////////////////////////////
    ldi temp, 0xff // Константа в temp
    out DDRD, temp // Порт D на выход, сюда будем выводить данныые с АЦП

    ldi temp, 0b01100000 // REFS = 01 контролирует откуда берется опорное напряжение; ADLAR = 1
    out ADMUX, temp // смещение битов(право или лево); мультиплексор на ножку ADC0, биты MUX = 00000  
    ldi temp, 0b11011111 // ADEN = 1 разрешаем АЦП; ADSC = 1 старт конвертации; ADIF, ADIE = 11
    out ADCSRA, temp // разрешают прерывание, ADPS = 111 предделитель АЦП(смотри тех.док.)
    ///////////////////////////////////// настройка USART ////////////////////////////////
    ldi temp, high(BAUD) // Записываем вычисленное число для битрейта
    out UBRRH, temp // Сперва в старший регистр UBRR
    ldi temp, low(BAUD) // Затем в младший регистр
    out UBRRL, temp
    ldi temp, 0b11011000 // Устанавливаем биты RXCIE, TXCIE, RXEN, TXEN, биты разрешают прерывания
    out UCSRB, temp // И работу с USART
    ldi temp, 0b10000110 // Устанавливаем бит URSEL = 1, это бит селектора(х.з. зачем он, но надо)
    out UCSRC, temp // биты UCSZ говорят о том, что прием/передача будет по 8 бит

    sei // Разрешаем глобально прерывания
    Proga: // Основная программа, тут ничего не делаем
    rjmp Proga // переход на метку Proga

    ADC_Conv: // Прерывание АЦП, сюда влетим как конвертация сделалась
    cli // запрещаем прерывания
    in razr1, ADCL // Копируем в razr1 значение младшего регистра
    in razr2, ADCH // в razr2 значение старшего, читать всегда сперва младший!
    out UDR, razr2 // Отправляем полученное значение по USART
    sei // Разрешить прерывания
    reti // Выйти из прерывания

    USART_TXC: // Прерывание по отсылке байта
    cli // Запрещаем прерывания
    sbis UCSRA,UDRE // Стандартная проверка из тех.дока, если UDRE = 1 пропускаем следующую строку
    rjmp USART_TXC // Либо вращаемся в цикле
    ldi temp, 0b01100000 // Снова заряжаем АЦП, по аналогии как в предварительных настройках
    out ADMUX, temp
    ldi temp, 0b11011111
    out ADCSRA, temp
    sei // Разрешить прерывания
    reti // Выйти из прерывания
    В таком виде выдаёт ошибку.
     
  4. a1000

    a1000 Гик

    А так нет
    Код (C++):
    .include "m8def.inc" // Подключаем заголовочный файл ATMega16
    .def temp = r16 // Присваиваем символические имена регистрам
    .def razr1 = r17
    .def razr2 = r18
    .equ BAUD = 51  //задаём битрейт USART
    .dseg // Сегмент ОЗУ

    .cseg // Программный сегмент
    .org 0 // Указатель вектора Reset
    rjmp Reset // Переходим на Reset

    //.org $00e // Указатель вектора ADC_Conv(Прерывание по выполнению конвертации АЦП)
    //rjmp ADC_Conv // Переходим на ADC_Conv

    .org $00d // Адрес вектора прерывания по посылке байта
    rjmp USART_TXC ; // Переходим на USART_TXC

    .org $00e // Указатель вектора ADC_Conv(Прерывание по выполнению конвертации АЦП)
    rjmp ADC_Conv // Переходим на ADC_Conv

    Reset: // Метка Reset
    ldi temp, high(RAMEND) // Инициализируем стек
    out sph, temp
    ldi temp, low(RAMEND)
    out spl, temp
    //////////////////////////////// настройка АЦП ///////////////////////////////
    ldi temp, 0xff // Константа в temp
    out DDRD, temp // Порт D на выход, сюда будем выводить данныые с АЦП

    ldi temp, 0b01100000 // REFS = 01 контролирует откуда берется опорное напряжение; ADLAR = 1
    out ADMUX, temp // смещение битов(право или лево); мультиплексор на ножку ADC0, биты MUX = 00000  
    ldi temp, 0b11011111 // ADEN = 1 разрешаем АЦП; ADSC = 1 старт конвертации; ADIF, ADIE = 11
    out ADCSRA, temp // разрешают прерывание, ADPS = 111 предделитель АЦП(смотри тех.док.)
    ///////////////////////////////////// настройка USART ////////////////////////////////
    ldi temp, high(BAUD) // Записываем вычисленное число для битрейта
    out UBRRH, temp // Сперва в старший регистр UBRR
    ldi temp, low(BAUD) // Затем в младший регистр
    out UBRRL, temp
    ldi temp, 0b11011000 // Устанавливаем биты RXCIE, TXCIE, RXEN, TXEN, биты разрешают прерывания
    out UCSRB, temp // И работу с USART
    ldi temp, 0b10000110 // Устанавливаем бит URSEL = 1, это бит селектора(х.з. зачем он, но надо)
    out UCSRC, temp // биты UCSZ говорят о том, что прием/передача будет по 8 бит

    sei // Разрешаем глобально прерывания
    Proga: // Основная программа, тут ничего не делаем
    rjmp Proga // переход на метку Proga

    ADC_Conv: // Прерывание АЦП, сюда влетим как конвертация сделалась
    cli // запрещаем прерывания
    in razr1, ADCL // Копируем в razr1 значение младшего регистра
    in razr2, ADCH // в razr2 значение старшего, читать всегда сперва младший!
    out UDR, razr2 // Отправляем полученное значение по USART
    sei // Разрешить прерывания
    reti // Выйти из прерывания

    USART_TXC: // Прерывание по отсылке байта
    cli // Запрещаем прерывания
    sbis UCSRA,UDRE // Стандартная проверка из тех.дока, если UDRE = 1 пропускаем следующую строку
    rjmp USART_TXC // Либо вращаемся в цикле
    ldi temp, 0b01100000 // Снова заряжаем АЦП, по аналогии как в предварительных настройках
    out ADMUX, temp
    ldi temp, 0b11011111
    out ADCSRA, temp
    sei // Разрешить прерывания
    reti // Выйти из прерывания
     
  5. AlexU

    AlexU Гуру

    Директива .org задаёт начальный адрес памяти, в которую будет "укладываться" всё, что после этой директивы -- всё до конца исходника или до следующей директивы .org.
    В том коде, где выдаёт ошибку, Вы устанавливаете:
    Код (C++):
    .org $00e
    rjmp ADC_Conv
    соответственно по адресу 0x00E будет записана команда перехода на обработчик ADC_Conv.
    Потом:
    Код (C++):
    .org $00d
    rjmp USART_TXC
    по адресу 0x00D будет команда перехода на обработчик USART_TXC.
    А далее:
    Код (C++):
    Reset:
    ldi temp, high(RAMEND)
    по адресу 0x00E (вместо перехода на обработчик ADC_Conv) будет записана команда загрузки значения.
    И так далее...

    Нужна ещё одна директива .org.
     
    parovoZZ нравится это.
  6. AlexU

    AlexU Гуру

    А вообще с этими директивами лучше не мудрить, а прописывать всю таблицу векторов полностью. Для неиспользуемых векторов обычно ставят заглушки, которые перенаправляют на вектор RESET.
     
    a1000 нравится это.
  7. a1000

    a1000 Гик

    Как-то думалось, что память отведённая под таблицу векторов по другому не используется. Видимо память экономят. Видел тут другой вариант заглушки для неиспользуемых векторов
    Код (C++):
    .ORG $000        ; (RESET)
    RJMP   Reset
    .ORG $002
    RETI          
    ORG $004
    RETI
    ...................................................    
    тогда не понял зачем это нужно, но теперь думаю, что этот вариант имение травматичен чем RESET.
    Большое спасибо за быструю и квалифицированную помощь.
     
  8. DetSimen

    DetSimen Гуру

    или на iret/reti (не помню), если заморачиваца не хочеца. Но вызов неиспользуемого/неописанного прерывания - критическая ошибка, я бы сётаки на RESET переназначил.

    iret/reti отличается от ret тем, что сначала восстанавливается SREG а потом делается возврат, нельзя сделать просто ret из прерывания, во-первых, стек будет несбалансирован, во-вторых хрен знает, куда вернешьса.
     
  9. AlexU

    AlexU Гуру

    Си-шный компилятор различает участки памяти по типам (точнее это делает линкер). А ассемблер этого не делает и вся память для него одинаковая (имеется в виду flash) -- всё команды для исполнения. И исполнение начинается с адреса прерывания RESET.
    Если посмотреть на Ваш "работающий" код, так там часть таблицы прерываний используется для хранения кода программы. После метки 'Reset' следующие четыре команды занимают место четырёх последних векторов прерываний. И ничего -- всё работает.
     
  10. a1000

    a1000 Гик

    Не судите строго, я только учусь.:)
     
  11. AlexU

    AlexU Гуру

    Может не правильно выразился, но ни какого осуждения в сообщении выше не было. Была попытка объяснить суть кода, т.к. у Вас нет чёткого понимания, что получается на выходе.
     
  12. a1000

    a1000 Гик

    Всё нормально. Вы хорошо и доходчиво разъяснили суть ошибки. Я благодарен за консультацию.