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
 42
 43def button_save_event_handler(event_struct):
 44    global \
 45        page0, \
 46        label_title, \
 47        button_save, \
 48        label_angle_x, \
 49        label_angle_y, \
 50        label_tip, \
 51        stackchan, \
 52        x_angle, \
 53        y_angle, \
 54        last_time
 55    event = event_struct.code
 56    if event == lv.EVENT.SHORT_CLICKED and True:
 57        button_save_short_clicked_event(event_struct)
 58    return
 59
 60
 61def setup():
 62    global \
 63        page0, \
 64        label_title, \
 65        button_save, \
 66        label_angle_x, \
 67        label_angle_y, \
 68        label_tip, \
 69        stackchan, \
 70        x_angle, \
 71        y_angle, \
 72        last_time
 73
 74    M5.begin()
 75    Widgets.setRotation(1)
 76    m5ui.init()
 77    page0 = m5ui.M5Page(bg_c=0x000000)
 78    label_title = m5ui.M5Label(
 79        "Servo Calibration",
 80        x=55,
 81        y=5,
 82        text_c=0x0DC9F4,
 83        bg_c=0x000000,
 84        bg_opa=0,
 85        font=lv.font_montserrat_24,
 86        parent=page0,
 87    )
 88    button_save = m5ui.M5Button(
 89        text="Save",
 90        x=128,
 91        y=195,
 92        bg_c=0x2196F3,
 93        text_c=0xFFFFFF,
 94        font=lv.font_montserrat_14,
 95        parent=page0,
 96    )
 97    label_angle_x = m5ui.M5Label(
 98        "X-Axis Servo Angle:",
 99        x=10,
100        y=130,
101        text_c=0x0DC9F4,
102        bg_c=0x000000,
103        bg_opa=0,
104        font=lv.font_montserrat_18,
105        parent=page0,
106    )
107    label_angle_y = m5ui.M5Label(
108        "Y-Axis Servo Angle:",
109        x=8,
110        y=160,
111        text_c=0x0DC9F4,
112        bg_c=0x000000,
113        bg_opa=0,
114        font=lv.font_montserrat_18,
115        parent=page0,
116    )
117    label_tip = m5ui.M5Label(
118        "Tip:Move by hand, tap Save.",
119        x=33,
120        y=70,
121        text_c=0xD2E711,
122        bg_c=0xFFFFFF,
123        bg_opa=0,
124        font=lv.font_montserrat_18,
125        parent=page0,
126    )
127
128    button_save.add_event_cb(button_save_event_handler, lv.EVENT.ALL, None)
129
130    stackchan = StackChan(i2c=1, uart=1)
131    page0.screen_load()
132    stackchan.set_servo_power(enable=True)
133    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=False)
134    stackchan.set_servo_torque(stackchan.SERVO_ID_Y, enable=False)
135    Speaker.begin()
136    Speaker.setVolumePercentage(0.6)
137    Speaker.tone(1000, 100)
138
139
140def loop():
141    global \
142        page0, \
143        label_title, \
144        button_save, \
145        label_angle_x, \
146        label_angle_y, \
147        label_tip, \
148        stackchan, \
149        x_angle, \
150        y_angle, \
151        last_time
152    M5.update()
153    if (time.ticks_diff((time.ticks_ms()), last_time)) >= 100:
154        x_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_X)
155        y_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_Y)
156        label_angle_x.set_text(str((str("X-Axis Servo Angle:") + str(x_angle))))
157        label_angle_y.set_text(str((str("Y-Axis Servo Angle:") + str(y_angle))))
158
159
160if __name__ == "__main__":
161    try:
162        setup()
163        while True:
164            loop()
165    except (Exception, KeyboardInterrupt) as e:
166        try:
167            m5ui.deinit()
168            from utility import print_error_msg
169
170            print_error_msg(e)
171        except ImportError:
172            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)
61    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=False)
62    stackchan.set_servo_torque(stackchan.SERVO_ID_Y, enable=False)
63
64
65def loop():
66    global page0, label_title, label_agnle_x, label_angle_y, stackchan, last_time, x_angle, y_angle
67    M5.update()
68    if (time.ticks_diff((time.ticks_ms()), last_time)) >= 100:
69        last_time = time.ticks_ms()
70        x_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_X)
71        y_angle = stackchan.get_servo_angle(stackchan.SERVO_ID_Y)
72        label_agnle_x.set_text(str((str("X-Axis Servo Angle: ") + str(x_angle))))
73        label_angle_y.set_text(str((str("Y-Axis Servo Angle: ") + str(y_angle))))
74
75
76if __name__ == "__main__":
77    try:
78        setup()
79        while True:
80            loop()
81    except (Exception, KeyboardInterrupt) as e:
82        try:
83            m5ui.deinit()
84            from utility import print_error_msg
85
86            print_error_msg(e)
87        except ImportError:
88            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 setup():
21    global page0, label_title, label_status, stackchan
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 Control Example",
29        x=20,
30        y=5,
31        text_c=0x0DC9F4,
32        bg_c=0x000000,
33        bg_opa=0,
34        font=lv.font_montserrat_24,
35        parent=page0,
36    )
37    label_status = m5ui.M5Label(
38        "--",
39        x=153,
40        y=115,
41        text_c=0x0DC9F4,
42        bg_c=0xFFFFFF,
43        bg_opa=0,
44        font=lv.font_montserrat_16,
45        parent=page0,
46    )
47
48    stackchan = StackChan(i2c=1, uart=1)
49    page0.screen_load()
50    Speaker.begin()
51    Speaker.setVolumePercentage(0.5)
52    stackchan.set_servo_power(enable=True)
53    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=True)
54    stackchan.set_servo_torque(stackchan.SERVO_ID_X, enable=True)
55    stackchan.set_servo_angle(stackchan.SERVO_ID_X, 0, 1000, 0)
56    stackchan.set_servo_angle(stackchan.SERVO_ID_Y, 45, 1000, 0)
57    Speaker.tone(678, 300)
58    time.sleep_ms(2000)
59    label_status.set_text(str("Rotate counterclockwise"))
60    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
61    stackchan.set_servo_x_pwm(-50)
62    time.sleep_ms(3000)
63    label_status.set_text(str("Rotate clockwise"))
64    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
65    stackchan.set_servo_x_pwm(50)
66    time.sleep_ms(3000)
67    label_status.set_text(str("Go back to center"))
68    label_status.align_to(page0, lv.ALIGN.CENTER, 0, 0)
69    stackchan.set_servo_angle(stackchan.SERVO_ID_X, 0, 1000, 0)
70
71
72def loop():
73    global page0, label_title, label_status, stackchan
74    M5.update()
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

