cube-controller.ino 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Toggle over-the-air (WiFi) programming functionality (0 = disabled, 1 = enabled)
  2. #define OTA 1
  3. // Toggle between use of nonlinear or linear controller (0 = linear, 1 = nonlinear)
  4. #define USE_NONLINEAR_CONTROLLER 1
  5. // TickTwo provides ESP32-compatible timers
  6. #include <TickTwo.h>
  7. #if OTA
  8. // Libraries related to wireless functionality
  9. #include <WiFi.h>
  10. #include <ESPmDNS.h>
  11. #include <WiFiUdp.h>
  12. #include <ArduinoOTA.h>
  13. #endif
  14. // Cube controller library
  15. #include "src/cube-controller.h"
  16. #if OTA
  17. // WiFi settings
  18. const char* wifi_ssid = "YOUR_SSID";
  19. const char* wifi_password = "YOUR_PASSWORD";
  20. #endif
  21. // Instantiation of objects
  22. Motor motor1(M1_ENABLE, M1_CURRENT), motor2(M2_ENABLE, M2_CURRENT), motor3(M3_ENABLE, M3_CURRENT);
  23. WheelEstimator whe_est_1(M1_SPEED), whe_est_2(M2_SPEED), whe_est_3(M3_SPEED);
  24. AttitudeEstimator att_est(IMU_SDA, IMU_SCL);
  25. AttitudeWheelController cont(USE_NONLINEAR_CONTROLLER);
  26. AttitudeTrajectory att_tra;
  27. // Run cube controller at the frequency as specified in parameter file
  28. void controller();
  29. TickTwo timer(controller, dt_us, 0, MICROS_MICROS);
  30. // Status LED control: LED is either solid or blinking at 2.5 Hz
  31. void control_led();
  32. TickTwo timer_led(control_led, 200, 0, MILLIS);
  33. #if OTA
  34. // Handle OTA flashing requests every 5 seconds
  35. void handle_ota();
  36. TickTwo timer_ota(handle_ota, 5000, 0, MILLIS);
  37. #endif
  38. // Quaternion and angle error
  39. float qe0, qe1, qe2, qe3;
  40. float phi;
  41. float phi_lim = phi_min;
  42. // Trajectory initialization flag
  43. bool flag_tra = false;
  44. // LED status when blinking
  45. bool led_status = false;
  46. // Security flags
  47. bool flag_arm = false;
  48. bool flag_terminate = false;
  49. // Torques
  50. float tau_1 = 0, tau_2 = 0, tau_3 = 0;
  51. void setup() {
  52. // Open serial connection
  53. Serial.begin(921600);
  54. Serial.println("This is the ESP32 cube controller.");
  55. #if OTA
  56. // Set up WiFi connection
  57. WiFi.mode(WIFI_STA);
  58. WiFi.begin(wifi_ssid, wifi_password);
  59. unsigned int nAttempts = 0;
  60. while (WiFi.waitForConnectResult() != WL_CONNECTED && nAttempts < 3) {
  61. delay(1000);
  62. nAttempts++;
  63. }
  64. // If WiFi connection failed, indicate this using six yellow flashes
  65. if(WiFi.waitForConnectResult() != WL_CONNECTED) {
  66. for(int i = 0; i < 5; i++) {
  67. neopixelWrite(RGB_BUILTIN, 255, 255, 0);
  68. delay(250);
  69. neopixelWrite(RGB_BUILTIN, 0, 0, 0);
  70. delay(250);
  71. }
  72. }
  73. // Set hostname
  74. ArduinoOTA.setHostname("Cube ESP32");
  75. // Print WiFi status
  76. Serial.print("Connected to WiFi (local IP is ");
  77. Serial.print(WiFi.localIP());
  78. Serial.println(").");
  79. // Set up OTA flashing using ArduinoOTA
  80. ArduinoOTA.begin();
  81. #endif
  82. // Delay for one second to allow Maxon ESCON drivers to fully initialize
  83. delay(1000);
  84. // Motor setup (also spins each motor in positive direction very briefly)
  85. motor1.init();
  86. motor2.init();
  87. motor3.init();
  88. // Wheel estimator initialization (also calibrates the hall sensor for each wheel)
  89. whe_est_1.init();
  90. whe_est_2.init();
  91. whe_est_3.init();
  92. // Attitude estimator initialization (also calibrates the gyroscope)
  93. att_est.init();
  94. // Start controller and LED timers
  95. timer.start();
  96. timer_led.start();
  97. #if OTA
  98. // OTA handler timer
  99. timer_ota.start();
  100. #endif
  101. }
  102. void loop() {
  103. // Update controller timer
  104. timer.update();
  105. timer_led.update();
  106. #if OTA
  107. // Handle OTA updates
  108. timer_ota.update();
  109. #endif
  110. }
  111. #if OTA
  112. void handle_ota(){
  113. ArduinoOTA.handle();
  114. }
  115. #endif
  116. void controller() {
  117. // Estimate wheel velocities
  118. whe_est_1.estimate(tau_1);
  119. whe_est_2.estimate(tau_2);
  120. whe_est_3.estimate(tau_3);
  121. // Estimate cube attitude
  122. att_est.estimate();
  123. // Calculate rotation quaternion error
  124. qe0 = att_est.q0 * att_tra.qr0 + att_est.q1 * att_tra.qr1 + att_est.q2 * att_tra.qr2 + att_est.q3 * att_tra.qr3;
  125. qe1 = att_est.q0 * att_tra.qr1 - att_est.q1 * att_tra.qr0 - att_est.q2 * att_tra.qr3 + att_est.q3 * att_tra.qr2;
  126. qe2 = att_est.q0 * att_tra.qr2 - att_est.q2 * att_tra.qr0 + att_est.q1 * att_tra.qr3 - att_est.q3 * att_tra.qr1;
  127. qe3 = att_est.q0 * att_tra.qr3 - att_est.q1 * att_tra.qr2 + att_est.q2 * att_tra.qr1 - att_est.q3 * att_tra.qr0;
  128. // Normalize rotation quaternion error (real part only since we don't need the rest)
  129. qe0 /= sqrt(qe0 * qe0 + qe1 * qe1 + qe2 * qe2 + qe3 * qe3);
  130. // Calculate error angle
  131. phi = 2.0 * acos(qe0);
  132. // Controller enable and disable logic. The controller only activates once its orientation is such that the error with
  133. // respect to the desired orientation is smaller than phi_min. As soon as this is reached, the error tolerance phi_lim
  134. // is increased to phi_max. If this error is exceeded, the controller is disabled until the chip is reset.
  135. if(abs(phi) <= phi_lim && !flag_terminate) {
  136. // Controller is active
  137. flag_arm = true;
  138. // Increase error range to the maximum limit
  139. phi_lim = phi_max;
  140. // Generate trajectory
  141. // Comment out the generate() method if the cube is balancing on a side instead of a corner
  142. if(!flag_tra) {
  143. flag_tra = true;
  144. att_tra.init();
  145. }
  146. att_tra.generate();
  147. // Controller calculates motor torques based on cube and wheel states
  148. cont.control(att_tra.qr0, att_tra.qr1, att_tra.qr2, att_tra.qr3, att_est.q0, att_est.q1, att_est.q2, att_est.q3,
  149. att_tra.omega_r_x, att_tra.omega_r_y, att_tra.omega_r_z, att_est.omega_x, att_est.omega_y, att_est.omega_z,
  150. att_tra.alpha_r_x, att_tra.alpha_r_y, att_tra.alpha_r_z, whe_est_1.theta_w, whe_est_2.theta_w,
  151. whe_est_3.theta_w, whe_est_1.omega_w, whe_est_2.omega_w, whe_est_3.omega_w);
  152. // Get motor torques from controller
  153. tau_1 = cont.tau_1;
  154. tau_2 = cont.tau_2;
  155. tau_3 = cont.tau_3;
  156. } else {
  157. // Outside safe limits: reset motor torques to zero
  158. tau_1 = 0.0;
  159. tau_2 = 0.0;
  160. tau_3 = 0.0;
  161. // Disarm mechanism
  162. if(flag_arm) {
  163. flag_arm = false;
  164. flag_terminate = true;
  165. }
  166. }
  167. // Apply torques to motors
  168. // When balancing on the x side, comment out motor 2 and 3. When balancing on the y side, comment out 1 and 3.
  169. motor1.set_torque(tau_1);
  170. motor2.set_torque(tau_2);
  171. motor3.set_torque(tau_3);
  172. }
  173. void control_led() {
  174. if(flag_arm) {
  175. if(abs(phi) <= phi_lim - 10 * pi / 180) {
  176. // Cube is not close to error limit: solid green LED
  177. neopixelWrite(RGB_BUILTIN, 0, 255, 0);
  178. } else {
  179. // Cube is close to error limit: alternate between red and green. Cube should be manually put to rest.
  180. if(led_status) {
  181. neopixelWrite(RGB_BUILTIN, 255, 0, 0);
  182. } else {
  183. neopixelWrite(RGB_BUILTIN, 0, 255, 0);
  184. }
  185. led_status = !led_status;
  186. }
  187. } else {
  188. if(flag_tra) {
  189. // A trajectory was already generated so the cube must now be halted due to a limit error
  190. if(led_status) {
  191. neopixelWrite(RGB_BUILTIN, 0, 0, 0);
  192. } else {
  193. neopixelWrite(RGB_BUILTIN, 255, 0, 0);
  194. }
  195. led_status = !led_status;
  196. } else {
  197. // Show a solid red LED to indicate readiness to start
  198. neopixelWrite(RGB_BUILTIN, 255, 0, 0);
  199. }
  200. }
  201. }