基于STM32F407+NBIOT+华为云IOT平台设计的环境检测系统
实现的功能:
【1】能够采集本地环境的温度、湿度、烟雾浓度,火光信息,在OLED显示屏上显示。 如果检测到烟雾、温度、火光超过阀值会触发蜂鸣器报警。 【2】能够通过NBIOT将本地设备采集的信息上传到华为云物联网云平台。 【3】通过Qt(C++)开发Android手机APP,从华为云物联网云平台获取设备最新的数据进行显示,完成信息展示,在手机APP上可以显示检测到的温度、湿度、烟雾报警、火光报警信息。
硬件选型:
【1】主控芯片选择STM32F407
【2】环境温湿度传感器采用SHT30
【3】烟雾传感器采用MQ2
【4】火焰检测传感器采用火光检测传感器
【5】NBIOT采用BC26
【6】电源采用USB接口5V供电。
【7】蜂鸣器采用高电平触发的有源蜂鸣器。
【8】显示屏采用0.96寸IIC协议OLED显示屏。
文章目录
- 一、前言
- 1.1 项目介绍
- 【1】项目开发背景
- 【2】设计实现的功能
- 【3】项目硬件模块组成
- 【4】摘要
- 1.2 设计思路
- 1.3 系统功能总结
- 1.4 开发工具的选择
- 【1】设备端开发
- 【2】上位机开发
- 二、硬件选型
- 2.1 STM32F407系统板
- 2.2 杜邦线(2排)
- 2.3 SHT30温湿度模块
- 2.4 OLED显示屏(4线IIC接口)
- 2.5 MQ2 烟雾传感器
- 2.6 火焰检测模块
- 2.7 电源扩展接口(x2)
- 2.8 蜂鸣器模块
- 2.9 NBIOT模块
- 三、部署华为云物联网平台
- 3.1 物联网平台介绍
- 3.2 开通物联网服务
- 3.3 创建产品
- (1)创建产品
- (2)填写产品信息
- (3)产品创建成功
- (4)添加自定义模型
- 3.4 添加设备
- (1)注册设备
- (2)根据自己的设备填写
- (3)保存设备信息
- (4)设备创建完成
- (5)设备详情
- 3.5 MQTT协议主题订阅与发布
- (1)MQTT协议介绍
- (2)华为云平台MQTT协议使用限制
- (3)主题订阅格式
- (4)主题发布格式
- 3.6 MQTT三元组
- (1)MQTT服务器地址
- (2)生成MQTT三元组
- 3.7 模拟设备登录测试
- (1)填入登录信息
- (2)打开网页查看
- (3)MQTT登录测试参数总结
- 3.8 创建IAM账户
- 3.9 获取影子数据
- 四、STM32设备端代码设计
- 4.1 BC26-NBIOT配置代码
- 4.2 定时器代码封装
- 4.3 串口配置代码
- 五、上位机开发
- 5.1 Qt开发环境安装
- 5.2 新建上位机工程
- 5.3 设计UI界面与工程配置
- 【1】打开UI文件
- 【2】开始设计界面
- 5.5 编译Windows上位机
- 5.6 配置Android环境
- 【1】选择Android编译器
- 【2】创建Android配置文件
- 【3】配置Android图标与名称
- 【3】编译Android上位机
- 六、 BC26-NBIOT模块调试过程
- 6.1 模块调试接线
- 6.2 测试模块
- 6.3 上电初始化操作
- 6.4 开启GPS定位
- 七、总结
一、前言
1.1 项目介绍
【1】项目开发背景
随着物联网技术的快速发展,越来越多的设备和系统开始融入我们的生活和工作中。环境监测作为一个重要的应用领域,对于保障人们的健康和安全具有重要意义。基于STM32F407+NBIOT+华为云IOT平台设计的环境检测系统,实现对室内环境的实时监测,并通过物联网技术将数据传输至云端,为用户提供便捷的数据查询和远程监控功能。
该系统的主要功能包括:
(1)采集本地环境的温度、湿度、烟雾浓度和火光信息,并在OLED显示屏上显示。当检测到烟雾、温度或火光超过预设阈值时,触发蜂鸣器报警,以提醒用户采取相应措施。
(2)通过NBIOT技术将本地设备采集的信息上传至华为云物联网云平台,实现数据的远程存储和管理。
(3)利用Qt(C++)开发的Android手机APP,从华为云物联网云平台获取设备最新的数据进行显示,完成信息展示。在手机APP上可以显示检测到的温度、湿度、烟雾报警和火光报警信息。
硬件选型方面,选择了以下组件:
(1)主控芯片:STM32F407,具有较高的处理能力和丰富的外设接口,适合作为本系统的控制核心。
(2)环境温湿度传感器:SHT30,具有高精度、低功耗等特点,适用于室内环境监测。
(3)烟雾传感器:MQ2,能够检测空气中的烟雾浓度,及时发现火灾隐患。
(4)火焰检测传感器:火光检测传感器,用于检测火焰产生的光辐射,实现火焰报警功能。
(5)NBIOT模块:BC26,支持NBIOT网络通信,实现数据的远程传输。
(6)电源:采用USB接口5V供电,方便设备接入各种电源环境。
(7)蜂鸣器:高电平触发的有源蜂鸣器,用于发出报警声音。
(8)显示屏:0.96寸IIC协议OLED显示屏,用于显示采集到的环境参数和报警信息。
【2】设计实现的功能
(1)环境参数采集: 利用STM32F407微控制器集成的高精度ADC及数字信号处理能力,系统能够实时采集SHT30温湿度传感器输出的温湿度数据,以及MQ2烟雾传感器和火光检测传感器提供的烟雾浓度和火光信息。这些传感器的选择确保了对环境状态的全面监控。
(2)数据显示: 采集到的数据通过I²C接口传输至0.96寸OLED显示屏上实时显示,提供直观的温湿度读数、烟雾浓度指标和火光警告信息。OLED屏因其自发光特性,即便在低光照环境下也能清晰显示数据。
(3)本地预警系统: 设定合理的阈值,当检测到的温度、湿度、烟雾浓度或火光强度超过预设安全范围时,系统将自动激活高电平触发的有源蜂鸣器,发出报警声,即时提醒现场人员注意潜在的环境危险。
(4)NBIOT通信: 通过集成BC26 NBIOT模块,系统能够将本地采集的环境数据通过窄带物联网技术远程上传至华为云物联网平台。NBIOT技术因其低功耗、广覆盖的特点,特别适合于远程、低速数据传输的应用场景。
(5)云端数据处理与存储: 上传至华为云的环境监测数据会被自动接收并存储,华为云物联网平台提供强大的数据管理和分析能力,支持用户通过云端界面查看历史数据趋势,设置告警规则等。
(6)Android手机APP开发: 利用Qt框架(C++)开发的Android手机应用程序,用户可以远程实时查看连接到华为云物联网平台的环境监测设备的最新数据。APP界面友好,直观展示当前的温度、湿度、烟雾报警状态和火光报警信息,使用户无论身在何处都能及时了解环境状况。
(7)交互性与控制: 手机APP除了被动接收数据展示外,还可以进一步设计实现远程控制功能,如调整报警阈值、查询历史记录或发送指令给STM32F407控制端进行特定操作,增强系统的互动性和管理效率。
本项目不仅实现了对环境参数的精准监测与本地预警,还通过NBIOT技术与华为云物联网平台的结合,提供了远程监控和数据分析的能力,配以定制化的移动端应用,极大地提升了环境监测的智能化和便捷性。
【3】项目硬件模块组成
(1)主控单元 - STM32F407微控制器:
- 作为整个系统的核心,STM32F407是一款高性能的32位ARM Cortex-M4内核微控制器,具备丰富的外设资源和高速处理能力,负责数据采集、处理、显示控制以及与外部模块的通信协调。
(2)温湿度传感器 - SHT30:
- 用于测量环境中的温度和湿度,通过I²C接口与STM32F407通信,提供高精度的温湿度数据,是环境监测的基础组成部分。
(3)烟雾传感器 - MQ2:
- 用于检测环境中烟雾浓度,能对多种气体敏感,包括烟雾、LPG、甲烷等,通过模拟信号输出至STM32F407,用于评估火灾风险。
(4)火光检测传感器:
- 专门设计用于探测火焰或高强度光源,通过其光电效应产生电信号,经处理后由STM32F407判断是否有火光存在,实现火灾的早期预警。
(5)NBIOT通信模组 - BC26:
- 负责将STM32F407处理后的环境监测数据通过NBIOT网络上传至华为云物联网平台。BC26模组支持低功耗、远程连接,为系统提供稳定的数据传输通道。
(6)电源模块:
- 采用USB接口5V供电,为整个系统提供稳定可靠的能源。设计中需考虑电源管理,以优化系统能耗,延长电池续航或确保持续稳定供电。
(7)蜂鸣器报警模块:
- 高电平触发的有源蜂鸣器,当STM32F407检测到环境参数超过设定阈值时,通过GPIO输出高电平信号,激活蜂鸣器发出报警声音,实现本地即时警告。
(8)OLED显示屏:
- 采用0.96英寸I²C接口OLED显示屏,用于实时显示温湿度、烟雾浓度和火光报警等信息。OLED屏具有自发光、视角宽、响应速度快等特点,适合在不同光照条件下清晰展示数据。
【4】摘要
本项目设计并实现一款基于STM32F407微控制器、结合NBIOT通信技术与华为云物联网平台的智能环境监测系统。系统整合了SHT30温湿度传感器、MQ2烟雾传感器以及火光检测传感器,实现对环境的多维度监控,包括温度、湿度、烟雾浓度和火光信息的实时采集。通过OLED显示屏现场展示监测数据,并在检测到异常时启动蜂鸣器报警,确保即时预警。利用BC26 NBIOT模块,系统将监测数据远程传输至华为云物联网平台,借助云平台的强大数据处理能力,实现数据的远程访问、存储与分析。此外,项目还开发了基于Qt框架的Android手机APP,方便用户通过移动设备远程查看实时环境数据及历史记录,提升环境管理的便捷性和效率。本系统有效融合了物联网、云计算与移动应用技术,为环境保护、火灾预防等应用场景提供了智能化解决方案。
关键字:
- STM32F407
- NBIOT通信
- 华为云物联网平台
- 环境监测系统
- 温湿度传感器(SHT30)
- 烟雾传感器(MQ2)
- 火光检测
- OLED显示屏
- Qt/C++移动应用开发
- 智能预警系统
1.2 设计思路
(1)模块化设计:
- 将系统划分为多个独立但相互协作的模块,包括数据采集模块(温湿度、烟雾、火光)、数据处理与控制模块(STM32F407)、本地显示模块(OLED)、远程通信模块(NBIOT BC26)、报警模块(蜂鸣器)和移动应用模块(Android APP)。这种设计便于系统维护、升级和故障排查,同时也利于模块复用和扩展新功能。
(2)高效数据采集与处理:
- 选用高精度的SHT30温湿度传感器和灵敏的MQ2烟雾传感器,结合火光检测传感器,确保数据采集的准确性。STM32F407作为主控芯片,利用其强大计算能力和丰富的外设接口,负责实时处理采集数据,执行逻辑判断,控制报警与显示。
(3)低功耗远程通信策略:
- 采用NBIOT技术通过BC26模组实现数据的远程传输,看重其在低功耗、广覆盖方面的优势,特别适合于长时间运行、部署位置偏远的环境监测设备,确保数据传输的稳定性和经济性。
(4)云端数据管理与分析:
- 利用华为云物联网平台强大的数据处理能力,实现数据的远程存储、实时分析与可视化展示,为用户提供历史数据查询、趋势分析等功能,同时支持设置报警规则,通过云端平台实现对多个监测点的集中管理和远程监控。
(5)移动终端交互体验优化:
- 开发基于Qt的Android手机APP,注重用户体验设计,提供简洁直观的操作界面,让用户能够随时随地查看环境监测数据,接收报警通知,甚至进行远程配置和控制,提升系统的实用性和用户参与度。
1.3 系统功能总结
功能模块 | 具体功能描述 |
---|---|
数据采集 | - 使用SHT30温湿度传感器实时监测并采集环境温度与湿度数据。 |
- 利用MQ2烟雾传感器监测烟雾浓度,支持多种气体检测。 | |
- 火光检测传感器,对环境中的火光或强光源进行实时监测。 | |
数据处理与控制 | - STM32F407微控制器负责数据处理、逻辑判断及系统控制。 |
- 实现数据阈值判断,当检测值超限时,触发报警机制。 | |
本地显示 | - 通过0.96寸OLED显示屏,实时展示温湿度、烟雾浓度、火光报警信息。 |
远程通信 | - BC26 NBIOT模块实现与华为云物联网平台的数据上传。 |
云端服务 | - 华为云物联网平台存储、处理接收到的环境数据。 |
- 支持数据的历史记录查询、统计分析与可视化展示。 | |
移动端监控 | - Qt开发的Android手机APP,远程查看实时环境监测数据。 |
- 显示温度、湿度、烟雾浓度报警状态及火光报警信息。 | |
- 用户可设置报警阈值、查看历史记录等。 | |
安全与可靠性 | - 数据传输采用加密技术,保障云端数据安全。 |
- 设备内置错误处理与恢复机制,提高系统稳定性。 | |
可扩展性 | - 系统设计预留接口,支持未来接入更多类型传感器。 |
- 通信方式和技术框架易于升级,以适应技术发展和应用需求变化。 |
1.4 开发工具的选择
【1】设备端开发
STM32的编程语言选择C语言,C语言执行效率高,大学里主学的C语言,C语言编译出来的可执行文件最接近于机器码,汇编语言执行效率最高,但是汇编的移植性比较差,目前在一些操作系统内核里还有一些低配的单片机使用的较多,平常的单片机编程还是以C语言为主。C语言的执行效率仅次于汇编,语法理解简单、代码通用性强,也支持跨平台,在嵌入式底层、单片机编程里用的非常多,当前的设计就是采用C语言开发。
开发工具选择Keil,keil是一家世界领先的嵌入式微控制器软件开发商,在2015年,keil被ARM公司收购。因为当前芯片选择的是STM32F103系列,STMF103是属于ARM公司的芯片构架、Cortex-M3内核系列的芯片,所以使用Kile来开发STM32是有先天优势的,而keil在各大高校使用的也非常多,很多教科书里都是以keil来教学,开发51单片机、STM32单片机等等。目前作为MCU芯片开发的软件也不只是keil一家独大,IAR在MCU微处理器开发领域里也使用的非常多,IAR扩展性更强,也支持STM32开发,也支持其他芯片,比如:CC2530,51单片机的开发。从软件的使用上来讲,IAR比keil更加简洁,功能相对少一些。如果之前使用过keil,而且使用频率较多,已经习惯再使用IAR是有点不适应界面的。
【2】上位机开发
上位机的开发选择Qt框架,编程语言采用C++;Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。Qt能轻松创建具有原生C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。
二、硬件选型
2.1 STM32F407系统板
链接:https://item.taobao.com/item.htm?spm=a21n57.1.item.6.3406523cr317ux&priceTId=213e38bb17188687576681904e683b&utparam=%7B%22aplus_abtest%22:%22781d5aaa75dd4af6ad2408d4a62768ea%22%7D&id=560814289385&ns=1&abbucket=12&skuId=4346181285287
2.2 杜邦线(2排)
作用: 连接模块与单片机。
链接:https://detail.tmall.com/item.htm?ali_refid=a3_430582_1006:1104520036:N:MsF9mE9KLTC2IibWJh%20K1A==:adaa6d3d7abe6f1f07b87a36416ee4fb&ali_trackid=1_adaa6d3d7abe6f1f07b87a36416ee4fb&id=14466195609&skuId=3108214440215&spm=a230r.1.14.1
2.3 SHT30温湿度模块
链接: https://detail.tmall.com/item.htm?abbucket=8&id=624272456739&ns=1&skuId=4420709624009&spm=a21n57.1.item.4.5097523cBtlCji
2.4 OLED显示屏(4线IIC接口)
链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.2ad7523cyZF6lC&id=43639171586&ns=1&abbucket=18#detail
2.5 MQ2 烟雾传感器
链接:https://detail.tmall.com/item.htm?abbucket=5&id=17949567819&ns=1&spm=a21n57.1.0.0.242b523cwrRqcB&skuId=4078848750990
2.6 火焰检测模块
1、可以检测火焰或者波长在760纳米~1100纳米范围内的光源,打火机测试火焰距离为80cm,对火焰越大,测试距离越远
2、探测角度60度左右,对火焰光谱特别灵敏3、灵敏度可调(图中蓝色数字电位器调节)
4、比较器输出,信号干净,波形好,驱动能力强,超过15mA
5、配可调精密电位器调节灵敏度
6、工作电压3.3V-5V
7、输出形式:do数字开关量输出(o和1)和ao模拟电压输出
链接:https://detail.tmall.com/item.htm?abbucket=12&id=41197526279&ns=1&spm=a230r.1.14.13.82631c79yq9mvA&skuId=3954176793928
2.7 电源扩展接口(x2)
买电源扩展板,方便扩展5v电源 和 3.3V电源。
链接:https://item.taobao.com/item.htm?id=647681090119&skuId=4672158745999&spm=a1z0d.6639537/tb.1997196601.4.754374841n18eN
2.8 蜂鸣器模块
链接:https://detail.tmall.com/item.htm?ali_refid=a3_430582_1006:1104520036:N:X/YIdD%20/nzZWyWHIKhozj3ahdFvQYGOd:09a834d50903c653d8893f1f618eb321&ali_trackid=1_09a834d50903c653d8893f1f618eb321&id=21124132861&spm=a230r.1.14.1&skuId=4319138558993
2.9 NBIOT模块
链接:https://item.taobao.com/item.htm?abbucket=3&id=583282366803&ns=1&skuId=4749833023376&spm=a21n57.1.item.3.1ed9523cnUBQwb
三、部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA
。
3.1 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
3.2 开通物联网服务
地址: https://www.huaweicloud.com/product/iothub.html
点击立即创建
。
正在创建标准版实例,需要等待片刻。
创建完成之后,点击实例名称。 可以看到标准版实例的设备接入端口和地址。
在上面也能看到 免费单元的限制。
开通之后,点击总览
,也能查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。
总结:
端口号: MQTT (1883)| MQTTS (8883)
接入地址:ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
**根据域名地址得到IP地址信息: **
打开Windows电脑的命令行控制台终端,使用ping
命令。ping
一下即可。
Microsoft Windows [版本 10.0.19045.4170]
(c) Microsoft Corporation。保留所有权利。
C:\Users\11266>ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=39ms TTL=93
117.78.5.125 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 35ms,最长 = 39ms,平均 = 36ms
C:\Users\11266>
MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。
3.3 创建产品
(1)创建产品
(2)填写产品信息
根据自己产品名字填写,下面的设备类型选择自定义类型。
(3)产品创建成功
创建完成之后点击查看详情。
(4)添加自定义模型
产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。
模型简单来说: 就是存放设备上传到云平台的数据。
你可以根据自己的产品进行创建。
比如:
烟雾可以叫 MQ2
温度可以叫 Temperature
湿度可以叫 humidity
火焰可以叫 flame
其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。
先点击自定义模型。
再创建一个服务ID。
接着点击新增属性。
3.4 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。
(4)设备创建完成
(5)设备详情
3.5 MQTT协议主题订阅与发布
(1)MQTT协议介绍
当前的设备是采用MQTT协议与华为云平台进行通信。
MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。
MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。
华为云的MQTT协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
业务流程:
(2)华为云平台MQTT协议使用限制
描述 | 限制 |
---|---|
支持的MQTT协议版本 | 3.1.1 |
与标准MQTT协议的区别 | 支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg |
MQTTS支持的安全等级 | 采用TCP通道基础 + TLS协议(最高TLSv1.3版本) |
单帐号每秒最大MQTT连接请求数 | 无限制 |
单个设备每分钟支持的最大MQTT连接数 | 1 |
单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关 | 3KB/s |
MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝 | 1MB |
MQTT连接心跳时间建议值 | 心跳时间限定为30至1200秒,推荐设置为120秒 |
产品是否支持自定义Topic | 支持 |
消息发布与订阅 | 设备只能对自己的Topic进行消息发布与订阅 |
每个订阅请求的最大订阅数 | 无限制 |
(3)主题订阅格式
帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
(4)主题发布格式
对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。
这个操作称为:属性上报。
帮助文档地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:
发布的主题格式:
$oc/devices/{device_id}/sys/properties/report
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。
上传的JSON数据格式如下:
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值>,
..........
}
}
]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
3.6 MQTT三元组
MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。
接下来介绍,华为云平台的MQTT三元组参数如何得到。
(1)MQTT服务器地址
要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。
帮助文档地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。
根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)
华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883
如何得到IP地址?如何域名转IP? 打开Windows的命令行输入以下命令。
ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
(2)生成MQTT三元组
华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
3.7 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
(1)填入登录信息
打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
(2)打开网页查看
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据:
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
(3)MQTT登录测试参数总结
MQTT服务器: 117.78.5.125
MQTT端口号: 183
//物联网服务器的设备信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
//订阅与发布的主题
#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //订阅
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //发布
发布的数据:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
3.8 创建IAM账户
创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】获取项目凭证 ** 点击左上角用户名,选择下拉菜单里的我的凭证
项目凭证:
28add376c01e4a61ac8b621c714bf459
【2】创建IAM用户
鼠标放在左上角头像上,在下拉菜单里选择统一身份认证
。
点击左上角创建用户
。
创建成功:
【3】创建完成
用户信息如下:
主用户名 l19504562721
IAM用户 ds_abc
密码 DS12345678
3.9 获取影子数据
帮助文档:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
设备影子介绍:
设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
每个设备有且只有一个设备影子,由设备ID唯一标识
设备影子仅保存最近一次设备的上报数据和预期数据
无论该设备是否在线,都可以通过该影子获取和设置设备的属性
简单来说:设备影子就是保存,设备最新上传的一次数据。
我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。
如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。
调试完成看右下角的响应体,就是返回的影子数据。
设备影子接口返回的数据如下:
{
"device_id": "663cb18871d845632a0912e7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DHT11_T": 18,
"DHT11_H": 90,
"BH1750": 38,
"MQ135": 70
},
"event_time": "20240509T113448Z"
},
"version": 3
}
]
}
调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用Qt写代码访问此链接,获取影子数据,完成上位机开发。
链接如下:
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow
四、STM32设备端代码设计
4.1 BC26-NBIOT配置代码
#include "ec20.h"
#include "stdlib.h"
#include "string.h"
#include "usart.h"
#include "iwdg.h"
int errcount = 0;
char atstr[BUFLEN];
char AtStrBuf[BUFLEN];
char *strx,*extstrx,*Readystrx;
extern char AtRxBuffer[512],Rxcouter;
char GPRMCSTR[128]; //转载GPS信息 GPRMC 经纬度存储的字符串
char GPRMCSTRLON[64]; //经度存储字符串 也就是119.20694
char GPRMCSTRLAT[64]; //维度存储字符串,也就是26.06451
char IMEINUMBER[64];//+CGSN: "869523052178994"
//下面是纠正火星坐标的变量定义/
int Get_GPSdata(void);
void Getdata_Change(char status);
typedef struct
{
char UtcDate[6];
char longitude[11];//经度原数据
char Latitude[10];//纬度源数据
char longitudess[4];//整数部分
char Latitudess[3];
char longitudedd[8];//小数点部分
char Latitudedd[8];
char Truelongitude[12];//转换过数据
char TrueLatitude[11];//转换过数据
char getstautus;//获取到定位的标志状态
float gpsdata[2];
}LongLatidata;
LongLatidata latdata;
float tempdata[2];
char latStrAF[64]; //存放数据经纬度用来发送
char lonStrAF[64]; //存放数据经纬度用来显示
//火星纠偏结束///
只要修改三要素/
#define PRODUCEKEY "6402ac07352830580e48ff7b_dev1_0_0_2023030403" //设备ID
#define DEVICENAME "6402ac07352830580e48ff7b_dev1" //用户名
#define DEVICESECRET "517f1c8f18d6b7b2e4c134653dc892edd38c3e86153506df57f7c296d13b37f7" //密码
void Clear_Buffer(void)//清空缓存
{
u8 i;
NBIOTSend_RecAccessMode();
printf(AtRxBuffer);
for(i=0;i<Rxcouter;i++)
AtRxBuffer[i]=0;//缓存
Rxcouter=0;
}
//初始化模块
void NBIOT_Init(void)
{
Uart2_SendStr("AT\r\n");
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//返回OK
errcount = 0;
while(strx==NULL)
{
errcount++;
printf("\r\n单片机正在连接到模块...\r\n");
Clear_Buffer();
Uart2_SendStr("AT\r\n");
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//返回OK
if(errcount>50) //防止死循环
{
errcount = 0;
reset_4g();
__set_FAULTMASK(1); //关闭总中断
NVIC_SystemReset(); //请求单片机重启
break;
}
}
Uart2_SendStr("ATE1\r\n"); //回显
delay_ms(500);
Clear_Buffer();
/
Uart2_SendStr("AT+CPIN?\r\n");//检查SIM卡是否在位
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CPIN: READY");//查看是否返回ready
while(strx==NULL)
{
Clear_Buffer();
Uart2_SendStr("AT+CPIN?\r\n");
delay_ms(500);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CPIN: READY");//检查SIM卡是否在位,等待卡在位,如果卡识别不到,剩余的工作就没法做了
}
Clear_Buffer();
///
Uart2_SendStr("AT+CSQ\r\n"); //检查CSQ
delay_ms(500);
Clear_Buffer();
Uart2_SendStr("ATI\r\n"); //检查模块的版本号
delay_ms(500);
Clear_Buffer();
///
Uart2_SendStr("AT+CREG?\r\n");//查看是否注册GSM网络
delay_ms(500);
Clear_Buffer();
Uart2_SendStr("AT+CEREG?\r\n");//查看注册到哪个运营商,支持移动 联通 电信
delay_ms(500);
Clear_Buffer();
Uart2_SendStr("AT+CIMI\r\n");//获取卡号,类似是否存在卡的意思,比较重要。
delay_ms(1000);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//只要卡不错误 基本就成功
if(strx)
{
printf("============\r\n我的卡号是 : %s \r\n===============\r\n",AtRxBuffer);
delay_ms(1000);
Clear_Buffer();
}
else
{
// printf("卡错误 : %s \r\n",AtRxBuffer);
delay_ms(300);
Clear_Buffer();
}
Clear_Buffer();
Uart2_SendStr("AT+CGSN\r\n");//激活网络,PDP
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"OK");//返OK
Clear_Buffer();
Uart2_SendStr("AT+CGATT?\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CGATT: 1");//返1 表明激活成功 获取到IP地址了
Clear_Buffer();
errcount = 0;
while(strx==NULL)
{
errcount++;
Clear_Buffer();
Uart2_SendStr("AT+CGATT?\r\n");//获取激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"+CGATT: 1");//返回1,表明注网成功
if(errcount>100) //防止死循环
{
errcount = 0;
reset_4g();
__set_FAULTMASK(1); //关闭总中断
NVIC_SystemReset(); //请求单片机重启
break;
}
}
Uart2_SendStr("AT+QCCID\r\n");//获取当前卡的IP地址
delay_ms(500);
Clear_Buffer();
}
void Start_GPS(void)
{
Clear_Buffer();
Uart2_SendStr("AT+QGPS=1\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)AtStrBuf,(const char*)"OK");//返1
if(strx==NULL)
{
//__set_FAULTMASK(1);
//NVIC_SystemReset(); //超时重启
delay_ms(300);
}
Clear_Buffer();
}
//获取定位数据/
/*
AT+QGPSGNMEA="RMC"
+QGPSGNMEA: $GNRMC,035645.00,A,2603.9111,N,11912.4140,E,0.336,,140821,,,A,V*19
OK
AT+QGPSGNMEA="RMC"
+QGPSGNMEA: $GPRMC,145620.00,A,2603.972207,N,11912.411739,E,0.0,0.0,171022,3.5,W,A*2F
*/
char *Get_GPS_RMC(char type)
{
Clear_Buffer();
memset(GPRMCSTR,0,128);
Uart2_SendStr("AT+QGPSGNMEA=\"RMC\"\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"$GPRMC");//返1
while(strx==NULL)
{
Clear_Buffer();
Uart2_SendStr("AT+QGPSGNMEA=\"RMC\"\r\n");//获取激活状态
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)"$GPRMC");//返回1,表明注网成功
}
sprintf(GPRMCSTR,"%s",strx);
Clear_Buffer(); //打印收到的GPS信息
GPRMCSTR[2]= 'P';
//printf("============GETGPRMC==============\r\n%s",GPRMCSTR); //打印GPRMC
if(GPRMCSTR[17]=='A')
{
memset(latStrAF,0,64);
memset(lonStrAF,0,64);
Get_GPSdata();
if(type==1)
return latStrAF;
if(type==2)
return lonStrAF;
}
return 0;
}
/*****************************************************
下面是矫正火星坐标的
*****************************************************/
//解GPS析函数
//$GPRMC,134952.00,A,2603.9576,N,11912.4098,E,0.154,,280821,,,A,V*18
int Get_GPSdata()
{
int i=0;
strx=strstr((const char*)GPRMCSTR,(const char*)"A,");//获取纬度的位置
if(strx)
{
for(i=0;i<9;i++)
{
latdata.Latitude[i]=strx[i+2];//获取纬度值2603.9576
}
strx=strstr((const char*)GPRMCSTR,(const char*)"N,");//获取经度值
if(strx)
{
for(i=0;i<10;i++) //获取经度 11912.4098
{
latdata.longitude[i]=strx[i+2];
}
}
printf("latdata.Latitude ,%s \r\n",latdata.Latitude);
printf("latdata.longitude ,%s \r\n",latdata.longitude);
latdata.getstautus=1;//
}
else
{
latdata.getstautus=0;
}
Getdata_Change(latdata.getstautus);//数据换算
Clear_Buffer();
return 0;
}
/*************解析出经纬度数据,然后直接提交数据*******************/
void Getdata_Change(char status)
{
unsigned char i;
if(status)
{
for(i=0;i<3;i++)
latdata.longitudess[i]=latdata.longitude[i];
for(i=3;i<10;i++)
latdata.longitudedd[i-3]=latdata.longitude[i];
latdata.gpsdata[0]=(latdata.longitudess[0]-0x30)*100+(latdata.longitudess[1]-0x30)*10+(latdata.longitudess[2]-0x30)\
+((latdata.longitudedd[0]-0x30)*10+(latdata.longitudedd[1]-0x30)+(float)(latdata.longitudedd[3]-0x30)/10+\
(float)(latdata.longitudedd[4]-0x30)/100+(float)(latdata.longitudedd[5]-0x30)/1000+(float)(latdata.longitudedd[6]-0x30)/10000)/60.0;//获取完整的数据
///
for(i=0;i<2;i++)
latdata.Latitudess[i]=latdata.Latitude[i];
for(i=2;i<9;i++)
latdata.Latitudedd[i-2]=latdata.Latitude[i];
latdata.gpsdata[1]=(float)(latdata.Latitudess[0]-0x30)*10+(latdata.Latitudess[1]-0x30)\
+((latdata.Latitudedd[0]-0x30)*10+(latdata.Latitudedd[1]-0x30)+(float)(latdata.Latitudedd[3]-0x30)/10+\
(float)(latdata.Latitudedd[4]-0x30)/100+(float)(latdata.Latitudedd[5]-0x30)/1000+(float)(latdata.Latitudedd[6]-0x30)/10000)/60.0;//获取完整的数据b
sprintf(latStrAF,"%f",latdata.gpsdata[1]);
sprintf(lonStrAF,"%f",latdata.gpsdata[0]);
printf("latStrAF,%s \r\n",latStrAF);
printf("lonStrAF,%s \r\n",lonStrAF);
}
else
{
latdata.gpsdata[0]=0;
latdata.gpsdata[1]=0;
}
}
/*****************************************************
纠偏结束
*****************************************************/
/*********************************************************************************
** 函数名称 : MakeOnenetPayload(char *Str,u8 Temp,u8 Humi)
** 函数功能 : 将温度与湿度封装成Onenet有效载荷包
** 输 入 : *str有效载荷保存的目标字符串,
** 输 入 : Temp 温度值
** 输 入 : Humi 湿度值
** 输 出 : 有效载荷字符串
** 返 回 : 有效载荷字符串长度
*********************************************************************************/
char JsonBuf[256];//临时数据缓存
u8 MakeOnenetPayload(u8 *Str,u8 Temp,u8 Humi)//将温度与湿度封装成Onenet有效载荷包
{
char Payload[] = "{\"datastreams\":[{\"id\":\"Temp\",\"datapoints\":[{\"value\":%d}]},{\"id\":\"Humi\",\"datapoints\":[{\"value\":%d}]}]}";//onenet有效载荷固定格式
unsigned short StrLen;//有效载荷数据长度
memset(JsonBuf,0,256);
sprintf(JsonBuf,Payload,Temp,Humi);//合成数据有效载荷封装
//printf("\r\n有效载荷(%d):[%s]\r\n",strlen(JsonBuf),(char *)JsonBuf);
StrLen = strlen(JsonBuf)/sizeof(char);//计算有效载荷的数据长度
//printf("\r\n有效载荷长度(%d)\r\n",StrLen);
Str[0] = '\x01';//报文第一个字节的固定显示
//printf("\r\nStr[0]=0X(%02X)\r\n",Str[0]);
//报文第2个字节和第三个字节表示有效载荷的长度的固定显示
Str[1] = (StrLen & 0XFF00) >>8;//高位
//printf("\r\nStr[1]=0X(%02X)\r\n",Str[1]);
Str[2] = StrLen & 0XFF;//低位
//printf("\r\nStr[2]=0X(%02X)\r\n",Str[2]);
//拷贝有效载荷数据到Str
memcpy(Str+3,JsonBuf,StrLen);//从第三个字节开始拷贝
return (StrLen+3);//返回长度
}
u8 CSTX_4G_MQTT_Senddata(char *data)
{
u8 JsonLen;//Json包数据长度
memset(AtStrBuf,0,BUFLEN);
Clear_Buffer();
sprintf(AtStrBuf,"AT+QMTPUB=0,0,0,0,\"$oc/devices/6402ac07352830580e48ff7b_dev1/sys/properties/report\"\r\n");
// printf("ATSTR = %s \r\n",AtStrBuf);
Uart2_SendStr(AtStrBuf);//mqtt提交
delay_ms(300);
strx=strstr((const char*)AtRxBuffer,(const char*)">");//准备发送数据
errcount=0;
while(strx==NULL)
{
errcount++;
strx=strstr((const char*)AtRxBuffer,(const char*)">");//准备发送数据 模块的反馈
delay_ms(30);
if(errcount>10) //超时退出
{
errcount = 0;
break;
}
}
Clear_Buffer();
USART2_Send((char *)data,strlen(data));//发送json数据
UART2_send_byte(0x1A);
delay_ms(300);
errcount=0;
strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返SEND OK +QMTPUB: 0,0,0
while(strx==NULL)
{
errcount++;
strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返SEND OK
delay_ms(30);
if(errcount>10) //超时退出
{
errcount = 0;
break;
}
}
Clear_Buffer();
return 0;
}
u8 MakeOnenetPayloadGPS(u8* Str,char *latstr,char * lonstr)//将温度与湿度封装成Onenet有效载荷包
{
char Payload[] = "{\"datastreams\":[{\"id\":\"location\",\"datapoints\":[{\"value\":{\"lon\":%s,\"lat\":%s}}]}]}";//onenet有效载荷固定格式
unsigned short StrLen;//有效载荷数据长度
memset(JsonBuf,0,256);
sprintf(JsonBuf,Payload,lonstr,latstr);//合成数据有效载荷封装
printf("JSONEND= %s \r\n",JsonBuf);
//printf("\r\n有效载荷(%d):[%s]\r\n",strlen(JsonBuf),(char *)JsonBuf);
StrLen = strlen(JsonBuf)/sizeof(char);//计算有效载荷的数据长度
//printf("\r\n有效载荷长度(%d)\r\n",StrLen);
Str[0] = '\x01';//报文第一个字节的固定显示
//printf("\r\nStr[0]=0X(%02X)\r\n",Str[0]);
//报文第2个字节和第三个字节表示有效载荷的长度的固定显示
Str[1] = (StrLen & 0XFF00) >>8;//高位
//printf("\r\nStr[1]=0X(%02X)\r\n",Str[1]);
Str[2] = StrLen & 0XFF;//低位
//printf("\r\nStr[2]=0X(%02X)\r\n",Str[2]);
//拷贝有效载荷数据到Str
memcpy(Str+3,JsonBuf,StrLen);//从第三个字节开始拷贝
return (StrLen+3);//返回长度
}
void CSTX_4G_ONENETIOTSenddataGPS(char *latstr,char *lonstr)//上发数据,上发的数据跟对应的插件有关系,用户需要注意插件然后对应数据即可
{
// int JsonLen= 0;
// memset(AtStrBuf,0,BUFLEN);
//
// Clear_Buffer(); //发送命令之前清空之前的模块反馈的数据
// sprintf(AtStrBuf,"AT+QMTPUB=0,0,0,0,\"$dp\"\r\n"); //发送ONENET命令
// printf("AtStrBuf = %s \r\n",AtStrBuf);
// Uart2_SendStr(AtStrBuf);//mqtt提交
// delay_ms(300);
// strx=strstr((const char*)AtRxBuffer,(const char*)">");//模块反馈可以发送数据了
// errcount=0;
// while(strx==NULL)
// {
// errcount++;
// strx=strstr((const char*)AtRxBuffer,(const char*)">");//模块反馈可以发送数据了
// delay_ms(30);
// if(errcount>100) //防止死循环跳出
// {
// errcount = 0;
// break;
// }
// }
// //命令发送成功下面就去发送数据了
// //Clear_Buffer();
// JsonLen = MakeOnenetPayloadGPS((u8*)Send_Json,latstr,lonstr); //组建CJSON数据
// USART2_Send((char *)Send_Json,JsonLen);//发送json 数据
// delay_ms(10);
// UART2_send_byte(0x1A);
// strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返发送成功
// errcount=0;
// while(strx==NULL)
// {
// errcount++;
// strx=strstr((const char*)AtRxBuffer,(const char*)"+QMTPUB: 0,0,0");//返发送成功
// delay_ms(10);
// if(errcount>100) //超时退出死循环 表示服务器连接失败
// {
// errcount = 0;
// break;
// }
// }
//
// Clear_Buffer();
}
4.2 定时器代码封装
#include "timer.h"
#include "led.h"
//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器3
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
int tnk = 0;
int timeSend = 0;
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
LED1=!LED1;//DS1翻转
tnk++;
if(tnk>10)
{
timeSend=1;
tnk = 0;
}
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
4.3 串口配置代码
#include "sys.h"
#include "usart.h"
u8 AtRxBuffer[512],Rxcouter;
//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART1_RX
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
}
void uart2_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //GPIOA2复用为USART2
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //GPIOA3复用为USART2
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA2与GPIOA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA2,PA3
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART2, &USART_InitStructure); //初始化串口2
USART_Cmd(USART2, ENABLE); //使能串口2
USART_ClearFlag(USART2, USART_FLAG_TC);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//串口2中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
}
//配置来对串口2波特率进行修改 和模块通信
void USART_Config(uint32_t baud)
{
USART_InitTypeDef USART_InitStructure;
USART_Cmd(USART2, DISABLE);
USART_InitStructure.USART_BaudRate =baud;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);
USART_Cmd(USART2, ENABLE);
}
//串口1接收中断
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
Res=Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
}
}
//串口2的接收中断函数
void USART2_IRQHandler(void) //串口2中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断
{
Res =USART_ReceiveData(USART2);//(USART1->DR); //读取接收到的数据
AtRxBuffer[Rxcouter++]=Res;//
}
}
void UART2_send_byte(char data)
{
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, data);
}
/*-------------------------------------------------*/
/*函数名:串口2 发送数组 */
/*参 数:bound:波特率 */
/*返回值:无 */
/*-------------------------------------------------*/
void USART2_Send(char *Data,uint16_t Len)
{
uint16_t i;
for(i=0; i<Len; i++)
{
UART2_send_byte(Data[i]);
}
}
//串口2的发送函数
void Uart2_SendStr(char*SendBuf) //串口2打印数据
{
while(*SendBuf)
{
while((USART2->SR&0X40)==0);//等待发送完成
USART2->DR = (u8) *SendBuf;
SendBuf++;
}
}
#endif
五、上位机开发
为了方便查看设备上传的数据,接下来利用Qt开发一款Android手机APP 和 Windows上位机。
使用华为云平台提供的API接口获取设备上传的数据,进行可视化显示,以及远程控制设备。
5.1 Qt开发环境安装
Qt的中文官网: https://www.qt.io/zh-cn/
QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6
打开下载链接后选择下面的版本进行下载:
qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details
软件安装时断网安装,否则会提示输入账户。
安装的时候,第一个复选框里勾选一个mingw 32
编译器即可,其他的不管默认就行,直接点击下一步继续安装。
选择MinGW 32-bit 编译器: (一定要看清楚了)
说明: 我这里只是介绍PC端,也就是Windows系统下的Qt环境搭建。 Android的开发环境比较麻烦,如果想学习Android开发,想编译Android程序的APP,需要自己去搭建Android环境。
也可以看下面这篇文章,不过这个文章是在Qt开发专栏里付费的,需要订阅专栏才可以看。 如果不想付费看,也可以自行找其他教程,自己搭建好必须的环境就行了
Android环境搭建的博客链接: https://blog.csdn.net/xiaolong1126626497/article/details/117254453
5.2 新建上位机工程
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
5.3 设计UI界面与工程配置
【1】打开UI文件
打开默认的界面如下:
【2】开始设计界面
根据自己需求设计界面。
5.5 编译Windows上位机
点击软件左下角的绿色三角形按钮进行编译运行。
编译之后的效果:
5.6 配置Android环境
如果想编译Android手机APP,必须要先自己配置好自己的Android环境。(搭建环境的过程可以自行百度搜索学习)
然后才可以进行下面的步骤。
【1】选择Android编译器
【2】创建Android配置文件
创建完成。
【3】配置Android图标与名称
【3】编译Android上位机
Qt本身是跨平台的,直接选择Android的编译器,就可以将程序编译到Android平台。
然后点击构建。
成功之后,在目录下可以看到生成的apk
文件,也就是Android手机的安装包,电脑端使用QQ
发送给手机QQ,手机登录QQ接收,就能直接安装。
生成的apk
的目录在哪里呢? 编译完成之后,在控制台会输出APK文件的路径。
知道目录在哪里之后,在Windows的文件资源管理器里,找到路径,具体看下图,找到生成的apk文件。
D:/linux-share-dir/QT/build-app_Huawei_Eco_tracking-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release/android-build//build/outputs/apk/debug/android-build-debug.apk
六、 BC26-NBIOT模块调试过程
6.1 模块调试接线
6.2 测试模块
第一步接上之后,串口调试助手选择波特率为115200,勾选软件上的发送新行选项。发送AT
过去,正常模块会返回OK
。
只有收到了OK,才表示模块工作正常。
6.3 上电初始化操作
【1】查询模块是否正常
AT
OK
【2】获取卡号,查询卡是否插好
AT+CIMI
460041052911195
OK
【3】激活网络
AT+CGATT=1
OK
【4】获取网络激活状态
AT+CGATT?
+CGATT: 1
OK
【5】查询网络质量
AT+CSQ
+CSQ: 26,0
OK
【6】 检查网络状态
AT+CEREG=? //
+CEREG: 0,1 //找网成功
OK
6.4 开启GPS定位
如果需要使用GPS定位就开,不需要使用就不用管。
使用GPS定位还需要将模块上的GPS天线接好,否则也是没有信号的。
官方文档:
【1】激活GPS,要等一段时间
AT+QGNSSC=1
OK
【2】查询激活状态,1表示成功激活
AT+QGNSSC?
+QGNSSC: 1
OK
【3】获取一次GPS定位语句
AT+QGNSSRD="NMEA/RMC"
+QGNSSRD: $GNRMC,120715.00,A,3150.78179,N,11711.93433,E,0.000,,310818,,,A,V*19
OK
七、总结
基于STM32F407、NBIOT模块BC26和华为云物联网云平台设计的环境检测系统是一个集环境数据采集、本地显示、远程监控和预警功能于一体的综合性系统。该系统通过STM32F407作为主控芯片,实现环境参数的采集与处理,包括使用SHT30传感器进行温湿度检测,MQ2传感器进行烟雾浓度检测,以及火焰检测传感器进行火光信息捕获。采集到的数据不仅能在本地通过0.96寸IIC协议OLED显示屏实时显示,还能通过NBIOT模块BC26实时上传到华为云物联网云平台,实现远程监控和数据存储。
当系统检测到环境中的烟雾浓度、温度或火光超过预设的阈值时,会立即触发高电平触发的有源蜂鸣器进行声光报警,以提醒用户注意环境安全。这种实时预警功能大大增强了系统的实用性和安全性。
为了使用户能够方便地获取环境检测数据并进行监控,系统还开发了基于Qt(C++)的Android手机APP。该APP能够从华为云物联网云平台获取设备最新的数据,并以直观的方式展示给用户,包括温度、湿度、烟雾报警和火光报警等信息。用户无需现场操作,即可通过手机APP随时随地了解环境状况,极大地提高了系统的便捷性和灵活性。
该环境检测系统通过整合先进的硬件设备和软件技术,实现了环境参数的实时监测、数据远程上传和预警功能,为用户提供了一个高效、便捷、安全的环境检测解决方案。无论是在家庭、办公室还是工业环境中,该系统都具有广泛的应用前景和实用价值。