Прошу помощи - прерывания по таймеру-2
- Войдите на сайт для отправки комментариев
Прошу помочь. Составил программу для обработки прерываний по таймеру и по ней есть вопросы:
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 немного изменил и дополнил его, м.б. добавил ошибок ?
PS:
c cnt >= 1млн в строке 37 перемудрил, правильно ( cnt >= 4 )
Алек-сей, не обижайтесь, но 1..3 -это сущий бред. Обьясните подробно что вы хотите. 4 -не правильно, значение может быть не более 0xFF. 5 - ни в какой. А должен быть в регистре TCCR2B , и уберите из кода все обнуления отдельных битов по типу: TCCR2B &= ~(1<<WGM22); и не используйте их без полного понимания того, что вы делаете.);<>
Хочу научиться работать с прерыванием таймера и для этого понять какие действия произвести. Полностью прочел даташит на Atmega 328, но не все смог логически увязать тк не все жаргоны смог перевести.
Сейчас имеются общие представления о шагах решения задачи:
1) рассчитать с использованием прескалера и частоты значение таймера, скорее всего придется взять максимальное FF и нужное время отмерять на каком-то количестве прерываний при помощи дополнительного счетчика cnt, а может быть увеличить прескалер . Какой конкретно установить прескалер ?
2) написать программу обработки прерывания - уже написал ISR_A()
3) запретив все прерывания, установить регистры таймера2 в нужные значения,
4) записать в таблицу прерываний для таймера 2 (по условию переполнения или сравнения с нулем ?) адрес программы ISR_A
Алек-сей , начинать изучение таймеров по даташиту -слишко жёстко. Почитайте русскоязычные материалы, Евстифеева например "Микроконтроллеры 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 в принципе не нужны, могут понадобится только в случае когда нельзя допустить бесконтрольного срабатывания прерываний при запуске программы.
dimax, cпасибо за ответ и пример, хочу задать несколько вопросов:
Если я правильно понял, для разных МК биты флагов могут стоять на разных местах. Обнаружил несоответствие номера бита 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() {}dimax, кстати, пример программы из Вашего поста #4 обработки прерываний по таймеру2 на МК 2560 не управляет LED на ножке 13. Причину не знаю, может быть несовместимость регистров Atmega328 и 2560 ?
LED и порт вывода проверены:
void loop() { digitalWrite(13, HIGH); delay(100); digitalWrite(13, LOW); delay(100); }PS: как в сообщении форума спрятать текст программы в спойлер, чтобы укоротить пост ?
Форма записи тернарной операции в C++
"условие" ? "выражение 1" : "выражение 2";Если условие истинно, то выполняется выражение 1, иначе (условие ложно) выполняется выражение 2.
min = a<b ? a : b;
переменной min присваивается значение меньшее из a и b
вопрос 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
Вот это полная документация на avr-libc.
Вот, например, про прерывания.
И да, все, конечно, не по русски ;).
В 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; }Алек-сей, Это хорошо, что вы так скурпулёзно всё разбираете, но плохо что при этом всё равно не понимаете :) В даташите крайне скупо сказано об особенностях этого регистра. Если вы внимательно читали, то знаете что установленная единица в нём обнуляется записью единицы. А вот записать единицу, если в регистре ноль -нельзя. В этом его особенности. Обнулять так же можно красивой командой 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 */Алек-сей, очень хороший вопрос. Вы ушли по ложному следу. Переменная cnt по сути такой же миллис, и проверять точность одного счётчика по другому в принципе бесполезное занятие, т.к. у них одинаковая точность. А дело тут в другом. Пока ваша программа начинает вычислять if (cnt>=500000) эта самая cnt не стоит, а считает дальше. Контроллер как мы знаем 8-битный, поэтому число сравнивается в 4 захода. Если у cnt текущее число например 0x6FFFF, а сравниваем мы его с 7A120 (в десятичном 500000) и сначала сравнились младшие байты. Младшие байты больше чем надо, -ок. Потом счётчик счёлкнул в 0x70000 (458752 в десятичном счёте) и тут сравнились старшие байты, -опять ок. Но для нашей операции сравнения число было не как по факту - 0x70000, а 0x7FFFF поэтому имеем ту ошибку, которую вы наблюдали. Выход простой -либо сравнивать в том-же прерывании, либо брать в работу число, скопированное в другую переменную при запрещённых прерываниях.
Как измерить текущее время в мксек в начале прерывания, если нельзя использовать micros()?
Каким методом обеспечить тестовую задержку входа в прерывание таймера2 ? Пока думаю организовать синхронное конкурирующее прерывание, срабатывающее чуть раньше имеющегося "подопытного" прерывания и периодически меняющее длительность своего исполнения так, чтобы отодвигать начало исполнения "подопытного". Для синхронности логично сделать оба прерывания на одном и том же таймере, но по разным условиям - это допустимо ?
хотел посмотреть всегда ли прерывания выполняются строго по таймеру (интервалы между прерываниями одинаковые) и могут ли быть задержки из-за работы других прерываний? могут ли быть прпопущенные прерывания. Вроде бы для прерываний при невозможности выполнить устанавливается флаг и оно будет исполнено позже ?
Если мыслить тактами МК, то любое прерывание выполняется позже. В нашем случае таймер выставляет флаг OCF2A, а дальше как получится. Пропустится прерывание может только если пришло время второго, а первое всё еще не выполнилось. Если не злоупотреблять командами noInterrupts и не делать слишком частыми сами прерывания - то такое в общем случае маловероятно.
Как измерить текущее время в мксек в начале прерывания, если нельзя использовать micros()?