Funpack第三季第三期-X-NUCLEO-IKS4A1实现多传感器交互
该项目使用了NUCLEO-G0B1RE,实现了X-NUCLEO-IKS4A1的设计,它的主要功能为:驱动X-NUCLEO-IKS4A1里面的多个传感器,并使用触摸按键实现传感器的数据的切换与选择,传感器数据通过串口上传给PC的上位机,同时上位机也可以实现的对传感器进行选择,开启数据采集与关闭。。 该项目使用了TobudOS,实现了多任务操作系统的设计,它的主要功能为:移植TobudOS到 NUCLEO-G0B1RE,实同多任务控制X-NUCLEO-IKS4A1,同时与上位机实现实时通信。项目具体实现功能可见项目视频。。
标签
嵌入式系统
Funpack活动
开发板
lugl
更新2024-07-08
378

本项目为参与电子森林的Funpack第三季第三期活动。

【项目硬件】

1、 NUCLEO-G0B1RE开发板

2、X-NUCLEO-IKS4A1开发板

【项目软件环境】

1、MDK5.39

2、PyQt5

3、TobudOS

4、Pycharm

【项目设计思路】

本项目的使用器材主要为NUCLEO-G0B1RE与X-NUCLEO-IKS4A1两个开发板。NUCLEO-G0B1RE为主控制芯片,X-NUCLEO-IKS4A1为ST公司的多传感器组合,此次主要通过i2c接口来实现两个项目用料的完美结合,实现实时多传感器的数据获取,同时实时通过UART上传给PC端的上位机,实现数据的可视化。通过开发板上的触摸按键以及PC的上位机软件,实现实时的信息并互来选择、切换传感器的数据展示。

项目的实现的功能比较复杂,涉及的知识面也非常的广。因此我移植 了开源的TobudOS实时操作系统,来实现对STM32G0B1的多任务控制。能同时的处理串口、i2c等多任务协同处理。

为了实现上位机的可视化,我使用了PyQT5进行了上位机设计。上位机可以实现实时分屏展示传感器数据。也可以实时对开发板的数据可以进行传感器进行选择,数据采集的开启与关闭。

【程序流程图】

1、TobudOS任务:

image.png

2、触摸功能任务:

image.png

3、串口数据解析任务:

image.png

4、数据采集与发送任务:

image.png


【传感器驱动移植】

ST的传感都是有现成的stm32系列驱动库下载的,驱动库中有driver与examples文件夹,只需要把driver下面的驱动加载到工程中,并根据examples中的示例进行简单的组织就可以获取传感器的数据了,这可以说是非常的方便。

下面以stts22h为例,展示如何移植驱动:

1、在工程中新建mydriver文件夹image.png


2、把驱动包的stts22h_reg.h/c复制到工程目录中:

image.png

3、把stts22h_reg添加进工程分组中,同时把头文件路径也需要添加进工程。

image.png

4、添加drv_stts22.c/h文件,文件的主要代码如下:

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include "drv_stts22.h"
#include "stts22h_reg.h"
#include "i2c.h"
#include <stdio.h>
#define SENSOR_BUS hi2c1

/* Private macro -------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/

typedef union{
int16_t i16bit;
uint8_t u8bit[2];
} axis1bit16_t;
//声明变量
static axis1bit16_t data_raw_temperature;
static float temperature_degC;
static uint8_t whoamI;
static stmdev_ctx_t dev_ctx;
//声明i2c写入与读取函数
static int32_t platform_write(void *handle, uint8_t reg, uint8_t *bufp,
uint16_t len);
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
uint16_t len);

//初始化
void stts22_init(void)
{

/* Initialize mems driver interface */
dev_ctx.write_reg = platform_write;
dev_ctx.read_reg = platform_read;
dev_ctx.handle = &SENSOR_BUS;

/* Check device ID */
stts22h_dev_id_get(&dev_ctx, &whoamI);
if (whoamI != STTS22H_ID)
while(1); /* manage here device not found */

