English
English
简体中文
日本語

Arduino Quick Start

2. Devices & Examples

5. Extensions

6. Applications

Unit 8Servos2-Chain Arduino Tutorial

1. Preparation

2. Notes

Pin Compatibility
Since the pin configurations differ between host devices, M5Stack provides a pin compatibility table to make it easier to check the pin assignments. Modify the example program according to the actual pin connections.

3. Example Program

  • This tutorial uses CoreS3 as the host device and Unit 8Servos2-Chain to control servos. Unit 8Servos2-Chain communicates with the host through a serial port. After the devices are connected, the corresponding pins are G17 (TXD) and G18 (RXD).
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
#include <M5Unified.h>
#include "M5Chain.h"

#define TXD_PIN             17
#define RXD_PIN             18
#define SERVO_CHANNEL_COUNT 8
#define ANGLE_STEP          20
#define LOOP_DELAY_MS       200

// Chain state and device list.
Chain M5Chain;
device_list_t *devices_list = nullptr;
uint16_t device_nums = 0;
uint8_t operation_status = 0;
uint8_t angle = 0;
bool servo_ready = false;

void showMessage(const char *message)
{
    // Show a status message on the display.
    M5.Display.clear();
    M5.Display.setCursor(0, 0);
    M5.Display.println(message);
}

bool updateDeviceList()
{
    // Discover all devices on the Chain bus.
    if (!M5Chain.isDeviceConnected()) {
        Serial.println("Chain device not connected");
        return false;
    }

    if (M5Chain.getDeviceNum(&device_nums) != CHAIN_OK || device_nums == 0) {
        Serial.println("Failed to get Chain device count");
        return false;
    }

    // Allocate storage for the device list returned by M5Chain.
    devices_list = (device_list_t *)malloc(sizeof(device_list_t));
    if (devices_list == nullptr) {
        Serial.println("Failed to allocate device list");
        return false;
    }

    devices_list->count = device_nums;
    devices_list->devices = (device_info_t *)malloc(sizeof(device_info_t) * device_nums);
    if (devices_list->devices == nullptr) {
        free(devices_list);
        devices_list = nullptr;
        Serial.println("Failed to allocate device information");
        return false;
    }

    if (!M5Chain.getDeviceList(devices_list)) {
        free(devices_list->devices);
        free(devices_list);
        devices_list = nullptr;
        Serial.println("Failed to get Chain device list");
        return false;
    }

    // Print the detected device IDs and types.
    Serial.printf("Chain device count: %u\r\n", devices_list->count);
    for (uint8_t i = 0; i < devices_list->count; i++) {
        Serial.printf("ID[%u], type: 0x%02X\r\n", devices_list->devices[i].id,
                      devices_list->devices[i].device_type);
    }
    return true;
}

bool initializeServoMode()
{
    if (devices_list == nullptr) {
        return false;
    }

    // Configure all eight channels as servo outputs.
    user_gpio_mode_t modes[SERVO_CHANNEL_COUNT];
    for (uint8_t i = 0; i < SERVO_CHANNEL_COUNT; i++) {
        modes[i] = USER_GPIO_SERVO_MODE;
    }

    bool found = false;
    for (uint8_t i = 0; i < devices_list->count; i++) {
        if (devices_list->devices[i].device_type != UNIT_8SERVOS2_CHAIN_TYPE_CODE) {
            continue;
        }

        found = true;
        uint8_t device_id = devices_list->devices[i].id;
        chain_status_t status = M5Chain.setServosModeAll(
            device_id, modes, SERVO_CHANNEL_COUNT, &operation_status);
        if (status != CHAIN_OK || operation_status != 1) {
            Serial.printf("ID[%u] servo mode setup failed\r\n", device_id);
            continue;
        }

        Serial.printf("ID[%u] servo mode setup success\r\n", device_id);
    }
    return found;
}

bool setAndVerifyAngle(uint8_t target_angle)
{
    if (devices_list == nullptr) {
        return false;
    }

    // Set the same target angle on every servo channel.
    uint8_t target_angles[SERVO_CHANNEL_COUNT];
    uint8_t read_angles[SERVO_CHANNEL_COUNT] = {0};
    for (uint8_t i = 0; i < SERVO_CHANNEL_COUNT; i++) {
        target_angles[i] = target_angle;
    }

    bool all_success = true;
    for (uint8_t i = 0; i < devices_list->count; i++) {
        if (devices_list->devices[i].device_type != UNIT_8SERVOS2_CHAIN_TYPE_CODE) {
            continue;
        }

        uint8_t device_id = devices_list->devices[i].id;
        chain_status_t status = M5Chain.setServosAngleAll(
            device_id, target_angles, SERVO_CHANNEL_COUNT, &operation_status);
        if (status != CHAIN_OK || operation_status != 1 ||
            M5Chain.getServosAngleAll(device_id, read_angles, SERVO_CHANNEL_COUNT) != CHAIN_OK) {
            Serial.printf("ID[%u] angle operation failed\r\n", device_id);
            all_success = false;
            continue;
        }

        // Verify every channel by reading the angle back.
        for (uint8_t channel = 0; channel < SERVO_CHANNEL_COUNT; channel++) {
            if (read_angles[channel] != target_angle) {
                Serial.printf("ID[%u] CH[%u] angle mismatch: %u / %u\r\n",
                              device_id, channel, target_angle, read_angles[channel]);
                all_success = false;
            }
        }
    }
    return all_success;
}

