#include #include #include #include "defs.hpp" Adafruit_PWMServoDriver board1 = Adafruit_PWMServoDriver(0x40); int angleToPulse(int theta, int motorIndex) { return map(theta, 0, servo_range[motorIndex], SERVO_PMIN, SERVO_PMAX); } // ----- UART command handling ----- String inString = ""; String handleUartComm() { if (Serial.available() > 0) { unsigned char inChar = Serial.read(); // Read finished procedure if (inChar == '\n') { // Copy return value String retStr = inString; // Reset inString = ""; return retStr; } // Store byte procedure else { inString += (char)inChar; return ""; } } else { return ""; } } // ----- CAN config ----- const int CAN_CS_PIN = 10; // CS pin for MCP2515 MCP_CAN CAN0(CAN_CS_PIN); // Choose depending on your module crystal: // MCP_16MHZ for most shields // MCP_8MHZ for many cheap blue boards const byte CAN_CLOCK = MCP_8MHZ; // change to MCP_8MHZ if needed // CAN bitrate and ID we listen to const unsigned long SERVO_CAN_ID = 0x100; // must match sender // ----- String utils ----- int splitByChar(String in, char c, String* out) { int start = 0; int end; int count = 0; while (true) { end = in.indexOf(c, start); if (end == -1) { out[count++] = in.substring(start); break; } out[count++] = in.substring(start, end); start = end + 1; } return count; } // Process a complete command like "M 0 150" void processCommand(String command) { command.trim(); // remove any stray whitespace if (command.length() == 0) { return; } String argv[5]; int argc = splitByChar(command, ' ', argv); // Expecting command like: MA 0 150 // Motor Absolute sets absolute motor position if (argc >= 3 && argv[0] == "MA") { int motorIndex = argv[1].toInt(); int theta = argv[2].toInt(); // Safety range check if (theta < min_safe_angles[motorIndex]) { // Serial.println("Refusing to move motor to " + String(theta)); } else if (theta > max_safe_angles[motorIndex]) { // Serial.println("Refusing to move motor to " + String(theta)); } else { board1.setPWM(motorIndex, 0, angleToPulse(theta, motorIndex)); currentPose[motorIndex] = theta; } } // Expecting command like: MR 0 150 // Motor Relative sets relatove motor position, e.g. move by 10 else if (argc >= 3 && argv[0] == "MR") { int motorIndex = argv[1].toInt(); int delta = argv[2].toInt(); int targetPosition = constrain(currentPose[motorIndex] + delta, min_safe_angles[motorIndex], max_safe_angles[motorIndex]); if (targetPosition != currentPose[motorIndex]) { Serial.println("Moving motor " + String(argv[1]) + " to " + String(targetPosition)); board1.setPWM(motorIndex, 0, angleToPulse(targetPosition, motorIndex)); currentPose[motorIndex] = targetPosition; } } // Expecting command like: MR 0 150 // Command to reset pose else if (argc == 1 && argv[0] == "R") { takeInitialPose(); } else { // Serial.println("Received unknown or malformed command: " + command); } } void takeInitialPose() { for (int motorIndex = 0; motorIndex < 4; motorIndex++) { board1.setPWM(motorIndex, 0, angleToPulse(initial_pose[motorIndex], motorIndex)); currentPose[motorIndex] = initial_pose[motorIndex]; delay(500); } } void setup() { Serial.begin(9600); board1.begin(); board1.setPWMFreq(60); // Servos operate at ~60 Hz takeInitialPose(); while (!Serial) { ; // wait for serial port to connect (for native USB boards) } // ----- CAN init ----- while (CAN0.begin(MCP_ANY, CAN_500KBPS, CAN_CLOCK) != CAN_OK) { Serial.println("CAN init failed, retrying..."); delay(200); } CAN0.setMode(MCP_NORMAL); Serial.println("CAN init OK"); } void loop() { // ----- UART handling ----- String uartCommand = handleUartComm(); if (uartCommand != "") { //Serial.println(uartCommand); processCommand(uartCommand); } // ----- CAN handling ----- // Check if a CAN message is available if (CAN_MSGAVAIL == CAN0.checkReceive()) { unsigned long canId; byte len; byte buf[8]; // Read CAN frame CAN0.readMsgBuf(&canId, &len, buf); // Only accept frames with the expected ID (optional but recommended) if (canId == SERVO_CAN_ID) { // Interpret payload as ASCII string, e.g. "M 0 150" String canCommand = ""; for (byte i = 0; i < len; i++) { canCommand += (char)buf[i]; } Serial.println(canCommand); processCommand(canCommand); } } // You can add a small delay if you want to chill the loop a bit // delay(1); }