eefocus_3914144 发表于 2025-10-28 20:38:37

【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]
查看完整版本: 【AIMB-2210】AI定位小车