Прошу помощи - прерывания по таймеру-2

Алек-сей
Offline
Зарегистрирован: 01.05.2017

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

1) Есть ли готовая функция типа  attachInterrupt() , которая запишет адрес программы обработчика ISR_A в таблицу прерываний?

2) Если готовой функции нет,  помогите синтаксически грамотно записать:  "в ячейку, адрес который содержит PTR_T2OVER записать адрес функции обработчика прерывания ISR_A" ?

3) Начальное значение указателю PTR_T2OVER присвоить не удалось, компилятор ничего не знает про "_VECTOR(9)" , а файла <iom328p.h> у меня нет. Что делать?

4) Правильно ли расчитал значение tcnt2=6250 для 40 прер/сек  при тактовой 16 Мгц?

5) Не могу найти в какой строке кода инициализирован прескалер 64 для формулы 16000000/64/f=tcnt2
   или такое значение при включении питания по умолчанию?

6) Нужно при манипуляциях с регистрами (прогр.  setup ) запрещать  все прерывания или достаточно запрета обработки только прерываний  таймера2  ?

 

#include <iom328p.h>
volatile unsigned int tcnt2;
volatile bool flag1=false ;
volatile unsigned long cnt=0;
uint32_t * PTR_T2OVER=TIMER2_OVF_vect;  
// нашел в описанияХ что якобы в файле <iom328p.h> есть определение:
// #define TIMER2_OVF_vect     _VECTOR(9)  /*   Timer/Counter2 Overflow*/
    
void setup()  
{   pinMode(7, OUTPUT);
    digitalWrite(7,LOW);

    noInterrupts(); // запретить все прерывания 
    TIMSK2 &= ~(1<<TOIE2); //запрещение прерывания по переполнению таймера/счетчика Т2  
    TCCR2A &= ~((1<<WGM21) | (1<<WGM20));// Режим работы таймера/счетчика 
    TCCR2B &= ~(1<<WGM22);// Режим работы таймера/счетчика 
    ASSR &= ~(1<<AS2);  //Выбор источника синхронизации таймера если AS2=0 от системного генератора 
    tcnt2 = 6250; //  16000000/64/f=tcnt2 
    // значение 6250 рассчитано для 40 прерываний в секунду для системного генератора 16 Мгц 
    //  --------------------------------------------------    в какой строке кода установлен прескалер=64 ???????????????
    TIMSK2 |= (1<<TOIE2);//Разрешение прерывания по переполнению Т2. 
   
    //  как по адресу, на который указывает PTR_T2OVER записать адрес функции ISR_A ?    
    interrupts(); // раззрешить все прерывания
} 
/////////////////////////////////////////////////////////////////////////////////////////////////
void loop()  
{ 
  if(flag1) digitalWrite(7, HIGH); else digitalWrite(7, LOW); 
}  

/////////////////////////////////////////////////////////////////////////////////////////////////   
void ISR_A(void)  
{ 
    noInterrupts(); // запретить все прерывания
    cnt++;
    if (cnt >= 10000000) {cnt=0; flag1=!flag1;} else;
    TCNT2 = tcnt2; 
    interrupts(); // разрешить все прерывания
    
} 

 начальный код для примера взял здесь http://arduino.ru/forum/programmirovanie/preryvaniya-po-taimeru  немного изменил и дополнил его, м.б. добавил ошибок ?

Алек-сей
Offline
Зарегистрирован: 01.05.2017

PS:

c  cnt >= 1млн в строке 37  перемудрил, правильно ( cnt >= 4 )

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Алек-сей,  не обижайтесь, но 1..3 -это сущий бред. Обьясните подробно что вы хотите.  4 -не правильно, значение может быть не более 0xFF.  5 - ни в какой. А должен быть в регистре TCCR2B , и уберите из кода все обнуления отдельных битов по типу: TCCR2B &= ~(1<<WGM22); и не используйте их без полного понимания того, что вы делаете.);<>

Алек-сей
Offline
Зарегистрирован: 01.05.2017

dimax пишет:
Алек-сей,  не обижайтесь, но 1..3 -это сущий бред. Обьясните подробно что вы хотите.  4 -не правильно, значение может быть не более 0xFF.  5 - ни в какой. А должен быть в регистре TCCR2B , и уберите из кода все обнуления отдельных битов по типу: TCCR2B &= ~(1<<WGM22); и не используйте их без полного понимания того, что вы делаете.

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

