Introduction: Speed PID Using Digilent Gear-Motors and Encoders!
This tutorial will take a few steps ahead and show you how to take advantage of the shaft encoders built into these motors by programming a PID speed controller!
I've done a PID before in my For Cheap Robots series, but that used encoders made from bottle caps. That meant that a lot of effort went into cleaning up our signal so we could use it. Because I used real encoders for this project, I was able to focus more on the PID, and getting that code functioning smoothly.
To that end, I'll be taking you through the process of characterizing your motor and setting up a PID for it!
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 will need pretty much exactly what you used in the HB5 tutorial!
- A couple of Digilent gearmotors (I'm using the 19:1 gear ratio, but they also come in 53:1 for applications that need higher torque and less speed).
- The Pmod HB5 and 6-pin Pmod cable.
- A DP32 (with my beloved bread-board modification).
- A battery pack and several jumper wires.
You're also going to need a computer with the Arduino IDE and chipKIT core installed.
You can, of course, use any of Digilent's microcontroller boards, and there are even some that use headers designed specifically for use with Pmods, but keep in mind that I'm using the DP32 because it has a built-in pot and buttons that I use for controlling the motor in various ways.
Step 2: The Circuit
If you don't know how to wire the HB5, switch over to my Getting Started with the HB5 tutorial real-quick and familiarize yourself with it. Our circuit is going to be very similar, with the addition of two jumpers connecting SA to Pin 3, and SB to pin 9.
1 DIR -------------- Pin 7 (RB14)
2 EN --------------- Pin 6 (RB13)
3 SA --------------- Pin 3 (RB09)
4 SB --------------- Pin 9 (RA00)
5 GND ------------ GND
6 VCC ------------ 3V3
Step 3: Motor Characterization: the Code
Before we begin working with our PID, we need to know a little bit about our motor. PIDs work very well over the range of speeds that a motor can produce, but they tend to have trouble around the ends. That is to say, if you try telling your motor to go too fast or too slow, you can start getting weird behavior.
We are going to use the PWM_Characterizer code (attached to this step) to figure out what our motor's limits are.
Step 4: Motor Characterization: Operation
Assuming you downloaded the code from the previous step, load it onto your board and open up the serial monitor.
At first, you shouldn't see anything. Use a screwdriver or your thumb to adjust the onboard pot and you should see some messages pop up.
The pot controls the PWM increment. If you turn the pot all the way clockwise, it sets the PWM increment to 100.
The onboard buttons (BTN2 and BTN3) increase or decrease the PWM signal being sent to our motors. If you press BTN2 now, it will start sending a PWM signal of 100 (a little bit less than half-power) to our motor, which will start it moving.
When you press either of the buttons, you'll see a message with the new PWM output (under "Current"). You'll also see fields with the average, min, and maximum interrupt period (as seen by the board). These will be set to 0, 4294967295, and 0 (respectively). If we wait a little (one to three minutes) while for the board to gather data, a second message will appear that has these fields filled out using the data gathered by the board!
In this way, we can not only control precisely what PWM signal we send to our motor, but also get feedback about how fast it's going!
Step 5: Motor Characterization: Fastest Speed
With our PWM_Characterizer working, we need to start measuring the speed our motor can achieve.
Twist the pot all the way clickwise again, and hit BTN2 three or four times. That will maximize the PWM signal being output to the motor (should be 255), causing it to spin at it's maximum speed.
After a little bit of waiting, you should get a message like the one above. You'll want to write down the histAvg as PERIOD_MIN. We'll use this later.
Step 6: Motor Characterization: Slowest Speed
Unfortunately, finding the slowest speed isn't as easy.
You need to find the lowest PWM setting that causes the motor to go from stopped to spinning. It's important to distinguish this from the lowest PWM setting possible, because once the motor is spinning, you can decrease it a considerable amount before it will stop spinning. This is because of friction inside the motor and gearbox, which make it harder to start a motor spinning than to keep it spinning.
In order to find our lowest PWM, we'll want to use the pot to fine-tune our output. I like to start out with increments of 10, and go up until my motor starts spinning. Then I back off until it stops again, and use increments of 1 to get closer.
Once my motor starts spinning again, I wait for my board to gather the data I need. Then I write down the average period as PERIOD_MAX.
Step 7: Motor Characterization: Math
Now that we have measured the interrupt period for our motor's slowest and fastest speeds, we're ready to do a little math. We'll start by calculating our speed max and min.
SPEED_MIN = 1 / PERIOD_MAX
SPEED_MAX = 1 / PERIOD_MIN
Remember that with periods, max and min are switched. PERIOD_MAX -> SPEED_MIN.
Next, we'll calculate the speed range.
SPEED_RANGE = SPEED_MAX - SPEED_MIN
Save all these values, because we'll use them later in Speed_PID8.
Step 8: Intro to Speed_PID8
Download Speed_PID8 from this step, unzip it, and open it up. There are a few things you'll need to change before we can get it running.
When you first open Speed_PID8, you'll notice that it has a second tab called utility.h. This files stores all the constants we'll use for our PID. Clicking on the tab will open it up, and you can change those constants.
Near the top, under "Debugging Constants", are all the constants that control our debugging features of this PID code. Setting DEBUGGING to "true" will turn on debugging for the system, allowing the other debugging code to work. Then, setting any of the other debugging constants to "true" will turn debugging on for that function.
Please note, that only one function's debugging should be set to "true" at a time, otherwise the messages will get confusing.
Beneath that we have our ISR constants, PWM constants, Speed constants, and Period constants. The ISR constant ENCODER_DIRECTION will reverse the direction your ISR sees as "forward". If the direction your ISR thinks is forward, isn't right, your PWM will behave erratically.
PWM_SETTLE_TIME doesn't really need to get messed with.
SPEED_MIN, SPEED_RANGE, and PERIOD_MAX are all numbers that you have calculated just previously! Fill them in!
Finally, we have our PID constants, K_P, K_I, and K_D, which we'll use for tuning our PID. Beneath that we have our pin assignments, which you'll want to change if you're using a different board than I am!
Step 9: How It Works
The chart above shows both the generic, ideal PID, and how our PID is actually built.
It works as follows:
- Some external system sets a target value.
- This value can change over time. If you want the motor to spin faster, you set the target faster.
- We measure the actual output of our system using a sensor.
- In our case, this means measuring the speed of our motor with our shaft encoders.
- We subtract the measured value from the target value to get an error value.
- This error value gets put into our PID.
- The PID then generates a NEW value, that gets fed into whatever our output system is.
- In our case, the output system is our motor, the H-bridge that controls it, and the code that controls the H-bridge.
- Then the process repeats!
For example, let's say we want to set a speed of 200:
- We set the target to 200.
- We do this using the on-board pot and buttons.
- It's the same system we used previously.
- The pot sets the increment (1, 10, or 100), and the buttons add that to, or subtract that from, the target.
Of course, how the PID goes from an error to some corrected output is a little more complicated. I wrote an overview of how PIDs work in my Speed Controllers for Cheap Robots: Part 2, and I encourage you to check that out.
Step 10: Tuning Your PID: Getting the Data
At this point, you can load the Speed_PID8 code to your board, and start running it! In general it should work just fine, but it might need a little tuning.
If you decide to tune the PID, go back into utility.h, and change DEBUGGING and PID_DEBUGGING to true. This will generate some data that looks overwhelming, but is actually very useful.
Start by turning your POT to a setting of 100, and opening up Serial.You should get a stream of numbers, don't worry about these for now. Press BTN2, to set the target to 100, and wait until the motor has finished speeding up. Then press it again, increasing the target to 200, and wait for the motor to settle. Once it has, press reset.
Now you can copy and paste all that data into a spreadsheet, like Excel or Google Sheets.
Step 11: Tuning Your PID: Graphing the Data
Once you have your data copied into excel, we'll want to make some edits so it's easier to work with.
In the first picture, you can see that I've moved each data-label to the top of its column. You'll want to do that as well.
Next, select all your data. You'll want to use a shortcut like ctrl-A, because there will be too much to make selecting it manually practical.
Then, go to Insert -> Scatter -> Scatter with Straight Lines. This will automatically format and insert your graph for you!
I would recommend always adding a title to your graph with your settings for K_P, K_I, and K_D. This will help you out a great deal when you're comparing one tuning to another, because you won't have to keep track of which tunings you've already tried.
The most important lines to look at here are the blue line (target_speed) and red line (ISR_speed or PEU_speed, they're the same thing, don't worry). For the most part, you shouldn't see much of the blue line, because ideally our ISR speed will equal our target speed, and the red line will completely cover up the blue line.
A well tuned PID will have a quick rise to meet the target value, followed by a little bit of overshoot, and finally it will settle into a steady speed, approximately equal to our target speed. For contrast, I'll show you a few graphs of PIDs with problems, and tell you how you can fix them!
Step 12: Why PI?
Before I get into how to tune your PID, I'd like to take a minute, just sit right there, I'll tell you how I came to use a PI controller, instead of a PD controller. It has to do with the graphs above.
In order to improve the performance of my PID, I used a slightly different architecture than in my previous tutorial. Whereas previously, the P value would eventually raise the output until there was no error, this configuration leaves P with some constant error, called "steady state" error.
In the graphs above, you can see that our output (the red line) doesn't ever meet our target (the blue line). Even if we crank KP up to very high values, we still have some steady-state error (and some weird oscillations to boot).
That's why we have to use our I component to eliminate this steady-state error.
With that covered, I'll show you some graphs that can help you tune your PI controller.
Step 13: Common PI Issues.
Each of the graphs above has one common PID issue or another. For this, I've focused on tuning my P and I components, and I've ignored the D. That's because, in this case a PI controller is better than a PD controller.
In the first graph, you can see that my K_P value was much lower than the ideal tuning we saw in the last step. My ideal tuning is about 1, and for this graph I've set it to 0.1. This results in a bit of oscillation, because our P value can't help correct the error enough, and our I value has to push harder to compensate. Though I is able to correct for the error, it pushes so hard that it takes a while to compensate, hence the oscillation.
In the second graph, however, you can see what happens if K_P is too big! In this case, we have a huge overshoot, caused by our P value overshooting our target value! In this case, our I value has to compensate, not for steady-state error, but for P itself!
In the third graph, you can see what happens when K_I is too low. In this case, we see a slow rise up to meet the target, and no overshoot. You want a little bit of overshoot, because it means that the output will settle more quickly.
In the fourth graph, our K_I is too high, causing some oscillations before settling.
Step 14: The Full PID
At this point, your PI controller should be working well! I encourage you to try adding a D term, and see what effect it has. D is arguably the most subtle of all these terms, acting as a damper to movement instead of accelerating it.
Unfortunately my PID setup still isn't perfect, so I suspect that the D term won't have the full effect that it's meant to.
Step 15: And That's It!
I've got a project in mind that requires really well designed and highly tuned PID controllers, so I'm going to keep coming back to them until I've got that really nailed down. You can expect to see further updates from me in the future as this project evolves!
I hope this was helpful!