【一文搞定】Win11系统安装Win7虚拟系统离线开发PyQt串口通讯软件并用pyinstaller打包成exe—从零开始的保姆级教程

对于一些较早的设备的依然需要在Windows7的系统上运行软件,由于现在几乎很少见到win7系统了,因此摸索了一下在win11电脑安装win7虚拟机和pyqt并编译软件的过程&#xff0c

对于一些较早的设备的依然需要在Windows7的系统上运行软件,由于现在几乎很少见到win7系统了,因此摸索了一下在win11电脑安装win7虚拟机和pyqt并编译软件的过程,在此记录一下。

一、安装VMware虚拟机

1、下载VMware虚拟机软件

(1)现在VMware Workstation Pro for Personal Use 已可以免费下载并使用,无需注册码,打开vmware网页,点击down

vmware官网https://www.vmware/products/desktop-hypervisor/workstation-and-fusion

(2)登陆BROADCOM账号,若没有账号则先注册

(3)登陆后,连接自动跳转到My Downloads,可以看到有个提醒消息“Free Software Downloads available HERE“,点击”HERE“链接

(4)找到VMware Workstation Pro并点击

(5)选择需要的系统和版本下载,我这里下载Windows版本,免费的版本从17.5.2开始,我们选择下载17.6.3版本。

(6)点击同意条款,然后点击下载,新的弹窗点击yes

(7)填写必要的信息,然后提交

(8)提交完后会返回下载界面,在此点击下载按钮即可开始下载

2、在win11系统中安装VMware虚拟机

(1)选中安装包,单击右键,以管理员身份运行

(2)下一步

(3)点击接收协议,点击下一步

(4)根据需要选择安装位置,选好后点击下一步

(5)点击下一步

(6)点击下一步

(7)点击安装

(8)等待安装完成后,点击完成

(9)安装完成

二、安装win7系统

1、在VMware中安装win7系统

(1)打开VMware软件,新建虚拟机

(2)下一步

(3)选择下载好的win7镜像文件,点击下一步

(4)输入激活码

(5)命名虚拟机,然后点击下一步

(6)确定系统空间大小,我这里默认使用60G,然后点击下一步

(7)点击完成

等待安装完成即可。

2、配置共享文件夹,便于win11和win7互传文件

(1)VMware中选中对应的虚拟机,选择“虚拟机”→“设置”

(2)点击“网络适配器”,确定网络连接设置为NAT模式共享主机

(3)win7设置为工作网络

(4)win7的共享设置中,家庭或工作:启用网络发现、启用文件和打印机共享、启用公共文件夹共享、关闭密码保护共享

(5)设置共享文件夹

①选中想要共享的文件夹,单击右键

②点击共享按钮

③下拉框中选择“everyone”后点击“添加”按钮,

④将“”读取“修改为”读/写“,然后点击“共享”按钮,然后“完成”,然后点击“关闭”

(6)查看win7的IP

(7)在win11电脑中打开文件管理器,在地址栏输入“\\IP”,回车,即可看到共享文件夹

至此就可以在win11系统和win7虚拟机之间自由的拷贝文件了。

3、安装谷歌浏览器(若win11系统也是离线则忽略这条)

在NAT模式下,若win11可联网,则可在win7中直接下载后续需要用到的pycharm和python等,但win7的默认浏览器为IE8,无法打开pycharm等下载界面,需安装其他浏览器(火狐、谷歌或edge等),这里选择安装最后一个支持win7的谷歌浏览器版本v109.0.5414.120。在win7中无法下载,可以在win11中下载好后,通过刚才配置的共享文件夹拷贝到win7系统中,然后直接运行安装包,等待1一分钟左右就安装好了。

至此,即可在win7系统直接下载Python及相关的库等,联网情况下的软件下载和库的安装这里不详细展开。

⭐️本文重点介绍离线环境下Python及相关的库的下载及离线安装。

二、在win7系统安装pycharm

1、下载Pycharm

根据Jetbrains官方信息,2021.1的运行环境是win8及以上的版本,因此2019.3是最后一个支持windows7的版本

