树莓派CM0 NANO + TMC2209 步进电机云控方案:MQTT远程控制与LabVIEW自动化采集全解析

本文详细讲解如何利用工业树莓派 CM0 NANO 单板计算机搭配 TMC2209 驱动模块,实现 42 步进电机的精准控制。通过 MQTT 协议打通远程通信,再结合 LabVIEW 设计上位机软件,完成步进电机的自动化连续运行与数据采集,覆盖硬件接线、环境配置、OLED 显示、控制流程图、核心工程代码以及实际效果演示。
项目整体规划
- 前期准备:梳理所需硬件,搭建开发环境,安装必要的 Python 库;
- OLED 信息展示:读取 RTC 时间,驱动 OLED 显示文字与动画;
- 步进电机控制:通过 GPIO 控制 TMC2209 驱动步进电机,并在 OLED 上实时更新旋转状态;
- 云端远程操控:借助 MQTT 协议与云端服务器,实现跨网络控制步进电机;
- 自动化数据采集:设计 LabVIEW 上位机,让步进电机按照预定逻辑自动运行,并完成模拟数据采集与存储。
硬件连接详解
- 使用 Type-C 数据线为开发板供电;
- 用杜邦线将 CM0 NANO 开发板与 0.96 寸 OLED 模块相连;
- 再用杜邦线连接 CM0 NANO 和 TMC2209 步进电机驱动模块;
- 将步进电机正确接入 TMC2209 模块;
- 单独为 TMC2209 提供工业电源,保证步进电机稳定工作。
OLED 模块接入
OLED 模块采用 SSD1306 驱动,将其挂载在 IIC 总线 1 上。

实物连接示意如下:

对应的接线方式:

步进电机与驱动器配置
选用 NIDEC 尼得科 8mm 丝杆步进电机,步距角为 18 度。
参考链接:https://www.nidec.com/cn/product/search/category/B101/M109/S100/
步进电机外观示意如下:

驱动器使用 TMC2209,详细参数可查阅:https://item.szlcsc.com/18504516.html

具体接线方式可参考下图,根据步进电机额定电压选择供电方案。本例中使用板载 5V 排针直接为驱动器供电。

软件环境搭建
本项目使用 luma.oled 库驱动 OLED 模块,在终端执行以下命令即可安装:
pip install luma.oled
更多信息可参考:https://pypi.org/project/luma.oled/
MQTT 协议与 Broker
MQTT 是一种专为物联网设计的轻量级通信协议,其核心组件为 MQTT 服务器(Broker)。
MQTT Broker 充当中介实体,负责管理设备与应用之间的消息流转:接收客户端发布的消息,根据主题(Topic)进行过滤,再分发给所有订阅者。

用户既可以使用 EMQX 自行搭建本地 MQTT 服务器,也可以选用云服务商提供的 Broker。
本次教程使用 KZone喵星球 IoT Studio 作为消息中转平台,地址为:https://kzone2.kittenbot.cn/

OLED 显示驱动开发
为打造一款桌面 RTC 时钟,我们让 OLED 屏幕同时显示数字时钟、日期以及动态动画。
代码实现
在终端执行 touch oled_rtc.py 新建文件,并写入以下内容:
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import Image, ImageFont
import os
import time
import datetime
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial)
# 字体设置
font_fg = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 22)
font_date = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
# 加载20帧动画
frame_list = []
for i in range(1, 21):
img_path = os.path.join("frame", f"{i}.jpg")
img = Image.open(img_path).convert("1")
frame_list.append(img)
frame_index = 0
try:
while True:
now = datetime.datetime.now()
date_str = now.strftime("%Y-%m-%d")
time_str = now.strftime("%H:%M")
with canvas(device) as draw:
# 显示动画
draw.bitmap((32, 0), frame_list[frame_index], fill=1)
# 显示时间
draw.text((2, 25), time_str, font=font_fg, fill=1)
# 显示日期
draw.text((2, 5), date_str, fill=1)
frame_index = (frame_index + 1) % 20
time.sleep(0.1)
except KeyboardInterrupt:
device.clear()
动画帧图片可以自行定制,从 GIF 或视频中提取帧并按数字顺序命名,放入 frame 文件夹即可。
显示效果
在终端运行 python rtc_oled.py 后,OLED 模块会循环显示日期、时间以及太空人动画。

