目录
- 项目背景与需求分析
- 开发环境搭建(Windows/Linux)
- Python 基础语法速成
- 串口数据通信原理与实现
- 多设备 CSV 文件管理系统
- 数据分析核心技术
- 可视化图表设计与实现
- 完整项目架构与代码
- 部署与运行指南
- 故障排除与优化
- 进阶学习路径
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 个字段,用逗号分隔:
- 电机序号:整数,用于区分不同电机
- 时间戳:字符串,格式为 "YYYY-MM-DD HH:MM:SS"
- 编码器行程:浮点数,表示电机转动的行程
- 母线电流:浮点数,单位为安培 (A)
- 状态:字符串,表示电机状态(如 "启动"、"运行"、"停止" 等)
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

1730

被折叠的 条评论
为什么被折叠?



