Files
5dof/qt/qt_motor_main.py

305 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/08/04
# @Author : hx
# @File : qt_main.py
# 功能PyQt5 GUI + 五连杆轨迹动画 + 真实电机控制DM4310 + 轮趣切割)
# 修改:使用 qiege_motor.control() 统一控制切割电机
'''
import sys
import time
import numpy as np
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5.QtWidgets import (
QApplication, QDialog, QVBoxLayout, QMessageBox
)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtCore import Qt
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.animation import FuncAnimation
# 导入你的 UI 和算法模块
from untitled import Ui_Dialog
from calculate.ik import inverseF
from calculate.trajectory import circle_trajectory, line_trajectory
import matplotlib.pyplot as plt
# 设置中文字体和解决负号显示问题
plt.rcParams['font.sans-serif'] = ['SimHei', 'WenQuanYi Zen Hei', 'FangSong'] # 按优先级选择字体
plt.rcParams['axes.unicode_minus'] = False # 显示负号 -
# ------------------------ 调试与模式开关 ------------------------
DEBUG_MODE = True # <<< 设为 False 启用真实电机控制
USE_QIEGE_MOTOR = False # <<< 设为 True 才启用 qiege_motor.py 控制切割电机
# ------------------------ 机械臂参数 ------------------------
L1 = 250
L2 = 300
L3 = 300
L4 = 250
L0 = 250 # 右侧基座偏移
# ------------------------ 电机配置 ------------------------
CAN_PORT = '/dev/ttyACM0'
CAN_BAUD = 921600
DM_MOTOR1_ID = 0x01 # 左臂 DM4310
DM_MOTOR2_ID = 0x04 # 右臂 DM4310
KP = 5.0
KD = 1.0
# ------------------------ 切割参数 ------------------------
CUTTING_MIN_Y = 280 # Y > 此值才启动切割
CUT_SPEED_RAD = 10.0 # 切割转速 (rad/s),用于 qiege_motor 内部映射
# ------------------------ 全局变量 ------------------------
motor1 = motor2 = motor_control = None
current_cutting_state = False # 避免重复启停切割电机
# -------------------- 导入硬件模块(仅非 DEBUG 模式) --------------------
if not DEBUG_MODE:
try:
from DM_CAN.DM_CAN import Motor, MotorControl, DM_Motor_Type, Control_Type
import serial
print("【硬件模块导入成功】: DM4310 电机")
except Exception as e:
print(f"【硬件模块导入失败】: {e}")
sys.exit(1)
else:
print("【DEBUG MODE】: 不连接真实电机")
# -------------------- 导入切割电机模块(仅当启用时) --------------------
if USE_QIEGE_MOTOR and not DEBUG_MODE:
try:
from qiege_motor import control as cutting_control
print("【切割电机模块导入成功】: qiege_motor.control")
except Exception as e:
print(f"【qiege_motor 导入失败】: {e}")
print("【警告】将使用模拟模式")
USE_QIEGE_MOTOR = False
else:
def cutting_control(enable: bool):
"""模拟切割电机控制"""
state = "启动" if enable else "停止"
if DEBUG_MODE:
print(f"[DEBUG] 切割电机: {state}")
else:
if USE_QIEGE_MOTOR:
print(f"[WARN] qiege_motor 未启用或导入失败control({enable}) 被忽略")
class MyDialog(QDialog, Ui_Dialog):
def __init__(self):
super().__init__()
self.setupUi(self)
# 初始化 listView 模型
self.angle_model = QStandardItemModel(self.listView)
self.listView.setModel(self.angle_model)
# Matplotlib 图形初始化
self.figure = Figure(figsize=(8, 6), dpi=100)
self.ax = self.figure.add_subplot(111)
self.ax.set_xlim(-300, 500)
self.ax.set_ylim(0, 500)
self.ax.set_aspect('equal')
self.ax.grid(True, linestyle='--', alpha=0.6)
self.ax.set_title("五连杆机械臂运动控制")
# 画布
self.canvas = FigureCanvas(self.figure)
self.widget.setLayout(QVBoxLayout())
self.widget.layout().addWidget(self.canvas)
# 机械臂线
self.line, = self.ax.plot([], [], 'r-o', linewidth=2, markersize=6)
# 轨迹数据
self.x_list = []
self.y_list = []
self.ani = None
# 初始化电机(仅非 DEBUG
self.init_hardware()
# 连接按钮
self.pushButton.clicked.connect(self.start_line_trajectory)
self.pushButton_2.clicked.connect(self.start_circle_trajectory)
def init_hardware(self):
global motor1, motor2, motor_control
if DEBUG_MODE:
print("【DEBUG】跳过 DM4310 电机初始化")
return
try:
ser = serial.Serial(CAN_PORT, CAN_BAUD, timeout=0.5)
motor_control = MotorControl(ser)
motor1 = Motor(DM_Motor_Type.DM4310, DM_MOTOR1_ID, 0x11)
motor2 = Motor(DM_Motor_Type.DM4310, DM_MOTOR2_ID, 0x14)
motor_control.addMotor(motor1)
motor_control.addMotor(motor2)
motor_control.switchControlMode(motor1, Control_Type.MIT)
motor_control.switchControlMode(motor2, Control_Type.MIT)
time.sleep(0.1)
motor_control.enable(motor1)
motor_control.enable(motor2)
print("✅ DM4310 电机已使能")
except Exception as e:
QMessageBox.critical(self, "电机初始化失败", str(e))
print(f"❌ 电机初始化失败: {e}")
def control_cutting_motor(self, enable=True):
"""
控制切割电机
- 仅在状态变化时调用
- 使用 qiege_motor.control() 接口
"""
global current_cutting_state
if enable == current_cutting_state:
return # 状态未变,不重复操作
try:
cutting_control(enable)
print(f"✅ 切割电机: {'启动' if enable else '停止'}")
except Exception as e:
print(f"❌ 调用切割电机 control({enable}) 失败: {e}")
current_cutting_state = enable
def control_dm_motors(self, theta1, theta4):
"""控制两个 DM4310 电机"""
pos1 = np.degrees(theta1)
pos4 = np.degrees(theta4)
vel = 0.8 # 可根据需要调整
if DEBUG_MODE:
print(f"[DEBUG] DM4310 -> M1:{pos1:.1f}°, M2:{pos4:.1f}°")
else:
try:
motor_control.controlMIT(motor1, KP, KD, pos1, vel, 0.0)
motor_control.controlMIT(motor2, KP, KD, pos4, vel, 0.0)
except Exception as e:
print(f"[DM电机] 控制失败: {e}")
def start_line_trajectory(self):
self.angle_model.clear()
self.ax.set_title("直线轨迹运动 + 电机控制")
self.x_list, self.y_list = line_trajectory(start=(125, 300), end=(125, 400))
self.start_animation()
def start_circle_trajectory(self):
self.angle_model.clear()
self.ax.set_title("圆形轨迹运动 + 电机控制")
self.x_list, self.y_list = circle_trajectory(center=(100, 200), radius=40)
self.start_animation()
def start_animation(self):
# 安全停止旧动画
try:
if self.ani is not None and self.ani.event_source is not None:
self.ani.event_source.stop()
except:
pass
# ✅ 开始前:重置切割状态
global current_cutting_state
current_cutting_state = False
def animate_frame(i):
x = self.x_list[i]
y = self.y_list[i]
try:
theta1, theta4 = inverseF(x, y, L1, L2, L3, L4, L0)
# 显示角度(角度制)
theta1_deg = np.degrees(theta1)
theta4_deg = np.degrees(theta4)
item_text = f"{i}: θ1={theta1_deg:.1f}°, θ4={theta4_deg:.1f}°"
if self.angle_model.rowCount() >= 10:
self.angle_model.removeRow(0)
self.angle_model.appendRow(QStandardItem(item_text))
# 计算五连杆坐标
x2 = L1 * np.cos(theta1)
y2 = L1 * np.sin(theta1)
x4 = L0 + L4 * np.cos(theta4)
y4 = L4 * np.sin(theta4)
x_coords = [0, x2, x, x4, L0]
y_coords = [0, y2, y, y4, 0]
self.line.set_data(x_coords, y_coords)
# --- 控制真实电机 ---
self.control_dm_motors(theta1, theta4)
# --- 切割控制(进入区域才切)---
in_cut_zone = y > CUTTING_MIN_Y
self.control_cutting_motor(enable=in_cut_zone)
except Exception as e:
print(f"{i} 帧错误: {e}")
return self.line,
# 创建动画
self.ani = FuncAnimation(
self.figure,
animate_frame,
frames=len(self.x_list),
interval=100,
repeat=False,
blit=False
)
self.canvas.draw()
# ✅ 动画结束后自动停止所有电机
from PyQt5.QtCore import QTimer
timer = QTimer(self)
timer.setSingleShot(True)
timer.timeout.connect(self.on_animation_finish)
timer.start(int(len(self.x_list) * 100 + 500)) # 动画总时长 + 0.5s 延迟
def on_animation_finish(self):
"""动画结束后安全关闭所有电机"""
if not DEBUG_MODE:
try:
if 'motor_control' in globals() and motor_control:
motor_control.disable(motor1)
motor_control.disable(motor2)
print("✅ DM4310 电机已停用")
except Exception as e:
print(f"❌ DM 电机停用失败: {e}")
self.control_cutting_motor(False)
print("✅ 所有电机已安全关闭")
def closeEvent(self, event):
"""窗口关闭时确保电机停止"""
self.control_cutting_motor(False)
if not DEBUG_MODE and 'motor_control' in globals():
try:
if motor_control:
motor_control.disable(motor1)
motor_control.disable(motor2)
print("【退出】所有电机已停用")
except Exception as e:
print(f"【退出】电机停用异常: {e}")
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = MyDialog()
dialog.show()
sys.exit(app.exec_())