StackChan

支持以下产品:

StackChan

UiFlow2 应用示例

舵机零点校准

备注

不同设备之间的机械装配会有差异。刷入新固件后,需要手动校准舵机零点参考。

在 UiFlow2 中打开 stackchan_servo_zero_calibrate.m5f2 项目。

  1. 运行程序。

  2. 手动转动头部:在 X 方向上,使显示屏与底座朝向一致;在 Y 方向上,使显示屏与底座垂直。

  3. 点击 Save 按钮。

UiFlow2 代码块:

stackchan_servo_zero_calibrate.png

示例输出:

None

舵机角度读取

在 UiFlow2 中打开 stackchan_servo_read_example.m5f2 项目。

此示例演示在关闭力矩的情况下读取 X 和 Y 舵机角度(单位:度),使头部可以自由转动。

备注

开启力矩控制时会保持上次目标位置,并阻碍手动转动。关闭力矩控制后可在角度实时更新时自由调整头部姿态,便于检查校准结果。

UiFlow2 代码块:

stackchan_servo_read_example.png

示例输出:

None

舵机控制

在 UiFlow2 中打开 stackchan_servo_control_example.m5f2 项目。

此示例演示使用 set_servo_angleset_servo_x_pwm 控制舵机移动到指定位置,并以 PWM 模式驱动 X 轴舵机。

UiFlow2 代码块:

stackchan_servo_control_example.png

示例输出:

None

人脸跟踪

在 UiFlow2 中打开 stackchan_face_tracking_example.m5f2 项目。

此示例实现人脸跟踪。

UiFlow2 代码块:

stackchan_face_tracking_example.png

示例输出:

None

舵机电源信息

在 UiFlow2 中打开 stackchan_servo_power_example.m5f2 项目。

此示例演示读取并显示舵机电源信息。

UiFlow2 代码块:

stackchan_servo_power_example.png

示例输出:

None

触摸与 RGB

在 UiFlow2 中打开 stackchan_tp_rgb_example.m5f2 项目。

此示例演示将触摸区域映射到 RGB 灯带颜色(两条灯带上的三个逻辑触摸点)。

UiFlow2 代码块:

stackchan_tp_rgb_example.png

示例输出:

None

NFC

在 UiFlow2 中打开 stackchan_nfc_detect_example.m5f2 项目。

此示例演示检测 NFC 标签,并在屏幕上显示 UID 和标签类型。完整的 NFC Unit API 参考(detect、读写、标签类型等)请参见 NFC Unit

UiFlow2 代码块:

stackchan_nfc_detect_example.png

示例输出:

None

红外(IR)

在 UiFlow2 中打开 stackchan_ir_tx_rx_example.m5f2 项目。

此示例演示 NEC 风格的红外发送与接收。

UiFlow2 代码块:

stackchan_ir_tx_rx_example.png

示例输出:

None

MicroPython 应用示例

舵机零点校准

备注

不同设备之间的机械装配会有差异。刷入新固件后,需要手动校准舵机零点参考。

  1. 运行程序。

  2. 手动转动头部:在 X 方向上,使显示屏与底座朝向一致;在 Y 方向上,使显示屏与底座垂直。

  3. 点击 Save 按钮。

