mikroPascal for AVR. Урок 4. Прерывания, прерывания и еще раз прерывания. Таймеры

Как и было указано в описании, этот урок будет посвящен только прерываниям. Если вас волнует вопрос о интерфейсах, про которые ничего не было сказано (IC, OneWire, SPI), то отвечу: просто я с ними сам толком не разобрался, как — то надобности не было. Как разберусь, постараюсь сразу выложить сюда.

В качестве «подопытного кролика» возьмем микроконтроллер ATmega8. Этот МК всем знаком, и хотя бы одна штука есть у каждого радиолюбителя.

Если вы еще не работали с прерываниями, то первым делом посмотрите таблицу прерываний для выбранного МК. В этой таблице указаны адреса и вектора прерываний. Чем выше адрес, тем ниже приоритет прерывания. К примеру, адрес INT0= 0x01, а TIMER0 OVF = 0x09. Соответственно, если одновременно поступят сигналы, то первым выполнится прерывание по INT0.

Что касается регистров, то очень доходчиво их назначение раскрыто в даташите. Да для различных контроллеров они различны, по этому нет смысла тут их описывать. Так что рассмотим только то, что нам потребуется в сегодняшнем уроке. А это регистры TCCR0, TCCR1B, TIMSK, GICR.

1)TCCR0. Это регистр для выбора коэффициента предделения для таймера T0.

2)TCCR1B. 

Регистр, отвечающий как за предделитель (биты 2-0), так и за следующее: подавление дребезга контактов на входе ICP1 (бит 7), выбор фронта срабатывания прерывания по захвату (бит 6), ШИМ (биты 4,3). 

Предделитель настраивается так же как и для T0 (рисунок выше).

3)TIMSK. Это регистр «выбора» прерываний.

Прерывание по совпадению ТС2 (бит 7), прерывание по переполнению ТС2 (бит 6),  прерывание по захвату ТС1 (бит 5), прерывание по совпадению A ТС1 (бит 4), прерывание по совпадению В ТС1 (бит 3), прерывание по переполнению ТС1 (бит 2), не используется (бит 1), прерывание по переполнению ТС0 (бит 0).

4)GICR (он же GIMSK) — запрет/разрешение прерываний по сигналам на входах INT0, INT1

  

Разобравшись с даташитом, можно перейти к mikroPascal. Итак, конфигурация регистров, разрешение и запрет прерывания.

 TCCR1B:=0x02; // Можно сделать так (предделитель 8); TCCR1B.B0:=1; // А можно так. То же самое, предделитель на 8; TCCR1B.CS10:=1; // И это тоже установка предделителя на 8!  asm cli end; // Запрет прерываний. asm sei end; // Разрешение прерывний.  // Использование ассемблерных вставок может сильно снизить переносимость вашей программы с одного МК на другой.

Вот так, несколькими способами можно сконфигурировать не только предделитель, но все остальное, например прерывание по входу INT0.

 GICR:=0x64; GICR.B7:=1; GICR.INT0:=1;

Теперь, когда основы просмотрены, можно перейти к практике.

1. Прерывание по переполнению T0.

 program timerInt_0;   var count,i:integer;  procedure timer0_ovf(); iv IVT_ADDR_TIMER0_OVF; begin      if i>=1 then begin         if count>=4 then begin  // Так как таймер 8-ми битный, то макс. значение = 256.            PORTB0_bit:=1;       // Тгогда берем частоту кварца, делим на 1024 , а потом еще на 256.            count:=0;            i:=0;                // Получаем 3,8. Столько раз в секунду будет переполняться таймер T0.         end else inc(count);          end else begin                       if count>=4 then begin             PORTB0_bit:=0;             count:=0;             i:=1;          end else inc(count);      end; end;  begin      TCCR0:=0x05; //Èëè ìîæíî òàê CS00_bit:=1; CS01_bit:=0; CS02_bit:=1;      TIMSK:=0x01;      SREG_I_bit:=1;            DDB0_bit:=1;            while true do begin      end; end.