/*
* Set Output Data Rate
* WARNING: this function can reset the device configuration.
*/
stts22h_temp_data_rate_set(&dev_ctx, STTS22H_1Hz);
}

//数据读取与发送
void test_stts22(void)
{
uint8_t flag;
stts22h_temp_flag_data_ready_get(&dev_ctx, &flag);
if (flag)
{
/* Read temperature data */
memset(data_raw_temperature.u8bit, 0, sizeof(int16_t));
stts22h_temperature_raw_get(&dev_ctx, &data_raw_temperature.i16bit);
temperature_degC = stts22h_from_lsb_to_celsius(data_raw_temperature.i16bit);
printf("{\"dev\":\"stts22\",\"temp\":%3.2f}", temperature_degC);
//sprintf((char*)tx_buffer, "Temperature [degC]:%3.2f\r\n", temperature_degC);

}
}
/*
* @brief Write generic device register (platform dependent)
*
* @param handle customizable argument. In this examples is used in
* order to select the correct sensor bus handler.
* @param reg register to write
* @param bufp pointer to data to write in register reg
* @param len number of consecutive register to write
*
*/
static int32_t platform_write(void *handle, uint8_t reg, uint8_t *bufp,
uint16_t len)
{

HAL_I2C_Mem_Write(handle, STTS22H_I2C_ADD_H, reg,
I2C_MEMADD_SIZE_8BIT, bufp, len, 1000);

return 0;
}

/*
* @brief Read generic device register (platform dependent)
*
* @param handle customizable argument. In this examples is used in
* order to select the correct sensor bus handler.
* @param reg register to read
* @param bufp pointer to buffer that store the data read
* @param len number of consecutive register to read
*
*/
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
uint16_t len)
{

/** I2C Device Address 8 bit format **/
HAL_I2C_Mem_Read(handle, STTS22H_I2C_ADD_H, reg,
I2C_MEMADD_SIZE_8BIT, bufp, len, 1000);
return 0;
}

代码主要是先引入驱动库,再声明变量,组装i2c的读写函数。在数据读取与发送函数中,直接把获取到的以json字符串格式组装,使用printf函数通过串口发送给上位机。

在drv_stts22.h头文件中,我们开放了两个函数给外部使用,分别为初始化与数据读取发送。

#ifndef __DRV_STTS22_H__
#define __DRV_STTS22_H__

void stts22_init(void);
void test_stts22(void);

#endif

【注意】

有些传感器有使能与地址位的选择来配置传感器,我们需要认真的阅读X-NUCLEO-IKS4A1的原理图来确认I2C的地址,并新更到驱动库中。以stts22为例,他的ADDR通过硬件连接到了ADD上,他的地址位为0x70

image.png

这样我就需要在读数读取与写入时定位到驱动的相应地址位的宏定义上。

/** I2C Device Address 8 bit format **/
#define STTS22H_I2C_ADD_H 0x70U

【TobudOS移植】

1、TobudOS为国产开源的嵌入式操作系统,源码下载地址为https://atomgit.com/tobudos

2、移植步骤,在工程项目下面新建OS文件夹,并将源码中的arch、core复制该目录下面,新建一个config文件夹用存放config配置文件。

3、将arch下面的arch\arm\arm-v7m\cortex-m0+\gcc文件夹下面的.c以及.S添加进arch工程分组中,同时把arch\arm\arm-v7m\common下的所有.c也添加进该分组中。

4、将core下的所有.c文件添加进kernel分组中。

image.png

5、添加头文件路径到工程中,具体目录如下:

image.png

6、注释掉原先工程的PendSV_Handler函数,避免重复定义。

7、将TobudOS的心路包函数添加进SysTick_Handler函数中:

/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */

/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
if(tos_knl_is_running()) {
tos_knl_irq_enter();
tos_tick_handler();
tos_knl_irq_leave();
}
/* USER CODE END SysTick_IRQn 1 */
}

8、添加TobudOS配置文件,内容如下:

#ifndef _TOS_CONFIG_H_
#define _TOS_CONFIG_H_

#include "main.h"
#include <stdio.h>
#define TOS_CFG_TASK_PRIO_MAX 10u
#define TOS_CFG_ROUND_ROBIN_EN 1u
#define TOS_CFG_OBJECT_VERIFY_EN 1u
#define TOS_CFG_OBJ_DYNAMIC_CREATE_EN 1u
#define TOS_CFG_EVENT_EN 1u
#define TOS_CFG_MMBLK_EN 1u
#define TOS_CFG_MMHEAP_EN 1u
#define TOS_CFG_MMHEAP_DEFAULT_POOL_SIZE 0x400
#define TOS_CFG_MUTEX_EN 1u
#define TOS_CFG_TIMER_EN 1u
#define TOS_CFG_PWR_MGR_EN 0u
#define TOS_CFG_TICKLESS_EN 0u
#define TOS_CFG_SEM_EN 1u
#define TOS_CFG_TASK_STACK_DRAUGHT_DEPTH_DETACT_EN 1u
#define TOS_CFG_FAULT_BACKTRACE_EN 0u
#define TOS_CFG_IDLE_TASK_STK_SIZE 512u
#define TOS_CFG_CPU_TICK_PER_SECOND 1000u
#define TOS_CFG_CPU_CLOCK (system_core_clock)
#define TOS_CFG_TIMER_AS_PROC 1u
#define TOS_CFG_MAIL_QUEUE_EN 1u

#endif

到此移植TobudOS工程结束

【串口接收任务代码解析】

为了达到高效的获取上位机的通信,开启了中断接收,中断回调函数如下:

// 捕获中断回调函数,每次捕获到信号就会进入这个回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef*UartHandle)
{
usart2_Rxbuff[Rx_flag] = RxBuff[0];
Rx_flag ++;
HAL_UART_Receive_IT(&huart2, (uint8_t *)RxBuff, 1); //每接收一个数据,就打开一次串口中断接收,否则只会接收一个数据就停止接收
}

中断接收到数据后,把数据更新到usart2_Rxbuff缓总区中,并更新到接收到的数据长度。

在TobudOS的数据解析任务中,根据通信协议,如果接收到的数据长度为2,解析出相应的状态代码。其代码如下:

void uart_task(void *Parameter)
{

while(1)
{
if(Rx_flag>0)
{
if(Rx_flag == 2)
{
if(usart2_Rxbuff[0] == 0x22 && usart2_Rxbuff[1] == 0x01)
{
stts22_work_status = 221;
}
else if(usart2_Rxbuff[0] == 0x22 && usart2_Rxbuff[1] == 0x00)
{
stts22_work_status = 220;
}
else if(usart2_Rxbuff[0] == 0x40 && usart2_Rxbuff[1] == 0x01)
{
sht40_work_status = 41;
}
else if(usart2_Rxbuff[0] == 0x40 && usart2_Rxbuff[1] == 0x00)
{
sht40_work_status = 40;
}
else if(usart2_Rxbuff[0] == 0x50 && usart2_Rxbuff[1] == 0x01)
{
lps22_work_status = 51;
}
else if(usart2_Rxbuff[0] == 0x50 && usart2_Rxbuff[1] == 0x00)
{
lps22_work_status = 50;
}
else if(usart2_Rxbuff[0] == 0x60 && usart2_Rxbuff[1] == 0x01)
{
lis2dux_work_status = 61;
}
else if(usart2_Rxbuff[0] == 0x60 && usart2_Rxbuff[1] == 0x00)
{
lis2dux_work_status = 60;
}
}
Rx_flag = 0;
printf("recv %x %x %x\r\n",usart2_Rxbuff[0],usart2_Rxbuff[1],usart2_Rxbuff[2]);
memset(usart2_Rxbuff,0,sizeof(usart2_Rxbuff));
}
tos_task_delay(10);

}
}

