电脑主机端USB-can模块控制OmniPicker夹爪开合

一、设备:

1.维特智能USB-CAN模块

维特USB-CAN产品手册

2.智元灵犀X1 OmniPicker

智元灵犀X1 OmniPicker产品手册

二、调试软件

智元 上位机软件:REF-CLI v1.0.3.exe

维特智能 上位机软件: CanMiniIMU.exe

开源:XCOM 串口调试助手

注:上述软件 网页与产品手册有下载地址。

三、连接方式

image-20250906102554017

注:抓手是6p(6线) 只需接一对输入VIN 与GND,多余的VIN与GND无需接线。

OmniPicker夹爪有多个版本 ,线序不一致,注意以实际线序为主

四、can报文

image-20250906103224960

  • PC端直接夹爪USB口
    • 只能用官方的调试软件去使用
    • 无法二次开发
    • 目前只能win
  • PC端通过USB-CAN模块连接夹爪6P接口
    • 通过模块的发送CAN标准帧驱动夹爪
    • 把模块当串口使用–模块提供AT接口指令
    • 可以不用系统供其使用

五:调试方法

1、打开REF-CLI v1.0.3.exe 查看属性 -

  • 确保fw_version = 330 (int) 或者要大于330
  • can_node_id = 8 (int) 要知道数字 或者改为8最好 (智元的手册有更改方法)

image-20250906104508630

2、打开维特智能的上位软件 - 选择对应的 串口 - 波特率为115200

image-20250906104143733

3、AT指令模式 - 标准帧 -发送对应内容 - 通过can驱动夹爪

image-20250906104859592

帧ID 数据 常用
8 00 FF FF FF FF FF 00 00 全开
8 00 7F FF FF FF FF 00 00 半开
8 00 00 FF FF FF FF 00 00 关闭

注:数据详细要看智元的产品手册

4、查看AT命令(留给第5步使用)

image-20250906105433290

5、打开XCOM - 选择串口 -115200 - 停止位1 - 数据位8 - None - 打开串口

  • 以标准发送 AT+AT 等待返回OK就是 进入 AT模式指令

image-20250906105959411

  • 再以16进制发送CAN报文 - 即可完成发送can帧 - 在上一步方式 - AT命令记录看到的数据

image-20250906110503689

注:如你已经连接夹爪,夹爪已上电的话,夹爪会动,成功发送-是会有返回消息的

六、linux的驱动代码

在ubuntu上使用这个模块时,尝试使用多种调试助手,均无成功,决定直接使用代码驱动模块,向模块发送AT指令

import serial
import time
import subprocess
import sys
import signal
import binascii
import os

def release_serial_port(port):
"""尝试释放被占用的串口"""
try:
print(f"尝试释放被占用的串口: {port}")

# 使用 fuser 终止占用进程
result = subprocess.run(['sudo', 'fuser', '-k', port], capture_output=True, text=True)
if result.returncode == 0:
print("成功终止占用进程")
time.sleep(1) # 等待进程终止
return True

# 如果 fuser 不可用,使用 pkill
result = subprocess.run(['sudo', 'pkill', '-f', port], capture_output=True, text=True)
if result.returncode == 0:
print("成功终止占用进程")
time.sleep(1)
return True

print("无法终止占用进程,尝试直接访问")
return False

except Exception as e:
print(f"释放串口失败: {e}")
return False

def send_at_command(port, command, timeout=1.0, max_retries=3):
"""发送 AT 命令并返回响应(带重试机制)"""
for attempt in range(max_retries):
try:
# 创建串口连接
with serial.Serial(
port=port,
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=timeout
) as ser:
# 清空缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()

# 发送命令
full_command = command + '\r\n'
ser.write(full_command.encode('ascii'))
print(f"尝试 #{attempt+1}: 已发送 {command}")

# 等待响应
time.sleep(0.1)

# 读取返回数据
response = ser.read_all().decode('ascii', errors='ignore')

if response:
return response
else:
print(f"尝试 #{attempt+1}: 无响应")

except serial.SerialException as e:
if "Device or resource busy" in str(e):
print(f"尝试 #{attempt+1}: 设备忙,尝试释放")
release_serial_port(port)
else:
print(f"尝试 #{attempt+1}: 串口错误 - {e}")
except Exception as e:
print(f"尝试 #{attempt+1}: 未知错误 - {e}")

