Showing posts with label PID. Show all posts
Showing posts with label PID. Show all posts

Friday, November 13, 2009

Arduino PID library in the playground

I just found out that user br3ttb created a very useful looking PID library for the Arduino.

I had considered turning my hacked together code into a library, but never got to the point where I thought I could pull it off.

This should definitely make it easier to test various physical setups as it will standardize the software to a great extent. I can't wait to try it out with a real project. I even have one!

Monday, November 03, 2008

Arduino and motor control part 2


arduino linear positioning with an ink jet printer carcass from Josh Kopel on Vimeo.
I spent a bit more time messing with the ink jet guts, and finally had some success getting the linear print head positioning to work. The first challenge was that the motor kept hanging up and moving in big jerks. After burning my finger on the Arduino's voltage regulator, I finally realized that routing all the power through the Arduino was the problem. Now I am running the Arduino off of the USB cable, and I hooked a random 13.5 volt wall wart up directly to the power terminals on the motor control board. Once I got enough torque out of the motor it works quite well.

I am getting a measure of the upcoming challenges though. For one I need to add at least one limit switch so that I can establish a repeatable zero position. That should be fairly easy.

I think there may be a bigger problem dealing with the interrupts from the encoder circuit. When I try to get any feedback from Serial.print() inside my loop{} it hangs up and stutters out only an occasional message. The issue seems to be that the encoder is too precise! The encoder strip has 200 line per inch rulings, and I am currently triggering an interrupt with each 0 ->1 and 1->0 transition. This means it is generating interrupt hits 400 times for each inch it travels, and I think the Arduino is never able to get through a complete loop execution without being interrupted (I can sympathize).

I am going to try just using one of the edge transitions (i.e. low -> high) and see if that helps. It may mean that the code needs to handle the rapid interrupts more gracefully. I can see this being with a very tight loop while the motor is seeking, and then a detachInterrupt() and power down (or set to shorted out braking) while it does anything else.

In the mean time it is fun to zip the motor back and forth.

Saturday, October 25, 2008

Using a DC motor as a servo with PID control (part 1)


PID motor control with an Arduino from Josh Kopel on Vimeo.

MORE HERE IN PART 2

EVEN MORE HERE

I found that the newer ink jet printers often use a combination of a DC motor and an optical encoder to take the place of stepper motors for print head positioning (linear motion) and paper feed (rotary positioning). The pieces often come out (after some effort) as a whole, and they seemed like a natural to experiment with.

The motors themselves are pretty nice if you just want to make something move and control its speed, and in combination with the encoder, I thought I could use them as a free alternative to a hobby servo. I knew there had to be some information out there about using a feedback loop to position a motor, and of course there was. It turns out that the classic way of doing this is through a PID servo controller (Proportional, Integral, Derivative). At its simplest this method can be thought of as a way to use the difference between where the motor should be and where it is (error), plus the speed at which the error is changing, plus the sum of past errors to manage the speed and direction that the motor is turning.

Ok, so I have to say at this point, that I am not an engineer. I did take an engineering calc. class or two, but that was more then 25 years ago, and I never much understood it even then. So all I really have to go on is my intuition about how this is working, a few good explanations, and some example code. As always , Google is your friend. If you do have a good understanding I would welcome your comments!

The first resource I found was this exchange on the Arduino forums and then followed through to an article that really explained what was going on here at embedded.com.

After reading the embedded.com article a few times I put together some simple code, and was amazed to find that the thing worked almost perfectly the first time.
The key elements of the code look like this
int val = analogRead(potPin); // read the potentiometer value (0 - 1023)
int target = map(val, 0, 1023, 0, 3600);// set the seek to target by mapping the potentiometer range to the encoder max count
int error = encoder0Pos - target; // find the error term = current position - target
// generalized PID formula
//correction = Kp * error + Kd * (error - prevError) + kI * (sum of errors)

// calculate a motor speed for the current conditions
int motorSpeed = KP * error;
motorSpeed += KD * (error - lastError);
motorSpeed += KI * (sumError);
// set the last and sumerrors for next loop iteration
lastError = error;
sumError += error;
What is going on here is:
1. read a value from the potentiometer through an analog to digital input (0-1023)

2. map that value so that it lives in the range from 0-3600 (the count from one full rotation of the motor). We need to scale like this so we can make an apples to apples comparison with the current position reported by the optical encoder. The current position is being calculated in another function (an ISR) whenever an interrupt is generated by the optical encoder (more on what this is all about here).

3. create an error term by subtracting the value we want to go to (target) from where we are (encoder0Pos)

4. multiply the error by a constant (proportional gain) to make it large enough to effect the speed. This is the Proportional term, and gives us a simple linear speed value. Basically it says that the further we are from where we want to be, the faster we need to go to get there. The converse is true, so that we will slow down as we approach our destination. Of course, if we are going to fast to begin with, we may overshoot. Then the Proportional will switch signs (from - to + or visa versa) and we will back up towards our target. If the speed is really off then we end up zooming past many times and oscillating until we (hopefully) get to the target.

5. add the difference between the error NOW and the error from the last time around. This is a way to figure out how fast the error is changing, and is the Derivative term of the equation, and you can think of it a bit like looking into the future. If the error rate is changing fast then we are getting close to where we want to be FAST and we should think about slowing down. This helps dampen the oscillation from the position term by trying to slow us down faster if we are approaching the target too quickly. Again, we multiply the value by a constant to amplify it enough to matter.

6. lastly add in the sum of all past errors multiplied by a constant gain. This is the Integral term, and is like looking at our past performance and learning from our mistakes. The further off we were before, the bigger this value will be. If we were going too fast then it will try to slow us, if to slowly, then it speeds us up.

7. set the lastError to the current error, and add the current error to our sum.

The resulting motorSpeed value will be a signed integer. Since the Adafruit controller takes an unsigned int as its speed value I do a little more manipulation to figure out the direction and get a abs() value for the speed, but that part is all pretty easy.

As I said, it seems really simple for such a complicated process. It turns out that the geared motor from the ink jets is much easier to control that some other mechanical systems. The physics of its gears, and the performance curve of the simple DC motor mean that you can be pretty far off on your tuning and it will still end up in the right place (eventually). If we were trying to do extremely precise positioning, or make it all work really fast, or handle huge inertial loads, then it would need to be a lot more robust. I am still working on "tuning " the configuration (i.e. slowly changing the 3 constant parameters one-by-one until it works best) and I will write more about that later.

In the mean time here is the code [SORRY! It looks like the code has vanished, and I cannot find a copy. DO NOT DISPAIR! Instead go here http://www.arduino.cc/playground/Code/PIDLibrary]. Bear in mind that it is designed for use with the Adafruit controller and library. You will also most likely find that it does not work well with the constants I have chosen*. In that case you will need to do some tuning yourself. Stay tuned (hah) and I will show you how I did it.

*discerning readers of code will notice that the Kd constant is 0 meaning that this is actually a PI controller. I found that while the Derivative made the motor settle into position faster, it also injected an enormous amount of noise and jitter. This is due in part to the cheap, noisy, and non-linear potentiometer I am using. I need to do some more experimenting before I can really call it a complete PID.

Oh yeah, if you are taking apart an ink jet you may find these Optical Encoder datasheets to be valuable. These are specific to Agilent parts, but I have found they all share pretty much the same pin-outs.
http://pdf1.alldatasheet.com/datasheet-pdf/view/164530/HP/HEDS-9710.html
http://pdf1.alldatasheet.com/datasheet-pdf/view/163090/HP/HEDS-974X.html