Saltar al contenido principal

XIAO STM32C5 con MicroPython

Descripción general​

MicroPython es un intérprete de Python con compatibilidad para emisión de código nativo para el código crítico en rendimiento. Proporciona un subconjunto de las funciones principales de Python 3.6+, optimizado para microcontroladores y sistemas con recursos limitados. Es diferente de CPython, y puedes leer más sobre las diferencias en la página MicroPython vs CPython differences.

Preparación de hardware​

Antes de comenzar, necesitas preparar una placa de desarrollo XIAO STM32C5.

Seeed Studio XIAO STM32C5

Despliegue del firmware MicroPython​

Actualiza el firmware MicroPython en la XIAO STM32C5 entrando en modo bootloader y copiando un archivo de firmware .uf2 a la unidad de almacenamiento masivo USB de la placa.

  1. Descarga y extrae el archivo comprimido del firmware MicroPython para XIAO STM32C5:
  1. Conecta la XIAO STM32C5 a tu ordenador mediante USB. Pulsa rápidamente dos veces el botón Reset para entrar en modo bootloader. Debería aparecer una unidad USB llamada XIAOC5BOOT. Copia micropython-xiao-stm32c5.uf2 desde el archivo extraído a esa unidad. La placa se reiniciará automáticamente después de que el firmware se haya instalado.
tip

Si XIAOC5BOOT no aparece, desconecta y vuelve a conectar el cable USB y, a continuación, pulsa dos veces Reset de nuevo. Asegúrate de que el cable USB admite transferencia de datos.

Instalar Thonny IDE​

Elige la versión adecuada para la instalación. Aquí lo estoy instalando en un sistema Windows, así que he seleccionado la versión para Windows.

Sigue las instrucciones para la versión de Python deseada.


Luego, simplemente sigue los pasos predeterminados para la configuración.

Configuración del intérprete​

  • Abre Thonny IDE y configura el intérprete y el puerto serie para la XIAO STM32C5.
  • Después de que el firmware se haya instalado y Thonny se haya conectado a la XIAO STM32C5, la información del firmware MicroPython aparecerá en el panel Shell.

Mapa de pines​

tip
Pin / SeñalGPIOFunciónUso en MicroPython
D0 / A0PA0ADC1_IN0 / GPIOXiaoADC(0) / XiaoPin(0)
D1 / A1PA1ADC1_IN1 / GPIOXiaoADC(1) / XiaoPin(1)
D2 / A2PA2ADC1_IN2 / GPIOXiaoADC(2) / XiaoPin(2)
D3 / A3PA3ADC1_IN3 / GPIOXiaoADC(3) / XiaoPin(3)
D4PB7I2C1 SDA / GPIODatos I2C del encabezado
D5PB6I2C1 SCL / GPIOReloj I2C del encabezado
D6PA9USART1 TX / GPIOTransmisión UART
D7PA10USART1 RX / GPIORecepción UART
D8PE2SPI3 SCK / GPIOReloj SPI por hardware
D9PB0SPI3 MISO / GPIOEntrada de datos SPI por hardware
D10PB15SPI3 MOSI / GPIOSalida de datos SPI por hardware
D11PB8GPIO / FDCAN1 RX compatibleFDCAN1 no está habilitado en el firmware actual
D12PB9GPIO / FDCAN1 TX compatibleFDCAN1 no está habilitado en el firmware actual
D13PB5FDCAN2 RX / GPIOXiaoCAN("can0", ...)
D14PB13FDCAN2 TX / GPIOXiaoCAN("can0", ...)
D15 / CAN_STBPB14Modo de espera del transceptor CANBajo: normal; alto: espera
CAN_H—Bus diferencial CAN FD altoSalida del transceptor CAN integrado; no es un GPIO
CAN_L—Bus diferencial CAN FD bajoSalida del transceptor CAN integrado; no es un GPIO
BAT_ENPA15Habilitación de medición de bateríaXiaoPin("bat_en", Pin.OUT)
BAT_READPA4ADC1_IN4XiaoADC("vbat"); el voltaje de la batería es aproximadamente el voltaje ADC × 2
IMU_SDAPB4I2C2 SDADatos del LSM6DS3TR-C integrado
IMU_SCLPB3I2C2 SCLReloj del LSM6DS3TR-C integrado
IMU_INTPC13IMU INT1XiaoPin("imu_int", Pin.IN)
IMU_HEATER_PWMPA8TIM1_CH1 PWMXiaoPWM(0)
USER_LEDPB12Salida GPIOXiaoPin("led", Pin.OUT); activo en bajo