# 重试前等待
time.sleep(1)

return "所有尝试失败,无响应"

def send_hex_data(port, hex_data, timeout=1.0, max_retries=3):
"""发送原始十六进制数据并返回响应"""
# 将十六进制字符串转换为字节数组
try:
# 移除空格
hex_data = hex_data.replace(" ", "")
# 转换为字节数组
data_bytes = bytes.fromhex(hex_data)
except ValueError as e:
return f"无效的十六进制数据: {e}"

for attempt in range(max_retries):
try:
# 创建串口连接
with serial.Serial(
port=port,
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=timeout
) as ser:
# 清空缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()

# 发送原始字节数据
ser.write(data_bytes)
print(f"尝试 #{attempt+1}: 已发送原始数据: {hex_data}")

# 等待响应
time.sleep(0.1)

# 读取返回数据
response = ser.read_all()

if response:
# 同时显示十六进制和ASCII格式
hex_response = response.hex().upper()
ascii_response = ''.join([chr(b) if 32 <= b <= 126 else '.' for b in response])

return f"十六进制响应: {hex_response}\nASCII响应: {ascii_response}"
else:
print(f"尝试 #{attempt+1}: 无响应")

except serial.SerialException as e:
if "Device or resource busy" in str(e):
print(f"尝试 #{attempt+1}: 设备忙,尝试释放")
release_serial_port(port)
else:
print(f"尝试 #{attempt+1}: 串口错误 - {e}")
except Exception as e:
print(f"尝试 #{attempt+1}: 未知错误 - {e}")

# 重试前等待
time.sleep(1)

return "所有尝试失败,无响应"

def main():
# 默认设备
port = "/dev/ttyUSB0"

print(f"开始与 {port} 通信 (115200 bps)")

# 发送 AT+AT 命令
print("\n=== 发送 AT+AT 命令 ===")
response = send_at_command(port, "AT+AT", timeout=1.5)
print("\n响应:")
print(response)

# 发送原始十六进制数据
hex_data = "41 54 01 00 00 00 08 00 00 FF FF FF FF 00 00 0D 0A"
print(f"\n=== 发送原始十六进制数据: {hex_data} ===")
response = send_hex_data(port, hex_data)
print(f"\n响应:\n{response}")
time.sleep(1)

hex_data = "41 54 01 00 00 00 08 00 7F FF FF FF FF 00 00 0D 0A"
print(f"\n=== 发送原始十六进制数据: {hex_data} ===")
response = send_hex_data(port, hex_data)
print(f"\n响应:\n{response}")
time.sleep(1)

hex_data = "41 54 01 00 00 00 08 00 FF FF FF FF FF 00 00 0D 0A"
print(f"\n=== 发送原始十六进制数据: {hex_data} ===")
response = send_hex_data(port, hex_data)
print(f"\n响应:\n{response}")
time.sleep(1)

def signal_handler(sig, frame):
print("\n程序已终止")
sys.exit(0)

if __name__ == "__main__":
# 设置Ctrl+C信号处理 - 修复了Signal错误
signal.signal(signal.SIGINT, signal_handler)

try:
# 需要sudo权限来终止占用进程
if os.geteuid() != 0:
print("请使用sudo运行此程序")
sys.exit(1)

main()
except Exception as e:
print(f"程序异常终止: {e}")import serial
import time
import subprocess
import sys
import signal
import binascii
import os

def release_serial_port(port):
"""尝试释放被占用的串口"""
try:
print(f"尝试释放被占用的串口: {port}")

# 使用 fuser 终止占用进程
result = subprocess.run(['sudo', 'fuser', '-k', port], capture_output=True, text=True)
if result.returncode == 0:
print("成功终止占用进程")
time.sleep(1) # 等待进程终止
return True

# 如果 fuser 不可用,使用 pkill
result = subprocess.run(['sudo', 'pkill', '-f', port], capture_output=True, text=True)
if result.returncode == 0:
print("成功终止占用进程")
time.sleep(1)
return True

print("无法终止占用进程,尝试直接访问")
return False

except Exception as e:
print(f"释放串口失败: {e}")
return False

