《边学边练:Python 从零开始:无人机直流电机测试数据采集与可视化全攻略 2》

目录

  1. 项目背景与需求分析
  2. 开发环境搭建(Windows/Linux)
  3. Python 基础语法速成
  4. 串口数据通信原理与实现
  5. 多设备 CSV 文件管理系统
  6. 数据分析核心技术
  7. 可视化图表设计与实现
  8. 完整项目架构与代码
  9. 部署与运行指南
  10. 故障排除与优化
  11. 进阶学习路径

4. 串口数据通信原理与实现

4.1 串口通信基础

串口通信是一种设备间的数据传输方式,通过一根数据线逐位传输数据。在电机测试系统中,嵌入式设备通过串口向上位机发送电机运行数据。

串口通信参数
  • 波特率(Baud Rate):数据传输速度,单位是比特 / 秒(bps)。常见值有 9600、115200 等。通信双方必须使用相同的波特率。
  • 数据位(Data Bits):每个字符包含的数据位数,通常为 8 位。
  • 停止位(Stop Bits):表示一个字符传输结束的标志位,通常为 1 位。
  • 校验位(Parity):用于简单的错误检测,通常设置为 None(无校验)。
  • 流控制(Flow Control):用于控制数据传输速度,通常设置为 None。
数据格式约定

我们需要与嵌入式软件约定数据格式。假设嵌入式系统发送的数据格式如下:

plaintext

电机序号,时间戳,编码器行程,母线电流,状态
1,2023-10-01 12:00:00,125.5,2.3,运行
2,2023-10-01 12:00:00,0.0,0.0,停止
1,2023-10-01 12:00:01,126.8,2.4,运行
...

每条数据包含 5 个字段,用逗号分隔:

  1. 电机序号:整数,用于区分不同电机
  2. 时间戳:字符串,格式为 "YYYY-MM-DD HH:MM:SS"
  3. 编码器行程:浮点数,表示电机转动的行程
  4. 母线电流:浮点数,单位为安培 (A)
  5. 状态:字符串,表示电机状态(如 "启动"、"运行"、"停止" 等)

4.2 串口通信实现

我们使用pyserial库实现 Python 与串口设备的通信。

串口初始化

创建src/serial_communication.py文件,实现串口初始化功能:

python

运行

# src/serial_communication.py
import serial
import time
import datetime

def init_serial(port='COM3', baudrate=115200):
    """
    初始化串口通信
    
    参数:
        port: 串口号,Windows通常为'COMx',Linux为'/dev/ttyUSBx'
        baudrate: 波特率
        
    返回:
        初始化成功的串口对象,失败则返回None
    """
    try:
        # 创建串口对象
        ser = serial.Serial(
            port=port,
            baudrate=baudrate,
            timeout=1,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS
        )
        
        # 检查串口是否成功打开
        if ser.is_open:
            print(f"串口 {port} 已打开,波特率:{baudrate}")
            return ser
        else:
            print(f"无法打开串口 {port}")
            return None
            
    except Exception as e:
        print(f"串口初始化错误:{e}")
        return None
读取串口数据

在同一个文件中添加读取串口数据的函数:

python

运行

def read_serial_data(ser):
    """
    从串口读取一行数据
    
    参数:
        ser: 已初始化的串口对象
        
    返回:
        读取到的字符串数据,失败则返回None
    """
    try:
        # 检查串口是否有效且已打开
        if ser and ser.is_open:
            # 读取一行数据,解码为字符串并去除首尾空白
            data = ser.readline().decode('utf-8').strip()
            return data
        return None
        
    except Exception as e:
        print(f"读取串口数据错误:{e}")
        return None
解析串口数据

添加数据解析函数,将原始字符串转换为结构化数据:

python

运行

