Учебный курс. Понятие прерывания. Подаем голос микроконтроллером.

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

Задача: заставить микроконтроллер по нажатию кнопки издавать звуковой сигнал.
Схема для нашего примера здесь. Файлы проекта здесь.

Создаем в старом workspace проект ring.
Задаем настройки проекта для конфигурации Release:


Выбираем тип микроконтроллера.
General Options > Target > Processor configuration
У меня это ATmega8535.  

Разрешаем использование имен битов определенных в хидер файле
В General Options > System ставим галочку Enable bit definitions in I/O-Include files
До сих пор мы не пользовались именами битов, но сегодня они нам понадобятся.   

Меняем тип выходного файла.
Linker > Output.
B поле Output file cтавим галочку Override default и заменяем расширение d90 на hex
В поле Format выбираем Other и в выпадающем меню Output format выбираем тип файла intel-standart

Сохраняем проект и workspace.

______________________________  Прерывание  ___________________________

    Представьте себе ситуацию. Вы сидите на работе и корпите над очередной микроконтроллерной програмулиной. Подходит к вам начальник и говорит: “Слушай, Паш, нам  осциллографы в отдел закупили - Tektronix, четырехканальные. Помоги Васе  притащить их”. Вы думаете: ”Ну, е-мое, только мысль поперла.. и на тебе”. А начальник так смотрит на вас, а глаза у него такие добрые, такие добрые. Как тут ему откажешь. Ну, вы все бросаете и идете с другом за осциллографами. Притащили. Отчитались. И снова сели за свою программу. Вот примерно так и выглядит механизм прерывания.

    Довольно просто, но есть ряд принципиальных моментов.
Во-первых:
-    вы делали свою работу
-    параллельно кто-то покупал осциллографы
-    по наступлению события «осциллографы закупили» - вы прерываете выполнение своей работы
-    некоторое время вы занимаетесь другой работой – тащите осциллографы
-    потом вы возвращаетесь на рабочее место и продолжаете делать свою работу с того места, на котором остановились

Во-вторых:
-    вы вполне могли бы послать начальника и никуда не идти
-    уйдя за осциллографами, вы могли задержаться там надолго, а то и вовсе не вернуться
-    вернувшись на рабочее место, вы могли бы уже позабыть свои гениальные идеи

    Все это очень похоже на то, что происходит в микроконтроллере. Микроконтроллеры AVR имеют в своем составе целую тучу периферийных устройств (таймеры/счетчики, аналого-цифровой преобразователь, аналоговый компаратор, асинхронный приемопередатчик…и т.д). Мощь микроконтроллера в том, что все эти устройства могут работать параллельно и независимо друг от друга, а также параллельно выполняемой программе. Каждое периферийное устройство может вызывать прерывание по наступлению определенного события. Прерывание будет происходить только в том случае, если оно разрешено. Разрешение прерываний устанавливается для каждого устройства отдельно. Помимо этого есть флаг глобального разрешения/запрещения всех прерываний – это I флаг в регистре SREG. При возникновении прерывания микроконтроллер сохраняет содержимое счетчика команд PC в стеке, то есть запоминает место, на котором его прервали.  Загружает в счетчик команд адрес соответствующего вектора прерывания и переходит на этот адрес. Попадает на команду безусловного перехода по которой переходит на подпрограмму обработки прерывания. Запрещает прерывания сбросом флага I, выполняет подпрограмму. Выполнив подпрограмму обработки прерывания, микроконтроллер разрешает прерывания, устанавливая флаг I, и восстанавливает содержимое счетчика команд, то есть возвращается на то же место программы, на котором его прервали.
    По идее, обработчик прерывания не должен повреждать содержимое регистров микроконтроллера, потому что они могут содержать данные программы выполняемой в тот момент. Для этого в начале подпрограммы обработки прерывания содержимое регистров микроконтроллера сохраняют в стеке, а в конце подпрограммы восстанавливают. Таким образом, выйдя из прерывания, микроконтроллер сможет продолжить выполнение программы, как ни в чем не бывало. Программируя на ассемблере, сохранение регистров прописывает сам программист, на Си – это делает компилятор.

_______________________________________________________________

   Теперь поговорим о таймере. ATmega8535 имеет на борту три  таймера/счетчика - два восьмиразрядных (T0, T2) и один шестнадцатиразрядный (T1). Мы будем использовать восьмиразрядный таймер/счетчик T0. В состав этого таймера входят три регистра - регистр управления TCCR0, счетный регистр TCNT0 и регистр сравнения OCR0. Когда таймер запущен, счетный регистр TCNT0 увеличивает свое значение на единицу на каждый перепад тактового сигнала. Частота тактового сигнала выбирается из нескольких возможных значений в регистре управления TCCR0. Также с помощью этого регистра устанавливается режим работы таймера. Таймер T0 может вызвать прерывание по наступлению события “переполнение” – это когда переполняется счетный регистр TCNT0 и по наступлению события “совпадение” – это когда значение счетного регистра TCNT0 становится равным значению регистра сравнения OCR0. Флаги разрешающие эти прерывания находятся в регистре TIMSK. 
    Мы настроим таймер/счетчик Т0 так, чтобы он вызывал прерывание по событию “совпадение” с частотой 5 кГц. В функции обработчике будем инвертировать состояние вывода микроконтроллера, к которому подключен пьезодинамик. Таким образом, частота пищания пьезика будет равна 2,5 кГц.  (Подключен именно пьезодинамик! Не перепутайте. У пьезодинамика сопротивление зависит от частоты и на 2,5 КГц оно обычно еденицы Ком, поэтому его можно подключать к выводу микроконтроллера напрямую, без ограничительного резистора).

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