MicroPython 代码块:

  1# SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
  2#
  3# SPDX-License-Identifier: MIT
  4
  5import os, sys, io
  6import M5
  7from M5 import *
  8import m5ui
  9import lvgl as lv
 10import time
 11from hardware.stackchan import StackChan
 12
 13
 14page0 = None
 15label_title = None
 16button_save = None
 17label_angle_x = None
 18label_angle_y = None
 19label_tip = None
 20stackchan = None
 21x_angle = None
 22y_angle = None
 23last_time = None
 24
 25
 26def button_save_short_clicked_event(event_struct):
 27    global \
 28        page0, \
 29        label_title, \
 30        button_save, \
 31        label_angle_x, \
 32        label_angle_y, \
 33        label_tip, \
 34        stackchan, \
 35        x_angle, \
 36        y_angle, \
 37        last_time
 38    stackchan.set_servo_zero()
 39    label_tip.set_text(str("Tip: Calibration success"))
 40    Speaker.tone(1000, 100)
 41    last_time = time.ticks_ms()
 42
 43
 44def button_save_event_handler(event_struct):
 45    global \
 46        page0, \
 47        label_title, \
 48        button_save, \
 49        label_angle_x, \
 50        label_angle_y, \
 51        label_tip, \
 52        stackchan, \
 53        x_angle, \
 54        y_angle, \
 55        last_time
 56    event = event_struct.code
 57    if event == lv.EVENT.SHORT_CLICKED and True:
 58        button_save_short_clicked_event(event_struct)
 59    return
 60
 61
 62def setup():
 63    global \
 64        page0, \
 65        label_title, \
 66        button_save, \
 67        label_angle_x, \
 68        label_angle_y, \
 69        label_tip, \
 70        stackchan, \
 71        x_angle, \
 72        y_angle, \
 73        last_time
 74
 75    M5.begin()
 76    Widgets.setRotation(1)
 77    m5ui.init()
 78    page0 = m5ui.M5Page(bg_c=0x000000)
 79    label_title = m5ui.M5Label(
 80        "Servo Calibration",
 81        x=55,
 82        y=5,
 83        text_c=0x0DC9F4,
 84        bg_c=0x000000,
 85        bg_opa=0,
 86        font=lv.font_montserrat_24,
 87        parent=page0,
 88    )
 89    button_save = m5ui.M5Button(
 90        text="Save",
 91        x=128,
 92        y=195,
 93        bg_c=0x2196F3,
 94        text_c=0xFFFFFF,
 95        font=lv.font_montserrat_14,
 96        parent=page0,
 97    )
 98    label_angle_x = m5ui.M5Label(
 99        "X-Axis Servo Angle:",
100        x=10,
101        y=130,
102        text_c=0x0DC9F4,
103        bg_c=0x000000,
104        bg_opa=0,
105        font=lv.font_montserrat_18,
106        parent=page0,
107    )
108    label_angle_y = m5ui.M5Label(
109        "Y-Axis Servo Angle:",
110        x=8,
111        y=160,
112        text_c=0x0DC9F4,
113        bg_c=0x000000,
114        bg_opa=0,
115        font=lv.font_montserrat_18,
116        parent=page0,
117    )
118    label_tip = m5ui.M5Label(
119        "Tip:Move by hand, tap Save.",
120        x=33,
121        y=70,
122        text_c=0xD2E711,
123        bg_c=0xFFFFFF,
124        bg_opa=0,
125        font=lv.font_montserrat_18,
126        parent=page0,
127    )
128
129    button_save.add_event_cb(button_save_event_handler, lv.EVENT.ALL, None)
130
131    stackchan = StackChan(i2c=1, uart=1)
132    page0.screen_load()
133    stackchan.set_servo_power(enable=True, settle_ms=500)
134    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=False)
135    stackchan.set_servo_torque(stackchan.SERVO_ID_Y, enable=False)
136    Speaker.begin()
137    Speaker.setVolumePercentage(0.6)
138    Speaker.tone(1000, 100)
139    last_time = time.ticks_ms()
140
141
142def loop():
143    global \
144        page0, \
145        label_title, \
146        button_save, \
147        label_angle_x, \
148        label_angle_y, \
149        label_tip, \
150        stackchan, \
151        x_angle, \
152        y_angle, \
153        last_time
154    M5.update()
155    if (time.ticks_diff((time.ticks_ms()), last_time)) >= 100:
156        last_time = time.ticks_ms()
157        x_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_X)
158        y_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_Y)
159        label_angle_x.set_text(str((str("X-Axis Servo Angle:") + str(x_angle))))
160        label_angle_y.set_text(str((str("Y-Axis Servo Angle:") + str(y_angle))))
161
162
163if __name__ == "__main__":
164    try:
165        setup()
166        while True:
167            loop()
168    except (Exception, KeyboardInterrupt) as e:
169        try:
170            m5ui.deinit()
171            from utility import print_error_msg
172
173            print_error_msg(e)
174        except ImportError:
175            print("please update to latest firmware")

示例输出:

None

舵机角度读取

此示例演示在关闭力矩的情况下读取 X 和 Y 舵机角度(单位:度),使头部可以自由转动。

备注

开启力矩控制时会保持上次目标位置,并阻碍手动转动。关闭力矩控制后可在角度实时更新时自由调整头部姿态,便于检查校准结果。

MicroPython 代码块:

 1import os, sys, io
 2import M5
 3from M5 import *
 4import m5ui
 5import lvgl as lv
 6import time
 7from hardware.stackchan import StackChan
 8
 9
10page0 = None
11label_title = None
12label_agnle_x = None
13label_angle_y = None
14stackchan = None
15last_time = None
16x_angle = None
17y_angle = None
18
19
20def setup():
21    global page0, label_title, label_agnle_x, label_angle_y, stackchan, last_time, x_angle, y_angle
22
23    M5.begin()
24    Widgets.setRotation(1)
25    m5ui.init()
26    page0 = m5ui.M5Page(bg_c=0x000000)
27    label_title = m5ui.M5Label(
28        "Servo Read Example",
29        x=34,
30        y=10,
31        text_c=0x0DC9F4,
32        bg_c=0x000000,
33        bg_opa=0,
34        font=lv.font_montserrat_24,
35        parent=page0,
36    )
37    label_agnle_x = m5ui.M5Label(
38        "X-Axis Servo Angle:",
39        x=10,
40        y=80,
41        text_c=0x0DC9F4,
42        bg_c=0x000000,
43        bg_opa=0,
44        font=lv.font_montserrat_24,
45        parent=page0,
46    )
47    label_angle_y = m5ui.M5Label(
48        "Y-Axis Servo Angle:",
49        x=10,
50        y=125,
51        text_c=0x0DC9F4,
52        bg_c=0x000000,
53        bg_opa=0,
54        font=lv.font_montserrat_24,
55        parent=page0,
56    )
57
58    stackchan = StackChan(i2c=1, uart=1)
59    page0.screen_load()
60    stackchan.set_servo_power(enable=True, settle_ms=500)
61    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=False)
62    stackchan.set_servo_torque(stackchan.SERVO_ID_Y, enable=False)
63    last_time = time.ticks_ms()
64
65
66def loop():
67    global page0, label_title, label_agnle_x, label_angle_y, stackchan, last_time, x_angle, y_angle
68    M5.update()
69    if (time.ticks_diff((time.ticks_ms()), last_time)) >= 100:
70        last_time = time.ticks_ms()
71        x_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_X)
72        y_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_Y)
73        label_agnle_x.set_text(str((str("X-Axis Servo Angle: ") + str(x_angle))))
74        label_angle_y.set_text(str((str("Y-Axis Servo Angle: ") + str(y_angle))))
75
76
77if __name__ == "__main__":
78    try:
79        setup()
80        while True:
81            loop()
82    except (Exception, KeyboardInterrupt) as e:
83        try:
84            m5ui.deinit()
85            from utility import print_error_msg
86
87            print_error_msg(e)
88        except ImportError:
89            print("please update to latest firmware")

