cmvr-biohead/text_dgceshi.py

199 lines
5.4 KiB
Python
Raw Normal View History

2025-08-21 13:49:41 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
interactive_servo_test.py 基于 Python ESP32 舵机控制交互脚本
依赖pip install pyserial
用法
python3 interactive_servo_test.py /dev/ttyUSB0 [baudrate]
功能
1. 控制单个舵机
2. 控制多个舵机
3. 退出
4. 自动增减模式 0 180° 每步 +6° -6° 循环50Hz 频率发送
5. 设置自动模式舵机数量通道 1~N
"""
import sys
import time
import serial
FRAME_HEADER = 0xAA # 帧头
def wakeup_esp32(port, baud=115200):
"""通过 RTS/DTR 线复位并唤醒 ESP32"""
try:
ser = serial.Serial(port, baud, timeout=1)
ser.setDTR(False)
ser.setRTS(False)
time.sleep(0.01)
ser.setDTR(True)
ser.setRTS(True)
ser.write(b"\r\n")
time.sleep(1)
except Exception as e:
print(f"ESP32 唤醒失败: {e}")
finally:
try:
ser.close()
except:
pass
def open_serial(port, baud):
"""打开并配置串口"""
try:
ser = serial.Serial(port, baud, timeout=1)
print(f"已打开串口: {port} @ {baud}bps")
return ser
except Exception as e:
print(f"打开串口失败: {e}")
sys.exit(1)
def calc_checksum(data: bytearray) -> int:
"""计算 XOR 校验和"""
cs = 0
for b in data:
cs ^= b
return cs
def pack_commands(cmds):
"""
cmds: list of (addr, channel, angle, duration_ms)
返回完整帧字节数组
格式 [FRAME_HEADER][count] [addr, ch, ang, durL, durH]... [checksum]
"""
pkt = bytearray([FRAME_HEADER, len(cmds)])
for addr, ch, ang, dur in cmds:
pkt.extend([addr, ch, ang, dur & 0xFF, (dur >> 8) & 0xFF])
pkt.append(calc_checksum(pkt))
return pkt
def print_packet(pkt):
"""打印十六进制数据包"""
print("发送数据包:", ' '.join(f"0x{b:02X}" for b in pkt))
def input_hex(prompt):
"""输入 0x 开头或十进制整型"""
val = input(prompt).strip()
try:
return int(val, 0)
except ValueError:
print(f"无效数字: {val}")
return input_hex(prompt)
def input_cmd_single():
"""交互输入单条命令"""
addr = input_hex("输入 addr (hex or dec): ")
ch = input_hex("输入 channel (hex or dec): ")
ang = input_hex("输入 angle (0-180): ")
dur = input_hex("输入 duration(ms): ")
return [(addr, ch, ang, dur)]
def input_cmd_multiple():
"""交互输入多条命令"""
cnt = input_hex("输入命令数量: ")
cmds = []
for i in range(cnt):
print(f"{i+1} 条:")
cmds.extend(input_cmd_single())
return cmds
def auto_mode(ser, channels):
"""自动增减模式0→180→0步长650Hz多舵机"""
addr = input_hex("自动模式: 输入 addr (hex or dec): ")
interval = 1.0 / 50.0
dur_ms = int(interval * 1000)
angle = 0
direction = 1
step = 6
print(f"启动自动模式 @50Hz, addr=0x{addr:02X}, channels={channels}, step={step}°")
try:
while True:
cmds = [(addr, ch, angle, dur_ms) for ch in channels]
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt)
# 更新角度
angle += direction * step
if angle >= 180:
angle = 180
direction = -1
elif angle <= 0:
angle = 0
direction = 1
time.sleep(interval)
except KeyboardInterrupt:
print("\n自动模式已停止,返回菜单")
def set_channels():
"""自定义舵机数量,返回通道列表 1~N"""
cnt = input_hex("输入自动模式舵机数量 N: ")
if cnt < 1:
print("数量必须 >= 1")
return None
channels = list(range(1, cnt+1))
print(f"已设置通道列表: {channels}")
return channels
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} /dev/ttyUSB0 [baudrate]")
sys.exit(1)
port = sys.argv[1]
baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200
print("唤醒 ESP32...")
wakeup_esp32(port, baud)
ser = open_serial(port, baud)
channels = list(range(1, 10)) # 默认通道 1-9
try:
while True:
print("\n=== 操作菜单 ===")
print("1. 控制单个舵机")
print("2. 控制多个舵机")
print("3. 退出")
print("4. 自动增减模式(50Hz)")
print("5. 设置自动模式舵机数量")
opt = input("选择: ").strip()
if opt == '1':
cmds = input_cmd_single()
elif opt == '2':
cmds = input_cmd_multiple()
elif opt == '4':
auto_mode(ser, channels)
continue
elif opt == '5':
new_ch = set_channels()
if new_ch:
channels = new_ch
continue
elif opt == '3':
break
else:
print("无效选项,重试")
continue
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt)
print("✅ 指令已发送")
except KeyboardInterrupt:
print("\n用户中断,退出")
finally:
ser.close()
print("串口已关闭")
if __name__ == '__main__':
main()