Сейчас имеются общие представления о шагах решения задачи:

1) рассчитать с использованием прескалера и частоты значение таймера, скорее всего придется взять максимальное FF и нужное время отмерять на каком-то количестве прерываний при помощи дополнительного счетчика cnt, а может быть увеличить прескалер .  Какой конкретно установить прескалер ?

2) написать программу обработки прерывания - уже написал ISR_A()

3) запретив все прерывания, установить регистры таймера2 в нужные значения,  

4) записать в таблицу прерываний для таймера 2 (по условию переполнения или сравнения с нулем ?) адрес программы ISR_A

 

 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Алек-сей , начинать изучение таймеров по даташиту -слишко жёстко. Почитайте русскоязычные материалы, Евстифеева например "Микроконтроллеры AVR семейства Mega". Там тоже не разжевывают как первоклашкам, но хотя бы это нормальный перевод  на русский. В принципе когда вы прочтёте вы сами ответите на свои вопросы. Но подскажу (1) что бы настроить таймер2 на прерывания n раз в секунду нужно тактовую частоту разделить на n и на нужный прескалер. К примеру нужно нам 100 раз в секунду. Делим  16000000 / 100 =160000 -> Много, нужно использовать прескалер.  Бёрём 1024. 160000/1024=156. Отлично, попали в допустимый диапазон. Только у регистров счёт с ноля, стало быть рассчётное значение будет 155.

(2) у прерываний таймеров есть свои номера, жёстко определённые в даташите. Есть дефайны на эти номера в компиляторе для более наглядного восприятия. И выдумывать другие названия нет особого смысла. У второго таймера есть три прерывания. По переполению счётного регистра и по совпадению с регистрами сравнения а и б.

(3) запрещать прерывания без острой на то необходимости -нет смысла. Для корректного конфигурирования таймера удобнее его стачала остановить, настроить все регистры, а последней командой запустить. Скетч для примера.

 

void setup() {
pinMode(13,OUTPUT);// светодиод  на 13 ноге
TCCR2B=0; // таймер2 остановлен
TCNT2=0; //счётный регистр обнулён
OCR2B=0;
TIFR2&=0xff; //отчистить флаги прерываний
TCCR2A=(1<<WGM21); //Mode2(CTC)
OCR2A=155; // считать до 155
TIMSK2=(1<<OCIE2A); //прерывание по совпадению с OCR2A
TCCR2B=(1<<CS22)|(1<<CS21)|(1<<CS20); //запуск с таймера с делителем на 1024
}

ISR (_VECTOR(7)){  // или можно взять дефайн ISR (TIMER2_COMPA_vect)
//допустим нужно помигать неспешно светодиодом. Будем каждые
//128 входов в прерывание переключать пин 13.
static byte n; 
( n++& (1<<7)) ? PORTB|=1<<5 : PORTB&=~(1<<5);
}

void loop() {}

Строки 3-6 в принципе не нужны, могут понадобится только в случае когда нельзя допустить бесконтрольного срабатывания прерываний при запуске программы.

Алек-сей
Offline
Зарегистрирован: 01.05.2017

dimax, cпасибо за ответ и пример, хочу задать несколько вопросов:

dimax пишет:
(2) у прерываний таймеров есть свои номера, жёстко определённые в даташите. Есть дефайны на эти номера в компиляторе для более наглядного восприятия.

Если я правильно понял, для разных МК  биты флагов могут стоять на разных местах.  Обнаружил несоответствие  номера бита WGM21 в рекомендованной книге  (WGM21 указан как бит  3 для MCU 32Х ) тому, что реально выдает программа исполненная на Atmega328P . Еще исполнил программу (те посмотрел значение WGM21 ) на Atmega2560, он тоже =1, но 2560 не описан в этой книге.

вопрос 1: В каком файле описаны #define для  WGM21 , _VECTOR7(), PORTB   и других?

вопрос 2:  при побитовом умножении на маску 0xFF я ожидал неизменного результата в TIFR2  послде присваивания  TIFR2=TIFR2&0xFF  ,   но результат другой. Почему?

вопрос 3: помогите понять запись, что за оператор "?" это сокращенная форма "if" и где прочесть про нее ?

  n++; n=n& (1<<7); PORTB=PORTB | (1<<5);  if (PORTB) PORTB=PORTB &  ~ (1<<5); 

