Using timers for PID and stepper control

Hello,
I'm using an Arduino Mega to control stepper motors and close a PID loop.
I'm using timer 1 for the PID control. It sets of the ISR every 0.1 second, which in turn calculates the required PWM
for a DC motor. The setpoint is the turning frequency and the measurement is the encoder count. The control works well. As stated, this ISR runs every 0.1 second.

I'm using timer 3 to vary the PWM frequency to control the speed of a stepper motor. An external measurement relates directly to TCNT3, which changes the frequency of the PWM signal, which in turn changes the stepper motor speed. This also works well. The ISR is usually run every ~600-2000 microseconds.

However, when I try to use both of these applications together, the PID loop is affected. In order for the DC motor to respond at all I need to change my control constants (Kp, Ki, Kd) to extremely high, which makes my control very jittery and un-reliable.

Does anyone have any ideas what could be causing this? When I comment out the initialization of Timer3 the PID loop (with Timer1) works fine. Do these timers have a dependency I'm unaware of? This is my first project with timers so I'm probably missing something elementary.

//--------------Parameters-------------
double foam_freq = 3;//Hz, foaming frequency
int foaming_time = 10; //seconds, foaming time
int foam_volume = 130; //ml
double foam_draw_time = 10; //seconds, into syringes 
double foam_push_time = 20; //seconds, from syringes
int drug_volume = 30; //ml
double drug_admin_time = 2; //seconds
int delay_foam2drug = 50; //ms before drug administration starts
int delay_drug2foam = 50; //ms  of foam after drug administration starts
int air_volume = 250; //ml
double air_admin_time = 6; //seconds
//--------------End parameters-------------

String mySt = "";
char myChar;
boolean stringComplete = false;  // whether the string is complete
//Define pins
#define pwmOutput 5 //PWM for DC motor control
#define in1 6 //H-bridge transistor 1
#define in2 7 //H-bridge transistor 2
#define drug_direction 8 //
#define drug_step 9 //
#define foam_direction 10 //Stepper motor 1 dir
#define foam_step 11 //Stepper motor 1 pin

int push_mm=0, draw_mm=0;
double setPoint = 0;


//Volatile memory for changing in ISR
int encoder = 0;
double pv_speed = 0;
double e_speed = 0; //error of speed = setPoint - pv_speed
double e_speed_pre = 0;  //last error of speed
double e_speed_sum = 0;  //sum error of speed
double pwm_pulse = 0;     //this value is 0~255
double kp = 3;
double ki = 5;
double kd = 1;
int timer1_counter; //for timer
boolean motor_start = true;
volatile int steps;
volatile byte home_flag=false;
volatile byte end_flag=false;
volatile int pressure, pt;
byte pressure_control = false, plot_flag=false;
int prev_time=0;

double f_target= 1667;
double PSC = 1024;
volatile long int timer3_counter = 65536-15625/f_target;
volatile int step_count=1000000;
volatile byte drug_flag = false, foam_flag = false;
byte set_time_flag = false;
void setup() {
  noInterrupts();
  //--------------------------timer1 setup
  TCCR1A = 0;
  TCCR1B = 0;
  timer1_counter = 59286;   // 65536-16MHz/256/10Hz (59286 for 0.1sec)
  TCNT1 = timer1_counter;   // preload timer
  TCCR1B |= (1 << CS12);    // 256 prescaler
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  
  //--------------------------timer3 setup
  TCCR3A = 0;
  TCCR3B = 0;
  TCNT3 = timer3_counter;   // preload timer
  TCCR3B |= (1 << CS12);    // 256 prescaler, CS = source clock select
  TCCR3B |= (1 << CS10);
  TIMSK3 |= (1 << TOIE3);   // enable timer overflow interrupt
  //--------------------------end time setup
  interrupts();   
  Serial.begin(9600);
  pinMode(A0,INPUT); //Potentiometer (for pressure loop simulation)
  pinMode(2,INPUT); // Encoder interrupt
  attachInterrupt(digitalPinToInterrupt(2),detect_pulse,RISING);
  
  pinMode(in1, OUTPUT); 
  pinMode(in2, OUTPUT);
  pinMode(drug_direction,OUTPUT);  //Stepper direction
  pinMode(drug_step,OUTPUT); //Stepper step
  pinMode(foam_direction,OUTPUT);  //Stepper direction
  pinMode(foam_step,OUTPUT); //Stepper step
  pinMode(pwmOutput,OUTPUT);  //DC motor pwm (foamer)

  // Set initial rotation direction
  digitalWrite(in1, LOW); //Having both transistors on LOW means no current
  digitalWrite(in2, LOW); //This is a fsafety precaution to turn off foamer
  analogWrite(pwmOutput,0); //Another precaution. PWM=0 ---> Voltage=0.
}

void loop() {
      foam_freq=setPoint;
}

void detect_pulse() {
  encoder+=1; //increasing encoder at new pulse
}