В приложении к статье есть проекты в Proteus для каждого примера кода. Я не буду приводить скриншоты всех схем, так как в этом нет необходимости. Если вам интересно, что получилось, то можете посмотреть сам.

2. Прерывание по переполнению T1.

 program timerInt_1_ofv;  //Использование прерывания по переполнению счетчика 1-го таймера кардинально от такой же процедуры, //применительно к 0 таймеру, не отличается. Различия только в разрядности (16 против 8).  var count,i:integer;  procedure timer1_ofv(); iv IVT_ADDR_TIMER1_OVF; ics ICS_AUTO; begin      if i>=1 then begin         if count>=15 then begin                                                 //Так как счетчик таймера 16-ти разрядный, то предел счета - 65535. Значит мы должны            count:=0;                                                            //разделить частоту тактового генератора на коэф. предделения и еще на 65535.            i:=0;                                                                //В итоге получаем 15.            PORTB0_bit:=0;            PORTB1_bit:=1;         end else inc(count);      end else begin          if count>=15 then begin                count:=0;                i:=1;                PORTB0_bit:=1;                PORTB1_bit:=0;          end else inc(count);      end; end;  begin  TCCR1B:=0x01; TIMSK:=0x04; SREG_I_bit:=1;  DDB0_bit:=1; DDB1_bit:=1;    while true do begin    end; end.

 

3. Использование прерывания по совпадению (T1).

 program timer1_compare;  uses AADL;  var LCD_RS : sbit at PORTC4_bit;                    //Указываем куда подключен LCD var LCD_EN : sbit at PORTC5_bit; var LCD_D4 : sbit at PORTC0_bit; var LCD_D5 : sbit at PORTC1_bit; var LCD_D6 : sbit at PORTC2_bit; var LCD_D7 : sbit at PORTC3_bit;  var LCD_RS_Direction : sbit at DDC4_bit; var LCD_EN_Direction : sbit at DDC5_bit; var LCD_D4_Direction : sbit at DDC0_bit; var LCD_D5_Direction : sbit at DDC1_bit; var LCD_D6_Direction : sbit at DDC2_bit; var LCD_D7_Direction : sbit at DDC3_bit;  var  count, time:word;      i:integer;      tstr: string [23];  procedure timer1_comp_a(); iv IVT_ADDR_TIMER1_COMPA; ics ICS_AUTO; begin      TCCR1B:=0x00;      inc(time);      IntToStr(time,tstr);      TCCR1B:=0x04;      TCNT1L:=0x00;      TCNT1H:=0x00; end;  begin      TCCR1B:=0x04;            TIMSK:=0x10;            OCR1AH:=0x7a;      OCR1AL:=0x12;            SREG_I_bit:=1;       DDRC:=0xff;      DDRD:=0xff;      DDB0_bit:=0;      DDB1_bit:=0;      DDB2_bit:=0;      PORTB:=0x07;       lcd_init;                                                                       //Инициализация дисплея. lcd_cmd(_lcd_clear);                                                            //"Чистим" дисплей. lcd_cmd(_lcd_cursor_off);                                                       //Отключаем мигающуюю штуковину, //////////////////////////////////////////////////////////////////////////////////которую обозвали курсором.                     TCCR1B:=0x00;                                                //А это "лого" с адресом сайта                                                //и названием "прибора" :)                     lcd_out(1,16,'http://cxem.net');                     lcd_out(2,16,'Easy Timer');                     delay_ms(150);                                   for i:=0 to 14 do begin                       //Кому интереснogo                                    lcd_cmd(_lcd_shift_right);                    //наведите курсор на сслыку выше.                                   delay_ms(150);                                   end;                     lcd_cmd(_lcd_clear);                     delay_ms(2000);                     TCCR1B:=0x04; /////////////////////////////////////////////////////////////////////////////////            While true do begin            if Button(PINB,0,100,0) then TCCR1B:=0x04;            if Button(PINB,1,100,0) then TCCR1B:=0x00;            if Button(PINb,2,100,0) then begin               time:=0;               tstr:='0';               lcd_cmd(_lcd_clear);               lcd_out(1,1,tstr + ' sec');            end;            lcd_out(1,1,tstr + ' sec');            delay_ms(100);      end; end.

