For my mechatronics class, one of the quick projects we were assigned to build was a self-balancing two-wheeled robot - like a Segway, but much smaller. Our constraints were as follows:
Robot max width is 15 cm
Robot max height is 16 cm
Must be an inverted pendulum (ie. center of mass above the wheels)
Can only touch the ground via two coaxially-mounted wheels
Uses a PID control scheme for balancing
Cannot be tethered
Further, we were only allowed to use:
2 DC Motors and wheels
2 9V Batteries
1 Arduino Uno
1 Solderless breadboard
1 BNO055 IMU
1 Ultrasonic distance sensor
Frame can be made out of acrylic or 3D printed
Screws, nuts, and standoffs
I apologize for the vertical video (as opposed to horizontal); I didn't film it. In the cameraperson's defense, filming vertically seems natural on phones.
As you can see in the video, the robot can balance itself. Even after a lot of calibration of the PID constants and the setPoint, it's still a bit jittery. I think it's because of the short height limit (16 cm) we were given. This robot stands at under six inches tall, so the center of mass is probably only 3 inches from the axle of the motors. If we were allowed to make taller robots, the robot would be easier to balance and less jittery. Second, we weren't allowed to use rotary encoders, so the robot did not know if the two motors were rotating the same amount which affected its performance on inclines as the two motors applied differing amounts of torque.
Also, I forgot to take a video of this, but you can imagine that if I were to push the top of the robot a little bit forwards, the robot would roll forwards until it eventually corrects itself and stands upright (the way it remains upright is by moving in the direction it's falling in). This is the same principle that allow Segways to operate because leaning forward brings the center of mass forwards so the wheels will spin in that direction so that it doesn't fall over.
Inverted Pendulum Physics:
When designing a self-balancing robot that has to be an inverted pendulum, one of the first things to realize is that the center of mass should be near the top of the robot (up to a certain extent). The trade-off is that a higher center of mass requires more torque to correct because Torque = Radius x Force (the height would contribute to a larger radius), but a taller robot would also take a longer time to fall down which may make it easier to keep upright.
Let's derive an equation for the angular acceleration of an inverted pendulum point mass.
If we were to assume that the robot starts out upright (meaning it doesn't have to upright itself at the beginning), then theta = 0. At this point in time, the angular acceleration (second derivative of theta) is equal to the acceleration due to gravity divided by the length (the distance between the center of mass and the pivot point).
Therefore, as the length increases, the angular acceleration decreases. This means that tall pendulums fall more slowly than short pendulums.
To me, this seems a bit unintuitive but what made it more intuitive to me is thinking about balancing a long rod on my palm verses balancing a short pencil on my palm; it is much easier to balance a long rod.
We were only allowed a max height of 16 cm and a max width of 15 cm. Fifteen to sixteen centimeters is relatively small (about six inches) so I wanted to make sure that the robot maximized the allowed dimensions in both directions. Also, after deriving the equation for angular acceleration for inverted pendulums above, I wanted to put the center of mass as close as possible to the top so it would fall more slowly making it easier to keep upright. Further, I wanted to design the robot such that the center of mass was directly above the axis of rotation (ie. if the robot was upright on a flat surface, the center of mass would form an imaginary plane with the axle and this plane would be perpendicular to the ground). If I didn't design it this way, the robot would have a tendency to fall to one side and I would have to compensate for this in the code (not very difficult though).
My Actual Robot:
I used an Arduino for this, so the code is written in the Arduino language which is basically C/C++.
First, we have to include our libraries, define our pins, declare our variables, and initialize our objects. One interesting trend that I've noticed is that people tend to declare global variables when programming Arduinos even though that tends to be looked as bad coding practice when programming software for PC. I'm not quite sure why (maybe memory management and speed?).
For the PID, I could have calculated it myself, but I decided on using a PID library. The proportional, integral, and derivative constants were found via trial-and-error which is one of the more common methods of tuning it. I started by incrementing Kp until it could almost stand by itself. After that, I added derivative control and incremented it until it started shaking a bit and then lowered it a bit from there. Lastly, I tried several values of Ki but didn't notice any changes so I left it at zero.
I'm not going to explain how the PID closed-loop control scheme works because there's a lot of resources online for that. Here's a video series that helped me understand it.
When declaring the PID object, the first parameter is the input, the second parameter is the output, the third parameter is the target (ie. what we want the input to be), and the next three parameters are the PID constants.
Next comes the setup loop. Here, I set the baud rate to 9600 bits per second for the serial connection. I also initialize the IMU (the BNO055) and set the parameters of the PID object. After that, I set all the pin modes.
Here's where most of the action takes place - the void loop. At the beginning of the loop, I added a potentiometer that gets analogRead and mapped to 1.0 to 5.0. This allows me to change the setPoint on-the-fly while calibrating the robot for different inclines. After that, there's an if-statement to run the calcSetPointDist function if useDistSensor is true (more about this later). From here, it uses the IMU to gather the orientation data and computes the PID output which gets mapped from minEnableVal to 255. MinEnableVal is the minimum value we can analogWrite to the H-bridge so that the wheels start rotating (it will depend on the motor).
Using the sign of the pidOut variable, the program determines if the robot should rotate its wheels forward or backwards to minimize the difference between xDist and setPoint. However, if xDist is close enough to setPoint (meaning: smaller than minDiffFromCenter) then the motors will just be stopped and held with a holding torque.
When I placed the self-balancing robot on an incline, it was very difficult to keep it at one spot on the incline even though it would stay upright. It would either roll slowly down the ramp or up the ramp. Because we were testing our robots with inclines that measured maybe a foot in length, the robot was not allowed to roll up or down the ramp. So one of the things I tried was using an ultrasonic distance sensor to maintain a set distance from a wall placed at the top of the ramp. If it was getting too close to the wall, the setPoint would increment so that the robot would roll a bit down the ramp (just slightly) and if it was getting too far away from the wall, the setPoint would decrement so that the robot would roll a bit up the ramp.
The following implementation kind of worked, but it would kind of jitter if the robot's distance from the wall was around desiredDist. A better implementation would have been using PID again here with the input being the distance given by the ultrasonic sensor, the output being the increment, and the target being the desiredDist. We weren't given that much time to make changes to our robot while testing it, so I didn't have time to implement that and calibrate all the PID constants.
It's a very simple circuit because I was limited in the number and type of components I could use. Also, the simpler the circuit, the easier it is to debug.