简体中文
English
简体中文
日本語

Arduino 上手教程

2. 设备开发 & 案例程序

5. 扩展模块

6. 应用案例

Unit EncoderMotor Driver Arduino 使用教程

1. 准备工作

2. 注意事项

引脚兼容性
由于每款主机的引脚配置不同,为了让用户更方便地使用,M5Stack 官方提供了引脚兼容性表,方便用户查看,请根据实际引脚连接情况修改案例程序。

3. 案例程序

  • 本教程中使用的主控设备为 CoreS3,搭配 Unit EncoderMotor Driver 控制编码器电机。Unit EncoderMotor Driver 采用 I2C 协议通信,设备连接后对应的引脚为 G2 (SDA)、G1 (SCL)。
  • 下方分别提供驱动单元支持的三种控制方式的例程:PWM 模式(开环调速)、速度模式(速度环)、位置模式(位置环),另有软启动 / 软停止、RGB 指示灯两个例程。
  • 所有例程都使用驱动库的 M5UnitEncoderMotor 类,构造时传入产品选择器 M5Unit_EncoderMotor_Driver(默认地址 0x2F,单通道);驱动库通过 #include <M5Unit-EncoderMotor-Driver.h> 引入,显示功能还需使用上述 M5Unified 和 M5GFX 库。

3.1 PWM 模式(开环调速)

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
#include <M5Unified.h>
#include <M5Unit-EncoderMotor-Driver.h>

// Product selector sets the default address and channel count:
// U232 = 0x2F (1 channel), U233 = 0x24 (4 channels)
M5UnitEncoderMotor motor(M5Unit_EncoderMotor_Driver);

// Draw into an offscreen canvas so a full screen refresh does not flicker
M5Canvas canvas(&M5.Display);

constexpr int8_t I2C_SDA_PIN     = 2;  // CoreS3 PORT.A
constexpr int8_t I2C_SCL_PIN     = 1;
constexpr uint32_t I2C_FREQUENCY = 400000;
constexpr int8_t PWM_DUTY        = 100;   // -127 ~ 127
constexpr uint32_t RUN_MS        = 3000;  // Duration of each segment
constexpr uint32_t STEP_MS       = 200;   // Sampling interval

void halt(const char *message)
{
    Serial.println(message);
    while (true) {
        delay(1000);
    }
}

// Center the status screen and mix three font sizes so the key value stands out.
void showScreen(const char *title, const char *value, const char *detail1, const char *detail2)
{
    canvas.fillScreen(TFT_BLACK);
    canvas.setTextDatum(middle_center);

    canvas.setFont(&fonts::DejaVu18);
    canvas.setTextColor(TFT_LIGHTGREY);
    canvas.drawString(title, canvas.width() / 2, 32);

    canvas.setFont(&fonts::DejaVu40);
    canvas.setTextColor(TFT_WHITE);
    canvas.drawString(value, canvas.width() / 2, 112);

    canvas.setFont(&fonts::DejaVu12);
    canvas.setTextColor(TFT_LIGHTGREY);
    if (detail1 && detail1[0]) {
        canvas.drawString(detail1, canvas.width() / 2, 176);
    }
    if (detail2 && detail2[0]) {
        canvas.drawString(detail2, canvas.width() / 2, 204);
    }

    canvas.pushSprite(0, 0);
}

void drawStatus(const char *state, int8_t duty)
{
    int8_t speed = 0;
    int32_t encoder = 0;
    motor.getSpeed(speed);
    motor.getEncoder(encoder);

    char value[16];
    char detail1[48];
    char detail2[48];
    snprintf(value, sizeof(value), "%+d", speed);
    snprintf(detail1, sizeof(detail1), "%s   duty %+d", state, duty);
    snprintf(detail2, sizeof(detail2), "encoder %ld", static_cast<long>(encoder));
    showScreen("PWM", value, detail1, detail2);
}

