电子电路|基于树莓派4b的传感器数据可视化实现

概述 实验的第二部分将5个传感器同时搭建在面包板,每一个模块建立一个文件,并且为每一个模块创建一个类。另外创建一个利用QT Designer设计生成的一个界面类。在主文件中实例化5个传感器的类,并且创建5个子线程用于同时执行5个传感器的测量与显示,主线程用于实时显示整个界面。整个实验用python编写,界面代码编写使用pyqt5库。
一、mpu6050模块 1、整体思路
利用树莓派的IIC与mpu6050进行通信,读取mpu6050的姿态角信息,由于mpu6050的偏航角需要通过磁力计才能较为精准地测量,所以本次实验只测mpu6050的俯仰角(pitch)和翻滚角(roll)。考虑到树莓派的运行内存,mpu6050的姿态角计算采用简单的一阶互补滤波进行计算。Mpu6050类应该有一个方法计算pitch和roll并返回。
2、实验步骤
①接线

树莓派 mpu6050
+3.3V VCC
GND GND
P02 SDA
P03 SCL
②代码编写
一阶互补滤波原理:
mpu6050读取的加速度计和陀螺仪原始值各有优缺点,仅仅通过陀螺仪或者加速度计就能计算出pitch角和roll角,可以通过下面的式子将加速度计和陀螺仪的数据都融合到姿态角的计算当中:
Angle = kacc_angle+(1-k)(last_acc_angle+gy*dt)
其中,Angle 为最后得到的角度,acc_angle为这个周期加速度计计算出来的角度,last_acc_angle为上一个采样周期加速度计计算出来的角度,gy为这个周期陀螺仪计算出来的角加速度,dt为采样周期,k为占比系数。dt越小测量越精准。
import smbus# 导入I2C的SMBus模块 from time import sleep# 导入延时函数 import mathclass Mpu6050_Class(): def __init__(self):# MPU 6050 初始化工作 # 一些MPU6050寄存器及其地址 self.PWR_MGMT_1 = 0x6B self.SMPLRT_DIV = 0x19 self.CONFIG = 0x1A self.GYRO_CONFIG = 0x1B self.INT_ENABLE = 0x38 self.ACCEL_XOUT_H = 0x3B self.ACCEL_YOUT_H = 0x3D self.ACCEL_ZOUT_H = 0x3F self.GYRO_XOUT_H = 0x43 self.GYRO_YOUT_H = 0x45 self.GYRO_ZOUT_H = 0x47 self.makerobo_bus = smbus.SMBus(1)# 或bus = smbus.SMBus(0)用于较老的版本板 self.makerobo_Device_Address = 0x68# MPU6050设备地址 # 写入抽样速率寄存器 self.makerobo_bus.write_byte_data(self.makerobo_Device_Address, self.SMPLRT_DIV, 7)# 写入电源管理寄存器 self.makerobo_bus.write_byte_data(self.makerobo_Device_Address, self.PWR_MGMT_1, 1)# 写入配置寄存器 self.makerobo_bus.write_byte_data(self.makerobo_Device_Address, self.CONFIG, 0)# 写入陀螺配置寄存器 self.makerobo_bus.write_byte_data(self.makerobo_Device_Address, self.GYRO_CONFIG, 24)# 写中断使能寄存器 self.makerobo_bus.write_byte_data(self.makerobo_Device_Address, self.INT_ENABLE, 1)# 读取MPU6050数据寄存器 def makerobo_read_raw_data(self,addr): # 加速度值和陀螺值为16位 high = self.makerobo_bus.read_byte_data(self.makerobo_Device_Address, addr) low = self.makerobo_bus.read_byte_data(self.makerobo_Device_Address, addr + 1)# 连接更高和更低的值 value = https://www.it610.com/article/((high << 8) | low)# 从mpu6050获取有符号值 if (value> 32768): value = https://www.it610.com/article/value - 65536 return value last_pitch=0 last_roll=0 def get_data(self): # 读取加速度计原始值 acc_x = self.makerobo_read_raw_data(self.ACCEL_XOUT_H) acc_y = self.makerobo_read_raw_data(self.ACCEL_YOUT_H) acc_z = self.makerobo_read_raw_data(self.ACCEL_ZOUT_H)# 读陀螺仪原始值 gyro_x = self.makerobo_read_raw_data(self.GYRO_XOUT_H) gyro_y = self.makerobo_read_raw_data(self.GYRO_YOUT_H) gyro_z = self.makerobo_read_raw_data(self.GYRO_ZOUT_H)# 全刻度范围+/- 250度/℃,根据灵敏度刻度系数 self.ax = acc_x / 16384.0 self.ay = acc_y / 16384.0 self.az = acc_z / 16384.0self.gx = gyro_x / 131.0 self.gy = gyro_y / 131.0 self.gz = gyro_z / 131.0 #########################################一阶互补滤波姿态角解算###############################''' k=0.1 dt=0.5#dt为采样周期,0.01说明要10ms执行一次数据采集acc_pitch=math.atan(self.ax/self.az)*57.2974 acc_roll=math.atan(self.ay/self.az)*57.2974 self.pitch=k*acc_pitch+(1-k)*(self.last_pitch+self.gx*dt) self.last_pitch=self.pitch self.roll=k*acc_roll+(1-k)*(self.last_roll+self.gy*dt) self.last_roll=self.roll ''' acc_pitch=math.atan(self.ax/self.az)*57.2974 acc_roll=math.atan(self.ay/self.az)*57.2974 self.pitch=acc_pitch self.roll=acc_roll ''' if __name__ == "__main__": fle_mpu6050 = Mpu6050_Class() #print("%.2f"%fle_mpu6050.Ax) while True: # 打印出MPU相关信息 fle_mpu6050.get_data() print("pitch=%.2f" % fle_mpu6050.pitch, "\troll=%.2f" % fle_mpu6050.roll) sleep(0.01)# 延时10ms'''

