Introduction: Speed Controllers for Cheap Robots, Part 2: PID Controller
Hey! This is part two of a two part tutorial on how to make a speed controller out of a bottle cap and pen spring! If you haven't yet, check it out! If you like it, please consider voting for me in the Sensors contest! Thanks!
Even if you don't plan to make those shaft encoders, this I'ble is still a really good explanation of what a PID does and how it works. It has a gnome!
Alright! So you've made yourself a shaft encoder and you're probably ready to use it! This tutorial is going to go through all the steps you'll need to make that happen!
I don't just want to show you how to use my code though. That'd be lame. So instead, I'm going to take some time and explain the whys and wherefores and then show you how!
Let's get started!
For more Instructables on building cheap robots, please check out the For Cheap Robots collection!
For more things that I've done, you can check out my profile page!
For more info from Digilent or the Digilent Makerspace, check out the Digilent blog!
Step 1: What You'll Need
For this project you'll need the following:
- A microcontroller. I'm using my favorite, the DP32 from Digilent!
- A very high resistor. Here I've got a 47k Ohm resistor. (Or 40k? It's hard to tell if that's a purple or black band. Probably 47k. I don't think they make 40k.)
- Several wire connectors.
If you want, you can use the Motor Controllers for Cheap Robots tutorial to control your motor. However, if your motor is small enough, and you're not going to put much load on it, you can also power your motor directly from your board.
Step 2: Why Signal Processing?
Before we start actually using our shaft encoder, we need to clean up the signal we get from it a little.
Obviously, bottle caps and pen-springs aren't quite the same quality as precision manufactured optical or hall-effect encoders. The noise that we get from these sensors comes in the form of a very bouncy voltage level, which you can see in the first picture in this step.
You can see that while our sensor pin is being driven high, the voltage bounces around quite a bit. That's caused by the spring scraping over the rough metal surface of the wheel. It's really difficult to get a crisp signal this way, so it's a good thing we want to measure the length of the low signals anyway!
Speaking of which, check out the low signal. See how crisp and clean it is? It also has a very crisp starting and ending, which we can use to very precisely time how long our pin stays low! Remember back when you were cutting the strips of electrical tape for the encoder tutorial? I said that the width of each piece of tape had to be exactly the same, and this is why!
Step 3: What Do We Do About It?
The previous image was taken using the Digilent Analog Discovery, which is a useful tool for making quick measurements, with little to no fuss. But you don't need an oscilliscope to take a look at the signal you're getting from your encoders, as you'll soon see!
The image above is taken from the data logger program that you'll be using later in this tutorial. I got this graph by taking 100 encoder time measurements for each PWM signal I sent to my motor. That's 100 measurements of how long it takes my encoder sensor to go from high, down to low, and then back up to high again. Each of these different PWM signals have been graphed out along the X axis of the graph above, and you can see how fast the motor was spinning by how high each point is.
Notice how I've got almost a line of crisp, clean time measurements along the top there? That's our good data. All that mess below it? That's noise. That's what we're aiming to get rid of.
Step 4: Wiring
There are two ways you can wire this circuit.
The first, uses a FET (as in my Motor Controllers tutorial) to give the motor more power outside of the board.
The second, powers the motor directly from the board. This only works for sufficiently small motors, but it can be much faster and simpler than the FET circuit.
I should point out here that I've represented the encoder with a push-button switch, because that's pretty much what it is. However, you have to be a little careful when when connecting it than you would with a real button, so check out the next step before you wire anything!
Step 5: Always Connect the Spring to Your Sensor!
Check out the picture above. Notice how the spring has a very small surface area compared to the wheel and motor casing? Because the motor casing, the metal wheel, and whatever gearbox you have in between them, all have a large surface area, this means they can hold a lot of charge. That means they aren't as good for using as our actual sensor when it comes time to connect this to the board.
Instead, we want to connect our sensing pin to the side of the encoder that has the spring, because that will give us a much nicer signal. This is very important.
It's a simple requirement, but I spent several months struggling to get a clean signal before I figured this out. Always connect your interrupt pins to the spring.
Step 6: Taking Time Measurements
Download the Data_Logger program I've attached to this step.
(Note that I've started using Arduino instead of MPIDE. That's because I've installed the chipKIT core into Arduino. To do this, just follow the installation instructions on the chipKIT core wiki page. Alternatively, you can copy and paste this code into MPIDE.)
Go to line 20, where the PWMval constant is defined. (This line has been highlighted in the first picture of this step.)
This is the PWM value that is going to get sent to your motor. For now, make sure it is set to 255, which is the maximum.
When you load the code onto your board, the motor will immediately begin to spin, so make sure it's not going to roll away on you!
Now click the serial com button in the upper, right-hand corner of the Arduino IDE. You should see a stream of numbers like the ones in the second picture. It's okay if your numbers are much higher or much lower than these. Each of those numbers is a measurement between interrupt times, and should usually reflect how long it takes our encoder to go from the start of one strip of tape to its end.
Wait for two or three minutes so you can get enough values. You want at least 100. Then open the PWM Characterization excel spreadsheet (also attached to this step). In the Raw Encoder Data tab, copy and paste the values from the serial monitor into the column next to where it says "255", like in the third picture in this step.
Congratulations! You've just taken your first measurement!
Step 7: Finding Minimum PWM
Next we're going to find the minimum PWM that our motor will run at.
Go back to the Data Logger code, and change the PWMval to 5.
When you load the code onto your board, your motor probably won't run. Try turning it by hand and seeing if it continues to spin. Again, chances are it won't.
Keep increasing PWMval by 5 points and loading it onto your board. Do this until the motor can turn under its own power. Keep in mind that you may have to "kickstart" it by turning it manually first.
If the board turns on it's own, but then stops, it doesn't count. Keep testing until the motor can keep turning under its own power.
Once you find the smallest PWM value that the board can turn with, write that value down as PWMmin. We'll use PWMmin later in this tutorial.
Step 8: Take More Data
Starting from PWMmin, start recording data the same way that you did in step five. This can be a little tedious, but it's necessary for us to understand how our motor responds to the PWM signal it's given. After a little while, you should end up with a graph like the first picture in this step. (Ignore the blue markers. Those come later.)
Notice how I'm not bothering to collect data for the entire range of PWM values? That's tedious and not necessary. Really, you just want to get enough points on the low PWM range to get a feel for how your graph curves.
Check out the second picture. That's a graph that I made using the complete range of values. That graph took hours and most of it isn't even needed. I'm including it here so you can see the sort of graph that you would end up with if you did this.
Again, I'll point out that it's okay if your numbers are much larger or much smaller than the ones I have here. The two graphs above are actually taken from two different motors, one of which runs much faster than the other (and thus has smaller time measurements).
Step 9: How a Sliding Cutoff Works
Let's look at that complete graph again.
Notice how you can tell the good data from the noise, because there's a sort of gap between them? The data forming the line on the top is our good data, and everything under the gap is the noise.
Now check out the second graph. See how the blue line slips right between our good data and our noise? That's our sliding cutoff line. It's calculated using a very simple formula. It's this formula that we're going to find in our next step.
Now check out the third graph. By removing or ignoring all the data beneath the sliding cutoff, we suddenly have a much cleaner graph. This means our data is nothing but good data, and we can use that good data to control our motor!
Step 10: The Cutoff Formula
The formula for our cutoff is:
Max_time = [curvature / (PWMoutput - PWMmin)] + Min_time
Cutoff = k_cutoff * Max_time
The first formula is a model of the max time for each PWMoutput that we send to the motor. In other words, it's a model of the top of that graph.
The second formula takes the model of our maximum times and reduces it by some fraction. That fraction is our k_cutoff.
Step 11: Modeling Max_time
We'll start by creating that first formula..
Max_time = [curvature / (PWMoutput - PWMmin)] + Min_time
We already know PWMmin, because we recorded that value earlier in this tutorial. PWMmin becomes the vertical asymptote of our model.
Now we just need to find Min_time. Move to the Modeling Max(D_t) tab and scroll to the bottom where the maximum 255 PWM time response value is. I've highlighted it in picture 2 of this step. Round that value to the nearest thousand. For example, I rounded mine to 36000. That's your Min_time.
The hardest part of this formula to find is the value A. I've made this easier for you by making a formula that solves for our curvature value.
curvature = (Max_time - Min_time) * (PWMoutput - PWMmin)
Now we just need to pick a point to substitute for Max_time and PWMoutput. The best point for this is the highest on our graph (check out the first picture in this step). You can either find that value by switching to the Raw Encoder Graph tab, and hovering over the highest point with your mouse cursor, or you can find it at the top of the Max(D_t) column of Modeling Max(D_t). In the third picture of this step, I've highlighted my Max_time in blue, but yours may be somewhere slightly different, depending on what your PWMmin was.
(Note: Write down the Max_time that you used here, because we'll use it again in a much later step.)
Now that you have PWMmin, Min_time, and curvature, enter those values into their respective fields (highlighted in red). You should see the red crosses in the graph to the right jump into a curve, representing your Max_time function.
Step 12: Checking Your Model and Finding K_cutoff
Go to the Finding k_cutoff tab. Change k_cutoff (highlighted in green in the first pic) to 1. Now go to Raw Encoder Graph tab. Remember the blue data points I said would come later? That is the result of the second formula:
Cutoff = k_cutoff * Max_time
Right now, you want to make sure each of the blue points is at or above the top of your measured values (as in the first picture).
To find a good k_cutoff, go back to the Finding k_cutoff tab, and change k_cutoff to 0.50. Now check the Raw Encoder Graph again. You want to adjust k_cutoff so that all the blue points are above your noise, but below your good data (as in the third picture in this step). For this data 0.50 works just fine, but you may need to adjust your k_cutoff a little more.
You want k_cutoff to be as low as possible, but you also don't want to let any of the bad data through, so I usually add a buffer of about 0.02 or 0.03.
Step 13: Checking Your Sliding Cutoff
Now that we have k_cutoff, curvature, PWMmin, and Min_time, we can create and test our sliding cutoff formula.
Download Data Logger 2, and change the highlighted values to the ones you've just found.
Now, repeat the data logging steps, but this time input the data into the Filtered Encoder Data tab.
Once that's done, check out the Filtered Encoder Graph tab. You should see a graph similar to the one in picture 3 of this step.
It's worth noting that in picture 3, I have a few noise data points left over after my filter. In this case, I just increased k_cutoff by 0.02 or 0.03, and got rid of these.
Step 14: Finally! Setting Up Your Speed Controller!
Download the PID Speed Controller code. Before we run it, we've got to adjust some values.
In the second picture, PWMmin, Min_time, k_cutoff, and curvature are all the values you've just finished calculating. The only thing outstanding is Max_time.
If you recall from the Modeling Max Time step, I asked you to write down the Max_time value you used there. Theoretically, this will be the maximum time that any of your encoder readings will return. That's the value you want to use here.
Step 15: The Moment of Truth!
Now! With your code properly set up, you can upload it to your board and test it out!
By turning the POT onboard the DP32, you should be able to vary the speed of the motor. It's actually surprisingly responsive!
Unfortunately, there is some strange behavior that happens towards the low end. Check out the second gif in this step. When I turn the pot too low, the motor can't hit the desired value accurately enough, so it continually speeds up and slows down.
Step 16: The Control Loop
On a top level, our controller works like this:
- Measure the current speed.
- Error = current speed - target speed.
- Feed the error into our PID.
- Get a corrected PWM from our PID.
- Output the corrected PWM to our motor.
- Repeat from top.
The key part of this function is the PID. To best understand how a PID works, I'm going to break it apart into components, namely the Proportional, Integral, and Derivative controllers. I won't go into too much detail on how a PID works, but I'll do my best to explain how it works in the context of the speed controller you just used.
Step 17: The P Controller
The simplest way to understand a proportional controller, is to think of it as a sort of spring.
Imagine you've got a ball on a spring. If you just let it sit, that is its neutral position. If you push it down a little, you could feel the spring pushing it back with some force. The more you push the ball away from its neutral position, the harder the spring tries to push it back.
This force that the spring is applying is called a "corrective force". The strength of our spring's corrective force is proportional to how far from it's neutral position we push it.
If you look at the speed controller code, you can find the line where we calculate the P value for our PID (highlighted in the second picture above). In that line, we take the difference between our motor's current speed and the speed we want (stored in the PIDmemory array) and multiply it by some constant. This is our "corrective force".
Of course, springs are problematic. Let's go back to our ball and spring. Imagine what would happen if you pushed the ball out of its neutral position, and then let go. In this case, the ball and spring would bounce back and fourth, and take forever to settle down!
If we only use a spring, we would have a very poor system. Similarly, if we only use a P controller, we would have a very poor controller.
Step 18: The D Controller
Just trying to use a spring (or just trying to use a P controller) is no good, so what do we do?
Let's go back to our ball and spring example and add a piston to it.
Pistons work by forcing air to pass through a small opening. The faster you try to make them compress or expand, the more air you're trying to force through the small opening, and the slower the piston wants to move.
Check out the third picture in this step. If we pair the right spring with the right piston, the result is a smooth approach towards the ball's neutral position. That's more like the behavior we're trying to achieve!
But how does this apply to our speed controller?
Go into the speed controller code and find where we calculate our D value (highlighted in the fourth picture above). Those two lines take the error from the previous update and subtract it from the current! This means that if we're approaching our desired speed too quickly, then D grows more negative, and slows us down!
Step 19: The I Controller
So that's pretty much it, right? If a PD controller will get us the behavior that we're trying to achieve, then what's the point of adding an Integral controller as well?
Fact of the matter is, most controllers are PD controllers. Even when a full PID is implemented, the effect of I is usually very small, but that effect can also be very important.
Unfortunately, integral controllers aren't as easy to explain as they don't always have a physical counterpart. So we'll set aside our ball and spring for the time being. Instead check out the illustration for this step.
That's Ivan. Ivan is very impatient, and if you take too long to get where you're going, Ivan will start pushing you and speeding you up. The longer you take, the faster Ivan goes.
That's kind of what an integral controller does. It keeps track of past errors, and sums them. Therefore, as the error accumulates, the integral controller pushes harder and harder.
Now check out the code for the speed controller. The way we calculate I is a little more complex than P or D. First, we sum all the error values in our memory. Then we take that number and scale it by a constant for our I value.
Step 20: PID Constants
Now that we have our P, I, and D values. We add them together to get our PID output. That gets added to the previous PWM output, to make the new output (in the next line), which then goes to our motor!
However, as you may recall from the previous three steps, the formulas for all three were multiplied by k_P, k_I, and k_D. What are these constants and what do they do?
If you scroll back up towards the top of the speed controller code, you'll find where these constants are defined. They're used to scale the magnitude of P, I, and D, so that we can control how much influence each one has on the corrected PWM output.
As you can see, k_P is much larger than k_D. This is pretty typical. But wait, k_I is zero! Doesn't that mean I has no effect? Doesn't that make this a PD controller?
You're absolutely correct! k_I is notoriously difficult to tune, and like I said before, most of the time a simple PD controller will suffice, so in this case I left it out. Feel free to add k_I to the mix to see what happens! Just know that usually k_I will be significantly lower than both k_D and k_P!
Step 21: And That's It!
That's the completed speed controller!
I know this tutorial has taken much longer than most of my tutorials, but I wanted to make sure I did a thorough job of explaining both the sliding cutoff data filter and the the PID controller.
Now you may understand why I was so excited for my shaft encoder project! It opens up exploration of not only data processing, but also PID controllers, which are the most predominant control system in the world! (And seriously cool to boot!)
Not only that, but speed controllers and PIDs have some neat applications to robotics, allowing more refined movements and more sophisticated behaviors! I hope to have another tutorial up soon to explore those possibilities, but for now I hope you enjoyed this tutorial and I'd love to see what you do with it!