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:

ArduMY Architecture


Protocol basics

Serial configuration

Parameter Value
Baud Rate 115200 (default)
Data Bits 8
Parity None
Stop Bits 1
Flow Control None

Message structure

ArduMY Message

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

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:

Sync Sequence


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

In this section
Topics
reference hardware ArduMY protocol Arduino serial
See also