
总概:
(1)使用 Raspberry 4B作为主控芯片,通过不同模块采集信息。
(2)搭建BP神经网络模型并训练,根据采集信息算出蓄电池的补水量。
(3)使用PyQT编写人机交互界面。
硬件准备:
Raspberry Pi 4B

PT1000温度传感器

MAX31865放大器

超声波测距传感器

模数转换器

比重检测模块

总体连接图

环境配置:
树莓派新板子配置可以看我之前的博客,有详细介绍树莓派开机
蓝牙配置
以下是DM35密度仪蓝牙配置及数据传输

Blueman插件

在树莓派上将所需依赖安装上
sudo apt update && sudo apt upgrade
sudo apt install bluetooth pi-bluetooth bluez blueman


之后右上角就出线了蓝牙的图标,可以通过GUI点击蓝牙配对比重仪并且信任该设备,或者通过以下命令行:
sudo bluetoothctl
scan on
pair XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
exit
当比重仪点击发送数据后,树莓派就能接受到数据

发送的是一个Iso8859编码格式的txt文件

这里使用正则表达式将比重、温度、密度等需要信息提取出来


屏幕驱动

可以上LCDWIKI找到具体的LCD信号及其驱动文件。

按照步骤一步一步配置即可,注意的是有些触摸板上是power和touch接口,并不是下图这种双power接口,所以接线时除了HDMI还需要接touch线而不接power线。