Parpadeo de LED​

Los pines digitales realizan principalmente el control de encendido y apagado de sensores y actuadores externos mediante la salida de niveles lógicos altos y bajos. Combinado con la placa de expansión Grove Base for XIAO y los módulos periféricos Grove estándar, esta sección detalla la lógica de controlador subyacente y los métodos prácticos de invocación de los pines digitales en la XIAO STM32C5.

Preparación de hardware​

Seeed Studio XIAO STM32C5Seeed Studio Grove Base for XIAO Grove - LED Pack

Programa de software​

Este ejemplo utiliza el pin D1 de la XIAO STM32C5 como salida digital para hacer parpadear un LED externo.

  1. En Thonny, selecciona File > New para crear un archivo y luego introduce el siguiente código:
# Copyright (c) 2026 Seeed Technology Co., Ltd.
# SPDX-License-Identifier: Apache-2.0
#
# Blinky example for XIAO STM32C5 with TinyUF2 bootloader support.

import time
from machine import Pin
from boards.xiao import XiaoPin

SLEEP_TIME_MS = 1000
D1_PIN = 1

# D1 corresponds to PA1.
d1_gpio = XiaoPin(D1_PIN, Pin.OUT)

# Match GPIO_OUTPUT_ACTIVE in the Zephyr example.
led_state = True
d1_gpio.value(1)

try:
while True:
led_state = not led_state
d1_gpio.value(1 if led_state else 0)

print("LED state:", "ON" if led_state else "OFF")
time.sleep_ms(SLEEP_TIME_MS)

except KeyboardInterrupt:
# Turn the output off when the script is stopped.
d1_gpio.value(0)
print("Blinky stopped")
  1. Selecciona Run > Run current script, o pulsa F5.

Resultado​

Después de que el script se inicie, el LED externo conectado a D1 parpadea a intervalos de un segundo.

Controlar un servomotor​

PWM es una función de salida de forma de onda temporizada implementada sobre la base de GPIO de salida digital. Cambia rápidamente los niveles del pin a una frecuencia fija y ajusta dinámicamente el ciclo de trabajo del nivel alto dentro de un solo ciclo, para así enviar señales analógicas equivalentes a los periféricos. En aplicaciones de ingeniería práctica, PWM se utiliza ampliamente para el control preciso del ángulo de servomotores y el ajuste suave del brillo de los LED.

Preparación de hardware​

Seeed Studio XIAO STM32C5Seeed Studio Grove Base for XIAO Grove - Servo

Programa de software​

Este ejemplo utiliza D0 en el XIAO STM32C5 como el pin de señal de control del servo. El temporizado se genera por software en lugar de mediante un periférico PWM de hardware.

  1. En Thonny, selecciona File > New para crear un archivo y luego introduce el siguiente código:
import time
from machine import Pin
from boards.xiao import XiaoPin

SERVO_PIN = 0 # D0 / PA0
SERVO_PERIOD_US = 20_000 # 50 Hz
SERVO_MIN_US = 1_000 # Approximately 0 degrees
SERVO_MAX_US = 2_000 # Approximately 180 degrees

servo = XiaoPin(SERVO_PIN, Pin.OUT)
servo.value(0)


def output_servo_frame(angle):
"""Output one 20 ms servo-control frame."""
angle = max(0, min(180, angle))

pulse_us = SERVO_MIN_US + (
angle * (SERVO_MAX_US - SERVO_MIN_US) // 180
)

frame_start = time.ticks_us()

