程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Python+PyQt5实现串口数据采集和显示

本节我们将会通过PyQt5实现串口数据采集和实时通信,涉及到的技术栈包括:PythonPyQt5

一、环境搭建

1.1 Python 3.X安装

直接从官网下载安装包:https://www.python.org/ftp/python/

这里我下载的包为https://www.python.org/ftp/python/3.9.6/python-3.9.6-amd64.exe,安装版本:python 3.9.6

双击开始安装的时候,一定要把下面的 Add Path勾上 (表示添加到环境变量,这样cmd也能使用了),其他一路Next安装完成。默认会安装一键式工具pip

pip工具镜像源配置。配置方法如下:

  • cmd窗口下执行echo %HOMEPATH%获取用户HOME目录,并在该目录下创建pip目录;
  • pip目录下创建pip.ini文件。记住,后缀必须是.ini格式。并在该文件中写入如下内容;

内容如下:

[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
[install]
trusted-host = pypi.tuna.tsinghua.edu.cn

1.2 安装Pycharm

官方网站:http://www.jetbrains.com/pycharm/,提供以下安装版本:

  • Professional:专业版(收费,网上一大堆破解方法)
  • Community:社区版(免费,我用的这个),下载版本为pycharm-community-2023.3.4.exe

1.3 PyQt5安装

使用pip工具安装PyQt5工具,执行:

pip install pyqt5

如果慢,用国内源:

pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple

使用pip工具安装PyQt5-tools工具,执行:

pip install pyqt5-tools

如果慢,用国内源:

pip install pyqt5-tools -i https://pypi.tuna.tsinghua.edu.cn/simple

工具安装完成后的路径在E:\Program Files\Python\Lib\site-packages

PyQt5主要有三个部分:

  • QtCore: 包含了核心的非GUI的功能。主要和时间、文件与文件夹、各种数据、模型、流、URLsmime类文件、进程与线程一起使用;
  • QtGui: 包含了窗口系统、事件处理、2D图像、基本绘画、字体和文字类;
  • QtWidgets: 包含了一些创建桌面的UI元素和控件;

1.4 环境配置

PyCharm是开发Python程序主流常用的IDE。为方便调用Qt Designer实现界面开发和编译相应完成,可以在PyCharm配置Qt DesignerPyUICPyrcc

其中Qt DesignerQt 设计师,PyUics是把UI界面转换成py文件,Pyrcc是资源系统转换。

打开PyCharm, 新建一个项目,项目名称为serial_port

这里Python path选择我们之前安装的python 3.9.6的路径:E:\Program Files\Python\python.exe

1.4.1 配置Qt Designer

菜单File ->Settings -> Tools -> External Tools -> +号,进行添加。 参数配置说明:

  • Name:填入Qt Designer,实际可以任意取值;
  • Programdesigner.exe程序绝对路径。根据实际安装路径填写,这里我配置的是E:\Program Files\Python\Lib\site-packages\qt5_applications\Qt\bin\designer.exe
  • Working directory: 填入$FileDir$,固定取值;

具体如下:

1.4.2 配置PyUIC

该工具是用于将Qt Designer工具开发完成的.ui文件转化为.py文件。配置打开路径同Qt Designer,参数配置说明:

  • Name:填入PyUIC,实际可以任意取值。
  • Programpython.exe程序绝对路径,根据实际安装路径填写,这里我配置的是E:\Program Files\Python\python.exe
  • Arguments: -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
  • Working directory: 填入$FileDir$,固定取值;

具体如下:

1.4.3 配置Pyqcc

配置打开路径同Qt Designer。参数配置说明:

  • Name:填入Pyqcc,实际可以任意取值。
  • Program:这里我配置的是E:\Program Files\Python\Scripts\pyrcc5.exe
  • Arguments: $FileName$ -o $FileNameWithoutExtension$_rc.py
  • Working directory: 填入$FileDir$,固定取值;

具体如下:

1.5 测试

测试Qt DesignerPyUICPyqcc配置是否成功。打开路径:菜单栏Tools ->External Tools ->Qt Designer/PyUIC/Pyqcc

1.5.1 ui_serial_port.ui

点击Qt Designer,打开Designer程序主主界面,会弹出一个窗口,这里一般是选择Main Window或者Widget,其中Main Window继承自Widget,添加了一些内容,本质二者差不多。这里选择的是Main Window

将左侧Widget BoxPush button空间拖到主界面,Ctrl + S保存名称ui_serial_port.ui,默认后缀就是.ui

1.5.2 ui_serial_port.py

选中ui_serial_port.ui文件,同理点击PyUIC,自动完成ui_serial_port.ui文件的转换,生成文件名为ui_serial_port.py

1.5.3 serial_port.py

ui界面代码,还需要有一个逻辑代码,而逻辑代码个人感觉使用类的形式来组织更加方便,也更优雅。

还记得创建ui时选择的类吗?是Widget还是Main Window,逻辑代码类最好是继承这个这个类,即QWidgetQMainWindow。一般的代码结构如下所示:

from PyQt5.QtWidgets import QMainWindow

# 导入设计的ui界面转换成的py文件
from ui_serial_port import Ui_MainWindow


class SerialPort(QMainWindow):
    """
     串口行为
    """

    def __init__(self):
        # QMainWindow构造函数初始化
        super().__init__()
        self.ui = Ui_MainWindow()
        # 这个函数本身需要传递一个MainWindow类,而该类本身就继承了这个,所以可以直接传入self
        self.ui.setupUi(self)
1.5.4 main.py

在当前项目下,新建main.py 文件;

import sys
from serial_port import SerialPort
from PyQt5.QtWidgets import QApplication, QMainWindow

if __name__ == '__main__':
    # 先建立一个app
    app = QApplication(sys.argv)
    # 初始化一个对象,调用init函数,已加载设计的ui文件
    ui = SerialPort()
    # 显示这个ui
    ui.show()
    # 运行界面,响应按钮等操作
    sys.exit(app.exec_())

运行程序:

二、程序设计

2.1 需求

客户这里有一款惯性设备,在惯性装置的AXS31接口,里面有两路数据,一路称为导航解算,一路称为原始信息。我们通过串口读取该设备的数据并在界面显示处理,同时还需要将读取到的数据保存到文本中。

实际在测试时候发现只能接收到导航解算报文,因此猜测原始信息已经被传感器内部转换为导航解算报文了。

2.1.1 导航解算报文

波特率:230400,数据位8,停止位1,无校验;

字节 意义 类型 所占字节 备注
1-2 报文头 2字节 5a 5a
3 工作状态 1字节 0xFF等待对准
0x00码头对准
0x01海上对准
0x02牵引对准
0x03 是无阻尼
0x04是惯导阻尼
0x05 点校
0x06 综合校正
0x07 位置组合
4 参数状态 1字节 B8 =0手动 B8=1自动
5-8 运行时间 4字节 单位0.05s
9-11 纬度 3字节 量纲93206.75556
12-14 经度 3字节 量纲46603.37778
15-16 升沉 2字节 最小量纲100m
17-18 东速 2字节 最小量纲100kn
19-20 北速 2字节 最小量纲100kn
21-22 垂速 2字节 最小量纲100m/s
23-25 姿态角1 3字节 最小量纲0.25*93206.75556
26-28 姿态角2 3字节 最小量纲93206.75556
29-31 姿态角3 3字节 最小量纲93206.75556
32-34 姿态角速率1(纵摇角速率) 3字节 93206.75556 度每秒
35-37 姿态角速率2 (横摇角速率) 3字节 93206.75556 度每秒
38-40 姿态角速率3(航向角速率) 3字节 93206.75556度每秒
41 故障码 1字节 B0=1 IMU接收错
B1=1 测角采样错
B2=1 接收缓存错
B3=1 测角控制板错
B4=1 驱动错
B5=1 测角错
B6=1 激磁错
B7=1 转台保护错
42-43 IMUtime 2字节
44-47 备用 4字节
48 应答标志 1字节 可忽略
49 校验和 1字节 3-48字节累加和
2.1.2 原始信息报文

波特率:230400,数据位8,停止位1,无校验;

字节 意义 类型 所占字节 备注
1-2 报文头 Int 2字节 5a 5a
3-6 IMUtime Int 4字节 整型时戳
7-10 GYROX float 4字节 直接浮点数
11-14 GYROY float 4字节 直接浮点数
15-18 GYROZ float 4字节 直接浮点数
19-22 ACCEX float 4字节 直接浮点数
23-26 ACCEY float 4字节 直接浮点数
27-30 ACCEZ float 4字节 直接浮点数
31-34 备用 Int 4字节
35-37 转台角1 Int 3字节 量纲2.330168888888889*e4°
38-40 转台角2 Int 3字节 量纲2.330168888888889*e4°
41-43 GPS经度 Int 3字节 量纲93206.75556
44-46 GPS纬度 Int 3字节 量纲46603.37778
47-48 Para4 Int 2字节
49-50 Para[2]/para[5] Int 2字节
51-52 Para[3]/para[6] Int 2字节
53-54 Para[7] Int 2字节
55-56 Para[8] Int 2字节
57 comdatavalid Int 1字节
58 校验和 Int 1字节 3-57和校验

2.2 界面设计

首先,我们设计一个简单的用户界面,包括:

  • 串口配置区域:【串口】、【波特率】、【数据位】、【停止位】的设置,以及一个按钮用于开始打开和关闭串口;
  • 接收设置区域:用于设置接收和发送的数据格式,支持16进制以及ASCII两种格式;这里为了简单起见,程序中发送/接收采用一样的数据格式;
    • 16进制:例如:5a 5a 02 03 5a
    • ASCII格式:例如:DDR V1.12 52218f4949 cym 23/07/0
  • 数据发送区域:由一个文本框和一个发送按钮组成;
  • 数据接收区域:由一个文本域组成;
  • 如果接收到的数据时导航解算或者原始信息报文,则将解析后的数据显示在导航结算和原始信息区域;

界面效果如下(参考网上串口助手工具);

三、程序实现

我们将界面原型划分成了五个区域;

  • 串口设置区域;
  • 接收设置区域;
  • 数据发送区域;
  • 数据接收区域;
  • 导航解算和原始信息区域;

我们针对这五个区域编写相关实现代码,其具体流程如下;

  • 使用Qt Designer工具按照界面原型设置窗口,主要使用到一些基础控件,比如按钮、文本框、文本域、单选框、复选框等;‘
  • 针对窗体各个区域中的控件进行初始化工作;

我们将界面相关的代码均放置在serial_port.py文件中,该文件主要包含了如下功能;

  • 界面初始化工作;
  • 打开串口;
  • 接收数据;
  • 发送数据。

3.1 初始化工作

3.1.1 初始化串口设置区域

串口设置区域主要由【串口】、【波特率】、【校验位】、【数据位】、【停止位】下拉列表以及【打开串口】按钮组成。

首先需要初始化【串口】、【波特率】、【校验位】、【数据位】、【停止位】下拉列表;

  • 串口:下拉列表加载系统当前可用的串口;
  • 波特率:下拉列表设置常见的波特率,比如1200240096004800, 9600, 19200, 384000, 57600, 115200, 460800, 921600, 230400, 1500000等;
  • 校验位:下拉列表设置校验位为NoneOddEvenMarkSpace
  • 数据位:下拉列表设置为5678
  • 停止位:下拉列表设置为12

设置【打开串口】按钮点击事件对应的槽函数为self.open_serial_connection,当点击【打开串口】按钮时将会执行该函数;

代码位于serial_port.py__init_serial_setting__,具体如下;

def __init_serial_setting__(self):
	"""
	初始化串口设置相关控件默认参数
	设置下拉列表自动补全  https://blog.csdn.net/xuleisdjn/article/details/51434118
	:return:
	"""
	# 加载可用串口
	self.ui.cbx_com.setEditable(False)
	self.ui.cbx_com.setMaxVisibleItems(10)  # 设置最大显示下列项 超过要使用滚动条拖拉
	self.ui.cbx_com.setInsertPolicy(QComboBox.InsertAfterCurrent)  # 设置插入方式\
	port_list = list(serial.tools.list_ports.comports())  # 获取当前的所有串口,得到一个列表
	for port in port_list:
		self.ui.cbx_com.addItem(port.device)

	# 初始化波特率列表
	self.ui.cbx_baud_rate.setEditable(False)
	self.ui.cbx_baud_rate.setMaxVisibleItems(10)  # 设置最大显示下列项 超过要使用滚动条拖拉
	self.ui.cbx_baud_rate.setInsertPolicy(QComboBox.InsertAfterCurrent)  # 设置插入方式
	for baud_rate in [1200, 2400, 4800, 9600, 19200, 384000, 57600, 115200, 460800, 921600, 230400, 1500000]:
		self.ui.cbx_baud_rate.addItem(str(baud_rate), baud_rate)
	# 设置默认值
	self.ui.cbx_baud_rate.setCurrentIndex(7)

	# 初始化校验位列表
	self.ui.cbx_parity_bit.setEditable(False)
	self.ui.cbx_parity_bit.setMaxVisibleItems(10)  # 设置最大显示下列项 超过要使用滚动条拖拉
	self.ui.cbx_parity_bit.setInsertPolicy(QComboBox.InsertAfterCurrent)  # 设置插入方式
	for (key, value) in {'None': serial.PARITY_NONE, 'Odd': serial.PARITY_ODD,
						 'Even': serial.PARITY_EVEN, 'Mark': serial.PARITY_MARK,
						 'Space': serial.PARITY_SPACE}.items():
		self.ui.cbx_parity_bit.addItem(key, value)
	# 设置默认值
	self.ui.cbx_parity_bit.setCurrentIndex(0)

	# 初始化数据位列表
	self.ui.cbx_data_bit.setEditable(False)
	self.ui.cbx_data_bit.setMaxVisibleItems(10)  # 设置最大显示下列项 超过要使用滚动条拖拉
	self.ui.cbx_data_bit.setInsertPolicy(QComboBox.InsertAfterCurrent)  # 设置插入方式
	for data_bit in [serial.FIVEBITS, serial.SIXBITS, serial.SEVENBITS, serial.EIGHTBITS]:
		self.ui.cbx_data_bit.addItem(str(data_bit), data_bit)
	# 设置默认值
	self.ui.cbx_data_bit.setCurrentIndex(3)

	# 初始化停止位列表
	self.ui.cbx_stop_bit.setEditable(False)
	self.ui.cbx_stop_bit.setMaxVisibleItems(10)  # 设置最大显示下列项 超过要使用滚动条拖拉
	self.ui.cbx_stop_bit.setInsertPolicy(QComboBox.InsertAfterCurrent)  # 设置插入方式
	for data_bit in [serial.STOPBITS_ONE, serial.STOPBITS_TWO]:
		self.ui.cbx_stop_bit.addItem(str(data_bit), data_bit)
	# 设置默认值
	self.ui.cbx_stop_bit.setCurrentIndex(0)

	# 设置点击打开串口按钮对应的槽函数
	self.ui.btx_start.clicked.connect(self.open_serial_connection)
3.1.2 初始化串口接收区域

串口接收区域主要由【Hex】、【ASCII】单选框以及【显示时间】复选框组成;

设置发送/接收的数据格式,支持HexASCIIHexASCII是互斥的,默认选中Hex,这里我们设置:

  • Hex】单选框点击事件对应的槽函数为self.rbn_data_format_hex_clicked,当点击【Hex】单选框时将会执行该函数,在该函数内会记录当前选中的是hex
  • ASCII】单选框点击事件对应的槽函数为self.rbn_data_format_ascii_clicked,当点击【ASCII】单选框时将会执行该函数,在该函数内出记录当前选中的是ascii

【显示时间】复选框用于设置串口接收到数据时,是否在接收区域输出当前时间;

代码位于serial_port.py__init_recv_setting__,具体如下;

def __init_recv_setting__(self):
	"""
	接收设置初始化
	:return:
	"""
	self.ui.rbn_data_format_hex.clicked.connect(self.rbn_data_format_hex_clicked)
	self.ui.rbn_data_format_ascii.clicked.connect(self.rbn_data_format_ascii_clicked)

其中rbn_data_format_hex_clicked函数;

def rbn_data_format_hex_clicked(self):
	"""
	接收数据格式发生变化
	:return:
	"""
	if self.ui.rbn_data_format_hex.isChecked():
		self.ui.rbn_data_format_ascii.setChecked(False)
		if self.serial_thread:
			self.serial_thread.date_format = 'hex'

其中rbn_data_format_ascii_clicked函数;

def rbn_data_format_ascii_clicked(self):
	"""
	接收数据格式发生变化
	:return:
	"""
	if self.ui.rbn_data_format_ascii.isChecked():
		self.ui.rbn_data_format_hex.setChecked(False)
		if self.serial_thread:
			self.serial_thread.date_format = 'ascii'
3.1.3 初始化串口数据接收区域

串口数据接收区域由一个【文本域控件】组成,用于存放串口接收到的数据,数据长度默认最长为5000字符,超过5000字符自动清空;

这里初始化【文本域控件】为只读,并且串口接收到数据时,自动将滚动条移动至【文本域控件】的最低端;

代码位于serial_port.py__init_recv_setting__,具体如下;

def __init_recv_data_viewer__(self):
	"""
	初始化串口数据接收区域
	:return:
	"""
	self.ui.txt_recv_data_viewer.setReadOnly(True)
	self.ui.txt_recv_data_viewer.textChanged.connect(
		lambda: self.ui.txt_recv_data_viewer.moveCursor(QTextCursor.End))
3.1.4 初始化串口数据发送区域

串口数据接收区域由一个【文本输入框】和一个【发送】按钮组成,文本输入框用于输入要发送的内容,点击发送按钮,会将文本输入框的内容通过当前打开的串口发送出去;

代码位于serial_port.py__init_send_data_viewer__,具体如下;

def __init_send_data_viewer__(self):
	"""
	初始化串口数据发送区域
	:return:
	"""
	self.ui.btn_send.clicked.connect(self.send_serial_data)

设置【发送】按钮点击事件对应的槽函数为self.send_serial_data,当点击【发送】按钮时将会执行该函数进行串口数据的发送。

3.1.5 初始化报文解析区域

报文解析区域主要由两部分组成;

  • 导航结算区域:该部分主要由【报文头】、【工作状态】、【参数状态】、【运行时间】等文本输入框组成,其与导航解算报文字段一一对应;
  • 原始信息区域:该部分主要由【报文头】、【IMUTime】、【GYROX】、【GYROZ】等文本输入框组成,其与原始信息报文字段一一对应;

代码位于serial_port.py__init_package_setting__,具体如下;

def __init_package_setting__(self):
	self.ui.let_nav_header.setReadOnly(True)
	self.ui.let_nav_work_state.setReadOnly(True)
	self.ui.let_nav_param_state.setReadOnly(True)
	self.ui.let_nav_run_time.setReadOnly(True)
	self.ui.let_nav_latitude.setReadOnly(True)
	self.ui.let_nav_longitude.setReadOnly(True)
	self.ui.let_nav_heave.setReadOnly(True)
	self.ui.let_nav_east_speed.setReadOnly(True)
	self.ui.let_nav_north_speed.setReadOnly(True)
	self.ui.let_nav_vertical_speed.setReadOnly(True)
	self.ui.let_nav_attitude_angle1.setReadOnly(True)
	self.ui.let_nav_attitude_angle2.setReadOnly(True)
	self.ui.let_nav_attitude_angle3.setReadOnly(True)
	self.ui.let_nav_attitude_velocity1.setReadOnly(True)
	self.ui.let_nav_attitude_velocity2.setReadOnly(True)
	self.ui.let_nav_attitude_velocity3.setReadOnly(True)
	self.ui.let_nav_fault_code.setReadOnly(True)
	self.ui.let_nav_imu_time.setReadOnly(True)
	self.ui.let_nav_reserved.setReadOnly(True)
	self.ui.let_nav_response_flag.setReadOnly(True)
	self.ui.let_nav_check_sum.setReadOnly(True)
	self.ui.let_raw_header.setReadOnly(True)
	self.ui.let_raw__imu_time.setReadOnly(True)
	self.ui.let_raw_gyrox.setReadOnly(True)
	self.ui.let_raw_gyroy.setReadOnly(True)
	self.ui.let_raw_gyroz.setReadOnly(True)
	self.ui.let_raw_accex.setReadOnly(True)
	self.ui.let_raw_accey.setReadOnly(True)
	self.ui.let_raw_accez.setReadOnly(True)
	self.ui.let_raw_reserved.setReadOnly(True)
	self.ui.let_raw_turntable_angle1.setReadOnly(True)
	self.ui.let_raw_turntable_angle2.setReadOnly(True)
	self.ui.let_raw_longitude.setReadOnly(True)
	self.ui.let_raw_latitude.setReadOnly(True)
	self.ui.let_raw_para4.setReadOnly(True)
	self.ui.let_raw_para2_5.setReadOnly(True)
	self.ui.let_raw_para3_6.setReadOnly(True)
	self.ui.let_raw_para7.setReadOnly(True)
	self.ui.let_raw_para8.setReadOnly(True)
	self.ui.let_raw_comdata_valid.setReadOnly(True)
	self.ui.let_raw_check_sum.setReadOnly(True)

这里我们仅仅是将上述这些控件设置为只读状态。

3.2 打开串口

根据硬件设备参数在串口设置区域进行配置【串口】、【波特率】、【校验位】、【数据位】、【停止位】,配置完成后,当点击【打开串口】按钮,将会执行open_serial_connection函数;

open_serial_connection函数内主要会进行如下工作;

  • 获取串口设置区域配置的参数,并对参数进行校验,具体是由__validate_setting__函数完成;
  • 调用SerialThread创建串口线程,并将串口设置区域配置参数传递给线程;
  • 设置串口线程接收到数据时对应的槽函数为handle_data_received,即串口接收到数据时会执行handle_data_received函数;
  • 设置串口线程发生异常时对应的槽函数为handler_serial_error,即串口线程发生异常时,会将错误信息发送给主线程,主线程接收到错误信息,将会在界面显示;

核心代码如下:

def open_serial_connection(self):
	"""
	打开串口
	:return:
	"""
	if not self.serial_thread or not self.serial_thread.isRunning():
		# 参数校验
		if not self.__validate_setting__():
			return

		# 建立一个串口
		self.serial_thread = SerialThread(self.ui.cbx_com.currentText(),
												 self.ui.cbx_baud_rate.currentData(),
												 self.ui.cbx_data_bit.currentData(),
												 self.ui.cbx_parity_bit.currentData(),
												 self.ui.cbx_stop_bit.currentData(),
													   'hex' if self.ui.rbn_data_format_hex.isChecked() else 'ascii')

		self.serial_thread.data_received.connect(self.handle_data_received)
		self.serial_thread.serial_error.connect(self.handler_serial_error)
		self.serial_thread.start()
		self.ui.btx_start.setText("关闭串口")
	else:
		self.serial_thread.stop()
		# 打开串口操作
		self.ui.btx_start.setText("打开串口")
3.2.1 __validate_setting__

__validate_setting__函数主要就是校验【串口】、【波特率】、【校验位】、【数据位】、【停止位】下拉列表是否已经配置,如果未配置将会弹出警告提示信息;

def __validate_setting__(self):
	"""
	校验串口设置参数
	:return:
	"""
	# 参数校验
	if self.ui.cbx_com.currentIndex() == -1:
		QMessageBox.warning(self, "Warning", "请选择串口!")
		return False
	if self.ui.cbx_baud_rate.currentIndex() == -1:
		QMessageBox.warning(self, "Warning", "请选择波特率!")
		return False
	if self.ui.cbx_parity_bit.currentIndex() == -1:
		QMessageBox.warning(self, "Warning", "请选择校验位!")
		return False
	if self.ui.cbx_data_bit.currentIndex() == -1:
		QMessageBox.warning(self, "Warning", "请选择数据位!")
		return False
	if self.ui.cbx_data_bit.currentIndex() == -1:
		QMessageBox.warning(self, "Warning", "请选择停止位!")
		return False

	return True
3.2.2 SerialThread

SerialThread类的实现位于serial_thread.py文件中,这块我们后面单独接收。

3.2.3 handle_data_received

当串口接收到数据时会执行handle_data_received函数,这块我们后面单独接收。

3.2.4 handler_serial_error

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(572)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示