Вот тут уже схема необходима.

.

4. Прерывание по переполнению T0 и INT0. Частотомер.

 program timer0_ovf_int0;  uses AADL;  var LCD_RS : sbit at PORTC4_bit;                    //Указываем куда подключили LCD var LCD_EN : sbit at PORTC5_bit; var LCD_D4 : sbit at PORTC0_bit; var LCD_D5 : sbit at PORTC1_bit; var LCD_D6 : sbit at PORTC2_bit; var LCD_D7 : sbit at PORTC3_bit;  var LCD_RS_Direction : sbit at DDC4_bit; var LCD_EN_Direction : sbit at DDC5_bit; var LCD_D4_Direction : sbit at DDC0_bit; var LCD_D5_Direction : sbit at DDC1_bit; var LCD_D6_Direction : sbit at DDC2_bit; var LCD_D7_Direction : sbit at DDC3_bit;  var  freq, temp, count : word;      lcdout : string [23];      i:integer;  procedure timer0_ovf(); iv IVT_ADDR_TIMER0_OVF; ics ICS_AUTO; begin      if count>=3905 then begin                                                  //Проверяем, прошла 1 с , или нет.         TCCR0:=0x00;                                                            //Если прошла, отключаем прерывания.         GICR:=0x00;                                                             //И производим вычисления...                  Word2Str(freq, lcdout);                                                 //         freq:=0;                                                                //Обнуляем переменные.         i:=0;         count:=0;                  TCCR0:=0x02;                                                            //Включаем прерывания.         GICR:=0x64;         end else inc(count);                                                 end;                                                                                                                                                           procedure int0_(); iv IVT_ADDR_INT0; ics ICS_AUTO; begin      inc(freq);                                                                 //Увеличиваем с каждым импульсом значение переменной freq. end;                                                                              begin  TCCR0:=0x02;                                                                    //Конфигурируем регистры TIMSK:=0x01; GICR:=0x64;                                                                     //Разрешаем прерывания по INT0. SREG_I_bit:=1;                                                                                                                                                    DDRC:=0xFF;                                                                     // Для INT1 нужно записать GICR:=0x128; DDD2_bit:=0;  lcd_init;                                                                       //Инициализация дисплея lcd_cmd(_lcd_clear);                                                            // lcd_cmd(_lcd_cursor_off);                                                       // //////////////////////////////////////////////////////////////////////////////////                     TCCR0:=0x00;                                                                     GICR:=0x00;                                                 //Пасхалка :) Выводит адрес сайта Паяльник и название "прибора".                     lcd_out(1,16,'http://cxem.net');                     lcd_out(2,16,'Easy Freq');                     delay_ms(150);                                   for i:=0 to 14 do begin                                                        lcd_cmd(_lcd_shift_right);                                                     delay_ms(150);                                   end;                     delay_ms(2000);                     TCCR0:=0x02;                     GICR:=0x64; ///////////////////////////////////////////////////////////////////////////////// lcd_cmd(_lcd_clear);          While true do begin  //Основной цикл.                lcd_out(1,1,'Frequrecy');                                        //Выводим на дисплей, то над чем так мучались :)                lcd_out(2,1,lcdout+' Hz');                delay_ms(100);                lcd_cmd(_lcd_clear);          end; end.

И схема:

