2nd Project
2nd Project
2nd Project
Objective
The aim of this experiment is to design and implement a control action using the Linear
Quadratic Regulator (LQR) technique to balance the inverted pendulum in an upright
position, even under external disturbances. The specific goals for this experiment are:
• To restrict the pendulum arm vibration (α) within ±3◦ .
• To maintain the base angle oscillation (θ) within ±30◦ .
LQR Method
The LQR method is a state-space control technique used to design an optimal controller
that minimizes a cost function. The cost function balances performance and control
effort.
For a state-space system:
ẋ = Ax + Bu
where:
• x represents the system states (e.g., pendulum angle, base angle),
• u is the control input,
• A and B represent system dynamics.
The LQR finds an optimal control input u by minimizing:
Z ∞
J= (xT Qx + uT Ru) dt
0
where:
• Q penalizes state deviations from the desired state,
• R penalizes control effort.
The feedback control law is given by:
u = −Kx
where K is the optimal gain matrix calculated using the Riccati equation.
1
Control Algorithm
We chose the following initial values for the design of the LQR:
Q = diag(10, 1, 5, 1), R = 10
Where the values of q1 , q2 , q3 , q4 and r are tuned for our specific system.
After testing, the gain matrix K was fine-tuned to improve performance. The final
values obtained for K are as follows:
Parameter Value
K1 -3.189
K2 92.86
K3 -4.16
K4 12.12
Code Implementation
Note: Insert the code used for LQR implementation in this section. The following LaTeX
code block formats the code.
Listing 1: LQR Controller Code
1
2
3 //* Include the SPI library for the arduino boards */
4 #include <SPI.h>
5
18 /* We will use these define macros so we can write code once compatible with 12 or 14
bit encoders */
19 #define RES12 12
20 #define RES14 14
21
22 /* SPI pins */
23 #define ENC_0 2
24 #define ENC_1 3
25 #define SPI_MOSI 51
26 #define SPI_MISO 50
27 #define SPI_SCLK 52
28
2
29
30
31 #define motorPin1 12
32 #define motorPin2 13
33
34 int alpha_raw;
35 int theta_raw;
36 float alpha_rad;
37 float theta_rad;
38
39 float theta_prev = 0;
40 float alpha_prev = 0;
41 float theta_dot;
42 float alpha_dot;
43
44 int t_old = 0;
45 int t_new = 0;
46 int dt = 0;
47
48
49
50 //Q = diag(10,1,5,1) and R=10
51 //Working Test 2
52 float K_theta = -3.1623;//-3.1623,-2.5623
53 float K_alpha = 92.84756;//92.84756
54 float K_theta_dot = -4.0991504;//-3.8991504
55 float K_alpha_dot = 12.003874;//12.003874
56
57 float control_signal;
58 int motor_voltage = 0;
59 float scaling_factor = 2.8;
60 void setup()
61 {
62 //Set the modes for the SPI IO
63 pinMode(SPI_SCLK, OUTPUT);
64 pinMode(SPI_MOSI, OUTPUT);
65 pinMode(SPI_MISO, INPUT);
66 pinMode(ENC_0, OUTPUT);
67 pinMode(ENC_1, OUTPUT);
68
69 //Initialize the UART serial connection for debugging
70 Serial.begin(BAUDRATE);
71
3
87
4
and use it for calculating the checksum
144
145 //get first byte which is the high byte, shift it 8 bits. don’t release line for
the first byte
146 currentPosition = spiWriteRead(AMT22_NOP, encoder, false) << 8;
147
148 //this is the time required between bytes as specified in the datasheet.
149 //We will implement that time delay here, however the arduino is not the fastest
device so the delay
150 //is likely inherantly there already
151 delayMicroseconds(3);
152
153 //OR the low byte with the currentPosition variable. release line after second byte
154 currentPosition |= spiWriteRead(AMT22_NOP, encoder, true);
155
156 //run through the 16 bits of position and put each bit into a slot in the array so
we can do the checksum calculation
157 for(int i = 0; i < 16; i++) binaryArray[i] = (0x01) & (currentPosition >> (i));
158
159 //using the equation on the datasheet we can calculate the checksums and then make
sure they match what the encoder sent
160 if ((binaryArray[15] == !(binaryArray[13] ^ binaryArray[11] ^ binaryArray[9] ^
binaryArray[7] ^ binaryArray[5] ^ binaryArray[3] ^ binaryArray[1]))
161 && (binaryArray[14] == !(binaryArray[12] ^ binaryArray[10] ^ binaryArray[8]
^ binaryArray[6] ^ binaryArray[4] ^ binaryArray[2] ^ binaryArray[0])))
162 {
163 //we got back a good position, so just mask away the checkbits
164 currentPosition &= 0x3FFF;
165 }
166 else
167 {
168 currentPosition = 0xFFFF; //bad position
169 }
170
171 //If the resolution is 12-bits, and wasn’t 0xFFFF, then shift position, otherwise
do nothing
172 if ((resolution == RES12) && (currentPosition != 0xFFFF)) currentPosition =
currentPosition >> 2;
173
174 return currentPosition;
175 }
176
177 uint8_t spiWriteRead(uint8_t sendByte, uint8_t encoder, uint8_t releaseLine)
178 {
179 //holder for the received over SPI
180 uint8_t data;
181
182 //set cs low, cs may already be low but there’s no issue calling it again except
for extra time
183 setCSLine(encoder ,LOW);
184
185 //There is a minimum time requirement after CS goes low before data can be clocked
out of the encoder.
186 //We will implement that time delay here, however the arduino is not the fastest
device so the delay
187 //is likely inherantly there already
188 delayMicroseconds(3);
189
5
190 //send the command
191 data = SPI.transfer(sendByte);
192 delayMicroseconds(3); //There is also a minimum time after clocking that CS should
remain asserted before we release it
193 setCSLine(encoder, releaseLine); //if releaseLine is high set it high else it stays
low
194
Graphs
Below are the two graphs representing the pendulum’s performance:
6
Results
The experiment yielded the following results:
7
Challenges Faced
During the experiment, we encountered the following challenges:
1. Tuning Complexity: Finding the right combination of Q and R was challenging.
Improper tuning resulted in either instability or slow response.
2. System Nonlinearity: Our system’s nonlinearity caused difficulties in controlling
the pendulum during large swings.
3. Sensor Noise: The encoder feedback was noisy, affecting state estimation accu-
racy.
4. Actuator Delays: The motor driving the pendulum base exhibited delays in
response, contributing to oscillations.