4

I'm working on a robotics project where an arduino nano is acting as the motion controller. I'm trying to use a trapezoidal motion profile generator and PID loop to follow that profile, but I found out the motion was quite jerky and I assumed it's because both the motion profile and the PID run at the same speed (10ms loop using timer interrupt library)

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

now I want to use two seperate loops, one running at the same 10ms interval for the profile generation and one running at 1ms for the PID (I'm not sure if the motors can actually act that quickly, but I can adjust the timings a bit), this way the PID has about 10 loops to get to the desired position accurately, reducing the jerky motion.

I'm not sure how I can set up two different interrupts using that library or by setting the flags like TCCR0A to generate the desired interrupt timers.

Here's the actual "Compute" function in case it's relevant (there are some bugs regarding the direction and it's using two different methods for generating the profile, also I'm changing it so don't think it matters, but just in case it does, I posted it)

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));
/////////////////////////////////////////////////
}

p.s: I'm most likly going to use the PID library instead of trying to write my own like I currently have the code.

  • When you speak about different loops, does that mean, that you previously didn't use interrupts for this? – chrisl Sep 27 at 18:32
  • no I mean two loops with different periods, one running at 10ms and one at 1ms. I'm using a single 10ms interrupt timer right now. – OM222O Sep 27 at 18:37
  • 1
    the ISR should be as brief as possible and should not contain any serial I/O code ... if you wish to print a message, then use a flag ... set the flag in the ISR ... print message and clear the flag in loop() – jsotola Sep 28 at 1:40
3

Just use a single 1ms timer interrupt and count them. Every 10 interrupts do both things.

void interruptHandler() {
   counter++;

   // do things that happen every 1ms

   if(counter == 10) {
      // do things that happen every 10ms
      counter = 0;
   }
}
| improve this answer | |
  • that'd be a nice solution but I'm assuming there are better ways to do this? the counters have 3 options for interrupt: A, B and overflow. I'm assuming this was done to set a PWM signal with duty cycle and frequency control, but I'm not sure how to set the proper flags to use them. – OM222O Sep 27 at 18:55
  • 1
    Why would you assume that? What is wrong with this solution? Newbies always want something "elegant" and by that they mean hard to understand or deep into the hardware. But that's not very often the case. If you do this with separate interrupts then the 1ms and the 10ms will both have to fire at the same time. Thus one interrupt will be late and you'll have the extra overhead time interrupting your other code. Sometimes the simple answer is really the best answer. What would there be to gain by having one interrupt exit and another enter late? You want to waste time? – Delta_G Sep 27 at 21:40
  • In order to set two interrupts on that timer and have one be at 10ms, then you have to let the timer run all the way to 10ms. So for every 1ms interrupt you have to update the OCRA register to a new time so it fires at 1ms then 2ms then 3ms then 4ms. If you want the interrupts to be regular then you'll have to calculate how much to add each time. What's better about that? – Delta_G Sep 27 at 21:44
  • You could use a second timer, but then you lose PWM on two more pins. And I assume you're wanting to use Timer1 for the 16 bit resolution. You only get one of those. So you'd have to give up resolution on the second interrupt. How does that help? – Delta_G Sep 27 at 21:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.