n++& (1<<7)) ? PORTB|=1<<5 : PORTB&=~(1<<5);

вопрос 4: где прочитать про оператор ISR и что это - если #define, то в каком файле искать его?

вопрос 5: возможно он отпадет, когда получу ответ на в.4 - в  какой момент в программе происходит запись нового ветора обработчика программы прерывания _VECTOR(7)?

 

    #include <serial.h>
    #define NL; Serial.print("\n");   
    void setup() {
    
    pinMode(13,OUTPUT);// светодиод  на 13 ноге
    Serial.begin(57600);
    
    TCCR2B=0; // таймер2 остановлен
    TCNT2=0; //счётный регистр обнулён
    OCR2B=0;
          
    Serial.print("        MCU : ");      NL;
    Serial.print("        TIFR2 before TIFR2&=0xff;=");  Serial.print(TIFR2);  NL;
    TIFR2&=0xff; //отчистить флаги прерываний
    Serial.print("        TIFR2  after TIFR2&=0xff;=");  Serial.print(TIFR2);  NL;
    
    TCCR2A=(1<<WGM21); //Mode2(CTC)
    OCR2A=6; // считать до 155
    TIMSK2=(1<<OCIE2A); //прерывание по совпадению с OCR2A
    TCCR2B=(1<<CS22)|(1<<CS21)|(1<<CS20); //запуск с таймера с делителем на 1024
    
   
   Serial.print("        TCCR2B=");  Serial.print(TCCR2B);  NL;
   Serial.print("        TCCR2A=");  Serial.print(TCCR2A);  NL;
   Serial.print("        CS20=");    Serial.print(CS20);    NL;
   Serial.print("        CS21=");    Serial.print(CS21);    NL;
   Serial.print("        CS22=");    Serial.print(CS22);    NL;
   Serial.print("        WGM21=");   Serial.print(WGM21);   NL;
   Serial.print("        OCIE2A=");  Serial.print(OCIE2A);  NL;
   //Serial.print("        TIMER2_COMPA_vect=");  Serial.print(TIMER2_COMPA_vect);  NL;
   //Serial.print("        _VECTOR(7)=");  Serial.print(_VECTOR(7));  NL;
  }
    
    ISR (TIMER2_COMPA_vect){
    // ISR (_VECTOR(7)){  // или можно взять дефайн ISR (TIMER2_COMPA_vect)
    //допустим нужно помигать неспешно светодиодом. Будем каждые
    //128 входов в прерывание переключать пин 13.
    static byte n; 
      (n++& (1<<7)) ? PORTB|=1<<5 : PORTB&=~(1<<5);
    }
 
   
    void loop() {}
    

 

Алек-сей
Offline
Зарегистрирован: 01.05.2017

dimax, кстати,  пример программы из Вашего поста #4  обработки прерываний по таймеру2 на МК 2560  не управляет LED  на ножке 13.  Причину не знаю, может быть несовместимость регистров Atmega328 и  2560 ?

LED и порт вывода проверены:

    void loop() {
     digitalWrite(13, HIGH);   delay(100);
     digitalWrite(13, LOW);    delay(100);
  }

PS: как в сообщении форума спрятать текст программы в спойлер, чтобы укоротить пост ?

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Форма записи тернарной операции в C++

"условие" ? "выражение 1" : "выражение 2";

Если условие истинно, то выполняется выражение 1, иначе (условие ложно) выполняется выражение 2.

min = a<b ? a : b;

переменной min присваивается значение меньшее из a и b 

 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Алек-сей пишет:

вопрос 1: В каком файле описаны #define для  WGM21 , _VECTOR7(), PORTB   и других?

В девайнишенс -фалах, которые  ставятся в комплекте  с компилятором. Например iom328p.h для меги328

Алек-сей пишет:

вопрос 2:  при побитовом умножении на маску 0xFF я ожидал неизменного результата в TIFR2  послде присваивания  TIFR2=TIFR2&0xFF  ,   но результат другой. Почему?

Не все регистры одинаковы в процедурах чтения/записи в них. Почитайте повнимательнее про регистр TIFR в даташите, он один из таких "особых".

Алек-сей пишет:

вопрос 4: где прочитать про оператор ISR и что это - если #define, то в каком файле искать его?

