I can really use a nudge here as I seem to be out of stuff to try. I'm in (what I thought/hoped were...) the final low level tech stage of this large XY plotter project (2 DC gearhead motors with encoders, read by interrupts; speed control through PWM of H-Bridges, synchomesh cable for positive force).
My problem is getting accurate positioning- no matter what I've tried I get large positioning errors (in the hundreds of steps). I've checked and rechecked the encoder code, etc. There isn't a huge amount of slop in the rig and I am using positive braking through the H-Bridge. There just seems to be enough momentum that when the target position is reached and the motors stopped it just rolls past the target point - alas, not predictably.
I've tried some ramping code (snippet below) to try to slow down as the target point is reached but that hasn't helped much. Also have searched high and low for motion profile code examples without much luck. One critical thing is that the motor stall point with PWM is @ 75 cycles and at @ 120 cycles the rig wizzes- so I don't have much resolution for slowing down.
Anyway - wondering if anyone has done a project like this and has any tips. fwiw - I tried steppers initially but wasn't getting the torque/speed I wanted - though I guess there are much more powerful stepper out there ($$$).
Here's my feeble ramping code. The idea is I calculate half and quarter way points and then control speed based on the current encoder position. The checkStall() function compares the current encoder position to a previous position.
I suggest you consider using PID to control your motors so that they settle optimally quickly at their specified positions. This is basically the quintessential PID application: you have a closed loop with some direct measure of your error and a desire to drive that error to zero as quickly as possible (i.e. critical damping).
When you stop the motors do you just remove the voltage or do you short out the motor. Doing the latter allows flywheel breaking to take place and that stops the motors quickly.
Yes, I short out the motor to add some drag, and that does help but it still overshoots by too much. I can live with some small errors but I am still getting too much.
About PID - I had looked at that before when researching motion control but frankly the math scared me off. I came across some Arduino PID code though so I am going to try to tinker with that.
correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)
Error is the distance you are from where you want to be. Correction is the correction you apply to your motor speed based on the error. It's up to you to choose the appropriate proportional constant Kp, derivative constant Kd, and integral constant Ki for your particular application. Note that you might be able to get by with just Kp, or maybe just Kp and Kd. I only very rarely use the integral term, but then again the integral term serves to help the system settle at zero when it otherwise might not from just proportional and derivative alone.
Or you could take a simpler approach of successively refining your position. Do what you're currently doing, but then afterwards add in a correction stage that moves the motor at a fraction of it's normal speed so that you get closer to your target without overshooting. I'm not sure if you would have time for this type of correction, but presumably the motor wouldn't have very far to move in the correction stage.
The math can be fairly simple:
correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)
thanks! The whole PID maths thing scared me off but I've been tinkering with it for a day or two (trying to tune it)) and it looks like it will work - though tuning is a bit tough.
I've seen some PID routines that use P + I - D and some use P + I + D. Not clear why the difference.
I've seen some PID routines that use P + I - D and some use P + I + D. Not clear why the difference.
If you get the sign of D wrong your control loop will be unstable. The whole point of D is to correct less when you notice that your error is getting smaller (so that you don't overshoot) and to correct more when you notice your error is getting bigger (so that you can head off the problem faster). If you correct more as your error is getting smaller, you aren't helping yourself. The routines that use -D probably cancel out the minus sign by computing the derivative as (last - current) rather than (current - last).
hey thanks - I stumbled across that link a while back but didn't understand enough at the time to know what it was about. I actually am running out of steam on this part of the project and want to move on to the more fun parts. I have a PID working that is fairly tuned - at least I am deciding that it's "close enough for art". In some next version/iteration I'll give it another try.
I did see this in the FAQ @ that controller:
Q: What kind of motors can be controlled by my UHU ?
A: The simple answer is: For brushed DC motors. The details are a bit more complicated.
What you need is a motor with an incremental encoder. There is no support for old style tacho generators.
I am using tacho encoders on my rig (they are attached to the Maxon 24v motors I am using). I guess I thought they were incremental encoders (?) They are 2 channel, 100 counts per turn.
[
If you get the sign of D wrong your control loop will be unstable. The whole point of D is to correct less when you notice that your error is getting smaller (so that you don't overshoot) and to correct more when you notice your error is getting bigger (so that you can head off the problem faster). If you correct more as your error is getting smaller, you aren't helping yourself. The routines that use -D probably cancel out the minus sign by computing the derivative as (last - current) rather than (current - last).
typedef struct
{
double dState; // Last position input
double iState; // Integrator state
double iMax, iMin;
// Maximum and minimum allowable integrator state
double iGain, // integral gain
pGain, // proportional gain
dGain; // derivative gain
} SPid;
double UpdatePID(SPid * pid, double error, double position)
{
double pTerm,
dTerm, iTerm;
pTerm = pid->pGain * error;
// calculate the proportional term
// calculate the integral state with appropriate limiting
pid->iState += error;
if (pid->iState > pid->iMax) pid->iState = pid->iMax;
else if (pid->iState
<
pid->iMin) pid->iState = pid->iMin;
iTerm = pid->iGain * iState; // calculate the integral term
dTerm = pid->dGain * (position - pid->dState);
pid->dState = position;
return pTerm + iTerm - dTerm;
}
I've been testing/tuning my rig and it seems like it's close (enough...). A huge thanks for explaining/steering me back to the PID stuff! It actually has begun to make sense to my artist brain.
This code isn't multiplying Kd (what he calls dGain) by the change in the error, he's multiplying it by the change in position. My assumption is that the change in position is the same as the change in error, but in the opposite direction. For example, if you are at position 1000, your error is -1000 since what you want is:
position + error = desired location (usually 0)
This means that position = -error, so d(position)/dt = -d(error)/dt.
I've been testing/tuning my rig and it seems like it's close (enough...). A huge thanks for explaining/steering me back to the PID stuff! It actually has begun to make sense to my artist brain.
I'm very glad to hear it's working out for you! Good luck with the rest (fun part) of your project. (Is it sad if I find PID to be a fun part?)
I'm very glad to hear it's working out for you! Good luck with the rest (fun part) of your project. (Is it sad if I find PID to be a fun part?)
Ben
Not at all (and seeing the PID implementation making my rig correct position errors is pretty cool!). It's the desperate poking for too long at a code or electronics problem I don't understand that sucks the fun out of it- like a badly tuned learning PID.... endlessly oscillating...