3、测试用例结果
①模块水平于桌面放置:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

模块水平放置时,俯仰角理论值为0,翻滚角理论值为0,图中pitch和roll均与理论值相近。
②模块垂直于桌面放置:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

模块垂直放置时,俯仰角理论值为90°,翻滚角理论值为0,图中pitch和roll均与理论值相近。
③模块与桌面成45°角放置:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

模块与桌面成45°角放置时,俯仰角理论值为0,翻滚角理论值为45°,图中pitch和roll均与理论值相近。
二、HC-SR04超声波模块 1、整体思路
HC-SR04应该有一个方法计算距离并返回。
2、实验步骤
①接线
树莓派 HC-SR04模块
+5V VCC
GND GND
P05 Trig
P06 Echo
②代码编写
import RPi.GPIO as GPIO import timemakerobo_TRIG = 29# 超声波模块Tring控制管脚 makerobo_ECHO = 31# 超声波模块Echo控制管脚class HC_SR04_Class(): def __init__(self):# MPU 6050 初始化工作 GPIO.setmode(GPIO.BOARD)# 采用实际的物理管脚给GPIO口 GPIO.setwarnings(False)# 忽略GPIO操作注意警告 GPIO.setup(makerobo_TRIG, GPIO.OUT)# Tring设置为输出模式 GPIO.setup(makerobo_ECHO, GPIO.IN)# Echo设置为输入模式# 超声波计算距离函数 def ur_disMeasure(self): GPIO.output(makerobo_TRIG, 0)# 开始起始 time.sleep(0.000002)# 延时2usGPIO.output(makerobo_TRIG, 1)# 超声波启动信号,延时10us time.sleep(0.00001)# 发出超声波脉冲 GPIO.output(makerobo_TRIG, 0)# 设置为低电平while GPIO.input(makerobo_ECHO) == 0:# 等待回传信号 us_a = 0 us_time1 = time.time()# 获取当前时间 while GPIO.input(makerobo_ECHO) == 1:# 回传信号截止信息 us_a = 1 us_time2 = time.time()# 获取当前时间us_during = us_time2 - us_time1# 转换微秒级的时间# 声速在空气中的传播速度为340m/s, 超声波要经历一个发送信号和一个回波信息, # 计算公式如下所示: return us_during * 340 / 2 * 100# 求出距离 # 资源释放函数 def destroy(self): GPIO.cleanup()# 释放资源 ''' # 程序入口 if __name__ == "__main__": Hardware_HC_SR04=HC_SR04_Class() while True: us_dis = Hardware_HC_SR04.ur_disMeasure()# 获取超声波计算距离 print(us_dis, 'cm')# 打印超声波距离值 print('') time.sleep(0.3)# 延时300ms '''

3、测试用例结果
①距离10cm:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

由上面两张图片可知误差不超过1cm
②距离20cm:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

由上面两张图片可知误差不超过1cm
三、3mm双色LED模块 1、整体思路
双色LED类应该能够向外提供两个方法,分别设置红灯和绿灯的PWM。
2、实验步骤
①接线
树莓派 3mm双色LED模块
GND 负极(-)
P17 红色灯正极(中间引脚)
P18 绿色灯正极
②代码编写
编写两个函数分别调整两个灯的亮度。
import RPi.GPIO as GPIO import timeclass Double_LED_Class: def __init__(self):# double_led 初始化工作 makerobo_pins = (11, 12)# PIN管脚字典 GPIO.setmode(GPIO.BOARD)# 采用实际的物理管脚给GPIO口 GPIO.setwarnings(False)# 去除GPIO口警告 GPIO.setup(makerobo_pins, GPIO.OUT)# 设置Pin模式为输出模式 GPIO.output(makerobo_pins, GPIO.LOW)# 设置Pin管脚为低电平(0V)关闭LED self.p_R = GPIO.PWM(makerobo_pins[0], 2000)# 设置频率为2KHz self.p_G = GPIO.PWM(makerobo_pins[1], 2000)# 设置频率为2KHz # 初始化占空比为0(led关闭) self.p_R.start(0) self.p_G.start(0)def makerobo_pwm_map(self,x, in_min, in_max, out_min, out_max): return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_mindef makerobo_set_red_Color(self,col):# 例如:col = 0x1122 # 把0-255的范围同比例缩小到0-100之间 R_val = self.makerobo_pwm_map(col, 0, 255, 0, 100) self.p_R.ChangeDutyCycle(R_val)# 改变占空比 def makerobo_set_green_Color(self,col):# 例如:col = 0x1122 # 把0-255的范围同比例缩小到0-100之间 G_val = self.makerobo_pwm_map(col, 0, 255, 0, 100) self.p_G.ChangeDutyCycle(G_val)# 改变占空比# 释放资源 def makerobo_destroy(self): self.p_G.stop() self.p_R.stop() GPIO.output(self.makerobo_pins, GPIO.LOW)# 关闭所有LED GPIO.cleanup()# 释放资源