示例输出:

None

舵机控制

此示例演示使用 set_servo_angleset_servo_x_pwm 控制舵机移动到指定位置,并以 PWM 模式驱动 X 轴舵机。

MicroPython 代码块:

  1# SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
  2#
  3# SPDX-License-Identifier: MIT
  4
  5import os, sys, io
  6import M5
  7from M5 import *
  8import m5ui
  9import lvgl as lv
 10import time
 11from hardware.stackchan import StackChan
 12
 13
 14page0 = None
 15label_title = None
 16label_status = None
 17stackchan = None
 18
 19
 20def cleanup():
 21    global stackchan
 22    if stackchan is None:
 23        return
 24    try:
 25        stackchan.set_servo_x_pwm(0)
 26        time.sleep_ms(100)
 27        stackchan.set_servo_angle(stackchan.SERVO_ID_X, 0, 500, 0)
 28        stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 45, 500, 0)
 29        time.sleep_ms(600)
 30        stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=False)
 31        stackchan.set_servo_torque(stackchan.SERVO_ID_Y, enable=False)
 32        stackchan.set_servo_power(enable=False)
 33    except Exception as e:
 34        print("StackChan cleanup failed:", e)
 35
 36
 37def setup():
 38    global page0, label_title, label_status, stackchan
 39
 40    M5.begin()
 41    Widgets.setRotation(1)
 42    m5ui.init()
 43    page0 = m5ui.M5Page(bg_c=0x000000)
 44    label_title = m5ui.M5Label(
 45        "Servo Control Example",
 46        x=20,
 47        y=5,
 48        text_c=0x0DC9F4,
 49        bg_c=0x000000,
 50        bg_opa=0,
 51        font=lv.font_montserrat_24,
 52        parent=page0,
 53    )
 54    label_status = m5ui.M5Label(
 55        "--",
 56        x=153,
 57        y=115,
 58        text_c=0x0DC9F4,
 59        bg_c=0xFFFFFF,
 60        bg_opa=0,
 61        font=lv.font_montserrat_16,
 62        parent=page0,
 63    )
 64
 65    stackchan = StackChan(i2c=1, uart=1)
 66    page0.screen_load()
 67    Speaker.begin()
 68    Speaker.setVolumePercentage(0.5)
 69    # A previous run may have been interrupted while X was in PWM mode.
 70    # Power-cycle the servo rail before enabling torque to recover cleanly.
 71    stackchan.set_servo_power(enable=False, settle_ms=300)
 72    stackchan.set_servo_power(enable=True)
 73    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=True)
 74    stackchan.set_servo_torque(stackchan.SERVO_ID_Y, enable=True)
 75
 76    label_status.set_text(str("Center X/Y"))
 77    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
 78    stackchan.set_servo_angle(stackchan.SERVO_ID_X, 0, 1000, 0)
 79    stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 45, 1000, 0)
 80    Speaker.tone(678, 300)
 81    time.sleep_ms(2000)
 82
 83    label_status.set_text(str("Y tilt up"))
 84    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
 85    stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 20, 1000, 0)
 86    time.sleep_ms(1800)
 87
 88    label_status.set_text(str("Y tilt down"))
 89    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
 90    stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 70, 1000, 0)
 91    time.sleep_ms(1800)
 92
 93    label_status.set_text(str("Y back to center"))
 94    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
 95    stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 45, 1000, 0)
 96    time.sleep_ms(1500)
 97
 98    label_status.set_text(str("X rotate counterclockwise"))
 99    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
100    stackchan.set_servo_x_pwm(-50)
101    time.sleep_ms(3000)
102
103    label_status.set_text(str("X rotate clockwise"))
104    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
105    stackchan.set_servo_x_pwm(50)
106    time.sleep_ms(3000)
107
108    label_status.set_text(str("X/Y back to center"))
109    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
110    stackchan.set_servo_x_pwm(0)
111    time.sleep_ms(100)
112    stackchan.set_servo_angle(stackchan.SERVO_ID_X, 0, 1000, 0)
113    stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 45, 1000, 0)
114
115
116def loop():
117    global page0, label_title, label_status, stackchan
118    M5.update()
119
120
121if __name__ == "__main__":
122    try:
123        setup()
124        while True:
125            loop()
126    except (Exception, KeyboardInterrupt) as e:
127        cleanup()
128        try:
129            m5ui.deinit()
130            from utility import print_error_msg
131
132            print_error_msg(e)
133        except ImportError:
134            print("please update to latest firmware")

示例输出:

None

人脸跟踪

案例实现人脸跟踪功能