def send_at_command(port, command, timeout=1.0, max_retries=3):
"""发送 AT 命令并返回响应(带重试机制)"""
for attempt in range(max_retries):
try:
# 创建串口连接
with serial.Serial(
port=port,
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=timeout
) as ser:
# 清空缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()

# 发送命令
full_command = command + '\r\n'
ser.write(full_command.encode('ascii'))
print(f"尝试 #{attempt+1}: 已发送 {command}")

# 等待响应
time.sleep(0.1)

# 读取返回数据
response = ser.read_all().decode('ascii', errors='ignore')

if response:
return response
else:
print(f"尝试 #{attempt+1}: 无响应")

except serial.SerialException as e:
if "Device or resource busy" in str(e):
print(f"尝试 #{attempt+1}: 设备忙,尝试释放")
release_serial_port(port)
else:
print(f"尝试 #{attempt+1}: 串口错误 - {e}")
except Exception as e:
print(f"尝试 #{attempt+1}: 未知错误 - {e}")

# 重试前等待
time.sleep(1)

return "所有尝试失败,无响应"

def send_hex_data(port, hex_data, timeout=1.0, max_retries=3):
"""发送原始十六进制数据并返回响应"""
# 将十六进制字符串转换为字节数组
try:
# 移除空格
hex_data = hex_data.replace(" ", "")
# 转换为字节数组
data_bytes = bytes.fromhex(hex_data)
except ValueError as e:
return f"无效的十六进制数据: {e}"

for attempt in range(max_retries):
try:
# 创建串口连接
with serial.Serial(
port=port,
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=timeout
) as ser:
# 清空缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()

# 发送原始字节数据
ser.write(data_bytes)
print(f"尝试 #{attempt+1}: 已发送原始数据: {hex_data}")

# 等待响应
time.sleep(0.1)

# 读取返回数据
response = ser.read_all()

if response:
# 同时显示十六进制和ASCII格式
hex_response = response.hex().upper()
ascii_response = ''.join([chr(b) if 32 <= b <= 126 else '.' for b in response])

return f"十六进制响应: {hex_response}\nASCII响应: {ascii_response}"
else:
print(f"尝试 #{attempt+1}: 无响应")

except serial.SerialException as e:
if "Device or resource busy" in str(e):
print(f"尝试 #{attempt+1}: 设备忙,尝试释放")
release_serial_port(port)
else:
print(f"尝试 #{attempt+1}: 串口错误 - {e}")
except Exception as e:
print(f"尝试 #{attempt+1}: 未知错误 - {e}")

# 重试前等待
time.sleep(1)

return "所有尝试失败,无响应"

def main():
# 默认设备
port = "/dev/ttyUSB0"

print(f"开始与 {port} 通信 (115200 bps)")

# 发送 AT+AT 命令
print("\n=== 发送 AT+AT 命令 ===")
response = send_at_command(port, "AT+AT", timeout=1.5)
print("\n响应:")
print(response)

# 发送原始十六进制数据
hex_data = "41 54 01 00 00 00 08 00 00 FF FF FF FF 00 00 0D 0A"
print(f"\n=== 发送原始十六进制数据: {hex_data} ===")
response = send_hex_data(port, hex_data)
print(f"\n响应:\n{response}")
time.sleep(1)

hex_data = "41 54 01 00 00 00 08 00 7F FF FF FF FF 00 00 0D 0A"
print(f"\n=== 发送原始十六进制数据: {hex_data} ===")
response = send_hex_data(port, hex_data)
print(f"\n响应:\n{response}")
time.sleep(1)

hex_data = "41 54 01 00 00 00 08 00 FF FF FF FF FF 00 00 0D 0A"
print(f"\n=== 发送原始十六进制数据: {hex_data} ===")
response = send_hex_data(port, hex_data)
print(f"\n响应:\n{response}")
time.sleep(1)

def signal_handler(sig, frame):
print("\n程序已终止")
sys.exit(0)

if __name__ == "__main__":
# 设置Ctrl+C信号处理 - 修复了Signal错误
signal.signal(signal.SIGINT, signal_handler)

try:
# 需要sudo权限来终止占用进程
if os.geteuid() != 0:
print("请使用sudo运行此程序")
sys.exit(1)

main()
except Exception as e:
print(f"程序异常终止: {e}")

注:启动程序 。需要先给串口模块设备权限,再需要管理员权限启动程序