3、测试用例结果
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

3s后变为下面的图片
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

程序中首先亮红灯,然后用time.sleep(3)延时了3s,再关闭红灯,亮绿灯,测试结果与理论相符。
四、DS18B20测温模块 1、整体思路
DS18B20类应该能够向外提供获取温度的方法。
2、实验步骤
①接线
树莓派 DS18B20
GND 负极(-)
+5V 正极(中间引脚)
P04 输出引脚(S)
②代码编写
由于DS18B20是在一个文件中读取温度值,为了DS18B20能较为快速地读取温度值,所以在get_temperature方法中不频繁打开和关闭文件,在初始化中打开文件,不再关闭文件,但是每次DS18B20刷新文件内容,如果不执行关闭重开文件的操作的话,文件的光标指针将会指向换行符,导致该方法只能执行一次,所以每次在进入该方法之前先定位光标到69的位置,69由下图得出:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

#!/usr/bin/env python3 # -*- coding: utf-8 -*- # ----湖南创乐博智能科技有限公司---- #文件名:25_ds18b20.py #版本:V2.0 #author: zhulin # 说明: DS18B20数字温度传感器实验 # 注意事项:DS18B20有唯一的地址,一般为28-XXXXXX ##################################################### import osclass DS18B20_Class(): def __init__(self): for i in os.listdir('/sys/bus/w1/devices'): if i == '28-0317039468ff': self.makerobo_ds18b20 = i# ds18b20存放在ds18b20地址 makerobo_location = '/sys/bus/w1/devices/' + self.makerobo_ds18b20 + '/w1_slave' # 保存ds18b20地址信息 self.makerobo_tfile = open(makerobo_location)# 打开ds18b20 def get_temperature(self): self.makerobo_tfile.seek(69,0) makerobo_text = self.makerobo_tfile.read()# 读取到温度值 temperature = float(makerobo_text) temperature = temperature / 1000 return temperature ''' # 程序入口 if __name__ == '__main__': DS18B20=DS18B20_Class() while True: temperature=DS18B20.get_temperature() print("温度:%.2f\u00b0C"%temperature) '''

3、测试用例结果
①手指不触碰DS18B20
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

②手指触碰DS18B20
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

由上面两图可知,手指触碰DS18B20后测量到的温度逐渐升高,与理论相符。
五、KEYES金属触摸传感器模块 1、整体思路
当有东西触摸到金属触摸传感器时,金属触摸传感器的A0引脚会输出一个模拟电压,当不触碰传感器时A0输出的模拟电压为金属触摸传感器的VCC,当触碰传感器时A0输出的模拟电压低于金属触摸传感器的VCC。由于树莓派没有ADC模块,所以使用ADS1115模块进行ADC采集。使用Adafruit_ADS1x15库对ADS1115模块进行操作。
2、实验步骤
①接线
树莓派 ADS1115
+3.3V VCC
GND GND
GND ADDR
P02 SDA
P03 SCL
ADS1115 KEYES金属触摸传感器
VCC VCC
GND GND
A0(通道0,总共四个通道) A0
ADS1115和mpu6050共用一个IIC,但是可以对模块的地址来区分,ADS1115和mpu6050两个子线程同时开启时,会产生卡顿现象。另外,ADS1115的ADDR接GND时ADS1115的IIC地址为0x48。同时接ADS1115和mpu6050时可以读到如下图所示两个设备:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

②代码编写
import Adafruit_ADS1x15 import timeclass KEYES_Class(): def __init__(self): self.GAIN = 1 self.adc1 = Adafruit_ADS1x15.ADS1115(address=0x48) def get_voltage(self): voltage=self.adc1.read_adc(0, gain=self.GAIN, data_rate=128) voltage=voltage*4.096*2/65535 return voltage''' # 程序入口 if __name__ == '__main__': KEYES=KEYES_Class() while True: voltage=KEYES.get_voltage() print("电压:%.2fV"%voltage) '''

3、测试用例结果
①手指不触碰传感器上的金属
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

金属触摸传感器的VCC与树莓派的+3.3V相连,用万用表测量金属触摸传感器的VCC引脚结果如上面右图所示,测量值3.23与理论值3.22相近
②手指触碰传感器上的金属
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

当手指触摸金属传感器时,A0引脚的电压变小,与理论相符。
六、界面类 利用QT Designer设计界面如下:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