Install PyCharm | PyCharmhttps://www.jetbrains/help/pycharm/2021.1/installation-guide.html打开老版本链接:

Other Versions - PyCharmhttps://www.jetbrains/pycharm/download/other.html下拉找到2019.3.5版本下载

下载好后,通过共享文件夹拷贝到win7电脑中。

2、安装Pycharm

(1)选中安装包,单击右键以管理员身份运行

(2)点击“是”

(3)点击“Next”

(4)虚拟系统里只有一个磁盘,因此不更改安装位置了,直接点Next

(5)勾上创建桌面快捷方式和添加环境变量,然后点击Next

(6)点击Install

(7)出现的弹窗点击确定

(8)等待安装完后点击Finish

(9)确定需保存的文件都保存关闭后重启win7系统

三、在win7系统安装Python

1、下载Python

虽然Python官网从3.9.0版本才开始标注不支持win7,但很多网友安装3.8的版本依然失败,因此我们下载Python3.7.6版本。

ps:要是想安装Anaconda,可以通过官方链接下载Index of /https://repo.anaconda/archive/亲测,最后支持win7的版本为Anaconda3-2020.02-Windows-x86_64(原始是2020.07版本后内置的Python为3.8,因此可能导致安装失败,2020.02版本内置的python为3.7.6)

(1)打开Python官网下载链接,选择3.7.6版本进行下载

python release downloadhttps://www.python/downloads/

下载好后,通过共享文件夹拷贝到win7电脑中。

2、安装Python

(1)选中安装包,单击右键“以管理员身份运行”

(2)点击“是”

(3)勾选添加环境变量,然后点击安装

(4)安装完成

(5)检查安装,Win+R打开运行,输入cmd,输入python,看到Python 3.7.6说明安装成功,关闭运行窗

四、Pycharm配置虚拟环境

1、打开pycharm软件

2、选择Do not import settings,点击OK

3、勾选同意协议,点击continue

4、点击Don't send

5、选择自己喜欢的UI风格(我这里选的Light亮色版),然后点击跳过提示

6、点击Create New Project

7、命名新项目的名称(创建后会再Location的位置新建项目为名称的文件夹),然后点击下方的三角形,确认编译环境,然后点击Create

8、弹出的Tips点击Close

五、下载PyQt相关的库

1、在联网电脑中按照“三、四”步骤安装Pycharm(可以安装新的版本,我这里直接使用我之前装的2023.3.3的版本做演示)、Python(必须是3.7版本)和配置虚拟环境

2、打开Pycharm中的Terminal窗口

3、下载sip,在Terminal窗口输入pip download sip==6.2.0,然后回车

4、下载完成后,会保存到当前项目的文件夹目录中(ps:因为我之前下载过,软件自动扫描到了缓存文件,因此显示using cached....,第一次下载的话会有进度条,需要等待一会儿)

5、下载PyQt5,在Terminal窗口输入pip download PyQt5==5.15.4,然后回车,等待下载完

6、下载PyQt5时,有个PyQt5-sip时压缩包的格式,需要将其打包编译为whl文件,但操作上比较麻烦,因此我们选择另一种方式:访问网站https://pypi,搜索PyQt5_sip,下载其它版本的PyQt5_sip,如PyQt5_sip-12.12.2-cp37-cp37m-win_amd64.whl(cp37表示兼容python-3.7.x)

下载完后替换之前的tar.gz文件

7、下载PyQt5-tools,在Terminal窗口输入pip download PyQt5-tools==5.15.4.3.2,然后回车,等待下载完

这里将PyQt5-sip-12.13.0.tar.gz又下载了一遍,可以忽略它

8、下载setuptools,在Terminal窗口输入pip download setuptools==42.0.0,然后回车,等待下载完

9、下载pyinstaller,在Terminal窗口输入pip download pyinstaller==4.10,然后回车,等待下载完

10、示例软件是用于串口通讯的,因此还需要下载pyserial,在Terminal窗口输入pip download pyserial==3.5,然后回车,等待下载完

