1.项目的需求
• 使用板卡上的触摸按键,实现点按和左右滑动,实现传感器选择和切换,并将数据发送到上位机,功能选择的可视化也在上位机完成
如:能够选择加速度传感器,开启X轴数据发送,然后关闭加速度显示,选择温度
处理思路
(1)板卡上的触摸按键:板卡上的触摸按键是使用板卡自带的QVAR的滑动电极,我们通过在GitHub配置的程序移植一下到我们的STM32Demo历程中移植调用。
(2)通过OLED屏幕显示我们当前按键的状态,通过按键单击或者滑动,来控制我们的上位机的显示我们的数据。
(3)通过QT制作上位机,添加按键功能实现我们对数据采集的显示,通过滑动按键控制我们的上位机,对于我们的QT上位机的数据进行开启或者关闭。
2.STM32程序移植的配置
本次的历程是基于STM32自带的历程修改的,首先我们先将应用设置到我们DataLOG窗口。
根据实例程序需要什么端口,我们手动配置一下需要的串口,来实现我们的操作。
之后我们就开始移植我们的程序,我们先将程序下载之后,通过我们串口助手查看是否能发送数据。
我们可以看到串口助手有数据发送过来,我们的实例程序就已经配置好了
移植QVAR
在实例程序中,我们的QVAR没有代码进行配置,但是底层程序还是存在的,我们从GitHub上找到stm32对QVAR的配置,根据其中的代码来移植我们的程序。
这里我将QVAR的程序移植到一个.c文件上,之后可以方便我们模块化移植。整体框架就是这样,之后是我们细节问题。
程序流程图
QVAR的手势判断
QVAR的手势判断我们就是要通过判断电压值的变化,通过电压值的变化,来判断我门的滑动状态。
这里我们需要我们设置我们的阈值,到达我们的阈值来获取我们的操作的判断。
void QVAR_Proc(void)
{
static uint8_t QVAR_Val,QVAR_Down,QVAR_Up,QVAR_Old;
QVAR_Val = QVAR_Read(QVAR_Value());
QVAR_Down = QVAR_Val & (QVAR_Val ^ QVAR_Old);
QVAR_Up = ~QVAR_Val & (QVAR_Val ^ QVAR_Old);
QVAR_Old = QVAR_Val;
OLED_ShowNum(0,16,QVAR_Val,1,8);
if(QVAR_Down>0)//说明有按键按下
{
QVAR_Down_click_count++;
QVAR_Flag = 1;
time_200ms = 0;
}
//这里我们要判断的是down不等于old
if(time_200ms >= 200)
{
switch(QVAR_Down_click_count)
{
case 1:
//执行单击
OLED_ShowString(0,0,"One ",8);
printf("command:One\r\n");
break;
case 2:
//执行双击
OLED_ShowString(0,0,"Two ",8);
printf("command:Two\r\n");
QVAR_Flag = 0;
break;
}
QVAR_Down_click_count=0;
QVAR_Flag = 0;
time_200ms = 0;
}else if(QVAR_Flag==1&&QVAR_Down!=QVAR_Old&&time_200ms < 200) //按键按下的时候判断
{
QVAR_Flag = 0;
QVAR_Down_click_count = 0;
if(QVAR_Old==2)
{
OLED_ShowString(0,0,"LEFT ",8);
printf("command:Left\r\n");
}
else if(QVAR_Old==1)
{
OLED_ShowString(0,0,"RIGHT ",8);
printf("command:Right\r\n");
}
}
}
这里我们通过在滴答定时器定时我们的短按时间,来判断我们的触摸操作。
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(++time_1000ms>=2000)
{
Start = 1;
time_1000ms = 0;
}
if(QVAR_Flag == 1)
{
if(++time_200ms ==200)
{
time_200ms = 201;
}
}else{
time_200ms = 0;
}
if(++time_100s==100)
{
QVAR_Proc();
time_100s = 0;
}
/* USER CODE END SysTick_IRQn 1 */
}
这里我们添加 了 start的标志位,因为这样处理的话,不会让我们的串口发的太快,影响我们的触摸的判断,这里我们我们添加1s的标志位。
stm32单片机的大致操作就是这些了。
QT上位机的配置
我们的QT上位机的目的是,完成接收我们的传感器数据,通过下位机来控制我们的数据打开或者关闭。
整体UI为这样子显示,在每一行下面分别读取我们的数值的数据。
在打开串口的时候,我们就可以看到数据已经实时的发送到我们的上位机上。
QT软件流程图
QT正则表达式接收数据
这里我们通过获取我们的数据包,通过我们的数据包的正则表达式来判断。
static const QRegularExpression accelRegex("加速度计X\\[1\\]:\\s*(-?\\d+),\\s*加速度计Y\\[1\\]:\\s*(-?\\d+),\\s*加速度计Z\\[1\\]:\\s*(-?\\d+)");
static const QRegularExpression gyroRegex("陀螺仪X\\[1\\]:\\s*(-?\\d+),\\s*陀螺仪Y\\[1\\]:\\s*(-?\\d+),\\s*陀螺仪Z\\[1\\]:\\s*(-?\\d+)");
static const QRegularExpression magRegex("磁力计X\\[0\\]:\\s*(-?\\d+),\\s*磁力计Y\\[0\\]:\\s*(-?\\d+),\\s*磁力计Z\\[0\\]:\\s*(-?\\d+)");
static const QRegularExpression tempRegex("温度\\[1\\]:\\s*([+-]?\\d+\\.\\d+)\\s*C");
static const QRegularExpression tempBRegex("温度\\[2\\]:\\s*([+-]?\\d+\\.\\d+)\\s*C");
static const QRegularExpression humBRegex("湿度\\[2\\]:\\s*([+-]?\\d+\\.\\d+)\\s*%");
static const QRegularExpression commandRegex("command:\\s*One");
static const QRegularExpression command2Regex("command:\\s*Two");
我们通过正则表达式的数据匹配将我们的数据提取出来存放到我们的label上更新。
QRegularExpressionMatch match;
if ((match = accelRegex.match(data)).hasMatch()) {
QString xValue = match.captured(1);
QString yValue = match.captured(2);
QString zValue = match.captured(3);
// 将解析到的数据设置到QLabel上
for (int i = 0; i < labelsGroups[0].size(); ++i) {
if (!lockedFlagsGroups[0][i]) {
labelsGroups[0][i]->setText((i == 0 ? xValue : (i == 1 ? yValue : zValue)));
}
}
} else if((match = gyroRegex.match(data)).hasMatch()) {
QString xValue = match.captured(1);
QString yValue = match.captured(2);
QString zValue = match.captured(3);
// 将解析到的陀螺仪数据设置到QLabel上
for (int i = 0; i < labelsGroups[1].size(); ++i) {
if (!lockedFlagsGroups[1][i]) {
labelsGroups[1][i]->setText((i == 0 ? xValue : (i == 1 ? yValue : zValue)));
}
}
} else if((match = magRegex.match(data)).hasMatch()) {
QString xValue = match.captured(1);
QString yValue = match.captured(2);
QString zValue = match.captured(3);
// 将解析到的磁力计数据设置到QLabel上
for (int i = 0; i < labelsGroups[2].size(); ++i) {
if (!lockedFlagsGroups[2][i]) {
labelsGroups[2][i]->setText((i == 0 ? xValue : (i == 1 ? yValue : zValue)));
}
}
} else if ((match = tempRegex.match(data)).hasMatch()) {
QString temperature = match.captured(1);
// 将解析到的温度数据设置到QLabel上
if (!lockedFlagsGroups[4][0]) {
ui->H_tem->setText(temperature + " C");
}
} else if ((match = tempBRegex.match(data)).hasMatch()) {
QString temperature = match.captured(1);
// 将解析到的温度数据设置到QLabel上
if (!lockedFlagsGroups[3][0]) {
ui->B_tem->setText(temperature + " C");
}
} else if ((match = humBRegex.match(data)).hasMatch()) {
QString hum = match.captured(1);
// 将解析到的湿度数据设置到QLabel上
if (!lockedFlagsGroups[3][1]) {
ui->B_hum->setText(hum + " %");
}
} else if ((match = commandRegex.match(data)).hasMatch()) {
oneCounter++; // 每接收到一个 "command: One",计数器加一
updateButtonState(); // 更新按钮状态
} else if ((match = command2Regex.match(data)).hasMatch()) {
int Index = oneCounter % buttons.size();
simulateButtonClick(Index); // 更新按钮状态
}
}
我们通过读取我们的数据包,通过换行符来进行判断分析我们的数据
void Widget::readSerialData()
{
buffer.append(serialPort->readAll()); // 将读取到的数据添加到缓冲区
// 检查缓冲区中是否有完整的数据包(假设每个数据包以换行符结尾)
while (buffer.contains('\n')) {
int newlineIndex = buffer.indexOf('\n');
QByteArray completeData = buffer.left(newlineIndex); // 提取完整的数据包
buffer.remove(0, newlineIndex + 1); // 移除已处理的数据包
QString receivedData = QString::fromLocal8Bit(completeData);
parseAndDisplayData(receivedData); // 调用函数处理并显示数据
}
}
遇到的主要难题
1. QVAR数据读取判断错误
首先呢我们的QVAR是根据我们的电极进行判断,在电极进行判断的时候我们需要设置一个阈值,通过这个阈值来判断我们是否触摸到了电极。
在此过程中,我们对于阈值的设定需要在一个合理的范围内,假如阈值过高,会导致我们的触摸按键没有反应,假如阈值过低,会导致我们的触摸按键还没触摸就认为我们有手势动作。所以在阈值选择上我们可以多次尝试,让其中的阈值达到我们的理想的目标。
这里我选择的阈值是30,在此过程中估计还有些误判,但大致的操作还是能够实现的。
改进策略
我们可以适当的添加一些滤波处理,平均滤波或者滑动滤波,因为在触摸中可以电极的接触面积,会导致我们的数值会一会大一会小,可能会影响我们的阈值的判断,通过滤波的设计,主要目的是让一些不太紧要的数据从中滤波走,从而将我们的数据有一个准确的数值出来,这是我们之后可以改进的方式。这次我只是用了一段时间判断数值是否在正常的范围内判断我们的数值是否按下,这是我们可以改进的地方。
2.数据发送过快
我们从stm32cubemx生成出来程序中,其中的数据会串口数据是在while循环中一直调用,其中调用的频率过快,会导致我们的对QVAR的数据采集做出来一些误判。这里我是通过在定时器生成一个1s的中断计时,在一秒之后由中断来触发我们的数据发送,就避免了我们在QVAR触摸判断时,被串口数据影响我们的数据的接收。
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(++time_1000ms>=2000)
{
Start = 1;
time_1000ms = 0;
}
if(QVAR_Flag == 1)
{
if(++time_200ms ==200)
{
time_200ms = 201;
}
}else{
time_200ms = 0;
}
if(++time_100s==100)
{
QVAR_Proc();
time_100s = 0;
}
/* USER CODE END SysTick_IRQn 1 */
}
在系统滴答定时器下,配置我们相关的数据的发送,从而避免在串口在while循环快速发送中,影响我们的QVAR的手势判断。
改进策略
我们可以根据配置我们可以配置为添加实时的操作系统,我们可以在STM32cubemx中配置我们FreeRtos,根据我们的实时操作系统,在不断的配置我们的相关数据的发送根据数据的发送,通过任务的方式发送我们相关的数据,也要配置QVAR的操作,这样可以成功避免QVAR与串口数据的发送。
或者可以将串口数据,改为DMA发送,因为DMA数据会直接通过CPU内存操作。不会占用我们的系统资源,也不会对于我们的QVAR进行干扰。
未来的计划建议
该项目已经成功实现了简易示波器和信号发生器的功能,并达到了预期指标。还有一些的实现策略。
1.将QT界面优化一下UI,将其中的数据显示界面能更完善
2.将其中的数据采集放到FREERTOS上,添加到操作系统上,可以解决我们的一开始数据会被打断的问题
3.可以增加屏幕显示我们的数据,这样我们在脱离上位机也能实时查看我们的数据。(虽然代码有屏幕,但只是查看触摸状态而已)
4.添加一些滤波算法,让我们的QVAR的滑动电极误触的率降低。
感谢和总结
感谢硬禾学堂推出来这次活动,这次活动让我对stm32cubemx的系统的案例,和对于程序的移植有了进一步的提升和了解,对于相关的传感器的数据的收发也得到了进一步的提升,此次任务要求使用上位机,这也让我接触到QT上位机的程序的编写,让我能有目标来实现自己需要完成的任务,从而花费了几天学习QT并制作出一个简易的QT程序。在此过程中收获颇多,也更坚定的让我走上单片机的道路。
程序百度云盘连接:链接:https://pan.baidu.com/s/1_aLCCe58eoItVjnNZliqKQ?pwd=89cz
提取码:89cz
由于有点大上传不上附件。