ISR(TIMER1_OVF_vect) {       // interrupt service routine - tick every 0.1sec
  TCNT1 = timer1_counter;   // set timer
  pressure = map(analogRead(A0),0,1023,0,80); //Read sensor
  if (pressure_control == true){
    timer3_counter = 65536-15626*(6+pow(1.07,pressure))/10000;
  }
  else {
    timer3_counter = 65536-15625/f_target;
  }
  
  pv_speed = (encoder/20.0)/0.1;  //calculate motor speed
  encoder=0; 
  //PID program
  if (motor_start){
    e_speed = setPoint - pv_speed;
    pwm_pulse = e_speed*kp + e_speed_sum*ki + (e_speed - e_speed_pre)*kd;
    e_speed_pre = e_speed;  //save last (previous) error
    e_speed_sum += e_speed; //sum of error
    if (e_speed_sum >30) e_speed_sum = 30;
    if (e_speed_sum <-30) e_speed_sum = -30;
  }
  else{
    e_speed = 0;
    e_speed_pre = 0;
    e_speed_sum = 0;
    pwm_pulse = 0;
  }
  //update new speed
  if (pwm_pulse <255 & pwm_pulse >0){
    analogWrite(pwmOutput,pwm_pulse);  //set motor speed  
  }
  else{
    if (pwm_pulse>255){
      analogWrite(pwmOutput,255);
    }
    else{
      analogWrite(pwmOutput,0);
    }
  }
}

ISR(TIMER3_OVF_vect){
  if (step_count<steps){
    if (drug_flag==true){
      digitalWrite(drug_step,!digitalRead(drug_step));
      step_count++; 
    }
  }
  else {
    steps = 0;
  }
  TCNT3 = timer3_counter;   // preload timer
}

Some Arduino functions like analogWrite() use timers, so if you have set up for your needs that those functions depend on, those functions may not behave as documented.

I would not do the PID calculation within the ISR - it is using floating point maths which is very slow. Indeed I would not do any floating point calculations in an ISR if there was any way to avoid it. Just use the ISR to set a flag and do the calculations in a function in the main program.

How many encoder pulses are there per revolution? And how fast is the motor rotating?

In my experience with a small DC motor one pulse per revolution was quite sufficient to keep the speed very stable even in the face of varying speed settings as well as a varying load.

...R

volatile int step_count=1000000;

Overflow: int is too small, if step_count is never negative, use "volatile unsigned long int".

  if (pwm_pulse <255 & pwm_pulse >0){
shouldn't this be

if ((pwm_pulse < 255) && (pwm_pulse > 0)) {

While debugging, I suggest turning on compiler warnings in preferences.

I would change the way you generate both the TIMER1 and TIMER3 outputs

Since you need a 10Hz tick rate for timer1, it easier and more precise to use CTC mode instead. You don't need to do anything on the TCNT1 register since the TOP value is defined by OCR1A. So with a pre-scale of 256, timer1 runs at 62500 Hz, you set your TOP (OCR1A) to (6250 - 1), and you get 10 Hz interrupt

	// use CTC mode
	TCCR1A = 0<<WGM11 | 0<<WGM10;
	TCCR1B = 0<<WGM13 | 1<<WGM12;
	
	// set TOP [OCR1A] to generate 10 Hz
	OCR1A = (6250 - 1);
	
	// enable OCRA compare interrupt
	TIMSK1 = 1<<OCIE1A;
	
	// start timer1 at 62500 Hz
	TCCR1B = 0<<WGM13 | 1<<WGM12 | 1<<CS12 | 0<<CS11 | 0<<CS10;

And use TIMER1_COMPA interrupt instead of TIMER1_OVF ISR

// 10 Hz tick
ISR(TIMER1_COMPA_vect)
{
	// do your thing here
}

And now, the same goes for timer3, use fast PWM mode instead to messing around with the TCNT3 register

I'm using timer 3 to vary the PWM frequency to control the speed of a stepper motor

	// set PWM output to PORTE4 and PORTE5
	DDRE |= 1<<4 | 1<<5;
	
	// use Fast PWM mode for timer3
	TCCR3A = 1<<WGM31 | 1<<WGM30 | 1<<COM3B1 | 0<<COM3B0 | 1<<COM3C1 | 0<<COM3C0;
	TCCR3B = 1<<WGM33 | 1<<WGM32;
	
	// set TOP [OCR3A], defines the PWM frequency, initial value of 100 Hz
	OCR3A = (625 - 1);
	
	// set duty cycles for PWM output to 50%
	OCR3B = (624/2);
	OCR3C = (624/2);
	
	// no interrupts for timer3
	TIMSK3 = 0;
	
	// start timer3 at 62500 Hz
	TCCR3B = 1<<WGM33 | 1<<WGM32 | 1<<CS32 | 0<<CS31 | 0<<CS30;

The timer3 initialization above generates a 50% PWM at 100Hz to PORTE4 and PORTE5
To change the PWM frequency, all you need to do is change the OCR3A register using the formula
OCR3A = (62500 / PWM_freq) - 1
And for the duty cycle, if you need 50%, just divide OCR3A with 2 for the OCR3B and OCR3C registers. You can change the OCR3A, OCR3B and OCR3C registers in your timer1 interrupt. No need for separate interrupt on timer3.

PORTE4 and PORTE5 are on MEGA pins 2 and 3

To answer a few questions:
There are currently 20 encoder pulses per revolution. which will soon be 64 (new encoder on its way).
The frequency is in the 0.1-5 Hz range, so In order to get at least 2 pulses between readings - 20 PPR.

Thanks you very much for all of the suggestions!
I'll learn a bit about what you've all recommended and re-write some of the code.
I'll update this thread for future users once I reach some conclusions regarding my application.

jgreenb:
The frequency is in the 0.1-5 Hz range, so In order to get at least 2 pulses between readings - 20 PPR.

You have not told us how fast the motor must rotate.

It would also be useful to know what is the maximum acceptable variation in the rotation speed.

And also what is the motor being used for.

If the motor is rotating slowly then another stepper motor might be a simper solution.

...R

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.