至此,需要的库均已下完

六、win7系统中离线安装相关的库

1、打开win7系统中的Pycharm,点击Terminal,确定当前环境的路径文件夹

2、在该路径下新建ImportFiles文件夹,将第五步中下载的库均拷贝到该文件夹下

3、在Pycharm的Terminal中,使用cd命令导航到库所在的文件夹,然后逐一安装相应的库

cd C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles

pip install --no-index sip-6.2.0-cp37-cp37m-win_amd64.whl --find-links=C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles

pip install --no-index PyQt5-5.15.4-cp36.cp37.cp38.cp39-none-win_amd64.whl --find-links=C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles 

pip install --no-index pyqt5_tools-5.15.4.3.2-py3-none-any.whl --find-links=C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles

pip install --no-index pyinstaller-4.10-py3-none-win_amd64.whl --find-links=C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles

pip install --no-index setuptools-42.0.0-py2.py3-none-any.whl --find-links=C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles

pip install --no-index pyserial-3.5-py2.py3-none-any.whl --find-links=C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles

【结构说明】以上面第一个pip语句为例:

--no-index:指pip不从PyPI(即在线库)下载任何包

sip-6.2.0-cp37-cp37m-win_amd64.whl:指当前安装的库的文件名,必须包含.whl,

--find-links=C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles:指安装当前库需要的依赖库从“C:\Users\Fuwa7560\PycharmProjects\Test\ImportFiles”中寻找,例如安装该sip时需要toml和packaging两个库,pip会自动在指定文件夹中找到对应的whl文件进行安装,然后再安装sip

4、验证安装

在Pycharm中Python Console界面,输入import 库名称,回车,无报错表示安装成功

下面给个报错的示例,便于对比

至此,所有库安装完成,可以开始编程了。

七、编写串口通讯软件

本文的主要目的是捋清整个流程,因此本部分只做简单示例

1、在Pycharm的项目文件的venv文件夹下新建文件夹,然后新建一个空py文件

2、将下方代码复制到test01.py文件中

import sys
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QGroupBox, QComboBox, QPushButton, QTextEdit, QLineEdit,
                             QLabel, QFileDialog, QMessageBox)
