Funpack3-3:使用X-NUCLEO-IKS4A1和NUCLEO-G474RE实现VR体感追踪器的设计
该项目使用了X-NUCLEO-IKS4A1和NUCLEO-G474RE,实现了VR体感追踪器的设计,它的主要功能为:计算板卡的空间姿态以及相对海拔高度变化,并且可以将数据传输到电脑上。
标签
嵌入式系统
Funpack活动
开发板
接口
StreakingJerry
更新2024-07-08
50

项目描述

这个项目使用X-NUCLEO-IKS4A1和NUCLEO-G474RE,实现了VR体感追踪器的设计。它的主要功能为:计算板卡的空间姿态以及相对海拔高度变化,并且可以将数据传输到电脑上。


把数据传输到电脑上,所使用的是串口通讯。介绍一下项目用到的硬件部分:

NUCLEO-G474RE

  • 采用LQFP64封装的STM32 微控制器
  • 与ARDUINO®共享的1个用户LED
  • 1个用户按钮和1个复位按钮
  • 32.768 kHz晶体振荡器
  • 板连接器:ARDUINO® Uno V3扩展连接器意法半导体的morpho延长引脚头,用于完全访问所有STM32 I/O
  • 灵活的供电选项:ST-LINK、USB VBUS或外部电源
  • 具有USB重新枚举功能的板上ST-LINK调试器/编程器:大容量存储器、虚拟COM端口和调试端口
  • 提供了全面的免费软件库和例程,可从STM32Cube MCU软件包获得
  • 支持多种集成开发环境(IDE),包括IAR Embedded Workbench®、MDK-ARM,以及STM32CubeIDE
  • 外部SMPS生成Vcore逻辑电源
  • 24 MHz HSE
  • 板连接器:外部SMPS实验专用连接器Micro-AB或Mini-AB USB连接器(用于ST-LINK)MIPI®调试连接器
  • 兼容Arm® Mbed Enabled™


X-NUCLEO-IKS4A1

X-NUCLEO-IKS4A1是一款运动MEMS和环境传感器评估板套件,包括X-NUCLEO-IQS4A1主板(搭载运动MEMS和环境传感器)和可拆卸的STEVAL-MKE001A附加板(搭载Qvar滑动电极)。



LSM6DSV16X

LSM6DSV16X采用系统级封装,包含一个3D数字加速度计和一个3D数字陀螺仪(采用三核芯架构,通过专用的配置、处理和滤波功能在3个独立通道上处理加速度数据、角速度数据)。

LSM6DSV16X在高性能模式下以0.65 mA的电流提供强劲性能,并且具有始终开启功能的同时提供低功耗特性,可为消费者提供优越的运动体验。

LSM6DSV16X为OIS、EIS和运动处理而内嵌先进的专用功能(如有限状态机和MLC(具有面向物联网应用的可导出AI功能))和数据过滤特性。

LSM6DSV16X内嵌的Qvar(电荷变化检测)支持用户界面功能:单击、双击、三击、长按、左/右 - 右/左滑动。

LSM6DSV16X内嵌模拟集线器,能够连接外部模拟输入并将其转换为数字信号进行处理。


LPS22DF

LPS22DF是一款超紧凑型压阻绝对压力传感器,可用作数字输出气压计。LPS22DF相比前代产品具有更低的功耗和更小的压力噪声。

该器件包含传感元件和IC接口,该接口通过I²C、MIPI I3CSM或SPI接口实现传感元件与应用的通信,同时该器件也支持用于数据接口的广泛Vdd IO。检测绝对压力的传感元件由悬浮膜组成,采用ST开发的专门工艺进行制造。

LPS22DF采用全压塑孔LGA封装(HLGA)。可保证在-40 °C到+85 °C的温度范围都能工作。封装上有开孔,以便外部压力到达传感元件。


软件流程图及各功能对应的主要代码片段及说明

image.png

软件分两部分,一个是单片机上的程序,另一部分是上位机程序。先说单片机部分。


单片机部分使用的是PlatformIO中的Arduino框架。

项目配置信息写在platformio.ini文件中:

[env:nucleo_g474re]
platform = ststm32
board = nucleo_g474re
framework = arduino
monitor_speed = 115200
lib_deps =
https://github.com/stm32duino/X-NUCLEO-IKS4A1.git
https://github.com/stm32duino/LSM6DSV16X.git
https://github.com/stm32duino/LSM6DSO16IS.git
https://github.com/stm32duino/LIS2DUXS12.git
https://github.com/stm32duino/LIS2MDL.git
https://github.com/stm32duino/SHT40-AD1B.git
https://github.com/stm32duino/LPS22DF.git
https://github.com/stm32duino/STTS22H.git


接着是主程序部分,首先导入对应的包,声明对象,初始化变量:

#include <Arduino.h>
#include <LSM6DSV16XSensor.h>
#include <LPS22DFSensor.h>

#define ALGO_FREQ 120U /* Algorithm frequency 120Hz */
#define ALGO_PERIOD (1000U / ALGO_FREQ) /* Algorithm period [ms] */
unsigned long startTime, elapsedTime;

LSM6DSV16XSensor AccGyr(&Wire);
uint8_t tag = 0;
float quaternions[4] = { 0 };

LPS22DFSensor PrsTpt(&Wire);
unsigned int height_index = 0;
float height = 0;
const uint8_t height_length = 64;
float height_list[height_length] = { 0 };
float initial_height = 0;
float current_height = 0;


接下来定义高度计算的函数。由于计算值波动巨大,因此进行8次采样取平均:

void read_height() {
uint8_t num_sample = 8;
float pressure_temp;
float temperature_temp;
float pressure = 0;
float temperature = 0;
for (int i = 0; i < num_sample; i++) {
PrsTpt.GetPressure(&pressure_temp);
PrsTpt.GetTemperature(&temperature_temp);
pressure += pressure_temp;
temperature += temperature_temp;
}
pressure = pressure / num_sample;
temperature = temperature / num_sample;
height = (pow((101.325 / (pressure / 10)), (1 / 5.257)) - 1) * (temperature + 273.15) / 0.0065;
}


下面是setup函数,初始化两个传感器,并对初始高度进行计算。每次得到的高度数据装入一个长度为64的列表中,取平均得到初始高度:

void setup() {

Serial.begin(115200);
while (!Serial) {
yield();
}

Wire.begin();

uint8_t status = 0;
// Initialize LSM6DSV16X.
status |= AccGyr.begin();
status |= AccGyr.Enable_X();
status |= AccGyr.Enable_G();
// Enable Sensor Fusion
status |= AccGyr.Set_X_FS(4);
status |= AccGyr.Set_G_FS(2000);
status |= AccGyr.Set_X_ODR(120.0f);
status |= AccGyr.Set_G_ODR(120.0f);
status |= AccGyr.Set_SFLP_ODR(120.0f);
status |= AccGyr.Enable_Rotation_Vector();
status |= AccGyr.FIFO_Set_Mode(LSM6DSV16X_STREAM_MODE);

if (status != LSM6DSV16X_OK) {
Serial.println("LSM6DSV16X Sensor failed to init/configure");
while (1)
;
}

// Initialize LPS22DF.
status |= PrsTpt.begin();
status |= PrsTpt.Enable();
if (status != LPS22DF_OK) {
Serial.println("LPS22DF Sensor failed to init/configure");
while (1)
;
}
delay(1000);
for (int i = 0; i < height_length; i++) {
read_height();
height_list[i] = height;
}
for (int i = 0; i < height_length; i++) {
initial_height += height_list[i];
}
initial_height = initial_height / height_length;
Serial.println(initial_height);
Serial.println("Initialization Finished!");
}


下面是主循环函数,从LSM6DSV16X中获取四元数空间姿态,并打印到串口;高度数据是用最新的采样函数得到的数据,替换掉长度为64的列表中最老的一个高度数据,并取平均。相当于做了个长度为64的移动平均,来平滑数据:

void loop() {
uint16_t fifo_samples;

// Check the number of samples inside FIFO
if (AccGyr.FIFO_Get_Num_Samples(&fifo_samples) != LSM6DSV16X_OK) {
Serial.println("LSM6DSV16X Sensor failed to get number of samples inside FIFO");
return;
}

// Read the FIFO if there is one stored sample
if (fifo_samples > 0) {
for (int i = 0; i < fifo_samples; i++) {
AccGyr.FIFO_Get_Tag(&tag);
if (tag == 0x13u) {
AccGyr.FIFO_Get_Rotation_Vector(&quaternions[0]);
}
}
// Print last Quaternion data
Serial.print("Quaternion: ");
Serial.print(quaternions[3], 4);
Serial.print(", ");
Serial.print(quaternions[0], 4);
Serial.print(", ");
Serial.print(quaternions[1], 4);
Serial.print(", ");
Serial.println(quaternions[2], 4);
}

// Read the height
read_height();
height_list[height_index] = height;
height_index = (height_index + 1) % height_length;
current_height = 0;
for (int i = 0; i < height_length; i++) {
current_height += height_list[i];
}
current_height = current_height / height_length;
Serial.print("Height Data: ");
Serial.print(initial_height, 2);
Serial.print(", ");
Serial.print(current_height, 2);
Serial.print(", ");
Serial.println(current_height - initial_height, 2);
}


