ArduMY
Arduino communication protocol
ArduMY
Reference for the ArduMY protocol - OctoMY™'s communication system between the Agent software and Arduino hardware controllers.
Did You Know?
ArduMY is designed to be "nimble" - it uses minimal data transfer and memory, making it suitable for resource-constrained Arduino boards. The protocol supports multiple value representations (bit, byte, word, float) so you can balance precision against bandwidth for each actuator type.
ArduMY overview
ArduMY is a protocol designed to communicate robot actuator configuration and state from OctoMY™ to Arduino over a serial link, either wired (D-SUB/USB) or wireless (Bluetooth).
While ArduMY is geared towards Arduino, it is designed to be easily adaptable to other MCU/SOC devices.
Design principles
| Principle | Description |
|---|---|
| Nimble | Uses minimal data in transfer and memory |
| Efficient | Minimizes CPU/MCU load |
| Portable | Standalone sub-project, does not rely on Qt |
| Flexible | Supports many actuator types with optional features |
| Compatible | Runs on any Arduino-compatible device, backwards compatible |
| Robust | Extensive test coverage (unit, integration, fuzzing, stress) |
Architecture
ArduMY provides bidirectional communication between OctoMY™ and Arduino:
Protocol basics
Serial configuration
| Parameter | Value |
|---|---|
| Baud Rate | 115200 (default) |
| Data Bits | 8 |
| Parity | None |
| Stop Bits | 1 |
| Flow Control | None |
Message structure
| Field | Size | Description |
|---|---|---|
| Magic | 2 bytes | 0xAD 0x4D ("AM") |
| Length | 2 bytes | Payload length (little-endian) |
| Type | 1 byte | Message type |
| Flags | 1 byte | Message flags |
| Payload | Variable | Message data |
| CRC | 2 bytes | CRC-16 checksum |
Message types
Command messages (Agent → Arduino)
| Type | Code | Description |
|---|---|---|
| SYNC | 0x00 | Synchronization request |
| CONFIGURE | 0x01 | Send configuration |
| SET_ACTUATOR | 0x02 | Set actuator value |
| SET_ALL_ACTUATORS | 0x03 | Set all actuators |
| ENABLE_ACTUATOR | 0x04 | Enable/disable actuator |
| REQUEST_SENSORS | 0x05 | Request sensor data |
| RESET | 0x0F | Reset Arduino |
Response messages (Arduino → Agent)
| Type | Code | Description |
|---|---|---|
| ACK | 0x80 | Acknowledgment |
| NACK | 0x81 | Negative acknowledgment |
| CONFIG_ACK | 0x82 | Configuration accepted |
| SENSOR_DATA | 0x83 | Sensor readings |
| STATUS | 0x84 | Status report |
| ERROR | 0x8F | Error message |
Configuration protocol
Configuration sequence
Actuator configuration message
struct ActuatorConfigMessage {
quint8 count; // Number of actuators
ActuatorConfig configs[]; // Array of configs
};
struct ActuatorConfig {
quint8 id; // Actuator ID
quint8 type; // ActuatorType enum
quint8 pin; // Primary pin
quint8 pin2; // Secondary pin (optional)
quint8 flags; // Configuration flags
quint16 minValue; // Minimum value
quint16 maxValue; // Maximum value
quint16 defaultValue; // Default value
};
Sensor configuration message
struct SensorConfigMessage {
quint8 count; // Number of sensors
SensorConfig configs[]; // Array of configs
};
struct SensorConfig {
quint8 id; // Sensor ID
quint8 type; // SensorType enum
quint8 pin; // Primary pin
quint8 pin2; // Secondary pin (optional)
quint8 flags; // Configuration flags
quint16 sampleRate; // Sample rate (ms)
};
Actuator commands
SET_ACTUATOR
Set a single actuator value:
struct SetActuatorCommand {
quint8 id; // Actuator ID
qint16 value; // Target value
};
SET_ALL_ACTUATORS
Set all actuators at once (lower latency):
struct SetAllActuatorsCommand {
quint8 count; // Number of values
qint16 values[]; // Array of values
};
ENABLE_ACTUATOR
Enable or disable an actuator:
struct EnableActuatorCommand {
quint8 id; // Actuator ID
quint8 enable; // 1=enable, 0=disable
};
Sensor data
SENSOR_DATA message
struct SensorDataMessage {
quint32 timestamp; // Arduino millis()
quint8 count; // Number of readings
SensorReading readings[]; // Sensor data
};
struct SensorReading {
quint8 id; // Sensor ID
quint8 type; // SensorType
quint16 value; // Sensor value
quint8 quality; // Signal quality (0-100)
};
Sensor polling
Sensors are polled at configured intervals. The Agent can also request immediate readings:
struct RequestSensorsCommand {
quint8 flags; // REQUEST_ALL or specific IDs
quint8 sensorIds[]; // Optional: specific sensors
};
Status messages
STATUS message
Periodic status from Arduino:
struct StatusMessage {
quint32 uptime; // Milliseconds since boot
quint8 actuatorStatus; // Actuator status bitmask
quint8 sensorStatus; // Sensor status bitmask
quint16 freeMemory; // Free SRAM bytes
quint8 errorCount; // Errors since last status
quint8 flags; // Status flags
};
Status flags
| Bit | Flag | Description |
|---|---|---|
| 0 | CONFIGURED | Configuration received |
| 1 | RUNNING | Normal operation |
| 2 | ERROR | Error condition |
| 3 | LOW_MEMORY | Memory warning |
| 4 | MOTORS_ENABLED | Motors are enabled |
| 5 | SENSORS_READY | Sensors calibrated |
Error handling
ERROR message
struct ErrorMessage {
quint8 errorCode; // Error code
quint8 context; // Error context (actuator/sensor ID)
char message[32]; // Human-readable message
};
Error codes
| Code | Name | Description |
|---|---|---|
| 0x01 | INVALID_CONFIG | Invalid configuration |
| 0x02 | INVALID_ACTUATOR | Unknown actuator ID |
| 0x03 | INVALID_SENSOR | Unknown sensor ID |
| 0x04 | VALUE_OUT_OF_RANGE | Value exceeds limits |
| 0x05 | HARDWARE_FAULT | Hardware error detected |
| 0x06 | BUFFER_OVERFLOW | Message buffer full |
| 0x07 | CRC_ERROR | CRC mismatch |
| 0x08 | TIMEOUT | Operation timed out |
Parsers and serializers
ArduMYCommandParser
Parses incoming messages from Arduino:
class ArduMYCommandParser {
public:
void feed(quint8 byte);
void feed(const QByteArray& data);
bool hasMessage() const;
ArduMYMessage takeMessage();
void reset();
signals:
void messageReceived(const ArduMYMessage& msg);
void parseError(const QString& error);
private:
enum State {
WAITING_MAGIC_1,
WAITING_MAGIC_2,
READING_LENGTH_1,
READING_LENGTH_2,
READING_TYPE,
READING_FLAGS,
READING_PAYLOAD,
READING_CRC_1,
READING_CRC_2
};
};
ArduMYCommandSerializer
Serializes outgoing messages:
class ArduMYCommandSerializer {
public:
QByteArray serializeSync();
QByteArray serializeConfig(const ArduMYActuatorSet& actuators);
QByteArray serializeSetActuator(quint8 id, qint16 value);
QByteArray serializeSetAllActuators(const QVector<qint16>& values);
QByteArray serializeReset();
private:
QByteArray buildMessage(quint8 type, const QByteArray& payload);
quint16 calculateCRC(const QByteArray& data);
};
Magic detection
MagicDetector
Detects ArduMY protocol in serial stream:
class MagicDetector {
public:
void feed(quint8 byte);
bool detected() const;
void reset();
signals:
void magicDetected();
private:
bool mSeenAD = false;
bool mDetected = false;
};
Sync sequence
The Agent sends SYNC to establish communication:
Arduino firmware
ArduMYMain
Main firmware entry point:
class ArduMYMain {
public:
void setup();
void loop();
private:
void processCommand(const ArduMYMessage& msg);
void sendSensorData();
void sendStatus();
ArduMYCommandParser mParser;
ArduMYActuatorSet mActuators;
unsigned long mLastSensorUpdate;
unsigned long mLastStatusUpdate;
};
// main.cpp
ArduMYMain ardumy;
void setup() {
ardumy.setup();
}
void loop() {
ardumy.loop();
}
BoardInfo
Hardware information:
struct BoardInfo {
const char* boardName;
quint8 digitalPins;
quint8 analogPins;
quint8 pwmPins;
quint32 cpuFreq;
quint32 sramSize;
};
// Board detection
#if defined(ARDUINO_AVR_UNO)
static const BoardInfo board = {"Uno", 14, 6, 6, 16000000, 2048};
#elif defined(ARDUINO_AVR_MEGA2560)
static const BoardInfo board = {"Mega", 54, 16, 15, 16000000, 8192};
#endif
Value encoding
Representation types
ArduMY supports multiple value representations for bandwidth efficiency:
| Type | Size | Use Case |
|---|---|---|
| bit | 1 bit | Relay on/off, position left/right |
| byte | 8 bits | Coarse positioning, tri-state |
| word | 16 bits | Balance of size and precision |
| double-word | 32 bits | High precision |
| quad-word | 64 bits | Maximum precision |
| float | 32 bits | High precision with range |
| double | 64 bits | Maximum precision with range |
ArduMYActuatorValue
Union type for actuator values:
union ArduMYActuatorValue {
qint8 byteValue;
quint8 ubyteValue;
qint16 wordValue;
quint16 uwordValue;
qint32 dwordValue;
quint32 udwordValue;
float floatValue;
QByteArray serialize(ValueType type) const;
static ArduMYActuatorValue deserialize(const QByteArray& data,
ValueType type);
};
Type conversions
class ArduMYTypeConversions {
public:
// Convert between representations
static qint16 floatToWord(float value, float min, float max);
static float wordToFloat(qint16 value, float min, float max);
// Servo angle conversion
static quint16 angleToMicroseconds(float angle,
float minAngle, float maxAngle,
quint16 minUs, quint16 maxUs);
static float microsecondsToAngle(quint16 us,
quint16 minUs, quint16 maxUs,
float minAngle, float maxAngle);
};
Using ArduMY
Qt integration
// Create serial connection
QSerialPort* serial = new QSerialPort(this);
serial->setPortName("/dev/ttyUSB0");
serial->setBaudRate(115200);
serial->open(QIODevice::ReadWrite);
// Create ArduMY interface
ArduMY* ardumy = new ArduMY(serial, this);
// Configure actuators
ArduMYActuatorSet actuators;
actuators.addActuator(/* ... */);
ardumy->configure(actuators);
// Set actuator values
ardumy->setActuator(0, 100);
ardumy->setAllActuators({100, 100, 90, 45});
// Receive sensor data
connect(ardumy, &ArduMY::sensorData,
[](const SensorDataMessage& data) {
for (int i = 0; i < data.count; i++) {
qDebug() << "Sensor" << data.readings[i].id
<< "=" << data.readings[i].value;
}
});
Pin configuration widget
class ArduinoPinCombobox : public QComboBox {
public:
void setBoardInfo(const BoardInfo& board);
void setFilter(ArduinoPinFilter filter);
int selectedPin() const;
void setSelectedPin(int pin);
};
enum ArduinoPinFilter {
FILTER_ALL,
FILTER_DIGITAL,
FILTER_ANALOG,
FILTER_PWM,
FILTER_INTERRUPT,
};
Troubleshooting
Common issues
| Issue | Cause | Solution |
|---|---|---|
| No connection | Wrong port/baud | Verify serial settings |
| CRC errors | Noise/interference | Shorten cables, add shielding |
| Actuators don't respond | Not configured | Send configuration first |
| Sensors not updating | Wrong sample rate | Check sensor config |
| Arduino resets | Power issues | Use external power |
Debugging
// Enable protocol debugging
QLoggingCategory::setFilterRules("octomy.ardumy.debug=true");
// Log raw bytes
ardumy->setRawLogging(true);
// Check connection status
qDebug() << "Connected:" << ardumy->isConnected()
<< "Configured:" << ardumy->isConfigured()
<< "Status:" << ardumy->lastStatus();
Serial monitor
Monitor ArduMY traffic:
# Linux
screen /dev/ttyUSB0 115200
# Or use hexdump for raw bytes
stty -F /dev/ttyUSB0 115200
hexdump -C /dev/ttyUSB0