Motor Loop Control using Arduino Mega

Hello everyone,

I am trying to control the speed of two motors using an Arduino Mega board. I have the basic code in place (below).
However I'd like to know from the experienced people here as to how realistic is it to expect microsecond level resolution on the Arduino.

I've listed the condensed code below.

Also, is there a better option to work this out? Like using Timer interrupts?
I read a few posts about these but they tend to mess with the PWM operation of analogWrite which I don't want to.

I haven't tested the operation completely since I don't have the encoders yet and don't have a signal generator handy.

Would be great if you can link/list any suggestions and point out any fundamental mistakes in this code if noticed.

/*****
Requirements
Pin 8: Mot_1  : Control by PWM =>H-Bridge that accepts PWM
Pin 10: Mot_2 : Control by PWM =>H-Bridge that accepts PWM
Pin 40: Enc 1 : CPR 360, Motor at 900  rpm => max CPS = 5400
Pin 44: Enc 2 : CPR 5,    Motor at 1440 rpm => max CPS =  120
 !!!! Warning... some declarations/functions may be missing...
       Code is not directly runnable !!!!
*****/
// Running on an Arduino Mega //

//Sampling  frequency: 5x of max CPS = 5*5400 = 27000Hz
//PWM freq is ~500Hz (From documentation of Arduino Mega)
//So every 27000/500 = 54 loops, we recalc PWM duty cycles

int PWM_Delay_Cnt = 0 ;
int PWM_OVERFLOW  = 54;
int MAIN_DELAY_US = 20;  //27000Hz requires re-reads every 37us... allowing for 17us for instruction processing,

//Pins
int Pin_8   = 8 ;
int Pin_10  = 10;
int Pin_40  = 40;
int Pin_44  = 44;

unsigned long int elapsedTime, lastTime, firstTime;

void setUp()
{

 pinMode(Pin_40,INPUT);
 pinMode(Pin_44,INPUT);
}

void loop()
{
 readEncoder(Pin_40); //checks for +ve edge and increments a counter if found
 readEncoder(Pin_44); //checks for +ve edge and increments a counter if found
 PWM_Delay_Cnt++;
 if(PWM_Delay_Cnt==PWM_OVERFLOW)
 {
      freq1 = calc_Freq(Pin_40);
      freq2 = calc_Freq(Pin_44);
      recalcDutyCycles(); //does some PI stuff, Tustin's method etc
      resetCounters(); //resets enc,pwm delay cnters
 }
 analogWrite(Pin_8,Duty_Cycle_8);   //Pin 8 Motor 1
 analogWrite(Pin_10,Duty_Cycle_10); //Pin 10 Motor 2

 delayMicroseconds(MAIN_DELAY_US);

}

int calc_Freq(int pin)
{

  timeFirst = micros(); //yes, there is code that takes care of the microsec counter reset around every 70 minutes.
  elapsedTime = timeFirst - timeLast;
  timeLast = timeFirst;

  freq_Pin = cnt_Pin * 1000 * 1000 / elapsedTime; //elapsed time is in uS, hence cnt*10^6
  
}

Regards,
Abe

I'm curious to know why microsecond-level resolution is all that important in any hobbyist electromechanical system.

@Groove: This is not a hobbyist project. It is a undergrad research project where the motors and encoders are a part of a bigger system. As I have mentioned in the code comments, the encoder pulse freq is going to be around 5400Hz.
Using Shannon-Nyquist, at least 2x5400 is needed as sampling frequency.
Assuming a 5x sampling frequency, the requirement is sampling happens at 27000Hz or 37microseconds.
Even if the PID controller doesn't get triggered any faster than 500Hz, the sampler requires 37 microseconds.

Is there any contrary experience indicating that this wont work?

Thanks.

Regards,
Abe

P.S. Originally we were using a LPC2129 which died and the only other option is the Arduino Mega and one week to completion deadline

