octv2_motor_controller.ino 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. OCTv2 (Oreo Cookie Thrower v2) - ESP32 Motor Controller
  3. Controls two stepper motors for rotation and elevation
  4. Receives commands via Serial at 115200 baud
  5. Hardware:
  6. - Rotation Stepper: A4988 driver
  7. - Elevation Stepper: A4988 driver
  8. - Fire mechanism: Servo or solenoid
  9. - Limit switches for homing
  10. */
  11. #include <ESP32Servo.h>
  12. #include <AccelStepper.h>
  13. // Pin definitions
  14. #define ROTATION_STEP_PIN 2
  15. #define ROTATION_DIR_PIN 3
  16. #define ROTATION_ENABLE_PIN 4
  17. #define ROTATION_LIMIT_PIN 5 // Home limit switch
  18. #define ELEVATION_STEP_PIN 6
  19. #define ELEVATION_DIR_PIN 7
  20. #define ELEVATION_ENABLE_PIN 8
  21. #define ELEVATION_LIMIT_PIN 9 // Home limit switch
  22. #define FIRE_SERVO_PIN 10
  23. #define FIRE_SOLENOID_PIN 11
  24. #define STATUS_LED_PIN 13
  25. // Motor configuration
  26. #define STEPS_PER_REVOLUTION 200 // Standard stepper motor
  27. #define MICROSTEPS 16 // A4988 microstepping
  28. #define TOTAL_STEPS (STEPS_PER_REVOLUTION * MICROSTEPS)
  29. // Mechanical configuration
  30. #define ROTATION_GEAR_RATIO 5.0 // 5:1 gear reduction
  31. #define ELEVATION_GEAR_RATIO 3.0 // 3:1 gear reduction
  32. #define ROTATION_STEPS_PER_DEGREE (TOTAL_STEPS * ROTATION_GEAR_RATIO / 360.0)
  33. #define ELEVATION_STEPS_PER_DEGREE (TOTAL_STEPS * ELEVATION_GEAR_RATIO / 360.0)
  34. // Movement limits
  35. #define ROTATION_MIN_DEGREES -90.0
  36. #define ROTATION_MAX_DEGREES 90.0
  37. #define ELEVATION_MIN_DEGREES 0.0
  38. #define ELEVATION_MAX_DEGREES 60.0
  39. // Speed settings
  40. #define MAX_SPEED_ROTATION 2000 // Steps per second
  41. #define MAX_SPEED_ELEVATION 1500 // Steps per second
  42. #define ACCELERATION 1000 // Steps per second squared
  43. // Create stepper objects
  44. AccelStepper rotationStepper(AccelStepper::DRIVER, ROTATION_STEP_PIN, ROTATION_DIR_PIN);
  45. AccelStepper elevationStepper(AccelStepper::DRIVER, ELEVATION_STEP_PIN, ELEVATION_DIR_PIN);
  46. // Fire mechanism
  47. Servo fireServo;
  48. bool useServoForFire = true; // Set to false to use solenoid instead
  49. // Current positions in degrees
  50. float currentRotation = 0.0;
  51. float currentElevation = 0.0;
  52. bool isHomed = false;
  53. // Command parsing
  54. String inputCommand = "";
  55. bool commandReady = false;
  56. void setup() {
  57. Serial.begin(115200);
  58. Serial.println("🍪 OCTv2 ESP32 Motor Controller Starting...");
  59. // Setup pins
  60. pinMode(ROTATION_ENABLE_PIN, OUTPUT);
  61. pinMode(ELEVATION_ENABLE_PIN, OUTPUT);
  62. pinMode(ROTATION_LIMIT_PIN, INPUT_PULLUP);
  63. pinMode(ELEVATION_LIMIT_PIN, INPUT_PULLUP);
  64. pinMode(FIRE_SOLENOID_PIN, OUTPUT);
  65. pinMode(STATUS_LED_PIN, OUTPUT);
  66. // Configure steppers
  67. rotationStepper.setMaxSpeed(MAX_SPEED_ROTATION);
  68. rotationStepper.setAcceleration(ACCELERATION);
  69. elevationStepper.setMaxSpeed(MAX_SPEED_ELEVATION);
  70. elevationStepper.setAcceleration(ACCELERATION);
  71. // Enable steppers
  72. digitalWrite(ROTATION_ENABLE_PIN, LOW); // LOW = enabled for A4988
  73. digitalWrite(ELEVATION_ENABLE_PIN, LOW);
  74. // Setup fire mechanism
  75. if (useServoForFire) {
  76. fireServo.attach(FIRE_SERVO_PIN);
  77. fireServo.write(0); // Home position
  78. } else {
  79. digitalWrite(FIRE_SOLENOID_PIN, LOW);
  80. }
  81. // Status LED
  82. digitalWrite(STATUS_LED_PIN, HIGH);
  83. Serial.println("✅ OCTv2 Motor Controller Ready");
  84. Serial.println("Commands: HOME, MOVE <rot> <elev>, REL <rot> <elev>, FIRE, POS");
  85. }
  86. void loop() {
  87. // Handle serial commands
  88. handleSerialInput();
  89. if (commandReady) {
  90. processCommand();
  91. commandReady = false;
  92. inputCommand = "";
  93. }
  94. // Run steppers
  95. rotationStepper.run();
  96. elevationStepper.run();
  97. // Blink status LED when moving
  98. static unsigned long lastBlink = 0;
  99. if (rotationStepper.isRunning() || elevationStepper.isRunning()) {
  100. if (millis() - lastBlink > 100) {
  101. digitalWrite(STATUS_LED_PIN, !digitalRead(STATUS_LED_PIN));
  102. lastBlink = millis();
  103. }
  104. } else {
  105. digitalWrite(STATUS_LED_PIN, HIGH);
  106. }
  107. }
  108. void handleSerialInput() {
  109. while (Serial.available()) {
  110. char c = Serial.read();
  111. if (c == '\n' || c == '\r') {
  112. if (inputCommand.length() > 0) {
  113. commandReady = true;
  114. }
  115. } else {
  116. inputCommand += c;
  117. }
  118. }
  119. }
  120. void processCommand() {
  121. inputCommand.trim();
  122. inputCommand.toUpperCase();
  123. if (inputCommand == "HOME") {
  124. homeMotors();
  125. }
  126. else if (inputCommand.startsWith("MOVE ")) {
  127. handleMoveCommand();
  128. }
  129. else if (inputCommand.startsWith("REL ")) {
  130. handleRelativeCommand();
  131. }
  132. else if (inputCommand == "FIRE") {
  133. fireOreo();
  134. }
  135. else if (inputCommand == "POS") {
  136. reportPosition();
  137. }
  138. else if (inputCommand == "STOP") {
  139. stopMotors();
  140. }
  141. else if (inputCommand == "STATUS") {
  142. reportStatus();
  143. }
  144. else {
  145. Serial.println("ERROR: Unknown command");
  146. }
  147. }
  148. void homeMotors() {
  149. Serial.println("🏠 Homing motors...");
  150. // Disable acceleration for homing
  151. rotationStepper.setAcceleration(500);
  152. elevationStepper.setAcceleration(500);
  153. // Home rotation motor
  154. Serial.println("Homing rotation...");
  155. rotationStepper.setSpeed(-500); // Move slowly towards limit
  156. while (digitalRead(ROTATION_LIMIT_PIN) == HIGH) {
  157. rotationStepper.runSpeed();
  158. delay(1);
  159. }
  160. rotationStepper.stop();
  161. rotationStepper.setCurrentPosition(0);
  162. // Back off from limit
  163. rotationStepper.move(100); // Move away from limit
  164. while (rotationStepper.run()) { delay(1); }
  165. // Home elevation motor
  166. Serial.println("Homing elevation...");
  167. elevationStepper.setSpeed(-300); // Move slowly towards limit
  168. while (digitalRead(ELEVATION_LIMIT_PIN) == HIGH) {
  169. elevationStepper.runSpeed();
  170. delay(1);
  171. }
  172. elevationStepper.stop();
  173. elevationStepper.setCurrentPosition(0);
  174. // Back off from limit
  175. elevationStepper.move(50);
  176. while (elevationStepper.run()) { delay(1); }
  177. // Restore normal acceleration
  178. rotationStepper.setAcceleration(ACCELERATION);
  179. elevationStepper.setAcceleration(ACCELERATION);
  180. // Set home position
  181. currentRotation = 0.0;
  182. currentElevation = 0.0;
  183. isHomed = true;
  184. Serial.println("OK");
  185. }
  186. void handleMoveCommand() {
  187. // Parse "MOVE <rotation> <elevation>"
  188. int firstSpace = inputCommand.indexOf(' ', 5);
  189. if (firstSpace == -1) {
  190. Serial.println("ERROR: Invalid MOVE syntax");
  191. return;
  192. }
  193. float targetRotation = inputCommand.substring(5, firstSpace).toFloat();
  194. float targetElevation = inputCommand.substring(firstSpace + 1).toFloat();
  195. moveToPosition(targetRotation, targetElevation);
  196. }
  197. void handleRelativeCommand() {
  198. // Parse "REL <delta_rotation> <delta_elevation>"
  199. int firstSpace = inputCommand.indexOf(' ', 4);
  200. if (firstSpace == -1) {
  201. Serial.println("ERROR: Invalid REL syntax");
  202. return;
  203. }
  204. float deltaRotation = inputCommand.substring(4, firstSpace).toFloat();
  205. float deltaElevation = inputCommand.substring(firstSpace + 1).toFloat();
  206. float targetRotation = currentRotation + deltaRotation;
  207. float targetElevation = currentElevation + deltaElevation;
  208. moveToPosition(targetRotation, targetElevation);
  209. }
  210. void moveToPosition(float targetRotation, float targetElevation) {
  211. if (!isHomed) {
  212. Serial.println("ERROR: Not homed");
  213. return;
  214. }
  215. // Clamp to limits
  216. targetRotation = constrain(targetRotation, ROTATION_MIN_DEGREES, ROTATION_MAX_DEGREES);
  217. targetElevation = constrain(targetElevation, ELEVATION_MIN_DEGREES, ELEVATION_MAX_DEGREES);
  218. // Convert to steps
  219. long rotationSteps = (long)(targetRotation * ROTATION_STEPS_PER_DEGREE);
  220. long elevationSteps = (long)(targetElevation * ELEVATION_STEPS_PER_DEGREE);
  221. // Move steppers
  222. rotationStepper.moveTo(rotationSteps);
  223. elevationStepper.moveTo(elevationSteps);
  224. // Wait for completion
  225. while (rotationStepper.isRunning() || elevationStepper.isRunning()) {
  226. rotationStepper.run();
  227. elevationStepper.run();
  228. delay(1);
  229. }
  230. // Update current position
  231. currentRotation = targetRotation;
  232. currentElevation = targetElevation;
  233. Serial.println("OK");
  234. }
  235. void fireOreo() {
  236. Serial.println("🔥 FIRING OREO!");
  237. if (useServoForFire) {
  238. // Servo fire mechanism
  239. fireServo.write(90); // Fire position
  240. delay(200); // Fire duration
  241. fireServo.write(0); // Return to home
  242. } else {
  243. // Solenoid fire mechanism
  244. digitalWrite(FIRE_SOLENOID_PIN, HIGH);
  245. delay(100); // Fire pulse
  246. digitalWrite(FIRE_SOLENOID_PIN, LOW);
  247. }
  248. Serial.println("OK");
  249. }
  250. void stopMotors() {
  251. rotationStepper.stop();
  252. elevationStepper.stop();
  253. Serial.println("OK");
  254. }
  255. void reportPosition() {
  256. // Report actual position in degrees
  257. Serial.print(currentRotation, 1);
  258. Serial.print(" ");
  259. Serial.println(currentElevation, 1);
  260. }
  261. void reportStatus() {
  262. Serial.print("HOMED:");
  263. Serial.print(isHomed ? "1" : "0");
  264. Serial.print(" ROT:");
  265. Serial.print(currentRotation, 1);
  266. Serial.print(" ELEV:");
  267. Serial.print(currentElevation, 1);
  268. Serial.print(" MOVING:");
  269. Serial.print((rotationStepper.isRunning() || elevationStepper.isRunning()) ? "1" : "0");
  270. Serial.print(" LIMITS:");
  271. Serial.print(digitalRead(ROTATION_LIMIT_PIN) ? "0" : "1");
  272. Serial.print(",");
  273. Serial.println(digitalRead(ELEVATION_LIMIT_PIN) ? "0" : "1");
  274. }
  275. // Helper function for debugging
  276. void printDebugInfo() {
  277. Serial.print("Debug - Rot pos: ");
  278. Serial.print(rotationStepper.currentPosition());
  279. Serial.print(" target: ");
  280. Serial.print(rotationStepper.targetPosition());
  281. Serial.print(" | Elev pos: ");
  282. Serial.print(elevationStepper.currentPosition());
  283. Serial.print(" target: ");
  284. Serial.println(elevationStepper.targetPosition());
  285. }