added serial passthrough define option at astart of HansonServo.ino for using feetech app
parent
4375fb283f
commit
d603ef2e18
183
HansonServo.ino
183
HansonServo.ino
|
|
@ -1,24 +1,29 @@
|
||||||
/**
|
/**
|
||||||
* HansonServo Firmware
|
* HansonServo Firmware
|
||||||
*
|
*
|
||||||
* Unified robot controller with:
|
* Unified robot controller with:
|
||||||
* - Feetech servo control (SCS/STS)
|
* - Feetech servo control (SCS/STS)
|
||||||
* - Animation playback via node graphs
|
* - Animation playback via node graphs
|
||||||
* - BNO055 IMU
|
* - ADXL345 IMU
|
||||||
* - RD-03D mmWave radar
|
* - RD-03D mmWave radar
|
||||||
* - CRC16 tagged packet protocol
|
* - CRC16 tagged packet protocol
|
||||||
*
|
*
|
||||||
* Protocol: 0xA5 0x5A [TAG 4B][LEN 2B][SEQ 2B][PAYLOAD][CRC16 2B]
|
* Protocol: 0xA5 0x5A [TAG 4B][LEN 2B][SEQ 2B][PAYLOAD][CRC16 2B]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <FFat.h>
|
// Configuration defines
|
||||||
#include "protocol.h"
|
#define ENABLE_SERIAL_PASSTHROUGH \
|
||||||
|
false // Set true to use Feetech app comms straight to servos
|
||||||
|
|
||||||
|
#include "RobotConfig.h"
|
||||||
|
#include "animation.h"
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
#include "motorcontrol.h"
|
#include "motorcontrol.h"
|
||||||
#include "animation.h"
|
|
||||||
#include "nodegraph.h"
|
#include "nodegraph.h"
|
||||||
#include "RobotConfig.h"
|
#include "protocol.h"
|
||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
|
#include <FFat.h>
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Global State
|
// Global State
|
||||||
|
|
@ -37,9 +42,9 @@ constexpr uint32_t HEARTBEAT_INTERVAL_MS = 1000;
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
uint16_t getSineWaveValue(unsigned long centiseconds) {
|
uint16_t getSineWaveValue(unsigned long centiseconds) {
|
||||||
constexpr uint16_t WAVE_PERIOD_CS = 400; // 4 seconds
|
constexpr uint16_t WAVE_PERIOD_CS = 400; // 4 seconds
|
||||||
constexpr uint16_t WAVE_MAX = 4095;
|
constexpr uint16_t WAVE_MAX = 4095;
|
||||||
|
|
||||||
float theta = (2.0 * PI * centiseconds) / WAVE_PERIOD_CS;
|
float theta = (2.0 * PI * centiseconds) / WAVE_PERIOD_CS;
|
||||||
float sine = sin(theta);
|
float sine = sin(theta);
|
||||||
float scaled = (sine + 1.0) * (WAVE_MAX / 2.0);
|
float scaled = (sine + 1.0) * (WAVE_MAX / 2.0);
|
||||||
|
|
@ -56,6 +61,24 @@ void handleProtocol() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Serial Passthrough (USB ↔ Servo Serial)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#if ENABLE_SERIAL_PASSTHROUGH
|
||||||
|
void handleSerialPassthrough() {
|
||||||
|
// Forward USB Serial → Servo Serial1
|
||||||
|
while (Serial.available()) {
|
||||||
|
Serial1.write(Serial.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward Servo Serial1 → USB Serial
|
||||||
|
while (Serial1.available()) {
|
||||||
|
Serial.write(Serial1.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Animation Playback
|
// Animation Playback
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -80,28 +103,29 @@ void runNodeAnimation() {
|
||||||
config.enableAllMotors();
|
config.enableAllMotors();
|
||||||
|
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (now - lastTickTime < FRAME_INTERVAL_MS) return;
|
if (now - lastTickTime < FRAME_INTERVAL_MS)
|
||||||
lastTickTime = now;
|
return;
|
||||||
|
lastTickTime = now;
|
||||||
|
|
||||||
// Tick the node graph
|
// Tick the node graph
|
||||||
animState.current->nodeGraph.tick(currentTick, *animState.current);
|
animState.current->nodeGraph.tick(currentTick, *animState.current);
|
||||||
auto outputs = animState.current->nodeGraph.getServoOutputs();
|
auto outputs = animState.current->nodeGraph.getServoOutputs();
|
||||||
|
|
||||||
// Collect motor commands
|
// Collect motor commands
|
||||||
std::vector<uint8_t> motorIDs;
|
std::vector<uint8_t> motorIDs;
|
||||||
std::vector<uint16_t> positions;
|
std::vector<uint16_t> positions;
|
||||||
std::vector<uint16_t> speeds;
|
std::vector<uint16_t> speeds;
|
||||||
|
|
||||||
for (const auto& [motorID, value] : outputs) {
|
for (const auto &[motorID, value] : outputs) {
|
||||||
if (value != 65535) {
|
if (value != 65535) {
|
||||||
motorIDs.push_back(motorID);
|
motorIDs.push_back(motorID);
|
||||||
positions.push_back(value);
|
positions.push_back(value);
|
||||||
speeds.push_back(0);
|
speeds.push_back(0);
|
||||||
config.setMotorPosition(motorID, value);
|
config.setMotorPosition(motorID, value);
|
||||||
config.setMotorEnabled(motorID, true);
|
config.setMotorEnabled(motorID, true);
|
||||||
} else {
|
} else {
|
||||||
// Only disable torque for motors that should be limp
|
// Only disable torque for motors that should be limp
|
||||||
if (config.setMotorEnabled(motorID, false)) {
|
if (config.setMotorEnabled(motorID, false)) {
|
||||||
servoManager[0]->disableTorque(motorID);
|
servoManager[0]->disableTorque(motorID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,14 +133,8 @@ void runNodeAnimation() {
|
||||||
|
|
||||||
// Send all positions in one sync write - motors move together!
|
// Send all positions in one sync write - motors move together!
|
||||||
if (!motorIDs.empty()) {
|
if (!motorIDs.empty()) {
|
||||||
servoManager.syncWritePositions(
|
servoManager.syncWritePositions(motorIDs.data(), positions.data(),
|
||||||
motorIDs.data(),
|
speeds.data(), motorIDs.size(), config, 0);
|
||||||
positions.data(),
|
|
||||||
speeds.data(),
|
|
||||||
motorIDs.size(),
|
|
||||||
config,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit per-frame event: [frameLo, frameHi, playMode, status=0]
|
// Emit per-frame event: [frameLo, frameHi, playMode, status=0]
|
||||||
|
|
@ -125,45 +143,45 @@ void runNodeAnimation() {
|
||||||
payload[0] = currentTick & 0xFF;
|
payload[0] = currentTick & 0xFF;
|
||||||
payload[1] = (currentTick >> 8) & 0xFF;
|
payload[1] = (currentTick >> 8) & 0xFF;
|
||||||
payload[2] = static_cast<uint8_t>(animState.playMode);
|
payload[2] = static_cast<uint8_t>(animState.playMode);
|
||||||
payload[3] = 0; // in-progress
|
payload[3] = 0; // in-progress
|
||||||
sendPacket(Tag::FRAME, payload, 4);
|
sendPacket(Tag::FRAME, payload, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTick++;
|
currentTick++;
|
||||||
|
|
||||||
// Handle animation end (0 = run indefinitely for variable-only animations)
|
// Handle animation end (0 = run indefinitely for variable-only animations)
|
||||||
if (animState.current->getFrameCount() > 0 &&
|
if (animState.current->getFrameCount() > 0 &&
|
||||||
currentTick > animState.current->getFrameCount()) {
|
currentTick > animState.current->getFrameCount()) {
|
||||||
switch (animState.playMode) {
|
switch (animState.playMode) {
|
||||||
case PLAY_ONCE:
|
case PLAY_ONCE:
|
||||||
animState.stop();
|
animState.stop();
|
||||||
{
|
{
|
||||||
uint8_t done[4];
|
uint8_t done[4];
|
||||||
done[0] = currentTick & 0xFF;
|
done[0] = currentTick & 0xFF;
|
||||||
done[1] = (currentTick >> 8) & 0xFF;
|
done[1] = (currentTick >> 8) & 0xFF;
|
||||||
done[2] = static_cast<uint8_t>(animState.playMode);
|
done[2] = static_cast<uint8_t>(animState.playMode);
|
||||||
done[3] = 1; // complete
|
done[3] = 1; // complete
|
||||||
sendPacket(Tag::FRAME, done, 4);
|
sendPacket(Tag::FRAME, done, 4);
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PLAY_LOOP:
|
|
||||||
// Continue looping
|
|
||||||
break;
|
|
||||||
case PLAY_REPEAT:
|
|
||||||
if (--animState.repeatsRemaining == 0) {
|
|
||||||
animState.stop();
|
|
||||||
uint8_t done[4];
|
|
||||||
done[0] = currentTick & 0xFF;
|
|
||||||
done[1] = (currentTick >> 8) & 0xFF;
|
|
||||||
done[2] = static_cast<uint8_t>(animState.playMode);
|
|
||||||
done[3] = 1; // complete
|
|
||||||
sendPacket(Tag::FRAME, done, 4);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
currentTick = 0;
|
break;
|
||||||
|
case PLAY_LOOP:
|
||||||
|
// Continue looping
|
||||||
|
break;
|
||||||
|
case PLAY_REPEAT:
|
||||||
|
if (--animState.repeatsRemaining == 0) {
|
||||||
|
animState.stop();
|
||||||
|
uint8_t done[4];
|
||||||
|
done[0] = currentTick & 0xFF;
|
||||||
|
done[1] = (currentTick >> 8) & 0xFF;
|
||||||
|
done[2] = static_cast<uint8_t>(animState.playMode);
|
||||||
|
done[3] = 1; // complete
|
||||||
|
sendPacket(Tag::FRAME, done, 4);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentTick = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,10 +192,11 @@ void runNodeAnimation() {
|
||||||
void updateMotorPositions() {
|
void updateMotorPositions() {
|
||||||
static unsigned long lastUpdate = 0;
|
static unsigned long lastUpdate = 0;
|
||||||
|
|
||||||
if (millis() - lastUpdate < MOTOR_UPDATE_INTERVAL_MS) return;
|
if (millis() - lastUpdate < MOTOR_UPDATE_INTERVAL_MS)
|
||||||
|
return;
|
||||||
lastUpdate = millis();
|
lastUpdate = millis();
|
||||||
|
|
||||||
for (const Motor& motor : config.motors) {
|
for (const Motor &motor : config.motors) {
|
||||||
servoManager[0]->setFeetechMode(motor.servoModel.major);
|
servoManager[0]->setFeetechMode(motor.servoModel.major);
|
||||||
uint16_t position = servoManager[0]->getPosition(motor.motorID);
|
uint16_t position = servoManager[0]->getPosition(motor.motorID);
|
||||||
config.setMotorPosition(motor.motorID, position);
|
config.setMotorPosition(motor.motorID, position);
|
||||||
|
|
@ -196,21 +215,27 @@ void handleMotorStreaming() {
|
||||||
|
|
||||||
void sendHeartbeat() {
|
void sendHeartbeat() {
|
||||||
static unsigned long lastHeartbeat = 0;
|
static unsigned long lastHeartbeat = 0;
|
||||||
|
|
||||||
if (millis() - lastHeartbeat < HEARTBEAT_INTERVAL_MS) return;
|
if (millis() - lastHeartbeat < HEARTBEAT_INTERVAL_MS)
|
||||||
|
return;
|
||||||
lastHeartbeat = millis();
|
lastHeartbeat = millis();
|
||||||
|
|
||||||
// Pack state: uptime(4) + flags(2)
|
// Pack state: uptime(4) + flags(2)
|
||||||
uint8_t payload[6];
|
uint8_t payload[6];
|
||||||
uint32_t uptime = millis() / 1000;
|
uint32_t uptime = millis() / 1000;
|
||||||
uint16_t flags = 0;
|
uint16_t flags = 0;
|
||||||
|
|
||||||
// Build flags
|
// Build flags
|
||||||
if (adxl.isReady()) flags |= 0x01;
|
if (adxl.isReady())
|
||||||
if (animState.current && animState.current->isActive()) flags |= 0x02;
|
flags |= 0x01;
|
||||||
if (motorStream.active) flags |= 0x04;
|
if (animState.current && animState.current->isActive())
|
||||||
if (sensors.isADXLStreamEnabled()) flags |= 0x08;
|
flags |= 0x02;
|
||||||
if (sensors.isRadarStreamEnabled()) flags |= 0x10;
|
if (motorStream.active)
|
||||||
|
flags |= 0x04;
|
||||||
|
if (sensors.isADXLStreamEnabled())
|
||||||
|
flags |= 0x08;
|
||||||
|
if (sensors.isRadarStreamEnabled())
|
||||||
|
flags |= 0x10;
|
||||||
|
|
||||||
payload[0] = uptime & 0xFF;
|
payload[0] = uptime & 0xFF;
|
||||||
payload[1] = (uptime >> 8) & 0xFF;
|
payload[1] = (uptime >> 8) & 0xFF;
|
||||||
|
|
@ -230,7 +255,7 @@ void setup() {
|
||||||
// Serial setup (buffer size must be set before begin)
|
// Serial setup (buffer size must be set before begin)
|
||||||
Serial.setRxBufferSize(8192);
|
Serial.setRxBufferSize(8192);
|
||||||
Serial.begin(1000000);
|
Serial.begin(1000000);
|
||||||
|
|
||||||
// Startup delay
|
// Startup delay
|
||||||
delay(500);
|
delay(500);
|
||||||
Serial.println("\n[HansonServo] Starting...");
|
Serial.println("\n[HansonServo] Starting...");
|
||||||
|
|
@ -265,19 +290,25 @@ void setup() {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
// Serial passthrough (when enabled)
|
||||||
|
#if ENABLE_SERIAL_PASSTHROUGH
|
||||||
|
handleSerialPassthrough();
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Protocol handling
|
// Protocol handling
|
||||||
handleProtocol();
|
handleProtocol();
|
||||||
|
|
||||||
// Animation playback
|
// Animation playback
|
||||||
runNodeAnimation();
|
runNodeAnimation();
|
||||||
|
|
||||||
// Motor position updates
|
// Motor position updates
|
||||||
updateMotorPositions();
|
updateMotorPositions();
|
||||||
handleMotorStreaming();
|
handleMotorStreaming();
|
||||||
|
|
||||||
// Sensor updates and streaming
|
// Sensor updates and streaming
|
||||||
sensors.update();
|
sensors.update();
|
||||||
|
|
||||||
// Heartbeat
|
// Heartbeat
|
||||||
sendHeartbeat();
|
sendHeartbeat();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue