一、项目介绍
本项目基于 NUCLEO-F303RE 主控搭配传感器扩展板 X-NUCLEO-IKS4A1 实现读取环境温度数据、湿度数据、气压数据。通过WIFI模块与智能家居物联网平台集成,实现家居环境监测功能。
1.1 硬件介绍
本项目基于以下硬件实现:
- NUCLEO-F303RE: 由 STMicroelectronics 生产的一款基于ARM Cortex-M4微控制器的开发板,属于STM32 Nucleo系列。这款开发板旨在为开发者提供一个低成本且易于使用的平台,以便开发和测试基于STM32F303RE微控制器的应用程序。
- X-NUCLEO-IKS4A1: 由 STMicroelectronics 推出的用于搭配STM32 Nucleo的运动MEMS和环境传感器扩展板,搭载了传感器的主板IQS4A1和可拆卸的 Qvar 触摸/滑动电极附加板MKE001A组成,具有高度的集成性和兼容性。提供了在动作检测、环境监测等IoT领域的各种传感器的参考解决方案。
- ESP-12F: 由安信可科技推出的一款基于乐鑫 ESP8266 芯片的Wi-Fi模块,具有集成的处理器、Wi-Fi功能和多种外设接口。
1.2 功能概览
- 触摸按键功能实现(左划,右划,点击)
- 家居环境温度数据采集
- 家居环境湿度数据采集
- 家居环境气压数据采集
- 与智能家居平台集成
- 通过触摸按钮实现数据采集控制(打开或关闭一种或多种传感器数据采集)
1.3 设计思路
实现 功能概览 的全部功能,实现步骤如下:
- 准备硬件和软件环境
- 将传感器扩展板与NUCLEO-F303RE开发板连接上
- 编写传感器数据读取代码
- 实现数据通信, 将采集到的传感器数据上报至智能家居平台
- 测试与调试, 完成所有配置后, 测试整个系统以确保数据能够从传感器通过NUCLEO-F303RE正确发送到智能家居平台并正确显示
- 自动化与进阶集成,使用智能家居平台的自动化功能来基于传感器数据触发不同的动作,例如开灯、调节温度等(进阶功能,后续再扩展实现)
二、功能实现
2.1 软件流程图
如上图所示,我们需要三个任务(TASK):
- 触摸手势识别任务 (GestureRecognitionTask)
- 传感器数据采集任务 (SensorDataCollectionTask)
- LED控制任务 (LEDControlTask)
以及两个全局变量:
- Index (用于指示当前切换到的传感器索引)
- State (用于指示是否采集数据和控制LED状态)
程序流程如下:
- 初始化阶段: 创建并启动三个任务。 设置Index、State的初始值(比如State = 0)。
- 运行阶段:
- GestureRecognitionTask:
- (1) 循环检测识别用户触摸手势。
- (2) 如果识别到单击操作,则翻转 State的值(如果State = 0,则变为1;如果State = 1,则变为0)。
- (3) 如果识别到左划、右划操作,则自减或自增当前传感器索引 Index 的值。
- SensorDataCollectionTask:
- (1) 检查State的值。
- (2) 如果State为允许采集的值(比如State = 1),则定时采集传感器数据,并上报到上位机。
- (3) 如果State为不允许采集的值(比如State = 0),则不进行任何动作或进入低功耗模式。
- LEDControlTask:
- (1) 检查State的值。
- (2) 根据State的值来控制LED的亮灭(比如State = 1时亮,State = 0时灭)。
2.2 实现过程
2.2.1 触摸手势识别
通过采集 Qvar 电平值,将数据通过串口使用上位机展示,得到如下波形,分别有 单击,左滑,右滑的波形。
根据上图波形分析,这里我使用状态机来进行手势的识别。当 Qvar 电平值大于阈值时,判断触摸开始,根据值是正或负判断触摸的是左边还是右边。Qvar电平回到阈值以下时,状态为触摸结束,即完成手势识别。使用 FreeRTOS 的一个 Task 来进行触摸数据的采集与识别,Task中触摸手势识别代码如下所示:
// 采集Qvar电平值
sensorLSM6DSV16X.QVAR_GetData(&qvar_data);
// 这里使用状态机来识别触摸手势
switch (state) {
case IDLE: //
if (qvar_data > TOUCH_THRESHOLD) { // 空闲状态下,Qvar电平值大于触摸阈值,表示点击了左边触摸板
state = CLICK_LEFT;
} else if (qvar_data < -TOUCH_THRESHOLD) { // 空闲状态下,Qvar电平值小于负的触摸阈值,表示点击了右边触摸板
state = CLICK_RIGHT;
}
break;
case CLICK_LEFT:
if (abs(qvar_data) < TOUCH_THRESHOLD / 2) {
state = IDLE;
Serial.print("Single touch detected! ");
} else if(qvar_data < -TOUCH_THRESHOLD) {
state = SWIPE_RIGHT;
}
break;
case CLICK_RIGHT:
if (abs(qvar_data) < TOUCH_THRESHOLD / 2) {
state = IDLE;
Serial.print("Single touch detected!");
} else if (qvar_data > TOUCH_THRESHOLD) {
state = SWIPE_LEFT;
}
break;
case SWIPE_LEFT:
if (abs(qvar_data) < TOUCH_THRESHOLD / 2) {
state = IDLE;
Serial.print("swipe left detected!");
}
break;
case SWIPE_RIGHT:
if (abs(qvar_data) < TOUCH_THRESHOLD / 2) {
state = IDLE;
Serial.print("swipe right detected!");
}
break;
}
2.2.2 传感器数据采集
单独使用 FreeRTOS 的一个 Task 定时采集各传感器数据。每次采集时,遍历传感器列表并判断各传感器的激活状态,仅针对激活的传感器进行采集,关键代码片段如下:
for(int i = 0; i < SENSOR_COUNT; i ++) {
if(GET_BIT(sensor_state, i)) { // 传感器状态位为:1 时,才采集该传感器数据
if(0 == strcmp(SENSOR_SHT40AD1B, sersor_names[i])) { // SHT40AD1B 数字式湿度和温度传感器
sensorSHT40AD1B.GetHumidity(&humiditySHT40AD1B);
reportJsonDoc["humidity"] = humiditySHT40AD1B;
}
if(0 == strcmp(SENSOR_LPS22DF, sersor_names[i])) { // LPS22DF 超紧凑型压阻绝对压力传感器
sensorLPS22DF.GetPressure(&pressureLPS22DF);
reportJsonDoc["pressure"] = pressureLPS22DF;
}
if(0 == strcmp(SENSOR_STTS22H, sersor_names[i])) { // STTS22H 温度传感器
sensorSTTS22H.GetTemperature(&temperatureSTTS22H);
reportJsonDoc["temperature"] = temperatureSTTS22H;
}
if(0 == strcmp(SENSOR_LIS2MDL, sersor_names[i])) { // LIS2MDL 超低功耗、高性能三轴数字磁传感器
sensorLIS2MDL.GetAxes(magnetometer);
reportJsonDoc["Mag_X"] = magnetometer[0];
reportJsonDoc["Mag_Y"] = magnetometer[1];
reportJsonDoc["Mag_Z"] = magnetometer[2];
}
if(0 == strcmp(SENSOR_LIS2DUXS12, sersor_names[i])) { // LIS2DUXS12 超低功耗MEMS 3轴加速度计
sensorLIS2DUXS12.Get_X_Axes(accel);
reportJsonDoc["Accel_X"] = accel[0];
reportJsonDoc["Accel_Y"] = accel[1];
reportJsonDoc["Accel_Z"] = accel[2];
}
}
}
2.2.3 环境数据上报
2.2.3.1 格式定义
环境数据上报采用 JSON 格式封装数据,定义格式如下:
{
"humidity": 74.73166656, /* 湿度(单位:%) */
"pressure": 1002.628662, /* 气压(单位:hPa) */
"temperature": 25.09000015, /* 温度(单位:℃) */
"Mag_X": -115, /* X轴磁强数据(单位:mGauss) */
"Mag_Y": -736, /* Y轴磁强数据(单位:mGauss) */
"Mag_Z": -1518, /* Z轴磁强数据(单位:mGauss) */
"Accel_X": 151, /* X轴加速度数据(单位:mg) */
"Accel_Y": -294, /* Y轴加速度数据(单位:mg) */
"Accel_Z": 935 /* Z轴加速度数据(单位:mg) */
}
2.2.3.2 传感器数据发送
传感器数据的传输是通过 ESP-12F 无线模块, 使用 ESP-AT + MQTT 协议进行传输。
将 ESP-12F 的串口连接到 NUCLEO-F303RE 串口3 后即可,通过串口将数据透传至 MQTT 服务器。
上位机以及智能家居应用平台,订阅相应的主题即可接收并对传感器数据进行可视化展示。具体效果详见:功能展示
用到的 ESP-AT 指令清单
# 设置 Wi-Fi 模式为 Station 模式
AT+CWMODE=1
# 连接到 Wi-Fi
AT+CWJAP="@PHICOMM_34","12345678"
# 检查是否成功连接到Wi-Fi,查询 Station 的 IP 地址
AT+CIPSTA?
# 配置 MQTT 用户属性
AT+MQTTUSERCFG=0,1,"nucleo_f303re_publisher","xiao_esp32c3","123456",0,0,""
# 连接到 MQTT Broker
AT+MQTTCONN=0,"192.168.2.120",1883,1
# 发布 MQTT 消息
AT+MQTTPUB=0,"test","test message",1,0
2.2.4 传感器状态指示
为了便于查看当前传感器的启用状态,使用了一个 Task 来控制 NUCLEO-F303RE 开发板上 LED2 亮灭来表示当前选中传感器的启用状态。当前传感器启用状态为 1
时,点亮 LED2 。启用状态为 0
时,熄灭 LED2 。主要代码片段如下所示:
if(GET_BIT(sensor_state, current_selected_sensor)) { // 当前选中的传感器状态为启用,点亮LED灯
digitalWrite(LED_BUILTIN, HIGH);
} else { // 当前选中的传感器状态为禁用,熄灭LED灯
digitalWrite(LED_BUILTIN, LOW);
}
2.2.5 智能家居平台集成展示
在智能家居平台上,需要订阅传感器上报的数据,即可完成智能家居平台上数据展示的数据来源接收。然后在UI界面选择相应的控件进行添加即可完成环境数据展示。 如下图所示, 点击 添加卡片 按钮,在打开的对话框中选择 按实体 标签页,搜索 X-NUCLEO-IKS4A1
找到 相关的传感器实体进行添加即可:
2.2.6 使用 TTMQ 绘图展示
2.2.6.1 TTMQ 图表配置
// for more about chart option, please visit: https://doc.ttqm.app/#/en/chart/option
// 更多关于图表配置,请访问: https://doc.ttqm.app/#/zh-cn/chart/option
// 更多關於圖表配置,請訪問: https://doc.ttqm.app/#/zh-tw/chart/option
const { ChartHelper } = require("@ttqm/ttqm-support");
const option = {
title: {
left: 'center',
text: 'X-NUCLEO-IKS4A1 传感器数据展示'
},
tooltip: {
trigger: 'axis'
},
legend: {
top: 20,
data: ['温度', '湿度', '气压', 'X轴加速度', 'Y轴加速度', 'Z轴加速度', 'X轴磁强', 'Y轴磁强', 'Z轴磁强']
// data: ['Y轴磁强', 'Z轴磁强']
},
xAxis: {
type: "time",
boundaryGap: false
},
yAxis: {
type: "value",
boundaryGap: [0, '100%']
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
start: 0,
end: 100
}
],
series: [
{
name: '温度',
type: 'line',
data: [[]]
},
{
name: '湿度',
type: 'line',
data: [[]]
},
{
name: '气压',
type: 'line',
data: [[]]
},
{
name: 'X轴加速度',
type: 'line',
data: [[]]
},
{
name: 'Y轴加速度',
type: 'line',
data: [[]]
},
{
name: 'Z轴加速度',
type: 'line',
data: [[]]
},
{
name: 'X轴磁强',
type: 'line',
data: [[]]
},
{
name: 'Y轴磁强',
type: 'line',
data: [[]]
},
{
name: 'Z轴磁强',
type: 'line',
data: [[]]
}
]
};
ChartHelper.setOption(option);
2.2.6.2 TTMQ 脚本
// for more Chart Script, please visit: https://doc.ttqm.app/#/en/chart/script
// 更多关于图表脚本,请访问: https://doc.ttqm.app/#/zh-cn/chart/script
// 更多關於圖表腳本,請訪問: https://doc.ttqm.app/#/zh-tw/chart/script
// export the event listener, for more: https://doc.ttqm.app/#/en/chart/script
// 导出事件监听, 请访问: https://doc.ttqm.app/#/zh-cn/chart/script
// 導出事件監聽, 請訪問: https://doc.ttqm.app/#/zh-tw/chart/script
const { ChartHelper } = require("@ttqm/ttqm-support");
const moment = require("Moment");
module.exports = {
onMessage: (topic, payload, packet) => {
const currentTime = moment().format('YYYY-MM-DD HH:mm:ss');
const sensorData = JSON.parse(Buffer.from(payload).toString());
console.log("message payload: ", currentTime, topic, JSON.parse(Buffer.from(payload).toString()), packet);
// update chart with random data
const randomRange = (min, max) => {
return Math.floor(Math.random() * (max - min)) + min;
};
const data = [];
for (let i = 0; i < 7; i++) {
data.push(randomRange(100, 500))
}
const returnDatas = [
];
for (var key in sensorData) {
if ('temperature' == key) { // 温度
returnDatas.push({
targetPath: ["series", 0, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('humidity' == key) { // 湿度
returnDatas.push({
targetPath: ["series", 1, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('pressure' == key) { // 气压
returnDatas.push({
targetPath: ["series", 2, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('Accel_X' == key) { // X轴加速度
returnDatas.push({
targetPath: ["series", 3, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('Accel_Y' == key) { // Y轴加速度
returnDatas.push({
targetPath: ["series", 4, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('Accel_Z' == key) { // Z轴加速度
returnDatas.push({
targetPath: ["series", 5, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('Mag_X' == key) { // X轴磁强
returnDatas.push({
targetPath: ["series", 6, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('Mag_Y' == key) { // Y轴磁强
returnDatas.push({
targetPath: ["series", 7, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
if ('Mag_Z' == key) { // Z轴磁强
returnDatas.push({
targetPath: ["series", 8, "data"],
action: "array_append_end",
data: [currentTime, sensorData[key]]
});
}
}
ChartHelper.updateChartViewData(returnDatas);
},
onPublish: (topic, payload) => { }
};
三、功能展示
3.1 智能家居平台集成数据展示
3.2 温湿度历史数据展示
3.3 使用上位机绘图和查看接收的数据
如上图所示,可看到打开和关闭 LIS2MDL 三轴数字磁传感器,接收到的数据差异。
四、总结
遇到的问题
- 触摸数据采集,一开始拿到开发板时,参考示例烧录程序到板子上时,采集到的Qvar电平值始终为 0 。根据群友的提醒,需要将 X-NUCLEO-IKS4A1 传感器扩展板上 J4 和 J5 上 1,2 脚的跳线帽,分别接到 J4 和 J5 上 3,4 脚。
另外,需要注意的是如果通过 LSM6DSV16X MEMS(微电机系统)传感器,来读取 Qvar 电平值时,需要开启加速度计(
sensorLSM6DSV16X.Enable_X();
)和陀螺仪(sensorLSM6DSV16X.Enable_G();
)才能正确读取到 Qvar 电平值。
- 触摸手势识别我使用的是状态机的模式进行识别,但前提是采集到的Qvar电平值需要比较稳定,才能准确识别到。实际上在触摸时,人体本身的电荷差异以及在触摸板的边缘区域和中间区域时,会有频繁的电平跳动,导致识别不准确。可能需要使用DTW算法进行匹配计算也许能有比较好的结果,貌似也可以使用AI识别,但这已经超过我当前的能力范围了。
- 使用 ESP-AT 进行数据上报时,总是不能成功。通过查看 ESP-AT 交互日志才发现,由于 ESP-AT 只支持单线程交互模式,而且需要等待一个指令完成,才能进行下一个指令。这里我简单的使用延时进行处理,更好的处理方案是判断返回值。或者使用 AT-Command 库来实现交互。
心得体会
第一次基于STM32的芯片进行开发,前期找了很多资料,网上最多资料的芯片。本以为会很容易上手,谁知一开始就选择困难了,开发环境各种各样,都不知道选择啥了。折腾了一段时间 STM32CubeIDE 虽然能简单的初始化外设,和点灯。但总觉得知其然不知所以然,总是别别扭扭的,最终在群友的提醒下选择了基于 STM32duino 进行开发后终于完成了该项目。
最后感谢电子森林与得捷电子联合推出的 《Funpack》 系列活动,对于我来说是个很好的学习机会,理论结合实践。我们下期活动再见!
五、参考资料
- https://www.st.com/resource/en/data_brief/x-nucleo-iks4a1.pdf
- https://www.st.com/content/ccc/resource/technical/document/data_brief/c8/3c/30/f7/d6/08/4a/26/DM00105918.pdf/files/DM00105918.pdf/jcr:content/translations/en.DM00105918.pdf
- https://www.st.com/content/ccc/resource/technical/document/user_manual/98/2e/fa/4b/e0/82/43/b7/DM00105823.pdf/files/DM00105823.pdf/jcr:content/translations/en.DM00105823.pdf
- https://www.st.com/resource/en/user_manual/um3239-getting-started-with-the-xnucleoiks4a1-motion-mems-and-environmental-sensor-expansion-board-for-stm32-nucleo-stmicroelectronics.pdf
- https://github.com/stm32duino/X-NUCLEO-IKS4A1
- https://espressif-docs.readthedocs-hosted.com/projects/esp-at/zh-cn/release-v2.2.0.0_esp8266/Get_Started/index.html