Странное поведение таймеров

[quote=“AlexTash, post:1, topic:17761, full:true”]
Добрый день всем. есть задача запускать DC мотор в разные стороны и останавливать по возможности в точных положениях, для этого пытаюсь использовать таймеры в режиме FastPWM. по каждому срабатыванию прерывания TIMER5_COMPA(B,C)_vect и TIMER5_OVF_vect происходит понижение подаваемого ШИМ на двигатель. контроллер ATMEGA2560.
Но вот какая непонятная ситуация, на первом вращении вообще не происходит прерываний TIMER5_COMPA(B,C)_vect , на втором всё начинает работать как нужно, но с параметрами OCR5A(B,C) из первого вращения, в третьем вращении используются параметры из второго, и так далее. уже всё препробовал играл с разрешениями глобальных прерываний, менял момент определения OCR5A(B,C), полностью при вызове функции преписывал настройки региистров. Ничего не помогает, действие параметров OCR5A(B,C) смещается ровно на цикл. Код такой


#define motorPin1 42
#define motorPin2 40
#define motorEn 5
#define encoder 47  //вход Т5 для внешнего тактирования от энкодера

unsigned int cnt = 1;
int Speed1 = 70;
int Speed2 = 50;
int Speed3 = 20;
int deceleration1 = 10;
int deceleration2 = 6;
int deceleration3 = 3;
bool moving = false;
bool direction = true;

void MoveMotor(bool dir, int lenght) {
  cli();
  TCCR5B = 0;
  OCR5A = lenght - deceleration1;
  OCR5B = lenght - deceleration2;
  OCR5C = lenght - deceleration3;
  ICR5 = lenght;
  TCCR5B = 0b11111;
  sei();
  Serial.print("OCR5A =");
  Serial.println(OCR5A);
  Serial.print("OCR5B =");
  Serial.println(OCR5B);
  Serial.print("OCR5C =");
  Serial.println(OCR5C);
  Serial.print("ICR5 =");
  Serial.println(ICR5);
  Serial.print("TIMSK5 = ");
  Serial.println(TIMSK5, BIN);
  Serial.print("TCCR5A = ");
  Serial.println(TCCR5A, BIN);
  Serial.print("TCCR5B = ");
  Serial.println(TCCR5B, BIN);
  Serial.print("TIFR5 = ");
  Serial.println(TIFR5, BIN);
  Serial.print("TCNT5 = ");
  Serial.println(TCNT5);

  digitalWrite(motorEn, 1);
  if (dir) {
    digitalWrite(motorPin1, HIGH);
  } else {
    digitalWrite(motorPin2, HIGH);
  }

  moving = true;
}
//******************************************************************************
//Стартовые установки
//******************************************************************************

void setup() {
  TCCR5A = 0;
  TCCR5B = 0;
  TIMSK5 = 0;
  TIMSK5 |= (1 << OCIE5C) | (1 << OCIE5B) | (1 << OCIE5A) | (1 << TOIE5);  // включить прерывание по совпадению таймера
  TCCR5A |= (0 << COM5A1) | (1 << COM5A0) | (0 << COM5B1) | (1 << COM5B0) | (0 << COM5C1) | (1 << COM5C0) | (1 << WGM51) | (0 << WGM50);
  TCCR5B |= (1 << WGM53) | (1 << WGM52) | (1 << CS52) | (1 << CS51) | (1 << CS50);  // | (0 << 5);
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorEn, OUTPUT);
  pinMode(encoder, INPUT);
  digitalWrite(motorPin1, 0);
  digitalWrite(motorPin2, 0);
  digitalWrite(motorEn, 0);
  Serial.begin(115200);

  sei();  // включить глобальные прерывания

  MoveMotor(direction, 15);
}

void loop() {
  if (TCNT5 != cnt) {
    Serial.println(TCNT5);
    cnt = TCNT5;
  }
  if (!moving) {
    MoveMotor(direction, 20);
    direction = !direction;
  }
}