步进电机驱动与状态显示
利用树莓派 CM0 NANO 的 40pin GPIO,通过高低电平控制 TMC2209,驱动 42 步进电机转动,并同步在 OLED 上展示转速、角度和旋转方向。
核心代码
执行 touch stepmotor_oled.py 新建文件,添加如下代码:
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import Image, ImageFont
import os
import time
import datetime
import threading
import RPi.GPIO as GPIO
# ---------------------- OLED 初始化 ----------------------
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial)
# 字体
font_main = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
# 加载帧动画
frame_list = []
for i in range(1, 21):
img = Image.open(os.path.join("frame", f"{i}.jpg")).convert("1")
frame_list.append(img)
frame_index = 0
# ---------------------- 运行状态变量 ----------------------
current_angle = 180
speed_ms = 0.2
direct_text = "CW"
running = True
# 独立屏幕刷新线程(动画持续运行,不受电机阻塞)
def screen_task():
global frame_index
while running:
now = datetime.datetime.now()
date_str = now.strftime("%Y-%m-%d")
time_str = now.strftime("%H:%M:%S")
with canvas(device) as draw:
# 太空人动画
draw.bitmap((32, 0), frame_list[frame_index], fill=1)
# 左侧时间信息
draw.text((0, 0), date_str, font=font_small, fill=1)
draw.text((0, 11), time_str, font=font_main, fill=1)
# 左侧电机运行状态
info_dir = f"Dir: {direct_text}"
info_speed = f"V: {speed_ms}ms"
info_angle = f"Ang: {abs(current_angle)}deg"
draw.text((2, 24), info_dir, font=font_small, fill=1)
draw.text((2, 36), info_speed, font=font_small, fill=1)
draw.text((2, 48), info_angle, font=font_small, fill=1)
frame_index = (frame_index + 1) % len(frame_list)
time.sleep(0.1)
# ---------------------- 电机驱动函数 ----------------------
STEP = 17
DIR = 27
EN = 22
PULSE_PER_360DEG = 1600 # 8细分
PULSE_PER_DEG = PULSE_PER_360DEG / 360
GPIO.setmode(GPIO.BCM)
GPIO.setup(STEP, GPIO.OUT)
GPIO.setup(DIR, GPIO.OUT)
GPIO.setup(EN, GPIO.OUT)
GPIO.output(EN, GPIO.LOW)
def run_angle(degree, speed):
global direct_text
pulses = int(abs(degree) * PULSE_PER_DEG)
if degree > 0:
GPIO.output(DIR, GPIO.HIGH)
direct_text = "CW"
else:
GPIO.output(DIR, GPIO.LOW)
direct_text = "CCW"
delay_s = speed / 1000.0
# 发送脉冲,保证转速稳定
for _ in range(pulses):
GPIO.output(STEP, GPIO.HIGH)
time.sleep(delay_s)
GPIO.output(STEP, GPIO.LOW)
time.sleep(delay_s)
if __name__ == "__main__":
# 启动后台屏幕线程
t = threading.Thread(target=screen_task, daemon=True)
t.start()
try:
while True:
current_angle = 180
run_angle(current_angle, speed_ms)
time.sleep(1)
current_angle = -180
run_angle(current_angle, speed_ms)
time.sleep(1)
except KeyboardInterrupt:
running = False
time.sleep(0.2)
device.clear()
GPIO.output(EN, GPIO.HIGH)
GPIO.cleanup()
运行效果
在终端执行 python stepmotor_oled.py 后,OLED 会同步显示日期、时间、动画以及电机的方向、速度、角度等信息;同时步进电机在程序控制下正反交替旋转。

基于 MQTT 的远程控制
让树莓派 CM0 NANO 连接网络,通过 MQTT 协议与云端交互,实现从远程发送指令控制步进电机。
控制流程图

