PyQt5开发DSP SCI串口通讯

时间:2021-08-26 23:01:22

一、开发环境搭建

软件:Python3.6+Eric6+PyQt5+CCS3.3

硬件:TI的F2812+仿真器+超声电机+编码器+超声电机驱动


二、功能需求及任务安排

前几届师兄,做DSP SCI串口通讯使用的是MATLAB GUI开发的。如图1所示界面

PyQt5开发DSP SCI串口通讯




















                                                                                                              图1 matlab gui 界面

1、需求改进:a)可操作性:需要装matlab软件才能打开串口通讯界面,之前也将m文件转化成可执行程序exe文件,但是问题很多,慢、根本打不开。

       b)保存数据:存取数据的人工操作流程太复杂,先要选择特定的文件存取,然后在运行程序改变文件类型即数据格式,在运行matlab程序画图。

       c)界面风格:界面部分设计不合理,文本框较大,且没有应用任何功能,部分按钮、复选框也出现类似情况。

       d)功能测试:无串口判断功能以及提示信息,发送数据显示格式问题,没有实时显示日期、时间功能。

2、基于以上需求,采用PyQt5开发类似功能的串口通讯软件要实现一下功能:

       a)基本功能:通过串口通讯软件实现对超声电机的控制,启动、停止、施加各种控制率的情况下运动,并作出俯仰、沉浮、控制面转角的控制曲线。

       b)辅助功能:以10进制和16进制保存采集的运动数据,直接显示可编辑的运动曲线图,实时显示时间日期,串口数据的设定以及串口的检测、提醒。

3、开发进度计划

       a)查找资料安装必要的软件、包、插件,配置开发环境

       b)学习PyQt5,开发简单的串口通讯界面

       c)开发能与DSP实现通讯的串口软件

       d)测试软件功能,完善软件


三、实践开发

1、安装软件:在网上找资料关于用python+Qt开发的并不多,大部分是用c++开发Qt的,不过依然可以借鉴。网上有Anaconda3 + PyQt5 + Eric6配置环境安装的教程,但Anaconda3总是安装不好,放弃了这种形式。直接安装了PyQt5 + Eric6,关于很多模块直接在控制台采用pip安装。用到什么就安装什么。

2、设计基本的串口通讯界面。创建新的工程,选择自己放置工程的文件夹。创建后__init__文件会自动生成在Project-view的Sources一栏中,然后点击Sourcesl栏旁边的Forms栏,在下方右键后选择new forms。在选择窗口的类型为MainWindow,而不是Dialog。否则无法实现最小化功能。添加空间时要注意使用布局,把同一类型功能放在一起、添加标题。保持对其。其控件的属性基本不用设计。点击保存按钮关掉。在forms栏中多了个xxx.ui文件。

