From 91b1b394bb9bc448f31c8d537e4a76fbe1f25bc3 Mon Sep 17 00:00:00 2001 From: pengqi Date: Tue, 24 Mar 2026 16:52:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=9B=B4=E6=94=B9+=E6=8C=A1?= =?UTF-8?q?=E6=9D=BF=E7=94=B5=E6=9C=BA=E6=B7=BB=E5=8A=A0=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E5=92=8C=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 4 + .idea/wire_controlsystem.iml | 2 +- .vscode/settings.json | 4 + EMV/EMV_test.py | 5 + RK1106.zip | Bin 0 -> 9638 bytes RK1106/RK1106_server.py | 363 +++++------ RK1106/RK1106_server_test.py | 253 ++++++++ RK1106/image.png | Bin 0 -> 57891 bytes RK1106/motor_config.json | 4 + RK1106/readme.md | 5 +- RK1106/stepper_motor.py | 75 ++- .../__pycache__/error_code.cpython-39.pyc | Bin 0 -> 2380 bytes .../__pycache__/modbus.cpython-39.pyc | Bin 0 -> 15636 bytes conveyor_controller/conveyor_motor.py | 578 +++++++++++++++++- conveyor_controller/modbus.py | 27 +- img.png | Bin 0 -> 61780 bytes readme.md | 29 +- servo/servo_control.py | 17 +- servo/servo_test.py | 11 +- 19 files changed, 1125 insertions(+), 252 deletions(-) create mode 100644 .idea/misc.xml create mode 100644 .vscode/settings.json create mode 100644 RK1106.zip create mode 100644 RK1106/RK1106_server_test.py create mode 100644 RK1106/image.png create mode 100644 RK1106/motor_config.json create mode 100644 conveyor_controller/__pycache__/error_code.cpython-39.pyc create mode 100644 conveyor_controller/__pycache__/modbus.cpython-39.pyc create mode 100644 img.png diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..19dc141 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/wire_controlsystem.iml b/.idea/wire_controlsystem.iml index 4a631ed..428c7b1 100644 --- a/.idea/wire_controlsystem.iml +++ b/.idea/wire_controlsystem.iml @@ -2,7 +2,7 @@ - + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4b5a294 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:conda", + "python-envs.defaultPackageManager": "ms-python.python:conda" +} \ No newline at end of file diff --git a/EMV/EMV_test.py b/EMV/EMV_test.py index 0689d90..fdb58e4 100644 --- a/EMV/EMV_test.py +++ b/EMV/EMV_test.py @@ -110,6 +110,7 @@ sensor_name_map = { CONVEYOR2_SENSOR: '传送带2开关' } + class RelayController: def __init__(self): """初始化继电器控制器""" @@ -253,6 +254,7 @@ class RelayController: # private _GLOBAL_RELAY = RelayController() + def ng_push(): """NG推料流程""" try: @@ -272,6 +274,7 @@ def ng_push(): print(f"NG推料失败:{e}") raise RuntimeError("NG推料流程异常") from e + def write_do(device_name: str, state: bool): """ 控制单个数字输出设备(DO)的开关状态 @@ -297,6 +300,7 @@ def write_do(device_name: str, state: bool): except Exception as e: raise RuntimeError(f"控制设备 '{device_name}' 失败: {e}") + def read_all_io() -> dict[str, dict[str, bool]]: """ 读取所有DI(传感器)和DO(设备)状态 @@ -314,6 +318,7 @@ def read_all_io() -> dict[str, dict[str, bool]]: print(f"读取IO状态失败:{e}") raise RuntimeError("读取IO失败") from e + # ------------测试接口------------- if __name__ == '__main__': diff --git a/RK1106.zip b/RK1106.zip new file mode 100644 index 0000000000000000000000000000000000000000..fb1d78c58bed81806c87e53fd5ce38e0d47c246a GIT binary patch literal 9638 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 literal 0 HcmV?d00001 diff --git a/RK1106/RK1106_server.py b/RK1106/RK1106_server.py index 6466deb..1d15e5a 100644 --- a/RK1106/RK1106_server.py +++ b/RK1106/RK1106_server.py @@ -1,210 +1,217 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ''' -# @Time : 2026/1/9 10:45 +# @Time : 2026/3/16 15:00 # @Author : reenrr # @File : RK1106_server.py -# @Desc : RK1106服务端,等待工控机调用 通信为JSON格式 +# @Desc : RK1106服务端(类形式,多线程),等待工控机调用 通信为JSON格式 ''' import socket import logging -import sys import json +import threading from stepper_motor import motor_start, align_wire, cleanup, motor_stop -# ------------日志配置(终端+文件双输出)-------------- -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - # 核心新增:日志文件配置 - handlers=[ - # 1. 文件处理器:保存到.log文件 - logging.FileHandler( - "RK1106_server.log", # Buildroot推荐路径,临时测试可改/tmp/1106_server.log - mode='a', # 追加模式(不会覆盖历史日志) - encoding='utf-8' # 防止中文乱码(必加) - ), - # 2. 终端处理器:输出到控制台 - logging.StreamHandler(sys.stdout) - ] -) - # --------配置TCP服务端---------- -HOST = "192.168.0.100" +HOST = "192.168.5.100" PORT = 8888 +CONFIG_FILE = "motor_config.json" -# 全局参数缓存 -MOTOR_CONFIG = { - "speed": 2500, # 默认速度 - "cycle": 10.0, # 默认旋转圈数 -} -def handle_setting(para_type: str, para_value: str) ->dict: +class RK1106Server: """ - 处理客户端发送的参数设置指令(cmd:"setting"),更新全局电机配置 - :param para_type: 要设置的参数类型,仅支持"speed"或"cycle" - :param para_value: 参数值字符串,将尝试转换为int(speed)或float(cycle) - :return: 标准化相应字典dict,如: - { - "Result": "1"表示成功,"0"表示失败, - "ErrMsg": str # 成功提示或错误详情 - } + RK1106服务端类 + 在初始化时加载配置,提供配置管理和指令处理功能 """ - try: - if para_type == "speed": - MOTOR_CONFIG["speed"] = int(para_value) - elif para_type == "cycle": - MOTOR_CONFIG["cycle"] = float(para_value) - else: - return {"Result": "0", "ErrMsg": f"不支持的参数类型: {para_type}"} - return {"Result": "1", "ErrMsg": "设置成功"} - except ValueError as e: - return {"Result": "0", "ErrMsg": f"参数值格式错误: {str(e)}"} + + def __init__(self): + """初始化服务端,加载配置""" + self.config = self._load_config() + logging.info(f"服务初始化完成,加载配置:{self.config}") + + def _load_config(self): + """ + 从JSON文件加载电机配置 + :return: 加载的配置字典 + """ + try: + with open(CONFIG_FILE, "r") as f: + return json.load(f) + except FileNotFoundError: + logging.warning(f"配置文件 {CONFIG_FILE} 未找到,将使用默认配置") + return {"speed": 2500, "cycle": 10.0} + except json.JSONDecodeError: + logging.error(f"配置文件 {CONFIG_FILE} 格式错误") + return {"speed": 2500, "cycle": 10.0} + + def _save_config(self): + """ + 将电机配置保存到JSON文件 + """ + try: + with open(CONFIG_FILE, "w") as f: + json.dump(self.config, f, indent=4, ensure_ascii=False) + logging.info(f"配置已保存:{self.config}") + except IOError as e: + logging.error(f"配置文件 {CONFIG_FILE} 保存失败: {str(e)}") + + def handle_setting(self, para_type: str, para_value: str) -> dict: + """ + 处理客户端发送的参数设置指令(cmd:"setting"),更新电机配置 + :param para_type: 要设置的参数类型,仅支持"speed"或"cycle" + :param para_value: 参数值字符串,将尝试转换为int(speed)或float(cycle) + :return: 标准化相应字典dict + """ + try: + if para_type == "speed": + self.config["speed"] = int(para_value) + elif para_type == "cycle": + self.config["cycle"] = float(para_value) + else: + return {"Result": "0", "ErrMsg": f"不支持的参数类型: {para_type}"} + + # 保存到文件 + self._save_config() + return {"Result": "1", "ErrMsg": "设置成功"} + except ValueError as e: + return {"Result": "0", "ErrMsg": f"参数值格式错误: {str(e)}"} + + def handle_start(self, para_type: str, para_value: str) -> dict: + """ + 处理启动电机指令(cmd: "start"),使用当前配置运行电机 + :param para_type:为"direction"时,使用"para_value"作为临时方向 + :param para_value:为0或1 + :return: 标准化相应字典dict + """ + try: + if para_type == "direction": + direction = int(para_value) + if direction not in (0, 1): + return {"Result": "0", "ErrMsg": "方向必须为0或1"} -def handle_start(para_type: str, para_value: str) -> dict: - """ - 处理启动电机指令(cmd: "start"),使用当前MOTOR_CONFIG配置运行电机 - :param para_type:为"direction"时,使用"para_value"作为临时方向 - :param para_value:为0或1 - :return: 标准化相应字典dict,如: - { - "Result": "1"表示成功,"0"表示失败, - "ErrMsg": str #执行结果或异常信息 - } - """ - try: - if para_type == "direction": - direction = int(para_value) - if direction not in (0,1): - return {"Result": "0", "ErrMsg": "方向必须为0或1"} - - motor_start(speed=MOTOR_CONFIG["speed"], - cycle=MOTOR_CONFIG["cycle"], + motor_start(speed=self.config["speed"], + cycle=self.config["cycle"], direction=direction) - dir_str = "正向" if direction == 1 else "负向" - return {"Result": "1", "ErrMsg": f"电机启动成功({dir_str})"} + + dir_str = "正向" if direction == 1 else "负向" + return {"Result": "1", "ErrMsg": f"电机启动指令已发送({dir_str})"} + else: + return {"Result": "0", "ErrMsg": "start 指令仅支持'direction'"} + except Exception as e: + return {"Result": "0", "ErrMsg": f"电机启动失败:{str(e)}"} + + def handle_stop(self) -> dict: + """ + 处理停止电机指令(cmd: "stop") + :return: 标准化相应字典dict + """ + try: + motor_stop() + return {"Result": "1", "ErrMsg": "电机停止指令已发送"} + except Exception as e: + return {"Result": "0", "ErrMsg": f"电机停止失败:{str(e)}"} + + def handle_align(self) -> dict: + """ + 处理线条对齐(挡板一来一回) + :return: dict + """ + try: + align_wire(self.config['speed'], self.config['cycle']) + return {"Result": "1", "ErrMsg": "线条对齐指令已发送"} + except Exception as e: + return {"Result": "0", "ErrMsg": "线条对齐失败"} + + def parse_json_command(self, data: str) -> dict: + """ + 解析客户端发送的原始JSON字符串指令,并分发至对应处理函数 + :param data: 客户端发送的原始JSON字符串 + :return dict:标准化响应字典 + """ + try: + cmd_obj = json.loads(data.strip()) + except json.JSONDecodeError as e: + return {"Result": "0", "ErrMsg": f"JSON 格式错误: {str(e)}"} + + cmd = cmd_obj.get("cmd", "").strip() + para_type = cmd_obj.get("para_type", "").strip() + para_value = cmd_obj.get("para_value", "").strip() + + if cmd == "setting": + return self.handle_setting(para_type, para_value) + elif cmd == "start": + return self.handle_start(para_type, para_value) + elif cmd == "stop": + if para_type == "none" and para_value == "none": + return self.handle_stop() + else: + return {"Result": "0", "ErrMsg": "stop指令参数必须为none"} + elif cmd == "alignment": + if para_type == "none" and para_value == "none": + return self.handle_align() + else: + return {"Result": "0", "ErrMsg": "alignment指令参数必须为none"} else: - return {"Result": "0", "ErrMsg": "start 指令仅支持'direction'"} - except Exception as e: - return {"Result": "0", "ErrMsg": f"电机启动失败:{str(e)}"} + return {"Result": "0", "ErrMsg": f"未知指令:{cmd}"} + + def run_server(self): + """ + 启动TCP服务端,监听指定端口,接收工控机连接并循环处理JSON指令 + """ + # 创建TCP socket + server_socket = None + conn = None -def handle_stop() -> dict: - """ - 处理停止电机指令(cmd: "stop") - :return: 标准化相应字典dict,如: - { - "Result": "1"表示成功,"0"表示失败, - "ErrMsg": str #执行结果或异常信息 - } - """ - try: - motor_stop() - return {"Result": "1", "ErrMsg": "电机已停止"} - except Exception as e: - return {"Result": "0", "ErrMsg": f"电机停止失败:{str(e)}"} + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind((HOST, PORT)) + server_socket.listen(1) # 只允许1个工控机连接 + logging.info(f"[1106] 服务已启动,监听端口:{PORT},等待工控机连接...") -def handle_align() -> dict: - """ - 处理线条对齐(挡板一来一回) - :return: dict - """ - try: - align_wire(MOTOR_CONFIG['speed'], MOTOR_CONFIG['cycle']) - return {"Result": "1", "ErrMsg": "处理线条对齐"} - except Exception as e: - return {"Result": "0", "ErrMsg": "线条对齐失败"} + while True: # 持续接受新连接 + try: + # 等待工控机连接 + conn, addr = server_socket.accept() + logging.info(f"[1106] 工控机已连接:{addr}") -def parse_json_command(data: str) -> dict: - """ - 解析客户端发送的原始JSON字符串指令,并分发至对应处理函数 - :param data: 客户端发送的原始JSON字符串 - :return dict:标准化响应字典 - """ - try: - cmd_obj = json.loads(data.strip()) - except json.JSONDecodeError as e: - return {"Result": "0", "ErrMsg": f"JSON 格式错误: {str(e)}"} + # 循环接收指令 + while True: + # 接收指令(最大1024字节) + data = conn.recv(1024).decode() + if not data: + logging.warning("客户端断开连接") + break - cmd = cmd_obj.get("cmd", "").strip() - para_type = cmd_obj.get("para_type", "").strip() - para_value = cmd_obj.get("para_value", "").strip() + logging.info(f"\n[1106] 收到工控机指令:{data}") - if cmd == "setting": - return handle_setting(para_type, para_value) - elif cmd == "start": - return handle_start(para_type, para_value) - elif cmd == "stop": - if para_type == "none" and para_value == "none": - return handle_stop() - else: - return {"Result": "0", "ErrMsg": "stop指令参数必须为none"} - elif cmd == "alignment": - if para_type == "none" and para_value == "none": - return handle_align() - else: - return {"Result": "0", "ErrMsg": "alignment指令参数必须为none"} - else: - return {"Result": "0", "ErrMsg": f"未知指令:{cmd}"} + # 解析指令 + response_dict = self.parse_json_command(data) + response_json = json.dumps(response_dict, ensure_ascii=False) + "\n" # 看需不需要加换行符\n + # 发送响应给工控机 + conn.sendall(response_json.encode("utf-8")) + logging.info(f"[1106] 已发送响应:{response_json}") + except ConnectionError: + logging.info("客户端异常断开") + except Exception as e: + logging.info(f"处理连接时发生错误: {e}") + finally: + if conn is not None: + conn.close() + conn = None # 重置,避免重复关闭 + logging.info("客户端连接已关闭,等待新连接...") -# ----------对外接口---------- -def server(): - """启动TCP服务端,监听指定端口,接收工控机连接并循环处理JSON指令""" - # 创建TCP socket - server_socket = None - conn = None - - try: - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind((HOST, PORT)) - server_socket.listen(1) # 只允许1个工控机连接 - logging.info(f"[1106] 服务已启动,监听端口:{PORT},等待工控机连接...") - - while True: # 持续接受新连接 - try: - # 等待工控机连接 - conn, addr = server_socket.accept() - logging.info(f"[1106] 工控机已连接:{addr}") - - # 循环接收指令 - while True: - # 接收指令(最大1024字节) - data = conn.recv(1024).decode() - if not data: - logging.warning("客户端断开连接") - break - - logging.info(f"\n[1106] 收到工控机指令:{data}") - - # 解析指令 - response_dict = parse_json_command(data) - response_json = json.dumps(response_dict, ensure_ascii=False) + "\n" # 看需不需要加换行符\n - - # 发送响应给工控机 - conn.sendall(response_json.encode("utf-8")) - logging.info(f"[1106] 已发送响应:{response_json}") - - except ConnectionError: - logging.info("客户端异常断开") - except Exception as e: - logging.info(f"处理连接时发生错误: {e}") - finally: - if conn is not None: - conn.close() - conn = None # 重置,避免重复关闭 - logging.info("客户端连接已关闭,等待新连接...") - - except KeyboardInterrupt: - logging.info("\n收到 Ctrl+C,正在关闭服务...") - finally: - if server_socket: - server_socket.close() - logging.info("服务已停止,监听 socket 已释放") + except KeyboardInterrupt: + logging.info("\n收到 Ctrl+C,正在关闭服务...") + finally: + if server_socket: + server_socket.close() + logging.info("服务已停止,监听 socket 已释放") # ----------测试接口---------- if __name__ == "__main__": - server() - + SERVER = RK1106Server() + SERVER.run_server() diff --git a/RK1106/RK1106_server_test.py b/RK1106/RK1106_server_test.py new file mode 100644 index 0000000..47faaca --- /dev/null +++ b/RK1106/RK1106_server_test.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +''' +# @Time : 2026/3/16 15:00 +# @Author : reenrr +# @File : RK1106_server_test.py +# @Desc : RK1106服务端(类形式,多线程),等待工控机调用 通信为JSON格式 --待测试,加了线程 +''' +import socket +import logging +import json +import threading + +from stepper_motor import motor_start, align_wire, cleanup, motor_stop + + +# --------配置TCP服务端---------- +HOST = "192.168.5.100" +PORT = 8888 +CONFIG_FILE = "motor_config.json" + + +class RK1106Server: + """ + RK1106服务端类 + 在初始化时加载配置,提供配置管理和指令处理功能 + """ + + def __init__(self): + """初始化服务端,加载配置""" + self.config = self._load_config() + self.current_thread = None + logging.info(f"服务初始化完成,加载配置:{self.config}") + + def _load_config(self): + """ + 从JSON文件加载电机配置 + :return: 加载的配置字典 + """ + try: + with open(CONFIG_FILE, "r") as f: + return json.load(f) + except FileNotFoundError: + logging.warning(f"配置文件 {CONFIG_FILE} 未找到,将使用默认配置") + return {"speed": 2500, "cycle": 10.0} + except json.JSONDecodeError: + logging.error(f"配置文件 {CONFIG_FILE} 格式错误") + return {"speed": 2500, "cycle": 10.0} + + def _save_config(self): + """ + 将电机配置保存到JSON文件 + """ + try: + with open(CONFIG_FILE, "w") as f: + json.dump(self.config, f, indent=4, ensure_ascii=False) + logging.info(f"配置已保存:{self.config}") + except IOError as e: + logging.error(f"配置文件 {CONFIG_FILE} 保存失败: {str(e)}") + + def _execute_motor_command(self, cmd_func: callable, *args, **kwargs): + """ + 在单独的线程中执行电机命令 + :param cmd_func: 要执行的电机命令函数 + :param args: 传递给cmd_func的位置参数 + :param kwargs: 传递给cmd_func的关键字参数 + """ + try: + result = cmd_func(*args, **kwargs) + logging.info(f"电机命令执行完成: {result}") + except Exception as e: + logging.error(f"执行电机命令时出错: {str(e)}") + + def handle_setting(self, para_type: str, para_value: str) -> dict: + """ + 处理客户端发送的参数设置指令(cmd:"setting"),更新电机配置 + :param para_type: 要设置的参数类型,仅支持"speed"或"cycle" + :param para_value: 参数值字符串,将尝试转换为int(speed)或float(cycle) + :return: 标准化相应字典dict + """ + try: + if para_type == "speed": + self.config["speed"] = int(para_value) + elif para_type == "cycle": + self.config["cycle"] = float(para_value) + else: + return {"Result": "0", "ErrMsg": f"不支持的参数类型: {para_type}"} + + # 保存到文件 + self._save_config() + return {"Result": "1", "ErrMsg": "设置成功"} + except ValueError as e: + return {"Result": "0", "ErrMsg": f"参数值格式错误: {str(e)}"} + + def handle_start(self, para_type: str, para_value: str) -> dict: + """ + 处理启动电机指令(cmd: "start"),使用当前配置运行电机 + :param para_type:为"direction"时,使用"para_value"作为临时方向 + :param para_value:为0或1 + :return: 标准化相应字典dict + """ + try: + if para_type == "direction": + direction = int(para_value) + if direction not in (0, 1): + return {"Result": "0", "ErrMsg": "方向必须为0或1"} + + # 在新线程中启动电机 + thread = threading.Thread( + target=self._execute_motor_command, + args=(motor_start,), + kwargs={ + "speed": self.config["speed"], + "cycle": self.config["cycle"], + "direction": direction + } + ) + thread.start() + + dir_str = "正向" if direction == 1 else "负向" + return {"Result": "1", "ErrMsg": f"电机启动指令已发送({dir_str})"} + else: + return {"Result": "0", "ErrMsg": "start 指令仅支持'direction'"} + except Exception as e: + return {"Result": "0", "ErrMsg": f"电机启动失败:{str(e)}"} + + def handle_stop(self) -> dict: + """ + 处理停止电机指令(cmd: "stop") + :return: 标准化相应字典dict + """ + try: + # 在新线程中停止电机 + thread = threading.Thread( + target=self._execute_motor_command, + args=(motor_stop,) + ) + thread.start() + return {"Result": "1", "ErrMsg": "电机停止指令已发送"} + except Exception as e: + return {"Result": "0", "ErrMsg": f"电机停止失败:{str(e)}"} + + def handle_align(self) -> dict: + """ + 处理线条对齐(挡板一来一回) + :return: dict + """ + try: + # 在新线程中对齐线条 + thread = threading.Thread( + target=self._execute_motor_command, + args=(align_wire,), + kwargs={ + "speed": self.config['speed'], + "cycle": self.config['cycle'] + } + ) + thread.start() + return {"Result": "1", "ErrMsg": "线条对齐指令已发送"} + except Exception as e: + return {"Result": "0", "ErrMsg": "线条对齐失败"} + + def parse_json_command(self, data: str) -> dict: + """ + 解析客户端发送的原始JSON字符串指令,并分发至对应处理函数 + :param data: 客户端发送的原始JSON字符串 + :return dict:标准化响应字典 + """ + try: + cmd_obj = json.loads(data.strip()) + except json.JSONDecodeError as e: + return {"Result": "0", "ErrMsg": f"JSON 格式错误: {str(e)}"} + + cmd = cmd_obj.get("cmd", "").strip() + para_type = cmd_obj.get("para_type", "").strip() + para_value = cmd_obj.get("para_value", "").strip() + + if cmd == "setting": + return self.handle_setting(para_type, para_value) + elif cmd == "start": + return self.handle_start(para_type, para_value) + elif cmd == "stop": + if para_type == "none" and para_value == "none": + return self.handle_stop() + else: + return {"Result": "0", "ErrMsg": "stop指令参数必须为none"} + elif cmd == "alignment": + if para_type == "none" and para_value == "none": + return self.handle_align() + else: + return {"Result": "0", "ErrMsg": "alignment指令参数必须为none"} + else: + return {"Result": "0", "ErrMsg": f"未知指令:{cmd}"} + + def run_server(self): + """ + 启动TCP服务端,监听指定端口,接收工控机连接并循环处理JSON指令 + """ + # 创建TCP socket + server_socket = None + conn = None + + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind((HOST, PORT)) + server_socket.listen(1) # 只允许1个工控机连接 + logging.info(f"[1106] 服务已启动,监听端口:{PORT},等待工控机连接...") + + while True: # 持续接受新连接 + try: + # 等待工控机连接 + conn, addr = server_socket.accept() + logging.info(f"[1106] 工控机已连接:{addr}") + + # 循环接收指令 + while True: + # 接收指令(最大1024字节) + data = conn.recv(1024).decode() + if not data: + logging.warning("客户端断开连接") + break + + logging.info(f"\n[1106] 收到工控机指令:{data}") + + # 解析指令 + response_dict = self.parse_json_command(data) + response_json = json.dumps(response_dict, ensure_ascii=False) + "\n" # 看需不需要加换行符\n + # 发送响应给工控机 + conn.sendall(response_json.encode("utf-8")) + logging.info(f"[1106] 已发送响应:{response_json}") + + except ConnectionError: + logging.info("客户端异常断开") + except Exception as e: + logging.info(f"处理连接时发生错误: {e}") + finally: + if conn is not None: + conn.close() + conn = None # 重置,避免重复关闭 + logging.info("客户端连接已关闭,等待新连接...") + + except KeyboardInterrupt: + logging.info("\n收到 Ctrl+C,正在关闭服务...") + finally: + if server_socket: + server_socket.close() + logging.info("服务已停止,监听 socket 已释放") + +# ----------测试接口---------- +if __name__ == "__main__": + SERVER = RK1106Server() + SERVER.run_server() diff --git a/RK1106/image.png b/RK1106/image.png new file mode 100644 index 0000000000000000000000000000000000000000..f79a3973264572187f031cde327962c098ff4a02 GIT binary patch literal 57891 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 literal 0 HcmV?d00001 diff --git a/RK1106/motor_config.json b/RK1106/motor_config.json new file mode 100644 index 0000000..22e1cdc --- /dev/null +++ b/RK1106/motor_config.json @@ -0,0 +1,4 @@ +{ + "speed": 2500, + "cycle": 10.0 +} \ No newline at end of file diff --git a/RK1106/readme.md b/RK1106/readme.md index 4a59aa5..2aaa2bf 100644 --- a/RK1106/readme.md +++ b/RK1106/readme.md @@ -8,4 +8,7 @@ ![img.png](img.png) GPIO32 --脉冲引脚 GPIO33 --方向引脚 -剩下两个引脚为地线 \ No newline at end of file +剩下两个引脚为地线 + +# 自启动设置: + ![alt text](image.png) \ No newline at end of file diff --git a/RK1106/stepper_motor.py b/RK1106/stepper_motor.py index cfc1025..993b9b2 100644 --- a/RK1106/stepper_motor.py +++ b/RK1106/stepper_motor.py @@ -7,9 +7,29 @@ # @Desc : 线条厂控制步进电机测试 应该不会丢步 """ import time +import logging +import sys from periphery import GPIO +# ------------日志配置(终端+文件双输出)-------------- + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + # 核心新增:日志文件配置 + handlers=[ + # 1. 文件处理器:保存到.log文件 + logging.FileHandler( + "RK1106_server.log", # Buildroot推荐路径,临时测试可改/tmp/1106_server.log + mode='a', # 追加模式(不会覆盖历史日志) + encoding='utf-8' # 防止中文乱码(必加) + ), + # 2. 终端处理器:输出到控制台 + logging.StreamHandler(sys.stdout) + ] +) + # ------------参数配置------------- # 1. 脉冲(PUL)引脚配置 → GPIO32 PUL_Pin = 32 @@ -66,8 +86,8 @@ class StepperMotor: self.pul_gpio.write(False) self.dir_gpio.write(False) - print(f"✅ PUL引脚初始化完成:{self.pul_pin} 引脚") - print(f"✅ DIR引脚初始化完成:{self.dir_pin} 引脚") + logging.info(f"✅ PUL引脚初始化完成:{self.pul_pin} 引脚") + logging.info(f"✅ DIR引脚初始化完成:{self.dir_pin} 引脚") except PermissionError: raise RuntimeError("权限不足!请用sudo运行程序(sudo python xxx.py)") @@ -76,10 +96,10 @@ class StepperMotor: def _validate_params(self, rounds: float, direction: int) -> bool: if rounds <= 0: - print("圈数必须为正数") + logging.info("圈数必须为正数") return False if direction not in (0, 1): - print("方向必须为0(逆时针)或1(顺时针)") + logging.info("方向必须为0(逆时针)或1(顺时针)") return False return True @@ -97,17 +117,17 @@ class StepperMotor: # 设置旋转方向(DIR电平) if direction == 1: # 顺时针 self.dir_gpio.write(self.clockwise_level) - print(f"\n=== 顺时针旋转 {rounds} 圈 ===") + logging.info(f"\n=== 顺时针旋转 {rounds} 圈 ===") else: # 逆时针 self.dir_gpio.write(self.counter_clockwise_level) - print(f"\n=== 逆时针旋转 {rounds} 圈 ===") + logging.info(f"\n=== 逆时针旋转 {rounds} 圈 ===") # 计算总脉冲数和时序(精准控制,避免丢步) total_pulses = int(rounds * self.pulses_per_round) pulse_period = 1.0 / pulse_frequency # 脉冲周期(秒) half_period = pulse_period / 2 # 占空比50%(MA860H最优) - print(f"总脉冲数:{total_pulses} | 频率:{pulse_frequency}Hz | 周期:{pulse_period * 1000:.2f}ms") + logging.info(f"总脉冲数:{total_pulses} | 频率:{pulse_frequency}Hz | 周期:{pulse_period * 1000:.2f}ms") start_time = time.perf_counter() # 高精度计时(避免丢步) # 发送脉冲序列(核心:占空比50%的方波) @@ -122,13 +142,13 @@ class StepperMotor: # 更新下一个脉冲的起始时间 start_time += pulse_period - print("✅ 旋转完成") + logging.info("✅ 旋转完成") def stop(self): """紧急停止(置低脉冲引脚)""" if self.pul_gpio: self.pul_gpio.write(False) - print("🛑 电机已停止") + logging.info("🛑 电机已停止") def close(self): """释放GPIO资源""" @@ -136,12 +156,12 @@ class StepperMotor: if self.pul_gpio: self.pul_gpio.write(False) # 脉冲引脚置低 self.pul_gpio.close() - print("\n✅ PUL引脚已关闭(电平置低)") + logging.info("\n✅ PUL引脚已关闭(电平置低)") if self.dir_gpio: self.dir_gpio.write(False) # 方向引脚置低 self.dir_gpio.close() - print("✅ DIR引脚已关闭(电平置低)") + logging.info("✅ DIR引脚已关闭(电平置低)") # 重置GPIO对象 self.pul_gpio = None @@ -163,16 +183,16 @@ def motor_start(speed: int, cycle: float, direction: int): :param direction: 0=负向(逆时针),1=正向(顺时针) """ try: - print("\n=== 启动步进电机 ===") + logging.info("\n=== 启动步进电机 ===") GLOBAL_MOTOR.rotate(pulse_frequency=speed, rounds=cycle, direction=direction) time.sleep(5) # 暂停5秒 except ImportError: - print("\n❌ 缺少依赖:请安装python-periphery") - print("命令:pip install python-periphery") + logging.info("\n❌ 缺少依赖:请安装python-periphery") + logging.info("命令:pip install python-periphery") except Exception as e: - print(f"\n❌ 程序异常:{str(e)}") + logging.info(f"\n❌ 程序异常:{str(e)}") def motor_stop(): """紧急停止(仅停止脉冲,保留实例)""" @@ -180,7 +200,8 @@ def motor_stop(): if GLOBAL_MOTOR: GLOBAL_MOTOR.stop() except Exception as e: - print("停止失败:{e}") + logging.info("停止失败:{e}") + def align_wire(speed: int, cycle: float): """ @@ -189,20 +210,20 @@ def align_wire(speed: int, cycle: float): :param cycle: 旋转圈数 """ try: - print("\n=== 启动线条对齐 ===") + logging.info("\n=== 启动线条对齐 ===") # 靠近电机方向 逆时针 GLOBAL_MOTOR.rotate(pulse_frequency=speed, rounds=cycle, direction=0) - time.sleep(5) # 暂停5秒 + time.sleep(2) # 暂停3秒 # 远离电机方向 顺时针 GLOBAL_MOTOR.rotate(pulse_frequency=speed,rounds=cycle, direction=1) - time.sleep(5) # 暂停5秒 + time.sleep(2) # 暂停3秒 except ImportError: - print("\n❌ 缺少依赖:请安装python-periphery") - print("命令:pip install python-periphery") + logging.info("\n❌ 缺少依赖:请安装python-periphery") + logging.info("命令:pip install python-periphery") except Exception as e: - print(f"\n❌ 程序异常:{str(e)}") + logging.info(f"\n❌ 程序异常:{str(e)}") def cleanup(): """程序退出时统一清理""" @@ -212,14 +233,14 @@ def cleanup(): if __name__ == '__main__': align_wire(speed=2500, cycle=10.0) - time.sleep(10) # 电机运动需要的时间 + time.sleep(5) # 电机运动需要的时间 - # 测试是否电机能停止 + # # 测试是否电机能停止 motor_start(speed=2500, cycle=5.0, direction=0) time.sleep(1) # 电机运动需要的时间 - motor_stop() + motor_stop() time.sleep(0.2) - - while(True): # 防止程序退出 + + while True: # 防止程序退出 time.sleep(1) diff --git a/conveyor_controller/__pycache__/error_code.cpython-39.pyc b/conveyor_controller/__pycache__/error_code.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7e2f8cc7a6494206d3be3ab9831659b18c2da37 GIT binary patch literal 2380 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 bool: + """ + 设置伺服电机位置 + :param station_addr: 从机地址 + :param position_value: 位置值(32位整数) 正数:远离电机方向;负数:靠近电机方向 + :return: True--设置成功,False--设置失败 + """ + _check_handle_valid() + + try: + # 有符号32位位置值拆分为高16位和低16位 + position_value = position_value & 0xFFFFFFFF # 确保为32位无符号整数 + position_h = (position_value >> 16) & 0xFFFF # 高16位 + position_l = position_value & 0xFFFF # 低16位 + print(f"位置值拆分:{position_value} → 高16位:(0x{position_h:04x}) 低16位:(0x{position_l:04x})") + + except Exception as e: + err_msg = f"位置值拆分失败 - {str(e)}" + print(f"错误:{err_msg}") + raise ValueError(err_msg) from e + + try: + # 写入高16位到0x6201 + ret_code_h = write_single_register(handle1, station_addr, POSITION_H_REG_ADDR, + position_h, resp_offset=0, use_crc=1) + if ret_code_h != ModbusError.MODBUS_SUCCESS: + err_desc = ModbusError.get_error_desc(ret_code_h) + raise OSError(f"位置高位写入失败:{err_desc}", ret_code_h) + + # 写入低16位到 0x6202 + ret_code_l = write_single_register(handle1, station_addr, POSITION_L_REG_ADDR, + position_l, resp_offset=0, use_crc=1) + if ret_code_l != ModbusError.MODBUS_SUCCESS: + err_desc = ModbusError.get_error_desc(ret_code_l) + raise OSError(f"位置低位写入失败:{err_desc}", ret_code_l) + + print(f"成功:电机位置设置完成(从站{station_addr},位置值:{position_value})") + return True + + except OSError: + raise + except Exception as e: + err_msg = f"位置设置异常 - {str(e)}" + print(f"异常:{err_msg}") + raise Exception(err_msg) from e + + def set_motor_speed(station_addr: int, speed_value: int) -> bool: """ 设置伺服电机速度 :param station_addr: 从机地址 - :param speed_value: 速度值 + :param speed_value: 速度值 速度模式时,正负值表示方向,正值--远离电机方向,负值--靠近电机方向 + 其他模式时,速度没有方向概念,只表示速度大小 :return: True--设置成功,False--设置失败 """ _check_handle_valid() @@ -64,7 +121,7 @@ def set_motor_speed(station_addr: int, speed_value: int) -> bool: try: value = handle_obj.decimal_to_16bit(speed_value) - print(f"速度值转换:{speed_value} → {value}(0x{value:04X})") + # print(f"速度值转换:{speed_value} → {value}(0x{value:04X})") except Exception as e: err_msg = f"{ModbusError.get_error_desc(ModbusError.MODBUS_ERR_DATA_CONVERT)} - {str(e)}" @@ -93,6 +150,7 @@ def set_motor_speed(station_addr: int, speed_value: int) -> bool: print(f"异常:{err_msg}") raise Exception(err_msg) from e + def set_motor_acceleration(station_addr: int, value: int) -> bool: """ 设置伺服电机加速度 @@ -106,7 +164,7 @@ def set_motor_acceleration(station_addr: int, value: int) -> bool: try: conv_value = handle_obj.decimal_to_16bit(value) - print(f"加速度值转换:{value} → {conv_value}(0x{conv_value:04X})") + # print(f"加速度值转换:{value} → {conv_value}(0x{conv_value:04X})") except Exception as e: err_msg = f"{ModbusError.get_error_desc(ModbusError.MODBUS_ERR_DATA_CONVERT)} - {str(e)}" @@ -138,6 +196,7 @@ def set_motor_acceleration(station_addr: int, value: int) -> bool: print(f"异常:{err_msg}") raise Exception(err_msg) from e + def set_motor_deceleration(station_addr: int, value: int) -> bool: """ 设置伺服电机减速度 @@ -151,7 +210,7 @@ def set_motor_deceleration(station_addr: int, value: int) -> bool: try: conv_value = handle_obj.decimal_to_16bit(value) - print(f"减速度值转换:{value} → {conv_value}(0x{conv_value:04X})") + # print(f"减速度值转换:{value} → {conv_value}(0x{conv_value:04X})") except Exception as e: err_msg = f"{ModbusError.get_error_desc(ModbusError.MODBUS_ERR_DATA_CONVERT)} - {str(e)}" @@ -178,24 +237,28 @@ def set_motor_deceleration(station_addr: int, value: int) -> bool: print(f"异常:{err_msg}") raise Exception(err_msg) from e -def set_motor_mode(station_addr: int, value: int) -> bool: + +def set_motor_mode(station_addr: int, mode: int) -> bool: """ 设置电机模式 :param station_addr: 从机地址 - :param value: 0--速度模式 1--绝对位置模式 + :param mode: 0--速度模式 1--绝对位置模式 2--相对位置模式 :return: bool 设置是否成功 """ _check_handle_valid() try: - if value == 0: + if mode == 0: write_cmd = SPEED_CMD mode_desc = "速度模式" - elif value == 1: + elif mode == 1: write_cmd = ABSOLUTE_POSITION_CMD mode_desc = "绝对位置模式" + elif mode == 2: + write_cmd = RELATIVE_POSITION_CMD + mode_desc = "相对位置模式" - ret_code = write_single_register(handle1, station_addr, MODE_REG_ADDR, write_cmd, 0, 1) + ret_code = write_single_register(handle1, station_addr, MODE_REG_ADDR, write_cmd, resp_offset=0, use_crc=1) if ret_code == ModbusError.MODBUS_SUCCESS: print(f"成功:电机模式设置完成(从站{station_addr},地址0x{MODE_REG_ADDR:04X},模式:{mode_desc})") @@ -215,6 +278,7 @@ def set_motor_mode(station_addr: int, value: int) -> bool: print(f"异常:{err_msg}") raise Exception(err_msg) from e + def get_motor_speed(station_addr: int) -> int: """ 获取电机速度 @@ -247,6 +311,7 @@ def get_motor_speed(station_addr: int) -> int: print(f"错误:{err_msg}") raise Exception(err_msg) from e + def get_motor_acceleration(station_addr: int) -> int: """ 获取电机加速度 @@ -279,6 +344,7 @@ def get_motor_acceleration(station_addr: int) -> int: print(f"错误:{err_msg}") raise Exception(err_msg) from e + def get_motor_deceleration(station_addr: int) -> int: """ 获取电机减速度 @@ -310,6 +376,7 @@ def get_motor_deceleration(station_addr: int) -> int: print(f"错误:{err_msg}") raise Exception(err_msg) from e + def move_motor(station_addr: int, value: bool) -> bool: """ 启停电机 @@ -347,6 +414,7 @@ def move_motor(station_addr: int, value: bool) -> bool: print(f"异常:{err_msg}") raise Exception(err_msg) + def sync_motor_move(value: bool): """ 同步传送带电机 @@ -372,25 +440,479 @@ def sync_motor_move(value: bool): raise RuntimeError(err_msg) from e -# ------------调试接口---------- -if __name__ == '__main__': - # 配置传送带电机参数 只需配置一次 - set_motor_mode(1, 0) # 配置成速度模式 - set_motor_speed(1, -30) - set_motor_acceleration(1, 50) - set_motor_deceleration(1, 50) +def read_motor_value(station_addr: int) -> int: + """ + 读取电机位置值 + :param station_addr: 从站地址 + :return: 电机位置值 + """ + _check_handle_valid() - set_motor_mode(2, 0) # 配置成速度模式 - set_motor_speed(2, -30) - set_motor_acceleration(2, 50) - set_motor_deceleration(2, 50) + handle_obj = RTU_HANDLE_MAP.get(handle1) + + try: + reg_values = read_holding_register(handle1, station_addr, ENCODER_H_REG_ADDR, + reg_count=2, resp_offset=0, out_buffer=[], use_crc=1) + if not reg_values or len(reg_values) != 2: + err_msg = f"电机位置值读取失败,数据长度错误:{reg_values}" + print(f"错误:{err_msg}") + raise OSError(err_msg) + + # 组合高16位和低16位 + encoder_h = reg_values[0] # 高16位 + encoder_l = reg_values[1] # 低16位 + encoder_value = (encoder_h << 16) | encoder_l + # 转换为有符号数 + signed_encoder_value = handle_obj.uint32_to_int32(encoder_value) + + print(f"成功:读取电机位置值(从站{station_addr})-->高位:{encoder_h} 低位:{encoder_l} 总值:{signed_encoder_value}") + return signed_encoder_value + + except OSError: + raise + except Exception as e: + err_msg = f"电机位置值读取异常 - {str(e)}" + print(f"异常:{err_msg}") + raise Exception(err_msg) from e + + +def read_motor_value_command(station_addr: int) -> int: + """ + 读取电机位置值--指令单位 + :param station_addr: 从站地址 + :return: 电机位置值 + """ + _check_handle_valid() + + handle_obj = RTU_HANDLE_MAP.get(handle1) + + try: + reg_values = read_holding_register(handle1, station_addr, COMMAND_H_REG_ADDR, + reg_count=2, resp_offset=0, out_buffer=[], use_crc=1) + if not reg_values or len(reg_values) != 2: + err_msg = f"电机位置值读取失败,数据长度错误:{reg_values}" + print(f"错误:{err_msg}") + raise OSError(err_msg) + + # 组合高16位和低16位 + command_value_h = reg_values[0] # 高16位 + command_value_l = reg_values[1] # 低16位 + command_value = (command_value_h << 16) | command_value_l + # 转换为有符号数 + signed_command_value = handle_obj.uint32_to_int32(command_value) + + print(f"成功:读取电机位置值(从站{station_addr})-->高位:{command_value_h} 低位:{command_value_l} 总值:{signed_command_value}") + return signed_command_value + + except OSError: + raise + except Exception as e: + err_msg = f"电机位置值读取异常 - {str(e)}" + print(f"异常:{err_msg}") + raise Exception(err_msg) from e + +def read_position_error(station_addr: int) -> int: + """ + 读取电机位置误差--编码器单位 + :param station_addr: 从站地址 + :return: 电机位置误差 + """ + _check_handle_valid() + + handle_obj = RTU_HANDLE_MAP.get(handle1) + + try: + reg_values = read_holding_register(handle1, station_addr, POSITION_ERROR_H_REG_ADDR, + reg_count=2, resp_offset=0, out_buffer=[], use_crc=1) + if not reg_values or len(reg_values) != 2: + err_msg = f"电机位置误差读取失败,数据长度错误:{reg_values}" + print(f"错误:{err_msg}") + raise OSError(err_msg) + + # 组合高16位和低16位 + position_error_h = reg_values[0] # 高16位 + position_error_l = reg_values[1] # 低16位 + position_error = (position_error_h << 16) | position_error_l + # 转换为有符号数 + signed_position_error = handle_obj.uint32_to_int32(position_error) + + print(f"成功:读取电机位置误差(从站{station_addr})-->高位:{position_error_h} 低位:{position_error_l} 总值:{signed_position_error}") + return signed_position_error + + except OSError: + raise + except Exception as e: + err_msg = f"电机位置误差读取异常 - {str(e)}" + print(f"异常:{err_msg}") + raise Exception(err_msg) from e + +# ------------对外测试接口------------ +def test_speed_mode(): + # 配置传送带电机参数 只需配置一次 + # 速度模式 + set_motor_mode(station_addr=1, mode=0) # 配置成速度模式 + set_motor_speed(station_addr=1, speed_value=-30) + set_motor_acceleration(station_addr=1, acceleration=50) + set_motor_deceleration(station_addr=1, deceleration=50) + + set_motor_mode(station_addr=2, mode=0) # 配置成速度模式 + set_motor_speed(station_addr=2, speed_value=-30) + set_motor_acceleration(station_addr=2, acceleration=50) + set_motor_deceleration(station_addr=2, deceleration=50) sync_motor_move(True) - time.sleep(1) + time.sleep(2) sync_motor_move(False) time.sleep(0.5) - - while(True): + + while True: time.sleep(1) + +def test_absolute_position_mode(): + # 绝对位置模式 + set_motor_mode(station_addr=1, mode=1) # 配置成绝对位置模式 + set_motor_position(station_addr=1, position_value=0) # 每圈10000脉冲,跑两圈 + set_motor_speed(station_addr=1, speed_value=30) + set_motor_acceleration(station_addr=1, value=50) + set_motor_deceleration(station_addr=1, value=50) + + set_motor_mode(station_addr=2, mode=1) # 配置成绝对位置模式 + set_motor_position(station_addr=2, position_value=0) + set_motor_speed(station_addr=2, speed_value=30) + set_motor_acceleration(station_addr=2, value=50) + set_motor_deceleration(station_addr=2, value=50) + + # 运行前查看电机位置初始值 + print("\n=== 运行前电机位置初始值 ===") + motor1_initial = read_motor_value(station_addr=1) + motor2_initial = read_motor_value(station_addr=2) + print(f"电机1初始电机位置值: {motor1_initial}") + print(f"电机2初始电机位置值: {motor2_initial}") + + sync_motor_move(True) + + # 等待电机完成运动(根据实际情况调整等待时间) + time.sleep(5) + + # 读取编码器值检查是否丢步 + print("\n=== 检查电机是否丢步 ===") + target_position = 0 # 目标位置值 + + # 检查电机1 + motor1 = read_motor_value(station_addr=1) + error1 = abs(motor1 - target_position) + print(f"电机1 - 目标位置: {target_position}, 实际位置: {motor1}, 误差: {error1}") + + # 检查电机2 + encoder2 = read_motor_value(station_addr=2) + error2 = abs(encoder2 - target_position) + print(f"电机2 - 目标位置: {target_position}, 实际位置: {encoder2}, 误差: {error2}") + + encoder_unit3 = read_motor_value_command(station_addr=1) + encoder_unit4 = read_motor_value_command(station_addr=2) + print(f"电机1运动后电机的指令值: {encoder_unit3}") + print(f"电机2运动后电机的指令值: {encoder_unit4}") + + # 读取电机位置误差 + position_error1 = read_position_error(station_addr=1) + position_error2 = read_position_error(station_addr=2) + print(f"电机1位置误差: {position_error1}") + print(f"电机2位置误差: {position_error2}") + + while True: + time.sleep(1) + +def test_relative_position_mode(): + """ + 相对位置模式测试(已修复CRC错误、数据符号、通信延时问题) + 功能:每3秒相对移动6000脉冲,实时监控编码器误差,误差>100自动补偿 + """ + try: + # ===================== 1. 基础配置 ===================== + station_list = [1, 2] + base_pulse = 6000 + interval = 3 + error_threshold = 100 + move_wait_time = 1.5 + + # 补偿值 + compensate_map = {1: 0, 2: 0} + + print("===== 相对位置模式 + 误差监控 + 自动补偿(稳定版)=====") + print(f"基础移动:{base_pulse} 脉冲 | 误差阈值:>{error_threshold}") + + # 初始化电机 + for station in station_list: + set_motor_mode(station, 2) + set_motor_speed(station, 30) + set_motor_acceleration(station, 50) + set_motor_deceleration(station, 50) + time.sleep(0.1) + print(f"✅ 电机{station} 配置完成") + + print("\n===== 开始循环运动 =====") + count = 0 + + while True: + count += 1 + print(f"\n==================== 第{count}次运动 ====================") + + # 写入位置(带重试 + 间隔,解决CRC) + for station in station_list: + compensate = compensate_map[station] + final_pulse = base_pulse - compensate + + # 关键修复:相对位置必须用 有符号32位 + final_pulse = int(final_pulse) + + retry = 3 + while retry > 0: + try: + set_motor_position(station, final_pulse) + time.sleep(0.05) + break + except: + retry -= 1 + time.sleep(0.1) + if retry == 0: + print(f"❌ 电机{station} 写入位置失败") + + print(f"电机{station}:移动 {final_pulse} 脉冲") + + # 启动 + sync_motor_move(True) + time.sleep(move_wait_time) + + # 停止 + sync_motor_move(False) + time.sleep(0.2) + + # 读取误差 + print("\n---------------- 误差监控 ----------------") + for station in station_list: + try: + real_pos = read_motor_value(station) + pos_error = read_position_error(station) + + if abs(pos_error) > error_threshold: + new_comp = pos_error - error_threshold + compensate_map[station] = new_comp + status = f"⚠️ 补偿:{new_comp}" + else: + compensate_map[station] = 0 + status = "✅ 正常" + + print(f"电机{station} | 实际:{real_pos} | 误差:{pos_error} | {status}") + except: + print(f"⚠️ 电机{station} 读取失败") + + print(f"\n⏳ {interval}秒后继续...") + time.sleep(interval) + + except KeyboardInterrupt: + print("\n🛑 手动停止") + sync_motor_move(False) + except Exception as e: + print(f"\n❌ 异常:{e}") + sync_motor_move(False) + +def test_cycle_relative_position_mode(): + """ + 相对位置模式 —— 前20次每次+5,之后固定使用6007脉冲 + 稳定版:防CRC报错 + 自动重试 + """ + try: + # ===================== 配置 ===================== + station_list = [1, 2] + base_pulse = 6007 # 初始脉冲 + move_wait_time = 3 # 运动时间 + send_interval = 2 # 间隔2秒 + speed = 30 + acc = 50 + dec = 50 + + max_increase_times = 50 # 前20次递增 + increase_step = 2 # 每次加5 + current_count = 0 # 运行次数计数 + + print("===== 前20次每次+5,20次后恢复6007脉冲 =====") + + # 初始化电机 + for station in station_list: + set_motor_mode(station, 2) + time.sleep(0.1) + set_motor_speed(station, speed) + time.sleep(0.1) + set_motor_acceleration(station, acc) + time.sleep(0.1) + set_motor_deceleration(station, dec) + time.sleep(0.1) + print(f"✅ 电机{station} 初始化完成") + + # ===================== 主循环 ===================== + while True: + current_count += 1 + print(f"\n==================== 第 {current_count} 次运动 ====================") + + # ============== 核心逻辑 ============== + if current_count <= max_increase_times: + # 前20次:每次 +5 + send_pulse = base_pulse + current_count * increase_step + print(f"📈 前20次递增 → 本次发送:{send_pulse}") + else: + # 20次以后:固定 6007 + send_pulse = 6007 + print(f"✅ 已超过20次 → 固定发送:{send_pulse}") + + # ========== 写入位置(带重试)========== + for station in station_list: + retry = 5 + while retry > 0: + try: + set_motor_position(station, send_pulse) + time.sleep(0.1) + break + except Exception as e: + retry -= 1 + print(f"⚠️ 电机{station} 写入失败,重试 {retry} 次") + time.sleep(0.2) + + # ========== 启动电机 ========== + sync_motor_move(True) + + # ========== 运动中实时监控误差 ========== + print("\n------------ 运动中实时误差 ------------") + start_time = time.time() + while time.time() - start_time < move_wait_time: + for station in station_list: + try: + err = read_position_error(station) + print(f"电机{station} 实时误差 → {err}") + except: + pass + time.sleep(0.1) + + # ========== 停止 + 等待 ========== + sync_motor_move(False) + time.sleep(0.3) + print(f"\n✅ 运动完成,等待 {send_interval} 秒后继续...") + time.sleep(send_interval) + + except KeyboardInterrupt: + print("\n🛑 手动停止") + try: + sync_motor_move(False) + except: + pass + except Exception as e: + print(f"\n❌ 异常:{e}") + try: + sync_motor_move(False) + except: + pass + +def test_cycle_relative_position_mode_test(): + """ + 相对位置模式 —— 电机1每次+2,电机2每次+4 + 稳定版:防CRC报错 + 自动重试 + """ + try: + # ===================== 配置 ===================== + station_list = [1, 2] + base_pulse = 6007 # 初始基准脉冲 + move_wait_time = 3 # 运动时间 + send_interval = 2 # 间隔2秒 + speed = 30 + acc = 50 + dec = 50 + + current_count = 0 # 运行次数计数 + + print("===== 电机1每次+2,电机2每次+4 循环运行 =====") + + # 初始化电机 + for station in station_list: + set_motor_mode(station, 2) + time.sleep(0.1) + set_motor_speed(station, speed) + time.sleep(0.1) + set_motor_acceleration(station, acc) + time.sleep(0.1) + set_motor_deceleration(station, dec) + time.sleep(0.1) + print(f"✅ 电机{station} 初始化完成") + + # ===================== 主循环 ===================== + while True: + current_count += 1 + print(f"\n==================== 第 {current_count} 次运动 ====================") + + # ============== 核心:两台电机不同增量 ============== + pulse_1 = base_pulse + current_count * 10 # 电机1:每次 +2 靠近出料口的电机 + pulse_2 = base_pulse + current_count * 5 # 电机2:每次 +4 远离出料口的电机 + + print(f"📌 电机1本次脉冲:{pulse_1}") + print(f"📌 电机2本次脉冲:{pulse_2}") + + # ========== 分别写入两台电机(带重试)========== + # 写入电机1 + retry = 5 + while retry > 0: + try: + set_motor_position(1, pulse_1) + time.sleep(0.1) + break + except Exception as e: + retry -= 1 + print(f"⚠️ 电机1写入失败,重试 {retry} 次") + time.sleep(0.2) + + # 写入电机2 + retry = 5 + while retry > 0: + try: + set_motor_position(2, pulse_2) + time.sleep(0.1) + break + except Exception as e: + retry -= 1 + print(f"⚠️ 电机2写入失败,重试 {retry} 次") + time.sleep(0.2) + + # ========== 启动电机 ========== + sync_motor_move(True) + + # ========== 运动中实时监控误差 ========== + print("\n------------ 运动中实时误差 ------------") + start_time = time.time() + while time.time() - start_time < move_wait_time: + for station in station_list: + try: + err = read_position_error(station) + print(f"电机{station} 实时误差 → {err}") + except: + pass + time.sleep(0.1) + + time.sleep(1) + print(f"\n✅ 运动完成,等待 {send_interval} 秒后继续...") + time.sleep(send_interval) + + except KeyboardInterrupt: + print("\n🛑 手动停止") + try: + sync_motor_move(False) + except: + pass + except Exception as e: + print(f"\n❌ 异常:{e}") + try: + sync_motor_move(False) + except: + pass + +# ------------调试接口---------- +if __name__ == '__main__': + test_cycle_relative_position_mode() diff --git a/conveyor_controller/modbus.py b/conveyor_controller/modbus.py index 54d547a..3493914 100644 --- a/conveyor_controller/modbus.py +++ b/conveyor_controller/modbus.py @@ -161,10 +161,29 @@ class RTUSerialHandle: else: return unsigned_value + def uint32_to_int32(self, unsigned_value: int) -> int: + """ + 将32位无符号十进制数转换为32位有符号十进制数 + :param unsigned_value: 无符号十进制数 + :return: 有符号十进制数 + """ + # 先校验输入范围(必须是32位无符号数) + if not isinstance(unsigned_value, int): + raise ValueError(f"输入必须是整数,当前是{type(unsigned_value)}") + if unsigned_value < 0 or unsigned_value > 4294967295: + raise ValueError(f"输入必须是0~4294967295的整数,当前是{unsigned_value}") + + # 核心转换逻辑 + if unsigned_value > 2147483647: # 0x7FFFFFFF + return unsigned_value - 4294967296 # 0x100000000 + else: + return unsigned_value + def __del__(self): """析构函数,程序退出时自动关闭串口,防止资源泄露""" self.close() + # -------对外接口-------- # public def open_serial_port(port: str, baudrate: int, databits: int, stopbits: int, parity: int) -> Optional[int]: @@ -223,6 +242,7 @@ def open_serial_port(port: str, baudrate: int, databits: int, stopbits: int, par print(f"串口[{port}]打开成功,句柄ID:{handle_id}") return handle_id + def close_serial_port(handle: int): """ 关闭Modbus串口 @@ -245,6 +265,7 @@ def close_serial_port(handle: int): print(f"句柄{handle}(串口[{handle_obj.port}])已关闭") + # --------Modbus RTU CRC16校验函数----------- def modbus_crc16(data: bytes) -> bytes: """ @@ -269,6 +290,7 @@ def modbus_crc16(data: bytes) -> bytes: # 把16位CRC值拆成2个字节(小端序:低字节在前,高字节在后) return bytes([crc & 0xFF, (crc >> 8) & 0xFF]) + def verify_modbus_crc(data: bytes) -> bool: """ 验证Modbus RTU数据的CRC16校验码 @@ -411,6 +433,7 @@ def read_holding_register(handle: int, station_addr: int, start_reg_addr: int, print(f"读寄存器成功 | 从站{station_addr} | 起始地址{start_reg_addr} | 数量{reg_count} | 数据:{out_buffer}") return out_buffer + def write_single_register(handle: int, station_addr: int, reg_addr: int, write_value: int, resp_offset: int, use_crc: int) -> int: """ @@ -498,9 +521,10 @@ def write_single_register(handle: int, station_addr: int, reg_addr: int, write_v print(f"写响应不匹配 | 请求:{expected_resp.hex(' ')} | 响应:{response.hex(' ')}") return ModbusError.MODBUS_ERR_RESPONSE - print(f"写单个寄存器成功 | 从站{station_addr} | 地址{reg_addr} | 值{write_value}") + # print(f"写单个寄存器成功 | 从站{station_addr} | 地址{reg_addr} | 值{write_value}") return ModbusError.MODBUS_SUCCESS + def write_multi_register(handle: int, station_addr: int, start_reg_addr: int, reg_count: int, write_values: list[int], resp_offset: int, use_crc: int) -> int: """ @@ -620,6 +644,7 @@ def write_multi_register(handle: int, station_addr: int, start_reg_addr: int, re print(f"批量写寄存器成功 | 从站{station_addr} | 起始地址{start_reg_addr} | 数量{reg_count} | 值:{write_values}") return ModbusError.MODBUS_SUCCESS + # ---------测试接口-------- if __name__ == '__main__': # handle1 = open_serial_port( diff --git a/img.png b/img.png new file mode 100644 index 0000000000000000000000000000000000000000..9f631ca1e80755bf1ea8b468a066c7c193775d91 GIT binary patch literal 61780 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Ɍ*B{eraJ;Vj>9P^e&ALB3>UmvQb%K0``hSQ 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 int: """ 角度转原始值 @@ -61,6 +64,7 @@ def _degree_to_raw(degree: int) -> int: return max(0, min(raw_value, 4095)) + def _raw_to_degree(raw: int) -> float: """ 原始值转角度 @@ -69,6 +73,7 @@ def _raw_to_degree(raw: int) -> float: """ return round(raw * RAW_TO_DEGREE_RATIO, 2) + def set_servo_speed(value: int): """ 设置舵机速度 @@ -82,6 +87,7 @@ def set_servo_speed(value: int): else: raise ValueError("速度值超出限定范围") + def set_servo_acceleration(value: int): """ 设置舵机加速度 @@ -94,6 +100,7 @@ def set_servo_acceleration(value: int): else: raise ValueError("加速度值超出限定范围") + def set_servo_ori_position(ori_position: int): """ 设置舵机原点位 @@ -103,6 +110,7 @@ def set_servo_ori_position(ori_position: int): POS_START = _degree_to_raw(ori_position) print(f"舵机原点位置已设置为:{ori_position}度(对应原始值:{POS_START})") + def set_servo_rot_position(rot_position: int): """ 设置舵机翻转位置 @@ -112,6 +120,7 @@ def set_servo_rot_position(rot_position: int): POS_END = _degree_to_raw(rot_position) print(f"舵机翻转位置已设置为:{rot_position}度(对应原始值:{POS_END})") + def move_to_rot_position(): """舵机旋转到翻转位置""" try: @@ -123,6 +132,7 @@ def move_to_rot_position(): except Exception as e: raise RuntimeError(f"舵机移动到翻转位置失败:{str(e)}") from e + def move_to_ori_position(): """舵机旋转到原点""" try: @@ -134,6 +144,7 @@ def move_to_ori_position(): except Exception as e: raise RuntimeError(f"舵机移动到原点位置失败:{str(e)}") from e + # ----------调试接口---------- if __name__ == '__main__': set_servo_speed(1500) @@ -143,9 +154,9 @@ if __name__ == '__main__': move_to_rot_position() # 旋转180度 time.sleep(1) - move_to_ori_position() # 旋转0度 - time.sleep(1) + # move_to_ori_position() # 旋转0度 + # time.sleep(1) - while(True): + while True: time.sleep(1) diff --git a/servo/servo_test.py b/servo/servo_test.py index 2386b9e..583e9cf 100644 --- a/servo/servo_test.py +++ b/servo/servo_test.py @@ -11,7 +11,7 @@ import logging # -------------参数配置-------------- BAUDRATE = 115200 # 舵机的波特率 -PORT = 'COM4' +PORT = '/dev/ttyACM0 ' SERVO_IDS = [1, 2, 3, 4, 5] # 舵机们的 ID 号 POS_START = 2047 POS_END = 0 @@ -20,14 +20,15 @@ ACC = 0 TIME_INTERVAL1 = ((2047-0) / 1500) + 2 # 翻转回来的时间 TIME_INTERVAL2 = 10 # 不用翻转运行的时间 + class ServoController: def __init__(self, config=None): """ 初始化舵机控制器 """ self.config = config - self.time_interval1 = self.config['time_interval1'] - self.time_interval2 = self.config['time_interval2'] + # self.time_interval1 = self.config['time_interval1'] + # self.time_interval2 = self.config['time_interval2'] # 初始化串口和舵机处理器 self.port_handler = PortHandler(self.config['port']) @@ -130,14 +131,14 @@ class ServoController: self.config['speed'], self.config['acc']) print("运动到180度") - time.sleep(self.time_interval1) + time.sleep(2) # 运动到结束位置(0度) self.write_position(self.config['pos_end'], self.config['speed'], self.config['acc']) print("运动到0度") - time.sleep(self.time_interval2) + time.sleep(2) def run(self): """循环运行:复位位置之后,先转到180度,延时TIME_INTERVAL1一段时间,再转到0度"""