ISR(TIMER5_COMPA_vect) {
  Serial.println("timerA");
  analogWrite(motorEn, 255 * Speed1 / 100);
}

ISR(TIMER5_COMPB_vect) {
  analogWrite(motorEn, 255 * Speed2 / 100);
  Serial.println("timerB");
}
ISR(TIMER5_COMPC_vect) {
  analogWrite(motorEn, 255 * Speed3 / 100);
  Serial.println("timerC");
}
ISR(TIMER5_OVF_vect) {
  Serial.println("timerOVF");
  digitalWrite(motorPin1, 0);
  digitalWrite(motorPin2, 0);
  digitalWrite(motorEn, 0);
  Serial.print("TCNT5 = ");
  Serial.println(TCNT5);
  moving = false;
}

лог из терминала
‘’’

OCR5A =5
OCR5B =9
OCR5C =12
ICR5 =15
TIMSK5 = 1111
TCCR5A = 1010110
TCCR5B = 11111
TIFR5 = 0
TCNT5 = 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
timerOVF
TCNT5 = 0
0
OCR5A =10
OCR5B =14
OCR5C =17
ICR5 =20
TIMSK5 = 1111
TCCR5A = 1010110
TCCR5B = 11111
TIFR5 = 100000
TCNT5 = 0
1
2
3
4
5
timerA
6
7
8
9
timerB
10
11
12
timerC
13
14
15
16
17
18
19
20
timerOVF
TCNT5 = 0
0
OCR5A =10
OCR5B =14
OCR5C =17
ICR5 =20
TIMSK5 = 1111
TCCR5A = 1010110
TCCR5B = 11111
TIFR5 = 100000
TCNT5 = 0
1
2
3
4
5
6
7
8
9
10
timerA
11
12
13
14
timerB
15
16
17
timerC
18
19
20
timerOVF
TCNT5 = 0
0
OCR5A =10
OCR5B =14
OCR5C =17
ICR5 =20
TIMSK5 = 1111
TCCR5A = 1010110
TCCR5B = 11111
TIFR5 = 100000
TCNT5 = 0
1
2
3
4
5
6
7
8
9
10
timerA
11
12
13
14
timerB
15
16
17
timerC
18
19
20
timerOVF
TCNT5 = 0
OCR5A =10
OCR5B =14
OCR5C =17
ICR5 =20
TIMSK5 = 1111
TCCR5A = 1010110
TCCR5B = 11111
TIFR5 = 100000
TCNT5 = 0
0
1
2
3
4
5
6
7
8
9
10
timerA
11
12
13
14
timerB
15
16
17
timerC
18

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

Дык вроде 40 и 42 вывода никакого отношения к OCR5A/B/С не имеют?

40 и 42 используются для управления активацией и направлением вращения мотором, 5 для управления скоростью, OCR5A/B/С не используют физические выводы, только прерывания для пошагового уменьшения ШИМ на 5 выводе

Вот.

OCR5A =5
OCR5B =9
OCR5C =12
ICR5 =15
TIMSK5 = 1111
TCCR5A = 1010110
TCCR5B = 11111
TIFR5 = 0
TCNT5 = 1

И не очень понятна связь между прерываниями и таймерами.

Возможно не до конца объяснил суть программы. Требуется провернуть двигатель на определенное количество оборотов, для контроля вращения на двигателе стоит простой энкодеры в виде диска с прорезями и щелевого датчика, сигнал с датчика идёт на вход T5, счётчик сконфигурирован на внешнее тактирование от этого входа. По программе запускается двигатель и активируется счётчик TCNT5. Регистры OCR5A/B/C настраиваются таким образом чтобы начались прерывания по совпадению за определенное количество импульсов до достижения значения ICR5. И с каждым новым прерыванием ABC происходит понижение analogWrite для входа на L299N Enable. После достижения ICR5 мотор полностью дезактивируется. И ждёт следующей команды на вращение. В приложенном коде я уменьшил цифры чтобы логи много не занимали. Но по непонятной мне причине при первом вызове функции вращения вообще не происходит прерываний по совпадению, а все последующие включения как будто используют значения OCR5 из предыдущего включения, по логам это видно.

