总概:
(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 os
import 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 GPIO
os.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()
# 每一层截距项的初始值设置,Biases
self.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.b3
return 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):
# 计算h1
h1 = sigmoid(self.w11 * x[0] + self.w12 * x[1] + self.w13 * x[2] + self.w14 * x[3] + self.b1)
# 计算h2
h2 = 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 = h1
d_ypred_d_w2 = h2
d_ypred_d_b3 = 0
d_ypred_d_h1 = self.w1
d_ypred_d_h2 = self.w2
sum_1=self.w11 * x[0] + self.w12 * x[1] + self.w13 * x[2] + self.w14 * x[3] + self.b1
d_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.b2
d_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_w11
self.w12 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w12
self.w13 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w13
self.w14 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w14
self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
self.w21 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w21
self.w22 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w22
self.w23 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w23
self.w24 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_w24
self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h1_d_b2
self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_w1
self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_w2
self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3
if 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] = loss
self.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 = True
self.wendu = QLabel('温度(°)', self.window) # 温度按钮
self.wendu.setGeometry(80, 200, 150, 60) # (x坐标,y坐标,宽,高)
self.tem = 0
self.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 = 0
self.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 = 0
self.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 = 0
self.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=0
self.caculate_outData_label = QLabel(f'{self.Ncalculate_data}',self.window) # 在self.window窗口添加一个标签time_label
self.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_label
self.state_label.setGeometry(250, 50, 60, 30) # (x坐标,y坐标,宽,高)
self.state_label.setAlignment(Qt.AlignCenter)
self.time_label = QLabel(self.window) # 在self.window窗口添加一个标签time_label
self.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 voltage
try:
ADC = AD.ADS1263()
if (ADC.ADS1263_init_ADC1('ADS1263_7200SPS') == -1):
exit()
ADC.ADS1263_SetMode(0)
self.ADC_Value = ADC.ADS1263_GetAll() # get ADC1 value
self.height = self.ADC_Value[1] * REF / 0x7fffffff
print("ADC1 IN1 = %lf" % self.height) # 32bit
self.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=0
print(f'bluetooth error:not file')
self.state = False
else:
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 = True
self.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 = 10
self.buttonLabel3 = f'{self.shui}'
self.Label3.setText(self.buttonLabel3) # 改变按钮6标签的函数
if int(salary) == 2:
self.shui = 20
self.buttonLabel3 = f'{self.shui}'
self.Label3.setText(self.buttonLabel3) # 改变按钮6标签的函数
if int(salary) == 3:
self.shui = 30
self.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 True
else:
return False
def Ncalculate_handle(self):
calculate_data_Density=self.Label4.text()
if not self.is_number(calculate_data_Density):
calculate_data_Density = 0
print('Density input is not number')
self.Label4.setText('0')
self.state = False
else :
calculate_data_Density = float(calculate_data_Density)
if calculate_data_Density==self.midu :
calculate_data_Density=self.Density
else :
self.state = True
calculate_data_temperature=self.Label1.text()
if not self.is_number(calculate_data_temperature):
calculate_data_temperature = 0
print('Temperature input is not number')
self.state = False
self.Label1.setText('0')
else :
calculate_data_temperature = float(calculate_data_temperature)
if calculate_data_temperature==self.tem :
calculate_data_temperature=self.temperature
calculate_data_shui=self.Label3.text()
if not self.is_number(calculate_data_shui):
calculate_data_shui = 0
print('Water input is not number')
self.state = False
self.Label3.setText('0')
else :
calculate_data_shui = float(calculate_data_shui)
if calculate_data_shui==self.shui :
calculate_data_shui=self.shui
calculate_data_height=self.Label2.text()
if not self.is_number(calculate_data_height):
calculate_data_height = 0
print('Height input is not number')
self.state = False
self.Label2.setText('0')
else :
calculate_data_height = float(calculate_data_height)
if calculate_data_height==self.hei :
calculate_data_height=self.height
self.k1 = calculate_data_Density
self.k2 = calculate_data_height
self.k3 = calculate_data_shui
self.k4 = calculate_data_temperature
z = [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.b3
self.output = self.output * np.max(all_y_trues) + all_y_trues_mean
self.d1 = round(self.output, 2) # 0.0005 is changed as 0.05
self.Ncalculate_data=self.d1
self.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()
# 函数四 语言按钮的子窗口2
def 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 = 0
self.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:含神经网络计算部分,先将测量后的数据进行训练学习,利用模型得出计算量
Original: https://blog.csdn.net/qq_41495871/article/details/127809293
Author: PeepFuture橙子
Title: 基于树莓派的蓄电池控制系统
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/656665/
转载文章受原作者版权保护。转载请注明原作者出处!