// Apply the duty cycle and keep reporting speed and encoder counts
void holdDuty(const char *state, int8_t duty, uint32_t duration_ms)
{
    if (!motor.setPWMDuty(duty)) {
        halt("Failed to set PWM duty.");
    }
    for (uint32_t elapsed = 0; elapsed < duration_ms; elapsed += STEP_MS) {
        int8_t speed = 0;
        int32_t encoder = 0;
        motor.getSpeed(speed);
        motor.getEncoder(encoder);
        drawStatus(state, duty);
        Serial.printf("%s duty=%+d speed=%+d encoder=%ld\n", state, duty, speed,
                      static_cast<long>(encoder));
        delay(STEP_MS);
    }
}

void setup()
{
    auto cfg = M5.config();
    cfg.output_power = true;
    M5.begin(cfg);
    canvas.createSprite(M5.Display.width(), M5.Display.height());
    Serial.begin(115200);

    // Address 0 selects the default address of the selected product
    while (!motor.begin(&Wire, 0, I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQUENCY)) {
        Serial.println("Unit EncoderMotor Driver not found");
        delay(1000);
    }
    delay(100);  // give the unit time to finish the previous write
    // Soft start is saved in the unit flash; turn it off so this demo runs without a ramp
    if (!motor.setSoftStartStop(false)) {
        halt("Failed to disable soft start / stop.");
    }
    if (!motor.setMotorRun(true) || !motor.setMotorMode(M5_UNIT_ENCODER_MOTOR_MODE_NORMAL) || !motor.setPWMDuty(0)) {
        halt("Failed to initialise PWM mode.");
    }
    Serial.println("Unit EncoderMotor Driver ready");
}

void loop()
{
    holdDuty("forward", PWM_DUTY, RUN_MS);
    holdDuty("coast", 0, 1000);
    holdDuty("reverse", -PWM_DUTY, RUN_MS);
    holdDuty("coast", 0, 1000);
}

程序用产品选择器 M5Unit_EncoderMotor_Driver 构造 M5UnitEncoderMotor 对象,begin() 时地址传 0 表示使用默认地址 0x2F;随后 setMotorRun(true) 使能驱动、setMotorMode(M5_UNIT_ENCODER_MOTOR_MODE_NORMAL) 进入 PWM 开环模式、setPWMDuty(0) 先把占空比清零。循环中先以占空比 100 正转 3 秒,再归零滑行 1 秒,然后以 -100 反转 3 秒,如此往复。PWM 模式下占空比直接作用到 H 桥,转速不受闭环调节,占空比与转速的对应关系随供电电压和负载变化;每 200 ms 通过 getSpeed() 与 getEncoder() 打印实测转速(编码器计数 / 20 ms)和编码器计数。

串口返回信息示例如下:

Unit EncoderMotor Driver ready
forward duty=+100 speed=+0 encoder=0
forward duty=+100 speed=+8 encoder=92
forward duty=+100 speed=+12 encoder=221
forward duty=+100 speed=+17 encoder=404
forward duty=+100 speed=+17 encoder=1008
forward duty=+100 speed=+16 encoder=2226
coast duty=+0 speed=+15 encoder=2622
coast duty=+0 speed=+7 encoder=2875
coast duty=+0 speed=+0 encoder=3021
reverse duty=-100 speed=+0 encoder=3021
reverse duty=-100 speed=-6 encoder=2960
reverse duty=-100 speed=-16 encoder=2712

3.2 速度模式

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
#include <M5Unified.h>
#include <M5Unit-EncoderMotor-Driver.h>

// Product selector sets the default address and channel count:
// U232 = 0x2F (1 channel), U233 = 0x24 (4 channels)
M5UnitEncoderMotor motor(M5Unit_EncoderMotor_Driver);

// Draw into an offscreen canvas so a full screen refresh does not flicker
M5Canvas canvas(&M5.Display);

constexpr int8_t I2C_SDA_PIN     = 2;  // CoreS3 PORT.A
constexpr int8_t I2C_SCL_PIN     = 1;
constexpr uint32_t I2C_FREQUENCY = 400000;
constexpr int8_t TARGET_SPEED    = 15;                        // Encoder counts per 20 ms
constexpr M5UnitEncoderMotorPID SPEED_PID = {10, 1, 15};      // Default speed loop P / I / D
constexpr uint32_t RUN_MS        = 5000;
constexpr uint32_t STEP_MS       = 200;

void halt(const char *message)
{
    Serial.println(message);
    while (true) {
        delay(1000);
    }
}