MicroPython 代码块:

  1import os, sys, io
  2import M5
  3from M5 import *
  4import camera
  5import dl
  6import image
  7from hardware.stackchan import StackChan
  8
  9
 10stackchan = None
 11
 12
 13import math
 14
 15img = None
 16dl_detection_result = None
 17dl_detector = None
 18lost_frame = None
 19res = None
 20bbox = None
 21f_x = None
 22f_y = None
 23neutral = None
 24SMOOTH = None
 25f_w = None
 26DEADZONE_NORM = None
 27f_h = None
 28Y_NEUTRAL = None
 29f_cx = None
 30img_cx = None
 31f_cy = None
 32img_cy = None
 33ex = None
 34angle_x = None
 35ey = None
 36angle_y = None
 37x_target = None
 38y_target = None
 39
 40
 41def setup():
 42    global \
 43        stackchan, \
 44        img, \
 45        dl_detection_result, \
 46        dl_detector, \
 47        lost_frame, \
 48        res, \
 49        bbox, \
 50        f_x, \
 51        f_y, \
 52        neutral, \
 53        SMOOTH, \
 54        f_w, \
 55        DEADZONE_NORM, \
 56        f_h, \
 57        Y_NEUTRAL, \
 58        f_cx, \
 59        img_cx, \
 60        f_cy, \
 61        img_cy, \
 62        ex, \
 63        angle_x, \
 64        ey, \
 65        angle_y, \
 66        x_target, \
 67        y_target
 68
 69    M5.begin()
 70    Widgets.setRotation(1)
 71    Widgets.fillScreen(0x222222)
 72
 73    stackchan = StackChan(i2c=1, uart=1)
 74    camera.init(pixformat=camera.RGB565, framesize=camera.QVGA)
 75    dl_detector = dl.ObjectDetector(dl.model.HUMAN_FACE_DETECT)
 76    stackchan.set_servo_power(enable=True)
 77    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=True)
 78    stackchan.set_servo_torque(stackchan.SERVO_ID_Y, enable=True)
 79    stackchan.set_servo_angle(stackchan.SERVO_ID_X, 0, 0, 0)
 80    stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 45, 0, 0)
 81    SMOOTH = 0.1
 82    DEADZONE_NORM = 0.06
 83    Y_NEUTRAL = 45
 84    img_cx = 160
 85    img_cy = 120
 86    angle_x = 0
 87    angle_y = Y_NEUTRAL
 88    neutral = True
 89
 90
 91def loop():
 92    global \
 93        stackchan, \
 94        img, \
 95        dl_detection_result, \
 96        dl_detector, \
 97        lost_frame, \
 98        res, \
 99        bbox, \
100        f_x, \
101        f_y, \
102        neutral, \
103        SMOOTH, \
104        f_w, \
105        DEADZONE_NORM, \
106        f_h, \
107        Y_NEUTRAL, \
108        f_cx, \
109        img_cx, \
110        f_cy, \
111        img_cy, \
112        ex, \
113        angle_x, \
114        ey, \
115        angle_y, \
116        x_target, \
117        y_target
118    M5.update()
119    img = camera.snapshot()
120    dl_detection_result = dl_detector.infer(img)
121    if dl_detection_result:
122        lost_frame = 0
123        res = dl_detection_result[0]
124        bbox = res.bbox()
125        f_x = res.x()
126        f_y = res.y()
127        f_w = res.w()
128        f_h = res.h()
129        f_cx = int(f_x + f_w * 0.5)
130        f_cy = int(f_y + f_h * 0.5)
131        ex = (f_cx - img_cx) / img_cx
132        ey = (f_cy - img_cy) / img_cy
133        if math.fabs(ex) < DEADZONE_NORM:
134            ex = 0
135        if math.fabs(ey) < DEADZONE_NORM:
136            ey = 0
137        x_target = min(max(ex * -135, -135), 135)
138        y_target = min(max(Y_NEUTRAL - Y_NEUTRAL * ey, 0), 90)
139        angle_x = angle_x + SMOOTH * (x_target - angle_x)
140        angle_y = angle_y + SMOOTH * (y_target - angle_y)
141        stackchan.set_servo_angle(stackchan.SERVO_ID_X, angle_x, 100, 0)
142        stackchan.set_servo_angle(stackchan.SERVO_ID_Y, angle_y, 100, 0)
143        neutral = False
144        lost_frame = 0
145        img.draw_rectangle(f_x, f_y, f_w, f_h, color=0x6600CC, thickness=3, fill=False)
146    else:
147        lost_frame = (lost_frame if isinstance(lost_frame, (int, float)) else 0) + 1
148        if lost_frame > 20 and not neutral:
149            stackchan.set_servo_angle(stackchan.SERVO_ID_X, 0, 1000, 0)
150            stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 45, 1000, 0)
151            neutral = True
152    M5.Lcd.show(img, 0, 0, 320, 240)
153
154
155if __name__ == "__main__":
156    try:
157        setup()
158        while True:
159            loop()
160    except (Exception, KeyboardInterrupt) as e:
161        try:
162            from utility import print_error_msg
163
164            print_error_msg(e)
165        except ImportError:
166            print("please update to latest firmware")

示例输出:

None

舵机电源信息

此示例演示读取并显示舵机电源信息。

