constant acceleration motor control

Hey Guys,
So I'm working on a project where I need to make a motor move a draw bridge in an certain way.
The bridge would start out at point A and accelerate to point B, then it would move at a constant speed until point C and then decelerate to a stop at point D (trapezoidal interpolation). I would like the acceleration from point A to B to be constant and likewise the deceleration from C to D. I'm not sure how to implement this since from my understanding its not exactly position control, I was thinking of using the PID library but I'm sure how to implement it in this way. From browsing through the Forum I wasn't able to find anything with constant acceleration but I might have missed it.
Anyone have any thoughts on it?
Thanks in advance

What kind of motor are you planning to use?
If its a stepper motor you could use the AccelStepper library with an appropriate driver . If you plan to use a DC brushed you will need some kind of position feedback, most likely an encoder. Here you have two choices, get a servo driver and still use AccelStepper or write the code yourself. In the latter case you will still need a H-bridge to drive the motor.

I'm using a brushed DC motor with an H-bridge motor driver board, the motor has an attached pot so I have the position feedback, I'm just not certain how to write the code. I'm fairly new to programming and I sometimes have a hard time grasping how to write the code to do what I want.

here is the motor driver board I'm using
http://www.robotpower.com/products/MegaMoto_info.html
would this be an appropriate driver to use the AccelStepper Library?

No, not quite. What you have to do is run some tests to establish max acceleration, deacceleration and speed. Actually deacceleration is the most important since a brushed dc motor will give you full accelation when fully powered. But it is crucial to start deacceleration at a certain point to prevent overshoot.
I have a program for motor control with integer pid regulation that works with an encoder, i think it can be adapted to work with potentiometer feedback

wouldn't full power give a max speed?
how would I write a test to determine the max accel and deccel? Right now all I can think of to do accel and decel is to increase or decrease the PWM which results in a pretty jerky movement. If you don't mind I would love to look at your code and see how you're implementing the PID.

A motors purpose is to deliver torque. A brushed dc will use this torque to try to increase its speed up to the point where the induced EMF equals the applied voltage. At this (theoretical) point there will be no more torque since the current is zero. All mechanical load applied to the axle will lower its speed and thus the induced EMF which in turn increases current. The increased current will increase the torque to a point where the torque delivered by the motor and the torque demanded by the load balance each other. If the torque demand of the load is too high the current will cause excessive heat (i^2*r) and overheat and eventually burn the motor.
So a brushed dc motor goes only too full speed when it is not mechanical loaded. Any load (inertia and friction and useful mechanical work) will prevent that.

If you just disconnect a motor from its voltage it will continue spinning until the energy stored in the inertia of the load is dissipated by friction and useful work. If you short circuit the motor winding when disconnecting it will come to a very fast stop since the energy stored will be dissipated in the rather low resistance of the motor winding. If you slow down a motor by decreasing its voltage the stored energy will be fed back to the voltage source because the induced EMF is higher than the applied voltage. What happens when you decrease speed with a h-bridge depends on the current decay mode of the bridge. If you just turn the transistors off the freewheeling diodes will try to feed back the energy to the voltage source. This is not possible sine that voltage always is higher than the induced EMF. The motor will be effectively disconnected from the voltage source and will be free spinning. If you on the other hand let the two lower (or upper) transistors conduct the current can circulate through these and almost short circuit the motor.

So much for motor behavior, having this in mind will help you understand your project.

Here is the code i talked about. The TimerOne library can be found here.. Try to compile the code in order to check if you got the library right, You will not be able to run it since it is written for to encoders, not a potentiometer. But it will be a start at least. It can also help study the PID regulator. It is based on tjhis article but with integer variables.

#include <TimerOne.h>

const int outPwm = 9;//pin for speed control
const int outDir = 8;//pin for direction control
const int signalPin = 12;//pin for signaling and triggering

