HansonServo/feetech.cpp

669 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "feetech.h"
Feetech::Feetech(HardwareSerial& serial, int DE_PIN, int RE_PIN, int TX_PIN, int RX_PIN)
: serial(serial), DE_PIN(DE_PIN), RE_PIN(RE_PIN), TX_PIN(TX_PIN), RX_PIN(RX_PIN) {
}
void Feetech::begin() {
serial.begin(1000000, SERIAL_8N1, RX_PIN, TX_PIN);
pinMode(DE_PIN, OUTPUT);
//pinMode(RE_PIN, OUTPUT);
setModeReceive();
}
void Feetech::syncWritePos(uint8_t* ids, uint16_t* positions, uint16_t* speeds, uint8_t count) {
const uint8_t DATA_LEN_PER_SERVO = 0x04; // 2 bytes pos + 2 bytes speed
// Calculate packet length: instruction + address + data length + (count × data per servo)
uint8_t packetLen = 4 + (count * (1 + DATA_LEN_PER_SERVO)); // instruction + addr + len + servo data
// Create packet buffer
uint8_t packet[3 + packetLen]; // 3 header bytes + payload
uint8_t index = 0;
// Header
packet[index++] = 0xFF;
packet[index++] = 0xFF;
packet[index++] = BROADCAST_ID;
packet[index++] = packetLen;
packet[index++] = SYNCWRITE_DATA;
packet[index++] = REQUEST_GOAL_POSITION;
packet[index++] = DATA_LEN_PER_SERVO;
// Servo data
uint16_t pos;
uint16_t speed;
for (uint8_t i = 0; i < count; i++) {
packet[index++] = ids[i];
// if (feetechMode == MODE_SCS) {
// pos = map(positions[i], 0, 4095, 0, 1023);
// } else if (feetechMode == MODE_STS) {
// pos = positions[i];
// }
pos = positions[i];
speed = speeds[i];
packet[index++] = (uint8_t)((pos >> 8) & 0xFF); // High byte
packet[index++] = (uint8_t)(pos & 0xFF); // Low byte
packet[index++] = (uint8_t)((speed >> 8) & 0xFF); // Speed high byte
packet[index++] = (uint8_t)(speed & 0xFF); // Speed low byte
}
// Checksum
uint8_t checksum = 0;
for (uint8_t i = 2; i < index; i++) {
checksum += packet[i];
}
checksum = ~checksum;
// Send packet
setModeTransmit();
Serial1.write(packet, index);
Serial1.write(checksum);
Serial1.flush();
setModeReceive();
clearEcho(sizeof(packet));
}
// Send move command to servo id:0-255, position:0-4095
uint8_t Feetech::sendWritePos(uint8_t id, uint16_t position) {
uint8_t packet[9];
packet[0] = 0xFF; // Header
packet[1] = 0xFF; // Header
packet[2] = id; // Servo ID
packet[3] = 5; // Length = instruction + address + 2 bytes + checksum
packet[4] = WRITE_DATA; // Instruction: WRITE
packet[5] = 0x2A; // Address: Goal Position
if (feetechMode == MODE_SCS) {
packet[6] = (position >> 8) & 0xFF; // High byte
packet[7] = position & 0xFF; // Low byte
} else if (feetechMode == MODE_STS) {
packet[6] = position & 0xFF; // Low byte first for STS
packet[7] = (position >> 8) & 0xFF; // High byte second
}
// Calculate checksum
uint8_t sum = 0;
for (int i = 2; i < 8; i++) sum += packet[i];
packet[8] = ~sum;
// Send packet
setModeTransmit();
serial.write(packet, sizeof(packet));
serial.flush();
setModeReceive();
clearEcho(sizeof(packet));
// Should later recieve a 1 byte response code packet
return waitOnData1Byte(10);
}
void Feetech::sendPing(uint8_t id) {
uint8_t packet[6];
packet[0] = 0xFF; // Header
packet[1] = 0xFF; // Header
packet[2] = id; // Servo ID
packet[3] = 0x02; // Length = instruction + address + checksum
packet[4] = PING; // Instruction: PING
// Calculate checksum
uint8_t sum = 0;
for (int i = 2; i < 5; i++) sum += packet[i];
packet[5] = ~sum; // Checksum
// memcpy(lastSentPacket, packet, sizeof(packet));
// lastSentLength = sizeof(packet);
// Send packet
//Serial.println("PING");
setModeTransmit();
//delay(20);
serial.write(packet, sizeof(packet));
serial.flush();
//delay(20);
setModeReceive();
clearEcho(sizeof(packet));
}
void Feetech::clearEcho(uint8_t length) {
if (filterEcho) {
for (int i = 0; i < length; i++) {
if (serial.available()) serial.read();
}
}
}
void Feetech::testRequest() {
uint8_t packet[6] = { 0XFF, 0XFF, 0X00, 0X02, 0X06, 0XF7 };
uint8_t sum = 0;
for (int i = 2; i < 5; i++) sum += packet[i]; // Include all data bytes for checksum
packet[5] = ~sum; // Checksum
setModeTransmit();
serial.write(packet, sizeof(packet));
serial.flush();
setModeReceive();
waitOnReply(100);
}
uint8_t Feetech::enableTorque(uint8_t id) {
uint8_t packet[8];
packet[0] = 0xFF; // Header
packet[1] = 0xFF; // Header
packet[2] = id; // Servo ID (ensure this matches the expected ID, e.g., 0x02)
packet[3] = 0x04; // Length (4 bytes of following data)
packet[4] = WRITE_DATA; // Instruction
packet[5] = REQUEST_TORQUE_ENABLE; // Write first address
packet[6] = 1; // Data to write
// Calculate checksum
uint8_t sum = 0;
for (int i = 2; i < 7; i++) sum += packet[i]; // Include all data bytes for checksum
packet[7] = ~sum; // Checksum
setModeTransmit();
serial.write(packet, 8);
serial.flush();
setModeReceive();
return waitOnData1Byte(10);
}
uint8_t Feetech::disableTorque(uint8_t id) {
uint8_t packet[8];
packet[0] = 0xFF; // Header
packet[1] = 0xFF; // Header
packet[2] = id; // Servo ID (ensure this matches the expected ID, e.g., 0x02)
packet[3] = 0x04; // Length (4 bytes of following data)
packet[4] = WRITE_DATA; // Instruction
packet[5] = REQUEST_TORQUE_ENABLE; // Write first address
packet[6] = 0; // Data to write
// Calculate checksum
uint8_t sum = 0;
for (int i = 2; i < 7; i++) sum += packet[i]; // Include all data bytes for checksum
packet[7] = ~sum; // Checksum
setModeTransmit();
serial.write(packet, 8);
serial.flush();
setModeReceive();
return waitOnData1Byte(10);
}
uint16_t Feetech::getModel(uint8_t id) {
sendRequest(id, REQUEST_MODEL, 2);
return waitOnData2Bytes(10);
}
uint8_t Feetech::getID(uint8_t id) {
sendRequest(id, REQUEST_ID, 1);
return waitOnData1Byte(10);
}
uint8_t Feetech::setID(uint8_t id, uint8_t newId) {
write1Byte(id, REQUEST_ID, newId);
return waitOnData1Byte(10);
}
// Performs entire ID changing routine
uint8_t Feetech::changeID(uint8_t id, uint8_t newId) {
setLock(id, 0);
setID(id, newId);
setLock(newId, 1);
}
uint8_t Feetech::getBaudRate(uint8_t id) {
sendRequest(id, REQUEST_BAUD_RATE, 1);
return waitOnData1Byte(10);
}
uint16_t Feetech::getMinAngleLimit(uint8_t id) {
sendRequest(id, REQUEST_MIN_ANGLE_LIMIT, 2);
if (feetechMode == MODE_STS) {
return waitOnData2Bytes(10);
} else if (feetechMode == MODE_SCS) {
return flipBytes(waitOnData2Bytes(10));
}
}
uint16_t Feetech::setMinAngleLimit(uint8_t id, uint16_t limit) {
write2Bytes(id, REQUEST_MIN_ANGLE_LIMIT, limit);
if (feetechMode == MODE_SCS) {
return flipBytes(waitOnData2Bytes(10));
} else if (feetechMode == MODE_STS) {
return waitOnData2Bytes(10);
}
}
uint16_t Feetech::getMaxAngleLimit(uint8_t id) {
sendRequest(id, REQUEST_MAX_ANGLE_LIMIT, 2);
if (feetechMode == MODE_SCS) {
return flipBytes(waitOnData2Bytes(10));
} else if (feetechMode == MODE_STS) {
return waitOnData2Bytes(10);
}
}
uint16_t Feetech::setMaxAngleLimit(uint8_t id, uint16_t limit) {
write2Bytes(id, REQUEST_MAX_ANGLE_LIMIT, limit);
if (feetechMode == MODE_SCS) {
return flipBytes(waitOnData2Bytes(10));
} else if (feetechMode == MODE_STS) {
return waitOnData2Bytes(10);
}
}
uint8_t Feetech::getCWDeadZone(uint8_t id) {
sendRequest(id, REQUEST_CW_DEAD_ZONE, 1);
return waitOnData1Byte(10);
}
uint8_t Feetech::getCCWDeadZone(uint8_t id) {
sendRequest(id, REQUEST_CCW_DEAD_ZONE, 1);
return waitOnData1Byte(10);
}
uint16_t Feetech::getOffset(uint8_t id) {
sendRequest(id, REQUEST_OFFSET, 2);
return waitOnData2Bytes(10);
}
uint8_t Feetech::getMode(uint8_t id) {
sendRequest(id, REQUEST_MODE, 1);
return waitOnData1Byte(10);
}
uint8_t Feetech::getTorqueEnable(uint8_t id) {
sendRequest(id, REQUEST_TORQUE_ENABLE, 1);
return waitOnData1Byte(10);
}
uint8_t Feetech::getAcceleration(uint8_t id) {
sendRequest(id, REQUEST_ACCELERATION, 1);
return waitOnData1Byte(10);
}
uint16_t Feetech::getGoalPosition(uint8_t id) {
sendRequest(id, REQUEST_GOAL_POSITION, 2);
return waitOnData2Bytes(10);
}
uint16_t Feetech::getGoalTime(uint8_t id) {
sendRequest(id, REQUEST_GOAL_TIME, 2);
return waitOnData2Bytes(10);
}
uint16_t Feetech::getGoalSpeed(uint8_t id) {
sendRequest(id, REQUEST_GOAL_TIME, 2);
return waitOnData2Bytes(10);
}
uint8_t Feetech::getLock(uint8_t id) {
if (feetechMode == MODE_SCS) {
sendRequest(id, REQUEST_LOCK_SCS, 1);
} else if (feetechMode == MODE_STS) {
sendRequest(id, REQUEST_LOCK_SMS_STS, 1);
}
return waitOnData1Byte(10);
}
uint8_t Feetech::setLock(uint8_t id, uint8_t lockEnabled) {
if (feetechMode == MODE_SCS) {
write1Byte(id, REQUEST_LOCK_SCS, lockEnabled);
} else if (feetechMode == MODE_STS) {
write1Byte(id, REQUEST_LOCK_SMS_STS, lockEnabled);
}
return waitOnData1Byte(10);
}
uint8_t Feetech::getVoltage(uint8_t id) {
sendRequest(id, REQUEST_VOLTAGE, 1);
//float voltage = waitOnData1Byte(10) * 0.1;
return waitOnData1Byte(10);
}
uint16_t Feetech::getPosition(uint8_t id) {
sendRequest(id, REQUEST_POSITION, 2);
if (feetechMode == MODE_STS) {
return waitOnData2Bytes(10);
} else if (feetechMode == MODE_SCS) {
return map(flipBytes(waitOnData2Bytes(10)), 0, 1023, 0, 4095);
}
}
int16_t Feetech::getSpeed(uint8_t id) {
sendRequest(id, REQUEST_CURRENT_SPEED, 2);
int16_t val = waitOnData2Bytes(10);
if (val < 0) {
val -= 32767;
val = -val;
}
return val;
}
// NOT SURE ABOUT THIS ONE from 0-1000 one direction, 0-2024 the other.
uint16_t Feetech::getLoad(uint8_t id) {
sendRequest(id, REQUEST_CURRENT_LOAD, 2);
return waitOnData2Bytes(10);
}
uint8_t Feetech::getTemperature(uint8_t id) {
sendRequest(id, REQUEST_TEMPERATURE, 1);
return waitOnData1Byte(10);
}
uint8_t Feetech::getMoving(uint8_t id) {
sendRequest(id, REQUEST_MOVING, 1);
return waitOnData1Byte(10);
}
// Multiplier could be wrong
uint16_t Feetech::getCurrent(uint8_t id) {
sendRequest(id, REQUEST_CURRENT_CURRENT, 2);
//return waitOnData2Bytes(10) * 0.01; FLOAT
return waitOnData2Bytes(10);
}
void Feetech::sendRequest(uint8_t id, byte instruction, uint8_t byteCount) {
uint8_t packet[8];
packet[0] = 0xFF; // Header
packet[1] = 0xFF; // Header
packet[2] = id; // Servo ID (ensure this matches the expected ID, e.g., 0x02)
packet[3] = 0x04; // Length (4 bytes of following data)
packet[4] = READ_DATA; // Instruction
packet[5] = instruction; // Write first address
packet[6] = byteCount; // Number of bytes to read
// Calculate checksum
uint8_t sum = 0;
for (int i = 2; i < 7; i++) sum += packet[i]; // Include all data bytes for checksum
packet[7] = ~sum; // Checksum
setModeTransmit();
serial.write(packet, sizeof(packet));
serial.flush();
setModeReceive();
clearEcho(sizeof(packet));
}
void Feetech::write1Byte(uint8_t id, byte instruction, uint8_t data) {
uint8_t packet[8];
packet[0] = 0xFF; // Header
packet[1] = 0xFF; // Header
packet[2] = id; // Servo ID (ensure this matches the expected ID, e.g., 0x02)
packet[3] = 0x04; // Length (4 bytes of following data)
packet[4] = WRITE_DATA; // Instruction
packet[5] = instruction; // Write first address
packet[6] = data; // Data to write
// Calculate checksum
uint8_t sum = 0;
for (int i = 2; i < 7; i++) sum += packet[i]; // Include all data bytes for checksum
packet[7] = ~sum; // Checksum
setModeTransmit();
serial.write(packet, 8);
serial.flush();
setModeReceive();
}
void Feetech::write2Bytes(uint8_t id, byte instruction, uint16_t data) {
uint8_t packet[9];
packet[0] = 0xFF; // Header
packet[1] = 0xFF; // Header
packet[2] = id; // Servo ID
packet[3] = 5; // Length = instruction + address + 2 bytes + checksum
packet[4] = WRITE_DATA; // Instruction: WRITE
packet[5] = instruction; // Address: Goal Position
if (feetechMode == MODE_SCS) {
packet[6] = (data >> 8) & 0xFF; // High byte
packet[7] = data & 0xFF; // Low byte
} else if (feetechMode == MODE_STS) {
packet[6] = data & 0xFF; // Low byte first for STS
packet[7] = (data >> 8) & 0xFF; // High byte second
}
// Calculate checksum
uint8_t sum = 0;
for (int i = 2; i < 8; i++) sum += packet[i];
packet[8] = ~sum;
// Send packet
setModeTransmit();
serial.write(packet, sizeof(packet));
serial.flush();
setModeReceive();
clearEcho(sizeof(packet));
// Should later recieve a 1 byte response code packet
//return waitOnData2Bytes(10);
}
void Feetech::pingAll(std::vector<uint8_t>& successfulAddresses) {
Serial.println("PINGING ALL 0-255");
successfulAddresses.clear(); // Clear any previous results
for (int i = 0; i < 255; i++) {
//Serial.println(i);
sendPing(i);
uint8_t val = waitOnData1Byte(10);
if (val != 0) {
//Serial.println(val);
successfulAddresses.push_back(i); // Store the successful address
}
}
Serial.println("PINGING COMPLETE");
}
void Feetech::waitOnReply(unsigned long timeout) {
unsigned long startTime = millis(); // Record the start time
while (millis() - startTime < timeout) { // Loop until timeout
if (serial.available()) {
//Serial.println("RECV");
uint8_t buffer[32];
int count = 0;
// Read all available bytes
while (serial.available() && count < sizeof(buffer)) {
buffer[count++] = serial.read();
}
//Serial.println(count);
// Display on Serial
Serial.print("recv: ");
Serial.print(buffer[2]);
Serial.print(" ");
if (buffer[4] == 0x00) {
Serial.print("OK");
} else {
Serial.print("NOK");
}
Serial.print("\t");
for (int i = 0; i < count; i++) {
Serial.print("0x");
if (buffer[i] < 0x10) Serial.print("0");
Serial.print(buffer[i], HEX);
Serial.print(" ");
}
Serial.println();
break; // Exit the loop after processing the reply
}
delay(10); // Small delay to prevent busy-waiting
}
}
uint8_t Feetech::waitOnData1Byte(unsigned long timeout) {
unsigned long startTime = millis();
while (millis() - startTime < timeout) {
if (serial.available()) {
uint8_t buffer[32];
int count = 0;
while (serial.available() && count < sizeof(buffer)) {
buffer[count++] = serial.read();
}
// Look for valid packet starting with 0xFF 0xFF
for (int i = 0; i <= count - 4; i++) {
if (buffer[i] == 0xFF && buffer[i + 1] == 0xFF) {
uint8_t length = buffer[i + 3];
int packetSize = length + 4;
if (i + packetSize <= count) {
// Serial.print("recv: ");
// Serial.print(buffer[i + 2]); // ID
// Serial.print(" ");
// Serial.print(buffer[i + 4] == 0x00 ? "OK" : "NOK");
// Serial.print("\t");
// for (int j = i; j < i + packetSize; j++) {
// Serial.print("0x");
// if (buffer[j] < 0x10) Serial.print("0");
// Serial.print(buffer[j], HEX);
// Serial.print(" ");
// }
// Serial.println();
return buffer[i + 5]; // Return value byte
}
}
}
}
delay(1);
}
return 0;
}
uint16_t Feetech::waitOnData2Bytes(unsigned long timeout) {
unsigned long startTime = millis();
while (millis() - startTime < timeout) {
if (serial.available()) {
uint8_t buffer[32];
int count = 0;
while (serial.available() && count < sizeof(buffer)) {
buffer[count++] = serial.read();
}
// Look for valid packet starting with 0xFF 0xFF
for (int i = 0; i <= count - 5; i++) {
if (buffer[i] == 0xFF && buffer[i + 1] == 0xFF) {
uint8_t length = buffer[i + 3];
int packetSize = length + 4;
if (i + packetSize <= count) {
// Serial.print("recv: ");
// Serial.print(buffer[i + 2]); // ID
// Serial.print(" ");
// Serial.print(buffer[i + 4] == 0x00 ? "OK" : "NOK");
// Serial.print("\t");
// for (int j = i; j < i + packetSize; j++) {
// Serial.print("0x");
// if (buffer[j] < 0x10) Serial.print("0");
// Serial.print(buffer[j], HEX);
// Serial.print(" ");
// }
// Serial.println();
// Combine two bytes into a uint16_t (little-endian)
uint16_t value = buffer[i + 5] | (buffer[i + 6] << 8);
return value;
}
}
}
}
delay(1);
}
return 0;
}
void Feetech::sendData(const byte* data, size_t length) {
// digitalWrite(_transmitPin, HIGH); // Enable transmit mode
// _serial.write(data, length);
// _serial.flush(); // Wait for transmission to complete
// digitalWrite(_transmitPin, LOW); // Disable transmit mode
}
size_t Feetech::receiveData(byte* buffer, size_t bufferSize) {
size_t bytesRead = 0;
while (serial.available() > 0 && bytesRead < bufferSize) {
buffer[bytesRead++] = serial.read();
}
return bytesRead;
}
void Feetech::setModeTransmit() {
// digitalWrite(DE_PIN, HIGH);
// digitalWrite(RE_PIN, HIGH);
//delay(10);
}
void Feetech::setModeReceive() {
// digitalWrite(DE_PIN, LOW);
// digitalWrite(RE_PIN, LOW);
//delay(10);
}
uint16_t Feetech::flipBytes(uint16_t value) {
return (value >> 8) | (value << 8);
}
void Feetech::setFeetechMode(FeetechMode newMode) {
feetechMode = newMode;
}
// Pass motor model number (major) to set SCS/STS mode
void Feetech::setFeetechMode(uint8_t modelMajor) {
feetechMode = static_cast<FeetechMode>(modelMajor);
}
void Feetech::setFeetechMode(uint16_t model) {
uint8_t major = model & 0xFF; // Get major model
setFeetechMode(major);
}
void Feetech::update() {
if (Serial.available()) {
setModeTransmit();
while (Serial.available()) {
char incomingByte = Serial.read(); // Read from USB Serial
serial.write(incomingByte); // Send to Serial1
}
setModeReceive();
}
// Pass data from Serial1 (Feetech) to Serial (USB)
if (serial.available()) {
while (serial.available()) {
char incomingByte = serial.read(); // Read from Serial1
Serial.write(incomingByte); // Send to USB Serial
}
}
}