一、Pycharm
如果用到Pycharm我们首先最关键的就是要知道Pycharm的Python环境
1.环境配置
Pycharm的环境配置在这里: 左上角->文件->setting(设置)
许多我们需要的函数及类都是在这里导入的,非常好用便捷。
2.镜像源配置
在左下角按照如图点击:我这里使用的是清华源 ↓
3.工具配置(QtDesigner、PyUIC)
左上角->文件->setting(设置)
接下来就是对应你下载的python里面的工具的路径:
二、代码重要节点
我们要清楚上位机的关键节点,下面就以我个人的节点及个人目的实际操作为例
1.接收串口
这里我想的一些功能包括:
①、可以打开一个或多个串口
②、能够自动识别当前存在的串口号
所以我们首先引入两个库
import serial
import serial.tools.list_ports
我们来简单理解一下
my_ports = list(serial.tools.list_ports.comports())
if not my_ports:
print("当前无串口")
else:
for port in my_port:
print(list(port))
结果就会显示你当前存在的串口
接下来直奔主题现在我们尝试打开串口并接收串口传输的数据:
这里的串口数据最终会存到final_package。
数据协议要按照自己的协议去划分(我这里的是ti毫米波雷达demo的解析格式)。
class Process_data(QThread):
fin = pyqtSignal('PyQt_PyObject')
def __init__(self, com):
super().__init__()
self.ser = com
def run(self): # 重写QThread run函数
data = self.processing()
print(data)
self.fin.emit(data) # 信号槽 emit传递函数
def processing(self):
flag_Byte = bytearray(b'\x02\x01\x04\x03\x06\x05\x08\x07') # 当找到附合我们前8个字节的数据 我们才开始按照我们自己的逻辑去读与配置
this_Byte = self.ser.read(1)
final_package = bytearray(b'')
index = 0
while not this_Byte:
this_Byte = self.ser.read(1)
while 1: # 不断获取数据找符合我们的前八个字节逻辑的那一串数据
if this_Byte[0] == flag_Byte[index]: # 这里的[0]是不可以省的 因为flag_Byte[0]为 2 对应this_Byte为b'\x02' 不是2 不能划等号
index += 1
final_package.append(this_Byte[0])
if index == 8:
break
this_Byte = self.ser.read(1)
else:
if index == 0:
this_Byte = self.ser.read(1)
index = 0
final_package = bytearray(b'')
versionBytes = self.ser.read(4)
final_package += bytearray(versionBytes)
lengthBytes = self.ser.read(4)
final_package += bytearray(lengthBytes)
frameLength = int.from_bytes(lengthBytes, byteorder='little')
frameLength -= 16
final_package += bytearray(self.ser.read(frameLength))
return final_package
def recive_start():
recive.start(priority=QThread.HighestPriority)
def update_data(data):
print(data) #这里就是我们要显示在图上的数据了
thread_0 = QTimer()
thread_0.setSingleShot(False)
thread_0.timeout.connect(recive_start)
my_ports = [] # 在查找所有COM之前先初始化列表
ports = list(serial.tools.list_ports.comports()) # 获取串口list的长度
for com in ports:
my_ports.append(com[0]) # 添加刷新后得到的串口
if com[0] == 'COM16':
print('found')
index = my_ports.index(com[0])
# 这里我们可以加个checkbox 然后将my_ports 加到内部供选择,但这里为调试所以直接进入下一步
try:
ser = serial.Serial(my_ports[index], 921600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=0.08)
print('串口打开成功' + my_ports[index])
except:
print(my_ports[index])
print('串口打开失败')
if ser.is_open:
# 在这里我们要思考一个问题, 读取串口数据的同时 我们还要做的是去更新我们的前端显示,所以这里我们直接加个线程来为数据接收增加通道
recive = Process_data(ser)
print(recive.fin)
recive.fin.connect(update_data)
thread_0.start()
这段代码在执行时 会出现
1、 thread_0.start() 不执行
2、 Process_data中的run函数不执行
是因为代码要在PyQt应用程序上下文中执行。QTimer类是PyQt库的一部分,需要一个QApplication实例才能正常工作。确保在运行代码之前初始化了一个QApplication对象
我这里的执行结果为
found
串口打开成功COM16
bytearray(b’\x02\x01\x04\x03\x06\x05\x08\x07\x03\x01\x04\x07@\x00\x00\x00Ch\n\x00\xc86\x00\x00\x11Mv\xea\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x01\x04\x03\x06\x05\x08\x07\x03\x01\x04\x07@\x00\x00\x00Ch\n\x00\xc86\x00\x00’)
<bound PYQT_SIGNAL fin of Process_data object at 0x0000025E08C10790>
需要注意的是 里面没有\x的值是ASCII 需要手动转 Hex (\n是换行)
2.处理数据
既然收到数据,那就可以按照我们的协议来将我们需要的信息实例化,这就比较简单了,偏逻辑。我们这部分暂时先不做过多工作,上面说到要在QApplication实例中才能使用QTimer类,所以我们进入下一个步骤设计前端,实例QApplication,请看下一步。
3.设计上位机前端
打开我们前面新添加的外部工具QtDesigner.
随后思考自己所需要的控件,我需要:
①.一个单独布局用于放置可视化画布
②.需要控制选择COM口 填写波特率等配置的功能框
简单的设计了一下,大家可按照自己的需求和审美加以改进。
随后另存到工程下面,使用external Tools 的 PyUIC功能即可生成python文件。
4.处理上位机后端
前端已经有了现在开始后端继续编写。
首先:引用我们生成的前端py文件。
import test_window #这里你的py文件叫什么你就些什么
随后:新增下面代码(如果你没有引入可以通过pycharm的提示来自动引入)
class window(QMainWindow, test_window.Ui_MainWindow): # 前面是QMainWindow方法 后面是我们生成的前端py文件的类名
def __init__(self):
super(window, self).__init__()
self.setupUi(self)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = window() #对应上面的方法
window.show()
sys.exit(app.exec_())
这里我们只运行上面两串的话就会出现我们刚才设计的前端页面。
最后:我们开始进行控件与语句的配合,使前端与我们前面设计的逻辑相匹配。
# 现在我们已经实例化pyqt了 所以我们重新将我们解析串口数据的函数主要部分修改为下面
class Process_data(QThread):
fin = pyqtSignal('PyQt_PyObject')
def __init__(self, com):
super().__init__()
self.ser = com
def run(self): # 重写QThread run函数
data = self.processing()
self.fin.emit(data) # 信号槽 emit传递函数
def processing(self):
flag_Byte = bytearray(b'\x02\x01\x04\x03\x06\x05\x08\x07') # 当找到附合我们前8个字节的数据 我们才开始按照我们自己的逻辑去读与配置
this_Byte = self.ser.read(1)
final_package = bytearray(b'')
index = 0
while not this_Byte:
this_Byte = self.ser.read(1)
while 1: # 不断获取数据找符合我们的前八个字节逻辑的那一串数据
if this_Byte[0] == flag_Byte[index]: # 这里的[0]是不可以省的 因为flag_Byte[0]为 2 对应this_Byte为b'\x02' 不是2 不能划等号
index += 1
final_package.append(this_Byte[0])
if index == 8:
break
this_Byte = self.ser.read(1)
else:
if index == 0:
this_Byte = self.ser.read(1)
index = 0
final_package = bytearray(b'')
versionBytes = self.ser.read(4)
final_package += bytearray(versionBytes)
lengthBytes = self.ser.read(4)
final_package += bytearray(lengthBytes)
frameLength = int.from_bytes(lengthBytes, byteorder='little')
frameLength -= 16
final_package += bytearray(self.ser.read(frameLength))
return final_package
class window(QMainWindow, test_window.Ui_MainWindow): # 前面是QMainWindow方法 后面是我们生成的前端py文件的类名
def __init__(self):
self.my_ports = [] # 在查找所有COM之前先初始化列表
super(window, self).__init__()
self.setupUi(self)
ports = list(serial.tools.list_ports.comports()) # 获取串口list的长度
# 此时我们已经有了复选框了 所以我们获取串口之后直接将其添加到复选框的选项中去
for com in ports:
self.my_ports.append(com[0]) # 添加刷新后得到的串口
# 这里我们可以加个checkbox 然后将my_ports 加到内部供选择,但这里为调试所以直接进入下一步
self.comboBox.addItems(self.my_ports)
self.thread_0 = QTimer()
self.thread_0.setSingleShot(False)
self.thread_0.timeout.connect(self.recive_start)
self.pushButton.clicked.connect(self.require_com)
self.pushButton_2.clicked.connect(self.start_com)
def require_com(self):
self.my_ports = []
self.comboBox.clear()
ports = list(serial.tools.list_ports.comports())
for com in ports:
self.my_ports.append(com[0])
self.comboBox.addItems(self.my_ports)
def recive_start(self):
self.recive.start(priority=QThread.HighestPriority)
def update_data(self, data): #这里就可以按照自己的串口数据去解析了
# hex_data = ' '.join(format(byte, '02x') for byte in data)
# print(hex_data) # 这里就是我们要显示在图上的数据了
if self.ser.is_open:
framelength = struct.unpack('I', data[12:16])[0]
frame = struct.unpack('I', data[20:24])[0]
det_num = struct.unpack('I', data[28:32])[0]
tlv_numflag = struct.unpack('I', data[32:36])[0]
index = 40
print(tlv_numflag)
for i in range(tlv_numflag):
tlvnum = struct.unpack('I', data[index:index + 4])[0]
if tlvnum == 1:
total = struct.unpack('I', data[index + 4:index + 8])[0]
self.deal_1(det_num, data, index)
index = total + 8
elif tlvnum == 2:
total = struct.unpack('I', data[index + 4:index + 8])[0]
index = total + 8
elif tlvnum == 3:
total = struct.unpack('I', data[index + 4:index + 8])[0]
# self.deal_2(det_num, data, index)
index = total + 8
def deal_1(self, num, data, index):
if num != 0:
print(index)
q = struct.unpack('h', data[index + 10:index + 12])[0]
print(q)
for i in range(num):
add = 12 + i * 10
x = (struct.unpack('h', data[index + add + 4:index + add + 6])[0]) / (2 ** q)
y = (struct.unpack('h', data[index + add + 6:index + add + 8])[0]) / (2 ** q)
z = (struct.unpack('h', data[index + add + 8:index + add + 10])[0]) / (2 ** q)
print(x)
print(y)
print(z)
print('---------------')
def deal_2(self, num, data, index):
print('1')
def start_com(self):
if self.pushButton_2.text() == '开始':
try:
self.ser = serial.Serial(self.comboBox.currentText(), self.lineEdit_2.text(), parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE, timeout=0.08)
print('串口打开成功' + self.comboBox.currentText())
except:
QMessageBox.about(self, "出错", '串口打开失败')
if self.ser.is_open:
# 在这里我们要思考一个问题, 读取串口数据的同时 我们还要做的是去更新我们的前端显示,所以这里我们直接加个线程来为数据接收增加通道
self.recive = Process_data(self.ser)
self.recive.fin.connect(self.update_data)
self.thread_0.start()
self.pushButton_2.setText('关闭') # 点击开始之后 要相应把摁扭改为关闭
else:
self.ser.close()
self.pushButton_2.setText('开始')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = window()
window.show()
sys.exit(app.exec_())
5.测试验证
像我这边就是输出x y z
总之整体的节点流程已经完成了 其他就是自己设计自己想要的结果了,比如:
好了到这里就结束了。