servo.value(1)
time.sleep_us(pulse_us)
servo.value(0)

elapsed = time.ticks_diff(time.ticks_us(), frame_start)
remaining = SERVO_PERIOD_US - elapsed

if remaining > 0:
time.sleep_us(remaining)


def hold_angle(angle, duration_ms):
"""Keep the servo at an angle for the specified duration."""
frames = max(1, duration_ms // 20)

for _ in range(frames):
output_servo_frame(angle)


try:
print("Software servo control started on D0")

hold_angle(90, 1000)

while True:
for angle in range(0, 181):
output_servo_frame(angle)

for angle in range(180, -1, -1):
output_servo_frame(angle)

except KeyboardInterrupt:
print("Servo control stopped")

finally:
servo.value(0)
  1. Selecciona Run > Run current script, o pulsa F5.

Resultado​

Después de que el script se inicie, el servo barre repetidamente desde aproximadamente 0° hasta 180° y de vuelta a 0°. El bucle avanza la orden en 1° por cada trama de control de 20 ms, lo que corresponde a una velocidad nominal de barrido de 50°/s (aproximadamente 0,87 rad/s), excluyendo la sobrecarga de ejecución y los propios límites mecánicos del servo.

Controlar un motor DM-J4310-2EC mediante CAN​

El XIAO STM32C5 proporciona dos interfaces CAN FD. Una expone señales de transmisión y recepción a nivel lógico para usar con un transceptor CAN externo. La otra está conectada al transceptor CAN FD TCAN3403-Q1 integrado y puede conectarse directamente a un bus CAN a través de CAN_H y CAN_L. CAN FD sigue siendo compatible con CAN Clásico y al mismo tiempo admite una carga útil mayor y una mayor velocidad de bits en la fase de datos, lo que lo hace adecuado para electrónica automotriz, control industrial, robótica y redes de sensores distribuidos.

El circuito CAN FD integrado también incluye una resistencia de terminación de 120 Ω desconectable. Activa esta resistencia cuando la placa esté en un extremo del bus. Desconéctala cuando la placa esté en medio del bus o cuando ya haya otra resistencia de 120 Ω instalada en el extremo del bus del XIAO. El transceptor también admite control de modo de espera para reducir el consumo de energía cuando la comunicación CAN no está en uso.

Preparación de hardware​

  • XIAO STM32C5 × 1
  • Ordenador y cable de datos USB tipo C
  • Fuente de alimentación de CC regulada de 24 V adecuada para el motor
  • Motor DM-J4310-2EC × 1
  • XT30 2+2 Power Separation Board
Seeed Studio XIAO STM32C5DM4310XT30 2+2 Power Separation Board

Programa de software​

precaución

El motor requiere una alimentación de 24 V. Utiliza una fuente de alimentación conforme, limitada en corriente y con una corriente nominal adecuada. Apaga la fuente antes de cambiar cualquier cableado, mantén el motor sin carga durante las pruebas iniciales y conecta la tierra del XIAO al terminal negativo de la fuente de alimentación del motor. Una alimentación o un cableado incorrectos pueden dañar el equipo y crear un riesgo para la seguridad.

  1. Utiliza el software de configuración Damiao para ordenador host para establecer el CAN ID del motor en 0x001 y el Master ID en 0x011. Para más detalles, consulta Getting Started with the Damiao 43 Series Motors.
  • Conecta el motor a las almohadillas CAN_H y CAN_L traseras del XIAO STM32C5. El transceptor integrado utiliza D13/PB5 (RX), D14/PB13 (TX) y D15/PB14 (modo de espera); lleva D15 a nivel bajo para el funcionamiento normal.
  1. En Thonny, selecciona File > New para crear un archivo y luego guarda el siguiente programa como driver_dm4310.py:
driver_dm4310.py
"""Drive a Damiao DM-J4310-2EC V1.1 from XIAO STM32C5.
This motor example uses Classical CAN at 1 Mbit/s (fd=False), although the board supports CAN FD.
Protocol: classic CAN 2.0B at 1 Mbit/s, Damiao speed mode.

Wiring:
XIAO CAN_H -> motor CAN_H
XIAO CAN_L -> motor CAN_L
XIAO GND -> motor GND / 24 V negative
Motor VCC -> external 24 V positive

Use 120-ohm termination at both ends of the CAN bus. Never connect the
MCU-side CAN TX/RX pins directly to the motor.
"""

import struct
import time
from machine import Pin
from boards.xiao import XiaoCAN, XiaoPin


# Motor and CAN configuration.
DM_MOTOR_ID = 0x001
DM_MASTER_ID = 0x011
DM_CAN_BITRATE = 1_000_000

DM_CONTROL_ID = 0x200 + DM_MOTOR_ID
DM_PARAM_ID = 0x7FF

DM_CMD_ENABLE = 0xFC
DM_CMD_DISABLE = 0xFD
DM_CMD_CLEAR_ERROR = 0xFB

DM_PARAM_WRITE = 0x55
DM_REG_CTRL_MODE = 0x0A
DM_CTRL_MODE_SPEED = 3

DM_VMAX_RAD_S = 30.0

CONTROL_PERIOD_MS = 20
GEAR_PERIOD_MS = 5_000
FEEDBACK_PRINT_MS = 500
START_DELAY_MS = 3_000

# Same speed sequence as the Zephyr example.
SPEED_SEQUENCE = (0.0, 3.0, 6.0, 10.0, 6.0, 3.0)


def send_control(can, command):
payload = bytes((0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, command))
can.send(DM_CONTROL_ID, payload)


def write_u32_parameter(can, register, value):
payload = struct.pack(
"<HBBI",
DM_MOTOR_ID,
DM_PARAM_WRITE,
register,
value,
)
can.send(DM_PARAM_ID, payload)


def send_speed(can, speed_rad_s):
can.send(DM_CONTROL_ID, struct.pack("<f", float(speed_rad_s)))


def decode_feedback(frame):
"""Return decoded Damiao feedback, or None for an unrelated frame."""
if frame is None or len(frame) < 2:
return None

can_id = frame[0]
data = frame[1]

if data is None or len(data) < 8:
return None

motor_id = data[0] & 0x0F
if motor_id != (DM_MOTOR_ID & 0x0F):
return None

fault = data[0] >> 4
position_raw = (data[1] << 8) | data[2]
velocity_raw = (data[3] << 4) | (data[4] >> 4)
torque_raw = ((data[4] & 0x0F) << 8) | data[5]

velocity_rad_s = (
(velocity_raw / 4095.0) * 2.0 - 1.0
) * DM_VMAX_RAD_S

return {
"can_id": can_id,
"motor_id": motor_id,
"fault": fault,
"position_raw": position_raw,
"velocity_raw": velocity_raw,
"velocity_rad_s": velocity_rad_s,
"torque_raw": torque_raw,
"mos_temperature": data[6],
"rotor_temperature": data[7],
}


def print_feedback(feedback):
print(
"FB id=0x{:03X} motor={} err=0x{:X} pos=0x{:04X} "
"vel=0x{:03X} {:.2f}rad/s tq=0x{:03X} mos={}C rotor={}C".format(
feedback["can_id"],
feedback["motor_id"],
feedback["fault"],
feedback["position_raw"],
feedback["velocity_raw"],
feedback["velocity_rad_s"],
feedback["torque_raw"],
feedback["mos_temperature"],
feedback["rotor_temperature"],
)
)


def drain_feedback(can, duration_ms, print_frames=False):
deadline = time.ticks_add(time.ticks_ms(), duration_ms)

while time.ticks_diff(deadline, time.ticks_ms()) > 0:
frame = can.recv(0)
if frame is None:
time.sleep_ms(5)
continue

feedback = decode_feedback(frame)
if print_frames and feedback is not None:
print_feedback(feedback)


def initialise_motor(can):
# Clear error -> select speed mode -> enable.
send_control(can, DM_CMD_CLEAR_ERROR)
drain_feedback(can, 100)

write_u32_parameter(can, DM_REG_CTRL_MODE, DM_CTRL_MODE_SPEED)
drain_feedback(can, 100)

send_control(can, DM_CMD_ENABLE)
drain_feedback(can, 100)


def stop_motor(can):
"""Best-effort controlled stop followed by motor disable."""
if can is None:
return

try:
# Repeat zero speed briefly so the motor receives the stop request.
for _ in range(5):
send_speed(can, 0.0)
time.sleep_ms(CONTROL_PERIOD_MS)
send_control(can, DM_CMD_DISABLE)
time.sleep_ms(50)
except Exception as exc:
print("Motor stop warning:", exc)


def main():
can = None
can_stb = None

try:
print("XIAO STM32C5 DM-J4310-2EC speed-mode driver")
print(
"motor_id=0x{:03X} master_id=0x{:03X} bitrate={}Mbps".format(
DM_MOTOR_ID,
DM_MASTER_ID,
DM_CAN_BITRATE // 1_000_000,
)
)

# PB14 / D15 is active-high standby. Drive it low for normal mode.
can_stb = XiaoPin(15, Pin.OUT)
can_stb.value(0)
time.sleep_ms(10)

can = XiaoCAN(
"can0",
bitrate=DM_CAN_BITRATE,
data_bitrate=2_000_000,
fd=False,
loopback=False,
)

print("Check wiring and keep the motor unloaded.")
print("Starting in {} seconds...".format(START_DELAY_MS // 1000))
time.sleep_ms(START_DELAY_MS)

initialise_motor(can)
print("Motor enabled in speed mode")

sequence_index = 0
speed = SPEED_SEQUENCE[sequence_index]
print("Target speed: {:.2f} rad/s".format(speed))

next_gear = time.ticks_add(time.ticks_ms(), GEAR_PERIOD_MS)
last_feedback_print = time.ticks_ms()

while True:
now = time.ticks_ms()

if time.ticks_diff(now, next_gear) >= 0:
sequence_index = (sequence_index + 1) % len(SPEED_SEQUENCE)
speed = SPEED_SEQUENCE[sequence_index]
print("Target speed: {:.2f} rad/s".format(speed))
next_gear = time.ticks_add(next_gear, GEAR_PERIOD_MS)

send_speed(can, speed)

# Drain every queued frame so the MicroPython CAN queue cannot fill.
while True:
frame = can.recv(0)
if frame is None:
break

feedback = decode_feedback(frame)
if feedback is None:
continue

if time.ticks_diff(now, last_feedback_print) >= FEEDBACK_PRINT_MS:
print_feedback(feedback)
last_feedback_print = now

time.sleep_ms(CONTROL_PERIOD_MS)

except KeyboardInterrupt:
print("User requested stop")
except Exception as exc:
print("Motor driver error:", exc)
raise
finally:
stop_motor(can)

if can is not None:
try:
can.deinit()
except Exception:
pass

if can_stb is not None:
try:
can_stb.value(1)
except Exception:
pass

print("Motor disabled; CAN transceiver in standby")


if __name__ == "__main__":
main()

  1. Selecciona Run > Run current script o presiona F5.

Resultado​

Después de que el motor esté habilitado, el DM-J4310 recorre velocidades objetivo de 0 → 3 → 6 → 10 → 6 → 3 rad/s, cambiando el objetivo cada cinco segundos. La Shell muestra la velocidad objetivo seleccionada y los datos de retroalimentación del motor decodificados. El ejemplo no controla el LED integrado.

tip

Conecta CAN_H en la XIAO STM32C5 a CAN_H en el motor, y CAN_L a CAN_L. Conecta también las tierras. Invertir las líneas diferenciales u omitir la tierra común puede impedir la comunicación.

Soporte técnico y debate sobre el producto​

Gracias por elegir nuestros productos. Estamos aquí para ofrecerte diferentes tipos de soporte y garantizar que tu experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para adaptarnos a distintas preferencias y necesidades.

Loading Comments...