Funpack3-3 基于X-NUCLEO-IKS4A1 的数据显示及切换
该项目使用了X-NUCLEO-IKS4A1扩展版和NUCLEO-G0B1RE开发板,C语言,实现了传感器数据显示切换的设计,它的主要功能为:读取开发板上自带的传感器,将其发送给上位机,显示出来。
标签
嵌入式系统
Funpack活动
反正都一样
更新2024-07-08
33

项目介绍

这里是我参加Funpack第三季第三期活动的任务总结报告,我所完成的是任务一,使用板卡上的触摸按键,实现点按和左右滑动,实现传感器选择和切换,并将数据发送到上位机,功能选择的可视化也在上位机完成。

步骤一:配置开发板引脚功能

  1. 使用 STM32CubeMX 打开 NUCLEO-G0B1RE 开发板配置。
  2. 手动开启 I2C1 的引脚并配置相应功能,以实现与 X-NUCLEO-IKS4A1 扩展板的通信。

步骤二:选择传感器软件包

  1. 在 CubeMX 中找到 X-CUBE-MEMS1 软件包,该软件包包含所需的传感器驱动程序。
  2. 选择该软件包,无需自行编写传感器的驱动程序。

步骤三:配置串口通信

  1. 开启 UART1 用于开发板与上位机的串口通信。
  2. 通过 UART1 将传感器数据传输到 PC 上。

步骤四:制作上位机软件

  1. 使用 PyQt5 制作上位机软件,用于接收来自开发板的数据。
  2. 在上位机软件中显示传感器数据。

步骤五:按钮控制

  1. 使用板子上的触摸按钮与上位机软件中的切换按钮,实现选择要显示的传感器数据的功能。


通过依次执行这些步骤,搭建一个完整的系统,实现开发板与传感器扩展板之间的通信,并将传感器数据显示在 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软件生成相应的驱动程序。在这个程序的基础上,修改成我们的任务内容。

image.png

需要手动开启的引脚,上一个箭头,通过这个i2c与扩展板进行数据通信,下一个箭头则是我们与上位机通信的串口。

image.png

这个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发送传感器数据。

image.png

上位机软件则是使用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

image.png

这是pyqt5的界面制作展示

image.png

上图即通过板子上的串口向上位机发送数据,并在上位机上显示。

image.png

上图展示触摸板的滑动效果

总结

在本次活动中,学习了如何使用pyqt5进行上位机的制作。在过程中遇到的问题,通过百度搜索都能找到适合的答案,使自我得到了提升,感谢硬禾学堂平台。

附件下载
funpack3-3.zip
上位机的exe可执行程序请自己用pyinstaller生成
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号