【AIMB-2210】AI定位小车
【前言】在前面我使用AI获取了小车的状态信息,这一篇我将使用定位模块来定位小车的位置。
【硬件】
1、【AIMB-2210】NPU主机
2、智能小车底盘
3、室内UWB定位模块
【软件】
1、vscode + AI代码生成
2、python11
【实现原理】
1、我使用三个UWB定位模块。其中0与1为固定位置间距1米,2装置于小车位置。
2、0与1模块每5毫秒读取一次与小车的距离通过串口回传给AIMB-2210主机,主机通过超高速的计算,计算出坐标。
3、在GUI界面动态显示小车的位置:
【实验成果】
!(https://www.eefocus.com/forum/data/attachment/forum/202510/28/203624z3e3x6b8uj2298u3.png)
【实验心得】
【AIMB-2210】主板拥有4个COM接口,我们可以通过他USB转串口连接多路设备,通过AI编实,超快速实现小车定位。
【附源码】
```
import serial
import time
import struct
import tkinter as tk
from tkinter import ttk, messagebox
import serial.tools.list_ports
import threading
import math
import queue
import collections
def calculate_checksum(data):
"""
计算校验和:前面数据累加和的低8位
"""
return sum(data) & 0xFF
def parse_x_protocol_frame(frame):
"""
解析X-Protocol帧
Args:
frame: 完整的帧数据
Returns:
dict: 解析出的数据字典
"""
if len(frame) < 5:
return None
# 检查帧头
if frame != 0xAA or frame != 0x55:
return None
frame_len = frame
if len(frame) < frame_len:
return None
# 验证校验和
checksum = frame
calculated_checksum = calculate_checksum(frame[:-1])
if checksum != calculated_checksum:
return None
frame_code = frame
data_bytes = frame
# 根据帧码解析数据
result = {"frame_code": frame_code}
# 处理您实际使用的帧格式 (帧码 0x10)
if frame_code == 0x10:
# 数据部分有21字节,按照常见的传感器数据格式解析
# 前6字节是加速度数据 (3个16位整数)
try:
if len(data_bytes) >= 2:
result["acc_x"] = struct.unpack('>h', data_bytes)
if len(data_bytes) >= 4:
result["acc_y"] = struct.unpack('>h', data_bytes)
if len(data_bytes) >= 6:
result["acc_z"] = struct.unpack('>h', data_bytes)
# 接下来6字节是陀螺仪数据 (3个16位整数)
if len(data_bytes) >= 8:
result["gyro_x"] = struct.unpack('>h', data_bytes)
if len(data_bytes) >= 10:
result["gyro_y"] = struct.unpack('>h', data_bytes)
if len(data_bytes) >= 12:
result["gyro_z"] = struct.unpack('>h', data_bytes)
# 接下来4字节是速度数据 (2个16位整数)
if len(data_bytes) >= 14:
result["vel_x"] = struct.unpack('>h', data_bytes)
if len(data_bytes) >= 16:
result["vel_y"] = struct.unpack('>h', data_bytes)
# 接下来2字节是角速度W
if len(data_bytes) >= 18:
result["vel_w"] = struct.unpack('>h', data_bytes)
# 最后几个字节中取电压数据
if len(data_bytes) >= 20:
result["voltage"] = struct.unpack('>h', data_bytes)
except Exception as e:
print(f"数据解析错误: {e}")
# 保留原有的解析逻辑以兼容其他帧格式
elif frame_code == 0x01:
if len(data_bytes) >= 6:
result["acc_x"] = struct.unpack('>h', data_bytes)
result["acc_y"] = struct.unpack('>h', data_bytes)
result["acc_z"] = struct.unpack('>h', data_bytes)
elif frame_code == 0x02:
if len(data_bytes) >= 6:
result["gyro_x"] = struct.unpack('>h', data_bytes)
result["gyro_y"] = struct.unpack('>h', data_bytes)
result["gyro_z"] = struct.unpack('>h', data_bytes)
elif frame_code == 0x03:
if len(data_bytes) >= 6:
result["vel_x"] = struct.unpack('>h', data_bytes)
result["vel_y"] = struct.unpack('>h', data_bytes)
result["vel_w"] = struct.unpack('>h', data_bytes)
elif frame_code == 0x04:
if len(data_bytes) >= 2:
result["voltage"] = struct.unpack('>h', data_bytes)
return result
def send_x_protocol_frame(ser, frame_code, data):
"""
发送X-Protocol帧
Args:
ser: 串口对象
frame_code: 帧码
data: 数据字节串
"""
# 构造帧(不包括校验和)
frame = bytearray()
frame.append(0xAA)# 帧头1
frame.append(0x55)# 帧头2
frame.append(5 + len(data))# 帧长
frame.append(frame_code)# 帧码
frame.extend(data)# 数据
# 计算校验和(从第0位开始计算整个帧)
checksum = calculate_checksum(frame)
frame.append(checksum)# 校验和
# 发送帧
ser.write(frame)
print(f"发送帧: {' '.join(f'{b:02X}' for b in frame)}")
return len(frame)
def send_values(ser, frame_code, value1, value2, value3):
"""
发送三个16位整数
Args:
ser: 串口对象
frame_code: 帧码
value1, value2, value3: 三个16位整数
"""
# 构造6字节的有效数据
data = bytearray()
data.extend(struct.pack('>h', value1))# 第一个16位整数
data.extend(struct.pack('>h', value2))# 第二个16位整数
data.extend(struct.pack('>h', value3))# 第三个16位整数
# 发送帧
bytes_sent = send_x_protocol_frame(ser, frame_code, data)
print(f"发送数据: {value1}, {value2}, {value3}")
return bytes_sent
class MovingAverageFilter:
"""移动平均滤波器"""
def __init__(self, window_size=5):
self.window_size = window_size
self.values = collections.deque(maxlen=window_size)
def add_value(self, value):
self.values.append(value)
return self.get_filtered_value()
def get_filtered_value(self):
if not self.values:
return 0
return sum(self.values) / len(self.values)
class XProtocolGUI:
def __init__(self, root):
self.root = root
self.root.title("X-Protocol 串口发送工具")
self.root.geometry("800x600")
self.ser = None
self.ser_com3 = None # COM3串口 (距离0->2)
self.ser_com33 = None # COM33串口 (距离1->2)
self.receiving_thread = None
self.com3_receiving_thread = None
self.com33_receiving_thread = None
self.running = False
self.com3_running = False
self.com33_running = False
self.distance_0_2 = 0 # 点0到点2的距离 (cm)
self.distance_1_2 = 0 # 点1到点2的距离 (cm)
self.point2_x = 0 # 点2的x坐标
self.point2_y = 0 # 点2的y坐标
# 滤波器
self.filter_0_2 = MovingAverageFilter(window_size=5)
self.filter_1_2 = MovingAverageFilter(window_size=5)
# 数据队列
self.com3_data_queue = queue.Queue()
self.com33_data_queue = queue.Queue()
self.create_widgets()
self.refresh_ports()
# 启动数据处理线程
self.start_data_processing()
def create_widgets(self):
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 创建左右布局的PanedWindow
paned_window = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
paned_window.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S))
# 左侧控制面板
left_frame = ttk.Frame(paned_window)
paned_window.add(left_frame, weight=1)
# 右侧面板用于显示点位置
right_frame = ttk.Frame(paned_window)
paned_window.add(right_frame, weight=2)
# 串口选择(在左侧)
ttk.Label(left_frame, text="主串口:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.port_var = tk.StringVar()
self.port_combo = ttk.Combobox(left_frame, textvariable=self.port_var, width=15)
self.port_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
# 刷新按钮
refresh_btn = ttk.Button(left_frame, text="刷新", command=self.refresh_ports)
refresh_btn.grid(row=0, column=2, padx=(10,0), pady=5)
# 连接按钮
self.connect_btn = ttk.Button(left_frame, text="连接", command=self.toggle_connection)
self.connect_btn.grid(row=0, column=3, padx=(10,0), pady=5)
# 双串口连接部分
ttk.Separator(left_frame, orient='horizontal').grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)
ttk.Label(left_frame, text="双串口测距系统:").grid(row=2, column=0, columnspan=4, sticky=tk.W, pady=5)
# COM3串口连接
ttk.Label(left_frame, text="COM3 (距离0→2):").grid(row=3, column=0, sticky=tk.W, pady=5)
self.com3_btn = ttk.Button(left_frame, text="连接COM3", command=self.toggle_com3_connection)
self.com3_btn.grid(row=3, column=1, pady=5)
# COM33串口连接
ttk.Label(left_frame, text="COM33 (距离1→2):").grid(row=4, column=0, sticky=tk.W, pady=5)
self.com33_btn = ttk.Button(left_frame, text="连接COM33", command=self.toggle_com33_connection)
self.com33_btn.grid(row=4, column=1, pady=5)
# 显示距离信息
ttk.Label(left_frame, text="距离信息:").grid(row=5, column=0, columnspan=2, sticky=tk.W, pady=(10,5))
self.distance_0_2_var = tk.StringVar(value="距离 0→2: --- cm")
ttk.Label(left_frame, textvariable=self.distance_0_2_var).grid(row=6, column=0, columnspan=2, sticky=tk.W)
self.distance_1_2_var = tk.StringVar(value="距离 1→2: --- cm")
ttk.Label(left_frame, textvariable=self.distance_1_2_var).grid(row=7, column=0, columnspan=2, sticky=tk.W)
self.coord_2_var = tk.StringVar(value="点2坐标: (---, ---)")
ttk.Label(left_frame, textvariable=self.coord_2_var).grid(row=8, column=0, columnspan=2, sticky=tk.W)
# 分隔线
separator = ttk.Separator(left_frame, orient='horizontal')
separator.grid(row=9, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)
# 原有数据输入部分
ttk.Label(left_frame, text="数据发送:").grid(row=10, column=0, columnspan=4, sticky=tk.W, pady=5)
ttk.Label(left_frame, text="数据1:").grid(row=11, column=0, sticky=tk.W, pady=5)
self.value1_var = tk.StringVar(value="100")
ttk.Entry(left_frame, textvariable=self.value1_var, width=10).grid(row=11, column=1, sticky=tk.W, pady=5)
ttk.Label(left_frame, text="数据2:").grid(row=12, column=0, sticky=tk.W, pady=5)
self.value2_var = tk.StringVar(value="0")
ttk.Entry(left_frame, textvariable=self.value2_var, width=10).grid(row=12, column=1, sticky=tk.W, pady=5)
ttk.Label(left_frame, text="数据3:").grid(row=13, column=0, sticky=tk.W, pady=5)
self.value3_var = tk.StringVar(value="0")
ttk.Entry(left_frame, textvariable=self.value3_var, width=10).grid(row=13, column=1, sticky=tk.W, pady=5)
# 帧码输入
ttk.Label(left_frame, text="帧码:").grid(row=14, column=0, sticky=tk.W, pady=5)
self.frame_code_var = tk.StringVar(value="50")
ttk.Entry(left_frame, textvariable=self.frame_code_var, width=10).grid(row=14, column=1, sticky=tk.W, pady=5)
# 发送按钮
self.send_btn = ttk.Button(left_frame, text="发送数据", command=self.send_data, state=tk.DISABLED)
self.send_btn.grid(row=15, column=0, columnspan=2, pady=20)
# 显示区域
display_frame = ttk.LabelFrame(left_frame, text="接收到的数据", padding="10")
display_frame.grid(row=16, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)
# 创建多个标签用于显示数据
labels = [
("帧码:", "frame_code"),
("加速度X:", "acc_x"),
("加速度Y:", "acc_y"),
("加速度Z:", "acc_z"),
("陀螺仪X:", "gyro_x"),
("陀螺仪Y:", "gyro_y"),
("陀螺仪Z:", "gyro_z"),
("速度X:", "vel_x"),
("速度Y:", "vel_y"),
("速度W:", "vel_w"),
("电池电压:", "voltage"),
]
self.display_vars = {}
for i, (label_text, key) in enumerate(labels):
ttk.Label(display_frame, text=label_text).grid(row=i, column=0, sticky=tk.W)
var = tk.StringVar(value="---")
self.display_vars = var
ttk.Label(display_frame, textvariable=var, width=15).grid(row=i, column=1, sticky=tk.W)
# 在右侧面板添加点位置显示
points_frame = ttk.LabelFrame(right_frame, text="三角定位显示", padding="10")
points_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 创建Canvas用于显示三个点
self.points_canvas = tk.Canvas(points_frame, width=500, height=400, bg='white')
self.points_canvas.pack(fill=tk.BOTH, expand=True)
# 添加标签说明坐标系
ttk.Label(points_frame, text="坐标参考: 比例 10:1 (1cm = 0.1像素), 原点在左下角").pack()
# 初始化点的显示
self.draw_triangle_points()
# 状态栏(放在底部)
self.status_var = tk.StringVar(value="请选择串口并连接")
self.status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN)
self.status_bar.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(10,0))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(0, weight=1)
left_frame.columnconfigure(1, weight=1)
def refresh_ports(self):
"""刷新可用串口列表"""
ports =
self.port_combo['values'] = ports
if ports:
self.port_combo.set(ports)
def toggle_connection(self):
"""切换主串口连接状态"""
if self.ser is None:
self.connect_serial()
else:
self.disconnect_serial()
def connect_serial(self):
"""连接主串口"""
try:
port = self.port_var.get()
if not port:
messagebox.showerror("错误", "请选择串口")
return
self.ser = serial.Serial(port, 230400, timeout=1)
self.running = True
self.connect_btn.config(text="断开")
self.send_btn.config(state=tk.NORMAL)
self.status_var.set(f"已连接到 {port}")
print(f"串口 {port} 打开成功")
# 启动接收线程
self.start_receiving()
except Exception as e:
messagebox.showerror("串口错误", f"无法打开串口: {str(e)}")
self.ser = None
def disconnect_serial(self):
"""断开主串口连接"""
self.running = False
if self.ser:
self.ser.close()
self.ser = None
self.connect_btn.config(text="连接")
self.send_btn.config(state=tk.DISABLED)
self.status_var.set("已断开连接")
def toggle_com3_connection(self):
"""切换COM3串口连接状态"""
if self.ser_com3 is None:
self.connect_com3_serial()
else:
self.disconnect_com3_serial()
def toggle_com33_connection(self):
"""切换COM33串口连接状态"""
if self.ser_com33 is None:
self.connect_com33_serial()
else:
self.disconnect_com33_serial()
def connect_com3_serial(self):
"""连接COM3串口"""
try:
self.ser_com3 = serial.Serial('COM3', 115200, timeout=1)
self.com3_running = True
self.com3_btn.config(text="断开COM3")
self.status_var.set("已连接到COM3")
print("串口 COM3 打开成功")
# 启动COM3接收线程
self.com3_receiving_thread = threading.Thread(target=self.receive_com3_data, daemon=True)
self.com3_receiving_thread.start()
except Exception as e:
messagebox.showerror("串口错误", f"无法打开COM3串口: {str(e)}")
self.ser_com3 = None
def connect_com33_serial(self):
"""连接COM33串口"""
try:
self.ser_com33 = serial.Serial('COM33', 115200, timeout=1)
self.com33_running = True
self.com33_btn.config(text="断开COM33")
self.status_var.set("已连接到COM33")
print("串口 COM33 打开成功")
# 启动COM33接收线程
self.com33_receiving_thread = threading.Thread(target=self.receive_com33_data, daemon=True)
self.com33_receiving_thread.start()
except Exception as e:
messagebox.showerror("串口错误", f"无法打开COM33串口: {str(e)}")
self.ser_com33 = None
def disconnect_com3_serial(self):
"""断开COM3串口连接"""
self.com3_running = False
if self.ser_com3:
self.ser_com3.close()
self.ser_com3 = None
self.com3_btn.config(text="连接COM3")
self.status_var.set("已断开COM3连接")
def disconnect_com33_serial(self):
"""断开COM33串口连接"""
self.com33_running = False
if self.ser_com33:
self.ser_com33.close()
self.ser_com33 = None
self.com33_btn.config(text="连接COM33")
self.status_var.set("已断开COM33连接")
def receive_com3_data(self):
"""接收COM3数据 (距离0->2)"""
while self.com3_running and self.ser_com3 and self.ser_com3.is_open:
try:
# 读取数据
data = self.ser_com3.read(8)# 读取8字节数据
if len(data) == 8:
# 检查帧头
if data == 0xF0 and data == 0x05 and data == 0xAA:
# 解析距离 (字节4-5)
distance = (data << 8) | data# 高低位组合
# 将数据放入队列
self.com3_data_queue.put(distance)
print(f"COM3接收到原始距离数据: {distance} cm")
except Exception as e:
print(f"COM3接收异常: {e}")
if not self.com3_running:
break
def receive_com33_data(self):
"""接收COM33数据 (距离1->2)"""
while self.com33_running and self.ser_com33 and self.ser_com33.is_open:
try:
# 读取数据
data = self.ser_com33.read(8)# 读取8字节数据
if len(data) == 8:
# 检查帧头
if data == 0xF0 and data == 0x05 and data == 0xAA:
# 解析距离 (字节4-5)
distance = (data << 8) | data# 高低位组合
# 将数据放入队列
self.com33_data_queue.put(distance)
print(f"COM33接收到原始距离数据: {distance} cm")
except Exception as e:
print(f"COM33接收异常: {e}")
if not self.com33_running:
break
def start_data_processing(self):
"""启动数据处理线程"""
processing_thread = threading.Thread(target=self.process_distance_data, daemon=True)
processing_thread.start()
def process_distance_data(self):
"""处理距离数据(滤波)"""
while True:
try:
# 处理COM3数据
while not self.com3_data_queue.empty():
raw_distance = self.com3_data_queue.get_nowait()
# 应用滤波
filtered_distance = self.filter_0_2.add_value(raw_distance)
self.distance_0_2 = filtered_distance
self.root.after(0, self.update_distance_0_2_display, filtered_distance)
self.root.after(0, self.calculate_point2_position)
print(f"COM3滤波后距离数据: {filtered_distance:.1f} cm")
# 处理COM33数据
while not self.com33_data_queue.empty():
raw_distance = self.com33_data_queue.get_nowait()
# 应用滤波
filtered_distance = self.filter_1_2.add_value(raw_distance)
self.distance_1_2 = filtered_distance
self.root.after(0, self.update_distance_1_2_display, filtered_distance)
self.root.after(0, self.calculate_point2_position)
print(f"COM33滤波后距离数据: {filtered_distance:.1f} cm")
# 短暂休眠以避免过度占用CPU
time.sleep(0.01)
except queue.Empty:
continue
except Exception as e:
print(f"数据处理异常: {e}")
def update_distance_0_2_display(self, distance):
"""更新COM3距离显示"""
self.distance_0_2_var.set(f"距离 0→2: {distance:.1f} cm")
def update_distance_1_2_display(self, distance):
"""更新COM33距离显示"""
self.distance_1_2_var.set(f"距离 1→2: {distance:.1f} cm")
# ... existing code ...
def calculate_point2_position(self):
"""根据距离计算点2的坐标"""
# 已知条件:
# 点0坐标: (0, 0)
# 点1坐标: (100, 0)# 修改为100cm
# 点2到点0的距离: self.distance_0_2
# 点2到点1的距离: self.distance_1_2
if self.distance_0_2 > 0 and self.distance_1_2 > 0:
try:
# 使用三角定位算法计算点2坐标
# 点0 (0,0) 和点1 (100,0) 之间的距离是100cm
d = 100# 点0和点1之间的距离修改为100cm
r1 = self.distance_0_2# 点2到点0的距离
r2 = self.distance_1_2# 点2到点1的距离
# 检查是否能形成三角形
if r1 + r2 >= d and abs(r1 - r2) <= d:
# 计算点2的x坐标
x = (r1*r1 - r2*r2 + d*d) / (2*d)
# 计算点2的y坐标
y_squared = r1*r1 - x*x
if y_squared >= 0:
y = math.sqrt(y_squared)
self.point2_x = x
self.point2_y = y
self.coord_2_var.set(f"点2坐标: ({x:.1f}, {y:.1f})")
self.draw_triangle_points()
print(f"计算得到点2坐标: ({x:.1f}, {y:.1f})")
else:
print("无法计算点2坐标: 数学错误")
else:
print("无法计算点2坐标: 距离数据无法构成三角形")
except Exception as e:
print(f"计算点2坐标时出错: {e}")
# ... existing code ...
def start_receiving(self):
"""启动接收线程"""
if self.ser is None:
return
self.receiving_thread = threading.Thread(target=self.receive_data, daemon=True)
self.receiving_thread.start()
def receive_data(self):
"""接收并解析数据"""
buffer = bytearray()
while self.running and self.ser and self.ser.is_open:
try:
# 逐字节读取
byte = self.ser.read(1)
if not byte:
continue
buffer.append(byte)
# 至少需要帧头3个字节才能判断帧长
if len(buffer) >= 3:
# 检查是否是帧头
if buffer == 0xAA and buffer == 0x55:
frame_len = buffer# 第三个字节是帧长度
# 当缓冲区长度达到帧长度时尝试解析
if len(buffer) >= frame_len and frame_len > 0:
# 尝试解析
parsed = parse_x_protocol_frame(buffer[:frame_len])
if parsed:
# 使用after方法确保在主线程更新UI
self.root.after(0, self.update_display, parsed)
buffer = buffer# 移除已处理部分
else:
buffer = buffer# 移动窗口
elif len(buffer) > 100:# 如果缓冲区太长且没有找到帧头,则清空
buffer = bytearray()
except Exception as e:
print(f"接收异常: {e}")
break
def update_display(self, data):
"""更新显示区域"""
# print(f"更新显示: {data}")
for key, var in self.display_vars.items():
if key in data:
# 对于帧码,显示为十六进制;其他数据显示为十进制
if key == "frame_code":
var.set(f"0x{data:02X}")
elif key == "voltage":
# 电池电压需要除以100
var.set(f"{data/100:.3f}")
elif key in ["vel_x", "vel_y", "vel_w"]:
# 速度值需要除以1000
var.set(f"{data/1000:.3f}")
else:
var.set(f"{data:.3f}")
else:
# 如果某个字段不存在,保持原值或设置为默认值
if var.get() == "---":
pass# 保持默认值
# ... existing code ...
def draw_triangle_points(self):
"""
在Canvas上绘制三角定位图
点0: (0, 0)
点1: (100, 0)# 修改为100cm
点2: 根据距离计算得出
"""
# 清除Canvas上的所有内容
self.points_canvas.delete("all")
# 获取Canvas尺寸
canvas_width = 500
canvas_height = 400
# 定义比例: 1cm = 0.1像素 (10:1比例)
scale = 0.1
# 坐标偏移,使图形居中显示
offset_x = 50
offset_y = 50
# 绘制参考线(坐标轴)
# Y轴(垂直线)
self.points_canvas.create_line(offset_x, 0, offset_x, canvas_height, fill="lightgray", dash=(2, 2))
# X轴(水平线,位于底部)
self.points_canvas.create_line(offset_x, canvas_height - offset_y, canvas_width, canvas_height - offset_y, fill="lightgray", dash=(2, 2))
# 绘制网格线
grid_spacing = 50# 500cm间隔
# 垂直网格线
for i in range(offset_x, canvas_width, grid_spacing):
self.points_canvas.create_line(i, 0, i, canvas_height - offset_y, fill="lightgray", dash=(2, 4))
# 水平网格线
for i in range(offset_y, canvas_height, grid_spacing):
self.points_canvas.create_line(offset_x, canvas_height - i, canvas_width, canvas_height - i, fill="lightgray", dash=(2, 4))
# 添加坐标标签 (X轴标签在底部)
for i in range(0, int((canvas_width - offset_x) / scale), 500):
x_pos = offset_x + i * scale
if x_pos < canvas_width:
self.points_canvas.create_text(x_pos, canvas_height - offset_y + 15, text=str(i), anchor=tk.N, fill="gray", font=("Arial", 8))
# Y轴标签在左侧
for i in range(0, int((canvas_height - offset_y) / scale), 500):
y_pos = canvas_height - offset_y - i * scale
if y_pos > 0:
self.points_canvas.create_text(offset_x - 10, y_pos, text=str(i), anchor=tk.E, fill="gray", font=("Arial", 8))
# 绘制三个点
# 点0: (0, 0) - 红色方块
point0_x = offset_x
point0_y = canvas_height - offset_y
self.points_canvas.create_rectangle(point0_x-5, point0_y-5, point0_x+5, point0_y+5, fill="red", outline="darkred", tags="point0")
self.points_canvas.create_text(point0_x, point0_y-15, text="点0 (0,0)", fill="red", font=("Arial", 9))
# 点1: (100, 0) - 蓝色方块 (100cm间距)
point1_x = offset_x + (100 * scale / 0.1)# 100cm按比例显示
point1_y = canvas_height - offset_y
self.points_canvas.create_rectangle(point1_x-5, point1_y-5, point1_x+5, point1_y+5, fill="blue", outline="darkblue", tags="point1")
self.points_canvas.create_text(point1_x, point1_y-15, text="点1 (100,0)", fill="blue", font=("Arial", 9))
# 点2: 根据计算结果绘制 - 绿色圆点
point2_x = offset_x + (self.point2_x * scale / 0.1)# 坐标按比例显示
point2_y = canvas_height - offset_y - (self.point2_y * scale / 0.1)# 坐标按比例显示
self.points_canvas.create_oval(point2_x-6, point2_y-6, point2_x+6, point2_y+6, fill="green", outline="darkgreen", tags="point2")
self.points_canvas.create_text(point2_x, point2_y-20, text=f"点2 ({self.point2_x:.1f},{self.point2_y:.1f})", fill="green", font=("Arial", 9))
# 绘制三角形边
self.points_canvas.create_line(point0_x, point0_y, point1_x, point1_y, fill="lightblue", width=1)# 边0-1
self.points_canvas.create_line(point0_x, point0_y, point2_x, point2_y, fill="lightcoral", width=1, dash=(4, 2))# 边0-2
self.points_canvas.create_line(point1_x, point1_y, point2_x, point2_y, fill="lightgreen", width=1, dash=(4, 2))# 边1-2
# 添加距离标注
# 标注边0-1的距离
mid_x = (point0_x + point1_x) / 2
mid_y = point0_y + 15
self.points_canvas.create_text(mid_x, mid_y, text="100cm", fill="blue", font=("Arial", 8))
# 标注边0-2的距离
if self.distance_0_2 > 0:
mid_x = (point0_x + point2_x) / 2
mid_y = (point0_y + point2_y) / 2 - 10
self.points_canvas.create_text(mid_x, mid_y, text=f"{self.distance_0_2:.1f}cm", fill="red", font=("Arial", 8))
# 标注边1-2的距离
if self.distance_1_2 > 0:
mid_x = (point1_x + point2_x) / 2
mid_y = (point1_y + point2_y) / 2 - 10
self.points_canvas.create_text(mid_x, mid_y, text=f"{self.distance_1_2:.1f}cm", fill="green", font=("Arial", 8))
# 添加标题
self.points_canvas.create_text(canvas_width/2, 20, text="三角定位显示 (比例 10:1)", fill="black", font=("Arial", 12, "bold"))
# ... existing code ...
# ... existing code ...
def draw_triangle_points(self):
"""
在Canvas上绘制三角定位图
点0: (0, 0)
点1: (50, 0)
点2: 根据距离计算得出
"""
# 清除Canvas上的所有内容
self.points_canvas.delete("all")
# 获取Canvas尺寸
canvas_width = 500
canvas_height = 400
# 定义比例: 1cm = 0.5像素 (缩小10倍)
scale = 0.5
# 坐标偏移,使图形居中显示
offset_x = 50
offset_y = 50
# 绘制参考线(坐标轴)
# Y轴(垂直线)
self.points_canvas.create_line(offset_x, 0, offset_x, canvas_height, fill="lightgray", dash=(2, 2))
# X轴(水平线,位于底部)
self.points_canvas.create_line(offset_x, canvas_height - offset_y, canvas_width, canvas_height - offset_y, fill="lightgray", dash=(2, 2))
# 绘制网格线
grid_spacing = 25# 50cm间隔
# 垂直网格线
for i in range(offset_x, canvas_width, grid_spacing):
self.points_canvas.create_line(i, 0, i, canvas_height - offset_y, fill="lightgray", dash=(2, 4))
# 水平网格线
for i in range(offset_y, canvas_height, grid_spacing):
self.points_canvas.create_line(offset_x, canvas_height - i, canvas_width, canvas_height - i, fill="lightgray", dash=(2, 4))
# 添加坐标标签 (X轴标签在底部)
for i in range(0, int((canvas_width - offset_x) / scale), 100):
x_pos = offset_x + i * scale
if x_pos < canvas_width:
self.points_canvas.create_text(x_pos, canvas_height - offset_y + 15, text=str(i), anchor=tk.N, fill="gray", font=("Arial", 8))
# Y轴标签在左侧
for i in range(0, int((canvas_height - offset_y) / scale), 100):
y_pos = canvas_height - offset_y - i * scale
if y_pos > 0:
self.points_canvas.create_text(offset_x - 10, y_pos, text=str(i), anchor=tk.E, fill="gray", font=("Arial", 8))
# 绘制三个点
# 点0: (0, 0) - 红色方块
point0_x = offset_x
point0_y = canvas_height - offset_y
self.points_canvas.create_rectangle(point0_x-5, point0_y-5, point0_x+5, point0_y+5, fill="red", outline="darkred", tags="point0")
self.points_canvas.create_text(point0_x, point0_y-15, text="点0 (0,0)", fill="red", font=("Arial", 9))
# 点1: (50, 0) - 蓝色方块
point1_x = offset_x + 50 * scale
point1_y = canvas_height - offset_y
self.points_canvas.create_rectangle(point1_x-5, point1_y-5, point1_x+5, point1_y+5, fill="blue", outline="darkblue", tags="point1")
self.points_canvas.create_text(point1_x, point1_y-15, text="点1 (50,0)", fill="blue", font=("Arial", 9))
# 点2: 根据计算结果绘制 - 绿色圆点
point2_x = offset_x + self.point2_x * scale
point2_y = canvas_height - offset_y - self.point2_y * scale
self.points_canvas.create_oval(point2_x-6, point2_y-6, point2_x+6, point2_y+6, fill="green", outline="darkgreen", tags="point2")
self.points_canvas.create_text(point2_x, point2_y-20, text=f"点2 ({self.point2_x:.1f},{self.point2_y:.1f})", fill="green", font=("Arial", 9))
# 绘制三角形边
self.points_canvas.create_line(point0_x, point0_y, point1_x, point1_y, fill="lightblue", width=1)# 边0-1
self.points_canvas.create_line(point0_x, point0_y, point2_x, point2_y, fill="lightcoral", width=1, dash=(4, 2))# 边0-2
self.points_canvas.create_line(point1_x, point1_y, point2_x, point2_y, fill="lightgreen", width=1, dash=(4, 2))# 边1-2
# 添加距离标注
# 标注边0-1的距离
mid_x = (point0_x + point1_x) / 2
mid_y = point0_y + 15
self.points_canvas.create_text(mid_x, mid_y, text="50cm", fill="blue", font=("Arial", 8))
# 标注边0-2的距离
if self.distance_0_2 > 0:
mid_x = (point0_x + point2_x) / 2
mid_y = (point0_y + point2_y) / 2 - 10
self.points_canvas.create_text(mid_x, mid_y, text=f"{self.distance_0_2:.1f}cm", fill="red", font=("Arial", 8))
# 标注边1-2的距离
if self.distance_1_2 > 0:
mid_x = (point1_x + point2_x) / 2
mid_y = (point1_y + point2_y) / 2 - 10
self.points_canvas.create_text(mid_x, mid_y, text=f"{self.distance_1_2:.1f}cm", fill="green", font=("Arial", 8))
# 添加标题
self.points_canvas.create_text(canvas_width/2, 20, text="三角定位显示", fill="black", font=("Arial", 12, "bold"))
# ... existing code ...
"""
在Canvas上绘制三角定位图
点0: (0, 0)
点1: (50, 0)
点2: 根据距离计算得出
"""
# 清除Canvas上的所有内容
self.points_canvas.delete("all")
# 获取Canvas尺寸
canvas_width = 500
canvas_height = 400
# 定义比例: 1cm = 5像素
scale = 5
# 坐标偏移,使图形居中显示
offset_x = 50
offset_y = 50
# 绘制参考线(坐标轴)
# Y轴(垂直线)
self.points_canvas.create_line(offset_x, 0, offset_x, canvas_height, fill="lightgray", dash=(2, 2))
# X轴(水平线,位于底部)
self.points_canvas.create_line(offset_x, canvas_height - offset_y, canvas_width, canvas_height - offset_y, fill="lightgray", dash=(2, 2))
# 绘制网格线
grid_spacing = 50# 10cm间隔
# 垂直网格线
for i in range(offset_x, canvas_width, grid_spacing):
self.points_canvas.create_line(i, 0, i, canvas_height - offset_y, fill="lightgray", dash=(2, 4))
# 水平网格线
for i in range(offset_y, canvas_height, grid_spacing):
self.points_canvas.create_line(offset_x, canvas_height - i, canvas_width, canvas_height - i, fill="lightgray", dash=(2, 4))
# 添加坐标标签 (X轴标签在底部)
for i in range(0, (canvas_width - offset_x) // scale, 10):
x_pos = offset_x + i * scale
if x_pos < canvas_width:
self.points_canvas.create_text(x_pos, canvas_height - offset_y + 15, text=str(i), anchor=tk.N, fill="gray", font=("Arial", 8))
# Y轴标签在左侧
for i in range(0, (canvas_height - offset_y) // scale, 10):
y_pos = canvas_height - offset_y - i * scale
if y_pos > 0:
self.points_canvas.create_text(offset_x - 10, y_pos, text=str(i), anchor=tk.E, fill="gray", font=("Arial", 8))
# 绘制三个点
# 点0: (0, 0) - 红色方块
point0_x = offset_x
point0_y = canvas_height - offset_y
self.points_canvas.create_rectangle(point0_x-5, point0_y-5, point0_x+5, point0_y+5, fill="red", outline="darkred", tags="point0")
self.points_canvas.create_text(point0_x, point0_y-15, text="点0 (0,0)", fill="red", font=("Arial", 9))
# 点1: (50, 0) - 蓝色方块
point1_x = offset_x + 50 * scale
point1_y = canvas_height - offset_y
self.points_canvas.create_rectangle(point1_x-5, point1_y-5, point1_x+5, point1_y+5, fill="blue", outline="darkblue", tags="point1")
self.points_canvas.create_text(point1_x, point1_y-15, text="点1 (50,0)", fill="blue", font=("Arial", 9))
# 点2: 根据计算结果绘制 - 绿色圆点
point2_x = offset_x + self.point2_x * scale
point2_y = canvas_height - offset_y - self.point2_y * scale
self.points_canvas.create_oval(point2_x-6, point2_y-6, point2_x+6, point2_y+6, fill="green", outline="darkgreen", tags="point2")
self.points_canvas.create_text(point2_x, point2_y-20, text=f"点2 ({self.point2_x:.1f},{self.point2_y:.1f})", fill="green", font=("Arial", 9))
# 绘制三角形边
self.points_canvas.create_line(point0_x, point0_y, point1_x, point1_y, fill="lightblue", width=1)# 边0-1
self.points_canvas.create_line(point0_x, point0_y, point2_x, point2_y, fill="lightcoral", width=1, dash=(4, 2))# 边0-2
self.points_canvas.create_line(point1_x, point1_y, point2_x, point2_y, fill="lightgreen", width=1, dash=(4, 2))# 边1-2
# 添加距离标注
# 标注边0-1的距离
mid_x = (point0_x + point1_x) / 2
mid_y = point0_y + 15
self.points_canvas.create_text(mid_x, mid_y, text="50cm", fill="blue", font=("Arial", 8))
# 标注边0-2的距离
if self.distance_0_2 > 0:
mid_x = (point0_x + point2_x) / 2
mid_y = (point0_y + point2_y) / 2 - 10
self.points_canvas.create_text(mid_x, mid_y, text=f"{self.distance_0_2:.1f}cm", fill="red", font=("Arial", 8))
# 标注边1-2的距离
if self.distance_1_2 > 0:
mid_x = (point1_x + point2_x) / 2
mid_y = (point1_y + point2_y) / 2 - 10
self.points_canvas.create_text(mid_x, mid_y, text=f"{self.distance_1_2:.1f}cm", fill="green", font=("Arial", 8))
# 添加标题
self.points_canvas.create_text(canvas_width/2, 20, text="三角定位显示", fill="black", font=("Arial", 12, "bold"))
def send_data(self):
"""发送数据"""
if not self.ser:
messagebox.showerror("错误", "请先连接串口")
return
try:
# 获取输入值
value1 = int(self.value1_var.get())
value2 = int(self.value2_var.get())
value3 = int(self.value3_var.get())
frame_code = int(self.frame_code_var.get(), 16)# 帧码以十六进制解析
# 发送数据
send_values(self.ser, frame_code, value1, value2, value3)
self.status_var.set(f"已发送: {value1}, {value2}, {value3}")
except ValueError as e:
messagebox.showerror("输入错误", "请输入有效的整数")
except Exception as e:
messagebox.showerror("发送错误", f"发送失败: {str(e)}")
def on_closing(self):
"""关闭程序时断开串口"""
self.running = False
self.com3_running = False
self.com33_running = False
if self.ser:
self.ser.close()
if self.ser_com3:
self.ser_com3.close()
if self.ser_com33:
self.ser_com33.close()
self.root.destroy()
def main():
root = tk.Tk()
app = XProtocolGUI(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()
if __name__ == "__main__":
main()
```
页:
[1]