the encoder pulse freq is going to be around 5400Hz.
Using Shannon-Nyquist, at least 2x5400 is needed as sampling frequency

Any reason for not using edge-triggered interrupts?

Two encoders in action at the same time. Ideally the edges need to be matched, but may not be.

Thanks
Abe

However I'd like to know from the experienced people here as to how realistic is it to expect microsecond level resolution on the Arduino.

That excludes me but I'll offer some opinions anyway. ;D

This is not a hobbyist project. It is a undergrad research project

Is there a difference? :smiley: (sorry, couldn't resist)

I am trying to control the speed of two motors using an Arduino Mega board. I have the basic code in place (below).

Two motors. Got it.

Assuming a 5x sampling frequency, the requirement is sampling happens at 27000Hz or 37microseconds.

At 37 microseconds the Arduino will be able to execute 592 machine instructions. With two motors that's 296 machine instructions per motor per pulse.

Even if the PID controller doesn't get triggered any faster than 500Hz, the sampler requires 37 microseconds.

PID controller? Are you planning to perform two PID calculations on the Arduino?

Is there any contrary experience indicating that this wont work?

There are two things that may interfere...

  1. Lack of horsepower. The Arduino simply may not be able to execute enough machine instructions in the given time. You haven't included enough information to know if this will be a problem.

  2. "Background" interrupts. There are interrupts driving millis, Serial, and analogWrite. Each of these interrupt service routines takes time to run; time that is taken away from your code. It may be possible for your code to miss pulses if any of the ISRs takes too long to run or if more than one runs in quick succession.

Also, is there a better option to work this out? Like using Timer interrupts?

Using Timer interrupts may actual may the problems worse. There is considerable overhead with an ISR that will take even more time away from your code.

Is there a difference? Cheesy (sorry, couldn't resist)

Yes there is. Just one. Deadlines. :slight_smile:

At 37 microseconds the Arduino will be able to execute 592 machine instructions. With two motors that's 296 machine instructions per motor per pulse.

So I have to be very conservative with the loop() code. Actual pulse reading is simply positive edge detection

void readEncoder(int encoder){//encoder should be the pin that is to be read
  if(encoder == PIN_ENC_SLIT)
  {//stuff for slit opt 101 encoder
    currState_Slit = digitalRead(encoder);
    if(lastState_Slit == LOW && currState_Slit == HIGH)
    {//positive edge detected
      cntEnc_Slit++;    
    }
    lastState_Slit = currState_Slit; 
  }
  else //(encoder == PIN_ENC_SCREEN)
  {//stuff for screen E8P encoder
    currState_Screen = digitalRead(encoder);
    if(lastState_Screen == LOW && currState_Screen == HIGH)
    {//positive edge detected
      cntEnc_Screen++;    
    }
    lastState_Screen = currState_Screen; 
  }
}

PID controller? Are you planning to perform two PID calculations on the Arduino?

Yep, if that's not equal to trying to cross the Atlantic in a rubber dinghy.

There are two things that may interfere...

  1. Lack of horsepower... The Arduino simply may not be able to execute enough machine instructions in the given time. You haven't included enough information to know if this will be a problem.

Have included the encoder read code above. The PID code is still a W.I.P. Possibly might end up using the PID library made available on the Arduino website.

  1. "Background" interrupts. There are interrupts driving millis, Serial, and analogWrite. Each of these interrupt service routines takes time to run; time that is taken away from your code. It may be possible for your code to miss pulses if any of the ISRs takes too long to run or if more than one runs in quick succession.

While this may be a definite problem, the PID part might work out. I have this crazy idea that I don't want to really worry about the encoder pulses while I am doing the PID math. Before and after, however, it is a critical thing not to miss pulses.

Using Timer interrupts may actual may the problems worse.

Thought so. Interrupts are now off the board.

I have received the encoders and am trying to get the whole thing to sing together. Will post back with success or queries whichever come first.

Regards,
Abe