mpu6050:用mpu6050获得的数据画一个三维图像来显示姿态角,而三维图像用groupBox组件来呈现。
HC-SR04:与mpu6050类似,用HC-SR04获得的数据画一个三维图像来显示距离,而三维图像用groupBox组件来呈现。
3mm双色LED:用两条滑动条来控制两个灯的PWM,范围为0~255。
DS18B20:用一个TextLabel来接受采集的温度值并显示。
金属触摸模块:用一个TextLabel来接受采集的电压值并显示。
最右下角为退出主界面的控件,另外每一个模块都配套了一个按钮,用于打开或关闭线程,防止树莓派运行卡死。
用PYUIC扩展工具可以生成python代码,从而可以在Window端设计界面,将代码拷贝到树莓派上运行,而树莓派不需要安装QT Designer。生成的代码如下:
from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.resize(924, 769) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox.setGeometry(QtCore.QRect(580, 720, 341, 32)) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.mpu6050_groupBox = QtWidgets.QGroupBox(Dialog) self.mpu6050_groupBox.setGeometry(QtCore.QRect(36, 106, 421, 381)) self.mpu6050_groupBox.setTitle("") self.mpu6050_groupBox.setObjectName("mpu6050_groupBox") self.red_horizontalSlider = QtWidgets.QSlider(Dialog) self.red_horizontalSlider.setGeometry(QtCore.QRect(110, 590, 160, 22)) self.red_horizontalSlider.setMaximum(255) self.red_horizontalSlider.setOrientation(QtCore.Qt.Horizontal) self.red_horizontalSlider.setObjectName("red_horizontalSlider") self.double_red_led_label = QtWidgets.QLabel(Dialog) self.double_red_led_label.setGeometry(QtCore.QRect(156, 620, 72, 15)) font = QtGui.QFont() font.setFamily("黑体") self.double_red_led_label.setFont(font) self.double_red_led_label.setObjectName("double_red_led_label") self.HC_SR04_groupBox = QtWidgets.QGroupBox(Dialog) self.HC_SR04_groupBox.setGeometry(QtCore.QRect(486, 106, 411, 381)) self.HC_SR04_groupBox.setTitle("") self.HC_SR04_groupBox.setObjectName("HC_SR04_groupBox") self.mpu6050_label = QtWidgets.QLabel(Dialog) self.mpu6050_label.setGeometry(QtCore.QRect(195, 492, 191, 21)) font = QtGui.QFont() font.setFamily("黑体") self.mpu6050_label.setFont(font) self.mpu6050_label.setObjectName("mpu6050_label") self.HC_SR04_label = QtWidgets.QLabel(Dialog) self.HC_SR04_label.setGeometry(QtCore.QRect(615, 492, 231, 21)) font = QtGui.QFont() font.setFamily("黑体") self.HC_SR04_label.setFont(font) self.HC_SR04_label.setObjectName("HC_SR04_label") self.mpu6050_pushButton = QtWidgets.QPushButton(Dialog) self.mpu6050_pushButton.setGeometry(QtCore.QRect(205, 522, 93, 28)) self.mpu6050_pushButton.setObjectName("mpu6050_pushButton") self.HC_SR04_pushButton = QtWidgets.QPushButton(Dialog) self.HC_SR04_pushButton.setGeometry(QtCore.QRect(655, 522, 93, 28)) self.HC_SR04_pushButton.setObjectName("HC_SR04_pushButton") self.double_led_pushButton = QtWidgets.QPushButton(Dialog) self.double_led_pushButton.setGeometry(QtCore.QRect(136, 650, 93, 28)) self.double_led_pushButton.setObjectName("double_led_pushButton") self.DS18B20_label = QtWidgets.QLabel(Dialog) self.DS18B20_label.setGeometry(QtCore.QRect(492, 586, 131, 20)) font = QtGui.QFont() font.setFamily("黑体") self.DS18B20_label.setFont(font) self.DS18B20_label.setObjectName("DS18B20_label") self.DS18B20_label2 = QtWidgets.QLabel(Dialog) self.DS18B20_label2.setGeometry(QtCore.QRect(341, 586, 181, 21)) font = QtGui.QFont() font.setFamily("黑体") self.DS18B20_label2.setFont(font) self.DS18B20_label2.setObjectName("DS18B20_label2") self.DS18B20_pushButton = QtWidgets.QPushButton(Dialog) self.DS18B20_pushButton.setGeometry(QtCore.QRect(426, 650, 93, 28)) self.DS18B20_pushButton.setObjectName("DS18B20_pushButton") self.KEYES_label_2 = QtWidgets.QLabel(Dialog) self.KEYES_label_2.setGeometry(QtCore.QRect(676, 590, 72, 15)) font = QtGui.QFont() font.setFamily("黑体") self.KEYES_label_2.setFont(font) self.KEYES_label_2.setObjectName("KEYES_label_2") self.KEYES_label = QtWidgets.QLabel(Dialog) self.KEYES_label.setGeometry(QtCore.QRect(756, 590, 111, 16)) font = QtGui.QFont() font.setFamily("黑体") self.KEYES_label.setFont(font) self.KEYES_label.setObjectName("KEYES_label") self.KEYES_pushButton = QtWidgets.QPushButton(Dialog) self.KEYES_pushButton.setGeometry(QtCore.QRect(706, 650, 93, 28)) self.KEYES_pushButton.setObjectName("KEYES_pushButton") self.label = QtWidgets.QLabel(Dialog) self.label.setGeometry(QtCore.QRect(325, 0, 551, 71)) font = QtGui.QFont() font.setFamily("华光行书_CNKI") font.setPointSize(48) self.label.setFont(font) self.label.setFrameShape(QtWidgets.QFrame.NoFrame) self.label.setObjectName("label") self.label_2 = QtWidgets.QLabel(Dialog) self.label_2.setGeometry(QtCore.QRect(400, 75, 331, 16)) font = QtGui.QFont() font.setFamily("黑体") self.label_2.setFont(font) self.label_2.setObjectName("label_2") self.green_horizontalSlider = QtWidgets.QSlider(Dialog) self.green_horizontalSlider.setGeometry(QtCore.QRect(110, 720, 160, 22)) self.green_horizontalSlider.setMaximum(255) self.green_horizontalSlider.setOrientation(QtCore.Qt.Horizontal) self.green_horizontalSlider.setObjectName("green_horizontalSlider") self.double_green_led_label = QtWidgets.QLabel(Dialog) self.double_green_led_label.setGeometry(QtCore.QRect(158, 700, 72, 15)) font = QtGui.QFont() font.setFamily("黑体") self.double_green_led_label.setFont(font) self.double_green_led_label.setObjectName("double_green_led_label") self.label_3 = QtWidgets.QLabel(Dialog) self.label_3.setGeometry(QtCore.QRect(12, 592, 72, 15)) self.label_3.setObjectName("label_3") self.label_4 = QtWidgets.QLabel(Dialog) self.label_4.setGeometry(QtCore.QRect(12, 721, 72, 15)) self.label_4.setObjectName("label_4") self.line = QtWidgets.QFrame(Dialog) self.line.setGeometry(QtCore.QRect(0, 560, 931, 20)) font = QtGui.QFont() font.setFamily("AcadEref") font.setPointSize(26) self.line.setFont(font) self.line.setLineWidth(5) self.line.setFrameShape(QtWidgets.QFrame.HLine) self.line.setFrameShadow(QtWidgets.QFrame.Sunken) self.line.setObjectName("line") self.line_2 = QtWidgets.QFrame(Dialog) self.line_2.setGeometry(QtCore.QRect(462, 94, 20, 471)) self.line_2.setLineWidth(5) self.line_2.setFrameShape(QtWidgets.QFrame.VLine) self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_2.setObjectName("line_2") self.line_3 = QtWidgets.QFrame(Dialog) self.line_3.setGeometry(QtCore.QRect(0, 88, 921, 16)) self.line_3.setLineWidth(5) self.line_3.setFrameShape(QtWidgets.QFrame.HLine) self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_3.setObjectName("line_3") self.line_4 = QtWidgets.QFrame(Dialog) self.line_4.setGeometry(QtCore.QRect(293, 565, 20, 201)) self.line_4.setLineWidth(5) self.line_4.setFrameShape(QtWidgets.QFrame.VLine) self.line_4.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_4.setObjectName("line_4") self.line_5 = QtWidgets.QFrame(Dialog) self.line_5.setGeometry(QtCore.QRect(600, 565, 20, 201)) self.line_5.setLineWidth(5) self.line_5.setFrameShape(QtWidgets.QFrame.VLine) self.line_5.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_5.setObjectName("line_5") self.line_6 = QtWidgets.QFrame(Dialog) self.line_6.setGeometry(QtCore.QRect(609, 690, 311, 20)) self.line_6.setLineWidth(5) self.line_6.setFrameShape(QtWidgets.QFrame.HLine) self.line_6.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_6.setObjectName("line_6")self.retranslateUi(Dialog) self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.rejected.connect(Dialog.reject) QtCore.QMetaObject.connectSlotsByName(Dialog)def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Dialog")) self.double_red_led_label.setText(_translate("Dialog", "TextLabel")) self.mpu6050_label.setText(_translate("Dialog", "Mpu6050姿态显示")) self.HC_SR04_label.setText(_translate("Dialog", "HC-SR04超声波测距显示")) self.mpu6050_pushButton.setText(_translate("Dialog", "aa")) self.HC_SR04_pushButton.setText(_translate("Dialog", "PushButton")) self.double_led_pushButton.setText(_translate("Dialog", "PushButton")) self.DS18B20_label.setText(_translate("Dialog", "TextLabel")) self.DS18B20_label2.setText(_translate("Dialog", "DS18B20检测温度:")) self.DS18B20_pushButton.setText(_translate("Dialog", "PushButton")) self.KEYES_label_2.setText(_translate("Dialog", "触摸状态:")) self.KEYES_label.setText(_translate("Dialog", "TextLabel")) self.KEYES_pushButton.setText(_translate("Dialog", "PushButton")) self.label.setText(_translate("Dialog", "嵌入式实验")) self.label_2.setText(_translate("Dialog", "20192333068郭先达")) self.double_green_led_label.setText(_translate("Dialog", "TextLabel")) self.label_3.setText(_translate("Dialog", "红灯PWM:")) self.label_4.setText(_translate("Dialog", "绿灯PWM:"))

