Funpack3-3
——任务一:针对X-NUCLEO-IKS4A1板卡的可视化数据采集系统
一、项目介绍
本项目依托于Funpack3-3活动,采用NUCLEO-STM32L476作为主控板卡,读取X-NUCLEO-IKS4A1上各个传感器的数值,并根据QVAR的数据完成单、双击及滑动识别。最后,设计了一款基于Matlab的可视化上位机,使其能实时显示各个传感器的数据。
项目内容:
1、采集IKS4A1板卡上各传感器数据。
2、根据QVAR数据识别单、双击及滑动。
3、发送数据至上位机并完成可视化。
二、系统架构
系统整体分为受控端要求和上位机两部分。由于本设计仅触摸部分需要数据处理,对运算性能要求不高,因此将数据处理任务全部分配给受控端完成。上位机仅负责数据的可视化部分。系统框图如下。
三、受控端设计
受控端设计硬件采用板卡默认即可,软件主要以STM32的程序设计为中心,可以分成三个部分,即,传感器驱动、数据处理以及与上位机通信。此外,主控的调度方式也是个需要注意的问题。
3.1 传感器驱动
官方提供了大量针对与IKS4A1板卡的Demo,虽然没有包含全部的STM32芯片,但是得益于STM32CubeIDE强大的图形化配置功能,我们可以直接生成适用于各个STM32芯片的示例程序。如果正确生成了示例程序,可以在MEMS-Studio观察到相应的现象。生成示例程序的教程请参见如下网址。
基于CubeIDE/MX的IKS4A1配置示例教程
获取到示例程序后,第一步显然是查看示例的硬件配置,这部分在基于CubeIDE/MX的IKS4A1配置示例教程提到了,此处不再赘述。之后就要查看示例程序的main函数,查看有那些函数是专门为了IKS4A1服务的,都有什么作用。以下是某个示例程序的main函数部分。
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_RTC_Init();
MX_TIM3_Init();
MX_CRC_Init();
MX_MEMS_Init();
while (1)
{
MX_MEMS_Process();
}
}
显然能注意到 MX_MEMS_Init(); 以及 MX_MEMS_Process(); 是为了 IKS4A1 服务的,并能猜到 MX_MEMS_Init(); 用于 IKS4A1 的初始化, MX_MEMS_Process(); 用于传感器数值的更新。分别阅读两个函数,提取其中的调用过程,就能得到正确的传感器驱动程序。以下是一个根据官方示例,改编部分官方函数,驱动IKS4A1板载的全部传感器的示例。
IKS4A1读取传感器数值教程
以下是MX_MEMS_Init(); 的内容,其中封装了一层调用MX_DataLogFusion_Init(),此处直接显示调用的函数。
static void MX_DataLogFusion_Init(void)
{
float ans_float;
/* Initialize button */
BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_EXTI);
/* Check what is the Push Button State when the button is not pressed. It can change across families */
PushButtonState = (BSP_PB_GetState(BUTTON_KEY)) ? 0 : 1;
/* Initialize LED */
BSP_LED_Init(LED2);
/* Initialize Virtual COM Port */
BSP_COM_Init(COM1);
/* Initialize Timer */
BSP_IP_TIM_Init();
/* Configure Timer to run with desired algorithm frequency */
TIM_Config(ALGO_FREQ);
/* Initialize (disabled) sensors */
Init_Sensors();
/* Sensor Fusion API initialization function */
MotionFX_manager_init();
/* OPTIONAL */
/* Get library version */
MotionFX_manager_get_version(LibVersion, &LibVersionLen);
/* Enable magnetometer calibration */
MotionFX_manager_MagCal_start(ALGO_PERIOD);
/* Test if calibration data are available */
MFX_MagCal_output_t mag_cal_test;
MotionFX_MagCal_getParams(&mag_cal_test);
/* If calibration data are available load HI coefficients */
if (mag_cal_test.cal_quality == MFX_MAGCALGOOD)
{
ans_float = (mag_cal_test.hi_bias[0] * FROM_UT50_TO_MGAUSS);
MagOffset.x = (int32_t)ans_float;
ans_float = (mag_cal_test.hi_bias[1] * FROM_UT50_TO_MGAUSS);
MagOffset.y = (int32_t)ans_float;
ans_float = (mag_cal_test.hi_bias[2] * FROM_UT50_TO_MGAUSS);
MagOffset.z = (int32_t)ans_float;
MagCalStatus = 1;
}
DWT_Init();
BSP_LED_On(LED2);
HAL_Delay(500);
BSP_LED_Off(LED2);
/* Start receiving messages via DMA */
UART_StartReceiveMsg();
}
注意到函数Init_Sensors(); 猜测用于传感器初始化,下面是该函数定义。
static void Init_Sensors(void)
{
BSP_SENSOR_ACC_Init();
BSP_SENSOR_GYR_Init();
BSP_SENSOR_MAG_Init();
BSP_SENSOR_PRESS_Init();
BSP_SENSOR_TEMP_Init();
BSP_SENSOR_HUM_Init();
BSP_SENSOR_ACC_SetOutputDataRate(ACC_ODR);
BSP_SENSOR_ACC_SetFullScale(ACC_FS);
}
该函数调用的任意一个函数都是无法直接跳转的,需要 Crtl+H,在工作空间搜索,才能找到该函数的定义。下面展示其中某一函数的代码。
void BSP_SENSOR_ACC_Init(void)
{
(void)IKS4A1_MOTION_SENSOR_Init(IKS4A1_LSM6DSV16X_0, MOTION_ACCELERO);
}
该函数调用的函数过长,此处不再展示,仅需要知道内部是通用的传感器初始化函数,MotionCompObj是指向初始化后的传感器读值等操作所需的结构体的指针即可。读到这里,我们知道 MX_MEMS_Init(); 以及 Init_Sensors(); 所在的 app_mems.c 和文件名一样是应用层的文件,BSP_SENSOR_ACC_Init(); 等所在的iks4a1_mems_control.c 文件是将底层的函数封装的文件,IKS4A1_MOTION_SENSOR_Init();所在的 iks4a1_motion_sensors.c 则是较为底层的传感器驱动文件,其会直接调用各传感器的.c和.h文件。由于 iks4a1_motion_sensors.c 是不需要选择示例,直接包含在IKS4A1的板级驱动里的,因此我们自制驱动时需要复制、修改 iks4a1_mems_control.c 使其能支持板上的各个传感器,并添加到自己的项目中。至于app_mems.c ,则是为了方便可以直接部分修改为自己的应用。
根据这个思路,我们就得到了前文的IKS4A1读取传感器数值教程。
3.2 数据处理
3.2.1 QVAR数据处理与识别
由于QVAR电极部分出现极度不稳定的情况,噪声极大,根据阈值判定的话,容易在阈值附近反复跨越,导致程序错判。采用迟滞、延时组合,来判定当前的触摸状态(左、无、右)。首先利用迟滞方式将输入的传感器数据量化为±1或0,因为利用C语言难以体现连续上升,此处将没有达到阈值的全部点都设定为当前状态的标准值,以便于下次循环的判断。此处为节省计算资源,没有将传感器直出的数据转化为mv为单位的数据,如果单位为mv,将该部分阈值除以78即可。
int value_deviation=value-value_last;
int act=0;
if (value_deviation>=54600)
value_state_n=1;
else if(value_deviation>=23400)
value_state_n=value_state_l+1;
else if(value_deviation<=-54600)
value_state_n=-1;
else if(value_deviation<=-23400)
value_state_n=value_state_l-1;
else
value_state_n=value_state_l;
value_last= value_state_n * 32760;
然后,利用延时去抖的思想,只有当状态连续变化为同一个数值时,才认为当前状态改变。同时,将过长的状态持续时间量化为两次状态改变。藉此,可以判断点按以及长按。
if (value_state_n==value_state_l)
{
value_count+=1;
if ((value_count>=40)&&(value_state_n!=0)&&(value_state_n!=value_state_storage[0]))
{
value_count=0;
value_state_valid=1;
value_state_storage[2]=value_state_storage[1];
value_state_storage[1]=value_state_storage[0];
value_state_storage[0]=value_state_n;
}
else if((value_count>=55)&&(value_state_n==0))
{
value_count=0;
value_state_valid=1;
value_state_storage[2]=value_state_storage[1];
value_state_storage[1]=value_state_storage[0];
value_state_storage[0]=value_state_n;
}
}
else
value_count=0;
最后,利用序列检测器思想,在输入的状态序列中检测单击、双击及滑动的信号序列,至此,完成手势识别。
3.2.2 其他数据处理
对于除了QVAR的数据,我们获取它们仅仅为了可视化,其实没有处理的必要。但是为了节约串口通信的带宽,将部分数据精度降低。
四、上位机设计
上位机采用Matlab AppDesigner设计,Matlab语言本身就较为简单,AppDesigner采用的设计方法也比较简单,此处仅简单描述。系统采用定时器更新界面加串口中断更新数据,一定程度上分离了界面更新与数据接受。但是由于Matlab AppDesigner本身羸弱的多线程能力,导致数据更新速度只能限制到每秒5次左右。同时,即使限制到每秒1次甚至更少,也会存在一个至少0.5秒的固定延迟。详细代码请参见附件。
五、效果展示
以下是部分上位机图片展示。
主界面
连接成功
选中数据
禁用某个数据