Прерывания по таймеру 1

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Добрый день, корифеи

Есть задача по каналу А таймера 1 менять переменную timerAс частотой 1 Гц, по каналу B менять переменную timerB с частотой ~500 Гц. Несмотря на значения OCR1A = 15624 и OCR1B = 30, прерывания по обоим каналам идут с частотой 1 Гц. Прошу подсказать в какую сторону копать:

volatile unsigned int timerA;
volatile unsigned int timerB;

ISR (TIMER1_COMPA_vect)
{
  timerA++;
}

ISR (TIMER1_COMPB_vect)
{
  timerB++;
}

void setup() {
  cli();
  TCCR1B = 0;
  TCCR1A = 0;
  TCCR1B |= (1 << WGM12);                 // Режим CTC (сброс по совпадению)
  TCCR1B |= (1 << CS10) | (1 << CS12);    // CLK/1024
  OCR1A = 15624;                          // Частота прерываний A будет = 16.000.000/(1024*(1+15624)) = 1 гЦ
  OCR1B = 30;                             // Частота прерываний B будет = 16.000.000/(1024*(1+30)) = 504 гЦ 
  TIMSK1 |= (1 << OCIE1A);                // Разрешить прерывание по совпадению A
  TIMSK1 |= (1 << OCIE1B);                // Разрешить прерывание по совпадению B
  sei();

  Serial.begin(9600);
  Serial.println("Starting...");
}

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

По одному таймеру прерывания ВСЕГДА будут идти с одинаковой частотой по всем каналам.

С этим необходимо смириться и проектировать алгоритмы, исходя из этого очень неприятного обстоятельства.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

andriano пишет:
По одному таймеру прерывания ВСЕГДА будут идти с одинаковой частотой по всем каналам.С этим необходимо смириться и проектировать алгоритмы, исходя из этого очень неприятного обстоятельства.

Внезапно :( а в чем тогда практический смысл двух каналов (в раздельной настройке OCR1A и OCR1B)?

 

sadman41
Offline
Зарегистрирован: 19.10.2016

У таймера на оба канала один счётчик, который по CTC сбрасывается в 0 при достижении заданного TOP. Он физически не может досчитать до двух лимитов.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

То есть можно использовать один канал по совпадению, а второй по переполнению?

sadman41
Offline
Зарегистрирован: 19.10.2016

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

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

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

Как вариант, либо использовать под 500 гЦ таймер 2 (tone и pwm в проекте не задействованы). Насколько оправдано (правильно) использование двух таймеров? В предложенном Вами варианте не хочется unsigned int ворочать...

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Зачем его ворочать? byte тоже умеет считать до двух. И boolean имеет два значения. Перекидывайте его логической инверсией и прибавляйте к основному счётчику.

Сорри, что-то я подумал, что там килогерц и 500 герц.
На 500гц можно и ансигнед инт ворочать, МК не вспотеет.

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

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Сделал на двух таймерах, выложу, может пригодится кому.

volatile bool timer1;
volatile bool timer2;

ISR (TIMER1_COMPA_vect)
{
  timer1++;
}

ISR (TIMER2_COMPA_vect)
{
  timer2++;
}

void setup() {
  cli();

  // Timer 1 - long
  TCCR1B = 0;
  TCCR1A = 0;
  TCCR1B |= (1 << WGM12);                 // Режим CTC (сброс по совпадению)
  TCCR1B |= (1 << CS10) | (1 << CS12);    // CLK/1024
  OCR1A = 15624;                          // Частота прерываний A будет = 16.000.000/(1024*(1+15624)) = 1 гЦ
  TIMSK1 |= (1 << OCIE1A);                // Разрешить прерывание по совпадению A

  // Timer 2 - short
  TCCR2A = 0;
  TCCR2B = 0;
  TCCR2A |= (1 << WGM21);                 // Режим CTC (сброс по совпадению)
  TCCR2B = (1 << CS21) | (1 << CS22);     // CLK/256
  OCR2A = 124;                            // Частота прерываний B будет = 16.000.000/(256*(1+124)) = 500 гЦ
  TIMSK2 |= (1 << OCIE2A);                // Разрешить прерывание по совпадению B
  
sei();
}

 

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

sadman41 пишет:
На 500гц можно и ансигнед инт ворочать, МК не вспотеет. Можно, конечно, и два таймера использовать для инкремента переменных. Прикуривают же люди от долларовых банкнот.

Вот и интересуюсь как более кошерно и почему? Мне казалось что ансигнед инт тяжелее ворочать чем bool перевернуть...

 

sadman41
Offline
Зарегистрирован: 19.10.2016

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

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Ок, принял к сведению. Спасибо за консультацию. Но все равно хотелось бы понять логику - зачем два канала у таймера?

sadman41
Offline
Зарегистрирован: 19.10.2016

Почему два - надо спрашивать у разработчиков чипа. А используются они для PWM, к примеру.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Мммм, логично. про PWM вообще не подумал. Теперь все встало на свои места!

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Dinosaur пишет:

andriano пишет:
По одному таймеру прерывания ВСЕГДА будут идти с одинаковой частотой по всем каналам.С этим необходимо смириться и проектировать алгоритмы, исходя из этого очень неприятного обстоятельства.

Внезапно :( а в чем тогда практический смысл двух каналов (в раздельной настройке OCR1A и OCR1B)?

 

Практический смысл в умении применять существующее железо к конкретной задаче.

Например, нужно сформировать полный телевизионный сигнал, тогда, если время начал строчного синхроимпульса принять на 0, то нужно обеспечить время окончания синхроимпульса, время окончания импульса гашения и время начала импульса гашения. И все это на одной и той же кадровой частоте. Вполне естественно для этой задачи использовать 4 канала одного таймера.

Мне вот интересно, если бы каналы можно было программировать независимо на разные частоты, какой бы смысл было делать несколько таймеров вместо одного со множеством каналов?

Logik
Offline
Зарегистрирован: 05.08.2014

Другой пример, на том же таймере 1 делаю управление шаговиком на А4988. Один канал, понятно сам степ. А второй канал - енейбл, чтоб в паузах между шагами драйвер и движок меньше грелись. А период таймера (а это собственно скорость вращения) через ICR1 задаю.

sadman41
Offline
Зарегистрирован: 19.10.2016

А на каком микрошаге степпер ходит? Дрожание/биение не заметно? А то ведь вал при снятии тока может и немного назад откинуть.

Logik
Offline
Зарегистрирован: 05.08.2014

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Без микрошага всё должно быть ОК.

При микрошаге ротор висит где-то между двумя зубцами статора (точная позиция зависит от соотношения тока на обмотках и ещё разных факторов). И как только удержание будет снято, ротор притянет к ближайшему зубцу - а он может быть "впереди", так и "позади". Подача энейбла не может гарантировать возврат статора в ту же позицию. Точно так же ротор скакнёт непредсказуемо (для человека) или вперёд или назад. Хотя я и интуитивно чувствую, что должен взад вернуть, если соотношение токов в обмотках восстановится. Однако это всё же вызовет микробиения.

Во всяком случае мои эксперименты с 7,5-градусным движком на A4988 с микрошагом 1/16 порождали разные эффекты...  Вплоть до неравного микрошага - три мелких, один крупный, например. Хочу вот на TMC-шных ещё потестить...

На 1,8-градусном, наверное, это не так заметно будет. Повторять весь экспериментальный цикл что-то лень, поэтому и интересуюсь - как оно, чувствуется или нет?

Logik
Offline
Зарегистрирован: 05.08.2014

Да. Юзаю только без микрошага и ОК. С изложенным согласен, на микрошаге снимать енейбл - проблемный вариант. Разве что на высокой скорости и инерционной нагрузке, может и не успеет отскочить. Тут пробовать и/или рассчитывать нужно.

 

Beijo2908
Beijo2908 аватар
Offline
Зарегистрирован: 07.02.2019

Хмм... Можно в принципе обойтись одним таймером используя

switch(var){
 case 1:
 case **:
}

То есть

unsigned long i;
unsigned long u;

ISR (TIMER1_COMPA_vect){
  i++;
  switch(i){
    case 1000: timer1++; i=0; break; // Частота в 1Гц
    }
  u++;
  switch(u){
    case 2: timer2++; u=0; break; // Частота в 500Гц
    }
}
int main(){
i=0;
u=0;
//...
// строки настройки прерывания CLK/64
//...
OCR1A |= 0b11111010; //250 в двоичной системе. 16 000 000/64=250000/1000=250 // 250Гц в 1 мсек.
  while(1){
  }
}

Написанный код выше актуален не только для 16-битного таймера, но и 8-битного. Сам недавно начал изучать работу с регистрами AVR... Если есть ошибка в коде или недочет - пишите, буду исправляться))