MQTT 远程控制代码
执行 touch stepmotor_mqtt.py 新建文件,写入以下内容:
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import Image, ImageFont
import os
import time
import datetime
import threading
import json
import RPi.GPIO as GPIO
import paho.mqtt.client as mqtt
# ====================== 全局变量 ======================
current_angle = 0
speed_ms = 0.2
direct_text = "STOP"
running_flag = True
mqtt_client = None
# ====================== OLED 初始化 ======================
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial)
font_main = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
# 加载帧画面
frame_list = []
for i in range(1, 21):
img = Image.open(os.path.join("frame", f"{i}.jpg")).convert("1")
frame_list.append(img)
frame_index = 0
def screen_task():
"""OLED后台刷新线程"""
global frame_index
while running_flag:
now = datetime.datetime.now()
date_str = now.strftime("%Y-%m-%d")
time_str = now.strftime("%H:%M:%S")
with canvas(device) as draw:
# 太空人动画
draw.bitmap((32, 0), frame_list[frame_index], fill=1)
# 日期时间
draw.text((2, 0), date_str, font=font_small, fill=1)
draw.text((2, 10), time_str, font=font_main, fill=1)
# 运行状态
draw.text((2, 24), f"Dir: {direct_text}", font=font_small, fill=1)
draw.text((2, 36), f"V: {speed_ms}ms", font=font_small, fill=1)
draw.text((2, 48), f"Ang: {current_angle}deg", font=font_small, fill=1)
frame_index = (frame_index + 1) % len(frame_list)
time.sleep(0.07)
# ====================== 电机配置 ======================
STEP = 17
DIR = 27
EN = 22
PULSE_PER_360DEG = 1600 # 8细分
PULSE_PER_DEG = PULSE_PER_360DEG / 360
GPIO.setmode(GPIO.BCM)
GPIO.setup(STEP, GPIO.OUT)
GPIO.setup(DIR, GPIO.OUT)
GPIO.setup(EN, GPIO.OUT)
GPIO.output(EN, GPIO.LOW)
def run_angle(degree, speed):
global direct_text, current_angle
pulses = int(abs(degree) * PULSE_PER_DEG)
current_angle = degree
if degree > 0:
GPIO.output(DIR, GPIO.HIGH)
direct_text = "CW"
else:
GPIO.output(DIR, GPIO.LOW)
direct_text = "CCW"
delay_s = speed / 1000.0
for _ in range(pulses):
GPIO.output(STEP, GPIO.HIGH)
time.sleep(delay_s)
GPIO.output(STEP, GPIO.LOW)
time.sleep(delay_s)
direct_text = "STOP"
# ====================== MQTT 回调 ======================
def on_connect(client, userdata, flags, rc):
print("MQTT已连接")
client.subscribe("ljl/stepmotor")
def on_message(client, userdata, msg):
global speed_ms
try:
payload = json.loads(msg.payload.decode())
angle = payload.get("angle", 0)
speed_ms = payload.get("speed", 0.2)
print(f"收到指令: 角度 {angle}, 速度 {speed_ms}ms")
# 执行旋转
run_angle(angle, speed_ms)
# 旋转完成回复OK
client.publish("ljl/stepmotor", '{"result":"OK"}')
except Exception as e:
print("解析错误:", e)
# ====================== 主程序 ======================
if __name__ == "__main__":
# 启动OLED线程
t_screen = threading.Thread(target=screen_task, daemon=True)
t_screen.start()
# MQTT客户端
mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.connect("iot.kittenbot.cn", 1883, 60)
try:
mqtt_client.loop_forever()
except KeyboardInterrupt:
running_flag = False
time.sleep(0.2)
device.clear()
GPIO.output(EN, GPIO.HIGH)
GPIO.cleanup()
远程操控效果
运行 python stepmotor_mqtt.py 启动程序后,OLED 显示完整的设备状态,终端同时打印 MQTT 连接、指令接收及电机运行日志。

在 MQTTX 客户端中新建连接,输入服务器地址 iot.kittenbot.cn,并订阅主题 ljl/stepmotor。
随后以 ljl/stepmotor 为 topic 发送 JSON 指令,例如 {"angle": -95, "speed": 0.2} 来控制电机旋转到目标角度。

电机接收到指令后立即响应,OLED 状态信息同步刷新。

LabVIEW 上位机实现自动化控制与数据采集
在工业测试与科研场景中,通常需要长时间连续控制步进电机并自动记录数据,手动发送指令的方式已无法满足需求。为此我们采用 LabVIEW 快速搭建上位机逻辑,通过 MQTT 协议定时下发命令,完成步进电机的远程自动化控制,并可轻松集成其他仪器、实现数据采集与本地存储。

前面板布局
前面板划分为 MQTT 配置区、单步测试区、连续运行测试区、实时数据演化曲线显示、数据保存设置以及程序启停控制等模块。

程序框图设计
程序框图采用模块化思想,将 MQTT 消息封装为独立功能,确保连续指令能够准确有序地发送。

上位机运行效果
在 LabVIEW 中配置好 MQTT 服务器地址,设定主题、步长、目标角度、延时以及数据存储路径后,点击“START”按钮即可启动自动运行与采集。
程序启动后,步进电机按预设步长和延时连续旋转。

前面板同步绘制出模拟采集数据的实时演化曲线。

数据自动存储
当循环采集流程结束后,MQTT 停止发送消息,软件会自动将数据保存至预设路径。

打开存储的数据文件可以看到,第一列为角度值,第二列为模拟采集到的对应数值。

总结
本文全面介绍了基于工业树莓派 CM0 NANO 与 TMC2209 驱动器实现 42 步进电机驱动的方案,通过 MQTT 协议实现远程控制,并利用 LabVIEW 开发上位机完成自动化连续运行与数据采集。内容覆盖了硬件接线、开发环境配置、OLED 状态显示、通信流程设计、核心工程代码以及最终运行效果,为工业物联网领域中类似产品的快速开发与应用部署提供了系统性的参考。