MicroPython 代码块:

  1# SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
  2#
  3# SPDX-License-Identifier: MIT
  4
  5import os, sys, io
  6import M5
  7from M5 import *
  8import m5ui
  9import lvgl as lv
 10import time
 11from hardware.stackchan import StackChan
 12
 13
 14page0 = None
 15label_title = None
 16label_voltage = None
 17label_current = None
 18label_power = None
 19stackchan = None
 20last_time = None
 21volatge = None
 22current = None
 23power = None
 24
 25
 26def setup():
 27    global \
 28        page0, \
 29        label_title, \
 30        label_voltage, \
 31        label_current, \
 32        label_power, \
 33        stackchan, \
 34        last_time, \
 35        volatge, \
 36        current, \
 37        power
 38
 39    M5.begin()
 40    Widgets.setRotation(1)
 41    m5ui.init()
 42    page0 = m5ui.M5Page(bg_c=0x000000)
 43    label_title = m5ui.M5Label(
 44        "Servo Power Info",
 45        x=56,
 46        y=5,
 47        text_c=0x0DC9F4,
 48        bg_c=0x000000,
 49        bg_opa=0,
 50        font=lv.font_montserrat_24,
 51        parent=page0,
 52    )
 53    label_voltage = m5ui.M5Label(
 54        "Voltage:",
 55        x=10,
 56        y=80,
 57        text_c=0x0DC9F4,
 58        bg_c=0x000000,
 59        bg_opa=0,
 60        font=lv.font_montserrat_24,
 61        parent=page0,
 62    )
 63    label_current = m5ui.M5Label(
 64        "Current:",
 65        x=10,
 66        y=115,
 67        text_c=0x0DC9F4,
 68        bg_c=0x000000,
 69        bg_opa=0,
 70        font=lv.font_montserrat_24,
 71        parent=page0,
 72    )
 73    label_power = m5ui.M5Label(
 74        "Power:",
 75        x=25,
 76        y=150,
 77        text_c=0x0DC9F4,
 78        bg_c=0x000000,
 79        bg_opa=0,
 80        font=lv.font_montserrat_24,
 81        parent=page0,
 82    )
 83
 84    stackchan = StackChan(i2c=1, uart=1)
 85    page0.screen_load()
 86    last_time = time.ticks_ms()
 87
 88
 89def loop():
 90    global \
 91        page0, \
 92        label_title, \
 93        label_voltage, \
 94        label_current, \
 95        label_power, \
 96        stackchan, \
 97        last_time, \
 98        volatge, \
 99        current, \
100        power
101    M5.update()
102    if (time.ticks_diff((time.ticks_ms()), last_time)) >= 200:
103        last_time = time.ticks_ms()
104        volatge = stackchan.get_battery_voltage()
105        current = stackchan.get_battery_current()
106        power = stackchan.get_battery_power()
107        label_voltage.set_text(str((str("Voltage: ") + str((str(volatge) + str(" V"))))))
108        label_current.set_text(str((str("Current: ") + str((str(current) + str(" A"))))))
109        label_power.set_text(str((str("Power: ") + str((str(power) + str(" W"))))))
110
111
112if __name__ == "__main__":
113    try:
114        setup()
115        while True:
116            loop()
117    except (Exception, KeyboardInterrupt) as e:
118        try:
119            m5ui.deinit()
120            from utility import print_error_msg
121
122            print_error_msg(e)
123        except ImportError:
124            print("please update to latest firmware")

示例输出:

None

触摸与 RGB

此示例演示将触摸区域映射到 RGB 灯带颜色(两条灯带上的三个逻辑触摸点)。

MicroPython 代码块:

 1# SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
 2#
 3# SPDX-License-Identifier: MIT
 4
 5import os, sys, io
 6import M5
 7from M5 import *
 8import m5ui
 9import lvgl as lv
10import time
11from hardware.stackchan import StackChan
12
13
14HOLD_MS = 300
15TOUCH_COLORS = (0x33CC00, 0x00CCCC, 0x000099)
16TOUCH_TONES = (700, 900, 1100)
17TOUCH_LEDS = (
18    ((0, 0), (0, 1), (1, 0), (1, 1)),
19    ((0, 2), (0, 3), (1, 2), (1, 3)),
20    ((0, 4), (0, 5), (1, 4), (1, 5)),
21)
22
23page0 = None
24label_title = None
25stackchan = None
26tp = None
27last_time = None
28active = None
29
30
31def set_touch_group(index, color):
32    for strip, led in TOUCH_LEDS[index]:
33        stackchan.set_rgb_color(strip, led, color)
34
35
36def setup():
37    global page0, label_title, stackchan, tp, last_time, active
38
39    M5.begin()
40    Widgets.setRotation(1)
41    m5ui.init()
42    page0 = m5ui.M5Page(bg_c=0x000000)
43    label_title = m5ui.M5Label(
44        "TP & RGB Strip Example",
45        x=13,
46        y=10,
47        text_c=0x0DC9F4,
48        bg_c=0x000000,
49        bg_opa=0,
50        font=lv.font_montserrat_24,
51        parent=page0,
52    )
53
54    stackchan = StackChan(i2c=1, uart=1)
55    page0.screen_load()
56    now = time.ticks_ms()
57    last_time = [now, now, now]
58    active = [False, False, False]
59    Speaker.begin()
60    Speaker.setVolumePercentage(0.5)
61    stackchan.set_rgb_color(0x000000)
62
63
64def loop():
65    global page0, label_title, stackchan, tp, last_time, active
66    M5.update()
67    now = time.ticks_ms()
68    tp = stackchan.get_touch()
69    if tp is None:
70        tp = [0, 0, 0]
71
72    for index in range(3):
73        if tp[index]:
74            last_time[index] = now
75            if not active[index]:
76                active[index] = True
77                set_touch_group(index, TOUCH_COLORS[index])
78                Speaker.tone(TOUCH_TONES[index], 50)
79        elif active[index] and time.ticks_diff(now, last_time[index]) > HOLD_MS:
80            active[index] = False
81            set_touch_group(index, 0x000000)
82
83
84if __name__ == "__main__":
85    try:
86        setup()
87        while True:
88            loop()
89    except (Exception, KeyboardInterrupt) as e:
90        try:
91            m5ui.deinit()
92            from utility import print_error_msg
93
94            print_error_msg(e)
95        except ImportError:
96            print("please update to latest firmware")