// Center the status screen and mix three font sizes so the key value stands out.
void showScreen(const char *title, const char *value, const char *detail1, const char *detail2)
{
    canvas.fillScreen(TFT_BLACK);
    canvas.setTextDatum(middle_center);

    canvas.setFont(&fonts::DejaVu18);
    canvas.setTextColor(TFT_LIGHTGREY);
    canvas.drawString(title, canvas.width() / 2, 32);

    canvas.setFont(&fonts::DejaVu40);
    canvas.setTextColor(TFT_WHITE);
    canvas.drawString(value, canvas.width() / 2, 112);

    canvas.setFont(&fonts::DejaVu12);
    canvas.setTextColor(TFT_LIGHTGREY);
    if (detail1 && detail1[0]) {
        canvas.drawString(detail1, canvas.width() / 2, 176);
    }
    if (detail2 && detail2[0]) {
        canvas.drawString(detail2, canvas.width() / 2, 204);
    }

    canvas.pushSprite(0, 0);
}

void drawStatus(const char *state, int8_t target)
{
    int8_t speed = 0;
    int8_t duty = 0;
    motor.getSpeed(speed);
    motor.getPWMDuty(duty);

    char value[16];
    char detail1[48];
    char detail2[48];
    snprintf(value, sizeof(value), "%+d", speed);
    snprintf(detail1, sizeof(detail1), "%s   target %+d", state, target);
    snprintf(detail2, sizeof(detail2), "duty %+d", duty);
    showScreen("SPEED", value, detail1, detail2);
}

// Hold a target speed and print the closed loop response
void holdSpeed(const char *state, int8_t target, uint32_t duration_ms)
{
    if (!motor.runSpeed(target, SPEED_PID)) {
        halt("Failed to run in Speed mode.");
    }
    for (uint32_t elapsed = 0; elapsed < duration_ms; elapsed += STEP_MS) {
        int8_t speed = 0;
        int8_t duty = 0;
        motor.getSpeed(speed);
        motor.getPWMDuty(duty);
        drawStatus(state, target);
        Serial.printf("%s target=%+d measured=%+d duty=%+d\n", state, target, speed, duty);
        delay(STEP_MS);
    }
}

void setup()
{
    auto cfg = M5.config();
    cfg.output_power = true;
    M5.begin(cfg);
    canvas.createSprite(M5.Display.width(), M5.Display.height());
    Serial.begin(115200);

    while (!motor.begin(&Wire, 0, I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQUENCY)) {
        Serial.println("Unit EncoderMotor Driver not found");
        delay(1000);
    }
    delay(100);  // give the unit time to finish the previous write
    // Soft start is saved in the unit flash; turn it off so this demo runs without a ramp
    if (!motor.setSoftStartStop(false)) {
        halt("Failed to disable soft start / stop.");
    }
    if (!motor.setMotorRun(true) || !motor.setMotorMode(M5_UNIT_ENCODER_MOTOR_MODE_NORMAL) || !motor.setPWMDuty(0)) {
        halt("Failed to initialise Speed mode.");
    }
    Serial.println("Unit EncoderMotor Driver ready");
}

void loop()
{
    holdSpeed("run", TARGET_SPEED, RUN_MS);
    holdSpeed("stop", 0, 2000);
}

程序以默认速度环参数 {10, 1, 15} 调用 runSpeed(15, SPEED_PID),单元进入速度闭环模式,目标值为 15,单位是 20 ms 内的编码器计数增量,不是 RPM,范围 -127 ~ 127,正负表示方向。运行 5 秒后把目标改成 0 观察减速过程。循环中每 200 ms 打印目标值、实测值和当前 PWM 占空比。

串口返回信息示例如下:

Unit EncoderMotor Driver ready
run target=+15 measured=+15 duty=+0
run target=+15 measured=+15 duty=+107
run target=+15 measured=+15 duty=+80
run target=+15 measured=+14 duty=+92
run target=+15 measured=+15 duty=+91
run target=+15 measured=+15 duty=+89
run target=+15 measured=+15 duty=+88
stop target=+0 measured=+14 duty=+0
stop target=+0 measured=+6 duty=+0
stop target=+0 measured=+0 duty=+0

