Encoder Project Introduction
Our project involved adding an encoder component to the MAE 148 Autonomous Vehicle we previously built. The motivation for this project was to have access to data concerning the speed and distance travelled in order to improve speed control.
The original configuration of the vehicle controlled the speed of the car by controlling the voltage from the battery to the motor. This voltage was defined based on data from user inputs(explain in step 1). Adding the encoder allowed the voltage to be based on data concerning the actual current speed, giving the vehicle cruise control capability.
The first challenge our team had to face with this project was mounting the encoder. Once that was completed, we had to establish communication between the encoder and the Raspberry Pi board that served as the car's onboard computer. Since the encoder recorded data at a much faster rate than the Raspberry Pi could read, we needed to add a component between them to slow things down. We decided to use an Arduino Uno to accomplish this and perform a few other tasks, including displaying the speed or distance travelled. Once the communication was set up, it was time to add the PI controller to the code already on the Raspberry Pi to accomplish our project's goal of speed control with feedback.
Step One: Build the Autonomous Vehicle
Acrylic Board: We used calipers to measure the dimensions of the mounts on our RC car base and then laser cut an acrylic board with holes to attach camera mount and serve as a platform for all the electrical components.
Camera Mount: We designed and 3D printed a mount to hold the camera at the appropriate height and angle to capture the track. We included a slot to pass the cord band through to the Raspberry Pi without bending it.
Raspberry Pi Holder: We designed and 3D printed a tray to house the Raspberry Pi and attach it to our car.
Wiring: We wired together and mounted all the electronic components provided in our kit of parts. This required figuring out the pin outs of every part, soldering, and cable management. Then, we clearly documented our wiring in a wiring schematic.
Ubuntu or MobaXterm on our laptops were used to communicate with the Raspberry Pi. These were required because the communication had to be secure shell. We set up the DonkeyCar Framework provided by our professors to be able to calibrate and control the car with a PS3 controller, as well as collect data for autonomous training.
The class had an indoor and outdoor track set up on campus with tape on the floor. We drove about sixty laps around each track using the PS3 controller with the camera recording. Then, we used given commands to connect to UCSD's GPU Cluster and send over our images to train a model for the car to drive autonomously.
We completed the class goal of driving 10 autonomous laps on each track on schedule.
Step Two: Mounting the Encoder
We decided to mount the encoder on the bottom of the car next to a gear spun by the DC motor. First, we laser cut a gear for the encoder to mesh with the existing one. Then, we designed and 3D printed a mount that would attach the encoder to the car and keep the gears together. The encoder came with M3 holes on it's front face, so the printed mount incorporated clearance holes for those. Since it was too difficult to exactly measure where the holes for the attaching the bottom of the mount to the car needed to go in order to have the gears meshing well, they were made after the fact with heat set inserts. Finally, the gear was attached to the encoder with glue.
Step Three: Ardiuno Communication
The encoder used sends 600 pulses per revolution. This resolution is amplified by gear ratios, so every turn of the wheel causes approximitely three revolutions of the encoder. Additionally, four readings can be taken for each pulse from the encoder. Each pulse gives two square waves, each with readings from the rise and fall. Therefore, the encoder records 27400 data points per meter travelled, meaning at a maximimum velocity of approximately four meters per second, it records at 109.6 kHz. Meanwhile, the Raspberry Pi used for the remainder of the car's control operates at only 20 Hz. For this reason, we could not use the Raspberry Pi to directly read the response from the encoder. Instead, we used an Arduino Uno board to read and count the pulses of the encoder, calculate the distance travelled and current velocity, and send this information over serial to the RPI and an LCD display at a rate of 20 Hz.
When connecting the encoder to the Arduino, we used the digital pin inputs and configured Arduino's built-in pull up resistor. Then, we wrote a code using the attachInterrupt function to signal a change whenever either the A or B phase signal switched from HIGH to LOW or LOW to HIGH voltage. This provided maximum resolution and accuracy because the interrupt saves time and ensures that signals are not skipped.
Once the Arduino code was working, we calibrated it manually by driving the car 1m ten times and averaging the number of counts recorded by the Arduino. This gave us a value of 27400 counts/meter, or an accuracy of 0.0365mm.
Step Four: Developing PI Control
Creating a Proportional Model
As previously stated, the purpose of adding the encoder on our car was to create a closed-loop feedback system to control the velocity of the vehicle as it drove. Prior to installation of the encoder, velocity was controlled indirectly via the throttle, or power provided to the motors from the PWM. However, environmental variables such as the slope of the road, changing friction, or other disturbances made this method of velocity control inaccurate. For example, the car would clearly need more power to maintain its speed while driving up a hill, but it cannot sense the slope of the road it is on, so a closed-loop system could take that into account. The encoder could measure the car's actual velocity, and the controller could compare it to the desired velocity and use the error to adjust the throttle.
Initially, we derived a formula just for proportional control. We calculated the approximate gain value, G, defined by v(t)=G*T(t), where v=velocity (m/s) and T(t)=throttle (range 0-1). At full throttle, our car had a maximum velocity of approximately 4 m/s, so we set the gain to G=4. Then, we needed to develop a value for the proportional coefficient, Kp, defined by the equation T(t)=Kp*e(t), where e(t)=SP-v(t) and SP is the "set point" or desired speed of the car. The maximum value of e(t) occurred when SP was set to the maximum velocity and the car was started from rest, making e(t)=4 m/s. Therefore, to avoid exceeding a throttle of 1, we set Kp=1/4, which means Kp=1/G. However, only using proportional control means that the car velocity settles at SP*KG/(1+KG)=SP/2. This could be easily solved by using a SP value of twice our desired speed, but if another steady disturbance arose, the proportional control would not be able to correct this persisting error. Therefore, to increase the stability of our control system, we decided to add integral control.
Stabilizing With Integral Control
When upgrading to a PI controller, instead of finding the throttle simply by multiplying the error by the proportional coefficient, Kp, we used the error value to adjust the previous throttle value with each loop of the program. Therefore, instead of T(t)=Kp*e(t), we used the equation T(t)=Kp*e(t)+T(t-1). This change stabilized the system so that even with large disturbances, the velocity would settle to the SP value without causing amplifying oscillations.
To be sure that this equation would work the way we had hoped, we decided to model the system using MATLAB. Like our Raspberry Pi program, MATLAB runs in discrete time, not continuous, which makes it an accurate simulator. We created the program so that with each loop, it would calculate the new throttle using the equation T(t)=Kp*e(t)+T(t-1). However, when modelling the motion we incorporated a delay of one loop, or 0.05 seconds, allowing time for the new velocity to take effect. The wait time was based on the fact that our arduino only updates the RPI with the encoder data at a rate of 20 Hz. Additionally, we ran the program with random disturbances of the velocity to simulate environmental factors and show that our control would allow the car to correct for these disturbances. Finally, to verify the value of Kp we calculated before, we created code to simulate the car's response 10 times at 100 different Kp values between 0 and 1 and then picked the one that returned the smallest squared error. The MATLAB code confirmed that the Kp value should be approximately 0.25, or 1/G.
Step Five: Raspberry Pi Communication
After determining the equations for PID controller and simulating them in the MATLAB, the next step was to modify the DonkeyCar Framework run by our Raspberry Pi. In order to incorporate the encoder and the controller, two parts of python code were added to the framework. The first function, Encoder.py, was a threaded function that read the serial of the Arduino and calculated the speed of the car simultaneously. The second function, Controller.py, calculated the required throttle of the car to reach the desired speed and returned that to the system. The inputs for this controller function were the gain, 1/(Max Speed of the car), initial throttle, and the calculated velocity from Encoder function and the output throttle was calculated with the equation T(t)=Kp*e(t)+T(t-1).
Step Six: Testing
After completing and debugging all the code, as well as securely mounting all the extra electronic components, we were ready to test the car. We first ran the car normally to ensure that the encoder and display were recording properly. We then ran our autonomous model on the indoor track, using speed as an input, to test the controller. It seemed to be working correctly, until the torque from the motor started cracking the acrylic gear that allowed the encoder to read the car's speed. Some of the teeth broke off and the gears stopped meshing. This simply means that acrylic was not sufficiently strong for our purposes, but, unfortunately, we didn't have time to order a stronger manufactured gear, install it, make adjustments, and retest the code.
Overall, the encoder succeeded in allowing our system much more accurate speed control. We were able to mount the encoder so that it could accurately measure the speed, establish communication between it, the Arduino, and the Raspberry Pi, and incorporate a PI controller. Using the speed rather than voltage as an input when training the autonomous model made it a lot more reliable, especially when the battery was running low. This is an example of why feedback is preferred for so many systems and why control theory is such a popular field of study. Incorporating the encoder into the general MAE 148 configuration would be beneficial for future classes. Additionally, another team could expand on this encoder project to include path measurement and/or path planning. This could be accomplished by using multiple encoders to measure the speed of the wheels on each side, or with calculations using the angle of the servo motor. Either way, this project would be a great way for future students to learn more about controls.