Funpack3-3 基于X-NUCLEO-IKS4A1的传感器数据可视化
该项目使用了NUCLEO-G0B1RE、X-NUCLEO-IKS4A1,实现了传感器数据可视化的设计,它的主要功能为:通过STM32将传感器数据读出,并通过串口发送到上位机,上位机接收传感器数据并显示。。
标签
Funpack活动
X-NUCLEO-IKS4A1
C6C6C6
更新2024-07-08
58

一、项目简介

本项目基于NUCLEO-G0B1RE和X-NUCLEO-IKS4A1开发板,通过点按和滑动Qvar触摸电极来选择和切换不同的传感器,采集加速度、温度、气压等环境数据。采集到的数据通过串口发送至PC上位机,并利用Electron框架构建的界面进行实时可视化展示,从而实现对动作和环境的动态监测。

二、硬件简介

NUCLEO-G0B1RE是一款基于STM32G0B1RE微控制器的开发板,属于STMicroelectronics的Nucleo系列,旨在为开发者提供便捷、高效的开发环境。该开发板具有以下主要特性:

  • 核心微控制器:STM32G0B1RE,基于ARM Cortex-M0+内核,具有低功耗和高性能的特点。
  • 集成调试器:板载ST-Link/V2-1调试器,支持编程、调试和虚拟COM端口通信,无需额外的调试工具。
  • 易于使用:支持广泛的开发环境,包括STM32CubeIDE、IAR EWARM、Keil MDK和第三方开发工具,适合初学者和专业开发者快速上手。

NUCLEO-G0B1RE 开发板的设计旨在帮助开发者快速验证新设计并创建原型,广泛应用于物联网、工业控制、消费电子等领域。

X-NUCLEO-IKS4A1是是一款功能丰富的传感器扩展板,专为与STM32 Nucleo开发板配合使用而设计。该板集成了多种传感器,适用于广泛的应用场景,包括运动检测和环境监控。主要特性包括:

  • 集成传感器:3D加速度计、陀螺仪、磁力计、气压计、温湿度传感器。
  • 可拆卸Qvar触摸/滑动电极模块(STEVAL-MKE001A):提供点按和滑动触摸功能,增强了用户交互体验。
  • 兼容性:与多种STM32 Nucleo开发板兼容,通过I2C或SPI接口进行通信,方便开发者进行快速原型设计。

X-NUCLEO-IKS4A1 使得开发者能够轻松集成和使用多种传感器,为物联网和嵌入式系统项目提供了强大的硬件基础。



三、软件简介

软件概述

这是一款基于Electron框架开发的PC端上位机软件,主要用于接收并显示从NUCLEO-G0B1RE开发板通过串口发送的X-NUCLEO-IKS4A1传感器数据。软件提供了直观的用户界面,能够实时监控并显示多个传感器的数据,包括加速度计、陀螺仪、磁力计、温湿度传感器和压力传感器的数据。

功能特点

  1. 串口通信
    • 支持多种波特率选择(9600、19200、38400、57600、115200)。
    • 动态列出可用串口,并允许用户选择串口进行连接。
    • 提供串口打开和关闭功能,并实时显示连接状态。
  2. 数据显示
    • 实时接收并解析传感器数据。
    • 以表格形式显示加速度计、陀螺仪、磁力计、温湿度传感器和压力传感器的数据。
    • 支持点击传感器标题,切换显示状态。
  3. 日志记录
    • 显示实时接收到的串口数据日志,便于调试和监控数据变化。
    • 提供清空数据功能,方便用户重新监控。

技术实现

  • 基于Electron框架:实现跨平台桌面应用,支持Windows、macOS和Linux系统。
  • SerialPort库:用于实现串口通信,支持多种串口操作和数据处理。
  • HTML/CSS/JavaScript:前端界面开发,提供用户友好的交互界面和实时数据展示。

使用说明

  1. 打开软件后,在控制面板中选择串口和波特率。
  2. 点击“打开”按钮,连接到NUCLEO-G0B1RE开发板。
  3. 实时查看并监控从X-NUCLEO-IKS4A1传感器板发送的数据。
  4. 左右滑动QVAR电极模块切换选中传感器数据,单击模块右侧关闭选中数据显示,单击模块左侧开启选中数据显示。
  5. 可通过鼠标点击传感器标题切换数据的显示状态,或点击“清空数据”按钮清空当前数据和日志。
  6. 点击“关闭”按钮断开串口连接。

代码实现

更新日志框的函数

function updateLogBox(data) {
const logBox = document.getElementById('log-box');
logBox.textContent += data + '\n';
logBox.scrollTop = logBox.scrollHeight;
}
  • 将数据追加到日志框,并自动滚动到最新日志位置。

打开/关闭串口按钮事件监听器

document.getElementById('open-port').addEventListener('click', () => {
const portName = document.getElementById('port').value;
const baudRate = parseInt(document.getElementById('baudrate').value, 10);
ipcRenderer.send('open-port', { portName, baudRate });
updateConnectionStatus(true); // 假设连接成功
});

document.getElementById('close-port').addEventListener('click', () => {
ipcRenderer.send('close-port');
updateConnectionStatus(false); // 假设连接已关闭
});
  • 绑定打开和关闭串口按钮的点击事件,发送对应的IPC消息,并更新连接状态。

清除数据按钮事件监听器

document.getElementById('clear-data').addEventListener('click', clearAllData);
  • 绑定清除数据按钮的点击事件,调用clearAllData函数清空所有数据。

IPC 事件监听器

ipcRenderer.on('serial-data', (event, data) => {
console.log('Received data:', data);
dataBuffer += data;

if (dataBuffer.includes('\n')) {
const completeData = dataBuffer.split('\n');
dataBuffer = completeData.pop(); // 剩余的部分保留在缓冲区中

completeData.forEach(dataLine => {
console.log('Processing line:', dataLine);
const parsedData = parseSerialData(dataLine);
console.log('Parsed data:', parsedData);
updateSensorDisplay();
updateDisplay(parsedData);
updateLogBox(dataLine);
});
}
});

ipcRenderer.on('serial-error', (event, error) => {
console.error(`Error: ${error}`);
});

ipcRenderer.on('ports-list', (event, ports) => {
const portSelect = document.getElementById('port');
portSelect.innerHTML = '';
ports.forEach((port) => {
const option = document.createElement('option');
option.value = port.path;
option.textContent = port.path;
portSelect.appendChild(option);
});
});

ipcRenderer.send('list-ports');
  • 监听serial-data事件接收串口数据,处理并更新显示。
  • 监听serial-error事件处理错误。
  • 监听ports-list事件列出可用串口。

更新连接状态的函数

function updateConnectionStatus(connected) {
const statusElement = document.getElementById('connection-status');
statusElement.textContent = connected ? '已连接' : '未连接';
statusElement.style.color = connected ? '#00e676' : '#ff4081';
}
  • 更新连接状态的显示,改变文本和颜色。

清除所有数据的函数

function clearAllData() {
const sensorIds = [
'lsm6dsv16x-acc-x', 'lsm6dsv16x-acc-y', 'lsm6dsv16x-acc-z',
'lsm6dsv16x-gyr-x', 'lsm6dsv16x-gyr-y', 'lsm6dsv16x-gyr-z',
'lis2mdl-mag-x', 'lis2mdl-mag-y', 'lis2mdl-mag-z',
'environment-humidity', 'environment-temperature', 'environment-pressure'
];
sensorIds.forEach(id => {
document.getElementById(id).textContent = '-';
});
document.getElementById('log-box').textContent = '';
}
  • 清空所有传感器数据和日志框内容。

解析串口数据的函数

javascript复制代码function parseSerialData(data) {
const dataObj = {};

// 匹配数据的正则表达式
const pressMatch = data.match(/PressValue: PRE = (\d+)/);
const tempMatch = data.match(/TempValue: TEM = (\d+)/);
const humMatch = data.match(/HumValue: HUM = (\d+)/);

const accXMatch = data.match(/AccValue: ACC.X = (-?\d+)/);
const accYMatch = data.match(/AccValue: ACC.Y = (-?\d+)/);
const accZMatch = data.match(/AccValue: ACC.Z = (-?\d+)/);

// 根据匹配结果填充 dataObj 对象
if (pressMatch) dataObj['PRESSURE'] = pressMatch[1] / 100;
if (tempMatch) dataObj['TEMPERATURE'] = tempMatch[1] / 100;
if (humMatch) dataObj['HUMIDITY'] = humMatch[1] / 100;
if (accXMatch) dataObj['ACCELERATION_X'] = accXMatch[1];
if (accYMatch) dataObj['ACCELERATION_Y'] = accYMatch[1];
if (accZMatch) dataObj['ACCELERATION_Z'] = accZMatch[1]; //省略重复代码

return dataObj;
}
  • 解析接收到的串口数据,将其转换为对象格式。

更新显示数据的函数

function updateDisplay(data) {
if (data['OFF_ON']) { //根据OFF_ON修改显示状态
if (data['OFF_ON'] === '2') {
displayStatus[Cur] = 0;
} else if (data['OFF_ON'] === '1') {
displayStatus[Cur] = 1;
}
}

if (data['Cur_select']) { //更新当前选中数据
Cur = data['Cur_select'];
updateChooseDisplay();
}

if (data['ACCELERATION_X']) document.getElementById('lsm6dsv16x-acc-x').textContent = data['ACCELERATION_X']; //省略重复代码
}
  • 根据解析后的数据更新显示内容。

更新传感器显示的函数

function updateSensorDisplay() {
const sensorIds = [
'lsm6dsv16x-acc-x', 'lsm6dsv16x-acc-y', 'lsm6dsv16x-acc-z',
'lsm6dsv16x-gyr-x', 'lsm6dsv16x-gyr-y', 'lsm6dsv16x-gyr-z',
'lis2mdl-mag-x', 'lis2mdl-mag-y', 'lis2mdl-mag-z',
'environment-humidity', 'environment-temperature', 'environment-pressure'
];

sensorIds.forEach((id, index) => {
const element = document.getElementById(id);
if (displayStatus[index] === 0) {
element.style.color = '#333333'; // 文字颜色设置为与背景相同
} else {
element.style.color = '#00e676'; // 显示
}
});
}
  • 根据displayStatus数组的状态显示或隐藏传感器数据。

更新当前选择显示的函数

function updateChooseDisplay() {
const titleIds = [
'acc-x-title', 'acc-y-title', 'acc-z-title',
'gyr-x-title', 'gyr-y-title', 'gyr-z-title',
'mag-x-title', 'mag-y-title', 'mag-z-title',
'humidity-title', 'temperature-title', 'pressure-title'
];

titleIds.forEach((id, index) => {
const titleElement = document.getElementById(titleIds[index]);

if (Cur == index) {
titleElement.style.color = '#CCFFFF'; // 设置标题颜色
} else {
titleElement.style.color = '#ff4081'; // 恢复默认标题颜色
}
});
}
  • 根据当前选择的传感器索引更新标题颜色。

切换文本颜色的函数

function toggleTextColor(element) {
const currentColor = element.style.color;
if (currentColor === 'rgb(255, 64, 129)' || currentColor === '#ff4081') {
element.style.color = '#CCFFFF';
switch (element.id) {
case 'lsm6dsv16x-title':
displayStatus[0] = 0; displayStatus[1] = 0; displayStatus[2] = 0;
displayStatus[3] = 0; displayStatus[4] = 0; displayStatus[5] = 0;
break;
case 'lis2mdl-title':
displayStatus[6] = 0; displayStatus[7] = 0; displayStatus[8] = 0;
break;
case 'sht40ad1b-title':
displayStatus[9] = 0; displayStatus[10] = 0;
break;
case 'lps22df-title':
displayStatus[11] = 0;
break;
default:
break;
}
} else {
element.style.color = '#ff4081';
switch (element.id) {
case 'lsm6dsv16x-title':
displayStatus[0] = 1; displayStatus[1] = 1; displayStatus[2] = 1;
displayStatus[3] = 1; displayStatus[4] = 1; displayStatus[5] = 1;
break;
case 'lis2mdl-title':
displayStatus[6] = 1; displayStatus[7] = 1; displayStatus[8] = 1;
break;
case 'sht40ad1b-title':
displayStatus[9] = 1; displayStatus[10] = 1;
break;
case 'lps22df-title':
displayStatus[11] = 1;
break;
default:
break;
}
}
}
  • 切换传感器标题的文本颜色并更新对应的显示状态。