3.3 位置模式

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
#include <M5Unified.h>
#include <M5Unit-EncoderMotor-Driver.h>

// Product selector sets the default address and channel count:
// U232 = 0x2F (1 channel), U233 = 0x24 (4 channels)
M5UnitEncoderMotor motor(M5Unit_EncoderMotor_Driver);

// Draw into an offscreen canvas so a full screen refresh does not flicker
M5Canvas canvas(&M5.Display);

constexpr int8_t I2C_SDA_PIN     = 2;  // CoreS3 PORT.A
constexpr int8_t I2C_SCL_PIN     = 1;
constexpr uint32_t I2C_FREQUENCY = 400000;
constexpr int32_t TARGET_POSITION = 1320;  // One output revolution: 11 PPR x 4 edges x 30 gear ratio = 1320 counts
constexpr int8_t MAX_SPEED        = 60;    // Position loop output limit, the firmware uses the absolute value
constexpr M5UnitEncoderMotorPID POSITION_PID = {1, 1, 17};  // Default position loop P / I / D
constexpr uint32_t TIMEOUT_MS     = 8000;
constexpr uint32_t STEP_MS        = 200;

void halt(const char *message)
{
    Serial.println(message);
    while (true) {
        delay(1000);
    }
}

// Center the status screen and mix three font sizes so the key value stands out.
void showScreen(const char *title, const char *value, const char *detail1, const char *detail2)
{
    canvas.fillScreen(TFT_BLACK);
    canvas.setTextDatum(middle_center);

    canvas.setFont(&fonts::DejaVu18);
    canvas.setTextColor(TFT_LIGHTGREY);
    canvas.drawString(title, canvas.width() / 2, 32);

    canvas.setFont(&fonts::DejaVu40);
    canvas.setTextColor(TFT_WHITE);
    canvas.drawString(value, canvas.width() / 2, 112);

    canvas.setFont(&fonts::DejaVu12);
    canvas.setTextColor(TFT_LIGHTGREY);
    if (detail1 && detail1[0]) {
        canvas.drawString(detail1, canvas.width() / 2, 176);
    }
    if (detail2 && detail2[0]) {
        canvas.drawString(detail2, canvas.width() / 2, 204);
    }

    canvas.pushSprite(0, 0);
}

void drawStatus(int32_t target)
{
    int32_t encoder = 0;
    int8_t speed = 0;
    motor.getEncoder(encoder);
    motor.getSpeed(speed);

    char value[16];
    char detail1[48];
    char detail2[48];
    snprintf(value, sizeof(value), "%ld", static_cast<long>(encoder));
    snprintf(detail1, sizeof(detail1), "target %ld", static_cast<long>(target));
    snprintf(detail2, sizeof(detail2), "speed %+d", speed);
    showScreen("POSITION", value, detail1, detail2);
}

// Send the target position and wait until it is reached
void moveTo(int32_t target, uint32_t timeout_ms)
{
    if (!motor.runPosition(target, MAX_SPEED, POSITION_PID)) {
        halt("Failed to run in Position mode.");
    }
    for (uint32_t elapsed = 0; elapsed < timeout_ms; elapsed += STEP_MS) {
        int32_t encoder = 0;
        motor.getEncoder(encoder);
        drawStatus(target);
        Serial.printf("target=%ld encoder=%ld\n", static_cast<long>(target), static_cast<long>(encoder));
        if (abs(target - encoder) <= 10) {
            Serial.println("position reached");
            return;
        }
        delay(STEP_MS);
    }
    Serial.println("position timeout");
}

void setup()
{
    auto cfg = M5.config();
    cfg.output_power = true;
    M5.begin(cfg);
    canvas.createSprite(M5.Display.width(), M5.Display.height());
    Serial.begin(115200);

    while (!motor.begin(&Wire, 0, I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQUENCY)) {
        Serial.println("Unit EncoderMotor Driver not found");
        delay(1000);
    }
    delay(100);  // give the unit time to finish the previous write
    // Soft start is saved in the unit flash; turn it off so this demo runs without a ramp
    if (!motor.setSoftStartStop(false)) {
        halt("Failed to disable soft start / stop.");
    }
    if (!motor.setMotorRun(true) || !motor.setMotorMode(M5_UNIT_ENCODER_MOTOR_MODE_NORMAL) || !motor.setPWMDuty(0) ||
        !motor.clearEncoder()) {
        halt("Failed to initialise Position mode.");
    }
    Serial.println("Unit EncoderMotor Driver ready");
}

