Funpack3-3:X-NUCLEO-IKS4A1-数据采集和功能切换
该项目使用了X-NUCLEO-IKS4A1和 NUCLEO-G0B1RE板子,实现了传感器数据采集和上位机的设计,它的主要功能为:使用板卡上的触摸按键,实现点按和左右滑动,实现传感器选择和切换,并将数据发送到上位机。
标签
Funpack活动
开发板
鲍飞.
更新2024-07-08
116

项目需求

使用板卡上的触摸按键,实现点按和左右滑动,实现传感器选择和切换,并将数据发送到上位机,功能选择的可视化也在上位机完成。

比如:能够选择加速度传感器,开启X轴数据发送,然后关闭加速度显示,选择温度等相似的功能。

需求分析

  1. 实现传感器数据的读取。
  2. 读取触摸按键的数据,并且判断点击和滑动的事件。
  3. 完成上位机可视化的设计。

板卡介绍

X-NUCLEO-IKS4A1板卡集成了一系列环境和动作传感器,并且配备Qvar触摸/滑动电极,如下:

  • 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


搭配使用的STM32开发板为NUCLEO-G0B1RE,这是一款STM32 Nucleo-64板,为用户提供了一种可负担的灵活方法,具有以下及更多的功能:

  • 采用LQFP64封装的STM32 微控制器
  • 1个用户LED
  • 1个用户按钮和1个复位按钮
  • 32.768 kHz晶体振荡器
  • 板连接器:ARDUINO® Uno V3扩展连接器意法半导体的morpho延长引脚头,用于完全访问所有STM32 I/O
  • 灵活的供电选项:ST-LINK、USB VBUS或外部电源
  • 具有USB重新枚举功能的板上ST-LINK调试器/编程器:大容量存储器、虚拟COM端口和调试端口


X-NUCLEO-IKS4A1和NUCLEO-G0B1RE组合的照片如下:


功能实现

主要分为两个部分,MCU开发和QT界面开发。

下位机

采用STM32G0芯片,使用官方的CubeIDE图形化界面开发。

吐槽一下,可能是Keil用吸习惯了,感觉现在IDE并不是很好用,需要图形化配置还是用CubeMX吧...

在图形界面中主要配置的有以下个部分:

  • I2C1
  • USART2
  • GPIO_EXTI13
  • GPIO_Output
  • GPIO_Input
  • TIM6
  • X-CUBE-MEMS1

在图形化配置的时候,有时候经过会报错,提示XXX文件没有,XXX定义没有,这一般估计都是在图形化界面配置的时候,提示了Error,根据提示的错误进行修改,

主要的程序流程如下:

主要代码

main函数

int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM6_Init();
MX_MEMS_Init();
HAL_TIM_Base_Start_IT(&htim6);

int flag;
char* TouchState = "IDLE";
int i = 0;
while (1)
{
flag = TouchResult();
switch(flag){
case LEFT_PRESSED:
TouchState = "LEFT_PRESSED";
break;
case RIGHT_PRESSED:
TouchState = "RIGHT_PRESSED";
break;
case SLIDING_LEFT:
TouchState = "SLIDING_LEFT";
break;
case SLIDING_RIGHT:
TouchState = "SLIDING_RIGHT";
break;
default:
TouchState = "IDLE";
}
HAL_Delay(250);
MX_MEMS_Process();
}
}

触摸读取

主要是在定时器种,不断读取并且进行处理判断触摸状态

enum TouchState touchState = IDLE;            //触摸状态
const int PRESS_THRESHOLD = 25000; //按下阈值
const int RELEASE_THRESHOLD = 10000; //释放阈值
const int SLIDE_START_THRESHOLD = 10000; //滑动阈值

static int16_t currentTouchValue = 0; //当前触摸状态
static enum TouchState LastTouch = 0; //上次触摸状态
static enum TouchState LastLastTouch = 0; //上上次触摸状态

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static int lastTouchValue = 0;

if (htim->Instance == TIM6)
{
LastLastTouch = LastTouch;
LastTouch = touchState;

BSP_SENSOR_QVAR_GetValue(&currentTouchValue);
if (touchState == IDLE) { //空闲状态下,判断是否有按下
if (abs(currentTouchValue) > PRESS_THRESHOLD) {
if (currentTouchValue < 0)
touchState = LEFT_PRESSED;
else if (currentTouchValue > 0)
touchState = RIGHT_PRESSED;
}
}
else if (touchState == LEFT_PRESSED || touchState == RIGHT_PRESSED) {//左按或者右按
if (abs(currentTouchValue) <= RELEASE_THRESHOLD) {
touchState = IDLE;
}
else if ((touchState == LEFT_PRESSED && currentTouchValue > lastTouchValue + SLIDE_START_THRESHOLD)
|| (touchState == RIGHT_PRESSED && currentTouchValue < lastTouchValue - SLIDE_START_THRESHOLD)) {
touchState = (touchState == LEFT_PRESSED) ? SLIDING_RIGHT : SLIDING_LEFT;
}
}
else if (touchState == SLIDING_LEFT || touchState == SLIDING_RIGHT) {//左滑或者右滑
if (abs(currentTouchValue) <= RELEASE_THRESHOLD) {
touchState = IDLE;
}
}
lastTouchValue = currentTouchValue;
}
}