3、程序框架搭建。右键选择xxx.ui文件,然后选择compile form。编译成功后,在sources栏中会出现UI_xxx.py文件。如下是我生成的文件,但这不是初始版本,后期增加了很多槽函数和一些功能。槽函数很简单你可以自己在设计见面时添加,也可生成程序后自己编程添加。可以参考博客:http://blog.csdn.net/liuyukuan/article/details/50859722

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'E:\study_python\sec_step\serial_gui_A1\SerialGuiA.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
# edited by Rex.Rui
# date : 17.12.20
# versions: V_A_1
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
class Ui_Dialog(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("Dialog")
MainWindow.setEnabled(True)
MainWindow.resize(900, 500)
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(224, 255, 243))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(224, 255, 243))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(224, 255, 243))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(224, 255, 243))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
MainWindow.setPalette(palette)
font = QtGui.QFont()
font.setFamily("微软雅黑")
font.setPointSize(8)
font.setBold(True)
font.setWeight(75)
MainWindow.setFont(font)
MainWindow.setAutoFillBackground(False)
# MainWindow.setSizeGripEnabled(False)
self.groupBox_2 = QtWidgets.QGroupBox(MainWindow)
self.groupBox_2.setGeometry(QtCore.QRect(180, 110, 141, 121))
self.groupBox_2.setAutoFillBackground(True)
self.groupBox_2.setAlignment(QtCore.Qt.AlignCenter)
self.groupBox_2.setFlat(False)
self.groupBox_2.setCheckable(False)
self.groupBox_2.setObjectName("groupBox_2")
self.groupBox_5 = QtWidgets.QGroupBox(MainWindow)
self.groupBox_5.setGeometry(QtCore.QRect(30, 430, 281, 61))
self.groupBox_5.setAlignment(QtCore.Qt.AlignCenter)
self.groupBox_5.setObjectName("groupBox_5")
self.pushButton_2 = QtWidgets.QPushButton(self.groupBox_2)
self.pushButton_2.setGeometry(QtCore.QRect(20, 20, 111, 31))
font = QtGui.QFont()
font.setPointSize(10)
self.pushButton_2.setFont(font)
self.pushButton_2.setObjectName("pushButton_2")
self.checkBox = QtWidgets.QCheckBox(self.groupBox_2)
self.checkBox.setGeometry(QtCore.QRect(30, 50, 101, 31))
self.checkBox.setObjectName("checkBox")
self.checkBox_2 = QtWidgets.QCheckBox(self.groupBox_2)
self.checkBox_2.setGeometry(QtCore.QRect(30, 80, 71, 16))
self.checkBox_2.setObjectName("checkBox_2")
self.groupBox = QtWidgets.QGroupBox(MainWindow)
self.groupBox.setGeometry(QtCore.QRect(30, 10, 151, 221))
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(215, 255, 169))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(215, 255, 169))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(215, 255, 169))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(215, 255, 169))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush)
brush = QtGui.QBrush(QtGui.QColor(215, 255, 169))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(215, 255, 169))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush)
self.groupBox.setPalette(palette)
self.groupBox.setLayoutDirection(QtCore.Qt.LeftToRight)
self.groupBox.setAutoFillBackground(True)
self.groupBox.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
self.groupBox.setFlat(False)
self.groupBox.setCheckable(False)
self.groupBox.setObjectName("groupBox")
self.comboBox = QtWidgets.QComboBox(self.groupBox)
self.comboBox.setGeometry(QtCore.QRect(70, 20, 71, 31))
self.comboBox.setObjectName("comboBox")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.comboBox.addItem("")
self.comboBox_2 = QtWidgets.QComboBox(self.groupBox)
self.comboBox_2.setGeometry(QtCore.QRect(70, 50, 71, 31))
self.comboBox_2.setObjectName("comboBox_2")
self.comboBox_2.addItem("")
self.comboBox_2.addItem("")
self.comboBox_2.addItem("")
self.comboBox_2.addItem("")
self.comboBox_2.addItem("")
self.comboBox_3 = QtWidgets.QComboBox(self.groupBox)
self.comboBox_3.setGeometry(QtCore.QRect(70, 80, 71, 31))
self.comboBox_3.setObjectName("comboBox_3")
self.comboBox_3.addItem("")
self.comboBox_3.addItem("")
self.comboBox_3.addItem("")
self.comboBox_3.addItem("")
self.comboBox_3.addItem("")
self.comboBox_4 = QtWidgets.QComboBox(self.groupBox)
self.comboBox_4.setGeometry(QtCore.QRect(70, 110, 71, 31))
self.comboBox_4.setObjectName("comboBox_4")
self.comboBox_4.addItem("")
self.comboBox_4.addItem("")
self.comboBox_4.addItem("")
self.comboBox_4.addItem("")
self.comboBox_5 = QtWidgets.QComboBox(self.groupBox)
self.comboBox_5.setGeometry(QtCore.QRect(70, 140, 71, 31))
self.comboBox_5.setObjectName("comboBox_5")
self.comboBox_5.addItem("")
self.comboBox_5.addItem("")
self.label = QtWidgets.QLabel(self.groupBox)
self.label.setGeometry(QtCore.QRect(20, 20, 41, 21))
self.label.setTextFormat(QtCore.Qt.PlainText)
self.label.setScaledContents(False)
self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(self.groupBox)
self.label_2.setGeometry(QtCore.QRect(20, 60, 54, 12))
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(self.groupBox)
self.label_3.setGeometry(QtCore.QRect(20, 90, 54, 12))
self.label_3.setObjectName("label_3")
self.label_4 = QtWidgets.QLabel(self.groupBox)
self.label_4.setGeometry(QtCore.QRect(20, 120, 54, 12))
self.label_4.setObjectName("label_4")
self.label_5 = QtWidgets.QLabel(self.groupBox)
self.label_5.setGeometry(QtCore.QRect(20, 150, 54, 12))
self.label_5.setObjectName("label_5")
self.pushButton = QtWidgets.QPushButton(self.groupBox)
self.pushButton.setGeometry(QtCore.QRect(10, 180, 61, 31))
self.pushButton.setObjectName("pushButton")
self.pushButton_17 = QtWidgets.QPushButton(self.groupBox)
self.pushButton_17.setGeometry(QtCore.QRect(80, 180, 61, 31))
self.pushButton_17.setObjectName("pushButton_17")
self.groupBox_4 = QtWidgets.QGroupBox(MainWindow)
self.groupBox_4.setGeometry(QtCore.QRect(30, 350, 281, 81))
self.groupBox_4.setAutoFillBackground(True)
self.groupBox_4.setAlignment(QtCore.Qt.AlignCenter)
self.groupBox_4.setObjectName("groupBox_4")
self.pushButton_6 = QtWidgets.QPushButton(self.groupBox_4)
self.pushButton_6.setGeometry(QtCore.QRect(10, 20, 75, 23))
self.pushButton_6.setObjectName("pushButton_6")
self.pushButton_7 = QtWidgets.QPushButton(self.groupBox_4)
self.pushButton_7.setGeometry(QtCore.QRect(100, 20, 75, 23))
self.pushButton_7.setObjectName("pushButton_7")
self.pushButton_8 = QtWidgets.QPushButton(self.groupBox_4)
self.pushButton_8.setGeometry(QtCore.QRect(10, 50, 75, 23))
self.pushButton_8.setObjectName("pushButton_8")
self.pushButton_9 = QtWidgets.QPushButton(self.groupBox_4)
self.pushButton_9.setGeometry(QtCore.QRect(100, 50, 75, 23))
self.pushButton_9.setObjectName("pushButton_9")
self.pushButton_10 = QtWidgets.QPushButton(self.groupBox_4)
self.pushButton_10.setGeometry(QtCore.QRect(190, 20, 75, 23))
self.pushButton_10.setObjectName("pushButton_10")
self.pushButton_11 = QtWidgets.QPushButton(self.groupBox_4)
self.pushButton_11.setGeometry(QtCore.QRect(190, 50, 75, 23))
self.pushButton_11.setObjectName("pushButton_11")
self.pushButton_12 = QtWidgets.QPushButton(self.groupBox_5)
self.pushButton_12.setGeometry(QtCore.QRect(10, 20, 75, 30))
self.pushButton_12.setObjectName("pushButton_12")
self.groupBox_3 = QtWidgets.QGroupBox(MainWindow)
self.groupBox_3.setGeometry(QtCore.QRect(30, 240, 281, 111))
self.groupBox_3.setAutoFillBackground(True)
self.groupBox_3.setAlignment(QtCore.Qt.AlignCenter)
self.groupBox_3.setObjectName("groupBox_3")
self.checkBox_3 = QtWidgets.QCheckBox(self.groupBox_3)
self.checkBox_3.setGeometry(QtCore.QRect(10, 20, 71, 16))
self.checkBox_3.setObjectName("checkBox_3")
self.checkBox_4 = QtWidgets.QCheckBox(self.groupBox_3)
self.checkBox_4.setGeometry(QtCore.QRect(80, 20, 91, 16))
self.checkBox_4.setObjectName("checkBox_4")
self.pushButton_3 = QtWidgets.QPushButton(self.groupBox_3)
self.pushButton_3.setGeometry(QtCore.QRect(190, 20, 75, 23))
self.pushButton_3.setObjectName("pushButton_3")
self.pushButton_4 = QtWidgets.QPushButton(self.groupBox_3)
self.pushButton_4.setGeometry(QtCore.QRect(190, 50, 75, 23))
self.pushButton_4.setObjectName("pushButton_4")
self.label_6 = QtWidgets.QLabel(self.groupBox_3)
self.label_6.setGeometry(QtCore.QRect(10, 50, 81, 16))
self.label_6.setObjectName("label_6")
self.textEdit = QtWidgets.QTextEdit(self.groupBox_3)
self.textEdit.setGeometry(QtCore.QRect(80, 40, 71, 31))
self.textEdit.setObjectName("textEdit")
self.label_7 = QtWidgets.QLabel(self.groupBox_3)
self.label_7.setGeometry(QtCore.QRect(160, 50, 54, 12))
font = QtGui.QFont()
font.setPointSize(10)
self.label_7.setFont(font)
self.label_7.setObjectName("label_7")
self.label_8 = QtWidgets.QLabel(self.groupBox_3)
self.label_8.setGeometry(QtCore.QRect(10, 80, 54, 12))
self.label_8.setObjectName("label_8")
self.label_9 = QtWidgets.QLabel(self.groupBox_3)
self.label_9.setGeometry(QtCore.QRect(40, 80, 54, 12))
self.label_9.setObjectName("label_9")
self.label_10 = QtWidgets.QLabel(self.groupBox_3)
self.label_10.setGeometry(QtCore.QRect(100, 80, 54, 12))
self.label_10.setObjectName("label_10")
self.label_11 = QtWidgets.QLabel(self.groupBox_3)
self.label_11.setGeometry(QtCore.QRect(140, 80, 54, 12))
self.label_11.setObjectName("label_11")
self.pushButton_5 = QtWidgets.QPushButton(self.groupBox_3)
self.pushButton_5.setGeometry(QtCore.QRect(190, 80, 75, 23))
self.pushButton_5.setObjectName("pushButton_5")
self.textEdit_2 = QtWidgets.QTextEdit(MainWindow)
self.textEdit_2.setGeometry(QtCore.QRect(340, 10, 550, 160*3))
self.textEdit_2.setObjectName("textEdit_2")
self.pushButton_15 = QtWidgets.QPushButton(self.groupBox_5)
self.pushButton_15.setGeometry(QtCore.QRect(100, 20, 75, 30))
self.pushButton_15.setObjectName("pushButton_15")
self.pushButton_16 = QtWidgets.QPushButton(self.groupBox_5)
self.pushButton_16.setGeometry(QtCore.QRect(190, 20, 75, 30))
self.pushButton_16.setObjectName("pushButton_16")
self.textEdit_3 = QtWidgets.QTextEdit(MainWindow)
self.textEdit_3.setGeometry(QtCore.QRect(185,80, 137, 30))
self.textEdit_3.setObjectName("textEdit_3")
# design LCD screen for date and time
self.lcdNumber_1=QtWidgets.QLCDNumber(MainWindow)
self.lcdNumber_1.setGeometry(QtCore.QRect(185,20, 137, 30))
self.lcdNumber_1.setObjectName("lcdNumber_1")
self.lcdNumber_1.setDigitCount(10) # the number of the display
self.lcdNumber_1.setMode(QLCDNumber.Dec) # the data format of the display
self.lcdNumber_1.setSegmentStyle(QLCDNumber.Filled) #the style of the display
self.lcdNumber=QtWidgets.QLCDNumber(MainWindow)
self.lcdNumber.setGeometry(QtCore.QRect(185,52, 137, 27))
self.lcdNumber.setObjectName("lcdNumber")
self.lcdNumber.setDigitCount(10) # the number of the display
self.lcdNumber.setMode(QLCDNumber.Dec) # the data format of the display
self.lcdNumber.setSegmentStyle(QLCDNumber.Filled) #the style of the display
#self.lcdNumber.set
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.start()
self.groupBox_2.raise_()
self.groupBox_2.raise_()
self.groupBox.raise_()
self.groupBox_4.raise_()
self.groupBox_3.raise_()
self.textEdit_2.raise_()
self.groupBox_5.raise_()
self.textEdit_3.raise_()
self.label.setBuddy(MainWindow)
MainWindow.setFixedSize(MainWindow.width(), MainWindow.height())
self.retranslateUi(MainWindow)
self.comboBox_4.setCurrentIndex(2)
QtCore.QMetaObject.connectSlotsByName(MainWindow)

