作者:杨红超
本章主要阐述多功能气压计的应用背景,包括根据大气压强判断和确定建筑工人的楼层位置、帮助建筑工人感知周围环境温度变化等,同时也可根据大气压强判断无人机的GPS定位。其次着重介绍气压计的具体功能,如可以通过对工人周围的气压和温度的采集计算出当前所在的楼层位置,并将数据信息通过ONENET上传到云服务器上,通过语音识别实现设备在线升级功能。最后对本次设计的组织结构进行介绍,以表明每章节的主要内容和作用。
1.1应用背景
为确保建筑工人在工地施工时的人身安全,故此设计一款工人们的“运行手环”——多功能气压计。它内置气体压强检测装置可实时检测建筑工人身处的大气压强和周围的环境温度,进而根据采集的数据推算出工人所在的楼层高度,并将数据实时上传到云服务器上通过管理者对数据的检测给建筑工人提供一份双保险。
此外,该多功能气压计还具有无人机GPS定位和判断吸尘器吸力大小等应用于多对象、多环境的特点。
1.2实现功能
本次多功能气压计的设计硬件采用ART-Pi开发板、LPS22HH气压传感器和LD3320语音识别模块,操作系统使用RT-Thread 4.0.3,软件使用RT-Thread Studio 1.1.5开发平台和使用C语言实现软件编程,具有如下功能:
(1)利用LPS22HH气压传感器实时对大气压的压强进行采集,并通过SPI4接口将采集的数据传给单片机。
(2)利用AP6212 WIFI模块实时将经过处理后的数据,如温度值和楼层信息等上传到云服务器;同时通过WIFI模块实现在线升级用户程序。
(3)语音识别模块通过SPI2实现与开发板的数据交互,完成语音重启设备和语音在线升级功能。
1.3设计组织结构
本次基于ART-Pi开发板的多功能气压计设计,主要分为五个章节其具体设计组织结构如下:
第一章 绪论,主要介绍多功能气压计的实际应用背景和具有的具体功能,以及设计组织结构的规划。
第二章 RT-Thread概述,主要对在本设计中涉及的RT-Thread内核、及其组件和软件包进行阐述,欲以说明对RT-Thread操作系统的使用情况和了解程度,同时对其内容作些简单的介绍。
第三章 硬件设计,主要阐述硬件模块的电气连接和本次设计使用的硬件电路,如LPS22HH气压传感器、ART-Pi开发板和语音识别模块等。
第四章 软件设计,主要阐述软件实现的设计流程和各个软件模块设计的框架,以及模块之间的通讯方式。
第五章 总结与展望,主要阐述对本次设计的评估,即软件设计存在的不足和功能与性能存在的不足,以及针对不足之处提出的具体改进措施和方案、参加比赛的感悟和心得。
1.4本章小节
本章主要介绍多功能气压计的应用背景和具体的应用功能,以及对软硬件开发环境和该设计文档的组织结构进行阐述。
本章主要阐述在软件设计中关于RT-Thread操作系统的应用情况,如与线程运行有关的采用动态方式创建线程,与线程同步有关的信号量的动态创建、释放和获取,以及与网络有关的SAL组件、OneNET软件包等。
2.1内核
为了提高软件运行的并发性和数据采集的实时性,故使用内核中的核心部分——线程,使其维护和管理每个任务的运行,同时以使用信号量和事件集等的同步方式,以及使用邮箱和消息队列等的通信方式来确保每个任务在运行过程中能“自由飞翔”。
不仅如此,为增加任务在运行状态中的时间和提高每个任务响应的快慢速度,故使用线程中重要的两个属性即线程优先级和时间片,并根据应用的具体环境和软实时性的要求将每个任务赋予各自该具有的任务优先级和时间片。同时,使用rt_thread_mdelay延迟函数来定时释放线程(任务)确保在其处于非运行态时可以退出时间片以让其它任务能及时运行。
2.2驱动
在bootloader程序中主要初始化SPI和SFUD驱动实现对外部SPI FLASH的读写操作,同时结合使用FAL软件包将操作FLASH的函数进行分层,进而实现在bootloader程序中读取“download”分区的升级固件,以及使用ota_downloader软件包完成升级固件的下载,最后使用出厂W25QXX函数实现固件搬运工作进而完成在线升级应用。
2.3组件
SAL组件完成并提供了基于RT-Thread操作系统的对不同网络协议栈或网络实现接口的抽象和与上层应用有关的一组标准BSD Socket API,在网络开发设计时可只需关心和使用网络应用层提供的网络接口,而无需关心底层具体网络协议栈类型和具体实现,如TCP/IP协议栈、LWIP和AT Socket网络。SAL组件不仅提高了软件系统对底层驱动的兼容性,而且缩短了网络开发周期。此外,SAL组件还具有一下功能特点[1]:
- 抽象、统一多种网络协议栈接口;
- 提供Socket层面的TLS加密传输特性;
- 支持标准BSP Socket API;
- 统一的FD管理,便于使用read/write,poll/select来操作网络功能。
在本次AP6212 WIFI模块设计中主要使用SAL组件来实现多功能气压计自动检测热点、连接热点和断线重连的功能,并结合OneNET软件包提供的API接口以实现访问OneNET云服务器的功能。
2.4软件包
OneNET软件包是 RT-Thread 系统针对 OneNET 平台连接的适配,通过这个组件包可以让设备在 RT-Thread 上非常方便的连接 OneNet 平台,完成数据的发送、接收、设备的注册和控制等功能。具有如下功能特点[2]:
- 断线重连。在断网或网络不稳定导致连接断开时,会维护登陆状态,重新连接,并自动登陆 OneNET 平台。提高连接的可靠性,增加了软件包的易用性。
- 自动注册。不需要在 web 页面上手动的一个一个创建设备,输入设备名字和鉴权信息。当开启设备注册功能后,设备第一次登陆 OneNET 平台时,会自动调用注册函数向 OneNET 平台注册设备,并将返回的设备信息保存下来,用于下次登陆。
- 自动响应函数。OneNET 软件包提供了一个命令响应回调函数,当 OneNET 平台下发命令后,RT-Thread 会自动调用命令响应回调函数,用户处理完命令后,返回要发送的响应内容,RT-Thread 会自动将响应发回 OneNET 平台。
- 自定义topic和回调函数。OneNET 软件包除了可以响应 OneNET 官方 topic 下发的命令外,还可以订阅用户自定义的 topic,并为每个 topic 单独设置一个命令处理回调函数。方便用户开发自定义功能。
- 上传二进制数据。OneNET 软件包除了上传数字和字符串外,还支持二进制文件上传。当启用了 RT-Thread 的文件系统后,可以直接将文件系统内的文件以二进制的方式上传至云端。
2.5本章小节
本章主要介绍了内核线程在本次任务实现过程中的应用情况,如采集任务和网络发送任务的同步和通信等;以及阐述了SAL组件和OneNET软件包的概念和具有的功能特点等。
本章主要描述多功能气压计的硬件设计框架和模块之间的电气连接,如气压传感器通过SPI4接口与ART-Pi开发板的PE2(SPI4_CK)、PE5(SPI4_MISO)和PE6(SPI4_MOSI)引脚等相连,AP6212 WIFI模块通过SDIO2接口与主单片机(STM32H750XB)相连,语音识别模块通过SPI2接口与主单片机相连;以及介绍项目涉及的主要芯片或模块的应用特点和通讯协议。
3.1硬件框架
本设计采用ART-Pi开发板及板载WIFI模块,并使用LPS22HH气压传感器和语音识别模块完成硬件模块电路搭建,如下图3.1硬件设计框图所示。
图3.1 硬件设计框图
由硬件设计框架图可知:WIFI模块通过SMMC2(SDIO2)接口与单片机STM32H750相连;气压传感器通过SPI4接口与单片机相连;语音识别模块与单片机的SPI2接口连接。
3.2硬件模块介绍
(1)LPS22H气压传感器
LPS22HH是一款高精度气压传感器,可用作数字输出气压计,具有数字I2C / MIPI I3CSM / SPI 串行接口标准输出。工作压力范围为260 hPa 至1260 hPa,器件能够以最高200 Hz 的输出数据率测量压力值。绝对精度是0.5hPa.LPM下功耗只有4uA。可以用于测量海拔高度,楼层定位,无人机定高,吸尘器吸力大小判断等应用。在本次设计中使用模块中的SPI4接口与单片机通讯,以获取传感器采集和转换的气压值和温度值。
(2)AP6212 WIFI模块
AMPAK技术公司想宣布一个低成本和低功耗的模块它具有所有WiFi、蓝牙和FM功能。高度集成的模块使网络浏览、VoIP、蓝牙耳机、调频收音机功能成为可能应用程序和其他应用程序。具有无缝漫游功能和高级安全性,也可以与不同厂商的802.11b/g/n无线接入点进行交互局域网。该无线模块符合ieee802.11b/g/n标准,可以达到802.11n草案中的单流传输速度为72.2Mbps,IEEE规定为54Mbps,802.11g或11Mbps,用于连接到无线LAN的IEEE 802.11b。综合模块为WiFi提供SDIO接口,为蓝牙和用于FM的UART/I2S/PCM接口。这个紧凑的模块是一个结合WiFi+BT+FM技术的整体解决方案。该模块是专门为智能手机和便携式设备开发的。
(3)LD3320语音识别模块
LD3320 是一颗基于非特定人语音识别(SI-ASR:Speaker-IndependentAutomatic Speech Recognition)技术的语音识别/声控芯片。提供了真正的单芯片语音识别解决方案。LD3320 芯片上集成了高精度的 A/D 和 D/A 接口,不再需要外接辅助的Flash 和 RAM,即可以实现语音识别/声控/人机对话功能。并且,识别的关键词语列表是可以动态编辑的。在本次设计中通过SPI2接口与主单片机相连,实现语音命令的初始化和语音数据的传输。
3.3本章小节
本章着重介绍了硬件电路的设计框架,和主要的芯片或模块如LPS22HH气压传感器模块、AP6212 WIFI模块和LD3320语音识别模块等。
本章主要阐述在瀑布式开发模型下首先将软件系统分为各个模块并在逻辑控制单元模块下实现模块之间数据的交互,即软件系统框架。其次阐述各个模块的软件设计流程,以及模块的具体功能和接口。
4.1软件框架
本设计根据多功能气压计的功能将软件系统划分各个模块,如采集模块、网络通讯模块、语音识别模块和升级模块,并将其作为单独任务进行初始化和执行读写操作等。最后通过逻辑控制单元实现各模块之间的通信和同步,如图4.1(a)软件系统框架图所示:
图4.1(a) 软件系统设计框图
由图4.1(a)可知:逻辑控制单元主要负责接收采集模块输出的数据消息;升级模块主要从云端获取升级固件并将其输出给逻辑控制单元,再由逻辑控制单元将升级固件写入到downloader分区并通过bootloader程序引导完成升级操作;网络通讯模块主要将由逻辑控制单元处理后的数据如气压值和温度值传输到OneNET云服务器上;语音识别模块主要接收和解析用户输入的语音指令,进而实现设备在线升级等功能。
在硬件通电后软件首先初始化底层设备驱动,然后对各个系统软件模块进行初始化,初始化方式采用命令INIT_APP_EXPORT导入方式。最后使用for循环语句使其依次以线程(任务)的形式创建监测(采集)任务、用户任务和(网络)终端任务,若任务创建成功则开始运行,如下图4.1(b) 软件系统设计流程图所示:
图4.1(b) 软件设计流程图
4.2采集模块
(1)实现方式
首先使用STM32CubeMX工具配置底层SPI驱动,需要注意CPOL值为SPI_POLARITY_HIGH和CPHA值为SPI_PHASE_2EDGE;其次根据芯片手册配置SPI波特率为SPI_BAUDRATEPRESCALER_8,同时LPS22HH气压传感器模块初始化时CTRL_REG1设置为LPS22HH_50_Hz_LOW_NOISE;最后通过读取寄存器0x0FU的值判断SPI通讯是否正常,进而读取温度值和压强值。
(2)实时采集任务
对于实时采集任务是通过while循环方式每秒读取0x28U等寄存器值实现的,此函数在task_init函数中被调用,使用线程方式创建和运行。如下所示为实时采集任务函数的实现。
/** * @brief A thread entry, used to capture temperature[degC] and pressure[hPa]. * Call at task_init function in task module. * @param void* RT_NULL. * @retval int32_t */ int sensor_monitor_entry(void *parameter) { axis1bit32_t data_raw_pressure ={0}; axis1bit16_t data_raw_temperature ={0}; uint32_t pressure =0; uint16_t temperature =0; uint8_t floor =0; while (1) { /* read pressure from lps22hh */ rt_memset(data_raw_pressure.u8bit, 0x00, sizeof(int32_t)); lps22hh_pressure_raw_get(&dev_ctx, data_raw_pressure.u8bit); pressure =data_raw_pressure.i32bit *1.0 / 4096.0; DBG_SENSOR_PRINTF("pressure:%d\n", pressure);
/* write pressure into mailbox, offset: PRESSURE_OFFSET */ if (ld3320_assert(pressure, temperature) == 1) { mailbox_write(pressure, PRESSURE_OFFSET); } rt_thread_mdelay(10);
/* read temperature from lps22hh */ rt_memset(data_raw_temperature.u8bit, 0x00, sizeof(int16_t)); lps22hh_temperature_raw_get(&dev_ctx, data_raw_temperature.u8bit); temperature =data_raw_temperature.i16bit *1.0 / 100.0; DBG_SENSOR_PRINTF("temperature:%d\n", temperature);
/* write temperature into mailbox, offset: TEMPERATURE_OFFSET */ if (ld3320_assert(pressure, temperature) == 1) { mailbox_write(temperature, TEMPERATURE_OFFSET); }
rt_thread_mdelay(10); /* calculate floor by location algorithm */ floor =location_algorithm(pressure, temperature); DBG_SENSOR_PRINTF("floor:%d\n", floor);
/* write floor into mailbox, offset: TEMPERATURE_OFFSET */ if (floor !=255) { mailbox_write(floor, FLOOR_OFFSET); }
rt_thread_mdelay(500); }
return RT_EOK; }
|
4.3升级模块
(1)实现方式
首先将升级固件(rtthread.bin)上传到服务器;其次通过语音识别模块识别到“升级”语音命令时调用http_ota_fw_download(PKG_HTTP_OTA_URL)升级函数,实现将服务器固件文件下载到download分区,下载完成后通过bootloader程序将固件从download分区搬运到app分区,从而实现应用程序升级。注:download分区最后4个字节存储的值为固件大小并将其作为升级标志,即若为0xFFFFFFFF则直接跳转到app;若值不为0xFFFFFFFF则表示存在升级固件并且固件大小为该值,从而先擦除app分区、读取download分区固件写入到app分区、擦除download分区、最后进行跳转完成升级工作。
(2)读取download分区写入app分区的过程如下
/** * @brief IAP move update-firmware from download part to app part. * Call at main.c. * @param None. * @retval Not used. */ rt_uint8_t iap_move(void) { int ret = 0; int file_size = 0, length = 0, total_length = 0;
const struct fal_partition * dl_part = RT_NULL;
/* find download part */ if ((dl_part = fal_partition_find("download")) == RT_NULL) { rt_kprintf("Firmware download find error!"); ret = -RT_ERROR; goto __exit; }
/* get filesize of firmware */ fal_partition_read(dl_part, dl_part->len-4, (rt_uint8_t*)&file_size, 4); if (file_size ==0xFFFFFFFF) { ret = -RT_ERROR; goto __exit; }
/* erase app partition */ rt_kprintf("start erase app\n"); W25QXX_ChipErase(); rt_kprintf("erase app finished\n");
/*moved download to app */ rt_kprintf("start move\n"); do { /*read data from download part */ length = fal_partition_read(dl_part, total_length, buffer_read, file_size - total_length > 4096 ? 4096 : file_size - total_length); if (length > 0) { /* write data into app part */ W25QXX_Write_NoCheck(buffer_read, total_length, length); total_length += length; rt_kprintf("process: %d\n", total_length); } else { rt_kprintf("Exit: server return err (%d)!", length); ret = -RT_ERROR; goto __exit; } } while(total_length != file_size); rt_kprintf("move finished \n");
/* check data between download and app part */ total_length = 0; do{ length = fal_partition_read(dl_part, total_length, buffer_read, file_size - total_length > 4096 ? 4096 : file_size - total_length); if (length > 0) { W25QXX_Read(buffer_temp, total_length, length); if (rt_strncmp(buffer_read, buffer_temp, length) ==0) { total_length += length; rt_kprintf("check: %d\n", total_length); } else { for(int i = 0; i < length; i++) { rt_kprintf("-%x-%x-\n", buffer_temp[i], buffer_read[i]); } rt_kprintf("process: %d check error\n", total_length); } } else { rt_kprintf("Exit: return err (%d)!", length); ret = -RT_ERROR; goto __exit; } } while(total_length != file_size);
/* clear update flag */ if (total_length == file_size) { rt_kprintf("Start erase flash (%s) partition!", dl_part->name);
if (fal_partition_erase(dl_part, 0, dl_part->len) < 0) { LOG_E("Firmware download failed! Partition (%s) erase error!", dl_part->name); ret = -RT_ERROR; goto __exit; } rt_kprintf("Erase flash (%s) partition success!", dl_part->name); } rt_kprintf("start jump\n"); __exit: return ret; }
|
4.4网络通讯模块
(1)实现方式
首先,通过rt_wlan_connect函数成功WIFI网络后,添加Onenet软件包配置参数并添加onenet_mqtt_init函数实现与云服务器的连接;其次,通过邮箱方式获取采集的数据,如温度值和气压值;最后调用onenet_mqtt_upload_digit函数实现数据上传。
(2)数据上传
在WIFI网络连接成功后每隔5秒实现数据获取和数据上传,具体的数据上传如下所示。
/** * @brief A thread entry, used to upload temperature and pressure. * Call at task_init function in task module. * @param void* RT_NULL. * @retval int32_t */ int net_terminal_entry(void *parameter) { uint32_t value =0; while (1) {
/* get pressure in a mailbox from sensor */ value =mailbox_read(PRESSURE_OFFSET);
/* upload pressure to onenet cloud */ if (onenet_mqtt_upload_digit("pressure", value) < 0) { DBG_TERMINAL_PRINTF("upload has an error, stop uploading\n"); } else { DBG_TERMINAL_PRINTF("buffer : {\"pressure\":%d}\n", value); }
/* get temperature in a mailbox from sensor */ value =mailbox_read(TEMPERATURE_OFFSET);
/* upload temperature to onenet cloud */ if (onenet_mqtt_upload_digit("temperature", value) < 0) { DBG_TERMINAL_PRINTF("upload has an error, stop uploading\n"); } else { DBG_TERMINAL_PRINTF("buffer : {\"temperature\":%d}\n", value); }
/* get floor in a mailbox from sensor */ value =mailbox_read(FLOOR_OFFSET);
/* upload floor to onenet cloud */ if (onenet_mqtt_upload_digit("floor", value) < 0) { DBG_TERMINAL_PRINTF("upload has an error, stop uploading\n"); } else { DBG_TERMINAL_PRINTF("buffer : {\"floor\":%d}\n", value); } rt_thread_mdelay(5000); } return RT_EOK; }
|
4.5语音识别模块
(1)实现方式
首先通过STM32CubeMX工具初始化SPI2配置,通过调用HAL库函数实现开发板与语音模块通讯;其次初始化语音命令,通过使用PIN设备完成IRQ中断方式监听语音;最后,根据语音命令实现相对应的功能:重启设备和远程升级。
(2)语音识别
int audio_telling_entry(void *parameter) { uint8_t nAsrRes = 0; LD_init(); while(1) { switch(nAsrStatus) { case LD_ASR_RUNING: case LD_ASR_ERROR: break; case LD_ASR_NONE: nAsrStatus=LD_ASR_RUNING; if (LD_ASR()==0) nAsrStatus = LD_ASR_ERROR; break; case LD_ASR_FOUNDOK: nAsrRes = LD_GetResult(); switch(nAsrRes) { case CODE_CALLED: rt_kprintf("CODE_CALLED\n"); break; case CODE_REBOOT: /* reset cpu */ rt_kprintf("CODE_REBOOT\n"); rt_hw_cpu_reset(); break; case CODE_UPDATE: /* update app */ rt_kprintf("CODE_UPDATE\n"); extern int http_ota_fw_download(const char* uri); http_ota_fw_download(PKG_HTTP_OTA_URL); rt_kprintf("CODE_UPDATE END\n"); break; case CODE_PLAY: rt_kprintf("CODE_PLAY\n"); break; default: break; } nAsrStatus = LD_ASR_NONE; nAsrRes = 0; break; case LD_ASR_FOUNDZERO: default: nAsrStatus = LD_ASR_NONE; break; }
rt_thread_mdelay(1000); }
return RT_EOK; }
|
4.6本章小节
本章主要介绍了软件模块的设计思路和设计方法,以及包括在设计时需要注意的地方。
5.1作品展示
如图5.1所示为app程序运行的部分打印信息,图5.2为升级过程信息,图5.3气压、温度和楼层数据上传到onenet平台。
图5.1
图5.2
图5.3
视频演示
5.2不足和改进措施
本次创客创意大赛多功能气压计的设计,由于个人能力和时间原因导致目前的程序仍然存在小小的缺陷:如气压传感器采集温度和大气压时精度不够高,有时会出现采集数值错误的地方;其次语音识别功能灵敏度太高导致和初始化的命令码相似时也会执行重启和升级功能。
针对上述问题,可以通过增加多个杂乱的语音码以减少命令的匹配度,同时增加气压传感器的识别速度,以实现高精度采集。最后要着重定位算法,以达到根据温度和大气压实现高精度定位问题。
5.3心得和感悟
经过这次短短一个多月的比赛,感觉自己的能力有了明显的提升、对RT-Thread操作系统的认识有渐渐地前进了一个台阶。
如在刚开始使用SPI驱动时一直犹豫不决不知道是使用HAL库还是使用SPI设备,刚开始驱动传感器模块时单纯使用HAL库函数还能解决问题,但是到bootloader程序时要使用FAL软件包就必须使用SPI设备和SFUD驱动,这才使我花了好几天时间配置SPI设备和SFUD驱动,最后终于明白了要使用SPI设备需要这几个步骤:首先要在RT-Thread Settings使能SPI和SFUD Drivers,其次在board.c文件中初始化HAL_SPI_MspInit函数和HAL_SPI_MspDeInit函数,接着在rtconfig.h中定义宏BSP_USING_SPI和BSP_USING_SPI1,最后在stm32h7xx_hal_config.h文件中定义HAL_SPI_MODULE_ENABLED。若没有定义HAL_SPI_MODULE_ENABLED这个宏编译程序后会出现找不到与SPI HAL库有关的函数,这个地方比较容易忘记。
比如在调试语音命令升级程序时由于线程栈太小(512K)导致audio任务线程崩溃,最后通过增加其大小(10240K)顺利的完成升级。
5.4本章小节
本章主要介绍了此次应用开发设计存在的不足和后期改进的方法,以及比赛过程中的心得和感悟。