int TouchResult(void)
{
enum TouchState flag = 0;

//防误触发
if(touchState == LastLastTouch){
flag = IDLE;
}
else if((touchState == LEFT_PRESSED || touchState == RIGHT_PRESSED) && LastTouch == IDLE){
flag = IDLE;
}
else if((touchState == SLIDING_LEFT && LastTouch == SLIDING_LEFT) || (touchState == SLIDING_RIGHT && LastTouch == SLIDING_RIGHT)){
flag = IDLE;
}
else{
flag = touchState;
}

//printf("touchState = %d, LastTouch = %d, LastLastTouch = %d\r\n",touchState, LastTouch, LastLastTouch);
return flag;
}

传感器数据读取

主要是通过CubeMX直接生成的

void MX_IKS4A1_DataLogTerminal_Process(void)
{
int i;

if (PushButtonDetected != 0U)
{
/* Debouncing */
HAL_Delay(50);

/* Wait until the button is released */
while ((BSP_PB_GetState( BUTTON_KEY ) == PushButtonState));

/* Debouncing */
HAL_Delay(50);

/* Reset Interrupt flag */
PushButtonDetected = 0;

MX_IKS4A1_DataLogTerminal_Init();
}

snprintf(dataOut, MAX_BUF_SIZE, "\r\n__________________________________________________________________________\r\n");
printf("%s", dataOut);

for(i = 0; i < IKS4A1_MOTION_INSTANCES_NBR; i++)
{
if(MotionCapabilities[i].Acc)
{
Accelero_Sensor_Handler(i);
}
if(MotionCapabilities[i].Gyro)
{
Gyro_Sensor_Handler(i);
}
if(MotionCapabilities[i].Magneto)
{
Magneto_Sensor_Handler(i);
}
}

for(i = 0; i < IKS4A1_ENV_INSTANCES_NBR; i++)
{
if(EnvCapabilities[i].Humidity)
{
Hum_Sensor_Handler(i);
}
if(EnvCapabilities[i].Temperature)
{
Temp_Sensor_Handler(i);
}
if(EnvCapabilities[i].Pressure)
{
Press_Sensor_Handler(i);
}
}

HAL_Delay( 1000 );
}

上位机

采用QT进行开发。

在串口打开的时候,成功或者失败都会弹框进行提示,默认不显示任务数据,并且选择框为红色。

当右触摸点击的时候,选择框切换为紫色,并且显示数据【右图】。当左触摸点击的时候,变为缺省值【左图】

可以通选择框选择不同的传感器,也可以通过下位机上的触摸传感器,左滑或者右滑来切换。

主要代码

监听串口数据

Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ShowleLable(0);
// 初始化串口设置
SerialLoad();
connect(&serial, &QSerialPort::readyRead, this, &Widget::readData); // 当串口有数据可读时触发readData槽函数
}

开关控制串口的开或关

/* 打开和关闭串口,默认115200 */
void Widget::on_SerialOpen_But_clicked()
{
if(ui->SerialOpen_But->text() == "open"){
currentPortName = ui->SerialNum_Box->currentText();

if (!currentPortName.isEmpty()) {
serial.setPortName(currentPortName);
serial.setBaudRate(QSerialPort::Baud115200);
serial.setDataBits(QSerialPort::Data8);
serial.setParity(QSerialPort::NoParity);
serial.setStopBits(QSerialPort::OneStop);
serial.setFlowControl(QSerialPort::NoFlowControl);

if (serial.open(QIODevice::ReadWrite)) {
ui->SerialOpen_But->setText("close");
ui->SerialNum_Box->setEnabled(false);
ui->SerialRefresh_But->setEnabled(false);
QMessageBox::information(this,"提示","successful");
} else {
QMessageBox::critical(this,"提示","successful");
}
}
}
else{
serial.close();
ui->SerialNum_Box->setEnabled(true);
ui->SerialRefresh_But->setEnabled(true);
ui->SerialOpen_But->setText("open");
}
}

处理主要的数据

