STM32F103温控系统上位机开发实战Python串口通信与数据可视化在嵌入式系统开发中温度控制是一个经典而实用的课题。当STM32F103作为下位机完成温度采集和PID控制后如何通过上位机实现数据的可视化监控和交互控制就成为提升系统实用性的关键环节。本文将深入讲解如何使用Python构建功能完整的温控系统上位机涵盖串口通信协议设计、实时曲线绘制、历史数据回放等核心功能。1. 系统架构与通信协议设计一个完整的温控系统通常由下位机STM32F103、温度传感器如DS18B20、执行机构半导体制冷器/PTC加热片和上位机构成。上下位机之间通过串口进行数据交换因此首先需要设计一套简洁高效的通信协议。1.1 串口通信参数配置在Python中我们可以使用PySerial库实现串口通信。基本参数配置如下import serial ser serial.Serial( portCOM3, # 串口号根据实际连接修改 baudrate115200, # 波特率需与下位机一致 bytesize8, # 数据位 parityN, # 无校验 stopbits1, # 停止位 timeout1 # 超时时间(秒) )1.2 自定义通信协议设计为保证数据传输的可靠性我们设计如下通信帧格式字节位置内容说明00xAA帧头标识1数据长度有效数据字节数2命令类型0x01:上传数据 0x02:下传设定3~n数据内容有效数据n1校验和前面所有字节的累加和下位机上传的温度数据包示例十六进制AA 04 01 00 19 00 1E C2解析0xAA: 帧头0x04: 数据长度4字节0x01: 上传数据命令0x0019: 当前温度(25.0℃)0x001E: 目标温度(30.0℃)0xC2: 校验和(0xAA0x040x010x000x190x000x1E)2. PyQt5界面设计与实时数据显示PyQt5提供了丰富的UI组件非常适合开发专业的上位机界面。我们将创建一个包含温度曲线显示区、控制面板和数据记录区的综合界面。2.1 主界面框架搭建from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, QHBoxLayout, QGroupBox) from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas import matplotlib.pyplot as plt class MainWindow(QMainWindow): def __init__(self): super().__init__() # 主窗口设置 self.setWindowTitle(STM32温控系统监控平台) self.setGeometry(100, 100, 1000, 600) # 创建中央部件和布局 central_widget QWidget() self.setCentralWidget(central_widget) main_layout QHBoxLayout(central_widget) # 左侧控制面板 control_panel self.create_control_panel() main_layout.addWidget(control_panel, stretch1) # 右侧数据显示区 data_display self.create_data_display() main_layout.addWidget(data_display, stretch3) def create_control_panel(self): 创建左侧控制面板 panel QGroupBox(控制面板) layout QVBoxLayout() # 这里添加各种控制组件... panel.setLayout(layout) return panel def create_data_display(self): 创建右侧数据显示区 container QWidget() layout QVBoxLayout() # 创建Matplotlib图形 self.figure, self.ax plt.subplots(figsize(10, 6)) self.canvas FigureCanvas(self.figure) layout.addWidget(self.canvas) # 其他数据显示组件... container.setLayout(layout) return container2.2 实时曲线绘制实现使用Matplotlib实现动态曲线更新是上位机的核心功能之一。我们通过QTimer定时器实现曲线的实时刷新from PyQt5.QtCore import QTimer import numpy as np class MainWindow(QMainWindow): def __init__(self): # ...其他初始化代码... # 初始化数据存储 self.time_data np.arange(0, 300, 1) # 300个时间点 self.current_temp np.zeros(300) # 当前温度数组 self.target_temp np.zeros(300) # 目标温度数组 self.pointer 0 # 数据指针 # 初始化曲线 self.line_current, self.ax.plot(self.time_data, self.current_temp, r-, label当前温度) self.line_target, self.ax.plot(self.time_data, self.target_temp, b--, label目标温度) self.ax.legend() self.ax.set_xlabel(时间(s)) self.ax.set_ylabel(温度(℃)) self.ax.set_ylim(0, 100) # 温度范围 # 设置定时器 self.timer QTimer() self.timer.timeout.connect(self.update_plot) self.timer.start(1000) # 1秒刷新一次 def update_plot(self): 更新曲线数据 if self.pointer 300: # 从串口读取新数据并更新数组 new_data self.read_serial_data() if new_data: self.current_temp[self.pointer] new_data[current] self.target_temp[self.pointer] new_data[target] self.pointer 1 else: # 滚动显示 self.current_temp[:-1] self.current_temp[1:] self.target_temp[:-1] self.target_temp[1:] self.current_temp[-1] new_data[current] self.target_temp[-1] new_data[target] # 更新曲线数据 self.line_current.set_ydata(self.current_temp) self.line_target.set_ydata(self.target_temp) # 调整X轴范围 if self.pointer 300: self.ax.set_xlim(0, 300) else: self.ax.set_xlim(self.pointer-300, self.pointer) self.canvas.draw()3. 数据记录与历史回放功能完整的监控系统需要具备数据记录和回放功能便于后续分析系统性能。3.1 SQLite数据库存储使用轻量级的SQLite数据库存储历史数据import sqlite3 from datetime import datetime class DataLogger: def __init__(self): self.conn sqlite3.connect(temperature_data.db) self.cursor self.conn.cursor() self.create_table() def create_table(self): 创建数据表 self.cursor.execute( CREATE TABLE IF NOT EXISTS temperature_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, current_temp REAL NOT NULL, target_temp REAL NOT NULL, pid_output REAL ) ) self.conn.commit() def log_data(self, current, target, pid_out): 记录数据 timestamp datetime.now().strftime(%Y-%m-%d %H:%M:%S) self.cursor.execute( INSERT INTO temperature_log (timestamp, current_temp, target_temp, pid_output) VALUES (?, ?, ?, ?) , (timestamp, current, target, pid_out)) self.conn.commit() def get_history_data(self, start_time, end_time): 查询历史数据 self.cursor.execute( SELECT timestamp, current_temp, target_temp FROM temperature_log WHERE timestamp BETWEEN ? AND ? ORDER BY timestamp , (start_time, end_time)) return self.cursor.fetchall()3.2 历史数据可视化添加历史数据选择界面和回放功能from PyQt5.QtWidgets import (QDateEdit, QTimeEdit, QPushButton, QDialog, QLabel) class HistoryDialog(QDialog): def __init__(self, parentNone): super().__init__(parent) self.setWindowTitle(历史数据查询) self.setGeometry(200, 200, 400, 200) layout QVBoxLayout() # 日期时间选择 layout.addWidget(QLabel(开始时间:)) self.start_datetime QDateTimeEdit(self) layout.addWidget(self.start_datetime) layout.addWidget(QLabel(结束时间:)) self.end_datetime QDateTimeEdit(self) layout.addWidget(self.end_datetime) # 查询按钮 self.query_btn QPushButton(查询, self) self.query_btn.clicked.connect(self.query_data) layout.addWidget(self.query_btn) self.setLayout(layout) def query_data(self): 查询并显示历史数据 start self.start_datetime.dateTime().toString(yyyy-MM-dd hh:mm:ss) end self.end_datetime.dateTime().toString(yyyy-MM-dd hh:mm:ss) # 获取数据 data self.parent().data_logger.get_history_data(start, end) # 绘制历史曲线 self.parent().plot_history_data(data) self.close()4. PID参数整定与系统优化通过上位机可以实时调整PID参数观察系统响应实现更好的控制效果。4.1 PID参数下发实现在下位机程序中我们需要实现接收PID参数并更新的功能。上位机发送的PID参数包格式如下AA 0C 02 00 00 3F 80 00 00 3E 80 00 00 3D 80 XX解析0xAA: 帧头0x0C: 数据长度12字节0x02: 参数设置命令0x00003F80: Kp (1.0的IEEE754浮点表示)0x00003E80: Ki (0.25)0x00003D80: Kd (0.0625)XX: 校验和Python实现代码import struct def send_pid_params(self, kp, ki, kd): 发送PID参数到下位机 # 将浮点数转换为字节 kp_bytes struct.pack(f, kp) ki_bytes struct.pack(f, ki) kd_bytes struct.pack(f, kd) # 构建数据包 data bytearray() data.append(0xAA) # 帧头 data.append(0x0C) # 数据长度 data.append(0x02) # 命令类型 # 添加PID参数 data.extend(kp_bytes) data.extend(ki_bytes) data.extend(kd_bytes) # 计算校验和 checksum sum(data) 0xFF data.append(checksum) # 发送数据 if self.ser.is_open: self.ser.write(data)4.2 系统响应分析工具添加阶跃响应测试功能帮助分析系统动态特性def step_response_test(self): 阶跃响应测试 # 保存当前目标温度 original_target self.target_temp[self.pointer-1] if self.pointer 0 else 25.0 # 设置新的目标温度(阶跃变化) new_target original_target 10.0 # 10℃阶跃 self.send_target_temp(new_target) # 记录响应过程 self.response_data { time: [], temperature: [], start_time: time.time() } # 启动专用定时器记录数据 self.response_timer QTimer() self.response_timer.timeout.connect(self.record_response_data) self.response_timer.start(100) # 100ms采样一次 def record_response_data(self): 记录响应数据 elapsed time.time() - self.response_data[start_time] current_temp self.current_temp[self.pointer-1] if self.pointer 0 else 0 self.response_data[time].append(elapsed) self.response_data[temperature].append(current_temp) # 10秒后停止记录 if elapsed 10.0: self.response_timer.stop() self.plot_response_curve() def plot_response_curve(self): 绘制响应曲线 fig, ax plt.subplots() ax.plot(self.response_data[time], self.response_data[temperature]) ax.set_xlabel(时间(s)) ax.set_ylabel(温度(℃)) ax.set_title(阶跃响应曲线) ax.grid(True) fig.show()5. 系统集成与调试技巧完成各个模块开发后需要进行系统集成和调试。以下是几个实用的调试技巧串口调试助手辅助调试先用串口调试助手验证通信协议的正确性数据日志记录将所有收发数据记录到文件便于分析通信问题模拟数据模式在没有硬件连接时可以使用模拟数据进行界面测试异常处理完善串口通信的异常处理机制避免程序崩溃def serial_thread(self): 串口通信线程 while self.running: try: if self.ser.in_waiting: data self.ser.read(self.ser.in_waiting) self.process_serial_data(data) # 记录原始数据 with open(serial_log.txt, ab) as f: f.write(data) except serial.SerialException as e: print(f串口错误: {e}) time.sleep(1) except Exception as e: print(f未知错误: {e})通过以上步骤我们构建了一个功能完善的STM32温控系统上位机实现了数据监控、历史回放、PID参数调整等核心功能。这种架构也可以扩展到其他类型的监控系统开发中具有很好的通用性和可扩展性。
STM32F103 温控系统上位机开发:Python串口通信与实时波形显示(附源码)
发布时间:2026/7/9 15:52:37
STM32F103温控系统上位机开发实战Python串口通信与数据可视化在嵌入式系统开发中温度控制是一个经典而实用的课题。当STM32F103作为下位机完成温度采集和PID控制后如何通过上位机实现数据的可视化监控和交互控制就成为提升系统实用性的关键环节。本文将深入讲解如何使用Python构建功能完整的温控系统上位机涵盖串口通信协议设计、实时曲线绘制、历史数据回放等核心功能。1. 系统架构与通信协议设计一个完整的温控系统通常由下位机STM32F103、温度传感器如DS18B20、执行机构半导体制冷器/PTC加热片和上位机构成。上下位机之间通过串口进行数据交换因此首先需要设计一套简洁高效的通信协议。1.1 串口通信参数配置在Python中我们可以使用PySerial库实现串口通信。基本参数配置如下import serial ser serial.Serial( portCOM3, # 串口号根据实际连接修改 baudrate115200, # 波特率需与下位机一致 bytesize8, # 数据位 parityN, # 无校验 stopbits1, # 停止位 timeout1 # 超时时间(秒) )1.2 自定义通信协议设计为保证数据传输的可靠性我们设计如下通信帧格式字节位置内容说明00xAA帧头标识1数据长度有效数据字节数2命令类型0x01:上传数据 0x02:下传设定3~n数据内容有效数据n1校验和前面所有字节的累加和下位机上传的温度数据包示例十六进制AA 04 01 00 19 00 1E C2解析0xAA: 帧头0x04: 数据长度4字节0x01: 上传数据命令0x0019: 当前温度(25.0℃)0x001E: 目标温度(30.0℃)0xC2: 校验和(0xAA0x040x010x000x190x000x1E)2. PyQt5界面设计与实时数据显示PyQt5提供了丰富的UI组件非常适合开发专业的上位机界面。我们将创建一个包含温度曲线显示区、控制面板和数据记录区的综合界面。2.1 主界面框架搭建from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, QHBoxLayout, QGroupBox) from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas import matplotlib.pyplot as plt class MainWindow(QMainWindow): def __init__(self): super().__init__() # 主窗口设置 self.setWindowTitle(STM32温控系统监控平台) self.setGeometry(100, 100, 1000, 600) # 创建中央部件和布局 central_widget QWidget() self.setCentralWidget(central_widget) main_layout QHBoxLayout(central_widget) # 左侧控制面板 control_panel self.create_control_panel() main_layout.addWidget(control_panel, stretch1) # 右侧数据显示区 data_display self.create_data_display() main_layout.addWidget(data_display, stretch3) def create_control_panel(self): 创建左侧控制面板 panel QGroupBox(控制面板) layout QVBoxLayout() # 这里添加各种控制组件... panel.setLayout(layout) return panel def create_data_display(self): 创建右侧数据显示区 container QWidget() layout QVBoxLayout() # 创建Matplotlib图形 self.figure, self.ax plt.subplots(figsize(10, 6)) self.canvas FigureCanvas(self.figure) layout.addWidget(self.canvas) # 其他数据显示组件... container.setLayout(layout) return container2.2 实时曲线绘制实现使用Matplotlib实现动态曲线更新是上位机的核心功能之一。我们通过QTimer定时器实现曲线的实时刷新from PyQt5.QtCore import QTimer import numpy as np class MainWindow(QMainWindow): def __init__(self): # ...其他初始化代码... # 初始化数据存储 self.time_data np.arange(0, 300, 1) # 300个时间点 self.current_temp np.zeros(300) # 当前温度数组 self.target_temp np.zeros(300) # 目标温度数组 self.pointer 0 # 数据指针 # 初始化曲线 self.line_current, self.ax.plot(self.time_data, self.current_temp, r-, label当前温度) self.line_target, self.ax.plot(self.time_data, self.target_temp, b--, label目标温度) self.ax.legend() self.ax.set_xlabel(时间(s)) self.ax.set_ylabel(温度(℃)) self.ax.set_ylim(0, 100) # 温度范围 # 设置定时器 self.timer QTimer() self.timer.timeout.connect(self.update_plot) self.timer.start(1000) # 1秒刷新一次 def update_plot(self): 更新曲线数据 if self.pointer 300: # 从串口读取新数据并更新数组 new_data self.read_serial_data() if new_data: self.current_temp[self.pointer] new_data[current] self.target_temp[self.pointer] new_data[target] self.pointer 1 else: # 滚动显示 self.current_temp[:-1] self.current_temp[1:] self.target_temp[:-1] self.target_temp[1:] self.current_temp[-1] new_data[current] self.target_temp[-1] new_data[target] # 更新曲线数据 self.line_current.set_ydata(self.current_temp) self.line_target.set_ydata(self.target_temp) # 调整X轴范围 if self.pointer 300: self.ax.set_xlim(0, 300) else: self.ax.set_xlim(self.pointer-300, self.pointer) self.canvas.draw()3. 数据记录与历史回放功能完整的监控系统需要具备数据记录和回放功能便于后续分析系统性能。3.1 SQLite数据库存储使用轻量级的SQLite数据库存储历史数据import sqlite3 from datetime import datetime class DataLogger: def __init__(self): self.conn sqlite3.connect(temperature_data.db) self.cursor self.conn.cursor() self.create_table() def create_table(self): 创建数据表 self.cursor.execute( CREATE TABLE IF NOT EXISTS temperature_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, current_temp REAL NOT NULL, target_temp REAL NOT NULL, pid_output REAL ) ) self.conn.commit() def log_data(self, current, target, pid_out): 记录数据 timestamp datetime.now().strftime(%Y-%m-%d %H:%M:%S) self.cursor.execute( INSERT INTO temperature_log (timestamp, current_temp, target_temp, pid_output) VALUES (?, ?, ?, ?) , (timestamp, current, target, pid_out)) self.conn.commit() def get_history_data(self, start_time, end_time): 查询历史数据 self.cursor.execute( SELECT timestamp, current_temp, target_temp FROM temperature_log WHERE timestamp BETWEEN ? AND ? ORDER BY timestamp , (start_time, end_time)) return self.cursor.fetchall()3.2 历史数据可视化添加历史数据选择界面和回放功能from PyQt5.QtWidgets import (QDateEdit, QTimeEdit, QPushButton, QDialog, QLabel) class HistoryDialog(QDialog): def __init__(self, parentNone): super().__init__(parent) self.setWindowTitle(历史数据查询) self.setGeometry(200, 200, 400, 200) layout QVBoxLayout() # 日期时间选择 layout.addWidget(QLabel(开始时间:)) self.start_datetime QDateTimeEdit(self) layout.addWidget(self.start_datetime) layout.addWidget(QLabel(结束时间:)) self.end_datetime QDateTimeEdit(self) layout.addWidget(self.end_datetime) # 查询按钮 self.query_btn QPushButton(查询, self) self.query_btn.clicked.connect(self.query_data) layout.addWidget(self.query_btn) self.setLayout(layout) def query_data(self): 查询并显示历史数据 start self.start_datetime.dateTime().toString(yyyy-MM-dd hh:mm:ss) end self.end_datetime.dateTime().toString(yyyy-MM-dd hh:mm:ss) # 获取数据 data self.parent().data_logger.get_history_data(start, end) # 绘制历史曲线 self.parent().plot_history_data(data) self.close()4. PID参数整定与系统优化通过上位机可以实时调整PID参数观察系统响应实现更好的控制效果。4.1 PID参数下发实现在下位机程序中我们需要实现接收PID参数并更新的功能。上位机发送的PID参数包格式如下AA 0C 02 00 00 3F 80 00 00 3E 80 00 00 3D 80 XX解析0xAA: 帧头0x0C: 数据长度12字节0x02: 参数设置命令0x00003F80: Kp (1.0的IEEE754浮点表示)0x00003E80: Ki (0.25)0x00003D80: Kd (0.0625)XX: 校验和Python实现代码import struct def send_pid_params(self, kp, ki, kd): 发送PID参数到下位机 # 将浮点数转换为字节 kp_bytes struct.pack(f, kp) ki_bytes struct.pack(f, ki) kd_bytes struct.pack(f, kd) # 构建数据包 data bytearray() data.append(0xAA) # 帧头 data.append(0x0C) # 数据长度 data.append(0x02) # 命令类型 # 添加PID参数 data.extend(kp_bytes) data.extend(ki_bytes) data.extend(kd_bytes) # 计算校验和 checksum sum(data) 0xFF data.append(checksum) # 发送数据 if self.ser.is_open: self.ser.write(data)4.2 系统响应分析工具添加阶跃响应测试功能帮助分析系统动态特性def step_response_test(self): 阶跃响应测试 # 保存当前目标温度 original_target self.target_temp[self.pointer-1] if self.pointer 0 else 25.0 # 设置新的目标温度(阶跃变化) new_target original_target 10.0 # 10℃阶跃 self.send_target_temp(new_target) # 记录响应过程 self.response_data { time: [], temperature: [], start_time: time.time() } # 启动专用定时器记录数据 self.response_timer QTimer() self.response_timer.timeout.connect(self.record_response_data) self.response_timer.start(100) # 100ms采样一次 def record_response_data(self): 记录响应数据 elapsed time.time() - self.response_data[start_time] current_temp self.current_temp[self.pointer-1] if self.pointer 0 else 0 self.response_data[time].append(elapsed) self.response_data[temperature].append(current_temp) # 10秒后停止记录 if elapsed 10.0: self.response_timer.stop() self.plot_response_curve() def plot_response_curve(self): 绘制响应曲线 fig, ax plt.subplots() ax.plot(self.response_data[time], self.response_data[temperature]) ax.set_xlabel(时间(s)) ax.set_ylabel(温度(℃)) ax.set_title(阶跃响应曲线) ax.grid(True) fig.show()5. 系统集成与调试技巧完成各个模块开发后需要进行系统集成和调试。以下是几个实用的调试技巧串口调试助手辅助调试先用串口调试助手验证通信协议的正确性数据日志记录将所有收发数据记录到文件便于分析通信问题模拟数据模式在没有硬件连接时可以使用模拟数据进行界面测试异常处理完善串口通信的异常处理机制避免程序崩溃def serial_thread(self): 串口通信线程 while self.running: try: if self.ser.in_waiting: data self.ser.read(self.ser.in_waiting) self.process_serial_data(data) # 记录原始数据 with open(serial_log.txt, ab) as f: f.write(data) except serial.SerialException as e: print(f串口错误: {e}) time.sleep(1) except Exception as e: print(f未知错误: {e})通过以上步骤我们构建了一个功能完善的STM32温控系统上位机实现了数据监控、历史回放、PID参数调整等核心功能。这种架构也可以扩展到其他类型的监控系统开发中具有很好的通用性和可扩展性。