【传感器数据采集任务】

在任务中,判断各个传感器的状态码,根据状态码决定是否采集数据并上传,其任务代码如下:

void getdat_task(void *Parameter)
{
while(1)
{
if( 221 == stts22_work_status)
test_stts22();
tos_task_delay(100);
if( 41 == sht40_work_status)
get_sht40();
tos_task_delay(100);
if( 51 == lps22_work_status)
lps22h_test();
tos_task_delay(100);
if( 61 == lis2dux_work_status)
lis2dux12_get_data();
// lis2mdl_read_data_simple();
tos_task_delay(100);
}
}

【触摸状态任务】

void task_getqvr(void *Parameter)
{

//获取qvr
//
/*
1、如果检测到右键按下了
2、开始定时器
3、如果大于50ms 还是按下了,那状态转为按下状态
4、如果检测到 松开按键,那么为单击事件。
5、如果检测按键变成右键按下了,状态转为右向左按下。


*/

/*
0 -- 按键松开
1 -- 右键按下 防抖开始
2 -- 右健按下
3 --- 右键抬起
4 ----左滑到右
5 ----左滑到右抬起

11 -- 左键按下 防抖开始
21 -- 左健按下
31 --- 左键抬起
41 ----右滑到左
51 ----右滑到左抬起
*/
int16_t data;
static uint8_t check_status = 0;
while(1)
{
lsm6dsv16x_all_sources_get(&dev_ctx, &all_sources);
if ( all_sources.drdy_ah_qvar ) {
lsm6dsv16x_ah_qvar_raw_get(&dev_ctx, &data);
//qvr_mv = lsm6dsv16x_from_lsb_to_mv(data);
//printf("QVAR [mV]:%d\r\n", data/78);
data = data/78;

}
if(data > 400)
{
if(check_status == 0)
{
check_status = 1;
}
else if(check_status == 1)
{
// 判断时间
//如果超过了指定时间,则认为是已经按下了
check_status = 2;
}
else if(check_status == 11)
{
check_status = 4;
}
}
else if (-80< data && data < 80)
{
if(check_status == 2)
{
//右按键抬起
check_status = 3;
//printf("right key keyup\r\n");
printf("{\"dev\":\"lsm6dsv16x\",\"code\":1}");
}
else if (check_status == 21)
{
check_status = 31;
printf("{\"dev\":\"lsm6dsv16x\",\"code\":2}");
//printf("left key keyup\r\n");
}
else if (check_status == 4)
{
check_status = 5;
//printf("right->left->key keyup\r\n");
}
else if (check_status == 41)
{
check_status = 51;
//printf("left-> rightkey keyup\r\n");
}
else
{
check_status = 0;
}
}
else if(-400 > data)
{
if(check_status == 0)
{
check_status = 11;
}
else if(check_status == 11)
{
// 判断时间
//如果超过了指定时间,则认为是已经按下了
check_status = 21;
}
else if(check_status == 1)
{
check_status = 41;
}
}
tos_task_delay(50);
}
}

代码根据获取的mv值,更新状态,实现左右按键状态发送给上位机。

【TobudOS任务调度】

	tos_knl_init();
tos_task_create(&task_1,"button_task",getdat_task,NULL,2,task_1_stack,512,100);
tos_task_create(&task_2,"led_task",uart_task,NULL,1,task_2_stack,512,100);
tos_task_create(&task_qvr,"qvr_key_test",task_getqvr,NULL,1,task_getqvr_stack,512,100);
tos_knl_start();

整个系统分为三个任务块,由TobudOS来进行任务调度。

【上位机介绍】

1、上位机采用PyQt5进行界面开发设计界面如下:

image.png

