基于树莓派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 math

class 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 = ((high << 8) | low)

        # 从mpu6050获取有符号值
        if (value > 32768):
            value = 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.0

        self.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、测试用例结果
①模块水平于桌面放置:
在这里插入图片描述
在这里插入图片描述
模块水平放置时,俯仰角理论值为0,翻滚角理论值为0,图中pitch和roll均与理论值相近。
②模块垂直于桌面放置:
在这里插入图片描述
在这里插入图片描述
模块垂直放置时,俯仰角理论值为90°,翻滚角理论值为0,图中pitch和roll均与理论值相近。
③模块与桌面成45°角放置:
在这里插入图片描述
在这里插入图片描述
模块与桌面成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 time

makerobo_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)  # 延时2us

        GPIO.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:
在这里插入图片描述
在这里插入图片描述
由上面两张图片可知误差不超过1cm
②距离20cm:
在这里插入图片描述
在这里插入图片描述
由上面两张图片可知误差不超过1cm

三、3mm双色LED模块

1、整体思路
双色LED类应该能够向外提供两个方法,分别设置红灯和绿灯的PWM。
2、实验步骤
①接线

树莓派 3mm双色LED模块
GND 负极(-)
P17 红色灯正极(中间引脚)
P18 绿色灯正极

②代码编写
编写两个函数分别调整两个灯的亮度。

import RPi.GPIO as GPIO
import time

class 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_min


    def 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、测试用例结果
在这里插入图片描述
3s后变为下面的图片
在这里插入图片描述
程序中首先亮红灯,然后用time.sleep(3)延时了3s,再关闭红灯,亮绿灯,测试结果与理论相符。

四、DS18B20测温模块

1、整体思路
DS18B20类应该能够向外提供获取温度的方法。
2、实验步骤
①接线

树莓派 DS18B20
GND 负极(-)
+5V 正极(中间引脚)
P04 输出引脚(S)

②代码编写
由于DS18B20是在一个文件中读取温度值,为了DS18B20能较为快速地读取温度值,所以在get_temperature方法中不频繁打开和关闭文件,在初始化中打开文件,不再关闭文件,但是每次DS18B20刷新文件内容,如果不执行关闭重开文件的操作的话,文件的光标指针将会指向换行符,导致该方法只能执行一次,所以每次在进入该方法之前先定位光标到69的位置,69由下图得出:
在这里插入图片描述

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----湖南创乐博智能科技有限公司----
#  文件名:25_ds18b20.py
#  版本:V2.0
#  author: zhulin
# 说明: DS18B20数字温度传感器实验
# 注意事项:DS18B20有唯一的地址,一般为28-XXXXXX
#####################################################
import os


class 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
在这里插入图片描述
②手指触碰DS18B20
在这里插入图片描述
由上面两图可知,手指触碰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时可以读到如下图所示两个设备:
在这里插入图片描述
②代码编写

import Adafruit_ADS1x15
import time

class 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、测试用例结果
①手指不触碰传感器上的金属
在这里插入图片描述
在这里插入图片描述
金属触摸传感器的VCC与树莓派的+3.3V相连,用万用表测量金属触摸传感器的VCC引脚结果如上面右图所示,测量值3.23与理论值3.22相近
②手指触碰传感器上的金属
在这里插入图片描述
当手指触摸金属传感器时,A0引脚的电压变小,与理论相符。

六、界面类

利用QT Designer设计界面如下:
在这里插入图片描述

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, QtWidgets


class 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=time

    def 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=False

class 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 = title

        FigureCanvas.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 b

    def 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=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、实验结果
以下为运行过程中的界面:
在这里插入图片描述

八、个人总结

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

附录

代码:
链接:https://pan.baidu.com/s/19utb5cYwvhvYIZ-8lFsGBQ
提取码:2s9j

猜你喜欢

转载自blog.csdn.net/qq_46146657/article/details/125946060