На вопрос 3 уже ответили. ISR -это макрос компилятора avr-gcc , описан в interrupt.h  Не знаю где почитать. Гугль?  Я сам так делеко не заходил, что б разбирать макросы компилятора )

Алек-сей пишет:

dimax, кстати,  пример программы из Вашего поста #4  обработки прерываний по таймеру2 на МК 2560  не управляет LED  на ножке 13.  Причину не знаю, может быть несовместимость регистров Atmega328 и  2560 ?

Естессно, в ардуино меге он сидит на PB7

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Вот это полная документация на avr-libc.

Вот, например, про прерывания.

И да, все, конечно, не по русски ;).

Алек-сей
Offline
Зарегистрирован: 01.05.2017

dimax пишет:
Алек-сей пишет:
вопрос 2:  при побитовом умножении на маску 0xFF я ожидал неизменного результата в TIFR2  после присваивания  TIFR2=TIFR2&0xFF  ,   но результат другой. Почему?
Не все регистры одинаковы в процедурах чтения/записи в них. Почитайте повнимательнее про регистр TIFR в даташите, он один из таких "особых".

В TIFR2 некоторые биты защищены от записи , возможно только чтение. Побитное умножение  8-битного регистра на 0xFF не должно было изменить его значение (побитное умножение любого 8 битного  числа Z на  0xFF  дает результатом  Z), но  если посмотреть печать значений регистра по ходу программы, то его поведение непонятно.

Посмотрите, я добавил в программу присвоение значений  TIFR2, но его содержимое не меняется .

 

 #include <serial.h>
    #define NL; Serial.print("\n");   
    

    void setup() {
    
    pinMode(13,OUTPUT);// светодиод  на 13 ноге
    Serial.begin(57600);
    
    TCCR2B=0; // таймер2 остановлен
    TCNT2=0; //счётный регистр обнулён
    OCR2B=0;
          
    
    Serial.print("        TIFR2 before TIFR2&=0xff;=");  Serial.print(TIFR2);  NL;
    Serial.print(" 2-nd   TIFR2 before TIFR2&=0xff;=");  Serial.print(TIFR2);  NL;
    
    TIFR2&=0xff; //отчистить флаги прерываний
    Serial.print("        TIFR2  after TIFR2&=0xff;=");  Serial.print(TIFR2);  NL;
    TIFR2=7;
    Serial.print("        TIFR2  after TIFR2=7;=");  Serial.print(TIFR2);  NL;
    TIFR2=3;
    Serial.print("        TIFR2  after TIFR2=3;=");  Serial.print(TIFR2);  NL;
    TIFR2=2;
    Serial.print("        TIFR2  after TIFR2=2;=");  Serial.print(TIFR2);  NL;
    TIFR2=1;
    Serial.print("        TIFR2  after TIFR2=1;=");  Serial.print(TIFR2);  NL;
}

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Алек-сей, Это хорошо, что вы так скурпулёзно всё разбираете, но плохо что при этом всё равно не понимаете :) В даташите крайне скупо сказано об особенностях этого регистра. Если вы внимательно читали, то знаете что установленная единица в нём обнуляется записью единицы. А вот записать единицу, если в регистре ноль -нельзя. В этом его особенности. Обнулять так же можно красивой командой TIFR2=TIFR2.  :)

Алек-сей
Offline
Зарегистрирован: 01.05.2017

dimax пишет:
Алек-сей, Это хорошо, что вы так скурпулёзно всё разбираете, но плохо что при этом всё равно не понимаете :) В даташите крайне скупо сказано об особенностях этого регистра. Если вы внимательно читали, то знаете что установленная единица в нём обнуляется записью единицы. А вот записать единицу, если в регистре ноль -нельзя. В этом его особенности. Обнулять так же можно красивой командой TIFR2=TIFR2.  :)

dimax, cпасибо , Вы правы - регистр оказался "хитрым". Что ни запишешь в биты разрешенные для чтения, приводит к их обнулению. Вы правы - я  не смог понять смысл из английского описания этого регистра. В даташите написано о трегистре слишком кратко.

Решил протестировать стабильность частоты, исполнения прерываний,  взял любую с целым количеством отсчетов таймера (50 кГц) . В теле прерывания  счетчик циклов, а в основной программе через каждые 500 000 циклов, те 10 сек  измерение времени.