软件编写
写得很烂,完全没有去耦合,能跑就行
#用到的python解释库导入
import re
from PyQt5.QtWidgets import QLabel,QLineEdit
from PyQt5.QtCore import QTimer,QDateTime, QSize, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QComboBox
from PyQt5.QtGui import *import sys
import osimport numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from openpyxl.styles.builtins import output
import time
import board
import digitalio
import adafruit_max31865
import ADS1263.ADS1263 as AD
import RPi.GPIO as GPIOos.environ["QT_IM_MODULE"] = "qtvirtualkeyboard"#接下来这部分是BP神经网络的内容#定义每一层之间用到的传递函数
def sigmoid(x):# 第一层到第二层的激活函数return 1 / (1 + np.exp(-x))def deriv_sigmoid(x):# 第一层到第二层的激活函数的求导函数fx = sigmoid(x)return fx * (1 - fx)def mse_loss(y_true, y_pred):# 使用方差作为损失函数
return ((y_true - y_pred) ** 2).mean()#开始神经网络的建立过程
class OurNeuralNetwork:def __init__(self):# 第一层到第二层的权重初始值设置self.w11 = np.random.normal()self.w12 = np.random.normal()self.w13 = np.random.normal()self.w14 = np.random.normal()self.w21 = np.random.normal()self.w22 = np.random.normal()self.w23 = np.random.normal()self.w24 = np.random.normal()# 第二层到第三层的权重初始值设置self.w1 = np.random.normal()self.w2 = np.random.normal()# 每一层截距项的初始值设置,Biasesself.b1 = np.random.normal()self.b2 = np.random.normal()self.b3 = np.random.normal()# 前向传播学习函数def feedforward(self, x):h1 = sigmoid(self.w11 * x[0] + self.w12 * x[1] + self.w13 * x[2] + self.w14 * x[3] + self.b1)h2 = sigmoid(self.w21 * x[0] + self.w22 * x[1] + self.w23 * x[2] + self.w24 * x[3] + self.b1)o1 = self.w1 * h1 + self.w2 * h2 + self.b3return o1#训练函数def train(self, data, all_y_trues):learn_rate = 0.001 # 学习率epochs = 1000 # 训练的次数# 画图数据self.loss = np.zeros(100)self.sum = 0# 开始训练for epoch in range(epochs):for x, y_true in zip(data, all_y_trues):# 计算h1h1 = sigmoid(self.w11 * x[0] + self.w12 * x[1] + self.w13 * x[2] + self.w14 * x[3] + self.b1)# 计算h2h2 = sigmoid(self.w21 * x[0] + self.w22 * x[1] + self.w23 * x[2] + self.w24 * x[3] + self.b2)#计算输出节点y_pred = self.w1 * h1 + self.w2 * h2 + self.b3# 反向传播计算导数d_L_d_ypred = -2 * (y_true - y_pred)d_ypred_d_w1 = h1d_ypred_d_w2 = h2d_ypred_d_b3 = 0d_ypred_d_h1 = self.w1d_ypred_d_h2 = self.w2sum_1=self.w11 * x[0] + self.w12 * x[1] + self.w13 * x[2] + self.w14 * x[3] + self.b1d_h1_d_w11 = x[0] * deriv_sigmoid(sum_1)d_h1_d_w12 = x[1] * deriv_sigmoid(sum_1)d_h1_d_w13 = x[2] * deriv_sigmoid(sum_1)d_h1_d_w14 = x[3] * deriv_sigmoid(sum_1)d_h1_d_b1 = deriv_sigmoid(sum_1)sum_2 = self.w21 * x[0] + self.w22 * x[1] + self.w23 * x[2] + self.w24 * x[3] + self.b2d_h1_d_w21 = x[0] * deriv_sigmoid(sum_2)d_h1_d_w22 = x[1] * deriv_sigmoid(sum_2)d_h1_d_w23 = x[2] * deriv_sigmoid(sum_2)d_h1_d_w24 = x[3] * deriv_sigmoid(sum_2)d_h1_d_b2 = deriv_sigmoid(sum_2)# 梯度下降法self.w11 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w11self.w12 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w12self.w13 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w13self.w14 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w14self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1self.w21 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w21self.w22 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w22self.w23 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w23self.w24 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w24self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_b2self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_w1self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_w2self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3if epoch % 10 == 0:y_preds = np.apply_along_axis(self.feedforward, 1, data)loss = mse_loss(all_y_trues, y_preds)print("Epoch %d loss: %.3f" % (epoch, loss))self.loss[self.sum] = lossself.sum = self.sum + 1# 训练数据文件导入
FILENAME = "/home/pi/Downloads/data.xlsx"
# 禁用科学计数法
pd.set_option('float_format', lambda x: '%.3f' % x)
np.set_printoptions(suppress=True,threshold=sys.maxsize)
# 得到的DataFrame分别为补水量、密度、电压、高度、温度
data = pd.read_excel(FILENAME, header=0, usecols="A,B,C,D,E")
DataArray = data.values
Y = DataArray[:, 0]
X = DataArray[:, 1:5]
X = np.array(X)#转化为array,自变量
Y = np.array(Y)#转化为array,因变量# 处理数据
data = np.array(X)
data_mean = np.sum(data, axis=0) / np.size(data, 0)
data = (data - data_mean) / np.max(data)
all_y_trues = np.array(Y)
all_y_trues_mean = np.sum(all_y_trues) / np.size(all_y_trues)
all_y_trues = (all_y_trues - all_y_trues_mean) / np.max(all_y_trues)# 训练数据
network = OurNeuralNetwork()
network.train(data, all_y_trues)# 输出神经网络参数
print("w11-->%.3f" % network.w11)
print("w12-->%.3f" % network.w12)
print("w13-->%.3f" % network.w13)
print("w14-->%.3f" % network.w14)
print("w21-->%.3f" % network.w21)
print("w22-->%.3f" % network.w22)
print("w23-->%.3f" % network.w23)
print("w24-->%.3f" % network.w24)
print("w1-->%.3f" % network.w1)
print("w2-->%.3f" % network.w2)
print("b1-->%.3f" % network.b1)
print("b2-->%.3f" % network.b2)
print("b3-->%.3f" % network.b3)#这部分是人机界面的建立过程
class Stats():def __init__(self):#主窗口设计参数self.window = QMainWindow()self.window.showFullScreen()self.window.move(0, 0)self.window.setWindowTitle('测控界面')self.window.setWindowFlags(Qt.FramelessWindowHint)self.window.setObjectName("MainWindow")self.window.setStyleSheet("#MainWindow{border-image:url(bg.jpg)}")self.state = Trueself.wendu = QLabel('温度(°)', self.window) # 温度按钮self.wendu.setGeometry(80, 200, 150, 60) # (x坐标,y坐标,宽,高)self.tem = 0self.Label1 = QLineEdit(f'{self.tem}', self.window) # 单文本框1:温度显示值self.Label1.setGeometry(250, 200, 150, 60) # (x坐标,y坐标,宽,高)self.Label1.setAlignment(Qt.AlignCenter) # 设置文本框中内容居中self.bizhong = QLabel('密度(g/cm3)', self.window) # 比重按钮self.bizhong.setGeometry(80, 350, 150, 60) # (x坐标,y坐标,宽,高)self.midu = 0self.Label4 = QLineEdit(f'{self.midu}', self.window) # 单文本框4:比重显示值self.Label4.setGeometry(250, 350, 150, 60) # (x坐标,y坐标,宽,高)self.Label4.setAlignment(Qt.AlignCenter) # 设置文本框中内容居中self.gaodu = QLabel('高度(cm)', self.window) # 高度按钮self.gaodu.setGeometry(640, 200, 150, 60) # (x坐标,y坐标,宽,高)self.hei = 0self.Label2 = QLineEdit(f'{self.hei}', self.window) # 单文本框2:高度显示值self.Label2.setGeometry(810, 200, 150, 60) # (x坐标,y坐标,宽,高)self.Label2.setAlignment(Qt.AlignCenter) # 设置文本框中内容居中self.bushui = QLabel('水量(ml)', self.window) # 补水量按钮self.bushui.setGeometry(640, 350, 150, 60) # (x坐标,y坐标,宽,高)self.shui = 0self.Label3 = QLineEdit(f'{self.shui}', self.window) # 单文本框3:补水量显示值self.Label3.setGeometry(810, 350, 150, 60) # (x坐标,y坐标,宽,高)self.Label3.setAlignment(Qt.AlignCenter) # 设置文本框中内容居中self.ceshi = QPushButton('刷新', self.window) # 开始按钮,点击来对此时测量量进行显示以及赋值self.ceshi.setGeometry(100, 100, 60, 30) # (x坐标,y坐标,宽,高)self.ceshi.clicked.connect(self.handleCalc)self.caculate_out_label = QLabel('补水量',self.window)self.caculate_out_label.setGeometry(100, 50, 60, 30) # (x坐标,y坐标,宽,高)self.caculate_out_label.setAlignment(Qt.AlignCenter)self.Ncalculate_data=0self.caculate_outData_label = QLabel(f'{self.Ncalculate_data}',self.window) # 在self.window窗口添加一个标签time_labelself.caculate_outData_label.setGeometry(180, 50, 60, 30) # (x坐标,y坐标,宽,高)self.caculate_outData_label.setAlignment(Qt.AlignCenter)self.state_label = QLabel(f'OK',self.window) # 在self.window窗口添加一个标签time_labelself.state_label.setGeometry(250, 50, 60, 30) # (x坐标,y坐标,宽,高)self.state_label.setAlignment(Qt.AlignCenter)self.time_label = QLabel(self.window) # 在self.window窗口添加一个标签time_labelself.time_label.setGeometry(700, 0, 300, 200) # (x坐标,y坐标,宽,高)self.time_label.setAlignment(Qt.AlignCenter) # 函数setAlignment()主要将是消除布局中的空隙,让两个控件紧紧挨在一起self.Timer = QTimer() # 创建定时器self.Timer.start(500) # 定时器每500ms工作一次self.Timer.timeout.connect(self.TimeUpdate) # 500指的是500ms,即每500ms调用一下这个TimeUpdate函数# 建立定时器连接通道 注意这里调用TimeUpdate方法,不是方法返回的的结果,所以不能带括号, # 写成self.TimeUpdate()是不对的,带括号代表返回值self.setup = QPushButton('设置', self.window) # 设置按钮:点击进入下一界面self.setup.setGeometry(300, 100, 60, 30) # (x坐标,y坐标,宽,高)self.setup.clicked.connect(self.call_dialog)self.Ncalculate = QPushButton('计算', self.window) # 开始按钮,点击来对此时测量量进行显示以及赋值self.Ncalculate.setGeometry(200, 100, 60, 30) # (x坐标,y坐标,宽,高)self.Ncalculate.clicked.connect(self.Ncalculate_handle)# 电池类型选择:模式一,模式二,模式三分别代表不同的电池组类型self.moshi = QComboBox(self.window)self.moshi.setGeometry(400, 100, 110, 30) # (x坐标,y坐标,宽,高)self.moshi.addItem("模式1")self.moshi.addItem("模式2")self.moshi.addItem("模式3")# 下面是前面用到的函数功能定义总结# 函数一:用于变量显示def handleCalc(self): # 开始按钮对应的函数 def handleCalc(self):# 通过SPI通讯读取MAX31865上面的数据(读取热电偶输出值)spi = board.SPI()cs = digitalio.DigitalInOut(board.D5) # Chip select of the MAX31865 board.sensor = adafruit_max31865.MAX31865(spi, cs, rtd_nominal=1000.0, ref_resistor=4300.0, wires=4)print("Temperature: {0:0.3f}C".format(sensor.temperature))print("resistance: {0:0.3f} Ohms".format(sensor.resistance))self.tem = round(sensor.temperature,2)self.temperature=sensor.temperature
# 通过SPI通讯分时复用读取ADS1263上面的数据(读取超声波传感器上输出值)REF = 5.08 # Modify according to actual voltagetry:ADC = AD.ADS1263()if (ADC.ADS1263_init_ADC1('ADS1263_7200SPS') == -1):exit()ADC.ADS1263_SetMode(0)self.ADC_Value = ADC.ADS1263_GetAll() # get ADC1 valueself.height = self.ADC_Value[1] * REF / 0x7fffffffprint("ADC1 IN1 = %lf" % self.height) # 32bitself.hei=round(self.height, 2)except IOError as e:print(e)self.buttonLabel1 = f'{self.tem}'self.Label1.setText(self.buttonLabel1) # 改变标签1的函数,给标签1赋当前温度值self.buttonLabel2 = f'{self.hei}'self.Label2.setText(self.buttonLabel2) # 改变标签1的函数,给标签2赋当前温度值try:f = open('./measureLog_export.txt',encoding='iso8859')except:self.Density=0print(f'bluetooth error:not file')self.state = Falseelse:read_data_line=f.readlines()f.close()read_data_line_count=len(read_data_line)os.remove("./measureLog_export.txt")regex = re.compile("[0-9]+.*[0-9]")search_data=regex.search(read_data_line[read_data_line_count-6])SampleID=str(search_data.group())print(f'SampleID is {SampleID}')regex = re.compile("[0-9]+.*[0-9]")search_data=regex.search(read_data_line[read_data_line_count-5])self.Density=float(search_data.group())print(f'Density is {self.Density}')regex = re.compile("[0-9]+.*[0-9]")search_data=regex.search(read_data_line[read_data_line_count-3])SG=float(search_data.group())print(f'SG is {SG}')self.state = Trueself.midu=round(self.Density, 2)self.buttonLabel4 = f'{self.midu}'self.Label4.setText(self.buttonLabel4)info = self.moshi.currentText() # 提取模式多组合选择里面的文本出来,此时文本包括字符串以及数字salary = re.sub("\D", "", info) # 提取info里面的数字出来if int(salary) == 1: # 根据模式不同,赋给按钮6补水量的值也有所不同self.shui = 10self.buttonLabel3 = f'{self.shui}'self.Label3.setText(self.buttonLabel3) # 改变按钮6标签的函数if int(salary) == 2:self.shui = 20self.buttonLabel3 = f'{self.shui}'self.Label3.setText(self.buttonLabel3) # 改变按钮6标签的函数if int(salary) == 3:self.shui = 30self.buttonLabel3 = f'{self.shui}'self.Label3.setText(self.buttonLabel3) # 改变按钮6标签的函数# 函数二:用于时间刷新,实时时间显示:目前是调用当前电脑上时间def TimeUpdate(self):self.time_label.setText(QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss dddd'))if self.state == False :self.state_label.setText('Error')else :self.state_label.setText('OK')def is_number(self,s):pattern = re.compile(r'^[-+]?[-0-9]\d*\.\d*|[-+]?\.?[0-9]\d*$')result = pattern.match(s)if result:return Trueelse:return Falsedef Ncalculate_handle(self):calculate_data_Density=self.Label4.text()if not self.is_number(calculate_data_Density):calculate_data_Density = 0print('Density input is not number')self.Label4.setText('0')self.state = Falseelse :calculate_data_Density = float(calculate_data_Density)if calculate_data_Density==self.midu :calculate_data_Density=self.Densityelse :self.state = Truecalculate_data_temperature=self.Label1.text()if not self.is_number(calculate_data_temperature):calculate_data_temperature = 0print('Temperature input is not number')self.state = Falseself.Label1.setText('0')else :calculate_data_temperature = float(calculate_data_temperature)if calculate_data_temperature==self.tem :calculate_data_temperature=self.temperaturecalculate_data_shui=self.Label3.text()if not self.is_number(calculate_data_shui):calculate_data_shui = 0print('Water input is not number')self.state = Falseself.Label3.setText('0')else :calculate_data_shui = float(calculate_data_shui)if calculate_data_shui==self.shui :calculate_data_shui=self.shuicalculate_data_height=self.Label2.text()if not self.is_number(calculate_data_height):calculate_data_height = 0print('Height input is not number')self.state = Falseself.Label2.setText('0')else :calculate_data_height = float(calculate_data_height)if calculate_data_height==self.hei :calculate_data_height=self.heightself.k1 = calculate_data_Densityself.k2 = calculate_data_heightself.k3 = calculate_data_shuiself.k4 = calculate_data_temperaturez = [self.k1, self.k2, self.k3, self.k4]z = (z - data_mean) / np.max(data)z1 = z[0]z2 = z[1]z3 = z[2]z4 = z[3]self.h1 = sigmoid(network.w11 * z1 + network.w12 * z2 + network.w13 * z3 + network.w14 * z4 + network.b1)self.h2 = sigmoid(network.w21 * z1 + network.w22 * z2 + network.w23 * z3 + network.w24 * z4 + network.b2)self.output = network.w1 * self.h1 + network.w2 * self.h2 + network.b3self.output = self.output * np.max(all_y_trues) + all_y_trues_meanself.d1 = round(self.output, 2) # 0.0005 is changed as 0.05self.Ncalculate_data=self.d1self.caculate_outData_label.setText(f'{self.Ncalculate_data}')print(f'Neural Networks OutPut is {self.d1}')# 函数三:点击设置按钮后用于主界面的关闭与子界面1的打开def call_dialog(self):self.childwindow = QMainWindow() # 子界面childwindow的设置self.childwindow.resize(400, 300)self.childwindow.move(300, 100)self.childwindow.setWindowTitle('设置界面')self.save = QPushButton('返回', self.childwindow) # 存储按钮self.save.move(80, 200)self.save.clicked.connect(self.back)self.language = QPushButton('语言', self.childwindow) # 语言按钮self.language.move(80, 40)self.language.clicked.connect(self.call_dialog2)self.test = QPushButton('校零', self.childwindow) # 校零按钮self.test.move(80, 120)self.test.clicked.connect(self.backzero)self.childwindow.show()# 函数四 语言按钮的子窗口2def call_dialog2(self):self.childwindow2 = QMainWindow() # 子界面childwindow的设置self.childwindow2.resize(400, 300)self.childwindow2.move(300, 100)self.childwindow2.setWindowTitle('语言界面')self.foreign = QPushButton('英语', self.childwindow2) # 存储按钮self.foreign.move(80, 40)self.foreign.clicked.connect(self.English)self.china = QPushButton('中文', self.childwindow2) # 语言按钮self.china.move(80, 120)self.china.clicked.connect(self.Chinese)self.childwindow.close()self.childwindow2.show()def English(self):self.wendu.setText('Temeratiure')self.gaodu.setText('Height')self.ceshi.setText('Begin')self.bizhong.setText('Density')self.setup.setText('Setting')self.save.setText('Save')self.language.setText('Language')self.test.setText('Test')self.foreign.setText('English')self.china.setText('Chinese')self.save.setText('Save')self.childwindow2.close()self.childwindow.show()def Chinese(self):self.wendu.setText('温度')self.gaodu.setText('高度')self.ceshi.setText('刷新')self.bizhong.setText('密度')self.setup.setText('设置')self.save.setText('存储')self.language.setText('语言')self.test.setText('校零')self.foreign.setText('英语')self.china.setText('中文')self.save.setText('返回')self.childwindow2.close()self.childwindow.show()# 函数五:点击返回按钮后用于子界面1的关闭与主界面的打开def back(self):self.childwindow.close()self.window.show()# 函数六:点击返回按钮后用于子界面1的关闭与主界面的打开def backzero(self):self.shui = 0self.buttonLabel1 = f'{self.shui}c'self.Label1.setText(self.buttonLabel1) # 改变单文本框1标签的函数,给赋当前温度值self.buttonLabel2 = f'{self.shui}cm'self.Label2.setText(self.buttonLabel2) # 改变单文本框2标签的函数,赋当前高度值self.buttonLabel3 = f'{self.shui}ml'self.Label3.setText(self.buttonLabel3) # 改变单文本框3标签的函数self.buttonLabel4 = f'{self.shui}ml'self.Label4.setText(self.buttonLabel4) # 改变单文本框4标签的函数self.childwindow.close()self.window.show()app = QApplication([])
stats = Stats()
stats.window.show()
app.exec_()


为方便调试、测量数据,编写两套启动脚本
auto_start_test.sh:不含神经网络计算,只为测量、调试,启动、运行更快
auto_start.sh:含神经网络计算部分,先将测量后的数据进行训练学习,利用模型得出计算量