Ну да энкодер, мотор вращается быстро хоть и через редуктор подаёт бумагу, 4 импульса ~1мм на полной скорости может проскочить точку на 3-4 мм выключать заранее неблагодарное занятие. Изменение нагрузки физического состояния будет влиять на точность, хотелось бы уложиться в 1-2 импульса, для этого перед полной остановкой должен медленно вращаться

Сделайте скорость = остаток пути

Интересная идея, но я хотел полностью уйти от непрерывного опроса состояния энкодера, отдав это на аппаратные возможности контроллера потому и задействовал вход T5, кстати единственный доступный на плате мега2560((. А как аппаратно привязать TCNT одного таймера к OCR другого ума не приложу. На STM32 наверное это можно было бы через DMA реализовать, а тут не уверен что есть DMA, надо прогуглить

А на кой вы тогда делаете программный ШИМ?

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

Не вникал на всю глубину данных изречений, ибо я в состоянии слегка изменённого сознания, но трезвое подсознание аки интуиция подсказывает что дело в механизме обновления регистров OCRx. По-умолчанию они вступают в силу только после переполнения счётчика. Для мгновенного изменения значений OCRx нужно включить специальный режим, что-то вроде ForceOC. Надо в даташит глядеть, не помню.

1 лайк

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

Там принцип такой:

  1. Счетчик тикает
  2. Пишем в OCRA, например.
  3. Это значение попадает в промежуточный регистр.
  4. При переполнении счётчика, содержимое промежуточного регистра попадает в OCRA.

Ща гляну ДШ.

да, на Figure 17-4 142 страница, нарисован этот буфер

может у меня проблемы с английским но ForceOC просто переключит состояние пина OC не дожидаясь достижения счетчиком значения OCR

Да, я напутал.
Вот табличка:

Спойлер

Я выбирал режим 12: СТС, счёт до ICRn. Это не совсем ШИМ, каюсь. Но регистры сравнения обновляются моментально и соответствующие прерывания срабатывают. Поведение выводов можно настроить аналогично режиму ШИМ, при необходимости. Либо управлять произвольными выводами в прерывании “ручками”. У меня так на одном таймере 5-6 сервомашинок работает.
Какова частота ШИМ на моторе?
Пы.сы.: я даже протрезвел. Мосх зашевелился.

ШИМ на моторе пока от Ардуино analogWrite. Там по-моему пара кГц. В общем красиво сделать не получится, надеялся за 4 прерывания остановить двигатель. В СТС придется несколько раз запускать с изменением ICR. Хотя тут идея возникла а если попробовать всё прописать и самому установить флаг переполнения. Или просто запускать таймер на один тик. Костыли наше всё))

В таком случае придётся забыть AnalogWrite, это от бесовщина. Надо вникать в православные регистры и логику их работы.
Но я не совсем пойму: пара кГц ШИМ, а

это что? Прерывания от энкодера что ли?
Обычно применяют торможение закорачиванием мотора или

Нет, с энкодера считаются импульсы, например нужно 100 импульсов примерно 4 оборота двигателя. Я задаю OCR5A 80, происходит прерывание. В этом прерывании я на вход l298n pin Enable начинаю подавать ардуиновский ШИМ например 70% потом срабатывает прерывание от B компаратора например на 90-ом импульсе я понимаю ШИМ до 50% после срабатывания прерывания от “С” например на 95 импульсе, задаю 30% ну и при достижении 100 импульсов и событии переполнения полностью вырубаю мотор

Просто, всё что нужно это подобрать за сколько импульсов нужно начинать тормозиться. Три шага и три значения ШИМ. Главное совсем низкий ШИМ не делать, ибо движке можно сжечь который остановится и не доедет до сотого импульса и будет постоянно под током