前文《使用PyCharm与RS485串口控制步进电机正反转动》使用PyCharm运行Python程序,通过交互式输入加减速、转速、时间等参数,控制步进电机正反转。本例在前文基础上,对多台步进电机实现同步转动、先后顺序转动,均可正反转动。
借用前文的图。系统硬件组成包括:AC电源(220V)、计算机、步进电机(42H247-1684A)、USB转RS485串口、AC转DC电源、驱动器。每个硬件的接口、编号及其之间的连线如下ibd图所示。
上图中仅有1台驱动器、1台步进电机,定义为驱动器1、步进电机1。增加驱动器2、步进电机2,驱动器1的COM2与驱动器1的COM1使用网线连接,驱动器2与步进电机2连接及电源的连接与驱动器1形式相同。
Python程序定义了3个类、1个主方法。
1)StepperMotor()类用于创建1个步进电机对象并对其进行设置和运行控制。
2)MultiMotorController()类用于创建管理多个步进电机。
3)initSetting()类用于对交互式输出、读取各设置项。
设置端口、波特率、设备序号,输出设备ID及名称,确认是否对设备重命名。
输入各设备的名称、加速度、减速度、转速、运行时间。对电机使能、并进行参数设置。
设置运动模式。同步,即设备同时开始运行,到各设备运行时间后自动停止;顺序,即按照输入设备ID顺序,各设备依次启动、停止。下图为选择同步运动模式。
运行结束后,提示是否继续。若继续,则开始新一轮设置。
下图继续,设置端口号、波特率、设备ID,使用默认设备名称。
设置电机加速度、减速度、转速、时间。
选择设备顺序运动模式,按照设备2、1的顺序运行。结束后不再继续,退出程序。
# -*- coding: utf-8 -*-
"""
多电机控制
"""
import time, keyboard
import logging
from pymodbus.client import ModbusSerialClient
from pymodbus import FramerType
# 0--关闭 pymodbus 的详细日志(可选)
logging.basicConfig() # 初始化日志系统,使用默认配置
log = logging.getLogger() # 获取根日志记录器(所有日志的顶层记录器)
log.setLevel(logging.WARNING) #设置日志级别为WARNING,只显示警告及以上级别的日志
class StepperMotor:
"""步进电机控制类"""
# 1--寄存器地址常量
ADDR_ALARM = 0x0000 # 报警代码
ADDR_STATUS = 0x0001 # 状态寄存器
ADDR_JA = 0x002E # 加速度 JA (40047)
ADDR_JL = 0x002F # 减速度 JL (40048)
ADDR_JS = 0x0030 # 速度 JS (40049)
ADDR_COMMAND = 0x007C # 命令操作码 (40125)
ADDR_ENABLE = 0x008B # 使能 ME (40140)
# 2--命令操作码寄存器(40125)数据
CMD_START = 0x0096 # 启动点动 CJ
CMD_STOP = 0x00D8 # 停止点动 SJ
def __init__(self, client, slave_id, name):
"""
初始化电机
:param client: Modbus 客户端
:param slave_id: 从站地址 (1-247)
:param name: 电机名称
"""
self.client = client
self.slave_id = slave_id
self.name = name
self.is_enabled = False
def enable(self):
"""使能电机"""
result = self.client.write_register(
address=self.ADDR_ENABLE,
value=0x0001,
device_id=self.slave_id)
if not hasattr(result, 'isError') or not result.isError():
self.is_enabled = True
print(f"✓ [{self.name}] 电机已使能 (地址: {self.slave_id})")
else:
print(f"✗ [{self.name}] 使能失败")
time.sleep(0.3)
def disable(self):
"""失能电机"""
self.client.write_register(
address=self.ADDR_ENABLE,
value=0x0000,
device_id=self.slave_id)
self.is_enabled = False
print(f"✓ [{self.name}] 电机已失能")
def set_acceleration(self, value=100):
"""
设置加速度
:param value: 加速度值 (Rps/sec),范围 1-500
寄存器值 = value × 6
"""
reg_value = int(value * 6)
self.client.write_register(
address=self.ADDR_JA,
value=reg_value,
device_id=self.slave_id)
print(f" [{self.name}] 加速度: {value} Rps/sec")
def set_deceleration(self, value=100):
"""
设置减速度
:param value: 减速度值 (Rps/sec),范围 1-500
寄存器值 = value × 6
"""
reg_value = int(value * 6)
self.client.write_register(
address=self.ADDR_JL,
value=reg_value,
device_id=self.slave_id)
print(f" [{self.name}] 减速度: {value} Rps/sec")
def set_speed(self, value=10):
"""
设置速度
:param value: 速度值 (Rps),范围 0.1-50
寄存器值 = value × 240
"""
reg_value = int(value * 240) & 0xFFFF # 补码,将有符号整数转为无符号16位整数 (0~65535)
self.client.write_register(
address=self.ADDR_JS,
value=reg_value,
device_id=self.slave_id)
direction = "正转" if value > 0 else "反转" if value < 0 else "停止"
print(f" [{self.name}] 速度: {value} Rps {direction}")
def start(self):
"""启动电机转动"""
self.client.write_register(
address=self.ADDR_COMMAND,
value=self.CMD_START,
device_id=self.slave_id)
print(f"▶ [{self.name}] 启动转动")
def stop(self):
"""停止电机转动"""
self.client.write_register(
address=self.ADDR_COMMAND,
value=self.CMD_STOP,
device_id=self.slave_id)
print(f"■ [{self.name}] 停止转动")
def read_status(self):
"""读取状态寄存器"""
result = self.client.read_holding_registers(
address=self.ADDR_STATUS,
count=1,
device_id=self.slave_id)
if hasattr(result, 'registers'):
status = result.registers[0]
return {
'enabled': bool(status & 0x0001),
'moving': bool(status & 0x0002),
'alarm': bool(status & 0x0004),
'raw': status}
return None
def read_alarm(self):
"""读取报警代码"""
result = self.client.read_holding_registers(
address=self.ADDR_ALARM,
count=1,
device_id=self.slave_id)
if hasattr(result, 'registers'):
return result.registers[0]
return None
def jog(self, speed_rps=10, duration=2):
"""
点动控制(启动 -> 运行 -> 自动停止)
:param speed_rps: 速度 (Rps)
:param duration: 运行时间 (秒)
"""
self.set_speed(speed_rps)
self.start()
time.sleep(duration)
self.stop()
print(f" [{self.name}] 完成 {duration} 秒运动")
class MultiMotorController():
"""多电机控制器"""
def __init__(self, port, baudrate):
"""
初始化控制器
:param port: 串口号
:param baudrate: 波特率
"""
self.port = port
self.client = ModbusSerialClient(
port=port,
framer=FramerType.RTU,
baudrate=baudrate,
timeout=1,
stopbits=1,
bytesize=8,
parity='N')
self.motors = {}
self._connected = False
def connect(self):
"""连接串口"""
if self.client.connect():
self._connected = True
print(f"✓ 串口 {self.port} 连接成功") # 使用保存的端口号
return True
else:
print(f"✗ 串口 {self.port} 连接失败")
return False
def close(self):
"""关闭串口"""
self.client.close()
print("串口已关闭")
def add_motor(self, slave_id, name):
"""
添加电机
:param slave_id: 从站地址
:param name: 电机名称
"""
motor = StepperMotor(self.client, slave_id, name)
self.motors[slave_id] = motor
print(f"添加电机: {name} (地址: {slave_id})")
return motor
class initSetting:
def __init__(self):
self.motorsIdName = {} # 电机ID、Name字典:{Id:Name,...}
def clientSetting(self):
"""输入端口、波特率"""
port = input("\n请输入端口号(与所用计算机端口号一致,例如COM5):\n").strip() # 输入端口号供后续使用
baudrate = int(input("\n请输入波特率(与驱动器设置一致,例如115200):\n").strip()) # 输入端口号供后续使用
return port, baudrate
def exitSetting(self):
"""是否退出"""
ck = input("yes/no:\n").strip() # yes,退出;no,继续
if ck == "yes":
return True
else:
return False
def inputSlaveIDs(self):
"""输入所用设备ID,例如: 1,2,3"""
slv_ids = input("\n请输入设备ID(与驱动器设置一致,例如1,2,5):\n").strip().split(',') # 输入所有电机ID
slave_ids = [int(i) for i in slv_ids] #设备ID转为整型
for ids in slave_ids:
self.motorsIdName[ids] = 'Motor_' + str(ids)
def inputSlaveName(self):
"""输入所用设备ID对应的名称,默认Motor_1,..."""
# 1--输出设备ID、Name
for slave_id, name in self.motorsIdName.items():
print(f"设备ID:{slave_id},设备名称:{name}")
# 2--设备名称选项
print("是否采用默认设备名称?")
ck = self.exitSetting()
if ck:
return
# 3--输入设备名称
for slave_id in self.motorsIdName.keys():
slave_Name = input(f"\n请输入设备{slave_id}的名称:\n").strip() # 输入设备id的名称
self.motorsIdName[slave_id] = slave_Name
def inputAccDecSpeedTime(self):
"""设置各设备的加速度(Rps/sec)、减速度(Rps/sec)、转速(Rps)、时间(s)"""
setVals = {} #字典:{id:[JA,JL,JS,t],...}
print('请为以下设备设置加速度(Rps/sec)、减速度(Rps/sec)、转速(Rps)、时间(s):')
# 1--输出设备ID、Name
for slave_id, name in self.motorsIdName.items():
print(f"设备ID:{slave_id},设备名称:{name}")
# 2--输入设备ID、Name
for slave_id in self.motorsIdName.keys():
setVal = input(f"请输入设备{slave_id}的加速度(Rps/sec),减速度(Rps/sec),转速(Rps),时间(s):\n").strip().split(',')
setVals[slave_id] = [float(v) for v in setVal] #字符串转换为整型
return setVals
def inputSequence(self):
"""输入设备转动顺序"""
print('请设置设备运动模式:\n'
'1 同步\n'
'2 顺序\n')
while True:
try:
vMod=input("请输入选项:").strip()
except KeyboardInterrupt:
break
if vMod == "1":
return 0
else:
vSeq = input("\n请按照设备运行顺序输入设备ID:").strip().split(',')
vSeq=[int(v) for v in vSeq] #字符串转整型
return vSeq
# ============================================
# 应用主程序main()
# ============================================
def main():
# 1--输入端口,波特率,直至连接成功
inputSetting = initSetting()
while True:
port, baudrate = inputSetting.clientSetting() # 输入端口、波特率
xMotorController = MultiMotorController(port, baudrate) # 创建多电机控制器
xMotorController.connect() # 连接客户端
if xMotorController._connected:
break
else:
print('是否退出?')
ck = inputSetting.exitSetting() # 是否退出
if ck:
break
# 2--输入电机ID、Name,添加电机
inputSetting.inputSlaveIDs() # 输入所有电机ID
inputSetting.inputSlaveName() # 输入所有电机Name
for slave_id, name in inputSetting.motorsIdName.items():
xMotorController.add_motor(slave_id, name)
# 3--设置加速度、减速度、转速
jogVals = inputSetting.inputAccDecSpeedTime()
list_IdTime=[]# [(id, time),]各设备ID及运行时间
for slave_id, jogVal in jogVals.items(): #设置每台设备参数
xMotorController.motors[slave_id].enable() #电机使能
xMotorController.motors[slave_id].set_acceleration(jogVal[0]) #设置加速度
xMotorController.motors[slave_id].set_deceleration(jogVal[1]) #设置减速度
xMotorController.motors[slave_id].set_speed(jogVal[2]) #设置速度
list_IdTime.append((slave_id, jogVal[3])) #设备(ID,Time)加入列表
vals_IdTime=sorted(list_IdTime, key=lambda x: x[1])
# 4--设置电机转动顺序
vMod = inputSetting.inputSequence() # 0:同步,[]:设备id列表顺序
if vMod == 0: #同步
t=0 #计时
for vIdTm in vals_IdTime:
slave_id=vIdTm[0] # 设备ID
xMotorController.motors[slave_id].start()
for vIdTm in vals_IdTime:
slave_id=vIdTm[0] # 设备ID
vTime=vIdTm[1] # 设备运行时间
t=vTime-t # 运行时间差
time.sleep(t)
xMotorController.motors[slave_id].stop()#设备停止
xMotorController.motors[slave_id].disable() #设备失能
else:
for vIdTm in vals_IdTime:
slave_id=vIdTm[0]
vTime=vIdTm[1]
xMotorController.motors[slave_id].start()
time.sleep(vTime)
xMotorController.motors[slave_id].stop() # 设备停止
xMotorController.motors[slave_id].disable() # 设备失能
xMotorController.close()#关闭客户端
if __name__ == '__main__':
while True:
main()
ck=input('是否继续yes/no:').strip()
if ck == "no":
break