from PyQt5.QtCore import Qt, QTimer, QDateTime
from PyQt5.QtGui import QTextCursor
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # 串口相关初始化
        self.serial1 = None
        self.serial2 = None
        self.file1 = None
        self.file2 = None
        self.is_recording1 = False
        self.is_recording2 = False
        self.save_path = ''

        # 数据缓冲相关
        self.buffer1 = bytearray()
        self.buffer2 = bytearray()
        self.timer1 = QTimer()
        self.timer2 = QTimer()
        self.timer_interval = 10

        # 初始化定时器
        self.timer1.setSingleShot(True)
        self.timer2.setSingleShot(True)
        self.timer1.timeout.connect(lambda: self.process_data(1))
        self.timer2.timeout.connect(lambda: self.process_data(2))

        # UI初始化
        self.initUI()
        self.refresh_ports()

    def initUI(self):
        self.setWindowTitle("双串口数据监控 - Fuwa2025")
        self.setFixedSize(1028, 700)

        main_widget = QWidget()
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

        # 串口配置区域
        serials_layout = QHBoxLayout()
        self.serial1_group = self.create_serial_group("串口1")
        self.serial2_group = self.create_serial_group("串口2")
        serials_layout.addWidget(self.serial1_group)
        serials_layout.addWidget(self.serial2_group)
        main_layout.addLayout(serials_layout)

        # 数据显示区域
        data_display_layout = QHBoxLayout()
        self.text_edit1 = QTextEdit()
        self.text_edit2 = QTextEdit()
        for edit in [self.text_edit1, self.text_edit2]:
            edit.setStyleSheet("border: 1px solid gray;")
            edit.document().setMaximumBlockCount(100)
            edit.setLineWrapMode(QTextEdit.WidgetWidth)
        data_display_layout.addWidget(self.text_edit1)
        data_display_layout.addWidget(self.text_edit2)
        main_layout.addLayout(data_display_layout)

        # 保存路径
        path_layout = QHBoxLayout()
        self.path_edit = QLineEdit()
        self.path_edit.setReadOnly(True)
        browse_btn = QPushButton("选择文件夹")
        browse_btn.clicked.connect(self.browse_save_path)
        path_layout.addWidget(QLabel("保存路径:"))
        path_layout.addWidget(self.path_edit)
        path_layout.addWidget(browse_btn)
        main_layout.addLayout(path_layout)

    def create_serial_group(self, title):
        group = QGroupBox(title)
        group.setStyleSheet("QGroupBox { border: 1px solid gray; }")
        layout = QVBoxLayout()

        # 端口选择行
        port_layout = QHBoxLayout()
        port_combo = QComboBox()
        refresh_btn = QPushButton("刷新")
        refresh_btn.clicked.connect(self.refresh_ports)
        port_layout.addWidget(QLabel("端口:"), stretch=1)
        port_layout.addWidget(port_combo, stretch=3)
        port_layout.addWidget(refresh_btn, stretch=1)

        # 参数配置
        param_layout = QVBoxLayout()

        # 波特率行
        baud_layout = QHBoxLayout()
        baud_combo = QComboBox()
        baud_combo.addItems(['4800', '9600', '19200', '38400', '57600', '115200'])
        baud_layout.addWidget(QLabel("波特率:"), stretch=1)
        baud_layout.addWidget(baud_combo, stretch=3)

        # 数据位行
        data_layout = QHBoxLayout()
        data_bits_combo = QComboBox()
        data_bits_combo.addItems(['5', '6', '7', '8'])
        data_bits_combo.setCurrentIndex(3)
        data_layout.addWidget(QLabel("数据位:"), stretch=1)
        data_layout.addWidget(data_bits_combo, stretch=3)

        # 停止位行
        stop_layout = QHBoxLayout()
        stop_bits_combo = QComboBox()
        stop_bits_combo.addItems(['1', '1.5', '2'])
        stop_layout.addWidget(QLabel("停止位:"), stretch=1)
        stop_layout.addWidget(stop_bits_combo, stretch=3)

        # 校验位行
        parity_layout = QHBoxLayout()
        parity_combo = QComboBox()
        parity_combo.addItems(['None', 'Even', 'Odd', 'Space', 'Mark'])
        parity_layout.addWidget(QLabel("校验位:"), stretch=1)
        parity_layout.addWidget(parity_combo, stretch=3)

        # 控制按钮行
        btn_layout = QHBoxLayout()
        open_btn = QPushButton("打开端口")
        open_btn.clicked.connect(lambda: self.toggle_serial(group))
        start_btn = QPushButton("开始记录")
        start_btn.clicked.connect(lambda: self.start_recording(group))
        stop_btn = QPushButton("停止记录")
        stop_btn.clicked.connect(lambda: self.stop_recording(group))

        btn_layout.addWidget(open_btn)
        btn_layout.addWidget(start_btn)
        btn_layout.addWidget(stop_btn)

        # 整合布局
        param_layout.addLayout(baud_layout)
        param_layout.addLayout(data_layout)
        param_layout.addLayout(stop_layout)
        param_layout.addLayout(parity_layout)
        param_layout.addLayout(btn_layout)

        layout.addLayout(port_layout)
        layout.addLayout(param_layout)
        group.setLayout(layout)

        # 保存控件引用
        if title == "串口1":
            self.port_combo1 = port_combo
            self.baud_combo1 = baud_combo
            self.data_bits_combo1 = data_bits_combo
            self.stop_bits_combo1 = stop_bits_combo
            self.parity_combo1 = parity_combo
            self.open_btn1 = open_btn
        else:
            self.port_combo2 = port_combo
            self.baud_combo2 = baud_combo
            self.data_bits_combo2 = data_bits_combo
            self.stop_bits_combo2 = stop_bits_combo
            self.parity_combo2 = parity_combo
            self.open_btn2 = open_btn

        return group

    def refresh_ports(self):
        ports = QSerialPortInfo.availablePorts()
        self.port_combo1.clear()
        self.port_combo2.clear()
        for port in ports:
            self.port_combo1.addItem(port.portName())
            self.port_combo2.addItem(port.portName())

    def toggle_serial(self, group):
        if group.title() == "串口1":
            serial = self.serial1
            port_combo = self.port_combo1
            baud_combo = self.baud_combo1
            data_bits_combo = self.data_bits_combo1
            stop_bits_combo = self.stop_bits_combo1
            parity_combo = self.parity_combo1
            open_btn = self.open_btn1
        else:
            serial = self.serial2
            port_combo = self.port_combo2
            baud_combo = self.baud_combo2
            data_bits_combo = self.data_bits_combo2
            stop_bits_combo = self.stop_bits_combo2
            parity_combo = self.parity_combo2
            open_btn = self.open_btn2

        if not serial or not serial.isOpen():
            # 打开串口
            port_name = port_combo.currentText()
            baud_rate = int(baud_combo.currentText())
            data_bits = data_bits_combo.currentText()
            stop_bits = stop_bits_combo.currentText()
            parity = parity_combo.currentText()

            data_bits_map = {
                '5': QSerialPort.Data5,
                '6': QSerialPort.Data6,
                '7': QSerialPort.Data7,
                '8': QSerialPort.Data8
            }
            stop_bits_map = {
                '1': QSerialPort.OneStop,
                '1.5': QSerialPort.OneAndHalfStop,
                '2': QSerialPort.TwoStop
            }
            parity_map = {
                'None': QSerialPort.NoParity,
                'Even': QSerialPort.EvenParity,
                'Odd': QSerialPort.OddParity,
                'Space': QSerialPort.SpaceParity,
                'Mark': QSerialPort.MarkParity
            }

            serial = QSerialPort()
            serial.setPortName(port_name)
            serial.setBaudRate(baud_rate)
            serial.setDataBits(data_bits_map[data_bits])
            serial.setStopBits(stop_bits_map[stop_bits])
            serial.setParity(parity_map[parity])

            if serial.open(QSerialPort.ReadWrite):
                if group.title() == "串口1":
                    self.serial1 = serial
                else:
                    self.serial2 = serial
                serial.readyRead.connect(lambda: self.read_data(serial))
                open_btn.setText("关闭端口")
            else:
                QMessageBox.critical(self, "错误", "无法打开串口")
        else:
            # 关闭串口
            serial.close()
            if group.title() == "串口1":
                self.serial1 = None
            else:
                self.serial2 = None
            open_btn.setText("打开端口")

    def read_data(self, serial):
        if serial == self.serial1:
            self.buffer1.extend(serial.readAll().data())
            self.timer1.start(self.timer_interval)
        elif serial == self.serial2:
            self.buffer2.extend(serial.readAll().data())
            self.timer2.start(self.timer_interval)

    def process_data(self, serial_num):
        timestamp = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz")
        serial = self.serial1 if serial_num == 1 else self.serial2
        port_name = serial.portName() if serial else f"未知端口{serial_num}"

        if serial_num == 1:
            data = bytes(self.buffer1)
            self.buffer1.clear()
            text_edit = self.text_edit1
            is_recording = self.is_recording1
            file = self.file1
        else:
            data = bytes(self.buffer2)
            self.buffer2.clear()
            text_edit = self.text_edit2
            is_recording = self.is_recording2
            file = self.file2

        if data:
            try:
                text = data.decode('utf-8').strip()
            except UnicodeDecodeError:
                text = data.decode('latin-1').strip()

            # 格式化为带实际端口号的记录
            formatted_text = f"[{timestamp}] [{port_name}] {text}\n"

            # 更新显示
            self.append_text(text_edit, formatted_text)

            # 写入文件
            if is_recording and file:
                file.write(formatted_text)
                file.flush()

    def append_text(self, text_edit, text):
        text_edit.moveCursor(QTextCursor.End)
        text_edit.insertPlainText(text)
        text_edit.ensureCursorVisible()

    def browse_save_path(self):
        path = QFileDialog.getExistingDirectory(self, "选择保存文件夹")
        if path:
            self.save_path = path
            self.path_edit.setText(path)

    def start_recording(self, group):
        if not self.save_path:
            QMessageBox.warning(self, "警告", "请先选择保存文件夹")
            return

        current_time = QDateTime.currentDateTime().toString("yyyyMMdd_HHmmss")
        if group.title() == "串口1":
            if self.serial1 and self.serial1.isOpen():
                port_name = self.serial1.portName().replace(':', '_')
                filename = f"{port_name}_{current_time}.txt"
                try:
                    self.file1 = open(os.path.join(self.save_path, filename), 'a')
                    self.is_recording1 = True
                except Exception as e:
                    QMessageBox.critical(self, "错误", f"创建文件失败: {e}")
        else:
            if self.serial2 and self.serial2.isOpen():
                port_name = self.serial2.portName().replace(':', '_')
                filename = f"{port_name}_{current_time}.txt"
                try:
                    self.file2 = open(os.path.join(self.save_path, filename), 'a')
                    self.is_recording2 = True
                except Exception as e:
                    QMessageBox.critical(self, "错误", f"创建文件失败: {e}")

    def stop_recording(self, group):
        if group.title() == "串口1":
            if self.file1:
                self.file1.close()
                self.file1 = None
            self.is_recording1 = False
        else:
            if self.file2:
                self.file2.close()
                self.file2 = None
            self.is_recording2 = False

    def closeEvent(self, event):
        reply = QMessageBox.question(self, '确认退出', '确定要关闭程序吗?',
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.serial1 and self.serial1.isOpen():
                self.serial1.close()
            if self.serial2 and self.serial2.isOpen():
                self.serial2.close()
            if self.file1:
                self.file1.close()
            if self.file2:
                self.file2.close()
            event.accept()
        else:
            event.ignore()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())