示例输出:

None

NFC

此示例演示检测 NFC 标签,并在屏幕上显示 UID 和标签类型。完整的 NFC Unit API 参考(detect、读写、标签类型等)请参见 NFC Unit

MicroPython 代码块:

  1# SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
  2#
  3# SPDX-License-Identifier: MIT
  4
  5import os, sys, io
  6import M5
  7from M5 import *
  8import m5ui
  9import lvgl as lv
 10import time
 11from hardware.stackchan import StackChan
 12
 13
 14page0 = None
 15label_title = None
 16label_uid = None
 17label_type = None
 18label_size = None
 19stackchan = None
 20card_0 = None
 21card_uid = None
 22new = None
 23card_type = None
 24card_size = None
 25last_time = None
 26
 27
 28def setup():
 29    global \
 30        page0, \
 31        label_title, \
 32        label_uid, \
 33        label_type, \
 34        label_size, \
 35        stackchan, \
 36        card_0, \
 37        card_uid, \
 38        new, \
 39        card_type, \
 40        card_size, \
 41        last_time
 42
 43    M5.begin()
 44    Widgets.setRotation(1)
 45    m5ui.init()
 46    page0 = m5ui.M5Page(bg_c=0x000000)
 47    label_title = m5ui.M5Label(
 48        "NFC Card detect",
 49        x=58,
 50        y=5,
 51        text_c=0x13C2EB,
 52        bg_c=0xFFFFFF,
 53        bg_opa=0,
 54        font=lv.font_montserrat_24,
 55        parent=page0,
 56    )
 57    label_uid = m5ui.M5Label(
 58        "UID:",
 59        x=18,
 60        y=70,
 61        text_c=0xFFFFFF,
 62        bg_c=0xFFFFFF,
 63        bg_opa=0,
 64        font=lv.font_montserrat_16,
 65        parent=page0,
 66    )
 67    label_type = m5ui.M5Label(
 68        "Tyep:",
 69        x=10,
 70        y=100,
 71        text_c=0xFFFFFF,
 72        bg_c=0xFFFFFF,
 73        bg_opa=0,
 74        font=lv.font_montserrat_16,
 75        parent=page0,
 76    )
 77    label_size = m5ui.M5Label(
 78        "Size:",
 79        x=16,
 80        y=130,
 81        text_c=0xFFFFFF,
 82        bg_c=0xFFFFFF,
 83        bg_opa=0,
 84        font=lv.font_montserrat_16,
 85        parent=page0,
 86    )
 87
 88    page0.screen_load()
 89    stackchan = StackChan(i2c=1, uart=1)
 90    Speaker.begin()
 91    Speaker.setVolumePercentage(0.6)
 92
 93
 94def loop():
 95    global \
 96        page0, \
 97        label_title, \
 98        label_uid, \
 99        label_type, \
100        label_size, \
101        stackchan, \
102        card_0, \
103        card_uid, \
104        new, \
105        card_type, \
106        card_size, \
107        last_time
108    M5.update()
109    card_0 = stackchan.nfc.detect()
110    if card_0:
111        card_uid = card_0.uid_str
112        card_type = card_0.type_name
113        card_size = card_0.user_memory
114        label_uid.set_text(str((str("UID: ") + str(card_uid))))
115        label_type.set_text(str((str("Tyep: ") + str(card_type))))
116        label_size.set_text(str((str("Size: ") + str(card_size))))
117        if (time.ticks_diff((time.ticks_ms()), last_time)) >= 3000 or new:
118            last_time = time.ticks_ms()
119            stackchan.set_rgb_color(0x009900)
120            Speaker.tone(1234, 100)
121            time.sleep_ms(100)
122            stackchan.set_rgb_color(0x000000)
123        new = False
124    else:
125        new = True
126
127
128if __name__ == "__main__":
129    try:
130        setup()
131        while True:
132            loop()
133    except (Exception, KeyboardInterrupt) as e:
134        try:
135            m5ui.deinit()
136            from utility import print_error_msg
137
138            print_error_msg(e)
139        except ImportError:
140            print("please update to latest firmware")

示例输出:

None

红外(IR)

此示例演示 NEC 风格的红外发送与接收。