/* 串口接受的数据 */
void Widget::readData()
{
int index = 0;
QByteArray data = serial.readAll();
serbuf.append(data);

//提取单片机完整的一帧,以XXX-TOUCH\r\n结尾
if(serbuf.indexOf("-touch\r\n") != -1){
// 提取一个完整的消息
while ((index = serbuf.indexOf("\r\n")) != -1) {

QByteArray completeMessage = serbuf.left(index);
serbuf.remove(0, index + 2); // 移除已处理的消息和分隔符

// 处理完整的消息
QString message = QString::fromUtf8(completeMessage);
//qDebug() << "Received message: " << message;

if(message.indexOf("MAG_X[0]") != -1){
// 使用逗号作为分隔符分割字符串
QStringList parts = message.split(", ");
LIS2MDL_data[0] = parts[0].mid(parts[0].indexOf(": ") + 2).toInt(); // 提取x
LIS2MDL_data[1] = parts[1].mid(parts[1].indexOf(": ") + 2).toInt(); // 提取y
LIS2MDL_data[2] = parts[2].mid(parts[2].indexOf(": ") + 2).toInt(); // 提取z

SensorData[0] = message;
}
else if(message.indexOf("ACC_X[1]") != -1){
// 使用逗号作为分隔符分割字符串
QStringList parts = message.split(", ");
LSM6DSO16IS_data[0] = parts[0].mid(parts[0].indexOf(": ") + 2).toInt(); // 提取x
LSM6DSO16IS_data[1] = parts[1].mid(parts[1].indexOf(": ") + 2).toInt(); // 提取y
LSM6DSO16IS_data[2] = parts[2].mid(parts[2].indexOf(": ") + 2).toInt(); // 提取z

SensorData[1] = message;
}
else if(message.indexOf("GYR_X[1]") != -1){

// 使用逗号作为分隔符分割字符串
QStringList parts = message.split(", ");
LSM6DSO16IS_data[3] = parts[0].mid(parts[0].indexOf(": ") + 2).toInt(); // 提取x
LSM6DSO16IS_data[4] = parts[1].mid(parts[1].indexOf(": ") + 2).toInt(); // 提取y
LSM6DSO16IS_data[5] = parts[2].mid(parts[2].indexOf(": ") + 2).toInt(); // 提取z

SensorData[2] = message;
}
else if(message.indexOf("ACC_X[2]") != -1){
// 使用逗号作为分隔符分割字符串
QStringList parts = message.split(", ");
LIS2DUXS12_data[0] = parts[0].mid(parts[0].indexOf(": ") + 2).toInt(); // 提取x
LIS2DUXS12_data[1] = parts[1].mid(parts[1].indexOf(": ") + 2).toInt(); // 提取y
LIS2DUXS12_data[2] = parts[2].mid(parts[2].indexOf(": ") + 2).toInt(); // 提取z

SensorData[3] = message;
}
else if(message.indexOf("ACC_X[3]") != -1){
// 使用逗号作为分隔符分割字符串
QStringList parts = message.split(", ");
LSM6DSV16X_data[0] = parts[0].mid(parts[0].indexOf(": ") + 2).toInt(); // 提取x
LSM6DSV16X_data[1] = parts[1].mid(parts[1].indexOf(": ") + 2).toInt(); // 提取y
LSM6DSV16X_data[2] = parts[2].mid(parts[2].indexOf(": ") + 2).toInt(); // 提取z

SensorData[4] = message;
}
else if(message.indexOf("GYR_X[3]") != -1){
QStringList parts = message.split(", ");
LSM6DSV16X_data[3] = parts[0].mid(parts[0].indexOf(": ") + 2).toInt(); // 提取x
LSM6DSV16X_data[4] = parts[1].mid(parts[1].indexOf(": ") + 2).toInt(); // 提取y
LSM6DSV16X_data[5] = parts[2].mid(parts[2].indexOf(": ") + 2).toInt(); // 提取z

SensorData[5] = message;
}

else if(message.indexOf("Temp[0]") != -1){
QString tempValueStr = message.mid(message.indexOf(": ") + 2, message.indexOf(" degC") - message.indexOf(": ") - 2);
STTS22H_data = tempValueStr.toDouble();

SensorData[6] = message;
}
else if(message.indexOf("Press[1]") != -1){
QString tempValueStr = message.mid(message.indexOf(": ") + 2, message.indexOf(" hPa") - message.indexOf(": ") - 2);
LPS22DF_data = tempValueStr.toDouble();

SensorData[7] = message;
}
else if(message.indexOf("Hum[2]") != -1){
QString tempValueStr = message.mid(message.indexOf(": ") + 2, message.indexOf(" %") - message.indexOf(": ") - 2);
SHT40AD1B_data[0] = tempValueStr.toDouble();

SensorData[8] = message;
}
else if(message.indexOf("Temp[2]") != -1){
QString tempValueStr = message.mid(message.indexOf(": ") + 2, message.indexOf(" degC") - message.indexOf(": ") - 2);
SHT40AD1B_data[1] = tempValueStr.toDouble();

SensorData[9] = message;
}
else if(message.indexOf("-touch") != -1){
Touch = message;
}
}
DateProcess();
}
}

成果展示

6ed16b693ed975d57bfb6cb3a49f976.jpg

致谢

感谢官方大力提供帮助在此活动中,也感谢帮助我的所有人,感谢!


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