Почему прерывания исполняются чаще,  чем было назначено? Ладно бы какая-то программа занимала время своими прерываниями и в ISR были бы пропуски, но тогда время на фиксированное количество циклов увеличилось бы, но как понять 9175 мсек при ожидаемых  10000   с периодичностью повтора прибл.  320 сек ?   (см лог в нижних строках программы, строки 288, 124, 92, 65 и др.).

    volatile unsigned long cnt=0;
    static uint16_t t1, t2;
    
    void setup() {
    Serial.begin(115200);
    TCCR2B=0; // таймер2 остановлен
    TCNT2=0; //счётный регистр обнулён
    OCR2B=0;
    TIFR2&=0xff; //отчистить флаги прерываний
    TCCR2A=(1<<WGM21); //Mode2(CTC)
    OCR2A=39; // 
    TIMSK2=(1<<OCIE2A); //прерывание по совпадению с OCR2A
    TCCR2B=1<<CS21; //запуск с таймера с делителем на 8
    t1=millis();
  }
             
    
    ISR (TIMER2_COMPA_vect){  cnt++;}
    void loop()   {  if(cnt >= 500000) {t2=millis(); Serial.println(t2-t1);  t1=t2; cnt=0;} }
  
/*
9999
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
9175        (170 сек)
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
9176     (+280 сек)
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
9999
10001
10000
10000
10001
9176    (+270 сек)
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
9175   ( +320 сек)
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
9175  (+320 сек)
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
9175    (+320 сек)
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
9176   (+270)
9999
10000
10001
10000
10001
10000
10000
10001
9176 (+90 сек)
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
9175 (+ 320 сек)
10000
10000
10001
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
10001
10000
10000
10001
10000
9999
10001
10000
10001
10000
10000
10001
10000
10001
9175   (+320 сек)
10000
10000
10001
10000
10001
10000
10000
10001
10000
10000
10001
10000
*/  

 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Алек-сей, очень хороший вопрос. Вы ушли по ложному следу. Переменная cnt по сути такой же миллис, и проверять точность одного счётчика по другому в принципе бесполезное занятие, т.к. у них одинаковая точность. А дело тут в другом.  Пока ваша программа начинает вычислять if (cnt>=500000) эта самая cnt не стоит, а считает дальше. Контроллер как мы знаем 8-битный, поэтому число сравнивается в 4 захода. Если у cnt текущее число например 0x6FFFF,  а сравниваем мы его с 7A120 (в десятичном 500000)  и сначала сравнились младшие байты. Младшие байты больше чем надо, -ок. Потом счётчик счёлкнул в 0x70000 (458752 в десятичном счёте) и тут сравнились старшие байты, -опять ок.  Но для нашей операции сравнения число было не как по факту - 0x70000,  а  0x7FFFF поэтому имеем ту ошибку, которую вы наблюдали. Выход простой -либо сравнивать в том-же прерывании, либо брать в работу число, скопированное в другую переменную при запрещённых прерываниях.

Алек-сей
Offline
Зарегистрирован: 01.05.2017

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

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

Как измерить текущее время в мксек  в начале прерывания, если нельзя использовать micros()?

Каким методом обеспечить тестовую задержку входа в  прерывание таймера2 ?  Пока думаю организовать синхронное  конкурирующее  прерывание, срабатывающее чуть раньше имеющегося "подопытного" прерывания   и периодически меняющее длительность своего исполнения так, чтобы отодвигать начало исполнения "подопытного".  Для синхронности логично сделать оба прерывания на одном и том же таймере, но по разным условиям - это допустимо ? 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Алек-сей пишет:

хотел посмотреть всегда ли прерывания выполняются строго по таймеру (интервалы между прерываниями одинаковые) и могут ли быть задержки из-за работы других прерываний?  могут ли быть прпопущенные прерывания. Вроде бы для прерываний при невозможности выполнить устанавливается флаг и оно будет исполнено позже ?

Если мыслить тактами МК, то любое прерывание выполняется позже. В нашем случае таймер выставляет флаг OCF2A, а дальше как получится. Пропустится прерывание может только если пришло время второго, а первое всё еще не выполнилось. Если не злоупотреблять командами noInterrupts  и не делать слишком частыми сами прерывания - то такое в общем случае маловероятно.

Алек-сей пишет:

Как измерить текущее время в мксек  в начале прерывания, если нельзя использовать micros()?

А почему нельзя? можно. Про "тестовые задержки" -вообще не понял что вы хотите, и нафига это.