一、开发环境搭建
软件:Python3.6+Eric6+PyQt5+CCS3.3
硬件:TI的F2812+仿真器+超声电机+编码器+超声电机驱动
二、功能需求及任务安排
前几届师兄,做DSP SCI串口通讯使用的是MATLAB GUI开发的。如图1所示界面
图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):此函数内容初始化了串口的各项参数,COM段、波特率等等参数设定在组合框中,通过下拉确定。然后打开串口ser.open()。最后检查串口是否打开,如何打开成功则,让打开串口按钮失效并输出提醒文字到文本编辑框。
"""
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("打开失败")
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()):5、关于数据接收与发送
self.t1 = threading.Thread(target = self.receive_data) # set functional interface
self.t1.setDaemon(True)#protect thread
self.t1.start() # start thread
a)数据发送:PC—> DSP
通过PC控制DSP,控制电机的启停、控制率的设定。首先在DSP中定义SCI中断的接收函数:
interrupt void SCIRXINTA_ISR(void) // SCI-ADSP只要接受0~5中任意一个数字,即可实现相应的启停控制。在py文件中定义相关函数如motor_start()电机启动,law1_control()按照控制率1运动。
{
// 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;
}
def motor_stop(self):关于向串口写数据的write()函数,参考官网介绍
"""
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(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 angle2python serial 中读取数据的实现:
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;
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()binascii.b2a_hex()函数将二进制数据字符串转换为十六进制编码,即转变后为b'40D00000',然后在decode()方法对其解码为str格式:‘40D00000’,在struct.unpack()方法即可转变成浮点数。
recdata_float=struct.unpack('!f',bytes().fromhex(self.recstr_hex))[0]
有关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):将数据保存到桌面的txt文件中,操作时点击保存按钮即可生成要保存的数据。
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()
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])
图2图形显示
8、实时显示时间日期
利用LCD显示屏控件,在其上实时显示时间日期
在UI_xx.py文件中初始化LCD控件
self.lcdNumber_1=QtWidgets.QLCDNumber(MainWindow)设定一个定时器并写槽函数更新,时间,1s更新一次
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.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,已经能实现需求的所有功能,当然在开发过程中发现很多地方还值得进一步改进,后续会继续改进。
图3 PyQt5开发串口通讯界面