sadman41
Offline
Зарегистрирован: 19.10.2016

Недочёты есть. 

i я бы сделал static внутри обработчика, u вообще бы выкинул, а биты для OCR1A задавал через мнемонические макросы.

Beijo2908
Beijo2908 аватар
Offline
Зарегистрирован: 07.02.2019

До static ещё не дошел в изучении. А биты это уже личное))) Мне в двоичной или в шестнадцатеричной системе использовать удобнее. Ну ещё юзаю в виде (1<<5), если не очень хочется затрагивать другие биты в порте.

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

Beijo2908,

OCR1A |= 0b11111010; //250 в двоичной системе. 16 000 000/64=250000/1000=250 // 250Гц в 1 мсек.

А что сразу не написать 250, зачем этот макрос?  И кстати использование "|="   без острой надобности в "OR"  когда-нибудь до беды вас доведёт. Однажды в регистре будет что-то лежать, и вы к нему не глядя добавите новое значение. 

Logik
Offline
Зарегистрирован: 05.08.2014

То не макрос а двоичная константа. И действительно,  просто присвоить OCR1A =250 выглядит разумней. Прерывания получаются 1КГц. Если уж так хочется иметь 500Гц и 1Гц, то зачем нам 1КГц. Сразу просто настраиваем таймер на 500Гц, это просто OCR1A =500,  а 1Гц получаем пересчетом. 

Beijo2908
Beijo2908 аватар
Offline
Зарегистрирован: 07.02.2019

Okay, буду писать числа в десятичной системе. Про |= и например &= в курсе, но тоже возьму на заметку.

Beijo2908
Beijo2908 аватар
Offline
Зарегистрирован: 07.02.2019

Logik пишет:
Сразу просто настраиваем таймер на 500Гц, это просто OCR1A =500

А если таймер 8-bit? 500 не влезет же.
Далее, к примеру, если МК 8-bit, таймер 16-bit. Получается 16-bit таймер состоит из двух 8-bit регистров OCR1AH и OCR1AL - старшая и младшая часть. Это лишние ограничения + "пара" строчек, имхо.

Logik
Offline
Зарегистрирован: 05.08.2014

Если у восьмибитного таймера есть прескаллер - меняем его и пересчитываем значение. Например прескаллер выбрать 256, а значение выйдет 125. Про 16-битность регистров таймера - не переживайте излишне, там двойная буферизация. Простое присвоение отрабатывает корректно. А вот арифметические и логические действия напрямую на регистрах писать не стоит.