void loop()
{
    moveTo(TARGET_POSITION, TIMEOUT_MS);
    delay(1000);
    moveTo(0, TIMEOUT_MS);
    delay(1000);
}

程序先用 clearEncoder() 把编码器计数清零,再调用 runPosition(1320, 60, POSITION_PID) 进入位置闭环模式:1320 为目标位置,对应输出轴转一圈(11 PPR × 4 倍频 × 30 减速比 = 1320 计数);60 为位置环输出限幅,固件取绝对值作为最大速度,POSITION_PID = {1, 1, 17} 为固件默认位置环参数。移动过程中每 200 ms 打印目标位置与当前编码器计数,到位后打印 position reached,随后再回到 0 位置。

串口返回信息示例如下:

Unit EncoderMotor Driver ready
target=1320 encoder=0
target=1320 encoder=184
target=1320 encoder=536
target=1320 encoder=884
target=1320 encoder=1286
target=1320 encoder=1323
position reached
target=0 encoder=1323
target=0 encoder=1146
target=0 encoder=520
target=0 encoder=4
position reached

3.4 软启动与软停止

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
#include <M5Unified.h>
#include <M5Unit-EncoderMotor-Driver.h>

// Product selector sets the default address and channel count:
// U232 = 0x2F (1 channel), U233 = 0x24 (4 channels)
M5UnitEncoderMotor motor(M5Unit_EncoderMotor_Driver);

// Draw into an offscreen canvas so a full screen refresh does not flicker
M5Canvas canvas(&M5.Display);

constexpr int8_t I2C_SDA_PIN     = 2;  // CoreS3 PORT.A
constexpr int8_t I2C_SCL_PIN     = 1;
constexpr uint32_t I2C_FREQUENCY = 400000;
constexpr int8_t TARGET_SPEED    = 20;  // Encoder counts per 20 ms
constexpr M5UnitEncoderMotorPID SPEED_PID = {10, 1, 15};  // Default speed loop P / I / D
constexpr uint32_t RUN_MS        = 4000;
constexpr uint32_t STEP_MS       = 50;

void halt(const char *message)
{
    Serial.println(message);
    while (true) {
        delay(1000);
    }
}

// Center the status screen and mix three font sizes so the key value stands out.
void showScreen(const char *title, const char *value, const char *detail1, const char *detail2)
{
    canvas.fillScreen(TFT_BLACK);
    canvas.setTextDatum(middle_center);

    canvas.setFont(&fonts::DejaVu18);
    canvas.setTextColor(TFT_LIGHTGREY);
    canvas.drawString(title, canvas.width() / 2, 32);

    canvas.setFont(&fonts::DejaVu40);
    canvas.setTextColor(TFT_WHITE);
    canvas.drawString(value, canvas.width() / 2, 112);

    canvas.setFont(&fonts::DejaVu12);
    canvas.setTextColor(TFT_LIGHTGREY);
    if (detail1 && detail1[0]) {
        canvas.drawString(detail1, canvas.width() / 2, 176);
    }
    if (detail2 && detail2[0]) {
        canvas.drawString(detail2, canvas.width() / 2, 204);
    }

    canvas.pushSprite(0, 0);
}

void drawStatus(const char *state, int8_t target, int8_t speed)
{
    char value[16];
    char detail1[48];
    char detail2[48];
    snprintf(value, sizeof(value), "%+d", speed);
    snprintf(detail1, sizeof(detail1), "%s   target %+d", state, target);
    snprintf(detail2, sizeof(detail2), "soft start on");
    showScreen("SOFT START", value, detail1, detail2);
}

