PID control of motor synced to an external pulse.

Hi, I am trying to understand something that is probably very easy.

I have a motor that I need to sync up to an external pulse. The pulse is 60 times per second, I need the motor to perfectly match the speed and be at a specific place in its rotation relative to the pulse. I use PWM to control the motor. I have a simple hall effect sensor that reads a magnet fixed to the part that the motor turns(through some gears) My idea is that I need to get the two pulses to line up. Also I need to be able to shift the phase of the motor so I can offset it, so I dont have to move the magnet but just do that in software instead. I have looked at using PID but I dont know the best way to calculate the error between the pulses to feed the PID.

Thanks a lot for taking the time to read, if this has been answered before please point me to that and I will delete this post.

Best regards

PID is not what you want. You need a stepper.

Ok, that is a solution I guess. How would you sync the stepper to the pulse?

The thing is that this is a modification of an old projector to make it in sync with a videocamera. It would be possible to change the motor of course but it should be fairly simple to solve with just controlling the speed of the motor? There is plenty of braking going on from all things that the motor drives.

Thanks for you input!

I suppose you could do it with judicious pwm timing, but the only way to set a motor to that position at this time is to use a stepper.

How much error can you tolerate?

The system can tolerate quite a lot of error it can be solved with a brighter light and shorter exposure time in the camera. That also helps with sharpness. An error of 5-10% should not be a problem.

You could do it with a feedback algorithm such as PID. I suspect you might need to tackle the problem in two parts - first use a feedback algorithm to get the motor frequency the same as the input frequency, second use a separate feedback system to make the phase delay between the two signals match your required phasing. The first feedback system would need to have a lot of authority and the second part would need much less authority.

That makes sense! How do I best calculate the difference between the phases? I could use pulseIn on both channels, calculate the difference between the pulses and use that as error in the PID.
Then have a second measurement between how far the different channels are from each other, how do I do that? I want it to work in both ways so that if it overshoots a bit it doesnt need to go one whole phase forward but can sense the difference in pulse both before and after the reference pulse. Does that makes sense?

This works seemingly. The values for offset and gain have to be established by trial and error.

const int MotorPWM = 10;
const int MotorPulse = 2;
const int ExtPulse = 3;
const double Gain =1.4;//regulator gain
const int offset =30;//regulator offset

int Counter = 20;//initial value for quick start
unsigned long Timer;
int MotorState;//for edge detection 
int ExtState;//for edge detection

void setup(){
  Serial.begin(115200);
  pinMode(MotorPWM, OUTPUT);
  Counter = 37;
  Timer = millis();
}//setup()

void loop(){
  MotorState = ((MotorState<<1)|digitalRead(MotorPulse))&3;//detect rising edge on motor pulse
  if(MotorState == 1) --Counter;//decrement Counter on leading edge of motor pulse
  ExtState = ((ExtState<<1)|digitalRead(ExtPulse))&3;//detect rising edge on external pulse
  if(ExtState == 1) ++Counter;//increment Counter on leading edge of external pulse
  analogWrite(MotorPWM, offset + Counter*Gain);
  if(millis()-Timer > 2000){
    Serial.println(Counter);
    Timer = millis();
  }//if
}//loop()

What you want is called a "phase-locked loop". The approach has been around for decades and the theory has been very carefully worked out. You should be able to find plenty of tutorials on applications related to yours. https://en.wikipedia.org/wiki/Phase-locked_loop

luuude: How do I best calculate the difference between the phases?

The pulse length is the difference between the time the latest pulse was detected and the time the previous pulse was detected. The pulseIn() function is no use to you here, just use a simple interrupt handler or polling loop to detect the input pulses.

The phase is the difference between the time the latest pulse was detected for one input, and the time the latest pulse was detected for the other input.

I rewrote the code with some more precision on the phase difference. The error signal is now proportional to the time difference between the leading edges from the motor pulse and sync pulse.
In my setup the sync freq is 10 Hz, the max err is around 30ms, the average around 13ms. The setup is very simple (see pic).

A better motor with lower friction and higher inertia would probably improve the results. One other improvement would be to measure both the phase difference and the period time.

The SM library used in the code is found here: http://playground.arduino.cc//Code/SMlib. The state diagram is attached

#include <SM.h>

const int MotorPulse = 2;
const int ExtPulse = 3;
const int MotorPWM = 10;

const float incGain = 0.018;
const float decGain = 0.011;
float Integral = 33.0;

unsigned long Timer;
unsigned Cerr;
unsigned Aerr;
unsigned Merr;

SM Integrator(WaitLowB);//Statemachine

void setup(){
  Serial.begin(115200);
  pinMode(MotorPWM, OUTPUT);
  analogWrite(MotorPWM, 80);//kickstart
  delay(30);
}//setup()

void loop(){
  EXEC(Integrator);//run Statemachine
  if(millis()-Timer > 4999){//report errors
    Serial.print("Max = ");
    Serial.print(Merr);
    Serial.print(" Aver = ");
    Serial.println((float)Aerr/50);
    Merr = 0;
    Aerr = 0;
    Timer = millis();
  }  
}//loop()

State WaitLowB(){//wait for 1st raising edge
  if(digitalRead(ExtPulse)) Integrator.Set(IncB);
  if(digitalRead(MotorPulse)) Integrator.Set(DecB);
}//WaitLowB()

State IncH(){
  Integral += incGain;//correct signal
  Cerr += 2;//log error
}//IncH

State IncB(){
  if(digitalRead(MotorPulse)) Integrator.Set(WaitHighH, WaitHighB);//wait for other rasing edge
  if(Integrator.Timeout(2)) Integrator.Set(IncH, IncB);//timestep = 2ms
}//IncB

State DecH(){
  Integral -= decGain;//correct signal
  Cerr +=2;//log error
}//DecH()

State DecB(){
  if(digitalRead(ExtPulse)) Integrator.Set(WaitHighH, WaitHighB);//wait for other raising edge
  if(Integrator.Timeout(2)) Integrator.Set(DecH, DecB);//timestep 2ms
}//DecB()

State WaitHighH(){
  analogWrite(MotorPWM, Integral);//output signal
  if(Cerr > Merr) Merr = Cerr;//record max err
  Aerr += Cerr;//average err
  Cerr = 0;//reset current err
}//WaitHighH()

State WaitHighB(){
  if(!digitalRead(MotorPulse) && !digitalRead(ExtPulse)) Integrator.Set(WaitLowB);//wait for low on both pulses
}//WaitHighB()

setup.JPG

State diagram.png