七、主文件 1、整体思路
需要创建的类有:
①画三维图的类:
实例化:mpu6050和HC_SR04
②多线程类:
实例化:mpu6050_Thread
HC_SR04_Thread
double_led_Thread
DS18B20_Thread
KEYES_Thread
③主界面类:
实例化:main
每个子线程的run函数用于每个传感器获取数据并显示。每个模块的按钮和一个槽函数相连,每个槽函数控制每个子线程类中run函数中的标志位,用于控制是否执行传感器获取数据并显示这个动作。多个子线程应该可以灵活地设置每个子线程的采样显示周期,所以在线程类中需要有传入参数用于设置run函数的间隔时间。
2、代码编写
#-*-coding:utf-8-*- from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * import sys import numpy as np from testplot2pyqt5 import Ui_Dialog from mpl_toolkits.mplot3d import Axes3D import matplotlib from matplotlib import pyplot as plt matplotlib.use("Qt5Agg")# 声明使用QT5 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import matplotlib.pyplot as plt from PyQt5.QtCore import QTimer import random import math import scipy.linalg as linalg import threading import time from mpu6050 import Mpu6050_Class from double_led import Double_LED_Class from HC_SR04 import HC_SR04_Class from DS18B20 import DS18B20_Class from KEYES import KEYES_Class #多线程类 class MBThread(QThread): oneSecondTriger = pyqtSignal() run_time=0.5 def __init__(self,time): super(MBThread,self).__init__() self.stop_flag = True self.run_time=timedef run(self): while True: if self.stop_flag is False: self.oneSecondTriger.emit() time.sleep(self.run_time)#1s触发一次信号 else: time.sleep(self.run_time)#必须加上去,否则当stop_flag为True时会因为执行过快导致主界面卡死 def stop(self): self.stop_flag=True def contining(self): self.stop_flag=Falseclass plotCanvas(FigureCanvas): def __init__(self, title, parent=None, width=5, height=4, dpi=100, axis=2): self.fig = Figure(figsize=(width, height), dpi=dpi) self.axis = axis if axis == 2: self.axes = self.fig.add_subplot(111) else: self.axes = Axes3D(self.fig) FigureCanvas.__init__(self, self.fig) self.setParent(parent) self.title = titleFigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self)def ola_convert(self,pitch, roll, point): axis_x, axis_y = [1, 0, 0], [0, 1, 0]# 分别是x,y和z轴,也可以自定义旋转轴 rot_matrix_pitch = linalg.expm(np.cross(np.eye(3), axis_x / linalg.norm(axis_x) * pitch)) rot_matrix_roll = linalg.expm(np.cross(np.eye(3), axis_y / linalg.norm(axis_y) * roll)) a = np.dot(point, rot_matrix_pitch) b = np.dot(a, rot_matrix_roll) return bdef mpu6050_plot(self, dx, dy, dz, pitch, roll,color='red'): if self.axis == 2: self.axes.scatter(data["x"], data["y"]) self.axes.set_title(self.title) if self.axis == 3: self.axes.set_zlabel('Z', fontdict={'size': 15}) self.axes.set_ylabel('Y', fontdict={'size': 15}) self.axes.set_xlabel('X', fontdict={'size': 15}) self.axes.set_xlim(-10,10) self.axes.set_ylim(-10,10) self.axes.set_zlim(-10,10)X = [-10, 10] Y = [0, 0] Z = [0, 0] self.axes.plot3D(X,Y,Z,color='Green')#画X轴 X=[0,0] Y=[-10,10] Z=[0,0] self.axes.plot3D(X,Y,Z,color='Green')#画Y轴 X=[0,0] Y=[0,0] Z=[-10,10] self.axes.plot3D(X,Y,Z,color='Green')#画Z轴#画正方体 # 定义8个点 point1 = self.ola_convert(pitch, roll, np.array([-dx / 2, -dy / 2, dz / 2])) point2 = self.ola_convert(pitch, roll, np.array([-dx / 2, dy / 2, dz / 2])) point3 = self.ola_convert(pitch, roll, np.array([dx / 2, dy / 2, dz / 2])) point4 = self.ola_convert(pitch, roll, np.array([dx / 2, -dy / 2, dz / 2])) point5 = self.ola_convert(pitch, roll, np.array([-dx / 2, -dy / 2, -dz / 2])) point6 = self.ola_convert(pitch, roll, np.array([-dx / 2, dy / 2, -dz / 2])) point7 = self.ola_convert(pitch, roll, np.array([dx / 2, dy / 2, -dz / 2])) point8 = self.ola_convert(pitch, roll, np.array([dx / 2, -dy / 2, -dz / 2]))# 先画上下两个面,再画连接两个面的四条线 kwargs = {'alpha': 1, 'color': color} xx = [point1[0], point2[0], point3[0], point4[0], point1[0]] yy = [point1[1], point2[1], point3[1], point4[1], point1[1]] zz = [point1[2], point2[2], point3[2], point4[2], point1[2]] self.axes.plot3D(xx, yy, zz, **kwargs) xx = [point5[0], point6[0], point7[0], point8[0], point5[0]] yy = [point5[1], point6[1], point7[1], point8[1], point5[1]] zz = [point5[2], point6[2], point7[2], point8[2], point5[2]] self.axes.plot3D(xx, yy, zz, **kwargs) xx = [point1[0], point5[0]] yy = [point1[1], point5[1]] zz = [point1[2], point5[2]] self.axes.plot3D(xx, yy, zz, **kwargs) xx = [point2[0], point6[0]] yy = [point2[1], point6[1]] zz = [point2[2], point6[2]] self.axes.plot3D(xx, yy, zz, **kwargs) xx = [point3[0], point7[0]] yy = [point3[1], point7[1]] zz = [point3[2], point7[2]] self.axes.plot3D(xx, yy, zz, **kwargs) xx = [point4[0], point8[0]] yy = [point4[1], point8[1]] zz = [point4[2], point8[2]] self.axes.plot3D(xx, yy, zz, **kwargs) pitch_4float=round(pitch*57.3,2)#保留2位小数 roll_4float=round(roll*57.3,2) self.axes.text3D(0,10,0,"pitch:"+str(pitch_4float)+u'\u00b0') self.axes.text3D(10, 0, 0, "roll:" + str(roll_4float)+u'\u00b0') self.draw() def HC_SR04_plot(self, dx, dy, dz, distand,color='red'): if self.axis == 2: self.axes.scatter(data["x"], data["y"]) self.axes.set_title(self.title) if self.axis == 3: self.axes.set_zlabel('Z', fontdict={'size': 15}) self.axes.set_ylabel('Y', fontdict={'size': 15}) self.axes.set_xlabel('X', fontdict={'size': 15}) self.axes.set_xlim(-10,10) self.axes.set_ylim(0,30) self.axes.set_zlim(-10,10)X = [-10, 10] Y = [0, 0] Z = [0, 0] self.axes.plot3D(X,Y,Z,color='Green')#画X轴 X=[0,0] Y=[0,30] Z=[0,0] self.axes.plot3D(X,Y,Z,color='Green')#画Y轴 X=[0,0] Y=[0,0] Z=[-10,10] self.axes.plot3D(X,Y,Z,color='Green')#画Z轴#画正方体 # 定义8个点 point1 = np.array([-dx / 2, 0, dz / 2]) point2 = np.array([dx / 2, 0, dz / 2]) point3 = np.array([dx / 2, 0, -dz / 2]) point4 = np.array([-dx / 2, 0, -dz / 2]) point5 = np.array([-dx / 2, distand, dz / 2]) point6 = np.array([dx / 2, distand, dz / 2]) point7 = np.array([dx / 2, distand, -dz / 2]) point8 = np.array([-dx / 2, distand, -dz / 2])# 先画上下两个面,再画连接两个面的四条线 kwargs = {'alpha': 1, 'color': color} xx = [point1[0], point2[0], point3[0], point4[0], point1[0]] yy = [point1[1], point2[1], point3[1], point4[1], point1[1]] zz = [point1[2], point2[2], point3[2], point4[2], point1[2]] self.axes.plot3D(xx, yy, zz, **kwargs) xx = [point5[0], point6[0], point7[0], point8[0], point5[0]] yy = [point5[1], point6[1], point7[1], point8[1], point5[1]] zz = [point5[2], point6[2], point7[2], point8[2], point5[2]] self.axes.plot3D(xx, yy, zz, **kwargs) distand_2float=round(distand,2) self.axes.text3D(-10,30,10,"distand:"+str(distand_2float)+"cm") self.draw() def clean(self): self.axes.cla()class MainDialogImgBW(QDialog,Ui_Dialog): def __init__(self): super(MainDialogImgBW,self).__init__() self.setupUi(self) self.setWindowTitle("嵌入式实验") self.setMinimumSize(0,0) #创建五个线程 self.mpu6050_Thread = MBThread(0.25) self.mpu6050_Thread.oneSecondTriger.connect(self.mpu6050_updateImgs) self.HC_SR04_Thread = MBThread(0.25) self.HC_SR04_Thread.oneSecondTriger.connect(self.HC_SR04_updateImgs) self.double_led_Thread = MBThread(0.05) self.double_led_Thread.oneSecondTriger.connect(self.double_led_set_pwm) self.DS18B20_Thread = MBThread(1) self.DS18B20_Thread.oneSecondTriger.connect(self.DS18B20_Thread_show) self.KEYES_Thread = MBThread(0.5) self.KEYES_Thread.oneSecondTriger.connect(self.KEYES_Thread_show_state)#第五步:定义MyFigure类的一个实例 self.mpu6050 = plotCanvas(title="aa",width=3, height=2, dpi=100,axis=3) self.HC_SR04 = plotCanvas(title="bb",width=3, height=2, dpi=100,axis=3) self.Hardware_mpu6050 = Mpu6050_Class() self.Hardware_double_led=Double_LED_Class() self.Hardware_HC_SR04=HC_SR04_Class() self.Hardware_DS18B20=DS18B20_Class() self.Hardware_KEYES=KEYES_Class() #第六步:在GUI的groupBox中创建一个布局,用于添加MyFigure类的实例(即图形)后其他部件。 self.mpu6050_gridlayout = QGridLayout(self.mpu6050_groupBox)# 继承容器groupBox self.mpu6050_gridlayout.addWidget(self.mpu6050) self.HC_SR04_gridlayout = QGridLayout(self.HC_SR04_groupBox)# 继承容器groupBox self.HC_SR04_gridlayout.addWidget(self.HC_SR04) ''' self.timer = QTimer() self.timer.start(5)#每1ms执行一次self.UpdateImgs self.timer.timeout.connect(lambda:self.UpdateImgs()) #self.timer.stop()可以停止1ms执行一次,否则会一直执行下去知道程序结束 ''' #按键点击事件,用来关闭和开启线程 self.mpu6050_pushButton.clicked.connect(self.mpu6050_clickButton) self.HC_SR04_pushButton.clicked.connect(self.HC_SR04_clickButton) self.double_led_pushButton.clicked.connect(self.double_led_clickButton) self.DS18B20_pushButton.clicked.connect(self.DS18B20_clickButton) self.KEYES_pushButton.clicked.connect(self.KEYES_clickButton) self.mpu6050_pushButton.setText("打开线程") self.HC_SR04_pushButton.setText("打开线程") self.double_led_pushButton.setText("打开线程") self.DS18B20_pushButton.setText("打开线程") self.KEYES_pushButton.setText("打开线程")self.mpu6050_Thread.start() self.HC_SR04_Thread.start() self.double_led_Thread.start() self.DS18B20_Thread.start() self.KEYES_Thread.start()def mpu6050_clickButton(self): if self.mpu6050_Thread.stop_flag is False: self.mpu6050_Thread.stop() self.mpu6050_pushButton.setText("打开线程") else: self.mpu6050_Thread.contining() self.mpu6050_pushButton.setText("关闭线程")def HC_SR04_clickButton(self): if self.HC_SR04_Thread.stop_flag is False: self.HC_SR04_Thread.stop() self.HC_SR04_pushButton.setText("打开线程") else: self.HC_SR04_Thread.contining() self.HC_SR04_pushButton.setText("关闭线程") def double_led_clickButton(self): if self.double_led_Thread.stop_flag is False: self.double_led_Thread.stop() self.double_led_pushButton.setText("打开线程") else: self.double_led_Thread.contining() self.double_led_pushButton.setText("关闭线程") def DS18B20_clickButton(self): if self.DS18B20_Thread.stop_flag is False: self.DS18B20_Thread.stop() self.DS18B20_pushButton.setText("打开线程") else: self.DS18B20_Thread.contining() self.DS18B20_pushButton.setText("关闭线程") def KEYES_clickButton(self): if self.KEYES_Thread.stop_flag is False: self.KEYES_Thread.stop() self.KEYES_pushButton.setText("打开线程") else: self.KEYES_Thread.contining() self.KEYES_pushButton.setText("关闭线程")def mpu6050_updateImgs(self): self.mpu6050.clean() self.Hardware_mpu6050.get_data() #a=random.randint(1,9) self.mpu6050.mpu6050_plot(6,6,6,-self.Hardware_mpu6050.pitch/57.3,-self.Hardware_mpu6050.roll/57.3)def double_led_set_pwm(self): red_Slider_value=https://www.it610.com/article/self.red_horizontalSlider.value() #读取当前滑动条值 self.Hardware_double_led.makerobo_set_red_Color(red_Slider_value) red_Slider_value_str=str(red_Slider_value) self.double_red_led_label.setText(red_Slider_value_str) #做了个强转,不然报错:label框需要str类型值green_Slider_value=self.green_horizontalSlider.value() #读取当前滑动条值 self.Hardware_double_led.makerobo_set_green_Color(green_Slider_value) green_Slider_value_str=str(green_Slider_value) self.double_green_led_label.setText(green_Slider_value_str) #做了个强转,不然报错:label框需要str类型值''' print("double_led_set_pwm")''' def HC_SR04_updateImgs(self): self.HC_SR04.clean() us_dis = self.Hardware_HC_SR04.ur_disMeasure() self.HC_SR04.HC_SR04_plot(6,6,6,us_dis) ''' print("HC_SR04_updateImgs")'''def DS18B20_Thread_show(self): temperature=self.Hardware_DS18B20.get_temperature() temperature_str=str(temperature) self.DS18B20_label.setText(temperature_str+u'\u00b0'+"C") ''' print("buzzer_Thread_select")'''def KEYES_Thread_show_state(self): voltage=self.Hardware_KEYES.get_voltage() voltage_2float=round(voltage,2) voltage_str=str(voltage_2float) self.KEYES_label.setText(voltage_str+"V") ''' print("KEYES_Thread_show_state")'''if __name__ == "__main__": app = QApplication(sys.argv) main = MainDialogImgBW() #设置背景颜色 palette = QPalette() palette.setColor(QPalette.Background, Qt.white) main.setPalette(palette) main.show()sys.exit(app.exec_())