// Drive the target speed and sample it to show the ramp.
void holdSpeed(const char *state, int8_t target, uint32_t duration_ms)
{
    motor.runSpeed(target, SPEED_PID);
    for (uint32_t elapsed = 0; elapsed < duration_ms; elapsed += STEP_MS) {
        int8_t speed = 0;
        int32_t encoder = 0;
        motor.getSpeed(speed);
        motor.getEncoder(encoder);
        drawStatus(state, target, speed);
        Serial.printf("%s t=%4lums target=%+d measured=%+d encoder=%ld\n", state, static_cast<long>(elapsed),
                      target, speed, static_cast<long>(encoder));
        delay(STEP_MS);
    }
}

void setup()
{
    auto cfg = M5.config();
    cfg.output_power = true;
    M5.begin(cfg);
    canvas.createSprite(M5.Display.width(), M5.Display.height());
    Serial.begin(115200);

    while (!motor.begin(&Wire, 0, I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQUENCY)) {
        Serial.println("Unit EncoderMotor Driver not found");
        delay(1000);
    }
    delay(100);  // give the unit time to finish the previous write
    if (!motor.setMotorRun(true) || !motor.setMotorMode(M5_UNIT_ENCODER_MOTOR_MODE_SPEED) || !motor.setPWMDuty(0)) {
        halt("Failed to initialise speed mode.");
    }
    // The soft start / stop bit is saved in the unit flash, so set it once in setup()
    if (!motor.setSoftStartStop(true)) {
        halt("Failed to enable soft start / stop.");
    }
    Serial.println("Soft start / stop enabled");
    Serial.println("Unit EncoderMotor Driver ready");
}

void loop()
{
    holdSpeed("run", TARGET_SPEED, RUN_MS);
    holdSpeed("stop", 0, RUN_MS);
}

软启动 / 软停止由单元固件实现(寄存器 0xD1),setSoftStartStop(true) 打开后,速度给定的变化会按固件内部的斜坡渐变,而不是直接阶跃。该位写入后保存在单元 Flash,掉电不丢,因此在 setup() 里设置一次即可。

例程用速度模式演示:runSpeed(20, SPEED_PID) 起转,4 秒后 runSpeed(0, SPEED_PID) 停止,每 50 ms 采样一次实测转速。速度环会自己把占空比尽快拉满,所以转速爬升比 PWM 模式快;想看到更明显的占空比斜坡,可以改用 PWM 模式(见 3.1)。

5V Grove 供电时从静止直接给速度环阶跃,瞬时电流较大,建议外接 DC 供电。

串口返回信息示例如下:

Soft start / stop enabled
Unit EncoderMotor Driver ready
run t=   0ms target=+20 measured=+0
run t=  50ms target=+20 measured=+4
run t= 100ms target=+20 measured=+13
run t= 150ms target=+20 measured=+19
run t= 200ms target=+20 measured=+21
run t= 400ms target=+20 measured=+20
run t=1000ms target=+20 measured=+21
stop t=   0ms target=+0 measured=+20
stop t=  50ms target=+0 measured=+7
stop t= 100ms target=+0 measured=+0

3.5 RGB 指示灯

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
#include <M5Unified.h>
#include <M5Unit-EncoderMotor-Driver.h>

// Product selector sets the default address and channel count:
// U232 = 0x2F (1 channel), U233 = 0x24 (4 channels)
M5UnitEncoderMotor motor(M5Unit_EncoderMotor_Driver);

// Draw into an offscreen canvas so a full screen refresh does not flicker
M5Canvas canvas(&M5.Display);

constexpr int8_t I2C_SDA_PIN     = 2;  // CoreS3 PORT.A
constexpr int8_t I2C_SCL_PIN     = 1;
constexpr uint32_t I2C_FREQUENCY = 400000;

struct ColorStep {
    const char *name;
    uint8_t red;
    uint8_t green;
    uint8_t blue;
};

constexpr ColorStep STEPS[] = {
    {"red", 255, 0, 0},   {"green", 0, 255, 0},  {"blue", 0, 0, 255}, {"yellow", 255, 255, 0},
    {"cyan", 0, 255, 255}, {"magenta", 255, 0, 255}, {"off", 0, 0, 0},
};
constexpr uint8_t BRIGHTNESS = 80;  // 0 ~ 255
constexpr uint32_t STEP_MS   = 800;

void halt(const char *message)
{
    Serial.println(message);
    while (true) {
        delay(1000);
    }
}