def parse_data(data_str):
    """
    解析串口数据字符串
    
    参数:
        data_str: 从串口读取的字符串
        
    返回:
        解析后的字典数据,格式如下:
        {
            'motor_id': 电机序号(int),
            'timestamp': 时间戳(datetime),
            'encoder': 编码器行程(float),
            'current': 母线电流(float),
            'status': 状态(str)
        }
        解析失败则返回None
    """
    try:
        # 按逗号分割数据
        parts = data_str.split(',')
        
        # 验证数据格式是否正确(应该有5个字段)
        if len(parts) != 5:
            print(f"数据格式错误,预期5个字段,实际{len(parts)}个:{data_str}")
            return None
        
        # 提取各个字段
        motor_id_str, timestamp_str, encoder_str, current_str, status = parts
        
        # 转换数据类型
        try:
            # 解析电机序号(整数)
            motor_id = int(motor_id_str)
            
            # 解析时间戳
            timestamp = datetime.datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
            
            # 解析数值
            encoder = float(encoder_str)
            current = float(current_str)
            
            # 返回字典格式的数据
            return {
                'motor_id': motor_id,
                'timestamp': timestamp,
                'encoder': encoder,
                'current': current,
                'status': status
            }
            
        except ValueError as e:
            print(f"数据转换错误:{e},原始数据:{data_str}")
            return None
            
    except Exception as e:
        print(f"解析数据错误:{e}")
        return None
数据处理器类

实现一个数据处理器类,用于缓存和处理实时数据:

python

运行

class MotorDataProcessor:
    """电机数据处理器,用于缓存和处理实时数据"""
    
    def __init__(self, buffer_size=100):
        """
        初始化数据处理器
        
        参数:
            buffer_size: 数据缓冲区大小
        """
        self.data_buffer = {}  # 按电机ID存储数据缓冲区
        self.buffer_size = buffer_size  # 每个电机的缓冲区大小
        
    def add_data(self, data):
        """
        添加数据到缓冲区
        
        参数:
            data: 解析后的电机数据字典
        """
        if data:
            motor_id = data['motor_id']
            
            # 如果该电机ID不在缓冲区中,创建一个新列表
            if motor_id not in self.data_buffer:
                self.data_buffer[motor_id] = []
            
            # 添加数据到缓冲区
            self.data_buffer[motor_id].append(data)
            
            # 当缓冲区满时,移除最旧的数据
            if len(self.data_buffer[motor_id]) > self.buffer_size:
                self.data_buffer[motor_id].pop(0)
    
    def get_latest_data(self, motor_id, count=1):
        """
        获取指定电机的最新n条数据
        
        参数:
            motor_id: 电机ID
            count: 要获取的数据条数
            
        返回:
            包含最新数据的列表
        """
        if motor_id not in self.data_buffer:
            return []
            
        # 返回最新的count条数据
        return self.data_buffer[motor_id][-count:] if count <= len(self.data_buffer[motor_id]) else self.data_buffer[motor_id]
    
    def get_all_motor_ids(self):
        """获取所有已缓存数据的电机ID"""
        return list(self.data_buffer.keys())
    
    def get_statistics(self, motor_id):
        """
        计算指定电机缓冲区数据的统计信息
        
        参数:
            motor_id: 电机ID
            
        返回:
            包含统计信息的字典,或None(如果没有数据)
        """
        if motor_id not in self.data_buffer or not self.data_buffer[motor_id]:
            return None
            
        # 获取该电机的所有数据
        motor_data = self.data_buffer[motor_id]
            
        # 计算电流的统计值
        currents = [d['current'] for d in motor_data]
        avg_current = sum(currents) / len(currents)
        max_current = max(currents)
        min_current = min(currents)
        
        # 计算编码器行程的统计值
        encoders = [d['encoder'] for d in motor_data]
        max_encoder = max(encoders)
        min_encoder = min(encoders)
        total_distance = max_encoder - min_encoder
        
        return {
            'sample_count': len(motor_data),
            'avg_current': avg_current,
            'max_current': max_current,
            'min_current': min_current,
            'max_encoder': max_encoder,
            'min_encoder': min_encoder,
            'total_distance': total_distance,
            'latest_ti
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无人装备硬件开发爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值