MicroPython 代码块:

  1# SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
  2#
  3# SPDX-License-Identifier: MIT
  4
  5import os, sys, io
  6import M5
  7from M5 import *
  8from hardware import IR
  9from hardware.stackchan import StackChan
 10
 11
 12label_title = None
 13label_tx_addr = None
 14label_tx_data = None
 15label_rx_addr = None
 16label_rx_data = None
 17ir = None
 18stackchan = None
 19ir_data = None
 20ir_addr = None
 21tx_data = None
 22ir_tx = None
 23tx_addr = None
 24
 25
 26def ir_rx_event(_data, _addr, _ctrl):
 27    global \
 28        label_title, \
 29        label_tx_addr, \
 30        label_tx_data, \
 31        label_rx_addr, \
 32        label_rx_data, \
 33        ir, \
 34        stackchan, \
 35        ir_data, \
 36        ir_addr, \
 37        tx_data, \
 38        ir_tx, \
 39        tx_addr
 40    ir_data = _data
 41    ir_addr = _addr
 42    label_rx_addr.setText(str((str("RX Addr: ") + str(ir_addr))))
 43    label_rx_data.setText(str((str("RX Data: ") + str(ir_data))))
 44    Speaker.tone(700, 100)
 45
 46
 47def btn_pwr_was_clicked_event(state):
 48    global \
 49        label_title, \
 50        label_tx_addr, \
 51        label_tx_data, \
 52        label_rx_addr, \
 53        label_rx_data, \
 54        ir, \
 55        stackchan, \
 56        ir_data, \
 57        ir_addr, \
 58        tx_data, \
 59        ir_tx, \
 60        tx_addr
 61    tx_data = (tx_data if isinstance(tx_data, (int, float)) else 0) + 1
 62    if tx_data > 255:
 63        tx_data = 0
 64    ir.tx(tx_addr, tx_data)
 65    label_tx_addr.setText(str((str("TX Addr: ") + str(tx_addr))))
 66    label_tx_data.setText(str((str("TX Data: ") + str(tx_data))))
 67
 68
 69def setup():
 70    global \
 71        label_title, \
 72        label_tx_addr, \
 73        label_tx_data, \
 74        label_rx_addr, \
 75        label_rx_data, \
 76        ir, \
 77        stackchan, \
 78        ir_data, \
 79        ir_addr, \
 80        tx_data, \
 81        ir_tx, \
 82        tx_addr
 83
 84    M5.begin()
 85    Widgets.setRotation(1)
 86    Widgets.fillScreen(0x000000)
 87    label_title = Widgets.Label(
 88        "IR TX & RX Example", 41, 5, 1.0, 0x0DC9F4, 0x000000, Widgets.FONTS.Montserrat24
 89    )
 90    label_tx_addr = Widgets.Label(
 91        "TX Addr:", 9, 59, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.Montserrat18
 92    )
 93    label_tx_data = Widgets.Label(
 94        "TX Data:", 170, 59, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.Montserrat18
 95    )
 96    label_rx_addr = Widgets.Label(
 97        "RX Addr:", 10, 100, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.Montserrat18
 98    )
 99    label_rx_data = Widgets.Label(
100        "RX Data:", 170, 100, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.Montserrat18
101    )
102
103    BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btn_pwr_was_clicked_event)
104
105    stackchan = StackChan(i2c=1, uart=1)
106    ir = IR()
107    ir.rx_cb(ir_rx_event)
108    tx_addr = 1
109    tx_data = 0
110    Speaker.begin()
111    Speaker.setVolumePercentage(0.5)
112    label_tx_addr.setText(str((str("TX Addr: ") + str(tx_addr))))
113
114
115def loop():
116    global \
117        label_title, \
118        label_tx_addr, \
119        label_tx_data, \
120        label_rx_addr, \
121        label_rx_data, \
122        ir, \
123        stackchan, \
124        ir_data, \
125        ir_addr, \
126        tx_data, \
127        ir_tx, \
128        tx_addr
129    M5.update()
130    if ir_tx:
131        ir_tx = False
132
133
134if __name__ == "__main__":
135    try:
136        setup()
137        while True:
138            loop()
139    except (Exception, KeyboardInterrupt) as e:
140        try:
141            from utility import print_error_msg
142
143            print_error_msg(e)
144        except ImportError:
145            print("please update to latest firmware")

示例输出:

None

API 参考

StackChan

class hardware.stackchan.StackChan

StackChan 板级驱动:包括 UART 上的 SCS 串口舵机、M5IOE1 上的 RGB 和舵机供电、Si12T 触摸、INA226(电池总线),以及作为 unit.nfc.NFCUnit 的板载 NFC(ST25R3916)。

该类是**单例**;请始终使用相同的 i2cuart id 进行构造。

参数:
  • i2c (int) – I2C 外设 id。

  • uart (int) – 用于 1 Mbaud 舵机总线的 UART id。

After init, the instance exposes nfc (a unit.nfc.NFCUnit—see NFC Unit for the complete API), touch, i2c, and low-level servo (Scscl instance) for advanced use.

模块常量包括 SERVO_ID_X``(``1)、SERVO_ID_Y``(``2)及相关限制值;这些也可作为 StackChan 的类属性使用。

UiFlow2 代码块:

stackchan_api_init.png

MicroPython 代码块:

from hardware.stackchan import StackChan, SERVO_ID_X, SERVO_ID_Y