人脸跟踪

案例实现人脸跟踪功能

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
 87
 88def loop():
 89    global \
 90        page0, \
 91        label_title, \
 92        label_voltage, \
 93        label_current, \
 94        label_power, \
 95        stackchan, \
 96        last_time, \
 97        volatge, \
 98        current, \
 99        power
100    M5.update()
101    if (time.ticks_diff((time.ticks_ms()), last_time)) >= 200:
102        last_time = time.ticks_ms()
103        volatge = stackchan.get_battery_voltage()
104        current = stackchan.get_battery_current()
105        power = stackchan.get_battery_power()
106        label_voltage.set_text(str((str("Voltage: ") + str((str(volatge) + str(" V"))))))
107        label_current.set_text(str((str("Current: ") + str((str(current) + str(" A"))))))
108        label_power.set_text(str((str("Power: ") + str((str(power) + str(" W"))))))
109
110
111if __name__ == "__main__":
112    try:
113        setup()
114        while True:
115            loop()
116    except (Exception, KeyboardInterrupt) as e:
117        try:
118            m5ui.deinit()
119            from utility import print_error_msg
120
121            print_error_msg(e)
122        except ImportError:
123            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
 14page0 = None
 15label_title = None
 16stackchan = None
 17tp = None
 18tp1 = None
 19last_time = None
 20tp2 = None
 21tp3 = None
 22
 23
 24def setup():
 25    global page0, label_title, stackchan, tp, tp1, last_time, tp2, tp3
 26
 27    M5.begin()
 28    Widgets.setRotation(1)
 29    m5ui.init()
 30    page0 = m5ui.M5Page(bg_c=0x000000)
 31    label_title = m5ui.M5Label(
 32        "TP & RGB Strip Example",
 33        x=13,
 34        y=10,
 35        text_c=0x0DC9F4,
 36        bg_c=0x000000,
 37        bg_opa=0,
 38        font=lv.font_montserrat_24,
 39        parent=page0,
 40    )
 41
 42    stackchan = StackChan(i2c=1, uart=1)
 43    page0.screen_load()
 44    last_time = [0, 0, 0]
 45    Speaker.begin()
 46    Speaker.setVolumePercentage(0.5)
 47
 48
 49def loop():
 50    global page0, label_title, stackchan, tp, tp1, last_time, tp2, tp3
 51    M5.update()
 52    tp = stackchan.get_touch()
 53    tp1 = tp[0]
 54    tp2 = tp[1]
 55    tp3 = tp[2]
 56    if tp1:
 57        last_time[0] = time.ticks_ms()
 58        stackchan.set_rgb_color(0, 0, 0x33CC00)
 59        stackchan.set_rgb_color(0, 1, 0x33CC00)
 60        stackchan.set_rgb_color(1, 0, 0x33CC00)
 61        stackchan.set_rgb_color(1, 1, 0x33CC00)
 62        Speaker.tone(700, 50)
 63    else:
 64        if (time.ticks_diff((time.ticks_ms()), (last_time[0]))) > 300:
 65            stackchan.set_rgb_color(0, 0, 0x000000)
 66            stackchan.set_rgb_color(0, 1, 0x000000)
 67            stackchan.set_rgb_color(1, 0, 0x000000)
 68            stackchan.set_rgb_color(1, 1, 0x000000)
 69    if tp2:
 70        last_time[1] = time.ticks_ms()
 71        stackchan.set_rgb_color(0, 2, 0x00CCCC)
 72        stackchan.set_rgb_color(0, 3, 0x00CCCC)
 73        stackchan.set_rgb_color(1, 2, 0x00CCCC)
 74        stackchan.set_rgb_color(1, 3, 0x00CCCC)
 75        Speaker.tone(900, 50)
 76    else:
 77        if (time.ticks_diff((time.ticks_ms()), (last_time[1]))) > 300:
 78            stackchan.set_rgb_color(0, 2, 0x000000)
 79            stackchan.set_rgb_color(0, 3, 0x000000)
 80            stackchan.set_rgb_color(1, 2, 0x000000)
 81            stackchan.set_rgb_color(1, 3, 0x000000)
 82    if tp3:
 83        last_time[2] = time.ticks_ms()
 84        stackchan.set_rgb_color(0, 4, 0x000099)
 85        stackchan.set_rgb_color(0, 5, 0x000099)
 86        stackchan.set_rgb_color(1, 4, 0x000099)
 87        stackchan.set_rgb_color(1, 5, 0x000099)
 88        Speaker.tone(1100, 50)
 89    else:
 90        if (time.ticks_diff((time.ticks_ms()), (last_time[2]))) > 300:
 91            stackchan.set_rgb_color(0, 4, 0x000000)
 92            stackchan.set_rgb_color(0, 5, 0x000000)
 93            stackchan.set_rgb_color(1, 4, 0x000000)
 94            stackchan.set_rgb_color(1, 5, 0x000000)
 95
 96
 97if __name__ == "__main__":
 98    try:
 99        setup()
100        while True:
101            loop()
102    except (Exception, KeyboardInterrupt) as e:
103        try:
104            m5ui.deinit()
105            from utility import print_error_msg
106
107            print_error_msg(e)
108        except ImportError:
109            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)

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

参数:

enable (bool) – 打开或关闭电源。

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) – 如果为 None,返回包含三个电平值的列表;如果为 012,则返回对应槽位的电平值。

返回:

返回 OUTPUT_NONEOUTPUT_HIGH 这类值;失败时返回 None

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()