//constants for PID calculation
const unsigned long samplePeriod = 400;//time between pid calculations in us
const long kp = 12;//proportional gain
const long ki = 3;//integral gain
const long kd = 2700;//derivative gain
const byte shift = 2;//for shifting/scaling output value
const long outMax = (1024<<shift)-1;//keep max output <=1023
const byte iShift = 11;//right shift for i term
const long iMax = (1024<<(iShift+shift))-1;//keep i term contribution <=1023 and avoid windup
const int stepFrame = 1500;//Number of values for step response


//variables for PID calculation
volatile long iTerm;//summing up integral error
volatile long lastPos;//for calculation of derivative
volatile int output;//output to speedcontrol 
enum {FWD, REW} dir;

//data for position calculation
const char encTable[16] ={0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0};//gives -1, 0 or 1 depending on encoder movement
volatile byte encState;//remembering the encoder output and acting as index for encTable[]
volatile byte stepState;//for edge detection on step input = D10
volatile byte inp;//for reading encoder pins
volatile byte setInp;//input signals from second encoder
volatile byte setState;//remembering setpoint encoder state
volatile long setPos;//position setpoint, updated by second encoder
volatile long actPos;//actual position from encoder
volatile long error;
volatile long dInput;
volatile byte sched;//for scheduling calculations

unsigned long t;//timekeeper

void setup(){
  pinMode(outPwm, OUTPUT);
  pinMode(outDir, OUTPUT);
  pinMode(signalPin, OUTPUT);
  Timer1.initialize(samplePeriod);
  Timer1.attachInterrupt(doPID);
  Timer1.pwm(outPwm, 0);
}//setup()

void loop(){
  while(1){
    inp = PIND;//read inputs 0..7
    encState = ((encState<<2)|((inp>>2)&3))&15;//use encoder bits and last state to form index
    actPos += encTable[encState];//update actual position on encoder movement
    setState = ((setState<<2)|((inp>>4)&3))&15;//use encoder bits and last state to form index
    setPos += encTable[setState];
  }//while(1)
}//loop()

void doPID(){
  /*Compute all the working error variables*/
  error = setPos-actPos;
  iTerm += (ki*error);
  if(iTerm>iMax) iTerm = iMax;
  else if(iTerm<-iMax) iTerm = -iMax;
  dInput = (actPos-lastPos);
  /*Compute PID Output*/
  output = kp*error+(iTerm>>iShift)-kd*dInput;
  if(output < 0){
    dir = REW;
    output = -output;
  }else dir = FWD;
  //if(output<0)
  if(output > outMax) output = outMax;  
  Timer1.setPwmDuty(outPwm, output);
  digitalWrite(outDir, dir);
  lastPos = actPos;
}//doPID()

I don't understand what you are doing with the bitshifts and the ANDing in these lines:

setState = ((setState<<2)|((inp>>4)&3))&15;

encState = ((encState<<2)|((inp>>2)&3))&15;

also, what does subtracting 1 do here?

const long outMax = (1024<<shift)-1;//keep max output <=1023
const byte iShift = 11;//right shift for i term
const long iMax = (1024<<(iShift+shift))-1;//keep i term contribution <=1023 and avoid windup

Why put the PID in an interrupt? Does that help keep the timing consistent?
Also if I wanted to use another interrupt for an emergency stop how do I figure out which interrupt has priority?

Thanks for the help I really appreciate it, and it looks like I have to take some time and go into more depth on how motors work since I seem to have forgotten what I knew about them :fearful:

Ok, the program uses encoders and this is my technique to handle them. I'll explain step by step:

  • (setState<<2). setState is the variable that keeps track of the setpoint encoder. Since the encoder has 2 bits shiftting this variable left by 2 will preserve the last input state.
  • ((inp>>4)&3). inp reads all bits of the d port (pins 0..7 on a uno) The right shift by 4 moves bits 4 and 5 (where the setpoint encoder is connected) into bits 0 and 1. This value is then anded with 3 which zeros all other bits and leaves only the encoder input.
  • ((setState<<2)|((inp>>4)&3))&15; This ors the two previous values resulting in a bit pattern that contains bot the previous and the current encoder reading. In that way encoder movement can easily be detected. This is done by using the setState variable as an index for encTable[] which holds the encoder movement for all possible input combinations (1, 0 or -1)