self.setWindowIcon(QIcon(r'C:\Users\Administrator\Desktop\label.png'))


def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("Dialog", "串口通讯软件"))
self.groupBox_2.setTitle(_translate("Dialog", "数据接收"))
self.pushButton_2.setText(_translate("Dialog", "清空接收区"))
self.checkBox.setText(_translate("Dialog", "十六进制显示"))
self.checkBox_2.setText(_translate("Dialog", " 复制数据"))
self.groupBox.setTitle(_translate("Dialog", "串口参数"))
self.comboBox.setItemText(0, _translate("Dialog", "COM1"))
self.comboBox.setItemText(1, _translate("Dialog", "COM2"))
self.comboBox.setItemText(2, _translate("Dialog", "COM3"))
self.comboBox.setItemText(3, _translate("Dialog", "COM4"))
self.comboBox.setItemText(4, _translate("Dialog", "COM5"))
self.comboBox.setItemText(5, _translate("Dialog", "COM6"))
self.comboBox_2.setCurrentText(_translate("Dialog", "9600"))
self.comboBox_2.setItemText(0, _translate("Dialog", "4800"))
self.comboBox_2.setItemText(1, _translate("Dialog", "9600"))
self.comboBox_2.setItemText(2, _translate("Dialog", "19200"))
self.comboBox_2.setItemText(3, _translate("Dialog", "38400"))
self.comboBox_2.setItemText(4, _translate("Dialog", "115200"))
self.comboBox_3.setItemText(0, _translate("Dialog", "N"))
self.comboBox_3.setItemText(1, _translate("Dialog", "E"))
self.comboBox_3.setItemText(2, _translate("Dialog", "O"))
self.comboBox_3.setItemText(3, _translate("Dialog", "M"))
self.comboBox_3.setItemText(4, _translate("Dialog", "S"))
self.comboBox_4.setItemText(0, _translate("Dialog", "6"))
self.comboBox_4.setItemText(1, _translate("Dialog", "7"))
self.comboBox_4.setItemText(2, _translate("Dialog", "8"))
self.comboBox_4.setItemText(3, _translate("Dialog", "9"))
self.comboBox_5.setItemText(0, _translate("Dialog", "1"))
self.comboBox_5.setItemText(1, _translate("Dialog", "2"))
self.label.setText(_translate("Dialog", " 串口"))
self.label_2.setText(_translate("Dialog", "波特率"))
self.label_3.setText(_translate("Dialog", "检验位"))
self.label_4.setText(_translate("Dialog", "数据位"))
self.label_5.setText(_translate("Dialog", "停止位"))
self.pushButton.setText(_translate("Dialog", "打开串口"))
self.pushButton_17.setText(_translate("Dialog", "关闭串口"))
self.groupBox_4.setTitle(_translate("Dialog", "电机控制"))
self.pushButton_6.setText(_translate("Dialog", "数据传输"))
self.pushButton_7.setText(_translate("Dialog", "电机启动"))
self.pushButton_8.setText(_translate("Dialog", "Law1"))
self.pushButton_9.setText(_translate("Dialog", "Law2"))
self.pushButton_10.setText(_translate("Dialog", "电机停止"))
self.pushButton_11.setText(_translate("Dialog", "Law3"))
self.pushButton_12.setText(_translate("Dialog", "绘制曲线"))
self.groupBox_3.setTitle(_translate("Dialog", "数据发送"))
self.checkBox_3.setText(_translate("Dialog", "自动发送"))
self.checkBox_4.setText(_translate("Dialog", "十六进制发送"))
self.pushButton_3.setText(_translate("Dialog", "串口设定"))
self.pushButton_4.setText(_translate("Dialog", "手动发送"))
self.label_6.setText(_translate("Dialog", "自动发送周期:"))
self.textEdit.setHtml(_translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'微软雅黑\'; font-size:8pt; font-weight:600; font-style:normal;\">\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))
self.label_7.setText(_translate("Dialog", "ms"))
self.label_8.setText(_translate("Dialog", "接收:"))
self.label_9.setText(_translate("Dialog", " 0"))
self.label_10.setText(_translate("Dialog", "发送:"))
self.label_11.setText(_translate("Dialog", "0"))
self.pushButton_5.setText(_translate("Dialog", "计数清零"))
self.groupBox_5.setTitle(_translate("Dialog", "绘图与保存数据"))
self.pushButton_15.setText(_translate("Dialog", "显示图形"))
self.pushButton_16.setText(_translate("Dialog", "保存数据"))
self.pushButton.clicked.connect(self.port_open) #edit the private slots of pushButton
self.pushButton_2.clicked.connect(self.clean_recdata)
self.pushButton_3.clicked.connect(self.port_check)
self.pushButton_17.clicked.connect(self.port_close)
self.pushButton_4.clicked.connect(self.send_data)
self.pushButton_5.clicked.connect(self.clean_data)
self.pushButton_12.clicked.connect(self.ui_plot)
self.pushButton_16.clicked.connect(self.data_write)
self.pushButton_10.clicked.connect(self.motor_stop)
self.pushButton_6.clicked.connect(self.data_transfer)
self.pushButton_7.clicked.connect(self.motor_start)
self.pushButton_8.clicked.connect(self.law1control)
self.pushButton_9.clicked.connect(self.law2control)
self.pushButton_11.clicked.connect(self.law3control)
self.pushButton_15.clicked.connect(self.windows_plot)
self.pushButton_7.clicked.connect(self.deprint)
self.timer.timeout.connect(self.onTimerOut)
self.textEdit_3.setHtml(_translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'微软雅黑\'; font-size:8pt; font-weight:600; font-style:normal;\">\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))


if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_Dialog()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
 

此时只是创建了界面的程序,很多功能实现还是自己编写程序。如何让自己编写的程序和界面生成的程序联系到一起呢。我们选择Forms栏中生成的xxx.ui文件,右键选择generate dialog code,然后会出现新建对话框类,填写类名称和文件名称(xxxx)、路径,确认后。在Sources栏中出现xxxx.py文件。打开次文件

# -*- coding: utf-8 -*-

"""
Module implementing MainWindow.
"""

from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QMainWindow

from .Ui_123 import Ui_MainWindow


class MainWindow(QMainWindow, Ui_MainWindow):
"""
Class documentation goes here.
"""
def __init__(self, parent=None):
"""
Constructor

@param parent reference to the parent widget
@type QWidget
"""
super(MainWindow, self).__init__(parent)
self.setupUi(self)

程序的基本框架已经生成。但无法运行,需要添加、修改才能运行。上面的程序段不是本次开发的程序,因为在写博客时,此程序大部分被修改了,这只是基本框架

a)将from .Ui_123 import Ui_MainWindos  这一句中Ui前面的“.”去除。

b) 在末尾增加如下程序:

if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mygui = MainWindow() # set a GUI object to show it
mygui.show()
sys.exit(app.exec_())
同时在程序开头导入模块处增加:

from PyQt5 import  QtWidgets
这样应用程序的框架搭建基本完成。在工程文件夹下我们有一个ui文件,两个py文件,以py文件是界面文件,另一个文件为主程序文件。还有一个__init__.py文件。

3、串口通讯功能实现

在应用控制台 pip install pyserial ,安装串口通讯模块。其应用参见官网:https://pypi.python.org/pypi/pyserial

创建一个串口通讯的对象:

ser = serial.Serial() # establish a serial

定义 串口开启函数port_open()   串口关闭函数port_close()  串口检查函数port_check(),他们分别作为打开串口按钮、关闭串口按钮、串口设定按钮的 clicked事件的槽函数。

例如,设定打开串口按钮单击的槽函数,意思只要单击打开串口立即执行它所连接的函数。

        self.pushButton.clicked.connect(self.port_open)  #edit the private slots of pushButton 
此程序段写在Ui_xxx.py界面程序中,然后在主程序的类下定义port_open()函数

    def port_open(self):
"""
open and set the serial
"""
self.ser.port = self.comboBox.currentText() # set parameters of the serial communication
self.ser.baudrate = int(self.comboBox_2.currentText())
self.ser.parity = self.comboBox_3.currentText()
self.ser.bytesize = int(self.comboBox_4.currentText())
self.ser.stopbits = int(self.comboBox_5.currentText())
self.ser.open()
if(self.ser.isOpen()):
self.pushButton.setEnabled(False)
self.textEdit_3.setText("打开成功")
else:
self.textEdit_3.setText("打开失败")
此函数内容初始化了串口的各项参数,COM段、波特率等等参数设定在组合框中,通过下拉确定。然后打开串口ser.open()。最后检查串口是否打开,如何打开成功则,让打开串口按钮失效并输出提醒文字到文本编辑框。

    def port_close(self):