//Подаем голос микроконтроллером AVR
#include <ioavr.h>
#include <intrinsics.h>


int main(void)
{
//настраиваем порты ввода-вывода
  DDRD = (0<<PD1)|(1<<P0);
  PORTD = (1<<PD1)|(0<<PD);

//настраиваем таймер Т0
  TCCR0 = (1<<WGM01)|(0<<GM00)|(0<<CS02)(1<<CS01)(0<<CS00)
  TCNT0 = 0;
  OCR0 = 0xc8;
 
//разрешаем прерывания  
  __enable_interrupt();
 
//основной цикл программы – опрос кнопки
  while(1){
       if ((PIND & (1<<PD0)) == 0)
            TIMSK = (1<<OCIE0);
       else
            TIMSK = 0;
  }
  return 0;
}

//обработчик прерывания таймера Т0
#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect(void)
{
        PORTD ^= (1<<PD1); //инвертируем сигнал на выводе PD1
}


Настройка портов

    В нашей схеме к порту D подключена кнопка и пьезодинамик. Вывод, к которому подключена кнопка, нужно настроить на вход и включить подтягивающий резистор. Вывод, к которому подключен пьезодинамик, нужно настроить на выход.

    DDRD = (0<<PD1)|(1<<PD0);
    PORTD = (1<<PD1)|(0<<PD0);

Настройка таймера
    
    Режим работы таймера Т0 – СТС(сброс при совпадении), тактовый сигнал – clk/8. Отражаем это в регистре TCCR0

    TCCR0 = (1<<WGM01)|(0<<WGM0)|(0<<CS02)(1<<CS01)(0<<CS00)

Обнуляем на всякий случай счетный регистр TCNT0

    TCNT0 = 0;

В регистр сравнения OCR0 записываем 0xc8. Почему? Потому что я посчитал это на калькуляторе. Ну а на бумаге этот расчет выглядит так.
Тактовая частота микроконтроллера 8 МГц
Тактовый сигнал таймера равен 8000000 Гц/8 = 1000000 Гц.    
Время одного такта таймера 1/1000000 = 1 мкс
Время одного такта нужной нам частоты 1/5000 Гц = 200 мкс
Сколько тактов таймера укладывается в 200  мкс?   200/1 = 200 тактов
200 в шестнадцатеричной системе = 0xс8

    OCR0 = 0xс8;

Подробное описание таймера T0 смотрите в документации на ATMega8535.

Таймер мы настроили, разрешаем общее прерывание, используя встроенную функцию.

    __enable_interrupt();

Опрос кнопки

    Когда кнопка не нажата, вывод микроконтроллера через внутренний подтягивающий резистор подключен к питанию, то есть на выводе присутствует  единичка, когда кнопка нажата, вывод замкнут на землю, то есть на выводе ноль. Чтобы определить нажата ли кнопка, нужно считать содержимое регистра PIND и проверить значение нулевого бита (к PD0 подключена кнопка). Опрашивать кнопку будем в бесконечном цикле.


  while(1)
  {
        if ((PIND & (1<<PD0)) == 0){
                        //если кнопка нажата – микроконтроллер должен верещать
       }
       else {
                         //если нет - молчать как рыба
       }
  }

Не забывайте == это не оператор присваивания =.

Обработка нажатия/отпускания кнопки

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

TIMSK = (1<<OCIE1); // разрешаем прерывание таймера Т0 по событию совпадение

TIMSK = 0; //запрещаем прерывание

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

Функция прерывания

Это не совсем простая функция, поскольку ее вызывает микроконтроллер, и она имеет особенный синтаксис.

_____________________ Cинтаксис функции прерывания _____________________


Функция прерывания задается с помощью директивы #pragma vector= и служебного слова __interrupt. Функция должна иметь тип void и не должна принимать никаких параметров.

#pragma vector = Address
__interrupt void Name(void)
{
    //здесь располагается наш код
}

Name – имя функции, выбираем на наше усмотрение
Address – адрес вектора прерывания, можно задавать числом, можно именами определенными в заголовочном файле микроконтроллера (iom8535.h – раздел Interrupt Vector Definitions)

______________________________________________________________

Для нашей задачи функция-обработчик прерывания выглядит так

#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect(void)
{
        PORTD ^= (1<<PD1); //инвертируем сигнал на выводе PD1
}

Ну вот собственно и все. Надеюсь все понятно.
В следующий статье заставим микроконтроллер играть мелодию.

Схема для нашего примера здесь. Файлы проекта здесь.

У вас недостаточно прав для комментирования.