bool readAndDisplayPower(uint8_t current_angle)
{
    if (devices_list == nullptr) {
        return false;
    }

    // Read voltage and current telemetry from each matching device.
    bool all_success = true;
    bool display_updated = false;
    for (uint8_t i = 0; i < devices_list->count; i++) {
        if (devices_list->devices[i].device_type != UNIT_8SERVOS2_CHAIN_TYPE_CODE) {
            continue;
        }

        uint8_t device_id = devices_list->devices[i].id;
        uint16_t dc_voltage = 0;
        uint16_t grove_voltage = 0;
        uint16_t system_current = 0;
        chain_status_t dc_status = M5Chain.getServosDcVoltage(device_id, &dc_voltage);
        chain_status_t grove_status = M5Chain.getServosGroveVoltage(device_id, &grove_voltage);
        chain_status_t current_status = M5Chain.getServosSysCurrent(device_id, &system_current);

        if (dc_status != CHAIN_OK || grove_status != CHAIN_OK || current_status != CHAIN_OK) {
            Serial.printf("ID[%u] power monitor failed: DC=%d, Grove=%d, Current=%d\r\n",
                          device_id, dc_status, grove_status, current_status);
            all_success = false;
            continue;
        }

        Serial.printf("ID[%u] power: DC=%umV, Grove=%umV, Current=%umA\r\n",
                      device_id, dc_voltage, grove_voltage, system_current);

        // Display the first matching device while logging all devices.
        if (!display_updated) {
            M5.Display.clear();
            M5.Display.setCursor(0, 0);
            M5.Display.println("Unit 8Servos2-Chain");
            M5.Display.setCursor(0, 40);
            M5.Display.printf("Angle: %u", current_angle);
            M5.Display.setCursor(0, 80);
            M5.Display.printf("DC: %umV", dc_voltage);
            M5.Display.setCursor(0, 120);
            M5.Display.printf("Current: %umA", system_current);
            M5.Display.setCursor(0, 160);
            M5.Display.printf("Grove: %umV", grove_voltage);
            display_updated = true;
        }
    }
    return all_success;
}

void setup()
{
    M5.begin();
    M5.Display.setFont(&fonts::FreeMonoBold12pt7b);
    M5.Display.setTextSize(1);
    Serial.begin(115200);
    Serial.println("Unit 8Servos2-Chain Test");

    // Start Chain UART communication.
    M5Chain.begin(&Serial2, 115200, RXD_PIN, TXD_PIN);
    if (!updateDeviceList()) {
        showMessage("Chain device not found");
        return;
    }

    servo_ready = initializeServoMode();
    if (!servo_ready) {
        showMessage("8Servos2-Chain not found");
        return;
    }
    showMessage("8Servos2-Chain ready");
}

void loop()
{
    if (!servo_ready) {
        delay(LOOP_DELAY_MS);
        return;
    }

    // Set the next angle and verify the response.
    if (setAndVerifyAngle(angle)) {
        Serial.printf("All servo channels set to %u degrees\r\n", angle);
    } else {
        Serial.println("Servo angle operation failed");
    }

    // Update the power monitor display and serial log.
    readAndDisplayPower(angle);

    // Advance through the 0-180 degree test range.
    angle += ANGLE_STEP;
    if (angle > 180) {
        angle = 0;
    }
    delay(LOOP_DELAY_MS);
}

4. Compile and Upload

  • Copy and paste the example program above into the project code area, select the device port (for details, refer to Program Compilation and Upload), click the Compile and Upload button in the upper-left corner of Arduino IDE, and wait for the program to finish compiling and uploading to the device.

5. Example Result

  • After the device is powered on, the program outputs the Chain bus device count, device IDs, and device types to the serial monitor, then searches for Unit 8Servos2-Chain. After the device is found, the program configures all channels as servo mode, cycles through servo angles from 0° to 180° in 20° increments, and reads the DC input voltage, Grove interface voltage, and total system current. When multiple Unit 8Servos2-Chain devices are connected, the serial port outputs the monitoring data for each device, while the display shows the data from the first detected device.

Example serial output:

Unit 8Servos2-Chain Test
Chain device count: 1
ID[1], type: 0x0C
ID[1] servo mode setup success
All servo channels set to 0 degrees
ID[1] power: DC=7194mV, Grove=5062mV, Current=10mA
All servo channels set to 20 degrees
ID[1] power: DC=6358mV, Grove=5062mV, Current=250mA
All servo channels set to 40 degrees
ID[1] power: DC=6336mV, Grove=5058mV, Current=10mA
All servo channels set to 60 degrees
ID[1] power: DC=5456mV, Grove=4832mV, Current=720mA
All servo channels set to 80 degrees
ID[1] power: DC=5434mV, Grove=5062mV, Current=10mA
All servo channels set to 100 degrees
ID[1] power: DC=5126mV, Grove=2584mV, Current=714mA
All servo channels set to 120 degrees
ID[1] power: DC=5434mV, Grove=5064mV, Current=286mA
All servo channels set to 140 degrees
ID[1] power: DC=5247mV, Grove=5062mV, Current=12mA
All servo channels set to 160 degrees
ID[1] power: DC=5456mV, Grove=5056mV, Current=992mA
All servo channels set to 180 degrees
Page Tools
PDF
On This Page