Прерывания таймера Arduino для PID

Я работаю над проектом по робототехнике, в котором Arduino Nano выступает в качестве контроллера движения. Я пытаюсь использовать генератор трапециевидного профиля движения и ПИД-контур, чтобы следовать этому профилю, но обнаружил, что движение было довольно прерывистым, и я предположил, что это потому, что и профиль движения, и ПИД-регулятор работают с одинаковой скоростью (цикл 10 мс с использованием таймера). библиотека прерываний)

#include <TimerOne.h>
void setup() {
  Timer1.initialize(10000);
  Timer1.attachInterrupt(Compute);
}

Теперь я хочу использовать два отдельных цикла, один из которых работает с одинаковым интервалом 10 мс для генерации профиля, а другой с интервалом 1 мс для ПИД-регулятора (я не уверен, что двигатели действительно могут работать так быстро, но я могу настроить тайминги), таким образом, у PID есть около 10 циклов, чтобы точно попасть в желаемое положение, уменьшая рывки движения.

Я не знаю, как настроить два разных прерывания, используя эту библиотеку или установив флаги, такие как TCCR0A, для генерации нужных таймеров прерываний.

Вот фактическое значение "Compute" функция, если это важно (есть некоторые ошибки, касающиеся направления, и он использует два разных метода для создания профиля, также я меняю его, поэтому не думаю, что это имеет значение, но на всякий случай я разместил его) < /p>

void Compute(){
  int dir;
  if (setPoint > target){
    dir = 1;
  } else if (setPoint < target){
    dir = -1;
  }
  if (mode == 1) {
    target += v*dir;
    if (abs(setPoint - target) <= turningPoint){
      v -= acceleration;
    } else {
      if (v < maxV){
        v += acceleration;
      } 
    }
    if (abs(target) >= abs(setPoint)){
      target = setPoint;
      mode = 0;
      v = 0;
      Serial.println("Finished!");
    }
    #ifdef DEBUG
      printDebug();
    #endif
  } else if (mode ==2){
    target += v*dir; 
    if (phase == 1 and v < reachableV){
      v += acceleration;
      if (v >= reachableV){
        phase = 3;
        v = reachableV;
      }
    } else if (phase == 3){
      v -= acceleration;
      if (v <= 0){
        target = setPoint;
        mode = 0;
        v = 0;
        phase = 0;
        Serial.println("Finished!");
      }
    } 
    #ifdef DEBUG
      printDebug();
    #endif
  }
  error = target- pos;
/////////////////////////////////////////////////  
  PID_P = kp * error;
  if (PID_P > outMax) PID_P = outMax;
  if (PID_P < outMin) PID_P = outMin;
/////////////////////////////////////////////////
  if (abs(error) < 50){
    PID_I += ki * error * sampleTime;
  } else {
    PID_I *= 0.8;
  }
/////////////////////////////////////////////////  
  PID_D = kd * (pos - lastPos) / sampleTime;
  if (PID_D > outMax) PID_D = outMax;
  if (PID_D < outMin) PID_D = outMin;
/////////////////////////////////////////////////  
  output = PID_P - PID_D + PID_I;
  if (output > outMax) output = outMax;
  if (output < outMin) output = outMin;
/////////////////////////////////////////////////  
  lastPos = pos;
  if (output < 0){
    digitalWrite(DIR,1);
  } else {
    digitalWrite(DIR,0);
  }
  analogWrite(PWM,abs(output));
/////////////////////////////////////////////////
}

ps: скорее всего, я буду использовать библиотеку PID вместо того, чтобы пытаться написать свою собственную, как у меня сейчас есть код.

, 👍4

Обсуждение

Когда вы говорите о разных циклах, значит ли это, что раньше вы не использовали для этого прерывания?, @chrisl

нет, я имею в виду два цикла с разными периодами, один работает на 10 мс, а другой на 1 мс. Я использую один таймер прерывания 10 мс прямо сейчас., @OM222O

ISR должен быть как можно более кратким и не должен содержать серийный код ввода-вывода... если вы хотите напечатать сообщение, используйте флаг... установите флаг в ISR... напечатайте сообщение и очистите флаг в loop(), @jsotola


1 ответ


Лучший ответ:

4

Просто используйте одно прерывание таймера на 1 мс и посчитайте их. Каждые 10 прерываний делают обе вещи.

void interruptHandler() {
   counter++;

   // делаем то, что происходит каждую 1 мс

   if(counter == 10) {
      // делаем то, что происходит каждые 10 мс
      counter = 0;
   }
}
,

это было бы хорошим решением, но я предполагаю, что есть лучшие способы сделать это? счетчики имеют 3 варианта прерывания: A, B и переполнение. Я предполагаю, что это было сделано для установки ШИМ-сигнала с рабочим циклом и контролем частоты, но я не уверен, как установить правильные флаги для их использования., @OM222O

Почему вы так считаете? Что не так с этим решением? Новички всегда хотят чего-то «элегантного», и под этим они имеют в виду что-то сложное для понимания или глубокое аппаратное обеспечение. Но это бывает не очень часто. Если вы делаете это с отдельными прерываниями, то 1 мс и 10 мс должны срабатывать одновременно. Таким образом, одно прерывание будет опаздывать, и у вас будет дополнительное время на прерывание другого кода. Иногда простой ответ действительно лучший ответ. Что можно получить, если один выход из прерывания и другой вход с опозданием? Вы хотите тратить время?, @Delta_G

Чтобы установить два прерывания на этом таймере и сделать одно из них равным 10 мс, вы должны позволить таймеру работать до 10 мс. Таким образом, для каждого прерывания в 1 мс вы должны обновить регистр OCRA до нового времени, чтобы он срабатывал в 1 мс, затем в 2 мс, затем в 3 мс, затем в 4 мс. Если вы хотите, чтобы прерывания были регулярными, вам придется вычислять, сколько нужно добавлять каждый раз. Что в этом лучше?, @Delta_G

Вы можете использовать второй таймер, но тогда вы потеряете PWM еще на двух выводах. И я предполагаю, что вы хотите использовать Timer1 для 16-битного разрешения. Вы получаете только один из них. Таким образом, вам придется отказаться от разрешения на втором прерывании. Как это помогает?, @Delta_G