But we do not need this since you are using a potentiometer instead of a encoder.
And you are right. The PID is in a timer interrupt to keep timing consistent. Putting an e-stop in a pin change interrupt poses no problems. Did you manage to compile the program? In that case we can start working on modifying it to work with potentiometer feedback. It would basically be taking out the encoder lines an replacing them with an analog(read). maybe we should restructure the program a little and let the interrupt just set a signal to indicate that a PID calculation is due and so the analogRead() immediately before it. Since a potentiometer is an absolute sensor there is no need of keeping constant track of it.

Yes I was able to compile it, so I'd be replacing these lines:

inp = PIND;//read inputs 0..7
    encState = ((encState<<2)|((inp>>2)&3))&15;//use encoder bits and last state to form index
    actPos += encTable[encState];//update actual position on encoder movement
    setState = ((setState<<2)|((inp>>4)&3))&15;//use encoder bits and last state to form index
    setPos += encTable[setState];

with:

if (PIDdue = 1)
    {
      bridgePosition = analogRead(Pot);
      doPID();
    }

and set up the ISR to just set a flag to indicate that a calculation needs to be done?

an aside, what are the advantages of defining the pins as "const"?

Do you mean declaring them as const instead of #defines? Actually i dont think it matters much, both ways work fine and the compiler does a descent job optimizing.
Yes, you got the ideas right, but you also will need to retune the PID parameters. I can help you with that. What you can do is connect a second potentiometer to an analog input so that you can easily change the setpoint at runtime.
I have some ideas of how to create a speed profile but they need testing first so i will come back to you on that.

And one more thing. You have to reset the PIDdue variable inside the if statement

Ok so I have the code adjusted to fit my motor controller as follows

#include <TimerOne.h>

const int outPwm = 9;//pin for speed control
const int outDir = 8;//pin for direction control
const int signalPin = 12;//pin for signaling and triggering
const int Pot = A5;

int Enable = 8;                              //activate motor
int PWMA = 11;                               //motor pin
int PWMB = 3;                                //motor pin
int bridgeState = 0;                         //bridge is up(1) or down(2)
int PIDdue = 0;                              //PID flag

//constants for PID calculation
const unsigned long samplePeriod = 400;//time between pid calculations in us
const long kp = 12;//proportional gain
const long ki = 3;//integral gain
const long kd = 2700;//derivative gain
const byte shift = 2;//for shifting/scaling output value
const long outMax = 200;//keep max output <=200
const byte iShift = 11;//right shift for i term
const long iMax = (1024<<(iShift+shift))-1;//keep i term contribution <=1023 and avoid windup
const int stepFrame = 1500;//Number of values for step response


//variables for PID calculation
volatile long iTerm;//summing up integral error
volatile long lastPos;//for calculation of derivative
volatile int output;//output to speedcontrol 
enum {FWD, REW} dir;

//data for position calculation


volatile byte stepState;//for edge detection on step input = D10
volatile byte inp;//for reading encoder pins
volatile byte setInp;//input signals from second encoder
volatile byte setState;//remembering setpoint encoder state
volatile long setPos;//position setpoint, updated by second pot
volatile long error;
volatile long dInput;
volatile byte sched;//for scheduling calculations

int bridgePosition = 0;

unsigned long t;//timekeeper

void setup(){
  pinMode(Enable, OUTPUT);
  pinMode(PWMA, OUTPUT);
  pinMode(PWMB, OUTPUT);
  pinMode(Pot, INPUT);
  pinMode(outPwm, OUTPUT);
  pinMode(outDir, OUTPUT);
  pinMode(signalPin, OUTPUT);
  Timer1.initialize(samplePeriod);
  Timer1.attachInterrupt(doPID);
  Timer1.pwm(outPwm, 0);
}//setup()

void loop(){
  while(1){
    if (PIDdue = 1)
    {
      bridgePosition = analogRead(Pot);
      if(bridgePosition >= 970) bridgeState = 1;
      else if (bridgePosition <= 150) bridgeState = 2;
      else bridgeState = 2;
      doPID();
      PIDdue = 0;
    }
  }//while(1)
}//loop()

