项目介绍
这里是我参加Funpack第三季第三期活动的任务总结报告,我所完成的是任务一,使用板卡上的触摸按键,实现点按和左右滑动,实现传感器选择和切换,并将数据发送到上位机,功能选择的可视化也在上位机完成。
步骤一:配置开发板引脚功能
- 使用 STM32CubeMX 打开 NUCLEO-G0B1RE 开发板配置。
- 手动开启 I2C1 的引脚并配置相应功能,以实现与 X-NUCLEO-IKS4A1 扩展板的通信。
步骤二:选择传感器软件包
- 在 CubeMX 中找到 X-CUBE-MEMS1 软件包,该软件包包含所需的传感器驱动程序。
- 选择该软件包,无需自行编写传感器的驱动程序。
步骤三:配置串口通信
- 开启 UART1 用于开发板与上位机的串口通信。
- 通过 UART1 将传感器数据传输到 PC 上。
步骤四:制作上位机软件
- 使用 PyQt5 制作上位机软件,用于接收来自开发板的数据。
- 在上位机软件中显示传感器数据。
步骤五:按钮控制
- 使用板子上的触摸按钮与上位机软件中的切换按钮,实现选择要显示的传感器数据的功能。
通过依次执行这些步骤,搭建一个完整的系统,实现开发板与传感器扩展板之间的通信,并将传感器数据显示在 PC 上的上位机软件中。
主要硬件介绍
这里主要说明下4A1扩展版上的相应的传感器:
- LSM6DSO16IS:MEMS 3D加速度计 (±2/±4/±8/±16 g) + 3D陀螺仪 (±125/±250/±500/±1000/±2000 dps) 与ISPU(智能处理单元)
- LIS2MDL:MEMS 3D磁力计 (±50 gauss)
- LIS2DUXS12:超低功耗MEMS 3轴加速度计 (±2/±4/±8/±16 g),具有Qvar、AI,以及抗混叠功能
- LPS22DF:低功率和高精度MEMS压力传感器,260-1260 hPa绝对数字输出气压计
- SHT40AD1B
- STTS22H:低电压,超低功耗,0.5°C精度的温度传感器(–40 °C到+125 °C)
- LSM6DSV16X:MEMS 3D加速度计 (±2/±4/±8/±16 g) + 3D陀螺仪 (±125/±250/±500/±1000/±2000/±4000 dps),内嵌传感器融合、AI、Qvar
提供了用于 IoT 领域动作检测和环境监测的综合传感器解决方案,包含 X-NUCLEO-IQS4A1 主板(具备运动 MEMS 和环境传感器)以及可拆卸的 STEVAL-MKE001A 附加板(带有 Qvar 滑动电极)。该扩展板具备高度集成性和兼容性,为 IoT 领域的动作检测和环境监测提供了多种传感器解决方案。
主要软件介绍
软件开发上,我们可以先使用官方的stm32cubemx软件生成相应的驱动程序。在这个程序的基础上,修改成我们的任务内容。
需要手动开启的引脚,上一个箭头,通过这个i2c与扩展板进行数据通信,下一个箭头则是我们与上位机通信的串口。
这个x-cube-mems1软件包包含了扩展板上的每个传感器驱动方式,同时配置好右侧箭头所指,通信接口,用户按钮,用户led的引脚
uint8_t cal_qvar(int16_t temp1){
uint32_t temp_tick=HAL_GetTick();
int16_t qvar=0,qvar_t1=0,qvar_t2=0;
uint8_t num=0,state=0,return_data=0;//1 点按
printf("first %d\n",temp1);
while(1){
BSP_SENSOR_QVAR_GetValue(&QvarValue);
qvar=(int)QvarValue/78;
// printf("{one:%d}\n",qvar);
if(qvar > 100)
qvar_t1++;
if(qvar < -100)
qvar_t2++;
if(qvar < 50 && qvar > -50){
num++;
if(num > 50){
break;
}
}else
num=0;
HAL_Delay(10);
}
printf("@@@@@@@ %d %d\n",qvar_t1,qvar_t2);
if(abs(qvar_t1-qvar_t2) < 10){
if(temp1 < 0){
printf("右滑\n");
touch_buf[1]=2;
}else{
touch_buf[1]=3;
printf("左滑\n");
}
}else{
touch_buf[1]=1;
printf("点按\n");
}
HAL_UART_Transmit_IT(&huart1,touch_buf,2);
return return_data;
}
/*
* LM background task
*/
void MX_MEMS_Process(void)
{
/* USER CODE BEGIN MEMS_Process_PreTreatment */
uint32_t tick1,tick2;
uint8_t temp1;
/* USER CODE END MEMS_Process_PreTreatment */
tick1=HAL_GetTick();
tick2=HAL_GetTick();
int16_t qvar=0;
while(1){
BSP_SENSOR_QVAR_GetValue(&QvarValue);
qvar=(int)QvarValue/78;
if(qvar > 200 || qvar < -200){
temp1=cal_qvar(qvar);
}
if(HAL_GetTick()-tick1 > 1000){
MX_IKS4A1_DataLogTerminal_Process();
tick1=HAL_GetTick();
}
// printf("{one:%d}\n",qvar);
// HAL_Delay( 50 );
}
/* USER CODE BEGIN MEMS_Process_PostTreatment */
/* USER CODE END MEMS_Process_PostTreatment */
}
上述代码是修改后的驱动代码部分,我这里只开启了LSM6DSV16X和SHT40AD1B两个传感器数据的获取。while循环中,不断检测触摸按钮是否触发,同时每隔一段时间向串口1发送传感器数据。
上位机软件则是使用pyqt5制作而成,上图是界面的配置,可以看到有串口的刷新,选择,打开关闭,左侧则是两个传感器数据显示的区域,传来的数据都在这里显示。上下项按钮,可以切换当前传感器的显示。
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(640, 480)
MainWindow.setMinimumSize(QtCore.QSize(640, 480))
MainWindow.setMaximumSize(QtCore.QSize(640, 480))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.port_check_button = QtWidgets.QPushButton(self.centralwidget)
self.port_check_button.setGeometry(QtCore.QRect(530, 40, 75, 23))
self.port_check_button.setObjectName("port_check_button")
self.com_state_label = QtWidgets.QLabel(self.centralwidget)
self.com_state_label.setGeometry(QtCore.QRect(300, 0, 341, 31))
self.com_state_label.setText("")
self.com_state_label.setAlignment(QtCore.Qt.AlignCenter)
self.com_state_label.setObjectName("com_state_label")
self.s1__box_2 = QtWidgets.QComboBox(self.centralwidget)
self.s1__box_2.setGeometry(QtCore.QRect(440, 40, 69, 22))
self.s1__box_2.setObjectName("s1__box_2")
self.open_button = QtWidgets.QPushButton(self.centralwidget)
self.open_button.setGeometry(QtCore.QRect(440, 80, 75, 23))
self.open_button.setObjectName("open_button")
self.close_button = QtWidgets.QPushButton(self.centralwidget)
self.close_button.setGeometry(QtCore.QRect(530, 80, 75, 23))
self.close_button.setObjectName("close_button")
self.pre_button = QtWidgets.QPushButton(self.centralwidget)
self.pre_button.setGeometry(QtCore.QRect(530, 150, 75, 23))
self.pre_button.setObjectName("pre_button")
self.nex_button = QtWidgets.QPushButton(self.centralwidget)
self.nex_button.setGeometry(QtCore.QRect(530, 200, 75, 23))
self.nex_button.setObjectName("nex_button")
self.LSM6DSV16X = QtWidgets.QLabel(self.centralwidget)
self.LSM6DSV16X.setGeometry(QtCore.QRect(20, 50, 291, 31))
self.LSM6DSV16X.setStyleSheet("font: 16pt \"微软雅黑\";")
self.LSM6DSV16X.setAlignment(QtCore.Qt.AlignCenter)
self.LSM6DSV16X.setObjectName("LSM6DSV16X")
self.SHT40AD1B = QtWidgets.QLabel(self.centralwidget)
self.SHT40AD1B.setGeometry(QtCore.QRect(20, 230, 291, 31))
self.SHT40AD1B.setStyleSheet("font: 16pt \"微软雅黑\";")
self.SHT40AD1B.setAlignment(QtCore.Qt.AlignCenter)
self.SHT40AD1B.setObjectName("SHT40AD1B")
self.layoutWidget_2 = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget_2.setGeometry(QtCore.QRect(20, 270, 291, 82))
self.layoutWidget_2.setObjectName("layoutWidget_2")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget_2)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.label_9 = QtWidgets.QLabel(self.layoutWidget_2)
self.label_9.setStyleSheet("font: 12pt \"微软雅黑\";")
self.label_9.setAlignment(QtCore.Qt.AlignCenter)
self.label_9.setObjectName("label_9")
self.verticalLayout_3.addWidget(self.label_9)
self.label_10 = QtWidgets.QLabel(self.layoutWidget_2)
self.label_10.setStyleSheet("font: 12pt \"微软雅黑\";")
self.label_10.setAlignment(QtCore.Qt.AlignCenter)
self.label_10.setObjectName("label_10")
self.verticalLayout_3.addWidget(self.label_10)
self.horizontalLayout_2.addLayout(self.verticalLayout_3)
self.verticalLayout_4 = QtWidgets.QVBoxLayout()
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.hum_label = QtWidgets.QLabel(self.layoutWidget_2)
self.hum_label.setStyleSheet("font: 12pt \"微软雅黑\";")
self.hum_label.setAlignment(QtCore.Qt.AlignCenter)
self.hum_label.setObjectName("hum_label")
self.verticalLayout_4.addWidget(self.hum_label)
self.temp_label = QtWidgets.QLabel(self.layoutWidget_2)
self.temp_label.setStyleSheet("font: 12pt \"微软雅黑\";")
self.temp_label.setAlignment(QtCore.Qt.AlignCenter)
self.temp_label.setObjectName("temp_label")
self.verticalLayout_4.addWidget(self.temp_label)
self.horizontalLayout_2.addLayout(self.verticalLayout_4)
self.state_label = QtWidgets.QLabel(self.centralwidget)
self.state_label.setGeometry(QtCore.QRect(240, 390, 381, 31))
self.state_label.setStyleSheet("font: 16pt \"微软雅黑\";")
self.state_label.setAlignment(QtCore.Qt.AlignCenter)
self.state_label.setObjectName("state_label")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(20, 100, 291, 82))
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.label_2 = QtWidgets.QLabel(self.layoutWidget)
self.label_2.setStyleSheet("font: 12pt \"微软雅黑\";")
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName("label_2")
self.verticalLayout.addWidget(self.label_2)
self.label_3 = QtWidgets.QLabel(self.layoutWidget)
上面是pyqt5界面文件转换成py文件的形式。
import sys
import serial
import serial.tools.list_ports
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QMainWindow,QApplication
from Ui_untitled import Ui_MainWindow
class MyWindow(QMainWindow,Ui_MainWindow):
def __init__(self,parent =None):
super(MyWindow,self).__init__(parent)
self.setupUi(self)
self.init() #调用下面的init函数
self.ser = serial.Serial()
self.port_check() #串口检测
self.uart_recv_timer = QTimer(self)
self.uart_recv_timer.timeout.connect(self.data_receive) # 串口中断是2ms进一次
self.touch_label_timer = QTimer(self)
self.touch_label_timer.setSingleShot(True) # 设置定时器为单次触发模式
self.touch_label_timer.timeout.connect(self.blank_touch_label) # 连接定时器的超时信号到处理槽
def init(self):
self.port_check_button.clicked.connect(self.port_check) # 单击 检测串口 按钮链接到port_check函数
self.s1__box_2.currentTextChanged.connect(self.port_imf) # 当前文本改变(串口选择) 链接到串口信息函数
# 打开串口按钮
self.open_button.clicked.connect(self.port_open) # 单击 打开串口 按钮链接到打开串口函数
# 关闭串口按钮
self.close_button.clicked.connect(self.port_close) # 单击 关闭串口 按钮链接到关闭串口函数
self.nex_button.clicked.connect(self.nex_button_callback)
self.pre_button.clicked.connect(self.pre_button_callback)
def port_check(self):
# 检测所有存在的串口,将信息存储在字典中
self.Com_Dict = {} #创建一个字典,字典是可变的容器
port_list = list(serial.tools.list_ports.comports()) #list是序列,一串数据,可以追加数据
self.s1__box_2.clear() #s1__box_2为串口选择列表
for port in port_list:
self.Com_Dict["%s" % port[0]] = "%s" % port[1]
self.s1__box_2.addItem(port[0]) #将检测到的串口放置到s1__box_2串口选择列表
if len(self.Com_Dict) == 0:
self.com_state_label.setText(" 无串口")
# ------------------串口选择下拉框选择com口
def port_imf(self):
# 显示选定的串口的详细信息
imf_s = self.s1__box_2.currentText() #当前显示的com口
if imf_s != "":
self.com_state_label.setText(self.Com_Dict[self.s1__box_2.currentText()])#state_label显示窗口显当前串口
# -------------------打开串口
def port_open(self):
self.ser.port = self.s1__box_2.currentText() #串口选择框
self.ser.baudrate = 115200 #波特率输入框
self.ser.bytesize = 8 #数据位输入框
self.ser.stopbits = 1 #停止位输入框
self.ser.parity = 'N' #校验位输入框
try:
self.ser.open()
except:
QMessageBox.critical(self, "Port Error", "此串口不能被打开!")
return None
self.current_index=1
self.state_label.setText("当前显示 LSM6DSV16X")
self.uart_recv_timer.start(500) #打开串口接收定时器,周期为2ms
if self.ser.isOpen(): #打开串口按下,禁用打开按钮,启用关闭按钮
self.open_button.setEnabled(False) #禁用打开按钮
self.close_button.setEnabled(True) #启用关闭按钮
# --------------------关闭串口
def port_close(self):
self.uart_recv_timer.stop() # 停止计时器
try:
self.ser.close()
except:
pass
self.open_button.setEnabled(True) #启用打开按钮
self.close_button.setEnabled(False) #禁用停止按钮
self.acc_x_label.setText(str(0))
self.acc_y_label.setText(str(0))
self.acc_z_label.setText(str(0))
self.hum_label.setText("0 %")
self.temp_label.setText("0 degC")
self.state_label.setText("当前显示")
def nex_button_callback(self):
if self.current_index == 1:
self.current_index = 2
self.state_label.setText("当前显示 SHT40-AD1B")
self.acc_x_label.setText(str(0))
self.acc_y_label.setText(str(0))
self.acc_z_label.setText(str(0))
else:
self.current_index = 1
self.state_label.setText("当前显示 LSM6DSV16X")
self.hum_label.setText("0 %")
self.temp_label.setText("0 degC")
def pre_button_callback(self):
if self.current_index == 1:
self.current_index = 2
self.state_label.setText("当前显示 SHT40-AD1B")
self.acc_x_label.setText(str(0))
self.acc_y_label.setText(str(0))
self.acc_z_label.setText(str(0))
else:
self.current_index = 1
self.state_label.setText("当前显示 LSM6DSV16X")
self.hum_label.setText("0 %")
self.temp_label.setText("0 degC")
def blank_touch_label(self):
self.touch_label.setText("")
def data_receive(self):
# input_s = "abc"
# input_s = (input_s + '\r\n').encode('utf-8')
# num = self.ser.write(input_s) #串口写,返回的写入的数据数
# print("Number: %d" % num)
try:
num = self.ser.inWaiting() # 获取接受缓存中的字符数
except:
self.port_close()
return None
if num > 0:
self.recv_data = self.ser.read(num) # 从串口读取指定字节大小的数据
self.recv_num = len(self.recv_data) # 等到收到数据的长度
# print("recv: %d" % self.recv_num)
self.uart_pro()
def uart_pro(self):
# print(self.recv_data)
# print("uart_recv: %d" % self.recv_num)
index_dd = self.recv_data.find(b'\xDD')
if index_dd != -1:
print("dind dd")
if self.recv_data[1] == 1:
print("点按")
self.touch_label.setText("点按")
elif self.recv_data[1] == 2:
print("右滑")
self.touch_label.setText("右滑")
if self.current_index == 1:
self.current_index = 2
self.state_label.setText("当前显示 SHT40-AD1B")
self.acc_x_label.setText(str(0))
self.acc_y_label.setText(str(0))
self.acc_z_label.setText(str(0))
else:
self.current_index = 1
self.state_label.setText("当前显示 LSM6DSV16X")
self.hum_label.setText("0 %")
self.temp_label.setText("0 degC")
elif self.recv_data[1] == 3:
print("左滑")
self.touch_label.setText("左滑")
if self.current_index == 1:
self.current_index = 2
self.state_label.setText("当前显示 SHT40-AD1B")
self.acc_x_label.setText(str(0))
self.acc_y_label.setText(str(0))
self.acc_z_label.setText(str(0))
else:
self.current_index = 1
self.state_label.setText("当前显示 LSM6DSV16X")
self.hum_label.setText("0 %")
self.temp_label.setText("0 degC")
self.touch_label_timer.start(1000)
else:
print("no find dd")
index_aa = self.recv_data.index(b'\xaa')
index_bb = self.recv_data.index(b'\xbb')
index_cc = self.recv_data.index(b'\xcc')
# print("aa index:", index_aa)
# print("bb index:", index_bb)
# print("cc index:", index_cc)
if self.current_index == 1:
# 加速度
acc_x_bytes = self.recv_data[index_aa+1:5]
# print(self.recv_data[index_aa+1:5])
acc_y_bytes = self.recv_data[index_aa+5:9]
# print(self.recv_data[index_aa+5:9])
acc_z_bytes = self.recv_data[index_aa+9:13]
# print(self.recv_data[index_aa+9:13])
acc_x = int.from_bytes(acc_x_bytes, byteorder='big', signed=True)
acc_y = int.from_bytes(acc_y_bytes, byteorder='big', signed=True)
acc_z = int.from_bytes(acc_z_bytes, byteorder='big', signed=True)
# print(acc_x)
# print(acc_y)
# print(acc_z)
self.acc_x_label.setText(str(acc_x))
self.acc_y_label.setText(str(acc_y))
self.acc_z_label.setText(str(acc_z))
elif self.current_index == 2:
# 湿度
hum_int_bytes = self.recv_data[index_bb+1:index_bb+5]
# print(hum_int_bytes)
hum_dec_bytes = self.recv_data[index_bb+5:index_bb+9]
# print(hum_dec_bytes)
hum_int = int.from_bytes(hum_int_bytes, byteorder='big', signed=True)
hum_dec = int.from_bytes(hum_dec_bytes, byteorder='big', signed=True)
# print(hum_int)
# print(hum_dec)
humidity = f"{hum_int}.{hum_dec} %"
self.hum_label.setText(str(humidity))
# 温度
temp_int_bytes = self.recv_data[index_cc+1:index_cc+5]
# print(hum_int_bytes)
temp_dec_bytes = self.recv_data[index_cc+5:index_cc+9]
# print(hum_dec_bytes)
temp_int = int.from_bytes(temp_int_bytes, byteorder='big', signed=True)
temp_dec = int.from_bytes(temp_dec_bytes, byteorder='big', signed=True)
# print(hum_int)
# print(hum_dec)
temperature = f"{temp_int}.{temp_dec} degC"
self.temp_label.setText(str(temperature))
这个则是上位机软件的在业务处理,逻辑处理的上的函数内容。使用 Python 中的 Serial 库,实现在 PC 上打开、关闭串口,并进行串口数据的接收和发送操作。一旦串口打开,将持续监测是否有可接收的数据;如果有数据可接收,进行处理并将处理后的结果显示在上位机界面上。通过上下选项,可以选择显示不同传感器数据,实现数据的切换显示。s
这是pyqt5的界面制作展示
上图即通过板子上的串口向上位机发送数据,并在上位机上显示。
上图展示触摸板的滑动效果
总结
在本次活动中,学习了如何使用pyqt5进行上位机的制作。在过程中遇到的问题,通过百度搜索都能找到适合的答案,使自我得到了提升,感谢硬禾学堂平台。