界面添加TabWidget,添加4个传感器的标签。并生成ui的文件。

2、使用PyUi生成对应的.py文件。

3、主程序主要代码介绍

1)引入相应的模块文件

import sys
import time
import json
import re
import binascii

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo
from iks4a1 import Ui_Form

2)添加串口驱动:

self.com = QSerialPort()
self.com.setPortName('COM5')
self.com.setBaudRate(115200)
self.com.open(QSerialPort.ReadWrite)
self.com.readyRead.connect(self.readData)

def readData(self):
while self.com.waitForReadyRead(1):
pass
revData = self.com.readAll().data()
if len(revData) > 0:
print(str(revData))
try:
mydata = json.loads(revData)
print(mydata)
if 'dev' in mydata:
print(" 找到dev")
if mydata['dev'] == "stts22":
self.lcdNumStts.display(mydata['temp'])
elif mydata['dev'] == "sht40":
self.lcdNumSHT40_T.display(mydata['temp'])
self.lcdNumSHT40H.display(mydata['hum'])
elif mydata['dev'] == "lps22":
self.lcdNumLPS22_T.display(mydata['temp'])
self.lcdNumLPS22_P.display(mydata['hPa'])
elif mydata['dev'] == "Lis2dus":
self.lcdNumLIS2DUX_T.display(mydata['temp'])
self.lcdNumLIS2DUXx.display(mydata['data_xlx'])
self.lcdNumLIS2DUXy.display(mydata['data_xly'])
self.lcdNumLIS2DUXz.display(mydata['data_xlz'])
elif mydata['dev'] == "lsm6dsv16x":
if mydata['code'] == 1:
# 1为按键按下
pass
self.thisTabWidgetMaxIndex = self.tabWidget.currentIndex() + 1
if self.thisTabWidgetMaxIndex == 4:
self.thisTabWidgetMaxIndex = 0
self.tabWidget.setCurrentIndex(self.thisTabWidgetMaxIndex)
elif mydata['code'] == 2:
# 2为往右翻动
mytext = self.tabWidget.tabText(self.thisTabWidgetMaxIndex)
if mytext == "STTS22":
self.checkSTTS.setChecked(not self.checkSTTS.isChecked())
elif mytext == "SHT40":
self.checkSHT40.setChecked(not self.checkSHT40.isChecked())
elif mytext == "LPS22":
self.checkLPS22.setChecked(not self.checkLPS22.isChecked())
elif mytext == "lis2dux":
self.checkLIS2DUX.setChecked(not self.checkLIS2DUX.isChecked())

except Exception as e:
print(str(e))

串口驱动采用非阻塞式的接收,接收到下位机上的数据,进行json解析,并相应执行动作。

3)界面操作与下位机的互动代码:

def checkstate(self,check):
if check.text() == "STTS22":
if check.isChecked():
a = '22 01'
d = bytes.fromhex(a)
self.com.write(d)
else:
a = '22 00'
d = bytes.fromhex(a)
self.com.write(d)
elif check.text() == "SHT40":
if check.isChecked():
a = '40 01'
d = bytes.fromhex(a)
self.com.write(d)
else:
a = '40 00'
d = bytes.fromhex(a)
self.com.write(d)
elif check.text() == "LPS22":
if check.isChecked():
a = '50 01'
d = bytes.fromhex(a)
self.com.write(d)
else:
a = '50 00'
d = bytes.fromhex(a)
self.com.write(d)
elif check.text() == "LIS2DUX":
if check.isChecked():
a = '60 01'
d = bytes.fromhex(a)
self.com.write(d)
else:
a = '60 00'
d = bytes.fromhex(a)
self.com.write(d)

在上位机中,checkBox的选中与取消选中,生成相应的协议通过串口下发给下位面,实现传感器数据的采集与关闭功能。

附件下载
STM32G0B1RE_QVR.zip
下位机
iks4a1.zip
上位机
团队介绍
个人开发者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号