5. Последнее на сегодня. Прерывание по захвату. С помощью этого прерывания, доступного для T1, можно вычислить длительность импульсов, и как следствие частоту. К сожалению, у меня с помощью Proteus’а не получилось корректно воспроизвести картину… Возможно где-то в коде ошибка, но факт остается фактом — чем больше частота, тем громаднее погрешность. Итак, код:

 program timer1_capt;  var LCD_RS : sbit at PORTC4_bit;                                                //Указываем куда подключен LCD var LCD_EN : sbit at PORTC5_bit; var LCD_D4 : sbit at PORTC0_bit; var LCD_D5 : sbit at PORTC1_bit; var LCD_D6 : sbit at PORTC2_bit; var LCD_D7 : sbit at PORTC3_bit;  var LCD_RS_Direction : sbit at DDC4_bit; var LCD_EN_Direction : sbit at DDC5_bit; var LCD_D4_Direction : sbit at DDC0_bit; var LCD_D5_Direction : sbit at DDC1_bit; var LCD_D6_Direction : sbit at DDC2_bit; var LCD_D7_Direction : sbit at DDC3_bit;  var a,b,c,i:integer;     temp,temp_1,temp_2:integer;     freq:word;     lcdout:string [23];  const T = 0.000032;  procedure timer1_capt(); iv IVT_ADDR_TIMER1_CAPT; begin      asm cli end;                                                               //Ассемблерные вставки -                                                                                 //Полезная вещь. Но их      if c=0 then begin                                                          //использовать нужно крайне         b:=TCNT1L;                                                              //аккуратно, иначе код будет         a:=TCNT1H;                                                              //"не переносимым" на другие         temp_1:=a shl 8;                                                        //контроллеры.         temp_1:=temp_1 xor b;                                                   //Далее мы считываем данные         inc(c);                                                                 //из регистра TCNT1. И приводим      end else begin                                                             //их в читабельный вид.         b:=TCNT1L;                                                              //По следующему вх. импульсу         a:=TCNT1H;                                                              //Опять дергаем счетный регистр         temp_2:=a shl 8;                                                        //и вычисляем разницу.         temp_2:=temp_2 xor b;         temp:=temp_2-temp_1;         c:=0;                                                                   //Обнуляем регистр и переменные.         TCNT1H:=0;         TCNT1L:=0;      end;            asm sei end;    end;  begin TCCR1B.B2:=1;                                                                   //Тут мы ставим предделитель 256 TIMSK.B5:=1;                                                                    //Прерывание по захвату. SREG_I_bit:=1;                                                                  //Прерывания разрешены.  DDB0_bit:=0;                                                                    //Пин 0 порта B на вход.  lcd_init; lcd_cmd(_lcd_clear); lcd_cmd(_lcd_cursor_off);                                                       //Отключаем мигающуюю штуковину, //////////////////////////////////////////////////////////////////////////////////которую обозвали курсором.                     asm cli end;                                                //А это "лого" с адресом сайта и названием "прибора" :)                     lcd_out(1,16,'http://cxem.net');                     lcd_out(2,16,'Starter Freq');                     delay_ms(150);                                   for i:=0 to 14 do begin                       //Кому интереснo  ac:logo                                   lcd_cmd(_lcd_shift_right);                    //наведите курсор на сслыку выше.                                   delay_ms(150);                                   end;                     lcd_cmd(_lcd_clear);                     delay_ms(2000);                     asm sei end; ///////////////////////////////////////////////////////////////////////////////// While true do begin       freq:=abs(31250/temp);                                                    //Считаем частоту.       FloatToStr(freq,lcdout);                                                  //Копируем в строковую переменную       lcd_out(1,1,'Frequrency');                                                //Выводим на дисплей.       lcd_out(2,1,lcdout+' Hz');       delay_ms(300);       lcd_cmd(_lcd_clear); end;  end.

И схема:

Ну вот и все. Если где-то заметили ошибку, пишите в комментариях, я ведь тоже учусь вместе с вами. Удачи вам в ваших начинаниях!