3、运行测试,在代码界面单击右键,点击Run ”test01“,软件正常运行,无报错即表示代码初步没问题

4、通讯测试。可以直接将串口接在硬件主机,通过串口或USB映射到虚拟机Win7系统中;也可以通过虚拟串口软件进行测试。这里选择第二种,便于测试。

(1)下载安装虚拟串口软件和网络调试助手

①有人虚拟串口软件USR-VCOM:虚拟串口服务软件(USR-VCOM)可将TCP/IP连接、UDP广播,映射成本机虚拟串口,应用程序通过访问虚拟串口,就可以完成远程控制、数据传输等功能。下载链接:

②NetAssist网络调试助手:Windows平台下的TCP/IP网络调试工具,支持UDP/TCP/MQTT等应用协议,下载地址:

③下载完后通过共享文件夹拷贝到win7系统中,直接安装即可

(2)设置测试数据输出

①新建虚拟串口

②利用网络助手循环发送测试数据

③检查虚拟串口助手连接和数据接收情况

(3)Pycharm中运行代码,打开界面,设置串口,测试通讯及数据保存情况

测试通过后,即可打包为可执行文件。

八、pyinstaller打包成exe

1、打开Pycharm的Terminal窗口,输入pyinstaller --onefile C:\Users\Fuwa7560\PycharmProjects\Test\venv\Serial_test\test01.py,然后回车,

参数说明:

① --onefile:生成单个exe文件

C:\Users\Fuwa7560\PycharmProjects\Test\venv\Serial_test\test01.py:需要打包的py文件的路径

③默认显示控制窗口(关闭控制窗口需要加--noconsole的参数)

当显示“Building EXE from EXE-00.toc completed successfully.”表示打包完成

2、试运行新生成的exe

(1)exe文件在运行路径下的dist文件夹内,

(2)运行正常,打包完成,exe文件可以拷贝到其他win7以上的电脑使用

发布者:admin,转转请注明出处:http://www.yc00.com/web/1754637925a5185387.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信