// Center the status screen and mix three font sizes so the key value stands out.
void showScreen(const char *title, const char *value, const char *detail1, const char *detail2)
{
    canvas.fillScreen(TFT_BLACK);
    canvas.setTextDatum(middle_center);

    canvas.setFont(&fonts::DejaVu18);
    canvas.setTextColor(TFT_LIGHTGREY);
    canvas.drawString(title, canvas.width() / 2, 32);

    canvas.setFont(&fonts::DejaVu40);
    canvas.setTextColor(TFT_WHITE);
    canvas.drawString(value, canvas.width() / 2, 112);

    canvas.setFont(&fonts::DejaVu12);
    canvas.setTextColor(TFT_LIGHTGREY);
    if (detail1 && detail1[0]) {
        canvas.drawString(detail1, canvas.width() / 2, 176);
    }
    if (detail2 && detail2[0]) {
        canvas.drawString(detail2, canvas.width() / 2, 204);
    }

    canvas.pushSprite(0, 0);
}

void setup()
{
    auto cfg = M5.config();
    cfg.output_power = true;
    M5.begin(cfg);
    canvas.createSprite(M5.Display.width(), M5.Display.height());
    Serial.begin(115200);

    while (!motor.begin(&Wire, 0, I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQUENCY)) {
        Serial.println("Unit EncoderMotor Driver not found");
        delay(1000);
    }
    if (!motor.setRGBBrightness(BRIGHTNESS)) {
        halt("Failed to set RGB brightness.");
    }
    Serial.println("RGB demo ready");
}

void loop()
{
    for (const auto &step : STEPS) {
        const bool ok = motor.setRGBColor(step.red, step.green, step.blue);
        delay(100);

        uint8_t red = 0;
        uint8_t green = 0;
        uint8_t blue = 0;
        uint8_t brightness = 0;
        const bool read_ok = motor.getRGBColor(red, green, blue) && motor.getRGBBrightness(brightness);

        char value[16];
        char detail1[48];
        char detail2[48];
        snprintf(value, sizeof(value), "%s", step.name);
        snprintf(detail1, sizeof(detail1), "R%3u G%3u B%3u", red, green, blue);
        snprintf(detail2, sizeof(detail2), "brightness %u", brightness);
        showScreen("RGB", value, detail1, detail2);

        Serial.printf("set=%s rgb=(%u,%u,%u) brightness=%u result=%s read=(%u,%u,%u)/%u read_result=%s\n",
                      step.name, step.red, step.green, step.blue, BRIGHTNESS, ok ? "OK" : "FAIL", red, green,
                      blue, brightness, read_ok ? "OK" : "FAIL");
        delay(STEP_MS);
    }
}

setRGBBrightness(80) 设置亮度(0 ~ 255),随后依次下发红、绿、蓝、黄、青、品红、熄灭七种颜色,每一步都通过 getRGBColor() 与 getRGBBrightness() 回读颜色和亮度。屏幕显示颜色名称及回读值,串口同时打印下发值、回读值和读写结果。

串口返回信息示例如下:

RGB demo ready
set=red rgb=(255,0,0) brightness=80 result=OK read=(255,0,0)/80 read_result=OK
set=green rgb=(0,255,0) brightness=80 result=OK read=(0,255,0)/80 read_result=OK
set=blue rgb=(0,0,255) brightness=80 result=OK read=(0,0,255)/80 read_result=OK
set=yellow rgb=(255,255,0) brightness=80 result=OK read=(255,255,0)/80 read_result=OK
set=cyan rgb=(0,255,255) brightness=80 result=OK read=(0,255,255)/80 read_result=OK
set=magenta rgb=(255,0,255) brightness=80 result=OK read=(255,0,255)/80 read_result=OK
set=off rgb=(0,0,0) brightness=80 result=OK read=(0,0,0)/80 read_result=OK

4. 编译上传

将上述任一例程的代码复制粘贴到 Arduino IDE 的代码编辑区,选中设备端口(详情请参考 程序编译与烧录),点击 Arduino IDE 左上角编译上传按钮,等待程序完成编译并上传至设备。

Page Tools
PDF
On This Page