"""
close and display the serial
"""
self.ser.close()
if(self.ser.isOpen()):
self.textEdit_3.setText("关闭失败")
else:
self.textEdit_3.setText("关闭成功")
self.pushButton.setEnabled(True)
关闭串口函数,关闭成功后让使能打开串口按钮

    def port_check(self):
Com_List = []
port_list = list(serial.tools.list_ports.comports())
self.comboBox.clear()
for port in port_list:
Com_List.append(port[0])
self.comboBox.addItem(port[0])
self.textEdit_3.setText( ''.join(Com_List))
if(len(Com_List) == 0):
self.textEdit_3.setText("没有串口")
在程序之前要导入串口检查工具模块

import serial.tools.list_ports
如果存在可用串口,则直接设定COM端复选框的参数,并输出到文本编辑框上。固由于此函数的存在,打开软件后只要点击串口设定按钮即可完成COM端的检查、设定,相比之前的版本,操作更简单。

关于串口参数波特率的设定,由于采用TI F2812 DSP,固二者的通讯必须设为一直,串口通讯软件参数要依照DSP的串口通讯参数。

关于波特率、传输速率,下面的文章做了简单的介绍:

http://www.elecfans.com/dianzichangshi/20170823540954.html

9600 对应着1秒内传输1200个字节,那么我们的程序是:

定时器的中断为2ms,即2ms内传输一次数据。1s内发送500次,每次发出4*4=16个字节。固1s内需要传输8000个字节。对应的波特率需要64000以上,由于RS232最大的传输速率是115200波特率。此次我们选择115200,满足我们传输的要求。但是大的波特率意味着出现的误差就会打,实验发现影响也不是很大,基本没什么变化。

4、关于线程

Qt 中所有界面都是在 UI 线程中(也被称为主线程,就是执行了QApplication::exec()的线程),在这个线程中执行耗时的操作(比如那个循环),就会阻塞 UI 线程,从而让界面停止响应。界面停止响应,用户体验自然不好,不过更严重的是,有些窗口管理程序会检测到你的程序已经失去响应,可能会建议用户强制停止程序,这样一来你的程序可能就此终止,任务再也无法完成。所以,为了避免这一问题,我们要使用 QThread 开启一个新的线程:

导入线程模块:

import threading
在主类的__init__()函数中初始化线程:
threading.Thread.__init__(self)# initialize thread
在数据传输按钮的槽函数中开启线程,目的一旦数据开始传输,就要开启线程。

        if(self.ser.isOpen()):
self.t1 = threading.Thread(target = self.receive_data) # set functional interface
self.t1.setDaemon(True)#protect thread
self.t1.start() # start thread
5、关于数据接收与发送

a)数据发送:PC—> DSP

通过PC控制DSP,控制电机的启停、控制率的设定。首先在DSP中定义SCI中断的接收函数:

interrupt void SCIRXINTA_ISR(void)     // SCI-A
{
// Insert ISR Code here

int i_temp=0;
// for(i_temp=0;i_temp<6;i_temp++)
// {
Recieve_SciData[i_temp] = SciaRegs.SCIRXBUF.all;
// }
switch(Recieve_SciData[0]-48)
{
case 0:
control_start_flag1=0;
control_start_flag2=0;
control_start_flag3=0;
stop_return_flag=1;
//SciaRegs.SCICTL1.bit.TXENA=0;
StopCpuTimer0();
// SciaRegs.SCIFFTX.bit.TXFIFOXRESET=1;
break;
case 1:
//数据显示开始
control_start_flag1=0;
control_start_flag2=0;
control_start_flag3=0;
stop_return_flag=0;
SciaRegs.SCICTL1.bit.TXENA=1;
StartCpuTimer0();
// i_interupt=0;
break;
case 2:
control_start_flag1=0;
control_start_flag2=0;
control_start_flag3=0;
stop_return_flag=1;
break;
//控制1
case 3:
control_start_flag1=1;
control_start_flag2=0;
control_start_flag3=0;
break;
//// 控制2
case 4:
control_start_flag1=0;
control_start_flag2=1;
control_start_flag3=0;
break;
// 控制关,控制面复位
case 5:
control_start_flag1=0;
control_start_flag2=0;
control_start_flag3=1;
break;

}
DSP只要接受0~5中任意一个数字,即可实现相应的启停控制。在py文件中定义相关函数如motor_start()电机启动,law1_control()按照控制率1运动。

    def motor_stop(self):
"""
when click the motor stop pushButton
the motor stop and the communication of data end
"""
if(self.ser.isOpen()):
self.ser.write('000000'.encode())
self.sendnum=self.sendnum + 1
self.label_11.setText(str(self.sendnum))

def law1control(self):
if(self.ser.isOpen()):
self.ser.write('33333333'.encode())
self.sendnum=self.sendnum + 1
self.label_11.setText(str(self.sendnum))
关于向串口写数据的write()函数,参考官网介绍

write(data)

Parameters:

data – Data to send.

Returns:

Number of bytes written.

Return type:

int

Raises SerialTimeoutException:

 

In case a write timeout is configured for the port and the time is exceeded.

Write the bytes data tothe port. This should be of type bytes (or compatible such as bytearray or memoryview).Unicode strings must be encoded (e.g. 'hello'.encode('utf-8').

函数的参数必须是字节型数据,所以在'00000000'后面采用了encode()方法对数据进行编码。为什么要发送8个0呢,由于在实验中发现只有1个0的情况下,有时点击按钮后,控制动作未执行,需要多点击几下,这影响了电机的控制效果。所以增加发送的数据量,效果立竿见影。

b)数据接收

在编写程序时遇到很多的问题。首先还是看DSP发送过来的数据。

struct angle_struct_FB{
unsigned int a:8;
unsigned int b:8;
unsigned int c:8;
unsigned int d:8;
};
union angle_unon_FB{
double all;
struct angle_struct_FB bit;
};
union angle_unon_FB angle_intrupt_FB;
定义一个共同体用于存储待发送的数据,介绍一下位域的概念

C语言中位域的概念,就是把一个字节中的二进制位划分为不同的区域,并说明每个区域的位数。每个域都有一个域名允许在程序中按照域名进行操作。
1)、位域的定义必须按从右往左的顺序,也就是说得从最低位开始定义
2)、一个位域必须存储在同一个字节中,不能跨越两个字节。位域的长度不能大于一个字节
3)、位域可以无域名,这时它只用作填充或调整位置
定义共同体能对寄存器的整体进行操作也能对其中的位进行操作。
考虑到串口通讯的数据位为8位,固对数据进行分段处理。把32位的double型数据转化分成4个无符号整型8位数来发送。

数据发送:

double angle2
angle_intrupt_FB.all=angle2;//先发编码器角度
Sci_VarRx=angle_intrupt_FB.bit.a;
SciaRegs.SCITXBUF = Sci_VarRx;
Sci_VarRx=angle_intrupt_FB.bit.b;
SciaRegs.SCITXBUF = Sci_VarRx;
Sci_VarRx=angle_intrupt_FB.bit.c;
SciaRegs.SCITXBUF = Sci_VarRx;
Sci_VarRx=angle_intrupt_FB.bit.d;
SciaRegs.SCITXBUF = Sci_VarRx;
python serial 中读取数据的实现:
    def receive_data(self):        while  (self.ser.isOpen()):            size = self.ser.inWaiting()             if size:                self.recstr=self.ser.read(4)
采用循环的形式不断的读取接收的数据,size的单位为字节,所以read(4)表示一次读取4个字节,对应上面的DSP发送过程即一次读取一个角度值。

从串口读取的位字节串,比如当角度值为0时,读取的的self.recstr=b'\x00\x00\x00\x00',python3.x里默认的str是(py2.x里的)unicode,  bytes是(py2.x)的str, b""前缀代表的就是bytes.什么意思呢,

python3中,取消了unicode类型,代替它的是使用unicode字符的字符串类型(str),字符串类型(str)成为基础类型如下所示,而编码后的变为了字节类型(bytes)但是两个函数的使用方法不变:

     decode              encode

bytes ------> str(unicode)------>bytes

那么如何让b'\x00\x00\x00\x00‘这样的bytes转化成浮点数0.0呢,比如一个浮点数让其写成二进制数,5.0 = 01000000 10100000 00000000 00000000(bin)=40 D0 00 00(hex)

PC机从串口以字节形式读入(采用UTF-8编码)self.recstr=b'\x40\xD0\x00\x00',如果我们能够的得到40 D0 00 00,在进行逆变换即可得浮点数5.0

        self.recstr_hex=binascii.b2a_hex(self.recstr).decode()
recdata_float=struct.unpack('!f',bytes().fromhex(self.recstr_hex))[0]
binascii.b2a_hex()函数将二进制数据字符串转换为十六进制编码,即转变后为b'40D00000',然后在decode()方法对其解码为str格式:‘40D00000’,在struct.unpack()方法即可转变成浮点数。

有关python中的编码问题,可以参考https://www.cnblogs.com/evening/archive/2012/04/19/2457440.html,https://www.crifan.com/summary_python_string_encoding_decoding_difference_and_comparation_python_2_x_str_unicode_vs_python_3_x_bytes_str/

有关struct模块,可以参考http://blog.csdn.net/occupy8/article/details/11052103

6、保存数据

    def data_write(self):

