185 lines
4.7 KiB
Plaintext
185 lines
4.7 KiB
Plaintext
#include <Adafruit_PWMServoDriver.h>
|
|||
|
|
#include <SPI.h>
|
||
|
|
#include <mcp_can.h>
|
||
|
|
#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);
|
||
|
|
}
|