3、实验结果
以下为运行过程中的界面:
电子电路|基于树莓派4b的传感器数据可视化实现
文章图片

八、个人总结 【电子电路|基于树莓派4b的传感器数据可视化实现】①尝试了用一阶互补滤波进行mpu6050的姿态角解算,但是当加入界面的显示时根本做不到0.05s进行一个mpu6050数据采集,所以用陀螺仪的积分也就没有什么意义了。程序中用0.25s去采集一次mpu6050的数据并画图显示,直接用加速度计计算的姿态角比一阶互补滤波的效果更好。
②尝试了用PyQt5搭建简单的界面,没有涉及子窗口的设计,但还是学到了很多东西,知道了搭建一个简单界面的具体步骤。
③尝试了多线程的代码编写,了解了一些多线程的原理以及基本的框架。
④进一步加深了对树莓派文件系统的了解。
⑤除了多线程卡了很长时间,ADS1115也卡了较长的时间,最后发现竟然是ADS1115的GND并没有和树莓派的GND接在一起,但是在树莓派上却能显示0x48这个地址,这也说明了模块上要有电源指示灯的重要性。
附录 代码:
链接:https://pan.baidu.com/s/19utb5cYwvhvYIZ-8lFsGBQ
提取码:2s9j

    推荐阅读