filename=r'C:\Users\Administrator\Desktop\shuju.txt'
with open(filename, 'w') as file_object:
for i in range(len(self.recdata_dec)):
self.recdata_dec[i]=str(self.recdata_dec[i])
str1=' '.join(self.recdata_dec)
file_object.write(str1)
file_object.close()
self.b=' '
while i < len(self.recdata)-1:
self.b+=self.recdata[i+1] + self.recdata[i+2] + ' '
i+=2
hex_filename=r'C:\Users\Administrator\Desktop\shuju_hex.txt'
with open(hex_filename, 'w') as file_object:
file_object.write(self.b)
file_object.close()
将数据保存到桌面的txt文件中,操作时点击保存按钮即可生成要保存的数据。

a)保存十进制的数据,程序中使用循环和join()方法,是让list型数据转化成str,因为写数据到文件中的函数write接收的参数为str类型,也可以使用另外一种方式

str1= ‘’.join(map(str, self.recdata_dec))这段语句一部到位,关于map()方法

map(func, seq1[, seq2,…]) 
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个集合。 
Python函数编程中的map()函数是将func作用于seq中的每一个元素

b)保存为matlab程序可读取,并画图的数据。最后生成 倒叙的十六进制字节流即可被matlab程序所使用,为此设计一些简单的算法实现了。

7、显示图形

在串口通讯界面中设计了两个图形显示按钮吗,一个是绘制曲线按钮,点击按钮后直接在界面有段显示4组曲线,方便查看电机运行状态和控制效果。而是显示图形按钮,点击后会生成三幅图、四组曲线。

a)在见面上显示图形,要用到FigureCanvasQTAgg模块,需要导入。定义一个新的类,用于在界面上画图

class PlotCanvas(FigureCanvas):

def __init__(self, parent=None, width=11, height=3.4*3, dpi=50):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(311)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding) #zoom the widget
FigureCanvas.updateGeometry(self)
self.show()
实例化类的对象:

m1 = PlotCanvas(self)
画图:

        ax1.plot([i*0.002 for i in range(int(self.recnum/4))], self.angle2, 'k-')
ax1.set_title('Angle2')
ax1.set_xlim(0, 20)
ax1.set_ylim(-20, 20)

由于并不是直接的从pyplot模块实例化画图对象的,而是从PlotCanvas.figure.add_subplot继承的属性,固像title(),xlim()方法不能直接使用。要实现相同的功能只要在函数前加set_即可,比如像title()变成set_title()即可。


b)利用matplotlib.pyplot库画图,这个比较简单,生成的图形可以保存成各种格式。

        matplotlib.rcParams['font.family']='Microsoft YaHei'        # set font of picture 
matplotlib.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.figure(1)
plt.plot([i*0.002 for i in range(int(self.recnum/4))], self.angle2, 'k', \
linewidth=1.5, linestyle='-')
plt.xlabel('时间(s)')
plt.ylabel('幅值(degree)')
plt.title('俯仰角响应')
plt.axis([0, 20, -20, 20])
PyQt5开发DSP SCI串口通讯
图2图形显示

8、实时显示时间日期

利用LCD显示屏控件,在其上实时显示时间日期

在UI_xx.py文件中初始化LCD控件

        self.lcdNumber_1=QtWidgets.QLCDNumber(MainWindow)
self.lcdNumber_1.setGeometry(QtCore.QRect(185,20, 137, 30))
self.lcdNumber_1.setObjectName("lcdNumber_1")
self.lcdNumber_1.setDigitCount(10) # the number of the display
self.lcdNumber_1.setMode(QLCDNumber.Dec) # the data format of the display
self.lcdNumber_1.setSegmentStyle(QLCDNumber.Filled) #the style of the display
设定一个定时器并写槽函数更新,时间,1s更新一次

        self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.start()

self.timer.timeout.connect(self.onTimerOut)
    def onTimerOut(self):        self.time = QTime.currentTime()        #self.lcd.display(time.strftime("%X",time.localtime()))        self.lcdNumber.display(self.time.toString(Qt.ISODate)+' ')

Qt之QLCDNumber浅析

https://www.2cto.com/kf/201603/494134.html

这是c++语言对QT的实现,基本上不影响理解。

pyqt5 date and time 参考官网介绍

http://zetcode.com/gui/pyqt5/datetime/

9、生成可执行文件

需要先安装pyinstaller包

将cmd的目录切换至(命令:cd 文件路径(注意空格))需要打包的py文件目录下

输入pyinstaller -F test.py,运行 ,在当前文件夹下生成了build 和dist 两个文件夹,可执行文件exe在dist目录下

关于pyinstaller包,参考http://blog.csdn.net/lqzdreamer/article/details/77917493

四、测试总结

在测试过程中出现很多问题,特别是在数据接收和画图那两块耽误了很长的时间。目前设定的软件版本为V_A_1,已经能实现需求的所有功能,当然在开发过程中发现很多地方还值得进一步改进,后续会继续改进。

PyQt5开发DSP SCI串口通讯

图3 PyQt5开发串口通讯界面