sc = StackChan(i2c=1, uart=1)
set_servo_zero()

将两个轴的逻辑**零点**保存到 NVS 中(命名空间 servo,键名 zero_pos_1 / zero_pos_2)。

UiFlow2 代码块:

stackchan_api_set_servo_zero.png

MicroPython 代码块:

sc.set_servo_zero()
set_servo_power(enable=True, settle_ms=None)

通过 IO 扩展器启用或关闭舵机供电。

参数:
  • enable (bool) – 打开或关闭电源。

  • settle_ms (int) – Optional power-on wait time in milliseconds. If omitted, the default is 500 ms.

UiFlow2 代码块:

stackchan_api_set_servo_power.png

MicroPython 代码块:

sc.set_servo_power(True)
set_servo_torque(servo_id, enable=True)

启用或禁用单个舵机的力矩。

参数:
  • servo_id (int) – SERVO_ID_XSERVO_ID_Y

  • enable (bool) – 开启或关闭力矩。

UiFlow2 代码块:

stackchan_api_set_servo_torque.png

MicroPython 代码块:

sc.set_servo_torque(SERVO_ID_X, True)
set_servo_angle(servo_id, angle_deg, time_ms=10, speed=0)

将指定舵机移动到 angle_deg``(度)。X 轴(``SERVO_ID_X / 平移)建议范围约为 -135°~135°,Y 轴(SERVO_ID_Y / 俯仰)建议范围为 0°~90°

参数:
  • servo_id (int) – SERVO_ID_XSERVO_ID_Y

  • angle_deg (float) – 目标角度(X 为 -135~135,Y 为 0~90)。

  • time_ms (int) – 传递给控制器的运动时间(毫秒);0 表示时间参数不生效。

  • speed (int) – 用户速度 0~100(映射到总线);0 表示速度参数不生效。

UiFlow2 代码块:

stackchan_api_set_servo_angle_time.png

stackchan_api_set_servo_angle_speed.png

MicroPython 代码块:

sc.set_servo_angle(SERVO_ID_X, 0.0, 500, 0)
sc.set_servo_angle(SERVO_ID_X, 0.0, 0, 50)
get_servo_angle(servo_id)

读取舵机角度(单位:度)。

参数:

servo_id (int) – SERVO_ID_XSERVO_ID_Y

返回:

Angle in degrees, or None if the read failed.

UiFlow2 代码块:

stackchan_api_get_servo_angle.png

MicroPython 代码块:

deg = sc.get_servo_angle(SERVO_ID_X)
set_servo_x_pwm(value)

以 PWM 模式驱动 X 轴舵机进行连续转动。用户范围为 -100~100;符号表示旋转方向,绝对值表示驱动力大小。

参数:

value (int) – 带符号的 PWM 强度(会被限制在范围内)。正负值表示相反的旋转方向;0 表示停止输出。

UiFlow2 代码块:

stackchan_api_set_servo_x_pwm.png

MicroPython 代码块:

sc.set_servo_x_pwm(50)
set_rgb_color(*args)

设置灯带上的 RGB LED。

  • 一个参数:将所有 LED 填充为 color

  • 两个参数:strip``(``01)和该逻辑灯带对应的 color

  • 三个参数:stripindexcolor,用于设置单颗 LED(strip1 时,index 顺序与驱动一致)。

返回:

True on success where applicable.

UiFlow2 代码块:

stackchan_api_set_rgb_color_all.png

stackchan_api_set_rgb_color_strip.png

stackchan_api_set_rgb_color.png

MicroPython 代码块:

sc.set_rgb_color(0x00FF00)
sc.set_rgb_color(0, 0x0000FF)
sc.set_rgb_color(0, 0, 0xFF0000)
get_rgb_color(strip, index)

获取单颗 LED 的 RGB 颜色。

参数:
  • strip (int) – 01

  • index (int) – 每条逻辑灯带范围为 0~5

返回:

tuple (r, g, b)

UiFlow2 代码块:

stackchan_api_get_rgb_color.png

MicroPython 代码块:

r, g, b = sc.get_rgb_color(0, 0)
get_touch(index=None)

读取触摸状态(三个逻辑槽位)。

参数:

index (int) – If None, return a list of three levels; if 0, 1, or 2, return that slot’s level.

返回:

OUTPUT_NONE…``OUTPUT_HIGH`` style values. A read failure returns OUTPUT_NONE values so examples can safely index the result.

UiFlow2 代码块:

stackchan_api_get_touch_all.png

stackchan_api_get_touch.png

MicroPython 代码块:

tp = sc.get_touch()
one = sc.get_touch(0)
get_battery_voltage()

来自 INA226 的总线电压(伏特)。

返回:

float or None if unavailable.

UiFlow2 代码块:

stackchan_api_get_battery_voltage.png

MicroPython 代码块:

v = sc.get_battery_voltage()
get_battery_current()

来自 INA226 的电流(安培)。

返回:

float or None.

UiFlow2 代码块:

stackchan_api_get_battery_current.png

MicroPython 代码块:

a = sc.get_battery_current()
get_battery_power()

当电压和电流都有效时,返回 INA226 的功率(瓦特)。

返回:

float or None.

UiFlow2 代码块:

stackchan_api_get_battery_power.png

MicroPython 代码块:

p = sc.get_battery_power()