上位机使用的是QT6,基于python环境进行编程的。在3D场景中显示一个长方体,然后再根据串口中收到的信息来更新旋转角度和高度。

import sys
from PySide6.QtCore import Property, QObject, QPropertyAnimation, Signal, QTimer
from PySide6.QtGui import QGuiApplication, QMatrix4x4, QQuaternion, QVector3D
from PySide6.Qt3DCore import Qt3DCore
from PySide6.Qt3DExtras import Qt3DExtras

import serial

class Window(Qt3DExtras.Qt3DWindow):
def __init__(self):
super().__init__()
# 打开串口
self.serial = serial.Serial("COM14", 115200)

# Camera
self.camera().lens().setPerspectiveProjection(45, 16 / 9, 0.1, 1000)
self.camera().setPosition(QVector3D(0, 0, 40))
self.camera().setViewCenter(QVector3D(0, 0, 0))

# For camera controls
self.createScene()
self.camController = Qt3DExtras.QOrbitCameraController(self.rootEntity)
self.camController.setLinearSpeed(50)
self.camController.setLookSpeed(180)
self.camController.setCamera(self.camera())

self.setRootEntity(self.rootEntity)

# 创建 QTimer 对象
self.timer = QTimer()
self.timer.setInterval(0.1) # 设置定时器间隔为0.1ms
self.timer.timeout.connect(self.updateTorusRotation) # 连接超时信号到更新函数
self.timer.start() # 启动定时器

def createScene(self):
# Root entity
self.rootEntity = Qt3DCore.QEntity()

# Material
self.material = Qt3DExtras.QGoochMaterial(self.rootEntity)

# cube
self.cube_entity = Qt3DCore.QEntity(self.rootEntity)
self.cube_mesh = Qt3DExtras.QCuboidMesh()
self.cube_mesh.setXExtent(16)
self.cube_mesh.setYExtent(4)
self.cube_mesh.setZExtent(8)

self.cube_transform = Qt3DCore.QTransform()
self.cube_transform.setScale3D(QVector3D(1.0, 1.0, 1.0))
self.cube_transform.setTranslation(QVector3D(0, 0, 0))
self.cube_transform.setRotation(QQuaternion(0, 0, 0, 0))

self.cube_entity.addComponent(self.cube_mesh)
self.cube_entity.addComponent(self.cube_transform)
self.cube_entity.addComponent(self.material)

def updateTorusRotation(self):
# 从串口读取数据
data = self.serial.readline().decode().strip().split(": ")
print(data)
if len(data) == 2 and data[0] == "Quaternion":
quat_values = [float(x) for x in data[1].split(", ")]
quat1 = [quat_values[1], quat_values[0], quat_values[2], -quat_values[3]]
quat = QQuaternion(*quat1)
self.cube_transform.setRotation(quat)
if len(data) == 2 and data[0] == "Height Data":
height_values = [float(x) for x in data[1].split(", ")]
self.cube_transform.setTranslation(QVector3D(0, height_values[2] * 10, 0))

if __name__ == "__main__":
app = QGuiApplication(sys.argv)
view = Window()
view.show()
sys.exit(app.exec())


功能展示及说明

两块板子可以直接组合在一起,Qvar触摸部分需要掰下来插在板子上。

372d6b955d05a13740308f83a2d76fc.jpg


上位机中可以正确显示当前的空间姿态:

5091861ff8fbb04a8d802c76d1e1a17.jpg


串口读取到的信息:

e9b97daafbb3fc5dce7e3838c74854b.png


对本活动的心得体会

本次活动中体验了X-NUCLEO-IKS4A1这款拓展板,这块板子我觉得非常有意思,和其他传感器拓展板的设计思路完全不同。这款板子将多个相似的运动传感器都集成在了同一个板子上,可以方便的横向对比他们的功能以及性能,非常有趣。

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