void doPID(){
  /*Compute all the working error variables*/
  error = setPos-bridgePosition;
  iTerm += (ki*error);
  if(iTerm>iMax) iTerm = iMax;
  else if(iTerm<-iMax) iTerm = -iMax;
  dInput = (bridgePosition-lastPos);
  /*Compute PID Output*/
  output = kp*error+(iTerm>>iShift)-kd*dInput;
  /*if(output < 0){
    dir = REW;
    output = -output;
  }else dir = FWD;*/
  //if(output<0)
  if(output > outMax) output = outMax;
  if(bridgeState = 1){    //is up needs to go down
    bridgeDown();
  }
  if(bridgeState = 2){    //is down needs to go up
    bridgeUp();
  }  
  //Timer1.setPwmDuty(outPwm, output);
  //digitalWrite(outDir, dir);  //for direction control, doesn't work for my motor controller
  lastPos = bridgePosition;
}//doPID()



void bridgeDown(){
  PWMB = 11;
  PWMA = 3;
  digitalWrite(Enable, HIGH);
  Timer1.setPwmDuty(PWMB, 0);
  Timer1.setPwmDuty(PWMA, output);
}

void bridgeUp(){
  PWMB = 3;
  PWMA = 11;
  digitalWrite(Enable, HIGH);
  Timer1.setPwmDuty(PWMB, 0);
  Timer1.setPwmDuty(PWMA, output);
}

What does changing the set point do? Having read through the beginners PID I'm still not really clear on that

A PID regulator is what is called a 3 point regulator:

  • You have a setpoint which is the desired value, in this case the required position
  • You have a process value or actual value. In this case it is the position reported by the potentiometer on the drawbridge.
  • You have a control signal that tries to influence the process in such a matter that the difference between setpoint and process value is as small as possible, preferably zero. In this case the control signal is the direction and speed output to the motor.

The central part in the working of a PID regulator is the error value. This is simply the difference between setpoint and process value. The main part of the regulation is done by multiplying this error value by a constant and using the result as control signal out put. This is the P-part as in proportional. But using only P will render an error since the control signal will be zero if the error is zero. To remedy this the error is summed up over time (I-part for integral. An integral is nothing else than a sum, and the symbol for integrals is an S which symbolizes sums). The I part increases the precision but makes the system slower and sensitive to oscillation which is corrected by adding the difference between two consecutive errors. This is called the D-part for derivative.

So why use another pot to change the set point? isn't the tuning done by changing the K values? I must be missing something but I really want to understand

I thought for testing reasons. Incremental testing is a good way to avoid hard to find bugs. You will also need it for tuning the PID parameters. This normally done the following way.

  • Increase P as much as possible until the system starts to oscillate or overshoot. Back off a little.
  • Increase I in the same manor. Maybe you can leave a little overshoot.
  • Increase D until the response gets "harsh" and then back off a little

ok I get it now that makes sense

Also, until everything is tested you do not need any states, the direction willchange automatically. If you decrease the setpoint the bridge will lower, if you increase the setpoint the bridge will rise.

The reason I had the states is because the motor is in a full H-bridge and this motor driver can't change the direction without changing the pins. But I suppose if I keep track of the current and last pot value I can switch direction depending on which one is bigger. But the pot attached to the motor bounces around in value a lot even with a capacitor to gnd, is there anything I can do to help clean that up?

What type of h-bridge do you use? We can adapt the code that direction change is utilised on the h-bridge control pins.

The board I referenced in my second post has an H-bridge configuration where the two PWM pins must have opposite polarity to drive the motor. In their example sketch they have the pins swap to change direction and I tried to do that by changing which PWM I was applying the duty cycle to but it wouldn't drive the motor at all

It should be doable. Is it a home built bridge or a IC? If the latter, could you tell me its typenumer. If the former can you post the schematic?
The basic idea in that case is using pins 9 and 10 as outputs to the bridge, pulling one of them low and pwm the other one. Or use one as direction and invert the pwm signal as direction changes. This has to be done in the output section of the PID.