数据采集部分概述

数据采集部分采用STM32CubeIDE开发。首先搜索STM32G0B1选择NUCLEO-G0B1RE开发板创建工程。配置并开启I2C1,配置管脚为PB9PB8。安装和配置X-CUBE-MEMS1软件包,在Software Packs中安装X-CUBE-MEMS1软件包,在Middleware and Software Packs中配置X-CUBE-MEMS1。本部分详细内容推荐看ZERO大佬的文章。

IKS4A1读取传感器数值教程 - 知乎 (zhihu.com)

基于IKS4A1的QVAR电极的触摸识别的思路与实现 - 知乎 (zhihu.com)

代码实现

传感器数据采集发送

uint32_t lastTick = 0; // 用于记录上一次执行任务的时间
uint32_t currentTick = HAL_GetTick();
if (currentTick - lastTick >= 500) { //500ms采集并发送一次数据
MX_MEMS_Process();//添加至主循环
lastTick = currentTick; // 更新lastTick为当前时间
}
  • 根据当前选择的传感器索引更新标题颜色。


QVAR数据处理和控制命令发送

	  int16_t value; 
BSP_SENSOR_QVAR_GetValue(&value); //获取QVAR的数值
//函数具体细节见ZERO大佬的文章
qvar_digital(value, &act_output); // 调用 qvar_digital 函数处理触摸按键值
switch (act_output) {// 根据 act_output 的值执行相应的动作
case -2:
printf("OFF_ON: Val = %d\n", 2); //隐藏当前选中数据
break;
case -1:
if (++Cur > 11)
Cur = 0;
printf("Cur_select: Cur = %d\n", Cur); //切换选中数据
break;
case 0:
// 没有动作或者动作未被识别
break;
case 1:
if (--Cur < 0)
Cur = 11;
printf("Cur_select: Cur = %d\n", Cur); //切换选中数据
break;
case 2:
printf("OFF_ON: Val = %d\n", 1); //显示当前选中数据
break;
default:
// 其他未定义的动作
break;
}
HAL_Delay(1);
  • 获取并处理QVAR值,向上位机发送控制命令。

可编译下载的代码

链接:https://pan.baidu.com/s/1Sxd2JBMpJZe7-LNz3iTraA

提取码:C6C6

四、总结与展望

总结

在此次Funpack活动中,我收获颇丰,具体总结如下:

  1. 掌握了通过STM32CubeIDE开发STM32
    • 学会了如何配置和使用STM32CubeIDE开发环境。
    • 能够熟练应用ST官方提供的软件包进行传感器数据的读取和处理。
  2. 学会了使用ST官方提供的软件包
    • 理解并掌握了X-CUBE-MEMS1软件包的配置和使用方法。
    • 成功实现了通过触摸按键选择和切换传感器,并将数据发送到上位机。
  3. 掌握了通过Electron框架开发PC上位机
    • 使用Electron框架构建了上位机界面,实现了传感器数据的接收和处理。
    • 能够将MCU发送的数据在上位机上进行可视化展示。
  4. 学会了基础的视频剪辑
    • 完成了任务所需的3-5分钟短视频制作,学习并掌握了基础的视频剪辑和AI配音技巧。

展望

  1. 完善上位机
    • 目前上位机中仍存在一些BUG(如需要先将STM32连接到PC再启动上位机),有待进一步调试和修复。
    • 计划构建更美观、更加用户友好的上位机界面,提升整体用户体验。
  2. 尝试实现任务二和三
    • 活动结束后,我将参考其他朋友们的项目,尝试实现任务二和任务三,进一步提升自己的技能和经验。

此次活动不仅让我在实践能力上有了显著的提升,更加深了我对电子设计和物联网开发的热情和兴趣。未来,我将继续努力,争取在这些领域取得更大的进步和成长。

附件下载
stm32g0b1re.zip
STM32代码
my-electron-app.zip
上位机代码
团队介绍
萌新嵌入式爱好者。
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号