• [获奖公告] 【云声·建议】10月提产品优化建议,领多重华为电子好礼
    云声建议反馈活动第10期来啦~本月仍可以选择累积兑换,与9月累积金额合并兑换最高可兑换华为WATCH GT 3手表更有华为FreeBuds 4E耳机、12000毫安移动电源等多重电子好礼还有限量开发者文化礼品等你解锁快叫上你的小伙伴一起来云声提优化建议吧~【活动时间】10月1日-10月30日【参与方式】在云声平台提交对华为云产品的优化建议【奖项设置】奖项激励金额备注有效建议总积分第1名500元开发者盲盒礼包有效建议数量不少于10条有效建议总积分第2-3名300元开发者盲盒礼包有效建议数量不少于6条有效建议总积分第4-10名100元开发者盲盒礼包有效建议数量不少于2条高价值优质建议奖100-200元开发者盲盒礼包数量不限,可与其他奖项叠加9~10月可选择累积兑换,兑换福利如下:礼包总金额不少于1400,可选择兑换华为WATCH GT 3(46mm)手表礼包总金额不少于700,可优先选择兑换华为FreeBuds 4E耳机礼包总金额不少于400,可优先选择兑换华为12000mh移动电源更多限量开发者文化礼品等你解锁~【活动规则】1.有效建议积分云声关联产品/功能分为云产品建议、解决方案建议、平台建议,三种类型的建议对应的分值不相同,云产品建议(1分)、解决方案建议(0.5分)、平台建议(0.1分),按照当月有效建议类型对应不同分值以及邀请加分项来进行计算总排名,关联产品/功能可在云声提建议页面查看,示例如下;说明:云声关联产品/功能分别对应以上3种类型,取决于建议内容实际对应的产品/功能,与实际关联的标签无关。2.高价值优质建议奖1)经内部产品研发团队评估为高价值优质建议奖的用户,额外奖励100-200元开发者盲盒礼包/人说明:高价值优质建议要求建议对云产品功能及优化改进有重要作用,优先从已被采纳的建议的选择;建议内容需要表述清晰,有明确的建议方案,最好有操作截图或链接等能进一步详细描述;高价值建议数量不限,且与有效建议积分奖可叠加,每位用户每月最多可获得一次。3.注意事项1)若出现积分相同且排名一致的情况,结合已实现和已采纳建议情况,会由内部技术专家选出价值更高的建议用户给予奖励。2)同一用户在同一页面(文档)提出的同一类问题(包括但不限于错别字、语句不通顺、视觉体验等),在通过审核后仅算作一条有效建议数3)若发现代他人提交优化建议,此建议分值只取原分值30%;若发现2次及以上重复提交他人建议,或3次及以上重复提交体验类相关建议进行恶意刷量(包括但不限于错别字、语句不通顺、视觉体验等),取消本人活动参与资格;4)以上激励价值为实物礼品价值,兑换礼品以仓库现有实物礼品为准,不可以指定,如遇商品缺货,将随机换成其他等价值礼品发放5)活动结束后,7个工作日内会公示获奖结果,30个工作日内完成礼品邮寄
  • [技术干货] 基于STM32+微波雷达设计的非接触式睡眠监控系统(服务器采用华为云IOT)
    一、前言1.1 项目介绍项目设计里用到的全部工具软件都可以在这里下载。cid:link_2【1】项目开发背景随着现代生活节奏的加快,人们对于健康管理的需求日益增长,尤其是对于睡眠健康的关注度显著提升。良好的睡眠质量不仅关系到个人的精神状态,更直接影响着工作和学习效率乃至整体生活质量。然而,快节奏的生活压力、不规律的生活作息等因素导致越来越多的人遭受睡眠障碍的困扰。传统的睡眠监测方式通常需要佩戴设备或接触式传感器,这可能会干扰到用户的自然睡眠状态,从而影响监测结果的准确性。因此,开发一种非接触式的睡眠监测系统,成为了提高睡眠质量研究的重要方向之一。非接触式睡眠监测技术的发展,得益于近年来毫米波雷达技术的进步。毫米波雷达具有高精度、强穿透力的特点,可以在不直接接触人体的情况下,精准地捕捉到人体微动,如呼吸和心跳等细微动作。这种技术的应用,不仅可以避免传统监测手段可能带来的不适感,还能在用户不知情的状态下进行连续监测,保证了数据的真实性和有效性。与此同时,物联网技术的发展使得数据的远程传输与分析成为可能,进一步推动了智能健康监测系统的普及。本项目正是基于这样的背景下展开的。它利用了60GHz毫米波雷达技术,结合高性能的STM32微控制器,设计了一套完整的非接触式睡眠监控系统。该系统不仅能准确地获取用户的睡眠信息,还能通过Wi-Fi连接云端,让用户可以通过手机应用程序随时查看自己的睡眠报告。此外,系统还具备异常生理指标报警功能,能够在第一时间提醒用户注意健康状况,为用户提供了一个全方位、智能化的健康管理方案。通过这一创新性的解决方案,期望能够帮助更多人改善睡眠质量,提升生活质量。设备安装角度:【2】设计实现的功能(1)人体存在感知与运动感知:通过使用60GHz频段的毫米波雷达模块,系统能够感知房间内是否存在人体以及人体的微小运动,如呼吸和心跳的变化。(2)睡眠状态监测:系统能够根据睡眠过程中身体的运动幅度变化和呼吸心率的变化,实时判断目标的睡眠状态,并在睡眠周期结束后提供一个综合的睡眠评分。(3)生理指标检测:系统能够检测并记录睡眠者的心率、呼吸频率等重要生理指标,这些数据有助于分析睡眠质量。(4)远程数据上传与查看:系统集成了Wi-Fi模块,可以将监测到的睡眠数据上传到华为云物联网平台,用户可以通过智能手机应用程序远程查看每天的睡眠质量报告和其他生理指标。(5)异常情况报警:当检测到的生理指标超出预设的安全阈值时,系统会触发报警机制,及时通知用户或监护人可能存在健康风险。(6)本地数据显示:系统配备了1.44寸SPI协议的TFT LCD显示屏,用于实时显示监测到的生理指标及环境相关信息,便于用户即时查看。(7)体温检测:通过集成MLX90614红外体温传感器,系统能够检测人体体温,并将其作为一项重要的生理参数纳入睡眠质量评估体系中。【3】项目硬件模块组成(1)主控单元:选用STM32F103RCT6微控制器作为核心处理单元,负责接收来自各传感器的数据,并处理和控制系统的各项功能。(2)毫米波雷达模块:采用60GHz频段的R60ABD1毫米波雷达模块,用于非接触式地检测人体的存在、呼吸频率和心率等生理信号。(3)无线通信模块:集成ESP8266-Wi-Fi模块,实现数据的无线传输功能,确保睡眠数据能够实时上传至华为云物联网平台。(4)显示模块:采用1.44寸TFT LCD显示屏,分辨率为128x128像素,通过SPI协议与主控单元通讯,用于显示监测到的生理指标和环境信息。(5)体温检测模块:采用MLX90614红外体温传感器,用于无接触地测量人体体温,提供额外的健康监测数据。(6)电源管理模块:采用外置的5V稳压电压,包括电源转换电路和电池管理电路,确保整个系统能够稳定运行,并为各个模块提供所需电压。(7)报警模块:设计蜂鸣器声音形式的报警装置,当系统检测到异常生理指标时,能够及时提醒用户。【4】需求总结项目:基于STM32+微波雷达设计的非接触式睡眠监控系统​1. 可以实现 人体存在感知、人体运动感知、根据睡眠过程中的身体运动幅度变化和呼吸心率变化,对目标的睡眠状态、呼吸心跳频率进行实时判断,在一段睡眠过程结束后输出睡眠评分呼吸、能够检测心率、睡眠时长、睡眠质量等生理指标(此功能采用60GHz频段的毫波雷达来实现)2. 可以实现能将数据通过 ESP8266-WIFI上传到华为云物联网云平台、设计手机APP可以远程查看每天的睡眠质量、生理指标、环境相关信息。3. 可以实现当检测到的生理指标数据超过阈值时,系统发出报警提醒。 4. 可以实现能在本地LCD显示屏显示监测到的生理指标、环境相关信息。5. 支持检测人体体温。​ 硬件选型:主控芯片选择 STM32F103RCT6LCD显示屏采用1.44寸 SPI协议的 TFT显示屏,分辨率是128x128。人体体温检测采用MLX90614红外体温传感器。人体的呼吸、心率、采用60G毫米波 生物感知雷达R60ABD1模块来实现检测。呼吸睡眠雷达基于毫米波雷达体制实现人体生物存在感知及人体运动感知,持续记录人体存在情况,根据睡眠过程中的身体运动幅度变化和呼吸心率变化,对目标的睡眠状态、呼吸心跳频率进行实时判断,在一段睡眠过程结束后输出睡眠评分,根据相关睡眠参数的输出结合到健康康养的应用上。1.2 设计思路设计思路源于对现代人睡眠健康需求的关注以及对现有睡眠监测技术局限性的思考。在设计之初,注意到传统的睡眠监测手段往往依赖于接触式的穿戴设备,这种方式虽然能够提供较为精确的数据,但却有可能影响用户的自然睡眠状态。因此,设计目标是创造一个非侵入式的睡眠监控系统,能够让用户在自然的睡眠环境中得到准确而有效的监测。为了实现这一目标,选择了60GHz频段的毫米波雷达技术作为主要的监测手段。毫米波雷达具有非接触、高分辨率和强穿透性等特点,非常适合用来监测人体微弱的生理信号,如呼吸和心跳。通过算法优化,能够从雷达回波中提取出稳定的呼吸和心跳信号,并据此评估睡眠质量和生理指标。考虑到用户体验的重要性,决定将系统与互联网技术相结合,通过ESP8266-Wi-Fi模块将睡眠数据上传至云端,方便用户通过智能手机应用程序随时随地查看自己的睡眠报告。同时,为了应对突发状况,设计了阈值报警机制,当检测到异常生理指标时,系统能够立即向用户发出警告,以确保用户的安全。硬件选型方面,选择了性能稳定且广泛使用的STM32F103RCT6作为主控芯片,以确保系统的可靠性和可扩展性。为了直观展示数据,选用了1.44寸的TFT LCD显示屏,它可以清晰地显示监测到的各项生理指标和环境信息。此外,还加入了MLX90614红外体温传感器,以便系统能够监测用户的体温变化,进一步完善健康监测功能。总体的设计思路是在不干扰用户正常生活的情况下,利用先进的毫米波雷达技术和物联网平台,创建一个能够全天候监测睡眠状态、生理指标,并及时反馈给用户的智能系统。这样不仅能够帮助用户更好地了解自己的睡眠质量,还能在出现异常时提供及时的帮助,从而提升整体的生活品质。1.3 系统功能总结功能类别描述人体存在感知利用60GHz毫米波雷达检测房间内是否有人存在。运动感知感知人体的微小运动,如呼吸和心跳。睡眠状态监测根据身体运动幅度变化和呼吸心率变化实时判断睡眠状态。生理指标检测记录并分析心率、呼吸频率等重要生理指标。远程数据上传通过ESP8266-Wi-Fi模块将监测数据上传至华为云物联网平台。移动端查看用户可以通过手机应用程序远程查看睡眠质量报告和其他生理指标。异常报警当检测到的生理指标超过设定阈值时,系统会发出报警提醒。本地数据显示通过1.44寸TFT LCD显示屏实时显示监测到的生理指标和环境信息。体温检测使用MLX90614红外体温传感器检测人体体温,并将其纳入健康监测数据中。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)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。1.5 模块的技术详情介绍【1】ESP8266-WIFI模块ESP8266是一款广受欢迎的低成本、低功耗的Wi-Fi模块,广泛应用于物联网(IoT)领域。它由乐鑫科技(Espressif Systems)开发,最初作为一款简单易用的无线模块推向市场,但因其强大的功能和灵活性迅速获得了开发者们的青睐。ESP8266内置了Tensilica L106超低功耗32位微处理器,主频最高可达160MHz,并且拥有512KB的SRAM,这使得它不仅能够作为一个简单的Wi-Fi模块使用,还可以作为独立的微控制器来执行复杂的任务。ESP8266模块支持IEEE 802.11 b/g/n标准,能够工作在2.4GHz频段上。它具有多种工作模式,包括Station模式(客户端)、Access Point模式(热点)以及Station+AP模式(同时作为客户端和热点)。这意味着它可以连接到现有的Wi-Fi网络,也可以自己创建一个Wi-Fi热点供其他设备连接,极大地增加了其在不同应用场景中的适用性。对于开发者而言,ESP8266的一个重要优势在于其丰富的开发资源和支持。乐鑫科技提供了详细的开发文档,包括硬件接口说明、固件升级指南和API参考手册等。此外,ESP8266还支持多种编程语言,如C/C++和Lua,同时还有成熟的开发框架如Arduino IDE的支持,使得开发者能够快速上手,并利用各种库函数简化开发流程。ESP8266的低功耗特性也是一大亮点,它提供了多种省电模式,可以根据实际应用需求调整工作状态,以延长电池寿命。这对于那些依赖电池供电的物联网设备来说尤为重要。ESP8266凭借其出色的性价比、强大的功能、易于开发的特性以及广泛的社区支持,已经成为许多DIY项目、智能家居设备和小型物联网应用的理想选择。无论是作为独立的微控制器还是作为Wi-Fi模块,ESP8266都能够满足大多数物联网项目的需求。【2】MLX90614红外体温传感器MLX90614红外体温传感器是由Melexis公司生产的一款高性能、非接触式温度测量传感器。这款传感器集成了红外温度测量功能与环境温度测量功能于一体,适用于需要快速、准确测量物体表面温度的应用场合。由于其非接触式的特点,MLX90614特别适合用于医疗领域,如监测人体体温,以及其他工业或商业用途,例如食品温度检测、设备过热保护等。MLX90614的工作原理基于红外辐射理论。所有物体都会发射红外辐射,其强度与物体的温度成正比。MLX90614通过检测目标物体发射的红外辐射能量,并结合传感器所在环境的温度,计算出目标物体的表面温度。这款传感器具有较高的灵敏度,能够检测到非常微小的温度变化,并且具有较好的响应速度。从硬件角度来看,MLX90614采用了SMD(Surface Mount Device)封装,使其易于集成到各种设备中。它具有数字I²C接口,可以方便地与微控制器或其他数字系统进行通信。此外,MLX90614还提供了不同的视场角版本,允许用户根据具体的应用需求选择最适合的角度,从而获得最准确的测量结果。在使用MLX90614时,需要注意几个关键参数。首先是距离系数(Distance-to-Spot Size Ratio),即传感器与目标之间的距离与目标面积直径之比。这个参数决定了传感器的有效测量区域大小。其次是传感器的温度测量范围,一般为-70°C至+380°C,足以覆盖大部分日常应用。此外,MLX90614还具备较高的测温精度,通常在±0.5°C左右,这使得它在医疗和工业应用中具有很高的实用性。对于开发人员来说,MLX90614的另一个优点是其易于集成。Melexis提供了详尽的技术文档和支持,包括电路设计指南、编程示例等资源,使得开发者能够快速地将MLX90614集成到他们的产品中。此外,市面上也有许多现成的开发板和库文件,可以帮助开发者简化开发流程,加速产品的上市时间。综上所述,MLX90614红外体温传感器以其高精度、非接触式测量的特点,在多个行业中得到了广泛应用,尤其是在需要快速、准确温度读数的场合。【3】微波雷达模块生物感知雷达R60ABD1模块是一款基于60GHz毫米波雷达技术的产品,专为人体呼吸心率感知及睡眠评估而设计。它采用FMCW(调频连续波)雷达体制,能够针对特定场合内的人员进行呼吸心率频率的输出,并结合长时间的睡眠姿态体动采集,及时上报人员的睡眠状态和历史记录。模块的一发三收天线形式使得它适合于置顶安装模式,能够精准扫描人体全身的动作层析,实现人体动静态时的睡眠探测和不同姿态下的呼吸心率采集。该模块的工作原理基于雷达天线发射电磁波信号,并接收目标反射回来的回波信号。通过雷达处理器解析不同接收天线回波信号的波形参量之间的相位差和能量变化,从而反馈目标运动的微动能量变化、距离、方向和速度等信息。这使得R60ABD1模块能够探测目标的运动状态和胸腔呼吸起伏的频次状态。在雷达探测范围内,即便是轻微的手部晃动或呼吸引起的胸腔起伏等微小运动,也能够被模块捕捉到。R60ABD1模块具有多种功能,包括运动检测、呼吸探测、呼吸心率频率采集等功能。模块能够检测到诸如走动或小幅度手晃动等运动,并触发有人状态的指示。当人处于静止状态时,模块也能检测到由呼吸引起的胸腔起伏,并维持有人状态的输出。更重要的是,它能统计呼吸心跳引起的胸腔起伏,并输出每分钟的呼吸心跳数值。这些功能使得该模块在全屋智能、智能家电、区域人员探测和睡眠看护等领域有着广泛的应用前景。该模块的电气特性包括工作电压在4.6V至6V之间,典型工作电流为93mA,工作温度范围从-20°C至+60°C,存储温度范围则从-40°C至+105°C。其RF性能方面,工作频率位于61GHz至61.5GHz区间,发射功率不超过6dBm。天线增益为4dBi,水平和垂直波束宽度均为20°(-3dB点)。R60ABD1模块提供了标准的UART通信接口,并支持涂鸦协议,便于与其他设备集成。模块尺寸小巧,体积仅为35mm×31mm×7.5mm,并配有双排插针接口,接口间距为2.0mm。这些接口包括电源输入、地、串口接收和发送端、以及多个可定义的通用I/O引脚。其中,部分引脚可用于输出有人/无人状态、活跃/静止状态、体征参数等信息。此外,模块还支持多种参数设置,如人体存在开关、呼吸探测开关、心跳探测开关、睡眠探测开关以及探测模式切换开关(实时探测/睡眠模式)。这些设置使得模块可以根据不同应用场景的需求进行灵活配置。在安装方面,R60ABD1模块推荐倾斜安装,并且平行于扫描面的距离不超过1.5米。特别是用于睡眠呼吸心跳探测时,雷达应安装在床头正上方1米的高度,向下倾斜45°对着床中间,确保雷达与人体胸腔的距离在1.5米范围内,以确保雷达正常进行探测。1.6 微波雷达安装说明生物感知雷达R60ABD1模块的安装需要遵循特定的指导原则以确保其最佳性能。首先,雷达模块应该朝向为丝印标识的方向进行安装,这意味着在安装时需要确保雷达的正面朝向正确。为了达到理想的探测效果,R60ABD1雷达模块建议采用倾斜安装的方式,倾斜角度应在30到45度之间。这种安装方式有助于雷达的波束覆盖到所需的探测区域,并且可以减少因环境因素引起的误报。在确定雷达的具体安装位置时,建议将雷达安装在床头正上方大约1米的高度处,这样可以确保雷达的主要波束能够覆盖到床的中心区域。这样做是为了确保雷达可以有效地探测到床上人的呼吸和心跳活动,同时也能够监测到人体的其他微动。安装高度的选择也是基于雷达波束覆盖范围的考虑,以确保人体存在检测的最大距离为约2.5米,而人体呼吸频率检测的最大距离约为1.5米。除了正确的安装位置和角度外,还需要注意避免雷达前方出现明显的金属或电解质遮挡物。这是因为毫米波雷达的探测机制依赖于雷达波的反射,如果存在金属或电解质遮挡物,则可能会影响雷达波的反射路径,从而影响到雷达的探测准确性。因此,在安装雷达时,应确保雷达前方没有诸如金属窗帘条、风扇、空调电机等潜在的干扰源。在实际安装过程中,还应当注意雷达模块的安装高度和角度会影响到其探测效果。为了使雷达的主波束能够覆盖到整个睡眠区域,雷达的安装高度应该保持在与床面的高度差在0.9米左右,误差不超过0.2米。同时,雷达模块的安装需要保证其前方没有明显的遮挡物,尤其是金属材质的物体,因为这些物体可能会反射雷达波,造成干扰。为了确保雷达能够正常工作,安装完成后还需注意雷达模块的供电稳定性。雷达模块对电源品质有一定的要求,需要无门限毛刺或纹波现象,并且需要有效屏蔽来自附近设备的电源噪声。为了保证模块内部VCO电路的正常工作,雷达模块需要+5V到+6V的供电,且电压纹波不能超过100mV。外部电源还需要提供足够的电流输出能力和瞬态响应能力,以防止由于电源不稳定导致的探测距离缩短或误报率增加等问题。1.7 微波雷达的完整功能概述(快速上手)R60ABD1呼吸睡眠雷达模组是一款基于60GHz毫米波雷达技术设计的非接触式生物感知设备,主要用于人体存在感知及运动感知。它能够根据睡眠过程中身体运动幅度变化和呼吸心率变化,实时判断目标的睡眠状态、呼吸心跳频率,并在睡眠结束后输出睡眠评分。该模组的探测功能不受温度、湿度、噪声气流、尘埃、光照和人体完全静止等因素的影响,适合安装在室内顶部使用。模组具备多种功能,包括有人/无人状态检测、人体静止/活跃状态切换检测、人体距离主动上报、体动幅度参数输出、人体方位上报、心跳数值及波形输出、呼吸数值及波形输出、入床/离床状态判断、睡眠状态(清醒/浅睡/深睡)识别、清醒/浅睡/深睡时长统计、睡眠质量评分、睡眠异常上报、异常挣扎上报、无人计时上报以及睡眠质量评级上报等。这些功能通过不同的数据点(DP)以特定的时间间隔或状态变化时上报。为了确保雷达的准确探测,模组的安装需要遵循一定的规范。雷达应该安装在床头正上方1米的高度,向下倾斜30至45度,以确保主波束能够覆盖到睡眠区域。此外,雷达前方不应有明显的金属或电解质遮挡物,以免影响探测效果。在安装过程中,还需确认雷达探测范围内是否存在干扰源,如空调、风扇等,并尽可能移除这些干扰源。模组的引脚包括电源输入、地、串口接收与发送端、以及若干备用扩展引脚。其中,部分引脚可以根据用户需求重新定义功能。为了便于用户操作,在官方的文档还介绍了如何准备必要的工具,如TTL串口工具、杜邦线、PC电脑、串口助手终端和Radar-EVB demo板,并给出了上电及工作的数据上报规则和睡眠模式检测逻辑。在官方的文档最后提供了主要功能测试指引,包括睡眠质量状态判断测试、入离床状态判断测试、离床状态判断测试、呼吸频率测试以及心跳频率测试等。每项测试都有明确的操作步骤和判定标准,以帮助用户验证模组的功能是否正常。此外,官方的文档还对体动幅度参数的输出进行了详细说明,并附带了相关的测试表格格式,便于用户记录和分析测试结果。二、微波雷达调试过程2.1 接线说明2.2 安装说明倾斜安装: * 确保雷达探测准确性,建议安装在床头上方,以 45°斜向下安装! R60ABD1-呼吸睡眠雷达倾斜安装,倾斜角度为 30~45°,安装在床头上方,雷达安装高度建议为高于床面 0.8-1m;保证雷达主波束覆盖探测区域;雷达前面无明显(金属/电解质)遮挡物及覆盖物。受雷达安装高度及雷达波束范围影响,在该安装模式下,人体存在检测最大距离 L3 ≈ 2.5 米;睡眠检测最大距离 L2 ≈ 2.5 米;人体呼吸频率检测最大距离 L1 ≈ 1.5 米。2.3 连接电脑调试将60G毫米波雷达模块与电脑连接,调试模块是否正常可以运行,60G毫米波雷达模块默认的波特率是115200GND-----GNDVCC-----VCCTX------RXRX------TX串口调试助手返回的数据:睡眠雷达上位机:三、华为云服务器部署与上位机APP开发这里直接看视频,可以了解的更加清楚。(1)华为云物联网开发(一)设备上云:cid:link_0(2)华为云物联网云平台对应的上位机开发步骤:cid:link_1四、STM32代码开发(微波雷达模块数据处理)4.1 微波雷达数据处理(头文件)#ifndef _DATAHANDLE_H#define _DATAHANDLE_H​#include "stdint.h"​/* 定义包头及指令信息 *///帧头#define HEADER1 0x53#define HEADER2 0x59//控制字#define CMD_TICK 0x01 //心跳包#define CMD_PRODUCT_INFO 0x02 //产品信息#define CMD_OTA 0x03 // OTA升级#define CMD_WORK_STATE 0x05 //工作状态#define CMD_RADAR_DETECT_RANGE 0x07 //雷达探测范围#define CMD_BODY_EXIST_DETECT 0x80 //人体存在检测#define CMD_BREATH_DETECT 0x81 //呼吸检测#define CMD_SLEEP_DETECT 0x84 //睡眠检测#define CMD_HEART_DETECT 0x85 //心率检测//帧尾#define END1 0x54#define END2 0x43​/* 枚举读取数据报文的状态 */typedef enum{ IDLE, SEEN_HEADER1, SEEN_HEADER2, SEEN_CONTROL, SEEN_COMMAND, SEEN_LENGTH, SEEN_DATA, SEEN_SUM, SEEN_END1, SEEN_END2} rx_datagram_state_t;​/* 枚举控制模式 */typedef enum{ MODE_IDLE, MODE_SEND_TICK, MODE_SEND_PRODUCT_INFO, MODE_SEND_OTA, MODE_SEND_WORK_STATE, MODE_SEND_RADAR_DETECT_RANGE, MODE_SEND_BODY_EXIST_DETECT, MODE_SEND_BREATH_DETECT, MODE_SEND_SLEEP_DETECT, MODE_SEND_HEART_DETECT} control_mode_t;​/*******************************************************************************///人体存在功能typedef struct{ uint8_t body_exist_flag; //有人无人检测标志 uint8_t work_state; //运动状态 uint8_t body_move_param; //体动参数 uint16_t body_distance; //人体距离 uint8_t body_direction[3]; //人体方位} body_exist_detect_t;​//呼吸检测功能typedef struct{ uint8_t breath_detect_switch; //开关呼吸功能 uint8_t breath_detect_state; //呼吸检测状态 uint8_t breath_detect_value; //呼吸检测值 uint8_t breath_wave_data[5]; //呼吸波形} breath_detect_t;​//睡眠评分typedef struct{ uint8_t sleep_detail_exist; //睡眠详细状态 uint8_t sleep_detail_state; //睡眠详细评分​ uint8_t sleep_detail_score; //睡眠评分 uint16_t sleep_detail_time; //睡眠时间 uint8_t sleep_detail_awake; //清醒时长占比 uint8_t sleep_detail_light; //浅睡时长占比 uint8_t sleep_detail_deep; //深睡时长占比 uint8_t sleep_detail_away; //离床时长占比 uint8_t sleep_detail_away_times; //离床次数 uint8_t sleep_detail_turn_over_times; //翻身次数 uint8_t sleep_detail_avg_breath; //平均呼吸 uint8_t sleep_detail_avg_heart; //平均心率 uint8_t sleep_detail_breath_stoptimes; //呼吸停顿次数 uint8_t sleep_detail_turn_over_L; //大动作次数 uint8_t sleep_detail_turn_over_S; //小动作次数} sleep_detail_t;​//睡眠检测功能typedef struct{ uint8_t sleep_detect_switch; //开关睡眠功能 uint8_t sleep_bed_state; //入床/离床状态 uint8_t sleep_detect_state; //睡眠检测状态​ uint8_t sleep_wake_hour; //清醒时间 uint8_t sleep_light_hour; //浅睡时长 uint8_t sleep_deep_hour; //深睡时长​ uint8_t sleep_score; //睡眠质量评分 sleep_detail_t sleep_score_detail; //睡眠质量评分详情 sleep_detail_t sleep_score_detail_1; //睡眠质量评分详情1 uint8_t sleep_score_detail_err; //睡眠质量评分详情2} sleep_detect_t;​//心率检测功能typedef struct{ uint8_t heart_detect_switch; //开关心率功能 uint8_t heart_detect_value; //心率检测值 uint8_t heart_wave_data[5]; //心率波形} heart_detect_t;​//数据变化标志typedef struct{ unsigned data_change_body_exist : 1; unsigned data_change_breath : 1; unsigned data_change_sleep : 1; unsigned data_change_heart : 1;} data_change_t;​void ProcessRx(uint8_t *buff,uint8_t size);// void ProcessRx(void);​​extern body_exist_detect_t body_exist_detect;extern breath_detect_t breath_detect;extern sleep_detect_t sleep_detect;extern heart_detect_t heart_detect;​#endif​4.2 微波雷达数据处理(源文件)#include "datahandle.h"#include "debug.h"body_exist_detect_t body_exist_detect = {0};breath_detect_t breath_detect = {0};sleep_detect_t sleep_detect = {0};heart_detect_t heart_detect = {0};data_change_t data_change = {0};​/* 定义数据包接收状态的变量,并初始化为空闲状态 */rx_datagram_state_t rx_datagram_state = IDLE;control_mode_t control_mode = MODE_IDLE;​/* 协议数据处理函数 */void ProcessRx(uint8_t *buff, uint8_t size){ /****************************************************************************/ uint8_t receivedbyte, rx_sum, command; //数据存储数组 uint8_t rx_data[10];​ uint16_t rx_dategram_len; while (size) { switch (rx_datagram_state) { case IDLE: //在空闲时,判断是否读取帧头1 { receivedbyte = *buff; if (HEADER1 == receivedbyte) { // printf("HEADER1:%x\n", *buff); rx_sum = 0; rx_sum += receivedbyte; rx_datagram_state = SEEN_HEADER1; buff++; } break; } case SEEN_HEADER1: //读取第一帧之后,判断是否读取帧头2 { receivedbyte = *buff; if (HEADER2 == receivedbyte) { // printf("HEADER2:%x\n", *buff); rx_sum += receivedbyte; rx_datagram_state = SEEN_HEADER2; buff++; } break; }​ case SEEN_HEADER2: //读取第二帧后,根据控制字判断数据模式 { uint8_t ctrl_mode = *buff; //控制字 rx_sum += ctrl_mode;​ if (ctrl_mode == CMD_TICK) control_mode = MODE_SEND_TICK; else if (ctrl_mode == CMD_PRODUCT_INFO) control_mode = MODE_SEND_PRODUCT_INFO; else if (ctrl_mode == CMD_OTA) control_mode = MODE_SEND_OTA; else if (ctrl_mode == CMD_WORK_STATE) control_mode = MODE_SEND_WORK_STATE; else if (ctrl_mode == CMD_RADAR_DETECT_RANGE) control_mode = MODE_SEND_RADAR_DETECT_RANGE; else if (ctrl_mode == CMD_BODY_EXIST_DETECT) control_mode = MODE_SEND_BODY_EXIST_DETECT; else if (ctrl_mode == CMD_BREATH_DETECT) control_mode = MODE_SEND_BREATH_DETECT; else if (ctrl_mode == CMD_SLEEP_DETECT) control_mode = MODE_SEND_SLEEP_DETECT; else if (ctrl_mode == CMD_HEART_DETECT) control_mode = MODE_SEND_HEART_DETECT; else control_mode = MODE_IDLE;​ rx_datagram_state = SEEN_CONTROL; // printf("SEEN_CONTROL:%x\n", ctrl_mode); buff++; } break;​ case SEEN_CONTROL: //读取控制字后,判断命令字 { command = *buff; //命令字 rx_sum += command; rx_datagram_state = SEEN_COMMAND; // printf("SEEN_COMMAND:%x\n", command); buff++; } break;​ case SEEN_COMMAND: //读取命令字后,识别数据长度 { uint8_t len_temp[2]; len_temp[0] = *buff; rx_sum += len_temp[0];​ buff++; len_temp[1] = *buff; rx_sum += len_temp[1];​ rx_dategram_len = (len_temp[0] << 8) | len_temp[1]; rx_datagram_state = SEEN_LENGTH; // printf("SEEN_LENGTH:%x\n", rx_dategram_len); buff++; } break;​ case SEEN_LENGTH: //读取数据长度后,保存数据 { if (size < (int)rx_dategram_len) //判断数据包是否完整 { rx_datagram_state = IDLE; return; } uint8_t readlen = rx_dategram_len; //数据包长度 uint8_t tmp[rx_dategram_len]; //数据包缓存 uint8_t *ptmp = tmp; //数据包缓存指针 while (readlen--) { receivedbyte = *buff; *ptmp++ = receivedbyte; //将数据存入缓存 rx_sum += receivedbyte; //校验和 } // TODO 使用上面操作,可以直接操作rx_data指针,不用拷贝数据到rx_data数组中 for (uint8_t i = 0; i < rx_dategram_len; i++) //将数据存储到数组中 { rx_data[i] = tmp[i]; }​ rx_datagram_state = SEEN_DATA; buff++; } break;​ case SEEN_DATA: //读取数据后,判断校验和,根据数据、控制字、命令字读取状态 { uint8_t getsum = *buff;​ //判断校验和是否正确 if (getsum != rx_sum) { rx_datagram_state = IDLE; return; } else { //判断控制字模式 switch (control_mode) { case MODE_SEND_TICK: //心跳包 { printf("MODE_SEND_TICK\n"); } break;​ case MODE_SEND_PRODUCT_INFO: //产品信息 { printf("MODE_SEND_PRODUCT_INFO\n"); } break;​ case MODE_SEND_OTA: // OTA升级 { printf("MODE_SEND_OTA\n"); } break;​ case MODE_SEND_WORK_STATE: //工作状态 { printf("MODE_SEND_WORK_STATE\n"); } break;​ case MODE_SEND_RADAR_DETECT_RANGE: //雷达检测范围 { printf("MODE_SEND_RADAR_DETECT_RANGE\n"); } break;​ case MODE_SEND_BODY_EXIST_DETECT: //人体存在检测 { data_change.data_change_body_exist = 1; switch (command) { case 1: { //检测人体存在 body_exist_detect.body_exist_flag = (0 != rx_data[0]) ? 1 : 0; printf("Body Exist flag State:%d\n", body_exist_detect.body_exist_flag); } break;​ case 2: { //运动状态 body_exist_detect.work_state = rx_data[0]; printf("Move State:%d\n", body_exist_detect.work_state); } break;​ case 3: { //运动值 body_exist_detect.body_move_param = rx_data[0]; printf("Body Move Value:%d\n", body_exist_detect.body_move_param); } break;​ case 4: { //人体距离 body_exist_detect.body_distance = (rx_data[0] << 8) | rx_data[1]; printf("Body Distance:%d\n", body_exist_detect.body_distance); } break;​ case 5: { //人体方位 body_exist_detect.body_direction[0] = (rx_data[0] << 8) | rx_data[1]; body_exist_detect.body_direction[1] = (rx_data[2] << 8) | rx_data[3]; body_exist_detect.body_direction[2] = (rx_data[4] << 8) | rx_data[5]; } break; } } break;​ case MODE_SEND_BREATH_DETECT: //呼吸检测 { data_change.data_change_breath = 1; switch (command) { case 1: { //呼吸检测状态 breath_detect.breath_detect_state = rx_data[0]; printf("Breath State:%d\n", breath_detect.breath_detect_state); } break;​ case 2: { //呼吸值 breath_detect.breath_detect_value = rx_data[0]; printf("Breath Value:%d\n", breath_detect.breath_detect_value); } } } break;​ case MODE_SEND_SLEEP_DETECT: //睡眠检测 { data_change.data_change_sleep = 1; //判断命令字 switch (command) { case 1: { //入床/离床状态 sleep_detect.sleep_bed_state = rx_data[0]; printf("Sleep1 State:%d\n", sleep_detect.sleep_bed_state); } break;​ case 2: { //睡眠状态 sleep_detect.sleep_detect_state = rx_data[0]; printf("Sleep State:%d\n", sleep_detect.sleep_detect_state); } break;​ case 3: { //清醒时长 sleep_detect.sleep_wake_hour = (rx_data[0] << 8) | rx_data[1]; printf("Sleep Wake Hours:%d\n", sleep_detect.sleep_wake_hour); } break;​ case 4: { //浅睡时长 sleep_detect.sleep_light_hour = (rx_data[0] << 8) | rx_data[1]; printf("Sleep Light Hours:%d\n", sleep_detect.sleep_light_hour); } break;​ case 5: { //深睡时长 sleep_detect.sleep_deep_hour = (rx_data[0] << 8) | rx_data[1]; printf("Sleep Deep Hours:%d\n", sleep_detect.sleep_deep_hour); } break;​ case 0x06: { //睡眠质量评分 sleep_detect.sleep_score = rx_data[0]; printf("Sleep Score:%d\n", sleep_detect.sleep_score); } break;​ case 0x0c: { //睡眠检测结果 sleep_detect.sleep_score_detail.sleep_detail_exist = rx_data[0]; sleep_detect.sleep_score_detail.sleep_detail_state = rx_data[1]; sleep_detect.sleep_score_detail.sleep_detail_avg_breath = rx_data[2]; sleep_detect.sleep_score_detail.sleep_detail_avg_heart = rx_data[3]; sleep_detect.sleep_score_detail.sleep_detail_turn_over_times = rx_data[4]; sleep_detect.sleep_score_detail.sleep_detail_turn_over_L = rx_data[5]; sleep_detect.sleep_score_detail.sleep_detail_turn_over_S = rx_data[6]; sleep_detect.sleep_score_detail.sleep_detail_breath_stoptimes = rx_data[7]; } break;​ case 0x0d: { //睡眠详情 sleep_detect.sleep_score_detail_1.sleep_detail_score = rx_data[0]; sleep_detect.sleep_score_detail_1.sleep_detail_time = (rx_data[1] << 8) | rx_data[2]; sleep_detect.sleep_score_detail_1.sleep_detail_awake = rx_data[3]; sleep_detect.sleep_score_detail_1.sleep_detail_light = rx_data[4]; sleep_detect.sleep_score_detail_1.sleep_detail_away = rx_data[5]; sleep_detect.sleep_score_detail_1.sleep_detail_away_times = rx_data[6]; sleep_detect.sleep_score_detail_1.sleep_detail_avg_breath = rx_data[7]; sleep_detect.sleep_score_detail_1.sleep_detail_avg_heart = rx_data[8]; sleep_detect.sleep_score_detail_1.sleep_detail_breath_stoptimes = rx_data[9]; } break;​ case 0x0e: { //异常检测 sleep_detect.sleep_score_detail_err = rx_data[0]; } break; } } break;​ case MODE_SEND_HEART_DETECT: //心率检测 { data_change.data_change_heart = 1; switch (command) { case 0: { //开关心率检测 } break;​ case 2: { //心率值 heart_detect.heart_detect_value = rx_data[0]; printf("Heart rate Value:%d\n", heart_detect.heart_detect_value); } break;​ case 5: { //心率波形 heart_detect.heart_wave_data[0] = rx_data[0]; heart_detect.heart_wave_data[1] = rx_data[1]; heart_detect.heart_wave_data[2] = rx_data[2]; heart_detect.heart_wave_data[3] = rx_data[3]; heart_detect.heart_wave_data[4] = rx_data[4]; } break; } } break;​ case MODE_IDLE: //空闲 { } break; } } rx_datagram_state = SEEN_SUM; buff++; } break;​ case SEEN_SUM: //读取校验后,判断帧尾1 { rx_datagram_state = (END1 == *buff) ? SEEN_END1 : IDLE; } break;​ case SEEN_END1: //读取帧尾1后,判断帧尾2 { rx_datagram_state = (END2 == *buff) ? SEEN_END2 : IDLE; } break;​ case SEEN_END2: //判断帧尾2后,设置接收完成标志 { // rx_flag = 1; rx_datagram_state = IDLE; } break;​​ default: { receivedbyte = 0; rx_datagram_state = IDLE; size = 0; break; } } size--; }}五、总结本项目开发一种非接触式的睡眠监控系统,该系统利用先进的60GHz毫米波雷达技术和STM32微控制器,实现了对人体在睡眠过程中的存在感知、运动感知以及生理指标如呼吸频率、心率的实时监测。系统能够自动评估睡眠质量,并在用户睡眠周期结束时提供睡眠评分。为了确保用户能够在任何地点了解自己的睡眠状况,系统集成了Wi-Fi模块,可以将收集到的数据上传至华为云物联网平台,并通过专门设计的移动应用程序供用户远程访问。此外,系统还具备超阈值报警功能,当检测到异常的生理指标时会发出警报提醒。本地1.44寸TFT LCD显示屏用于实时显示监测到的信息,包括生理指标和环境数据。为了全面监测用户的健康状况,系统还加入了MLX90614红外体温传感器来检测人体体温。通过集成多种传感器和技术,该项目为健康管理和智能家居应用提供了有力支持。
  • [技术干货] Linux系统下串口AT指令控制EC20连接华为云物联网平台
    一、前言在当今万物互联的时代背景下,物联网技术的快速发展极大地推动了智能化社会的构建。作为其中的关键一环,设备与云端平台之间的通信变得尤为重要。本文介绍如何在Linux操作系统环境下,利用串口通信来实现EC20模块与华为云物联网平台的有效连接。使用Linux下的/dev/ttyUSB0设备文件通过AT指令来配置EC20模块,采用C语言和Python这两种编程语言实现这一过程。EC20 是Quectel 生产的 4G LTE 模块。是一个多功能的通信模块,广泛应用于各种 IoT(物联网)设备中,提供了可靠的无线通信能力。EC20 支持 LTE FDD、LTE TDD、WCDMA 和 GSM 网络,能够实现高速的数据传输和稳定的语音通信。EC20 模块支持全球主要的频段,使其能够在多个国家和地区的网络环境中正常工作。它能够提供最高达 150 Mbps 的下行速率和 50 Mbps 的上行速率,这使得它非常适合需要高带宽的应用场景,如视频流、远程监控和数据传输。该模块具有强大的数据通信能力,包括支持 TCP/IP 和 UDP 协议的网络连接。EC20 还支持各种 AT 命令,通过这些命令用户可以控制模块的操作,配置网络设置,发送和接收数据。其内置的网络协议栈使得模块可以直接进行 MQTT、HTTP、FTP 等网络协议的通信,为开发者提供了极大的便利。除了数据通信功能,EC20 模块还支持语音通话和短信功能。这使得它不仅可以用于数据传输,还可以作为语音通信解决方案。通过 AT 命令,用户可以方便地进行拨打和接听电话,发送和接收短信,满足不同应用场景的需求。在电源管理方面,EC20 模块具有低功耗模式,能够有效地延长设备的电池寿命。它支持多种电源管理功能,包括睡眠模式和省电模式,使得它在不使用的时候能够降低功耗,减少能量消耗。模块的物理接口包括多个 UART 串口、USB 接口和 GPIO 引脚,这些接口允许模块与其他硬件进行连接。通过这些接口,用户可以实现串口通信、USB 数据传输以及各种数字信号的输入输出,提供了高度的灵活性和可扩展性。在设计和生产方面,EC20 模块遵循了工业标准,确保其在各种恶劣环境下的可靠性。它的设计小巧且坚固,适合嵌入到各种嵌入式系统和终端设备中。Quectel 提供了详细的技术文档和开发工具,帮助开发者快速集成和部署模块。二、实例代码2.1 服务器信息下面是我的华为云物联网服务器设备信息。关于创建过程,可以看视频:cid:link_0IP地址:117.78.5.125端口号:1883ClientId 64000697352830580e48df07_dev1_0_0_2023030206Username 64000697352830580e48df07_dev1Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449订阅主题:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down发布主题:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report发布的消息:{"services": [{"service_id": "stm32","properties":{"DHT11_T":18,"DHT11_H":80,"MQ2":1,"water":1,"flame":1,"light":0,"LED1":0,"LED2":0,"LED3":0}}]}2.2 Python代码本小节介绍 通过串口 /dev/ttyUSB0 发送 AT 指令以控制 EC20 模块连接华为云物联网平台,并完成 MQTT 通信,使用 Python 脚本实现。AT 指令:AT+RST: 重启模块AT+CSIM=1: 设置工作模式为数据模式(请根据实际模块手册确认是否需要此指令)AT+QMTOPEN=0,"IP地址",端口号: 连接到 MQTT 服务器AT+QMTCONN=0,"ClientId","Username","Password": 使用提供的 ID、用户名和密码连接到 MQTT 服务器AT+QMTSUB=0,0,"主题",1: 订阅指定主题AT+QMTPUB=0,0,0,0,"主题",消息内容: 发布消息到指定主题下面是实现代码,展示了如何用 Python 通过串口发送 AT 指令来配置 EC20 模块并连接到华为云物联网平台:# -*- coding: utf-8 -*-​import serialimport time​# 串口配置SERIAL_PORT = '/dev/ttyUSB0'BAUDRATE = 115200TIMEOUT = 1​# MQTT 服务器信息MQTT_SERVER_IP = '117.78.5.125'MQTT_SERVER_PORT = '1883'MQTT_CLIENT_ID = '64000697352830580e48df07_dev1_0_0_2023030206'MQTT_USERNAME = '64000697352830580e48df07_dev1'MQTT_PASSWORD = 'a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449'​# 发布的消息PUBLISH_MESSAGE = '{"services": [{"service_id": "stm32","properties":{"DHT11_T":18,"DHT11_H":80,"MQ2":1,"water":1,"flame":1,"light":0,"LED1":0,"LED2":0,"LED3":0}}]}'​# 发送 AT 指令并获取响应def send_at_command(serial_conn, command, response_termination='OK', delay=1): print(f"Sending command: {command}") serial_conn.write((command + '\r\n').encode()) time.sleep(delay) response = serial_conn.read(serial_conn.inWaiting()).decode() print(f"Response: {response}") if response_termination and response_termination not in response: raise Exception(f"Unexpected response: {response}") return response​def main(): # 打开串口连接 with serial.Serial(SERIAL_PORT, BAUDRATE, timeout=TIMEOUT) as ser: try: # 重启模块 send_at_command(ser, 'AT+RST')​ # 设置工作模式为数据模式 send_at_command(ser, 'AT+CSIM=1')​ # 连接到 MQTT 服务器 send_at_command(ser, f'AT+QMTOPEN=0,"{MQTT_SERVER_IP}",{MQTT_SERVER_PORT}') send_at_command(ser, 'AT+QMTCONN=0,"{MQTT_CLIENT_ID}","{MQTT_USERNAME}","{MQTT_PASSWORD}"') time.sleep(5) # 等待连接​ # 订阅主题 send_at_command(ser, 'AT+QMTSUB=0,0,"$oc/devices/64000697352830580e48df07_dev1/sys/messages/down",1')​ # 发布消息 send_at_command(ser, f'AT+QMTPUB=0,0,0,0,"$oc/devices/64000697352830580e48df07_dev1/sys/properties/report",{PUBLISH_MESSAGE}')​ except Exception as e: print(f"An error occurred: {e}")​if __name__ == "__main__": main()运行命令:root@flexusx-1a58:~# python3 mqtt_connect.py 2.3 C语言代码本小节介绍 用 C 语言实现通过串口发送 AT 指令来控制 EC20 模块并完成 MQTT 通信。下面是实现代码,通过串口配置 EC20 模块并连接到 MQTT 服务器:#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <fcntl.h>#include <termios.h>#include <errno.h>​#define SERIAL_PORT "/dev/ttyUSB0"#define BAUDRATE B115200​int setup_serial_port(const char *port) { int fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("open"); exit(EXIT_FAILURE); }​ struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, BAUDRATE); cfsetospeed(&options, BAUDRATE);​ options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8;​ tcsetattr(fd, TCSANOW, &options);​ return fd;}​void send_at_command(int fd, const char *command) { write(fd, command, strlen(command)); write(fd, "\r\n", 2); usleep(100000); // Wait for 100ms for the module to respond​ char response[256]; int n = read(fd, response, sizeof(response) - 1); if (n > 0) { response[n] = '\0'; printf("Response: %s\n", response); } else { printf("No response or read error.\n"); }}​int main() { int fd = setup_serial_port(SERIAL_PORT);​ // 1. Reset the module send_at_command(fd, "AT+RST");​ // 2. Connect to MQTT server char command[300]; snprintf(command, sizeof(command), "AT+QMTOPEN=0,\"%s\",%d", "117.78.5.125", 1883); send_at_command(fd, command);​ snprintf(command, sizeof(command), "AT+QMTCONN=0,\"%s\",\"%s\",\"%s\"", "64000697352830580e48df07_dev1_0_0_2023030206", "64000697352830580e48df07_dev1", "a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449"); send_at_command(fd, command);​ usleep(5000000); // Wait 5 seconds for connection to establish​ // 3. Subscribe to a topic send_at_command(fd, "AT+QMTSUB=0,0,\"$oc/devices/64000697352830580e48df07_dev1/sys/messages/down\",1");​ // 4. Publish a message sprintf(command, "AT+QMTPUB=0,0,0,0,\"$oc/devices/64000697352830580e48df07_dev1/sys/properties/report\",\"{\\\"services\\\": [{\\\"service_id\\\": \\\"stm32\\\",\\\"properties\\\":{\\\"DHT11_T\\\":18,\\\"DHT11_H\\\":80,\\\"MQ2\\\":1,\\\"water\\\":1,\\\"flame\\\":1,\\\"light\\\":0,\\\"LED1\\\":0,\\\"LED2\\\":0,\\\"LED3\\\":0}}]}\""); send_at_command(fd, command);​ close(fd); return 0;}编译运行:root@flexusx-1a58:~# gcc mqtt_connect.c root@flexusx-1a58:~# ./a.out 2.4 创建虚拟串口方便测试在与硬件通信之前,可以先建立一个虚拟串口测试一下。创建一个虚拟的串口设备 /dev/ttyUSB0 主要有两种方式:使用 socat 工具创建虚拟串口对(虚拟串口设备),或者使用 tty0tty 驱动来创建虚拟串口对。方法 1: 使用 socat 创建虚拟串口socat 是一个强大的网络工具,可以创建虚拟串口设备。下面是如何使用 socat 创建两个虚拟串口对:安装 socat: 在大多数 Linux 发行版上,可以使用包管理工具安装 socat。例如,在 Debian 或 Ubuntu 上运行:sudo apt-get install socat创建虚拟串口对: 运行以下命令来创建两个虚拟串口设备 /dev/ttyV0 和 /dev/ttyV1:socat -d -d PTY,link=/dev/ttyV0,raw,echo=0 PTY,link=/dev/ttyV1,raw,echo=0这将创建两个虚拟串口设备 /dev/ttyV0 和 /dev/ttyV1,它们会像物理串口一样工作,可以在程序中使用它们进行测试。使用虚拟串口设备:可以将一个设备用于发送数据,另一个设备用于接收数据进行测试。例如,可以将一个串口设备配置为连接到模拟的设备,另一个设备配置为模拟的串口设备的接收端。方法 2: 使用 tty0tty 驱动tty0tty 是一个内核模块,用于创建虚拟串口对。以下是如何安装和使用 tty0tty:安装 tty0tty: 需要从源代码编译和安装 tty0tty。按照以下步骤进行:sudo apt-get install build-essential linux-headers-$(uname -r)git clone https://github.com/ntop/tty0tty.gitcd tty0ttymakesudo make installsudo depmod -a加载内核模块: 运行以下命令加载 tty0tty 内核模块:sudo modprobe tty0tty这将创建虚拟串口设备 /dev/ttyt0 和 /dev/ttyt1。使用虚拟串口设备:/dev/ttyt0 和 /dev/ttyt1 现在可以作为虚拟串口设备使用。可以在程序中将它们作为串口设备来进行测试。示例代码假设已经创建了 /dev/ttyV0 和 /dev/ttyV1(使用 socat),可以在 Python 中使用这些虚拟串口设备进行测试。例如:import serial​# 打开虚拟串口ser1 = serial.Serial('/dev/ttyV0', 115200, timeout=1)ser2 = serial.Serial('/dev/ttyV1', 115200, timeout=1)​# 发送数据ser1.write(b'Hello, world!\n')​# 读取数据response = ser2.readline()print('Received:', response.decode('utf-8'))​# 关闭串口ser1.close()ser2.close()
  • [技术干货] 基于STM32单片机设计的矿山环境作业安全监测系统
    一、前言1.1 项目介绍项目设计里用到的全部工具软件和文档源码,都可以在这里下载。cid:link_10【1】项目开发背景矿山环境作业安全监测系统的开发背景主要源于对矿井作业环境中潜在危险因素的有效监控需求。矿山作为重要的资源开采场所,其工作环境往往存在诸多安全隐患,如瓦斯爆炸、粉尘超标等,这些因素不仅威胁着矿工的生命安全,还可能导致严重的经济损失和社会影响。因此,建立一个能够实时监测矿井内环境状况,并能在危险发生前及时预警的安全监测系统显得尤为重要。随着物联网技术的发展,利用先进的传感器技术与无线通信技术相结合,可以实现对矿山环境的全方位监控。本项目选择以STM32F103RCT6单片机作为核心控制器,因其具备高性能、低功耗的特点,非常适合用于此类环境下的数据采集与控制任务。通过集成DHT11温湿度传感器、MQ5气体传感器、PM2.5传感器等,系统能够实时获取环境数据,并依据预设的阈值进行判断,从而采取相应的措施,比如启动通风装置降低瓦斯浓度或通过喷淋系统减少空气中的颗粒物含量。此外,为了使矿山管理人员能够远程监控矿井内的实际情况,本项目还将通过BC26(NBIOT)模块将收集到的数据上传至华为云物联网平台,实现了数据的云端存储与分析。同时,借助移动应用技术,开发了一款APP,以便于工作人员随时查看环境参数及接收警报信息,进一步增强了系统的实用性和灵活性。本项目的开发提供一套高效、可靠的矿山环境作业安全监测解决方案,通过技术手段提升矿山安全管理效率,保障矿工的人身安全,促进矿山行业的可持续发展。【2】设计实现的功能(1) 本项目设计的核心是以STM32F103RCT6单片机作为主控单元,负责整个系统的协调控制,实现对矿山环境各项关键参数的监测与管理。(2) 采用DHT11温湿度传感器进行环境温度和湿度的实时采集,一旦检测到的数值超出安全范围,则通过蜂鸣器发出警报信号。(3) 使用MQ5气体传感器监测瓦斯浓度,当浓度达到预设阈值时,系统将通过控制继电器启动风扇,以稀释瓦斯浓度。(4) 配备PM2.5传感器用以检测空气中颗粒物的浓度,当浓度超标时,激活雾化喷淋系统以降低灰尘含量。(5) 选用OLED显示屏作为人机交互界面,实时显示由各传感器采集到的环境数据。(6) 利用BC26(NBIOT)模块将现场采集到的数据上传至华为云物联网平台,便于远程监控和数据分析。(7) 实现自动模式功能,系统能够按照预先设定的阈值自动监测环境参数,并在必要时触发警报或执行相应控制动作,如启动风扇或喷淋系统。(8) 提供手动模式功能,允许用户通过按键直接控制风扇和雾化降尘设备的开关状态,并且开发了基于Qt框架的Android平台手机APP,以便于远程控制这些设备的运行状态。(9) 设计中考虑了系统的稳定供电方案,采用5V 2A的外部稳压电源为系统供电。(10) 风扇和雾化降尘设备均采用5V电源供电,并通过继电器模块实现开关控制。(11) OLED显示屏采用SPI协议进行数据传输,以确保信息显示的准确性和实时性。【3】项目硬件模块组成(1) 控制核心模块:STM32F103RCT6单片机最小系统模块,作为整个监测系统的控制中心。(2) 温湿度采集模块:DHT11温湿度传感器,用于实时检测环境的温度和湿度。(3) 气体检测模块:MQ5气体传感器,用于监测环境中的瓦斯浓度。(4) 颗粒物检测模块:PM2.5传感器,用于检测空气中悬浮颗粒物的浓度。(5) 显示模块:0.96寸OLED显示屏,采用SPI协议连接至控制核心,显示各项环境参数。(6) 报警模块:蜂鸣器,当检测到环境参数异常时发出声音警报。(7) 执行机构控制模块:继电器模块,用于控制风扇和雾化降尘设备的开关状态。(8) 通风设备:风扇,由继电器控制,用于降低瓦斯浓度。(9) 降尘设备:雾化喷淋系统,同样由继电器控制,用于减少空气中颗粒物含量。(10) 远程通信模块:BC26(NBIOT)模块,负责将采集到的数据通过窄带物联网技术上传至云端。(11) 电源供应模块:5V 2A外部稳压电源,为整个系统提供稳定的电力支持。(12) 操作接口:按键模块,允许用户手动控制设备的开启与关闭。(13) 移动终端交互模块:基于Qt开发的Android平台手机APP,实现远程监控和控制功能。【4】需求总结项目名称:基于STM32单片机设计的矿山环境作业安全监测系统1、本次设计以 STM32F103RCT6 单片机最小系统模块作为系统控制核心,确定各种传感器模块选型,完成系统硬件结构设计。2、采用 DHT11 温湿度采集模块进行环境温湿度检测。当传感器检测到环境值超过控制系统设定的阀值参数时,可触发蜂鸣器报警。3、采用 MQ5气体传感器来检测环境的瓦斯浓度。当瓦斯浓度达到阈值时,控制系统控制继电器开关模块动作,实现风扇自动控制功能。4、采用 PM2.5 传感器检测环境中的颗粒物,超过阈值触发报警,可打开雾化模块进行喷淋降低。5、采用 OLED 显示屏作为显示模块显示实时数据。6、将采集到的环境信息通过BC26(NBIOT)模块将数据上传到华为云物联网平台。7、自动模式功能根据预设的阈值设定,监测环境参数,并在超过阈值时触发警报和控制设备使用定时器进行周期性的环境参数监测。设计逻辑判断程序,根据环境参数触发不同的处理动作。8、手动模式功能实现按键功能,根据用户的操作控制风扇和雾化降尘设备的开关状态。开发手机 APP,使用Qt作为 Android 平台的开发工具实现与BC26(NBIOT)模块的通信和数据显示功能。在手机 APP 上实现远程控制风扇和雾化降尘设备的开关功能以及显示实时监测到的环境参数和警报信息9、供电采用 5V 2A外部稳压电源10、风扇和雾化降尘设备采用5V加湿器模块,通过继电器控制开关。11、OLED显示屏采用SPI协议的0.96寸OLED显示屏1.2 设计思路设计思路的核心是围绕提高矿山作业环境的安全性展开,考虑到矿山环境复杂多变的特点,本项目构建一个能够实时监测并有效应对潜在危险因素的自动化系统。该系统的设计从硬件选型到软件架构都遵循了模块化和易维护的原则,确保了系统的可靠性和扩展性。在硬件层面,选择了性能稳定且易于编程的STM32F103RCT6单片机作为中央处理器,这是因为STM32系列芯片拥有丰富的外设接口,能够方便地与各种传感器和执行机构进行通信。同时,考虑到矿山环境的特殊性,传感器的选择上优先考虑了可靠性与准确性,如DHT11用于温湿度监测,MQ5用于瓦斯浓度检测,PM2.5传感器则用于颗粒物浓度测量。此外,为了实现环境参数的直观展示,选用了OLED显示屏作为人机交互界面,并通过继电器模块来控制风扇和雾化喷淋系统,以应对不同的紧急情况。软件方面,系统的设计着重于逻辑清晰的程序架构,通过编写高效的算法来处理来自不同传感器的数据,并依据预设的安全阈值进行逻辑判断。当环境参数超出正常范围时,系统会自动触发相应的警报机制,并启动相应的应急措施,例如启动通风设备降低瓦斯浓度或启用喷淋系统减少粉尘。此外,为了便于远程监控,系统集成了BC26(NBIOT)模块,能够将采集到的数据上传至华为云物联网平台,同时开发了配套的手机应用程序,使得管理者能够随时随地查看环境状况,并进行远程控制。整体而言,该项目的设计思路充分结合了现代物联网技术和传统矿山安全管理的需求,力求通过智能化手段提升矿山作业的安全水平,减少事故发生的可能性,保障矿山工作的顺利进行。1.3 系统功能总结功能类别描述环境监测实时采集矿山环境的温度、湿度、瓦斯浓度、颗粒物浓度等数据。自动警报当环境参数超过预设安全阈值时,自动触发蜂鸣器警报。自动控制达到特定阈值时,自动控制风扇和雾化喷淋系统,以降低瓦斯浓度和颗粒物含量。数据显示OLED显示屏实时显示采集到的各种环境参数。数据上传通过BC26(NBIOT)模块将环境数据上传至华为云物联网平台,便于远程监控和数据分析。定时监测使用定时器进行周期性的环境参数监测,确保数据的连续性和及时性。手动控制用户可以通过按键手动控制风扇和雾化降尘设备的开关状态。远程控制开发了基于Qt框架的Android平台手机APP,实现远程控制风扇和雾化降尘设备的开关功能。数据可视化在手机APP上显示实时监测到的环境参数和警报信息。稳定供电采用5V 2A外部稳压电源为系统提供稳定的电力支持。设备控制风扇和雾化降尘设备采用5V电源供电,并通过继电器模块实现开关控制。人机交互OLED显示屏采用SPI协议,保证信息显示的准确性和实时性。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)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。1.5 模块的技术详情介绍【1】BC26-NBIOT模块BC26-NBIOT模块是一款专为窄带物联网(Narrow Band Internet of Things, NB-IoT)设计的无线通信模块,适用于低功耗广域网络(LPWAN)的应用场景。该模块主要针对物联网市场的需求而开发,尤其适用于那些需要长距离通信、低功耗、低成本和高容量的应用场合,如智能城市、环境监测、智能家居等领域。BC26-NBIOT模块具备良好的网络覆盖能力,能够在较远的距离内保持稳定的通信连接,这对于矿山环境作业安全监测系统来说至关重要。由于矿山内部结构复杂,通信条件苛刻,传统的无线通信技术可能难以满足要求,而NB-IoT技术凭借其优秀的穿透能力和低功耗特性,可以在这种环境下实现可靠的通信。BC26-NBIOT模块支持全球主流运营商的NB-IoT频段,这意味着它可以无缝接入不同的网络环境,为用户提供灵活的部署选项。这使得矿山监测系统不仅可以在国内使用,也可以在全球范围内实施,增强了系统的通用性和适用性。在能耗方面,BC26-NBIOT模块设计有低功耗模式,可以在不活跃期间大幅降低功耗,这对于延长电池寿命或减少系统整体功耗非常重要。尤其是在矿山这样的环境中,由于电源可能不是随时可得,低功耗特性就显得尤为关键。模块还提供了丰富的接口,包括UART、GPIO、PWM等,便于与其他传感器或执行器进行连接和数据交换。这使得开发者可以根据具体的应用需求,灵活地构建起复杂的物联网系统。同时,BC26-NBIOT模块通常支持AT命令集,这简化了开发过程,使得开发人员能够更快地上手进行开发工作。BC26-NBIOT模块以其卓越的通信性能、广泛的兼容性和低功耗特性,成为矿山环境作业安全监测系统中的理想选择,能够有效地支持数据的远程传输和系统的远程管理,提高了矿山作业的安全性和管理效率。【2】DHT11温湿度模块DHT11温湿度模块是一种经济实惠且广泛使用的数字温湿度传感器,它集成了温度和湿度感应元件以及一个信号转换电路。这款模块因其简单易用、成本低廉而被众多DIY爱好者和专业开发者所青睐,在智能家居、气象站、农业自动化等多种应用场景中都有广泛的应用。DHT11模块的核心是由一个NTC热敏电阻和一个湿度敏感电容组成的复合传感器。NTC热敏电阻用于检测环境温度的变化,而湿度敏感电容则用于检测空气中的水分含量。这些原始数据经过内部电路的处理后,通过单线串行接口输出给外部微控制器。这种集成化的处理方式大大简化了传感器的使用,使得开发人员无需关心内部的具体实现细节。在硬件接口方面,DHT11模块通常配备四个引脚,分别是电源正极(VCC)、电源地(GND)、信号输出(DATA)和预留的空引脚。其中,VCC引脚提供工作电压,通常为3.3V到5V之间;GND引脚接地;DATA引脚则是用于与外部微控制器进行数据通信的串行接口。为了保证数据传输的稳定性,通常会在DATA引脚与GND之间接一个上拉电阻。在软件层面上,DHT11模块的操作相对简单,它遵循一种特定的通信协议。当微控制器想要读取温湿度数据时,需要向DHT11发送一个启动信号,然后等待DHT11回应一个确认信号。之后,DHT11会依次发送湿度整数部分、湿度小数部分、温度整数部分、温度小数部分以及一个校验位。开发人员只需要编写简单的函数来发送启动信号,并接收和解析返回的数据即可。DHT11模块具有价格优势和易于使用的特性,它的精度不高,湿度测量范围为20%RH至90%RH,精度±5%RH;温度测量范围为0℃至50℃,精度±2℃。【3】PM2.5粉尘模块PM2.5粉尘模块是一种专门用于检测空气中细颗粒物(Particulate Matter 2.5,简称PM2.5)浓度的传感器。PM2.5是指直径小于或等于2.5微米的颗粒物,这类颗粒物因为体积小、面积大、活性强,容易携带污染物,对人体健康尤其是呼吸系统有着较大的危害。因此,监测PM2.5浓度对于环境保护和个人健康具有重要意义。PM2.5粉尘模块通常基于光散射原理工作。当空气中的颗粒物通过传感器时,内置的光源(通常是红外LED)会照射这些颗粒物,导致光的散射。传感器内部装有一个光电二极管,用来接收散射光,并将其转换成电信号。通过分析这些电信号的强度,就可以估算出空气中PM2.5颗粒物的浓度。这种检测方法简单、快速,适用于各种便携式或固定式的空气质量监测设备。市场上常见的PM2.5粉尘模块如PMS5003、SDS011等,它们通常具备较小的尺寸和较低的功耗,适合集成到各种物联网设备中。这些模块一般都提供标准的串行通信接口(如TTL UART),可以直接与微控制器(如STM32系列)相连,进行数据的读取和处理。此外,一些高级模块还支持I2C或SPI接口,提供更多的配置选项和更高的数据传输速率。在硬件设计上,PM2.5粉尘模块内部集成了气流通道、光源、光接收器以及信号处理电路。为了保证测量结果的准确性,模块内部通常设有风机来确保空气能够均匀流动并通过传感器区域。此外,为了防止外界干扰,传感器通常会配备有防尘网或过滤器,以保护内部元件不受污染。从软件角度来看,使用PM2.5粉尘模块相对简单。开发人员只需要按照模块提供的数据手册编写相应的驱动程序,就能实现对模块的初始化和数据读取。大多数模块都会提供一整套的通信协议,其中包括了如何发送查询命令以及如何解析返回的数据格式。例如,一些模块会以ASCII码形式返回数据,包含PM2.5、PM10等不同粒径颗粒物的浓度值,以及其他辅助信息如温度、湿度等。值得注意的是,虽然PM2.5粉尘模块在一定程度上能够提供准确的颗粒物浓度数据,但在实际应用中,还需要考虑诸如环境温度、湿度等因素对测量结果的影响。此外,为了确保数据的长期稳定性和准确性,定期对传感器进行校准也是非常必要的。PM2.5粉尘模块作为一种有效的颗粒物浓度监测工具,已经广泛应用于家庭、办公室、工厂等各种环境下的空气质量监测系统中,为人们提供了便捷的方式来监控和改善生活环境质量。二、BC26-NBIOT模块调试过程2.1 模块调试接线2.2 测试模块第一步接上之后,串口调试助手选择波特率为115200,勾选软件上的发送新行选项。发送AT过去,正常模块会返回OK。只有收到了OK,才表示模块工作正常。2.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 //找网成功OK2.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*19OK二、部署华为云物联网平台华为云官网: cid:link_11打开官网,搜索物联网,就能快速找到 设备接入IoTDA。2.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。2.2 开通物联网服务地址: cid:link_8点击立即创建。正在创建标准版实例,需要等待片刻。创建完成之后,点击实例名称。 可以看到标准版实例的设备接入端口和地址。在上面也能看到 免费单元的限制。开通之后,点击总览,也能查看接入信息。 我们当前设备准备采用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端口连接华为云物联网平台。2.3 创建产品(1)创建产品(2)填写产品信息根据自己产品名字填写,下面的设备类型选择自定义类型。(3)产品创建成功创建完成之后点击查看详情。(4)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。模型简单来说: 就是存放设备上传到云平台的数据。你可以根据自己的产品进行创建。比如:烟雾可以叫 MQ2温度可以叫 Temperature湿度可以叫 humidity火焰可以叫 flame其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。先点击自定义模型。再创建一个服务ID。接着点击新增属性。2.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备(2)根据自己的设备填写(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。(4)设备创建完成(5)设备详情2.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_6业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_6对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down 最终的格式:$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_2根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$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}}]}2.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_1MQTT协议的端口支持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鉴权三元组: cid:link_7打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911Username 663cb18871d845632a0912e7_dev1Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac2372.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。点击详情页面,可以看到上传的数据:到此,云平台的部署已经完成,设备已经可以正常上传数据了。(3)MQTT登录测试参数总结MQTT服务器: 117.78.5.125MQTT端口号: 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}}]}2.8 创建IAM账户创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。地址: cid:link_4【1】获取项目凭证 点击左上角用户名,选择下拉菜单里的我的凭证项目凭证:28add376c01e4a61ac8b621c714bf459【2】创建IAM用户鼠标放在左上角头像上,在下拉菜单里选择统一身份认证。点击左上角创建用户。创建成功:【3】创建完成用户信息如下:主用户名 l19504562721IAM用户 ds_abc密码 DS123456782.9 获取影子数据帮助文档:cid:link_5设备影子介绍:设备影子是一个用于存储和检索设备当前状态信息的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三、上位机开发为了方便查看设备上传的数据,接下来利用Qt开发一款Android手机APP 和 Windows上位机。使用华为云平台提供的API接口获取设备上传的数据,进行可视化显示,以及远程控制设备。3.1 Qt开发环境安装Qt的中文官网: cid:link_12QT5.12.6的下载地址:cid:link_9或者去网盘里下载:cid:link_10打开下载链接后选择下面的版本进行下载: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环境搭建的博客链接: cid:link_33.2 新建上位机工程前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。【1】新建工程【2】设置项目的名称。【3】选择编译系统【4】选择默认继承的类【5】选择编译器【6】点击完成【7】工程创建完成3.3 设计UI界面与工程配置【1】打开UI文件打开默认的界面如下:【2】开始设计界面根据自己需求设计界面。3.5 编译Windows上位机点击软件左下角的绿色三角形按钮进行编译运行。编译之后的效果:3.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四、STM32代码开发4.1 MQTT协议设计代码字数过多,无法显示...4.2 PM2.5与MQ5采集代码#include "adc.h"#include "delay.h" //初始化ADC1//这里我们仅以规则通道为例//我们默认仅开启通道1 void Adc_Init(void){ //先初始化IO口 RCC->APB2ENR|=1<<2; //使能PORTA口时钟 GPIOA->CRL&=0XFFFFFF0F;//PA1 anolog输入 RCC->APB2ENR|=1<<9; //ADC1时钟使能 RCC->APB2RSTR|=1<<9; //ADC1复位 RCC->APB2RSTR&=~(1<<9);//复位结束 RCC->CFGR&=~(3<<14); //分频因子清零 //SYSCLK/DIV2=12M ADC时钟设置为12M,ADC最大时钟不能超过14M! //否则将导致ADC准确度下降! RCC->CFGR|=2<<14; ADC1->CR1&=0XF0FFFF; //工作模式清零 ADC1->CR1|=0<<16; //独立工作模式 ADC1->CR1&=~(1<<8); //非扫描模式 ADC1->CR2&=~(1<<1); //单次转换模式 ADC1->CR2&=~(7<<17); ADC1->CR2|=7<<17; //软件控制转换 ADC1->CR2|=1<<20; //使用用外部触发(SWSTART)!!! 必须使用一个事件来触发 ADC1->CR2&=~(1<<11); //右对齐 ADC1->SQR1&=~(0XF<<20); ADC1->SQR1|=0<<20; //1个转换在规则序列中 也就是只转换规则序列1 //设置通道1的采样时间 ADC1->SMPR2&=~(3*1); //通道1采样时间清空 ADC1->SMPR2|=7<<(3*1); //通道1 239.5周期,提高采样时间可以提高精确度 ADC1->CR2|=1<<0; //开启AD转换器 ADC1->CR2|=1<<3; //使能复位校准 while(ADC1->CR2&1<<3); //等待校准结束 //该位由软件设置并由硬件清除。在校准寄存器被初始化后该位将被清除。 ADC1->CR2|=1<<2; //开启AD校准 while(ADC1->CR2&1<<2); //等待校准结束 //该位由软件设置以开始校准,并在校准结束时由硬件清除 } //获得ADC1某个通道的值//ch:通道值 0~16//返回值:转换结果u16 Get_Adc(u8 ch) { //设置转换序列 ADC1->SQR3&=0XFFFFFFE0;//规则序列1 通道ch ADC1->SQR3|=ch; ADC1->CR2|=1<<22; //启动规则转换通道 while(!(ADC1->SR&1<<1));//等待转换结束 return ADC1->DR; //返回adc值 }//获取通道ch的转换值,取times次,然后平均 //ch:通道编号//times:获取次数//返回值:通道ch的times次转换结果平均值u16 Get_Adc_Average(u8 ch,u8 times){ u32 temp_val=0; u8 t; for(t=0;t<times;t++) { temp_val+=Get_Adc(ch); delay_ms(5); } return temp_val/times;} 4.3 DHT11温湿度采集代码#include "dht11.h"#include "delay.h"//IO方向设置#define DHT11_IO_IN() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=8<<12;}#define DHT11_IO_OUT() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=3<<12;}////IO操作函数 #define DHT11_DQ_OUT PGout(11) //数据端口 PG11 #define DHT11_DQ_IN PGin(11) //数据端口 PG11​​u8 DHT11_Init(void); //初始化DHT11u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度u8 DHT11_Read_Byte(void); //读出一个字节u8 DHT11_Read_Bit(void); //读出一个位u8 DHT11_Check(void); //检测是否存在DHT11void DHT11_Rst(void); //复位DHT11 ​​//复位DHT11void DHT11_Rst(void) { DHT11_IO_OUT(); //SET OUTPUT DHT11_DQ_OUT=0; //拉低DQ delay_ms(20); //拉低至少18ms DHT11_DQ_OUT=1; //DQ=1 delay_us(30); //主机拉高20~40us}//等待DHT11的回应//返回1:未检测到DHT11的存在//返回0:存在u8 DHT11_Check(void) { u8 retry=0; DHT11_IO_IN();//SET INPUT while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; else retry=0; while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; return 0;}//从DHT11读取一个位//返回值:1/0u8 DHT11_Read_Bit(void) { u8 retry=0; while(DHT11_DQ_IN&&retry<100)//等待变为低电平 { retry++; delay_us(1); } retry=0; while(!DHT11_DQ_IN&&retry<100)//等待变高电平 { retry++; delay_us(1); } delay_us(40);//等待40us if(DHT11_DQ_IN)return 1; else return 0; }//从DHT11读取一个字节//返回值:读到的数据u8 DHT11_Read_Byte(void) { u8 i,dat; dat=0; for (i=0;i<8;i++) { dat<<=1; dat|=DHT11_Read_Bit(); } return dat;}//从DHT11读取一次数据//temp:温度值(范围:0~50°)//humi:湿度值(范围:20%~90%)//返回值:0,正常;1,读取失败u8 DHT11_Read_Data(u8 *temp,u8 *humi) { u8 buf[5]; u8 i; DHT11_Rst(); if(DHT11_Check()==0) { for(i=0;i<5;i++)//读取40位数据 { buf[i]=DHT11_Read_Byte(); } if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { *humi=buf[0]; *temp=buf[2]; } }else return 1; return 0; }//初始化DHT11的IO口 DQ 同时检测DHT11的存在//返回1:不存在//返回0:存在 u8 DHT11_Init(void){ RCC->APB2ENR|=1<<8; //使能PORTG口时钟 GPIOG->CRH&=0XFFFF0FFF;//PORTG.11 推挽输出 GPIOG->CRH|=0X00003000; GPIOG->ODR|=1<<11; //输出1 DHT11_Rst(); return DHT11_Check();}五、总结该项目开发一种基于STM32单片机的矿山环境作业安全监测系统,以提高矿山作业的安全性和效率。系统集成了多种传感器,包括DHT11温湿度传感器、MQ5气体传感器和PM2.5传感器,用于实时监测矿山环境中的关键参数,如温度、湿度、瓦斯浓度和颗粒物含量。通过这些传感器,系统能够及时发现潜在的安全隐患,并采取必要的预防措施。在硬件设计方面,系统采用了STM32F103RCT6单片机作为核心控制器,配合蜂鸣器、继电器模块、OLED显示屏等组件,形成了一个完整的监测与控制系统。当检测到的环境参数超过预设的安全阈值时,系统能够自动触发报警,并通过控制风扇和雾化喷淋系统来降低瓦斯浓度和颗粒物含量,从而保障矿工的生命安全。此外,为了实现远程监控与管理,项目还引入了BC26(NBIOT)模块,将环境数据上传至华为云物联网平台,并开发了一款基于Qt框架的Android平台手机应用程序。该应用程序不仅能够实时显示环境参数,还能接收警报信息,并允许用户远程控制风扇和雾化降尘设备的开关状态,极大地提升了系统的实用性和灵活性。该矿山环境作业安全监测系统通过集成先进的传感技术和物联网技术,实现了对矿山环境的全方位监控,能够在危险发生之前提供预警,并采取有效的防护措施,对于提升矿山作业的安全管理水平具有重要意义。
  • [技术干货] 基于STM32设计的农作物生长管理系统
    一、前言1.1 项目介绍【1】项目开发背景随着现代农业技术的发展,智能农业逐渐成为提高农作物产量和品质的关键手段之一。传统的农业生产方式依赖于人工经验,这种方式不仅效率低下,而且难以应对气候变化带来的挑战。特别是在水资源管理和光照控制方面,传统方法往往无法提供精确的控制,这直接影响到了农作物的生长周期和最终产量。在此背景下,基于现代物联网技术的农作物生长管理系统应运而生。这类系统能够通过各种传感器实时监测农田环境,如土壤湿度、光照强度、空气温度和湿度等,并根据这些数据自动调整灌溉、照明等设施的工作状态。这种智能化的管理不仅大大减轻了农民的劳动强度,而且通过优化资源利用,提高了农作物的生产效率。本项目提出了一种基于STM32微控制器和NBIOT通信技术的农作物生长管理系统设计方案。通过将先进的微处理器技术与窄带物联网(NBIOT)结合,本系统能够在无人干预的情况下,实现对农田环境的精确控制。系统还具备远程监控和控制功能,用户可以通过手机应用程序随时查看农田的实时数据,并根据需要调整设备的工作模式。随着科技的进步和人们对食品安全重视程度的提高,智能农业管理系统的需求日益增长。本项目的开发不仅有助于推动农业向更加科学化、精准化的方向发展,同时也为实现可持续农业生产和保障粮食安全提供了技术支持。通过本系统的应用,可以预见未来的农业生产将更加高效、环保,同时也为农民带来了更大的经济效益和社会效益。​【2】设计实现的功能(1)实时检测土壤含水量,并根据预设的阈值自动判断是否需要补水,进而实现自动化的灌溉控制。(2)除了自动补水功能外,系统还提供了本地按钮控制补水选项,同时支持通过远程控制手段进行手动补水操作,增加了补水控制的灵活性。(3)系统支持植物补光灯亮度的PWM自动调节,可以根据环境亮度的变化动态调整灯光亮度,以确保植物获得最佳光照条件。(4)环境温度与湿度检测功能也是系统的重要组成部分,当检测到空气温度或湿度超出设定的安全范围时,系统会触发相应的警报机制。(5)本地OLED屏幕显示当前环境的各项参数,如温度、湿度、土壤含水量以及光线照射强度,方便用户随时查看环境状态。(6)为了及时响应异常情况,系统配备了蜂鸣器报警功能。一旦发现温度、湿度或土壤含水量不符合预设的健康范围,系统会通过蜂鸣器发出声音报警。这些阈值可以通过手机应用程序进行设定。(7)为了避免由于温度突变引起的误操作,系统采用了适当的控制方法,在设定温度发生大变动时延迟系统的反应调节时间,确保系统的稳定性和准确性。(8)通过NBIOT-BC26模块,整个设备可以连接至华为云IoT物联网平台,并采用MQTT协议上传数据。基于此,设计了Android手机APP,以便用户能够远程查看设备上传的数据,并实现远程控制设备的功能,如设置设备的温度阈值等。(9)采用Qt框架开发Android手机应用程序【3】项目硬件模块组成(1)主控芯片:采用STM32F103RCT6作为核心处理器,负责处理系统中所有的逻辑运算和控制任务。(2)环境光照强度检测模块:使用BH1750传感器来测量环境光照强度,为补光灯的亮度调节提供数据支持。(3)环境温湿度检测模块:采用DHT11传感器,用于监测环境中的温度和湿度,确保农作物处于适宜的生长环境中。(4)OLED显示屏模块:选用0.96寸的SPI协议OLED显示屏,用于显示当前环境参数,如温度、湿度、土壤含水量及光线强度等信息。(5)补光灯模块:使用白色LED灯作为植物补光灯,通过PWM技术调节亮度,以适应不同环境下的光照需求。(6)声音报警模块:采用蜂鸣器作为报警装置,当环境参数超出预设阈值时,通过蜂鸣器发出声音警报。(7)土壤湿度检测模块:使用带有ADC模拟量接口的土壤湿度检测传感器,用于实时监测土壤含水量,确保适时补水。(8)网络通信模块:采用NBIOT-BC26模块,实现设备与华为云IoT物联网平台之间的数据传输,通过MQTT协议上传数据。(9)补水控制模块:使用继电器驱动5V抽水电机,根据土壤湿度传感器的反馈信息自动或手动控制补水过程。(10)供电模块:采用USB线5V供电方式,为整个系统提供稳定的电力供应,简化了设备的安装和使用流程。【4】需求总结项目名字: 基于STM32+NBIOT设计的农作物生长管理系统​实现功能:​1. 实时检测土壤含水量,根据设置的阀值判断实现自动补水2. 本地可用按钮控制补水、远程手动控制补水3. 支持植物补光灯亮度PWM自动调节,可根据环境亮度调节灯光亮度4. 支持检测环境温度、湿度,当空气温度、湿度超过设定值范围时会报警。5. 本地0LED屏幕显示当前环境温度、湿度、土壤含水量、光线照射强度等参数。6. 支持蜂鸣器报警提醒,当温度、湿度、土壤含水量阀值不符合设定值时通过蜂鸣器发出声音报警(阀值可通过手机APP设定)7.采用合适的控制方法,当设定温度发生大突变时,为了防止误设置,延迟系统的反应调节时间。8. 整个设备会通过NBIOT-BC26连接华为云IOT物联网平台,通过MQTT协议上传数据到物联网云平台,再设计Android手机APP实现远程显示设备上传的数据,同时可以远程控制设备,设置设备温度阀值等等。9. 采用Qt(C++)设计Android手机APP,实现数据远程监测显示和远程控制。​硬件选型:​1. 主控芯片选择STM32F103RCT62. 环境光照强度检测采用BH17503. 环境温湿度检测采用DHT114. OLED显示屏采用0.96寸SPI协议显示屏5. 补光灯采用白色LED灯6. 声音报警采用蜂鸣器7. 土壤湿度检测采用ADC模拟量接口的土壤湿度检测传感器8. 联网采用NBIOT-BC26模块9. 植物补水采用继电器驱动5V抽水电机抽水进行补水。10.供电电源:采用USB线-5V供电。1.2 设计思路本项目的设计思路围绕着实现一个高度自动化、智能化的农作物生长管理系统展开,通过现代电子技术与物联网技术的融合,为农作物提供最佳的生长环境。首先,系统的核心是基于STM32F103RCT6微控制器,这是因为该芯片具备高性能、低功耗的特点,非常适合用于需要实时处理大量数据的应用场景。通过集成多种传感器,系统能够实时采集环境数据,如土壤湿度、光照强度、温度和湿度等,为后续的决策和控制提供依据。在环境监测方面,系统选用了BH1750光照强度传感器和DHT11温湿度传感器。BH1750能够精确测量光照强度,从而支持植物补光灯的PWM自动调节功能;而DHT11则用于监测空气中的温度和湿度,当这些参数超出预设的安全范围时,系统会通过蜂鸣器报警,提醒用户采取措施。系统配置了土壤湿度检测传感器,它可以实时检测土壤的含水量,并根据设定的阈值自动控制补水,确保植物获得充足的水分。为了使用户能够直观地了解环境状态,系统配备了一块0.96寸的OLED显示屏,通过SPI协议与主控芯片相连,实时显示各项环境参数。这样,即使在没有智能手机的情况下,用户也能通过显示屏掌握当前的环境状况。在联网功能上,系统采用了NBIOT-BC26模块,通过窄带物联网技术实现设备与华为云IoT物联网平台的连接。借助MQTT协议,系统能够将采集到的数据上传至云端,用户可以通过Android手机APP实时查看数据,并远程控制设备。为了进一步增强用户体验,项目还计划使用Qt框架开发Android版的应用程序,以便于用户更方便地进行远程监控和管理。在控制策略上,系统特别考虑到了温度突变可能引发的误操作问题。为此,设计了适当的延时控制机制,即当设定温度发生较大变化时,系统不会立即作出反应,而是经过一段时间的延迟后再进行调节,从而避免了因温度波动导致的误动作。在供电方面,系统选择了简单且可靠的USB线5V供电方式,不仅便于安装和维护,也确保了系统的稳定性。通过这一系列的设计思路,本项目打造一个高效、可靠且易于使用的农作物生长管理系统,助力现代农业的智能化转型。1.3 系统功能总结功能类别描述实时土壤含水量检测与自动补水系统能够实时检测土壤含水量,并根据预设阈值自动判断是否需要启动补水机制。本地与远程补水控制用户可以通过本地按钮手动控制补水,也可以通过远程手段(如手机APP)进行手动补水控制。补光灯亮度自动调节根据环境亮度变化,系统能够自动调节补光灯的亮度,以满足植物生长所需的光照条件。环境温湿度检测与报警通过传感器检测环境温度和湿度,当检测到的数值超出设定范围时,系统会触发报警机制。环境参数本地显示OLED屏幕实时显示当前环境的温度、湿度、土壤含水量及光线照射强度等参数。蜂鸣器声音报警当温度、湿度或土壤含水量不符合预设的健康范围时,系统通过蜂鸣器发出声音报警。温度突变延迟控制针对温度突变的情况,系统采用了延迟反应机制,以防止因温度快速变化而导致的误操作。数据上传与远程监控通过NBIOT-BC26模块将数据上传至华为云IoT平台,并通过MQTT协议传输数据,支持远程监控。远程控制与设置用户可以通过设计的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)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。二、部署华为云物联网平台华为云官网: cid:link_11打开官网,搜索物联网,就能快速找到 设备接入IoTDA。2.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。2.2 开通物联网服务地址: cid:link_8点击立即创建。正在创建标准版实例,需要等待片刻。创建完成之后,点击实例名称。 可以看到标准版实例的设备接入端口和地址。在上面也能看到 免费单元的限制。开通之后,点击总览,也能查看接入信息。 我们当前设备准备采用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端口连接华为云物联网平台。2.3 创建产品(1)创建产品(2)填写产品信息根据自己产品名字填写,下面的设备类型选择自定义类型。(3)产品创建成功创建完成之后点击查看详情。(4)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。模型简单来说: 就是存放设备上传到云平台的数据。你可以根据自己的产品进行创建。比如:烟雾可以叫 MQ2温度可以叫 Temperature湿度可以叫 humidity火焰可以叫 flame其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。先点击自定义模型。再创建一个服务ID。接着点击新增属性。2.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备(2)根据自己的设备填写(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。(4)设备创建完成(5)设备详情2.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_6业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_6对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down 最终的格式:$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_2根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$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}}]}2.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_1MQTT协议的端口支持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鉴权三元组: cid:link_7打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911Username 663cb18871d845632a0912e7_dev1Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac2372.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。点击详情页面,可以看到上传的数据:到此,云平台的部署已经完成,设备已经可以正常上传数据了。(3)MQTT登录测试参数总结MQTT服务器: 117.78.5.125MQTT端口号: 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}}]}2.8 创建IAM账户创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。地址: cid:link_4【1】获取项目凭证 点击左上角用户名,选择下拉菜单里的我的凭证项目凭证:28add376c01e4a61ac8b621c714bf459【2】创建IAM用户鼠标放在左上角头像上,在下拉菜单里选择统一身份认证。点击左上角创建用户。创建成功:【3】创建完成用户信息如下:主用户名 l19504562721IAM用户 ds_abc密码 DS123456782.9 获取影子数据帮助文档:cid:link_5设备影子介绍:设备影子是一个用于存储和检索设备当前状态信息的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三、上位机开发为了方便查看设备上传的数据,接下来利用Qt开发一款Android手机APP 和 Windows上位机。使用华为云平台提供的API接口获取设备上传的数据,进行可视化显示,以及远程控制设备。3.1 Qt开发环境安装Qt的中文官网: cid:link_12QT5.12.6的下载地址:cid:link_9或者去网盘里下载:cid:link_10打开下载链接后选择下面的版本进行下载: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环境搭建的博客链接: cid:link_33.2 新建上位机工程前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。【1】新建工程【2】设置项目的名称。【3】选择编译系统【4】选择默认继承的类【5】选择编译器【6】点击完成【7】工程创建完成3.3 设计UI界面与工程配置【1】打开UI文件打开默认的界面如下:【2】开始设计界面根据自己需求设计界面。3.5 编译Windows上位机点击软件左下角的绿色三角形按钮进行编译运行。编译之后的效果:3.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四、STM32代码开发4.1 MQTT协议设计代码字数过多,无法显示...4.2 OLED显示屏驱动代码#include "oled.h"#include "stdlib.h"#include "oledfont.h" #include "delay.h"//OLED模式设置//0: 4线串行模式 (模块的BS1,BS2均接GND)//1: 并行8080模式 (模块的BS1,BS2均接VCC)#define OLED_MODE 1 //---------------------------OLED端口定义-------------------------- #define OLED_CS PDout(6)#define OLED_RST PGout(15) #define OLED_RS PDout(3)#define OLED_WR PGout(14) #define OLED_RD PGout(13) //PC0~7,作为数据线#define DATAOUT(x) GPIOC->ODR=(GPIOC->ODR&0xff00)|(x&0x00FF); //输出 //使用4线串行接口时使用 #define OLED_SCLK PCout(0)#define OLED_SDIN PCout(1) #define OLED_CMD 0 //写命令#define OLED_DATA 1 //写数据//OLED控制用函数void OLED_WR_Byte(u8 dat,u8 cmd); void OLED_Display_On(void);void OLED_Display_Off(void);void OLED_Refresh_Gram(void); void OLED_Init(void);void OLED_Clear(void);void OLED_DrawPoint(u8 x,u8 y,u8 t);void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size); //OLED的显存//存放格式如下.//[0]0 1 2 3 ... 127 //[1]0 1 2 3 ... 127 //[2]0 1 2 3 ... 127 //[3]0 1 2 3 ... 127 //[4]0 1 2 3 ... 127 //[5]0 1 2 3 ... 127 //[6]0 1 2 3 ... 127 //[7]0 1 2 3 ... 127 u8 OLED_GRAM[128][8]; //更新显存到LCD void OLED_Refresh_Gram(void){ u8 i,n; for(i=0;i<8;i++) { OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7) OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); } }#if OLED_MODE==1 //8080并口 //向SSD1306写入一个字节。//dat:要写入的数据/命令//cmd:数据/命令标志 0,表示命令;1,表示数据;void OLED_WR_Byte(u8 dat,u8 cmd){ DATAOUT(dat); OLED_RS=cmd; OLED_CS=0; OLED_WR=0; OLED_WR=1; OLED_CS=1; OLED_RS=1; } #else//向SSD1306写入一个字节。//dat:要写入的数据/命令//cmd:数据/命令标志 0,表示命令;1,表示数据;void OLED_WR_Byte(u8 dat,u8 cmd){ u8 i; OLED_RS=cmd; //写命令 OLED_CS=0; for(i=0;i<8;i++) { OLED_SCLK=0; if(dat&0x80)OLED_SDIN=1; else OLED_SDIN=0; OLED_SCLK=1; dat<<=1; } OLED_CS=1; OLED_RS=1; } #endif //开启OLED显示 void OLED_Display_On(void){ OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON}//关闭OLED显示 void OLED_Display_Off(void){ OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF} //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!! void OLED_Clear(void) { u8 i,n; for(i=0;i<8;i++)for(n=0;n<128;n++)OLED_GRAM[n][i]=0X00; OLED_Refresh_Gram();//更新显示}//画点 //x:0~127//y:0~63//t:1 填充 0,清空 void OLED_DrawPoint(u8 x,u8 y,u8 t){ u8 pos,bx,temp=0; if(x>127||y>63)return;//超出范围了. pos=7-y/8; bx=y%8; temp=1<<(7-bx); if(t)OLED_GRAM[x][pos]|=temp; else OLED_GRAM[x][pos]&=~temp; }//x1,y1,x2,y2 填充区域的对角坐标//确保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63 //dot:0,清空;1,填充 void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot) { u8 x,y; for(x=x1;x<=x2;x++) { for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot); } OLED_Refresh_Gram();//更新显示}//在指定位置显示一个字符,包括部分字符//x:0~127//y:0~63//mode:0,反白显示;1,正常显示 //size:选择字体 12/16/24void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode){ u8 temp,t,t1; u8 y0=y; u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数 chr=chr-' ';//得到偏移后的值 for(t=0;t { if(size==12)temp=asc2_1206[chr][t]; //调用1206字体 else if(size==16)temp=asc2_1608[chr][t]; //调用1608字体 else if(size==24)temp=asc2_2412[chr][t]; //调用2412字体 else return; //没有的字库 for(t1=0;t1<8;t1++) { if(temp&0x80)OLED_DrawPoint(x,y,mode); else OLED_DrawPoint(x,y,!mode); temp<<=1; y++; if((y-y0)==size) { y=y0; x++; break; } } } }//m^n函数u32 mypow(u8 m,u8 n){ u32 result=1; while(n--)result*=m; return result;} //显示2个数字//x,y :起点坐标 //len :数字的位数//size:字体大小//mode:模式 0,填充模式;1,叠加模式//num:数值(0~4294967295); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size){ u8 t,temp; u8 enshow=0; for(t=0;t { temp=(num/mypow(10,len-t-1))%10; if(enshow==0&&t<(len-1)) { if(temp==0) { OLED_ShowChar(x+(size/2)*t,y,' ',size,1); continue; }else enshow=1; } OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); }} //显示字符串//x,y:起点坐标 //size:字体大小 //*p:字符串起始地址 void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size){ while((*p<='~')&&(*p>=' '))//判断是不是非法字符! { if(x>(128-(size/2))){x=0;y+=size;} if(y>(64-size)){y=x=0;OLED_Clear();} OLED_ShowChar(x,y,*p,size,1); x+=size/2; p++; } } //初始化SSD1306 void OLED_Init(void){ RCC->APB2ENR|=1<<4; //使能PORTC时钟 RCC->APB2ENR|=1<<5; //使能PORTD时钟 RCC->APB2ENR|=1<<8; //使能PORTG时钟 GPIOD->CRL&=0XF0FF0FFF;//PD3,6 推挽输出 GPIOD->CRL|=0X03003000; GPIOD->ODR|=1<<3; GPIOD->ODR|=1<<6; #if OLED_MODE==1 //8080并口模式 GPIOC->CRL=0X33333333; //PC0~7 OUT GPIOC->ODR|=0X00FF; GPIOG->CRH&=0X000FFFFF; //PG13,14,15 OUT GPIOG->CRH|=0X33300000; GPIOG->ODR|=7<<13; #else //4线SPI模式 GPIOC->CRL&=0XFFFFFF00; //PC0,1 OUT GPIOC->CRL|=0X00000033; GPIOC->ODR|=3<<0; GPIOG->CRH&=0X0FFFFFFF; //RST GPIOG->CRH|=0X30000000; GPIOG->ODR|=1<<15;#endif OLED_CS=1; OLED_RS=1; OLED_RST=0; delay_ms(100); OLED_RST=1; OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示 OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率 OLED_WR_Byte(80,OLED_CMD); //[3:0],分频因子;[7:4],震荡频率 OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数 OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移 OLED_WR_Byte(0X00,OLED_CMD); //默认为0 OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数. OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置 OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭 OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式 OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127; OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置 OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置 OLED_WR_Byte(0x81,OLED_CMD); //对比度设置 OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮) OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期 OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2; OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率 OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示 OLED_WR_Byte(0xAF,OLED_CMD); //开启显示 OLED_Clear();} 4.3 DHT11温湿度模块驱动代码#include "dht11.h"#include "delay.h"//IO方向设置#define DHT11_IO_IN() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=8<<12;}#define DHT11_IO_OUT() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=3<<12;}////IO操作函数 #define DHT11_DQ_OUT PGout(11) //数据端口 PG11 #define DHT11_DQ_IN PGin(11) //数据端口 PG11u8 DHT11_Init(void); //初始化DHT11u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度u8 DHT11_Read_Byte(void); //读出一个字节u8 DHT11_Read_Bit(void); //读出一个位u8 DHT11_Check(void); //检测是否存在DHT11void DHT11_Rst(void); //复位DHT11 //复位DHT11void DHT11_Rst(void) { DHT11_IO_OUT(); //SET OUTPUT DHT11_DQ_OUT=0; //拉低DQ delay_ms(20); //拉低至少18ms DHT11_DQ_OUT=1; //DQ=1 delay_us(30); //主机拉高20~40us}//等待DHT11的回应//返回1:未检测到DHT11的存在//返回0:存在u8 DHT11_Check(void) { u8 retry=0; DHT11_IO_IN();//SET INPUT while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; else retry=0; while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; return 0;}//从DHT11读取一个位//返回值:1/0u8 DHT11_Read_Bit(void) { u8 retry=0; while(DHT11_DQ_IN&&retry<100)//等待变为低电平 { retry++; delay_us(1); } retry=0; while(!DHT11_DQ_IN&&retry<100)//等待变高电平 { retry++; delay_us(1); } delay_us(40);//等待40us if(DHT11_DQ_IN)return 1; else return 0; }//从DHT11读取一个字节//返回值:读到的数据u8 DHT11_Read_Byte(void) { u8 i,dat; dat=0; for (i=0;i<8;i++) { dat<<=1; dat|=DHT11_Read_Bit(); } return dat;}//从DHT11读取一次数据//temp:温度值(范围:0~50°)//humi:湿度值(范围:20%~90%)//返回值:0,正常;1,读取失败u8 DHT11_Read_Data(u8 *temp,u8 *humi) { u8 buf[5]; u8 i; DHT11_Rst(); if(DHT11_Check()==0) { for(i=0;i<5;i++)//读取40位数据 { buf[i]=DHT11_Read_Byte(); } if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { *humi=buf[0]; *temp=buf[2]; } }else return 1; return 0; }//初始化DHT11的IO口 DQ 同时检测DHT11的存在//返回1:不存在//返回0:存在 u8 DHT11_Init(void){ RCC->APB2ENR|=1<<8; //使能PORTG口时钟 GPIOG->CRH&=0XFFFF0FFF;//PORTG.11 推挽输出 GPIOG->CRH|=0X00003000; GPIOG->ODR|=1<<11; //输出1 DHT11_Rst(); return DHT11_Check();}4.4 定时器配置代码#include "timer.h"#include "led.h"//定时器3中断服务程序 void TIM3_IRQHandler(void){ if(TIM3->SR&0X0001)//溢出中断 { LED1=!LED1; } TIM3->SR&=~(1<<0);//清除中断标志位 }//通用定时器3中断初始化//这里时钟选择为APB1的2倍,而APB1为36M//arr:自动重装值。//psc:时钟预分频数//这里使用的是定时器3!void TIM3_Int_Init(u16 arr,u16 psc){ RCC->APB1ENR|=1<<1; //TIM3时钟使能 TIM3->ARR=arr; //设定计数器自动重装值//刚好1ms TIM3->PSC=psc; //预分频器7200,得到10Khz的计数时钟 TIM3->DIER|=1<<0; //允许更新中断 TIM3->CR1|=0x01; //使能定时器3 MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占1,子优先级3,组2 }//TIM3 PWM部分初始化 //PWM输出初始化//arr:自动重装值//psc:时钟预分频数void TIM3_PWM_Init(u16 arr,u16 psc){ //此部分需手动修改IO口设置 RCC->APB1ENR|=1<<1; //TIM3时钟使能 RCC->APB2ENR|=1<<3; //使能PORTB时钟 GPIOB->CRL&=0XFF0FFFFF; //PB5输出 GPIOB->CRL|=0X00B00000; //复用功能输出 RCC->APB2ENR|=1<<0; //开启辅助时钟 AFIO->MAPR&=0XFFFFF3FF; //清除MAPR的[11:10] AFIO->MAPR|=1<<11; //部分重映像,TIM3_CH2->PB5 TIM3->ARR=arr; //设定计数器自动重装值 TIM3->PSC=psc; //预分频器不分频 TIM3->CCMR1|=7<<12; //CH2 PWM2模式 TIM3->CCMR1|=1<<11; //CH2预装载使能 TIM3->CCER|=1<<4; //OC2 输出使能 TIM3->CR1=0x0080; //ARPE使能 TIM3->CR1|=0x01; //使能定时器3 } //定时器5通道1输入捕获配置//arr:自动重装值//psc:时钟预分频数void TIM5_Cap_Init(u16 arr,u16 psc){ RCC->APB1ENR|=1<<3; //TIM5 时钟使能 RCC->APB2ENR|=1<<2; //使能PORTA时钟 GPIOA->CRL&=0XFFFFFFF0; //PA0 清除之前设置 GPIOA->CRL|=0X00000008; //PA0 输入 GPIOA->ODR|=0<<0; //PA0 下拉 TIM5->ARR=arr; //设定计数器自动重装值 TIM5->PSC=psc; //预分频器 TIM5->CCMR1|=1<<0; //CC1S=01 选择输入端 IC1映射到TI1上 TIM5->CCMR1|=0<<4; //IC1F=0000 配置输入滤波器 不滤波 TIM5->CCMR1|=0<<10; //IC2PS=00 配置输入分频,不分频 TIM5->CCER|=0<<1; //CC1P=0 上升沿捕获 TIM5->CCER|=1<<0; //CC1E=1 允许捕获计数器的值到捕获寄存器中 TIM5->DIER|=1<<1; //允许捕获中断 TIM5->DIER|=1<<0; //允许更新中断 TIM5->CR1|=0x01; //使能定时器2 MY_NVIC_Init(2,0,TIM5_IRQn,2);//抢占2,子优先级0,组2 }//捕获状态//[7]:0,没有成功的捕获;1,成功捕获到一次.//[6]:0,还没捕获到高电平;1,已经捕获到高电平了.//[5:0]:捕获高电平后溢出的次数u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态 u16 TIM5CH1_CAPTURE_VAL; //输入捕获值//定时器5中断服务程序 void TIM5_IRQHandler(void){ u16 tsr; tsr=TIM5->SR; if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获 { if(tsr&0X01)//溢出 { if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了 { if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了 { TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次 TIM5CH1_CAPTURE_VAL=0XFFFF; }else TIM5CH1_CAPTURE_STA++; } } if(tsr&0x02)//捕获1发生捕获事件 { if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿 { TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽 TIM5CH1_CAPTURE_VAL=TIM5->CCR1; //获取当前的捕获值. TIM5->CCER&=~(1<<1); //CC1P=0 设置为上升沿捕获 }else //还未开始,第一次捕获上升沿 { TIM5CH1_CAPTURE_STA=0; //清空 TIM5CH1_CAPTURE_VAL=0; TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿 TIM5->CNT=0; //计数器清空 TIM5->CCER|=1<<1; //CC1P=1 设置为下降沿捕获 } } } TIM5->SR=0;//清除中断标志位 }五、总结本项目致力于构建一套基于STM32F103RCT6微控制器和NBIOT-BC26通信模块的农作物生长管理系统。系统的核心功能在于实时监测农田环境中的关键参数,例如土壤含水量、环境光照强度、温度与湿度,并据此实现自动化管理。具体而言,系统能够依据预设的阈值自动控制补水,同时允许用户通过本地按钮或远程控制的方式进行手动补水。此外,系统还能根据环境亮度的变化自动调节补光灯的亮度,以保证植物获得适宜的光照条件。为了保障作物生长环境的安全性,系统配置了环境温湿度传感器(DHT11),并在检测到温度或湿度超出预设范围时触发蜂鸣器报警。这些阈值可以通过手机应用程序进行设定,使得系统更加灵活易用。系统还配备了一块0.96寸的OLED显示屏,用于实时显示环境温度、湿度、土壤含水量以及光照强度等重要参数,使用户能够直观地了解当前的环境状况。考虑到突然的温度变化可能导致误操作,系统采用了适当的控制策略,在设定温度出现较大波动时延迟系统的反应调节时间,从而避免不必要的误动作。整个设备通过NBIOT-BC26模块连接至华为云IoT平台,并采用MQTT协议上传数据,使得用户能够借助微信小程序远程监控设备状态并进行控制,包括调整设备的温度阈值等设置。项目利用Qt框架开发了一个Android版的应用程序,进一步增强了系统的远程监控与控制能力。该应用程序不仅可以显示远程监测数据,还可以让用户随时随地控制设备的各项功能。供电方面,系统选择了简便且易于获取的USB线5V供电方案,方便用户安装与使用。该项目通过集成先进的传感器技术和物联网应用,为现代化农业生产提供了一套高效、便捷且智能化的解决方案。
  • [技术干货] 【华为云IOT实践】基于STM32设计的通信机房空调与新风系统联动装置
    一、前言1.1 项目介绍项目设计里用到的全部工具软件和文档源码,都可以在这里下载。https://pan.quark.cn/s/145a9b3f7f53【1】项目开发背景随着信息技术的发展,数据中心和通信机房作为信息处理的核心枢纽,在各行各业中扮演着越来越重要的角色。然而,由于设备密集且持续运行,机房内的温度和湿度控制成为保证设备正常运行的关键因素之一。传统的机房环境管理方式往往依赖于定期的人工巡检和手动调节,这种方式不仅效率低下,而且容易出现管理疏漏,导致设备过热或湿度过高,进而影响设备的稳定性和使用寿命。针对这一现状,本项目提出了一种基于STM32微控制器的通信机房空调与新风系统联动装置的设计方案。通过集成环境温湿度传感器、风扇控制模块以及4G通信模块,实现了对机房环境的实时监测和智能调控。特别是在数据传输方面,借助华为云IOT物联网云平台的强大功能,不仅可以远程监控机房内的环境变化,还能及时调整空调和新风系统的运行策略,确保机房环境始终处于最佳状态。同时,考虑到实际应用中的便捷性,本装置还提供了本地控制功能,用户可以通过直观的OLED显示屏了解当前环境状态,并通过简单的按键操作来调整设置,增强了用户体验。通过这样的设计,本项目不仅解决了传统机房管理中存在的问题,还极大地提高了机房环境管理的智能化水平,为机房管理人员提供了一个高效、可靠、易于操作的解决方案,进一步保障了通信设备的稳定运行,降低了运维成本。【2】设计实现的功能(1)实时采集环境参数:系统配备了DHT11温湿度传感器,能够连续不断地获取机房内的环境温度和湿度数据。(2)风扇通风散热控制:通过继电器模块控制两个5V直流电机小风扇的启停,以达到通风散热的效果,确保机房内部温度维持在安全范围内。(3)数据远程上传:利用合宙Air724UG 4G模块,将环境监测数据上传至华为云IOT物联网云平台,实现数据的云端存储与管理。(4)远程监控与控制:用户可以通过Windows客户端应用程序实时查看机房内的温度、湿度、风扇状态等信息,并能远程设置温度湿度阈值,选择运行模式(自动/手动),以及远程控制风扇的开关。(5)本地控制功能:在没有网络连接的情况下,用户依然可以通过设备上的按键来控制风扇的开关状态,以及切换设备的运行模式。(6)OLED显示屏信息展示:装置上安装了一个0.96寸的OLED显示屏,用于显示两页信息:第一页显示环境温度、湿度、温度和湿度阈值;第二页显示设备运行模式和风扇的开关状态。【3】项目硬件模块组成(1)主控模块:采用STM32F103RCT6微控制器作为核心处理器,负责处理各种传感器数据、控制输出信号以及与外部设备的通信。(2)环境监测模块:使用DHT11温湿度传感器来实时采集机房内的温度和湿度数据,为后续的环境控制提供依据。(3)显示模块:配置了一块0.96寸的OLED显示屏,通过SPI协议接口与主控芯片相连,用于显示当前环境的温度、湿度、设定阈值以及设备的运行状态。(4)风扇控制模块:包括两个5V直流电机的小风扇,通过继电器控制风扇的启动和停止,实现通风散热功能。(5)通信模块:采用合宙Air724UG 4G模块,支持全网通4G网络,用于将采集到的数据上传至华为云IOT物联网云平台,同时也支持接收来自云端的控制指令。(6)人机交互模块:包括若干个按键,允许用户进行本地控制,如设置温度湿度阈值、切换设备运行模式以及控制风扇的开关。(7)电源模块:系统使用一个5V 2A的稳压模块来提供稳定的电力供应,确保各个模块的正常工作。这些硬件模块相互协作,共同构成了一个完整的智能通信机房空调与新风系统联动装置,能够实现环境参数的实时监测、数据的远程传输与控制、以及本地的人机交互等功能。【4】需求总结项目名称: 基于STM32设计的通信机房空调与新风系统联动装置​支持的功能:(1) 能够实时采集环境温度与湿度。(2) 能够控制2个风扇进行通风扇热。(3) 设备端的数据通过4G网络上传到华为云IOT物联网云平台。(4) 能够通过Windows大屏远程查看设备上传的温度、湿度,风扇开关状态,设备运行的模式。(5) 能够通过Windows大屏远程设置温度和湿度阀值,设置运行模式(自动模式和手动模式),以及控制风扇的开启和关闭。(6) 本地设备也可以通过按键控制风扇的开关,以及切换设备运行模式。(7) 本地设备带了一个0.96寸的OLED显示屏,一共显示2个页面。 第一个页面显示: 可以显示环境的温度、环境的湿度、温度阀值、湿度阀值。 第二个页面显示: 设备运行模式、风扇的开关状态。​硬件选型:(1)主控芯片选择STM32F103RCT6(2)OLED显示屏选择SPI协议接口的0.96寸OLED显示屏。(3)4G模块采用合宙的Air724UG 4G模块,支持全网通4G网络。(4)环境温湿度检测采用DHT11模块。(5)风扇采用5V直流电机的小风扇,通过继电器控制风扇的开和关。(6)系统电源采用5V 2A的稳压模块进行供电,提供稳定电源。1.2 设计思路本项目的设计思路主要围绕提高通信机房环境管理的智能化和自动化水平展开。鉴于机房内设备众多且运行负荷大的特点,环境温湿度的实时监测显得尤为重要。选择了DHT11温湿度传感器来持续采集环境数据,确保能够及时反映机房内的温度和湿度变化情况。为了有效控制机房内的温度,设计中引入了风扇控制模块。通过继电器模块来驱动5V直流电机的小风扇,可以根据采集到的温度数据自动或手动控制风扇的开关,以此来调节机房内的温度,防止设备因过热而损坏。考虑到远程监控的需求,选用了合宙Air724UG 4G模块,通过4G网络将环境数据上传至华为云IOT物联网云平台,使得管理者能够在任何地点通过Windows客户端实时查看机房状态,并作出相应的调整。在本地控制方面,为了便于现场工作人员的操作,设计中加入了按键控制功能。通过简单的按键输入,可以实现风扇开关的控制以及设备运行模式的切换。为了使用户能够直观地了解当前机房的状态,项目中还配备了一块0.96寸的OLED显示屏,用以显示环境参数、设置阈值及设备运行模式等重要信息。系统整体采用STM32F103RCT6作为主控芯片,该芯片具备足够的性能来处理传感器数据、执行控制逻辑以及管理与其他模块之间的通信。为了保证系统的稳定运行,采用了5V 2A的稳压电源模块来提供可靠的电力供应。本项目的设计思路是在充分理解通信机房环境管理需求的基础上,结合现代物联网技术和智能控制手段,构建一个集数据采集、分析处理、远程监控与本地控制于一体的智能化管理系统,以提高机房环境管理的效率和可靠性。1.3 系统功能总结功能类别描述环境监测实时采集机房内的温度和湿度数据,使用DHT11模块。风扇控制通过继电器模块控制两个5V直流风扇的启停,实现通风散热。数据传输利用合宙Air724UG 4G模块将环境数据上传至华为云IOT物联网云平台。远程监控通过Windows客户端远程查看温度、湿度、风扇状态及设备运行模式。远程设置允许用户远程设置温度和湿度阈值,切换运行模式(自动/手动),控制风扇开关。本地控制提供按键输入功能,实现风扇开关控制及设备运行模式切换。显示信息OLED显示屏显示环境参数、设置阈值、设备运行模式及风扇状态。电源管理采用5V 2A稳压电源模块,确保系统稳定运行。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)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。二、部署华为云物联网平台华为云官网: cid:link_9打开官网,搜索物联网,就能快速找到 设备接入IoTDA。2.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。2.2 开通物联网服务地址: cid:link_8点击立即创建。正在创建标准版实例,需要等待片刻。创建完成之后,点击实例名称。 可以看到标准版实例的设备接入端口和地址。在上面也能看到 免费单元的限制。开通之后,点击总览,也能查看接入信息。 我们当前设备准备采用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=93117.78.5.125 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),往返行程的估计时间(以毫秒为单位): 最短 = 35ms,最长 = 39ms,平均 = 36msC:\Users\11266>MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。2.3 创建产品(1)创建产品(2)填写产品信息根据自己产品名字填写,下面的设备类型选择自定义类型。(3)产品创建成功创建完成之后点击查看详情。(4)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。模型简单来说: 就是存放设备上传到云平台的数据。你可以根据自己的产品进行创建。比如:烟雾可以叫 MQ2温度可以叫 Temperature湿度可以叫 humidity火焰可以叫 flame其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。先点击自定义模型。再创建一个服务ID。接着点击新增属性。2.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备(2)根据自己的设备填写(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。(4)设备创建完成(5)设备详情2.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_6业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_6对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down 最终的格式:$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_2根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$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}}]}2.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_1MQTT协议的端口支持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鉴权三元组: cid:link_7打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911Username 663cb18871d845632a0912e7_dev1Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac2372.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。点击详情页面,可以看到上传的数据:到此,云平台的部署已经完成,设备已经可以正常上传数据了。(3)MQTT登录测试参数总结MQTT服务器: 117.78.5.125MQTT端口号: 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}}]}2.8 创建IAM账户创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。地址: cid:link_4【1】获取项目凭证 点击左上角用户名,选择下拉菜单里的我的凭证项目凭证:28add376c01e4a61ac8b621c714bf459【2】创建IAM用户鼠标放在左上角头像上,在下拉菜单里选择统一身份认证。点击左上角创建用户。创建成功:【3】创建完成用户信息如下:主用户名 l19504562721IAM用户 ds_abc密码 DS123456782.9 获取影子数据帮助文档:cid:link_5设备影子介绍:设备影子是一个用于存储和检索设备当前状态信息的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三、上位机开发为了方便查看设备上传的数据,接下来利用Qt开发一款Android手机APP 和 Windows上位机。使用华为云平台提供的API接口获取设备上传的数据,进行可视化显示,以及远程控制设备。3.1 Qt开发环境安装Qt的中文官网: https://www.qt.io/zh-cn/QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6或者去网盘里下载:https://pan.quark.cn/s/145a9b3f7f53打开下载链接后选择下面的版本进行下载: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环境搭建的博客链接: cid:link_33.2 新建上位机工程前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。【1】新建工程【2】设置项目的名称。【3】选择编译系统【4】选择默认继承的类【5】选择编译器【6】点击完成【7】工程创建完成3.3 设计UI界面与工程配置【1】打开UI文件打开默认的界面如下:【2】开始设计界面根据自己需求设计界面。3.5 编译Windows上位机点击软件左下角的绿色三角形按钮进行编译运行。编译之后的效果:3.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四、STM32代码开发4.1 MQTT协议设计代码字数过多,无法显示...4.2 OLED显示屏驱动代码#include "oled.h"#include "stdlib.h"#include "oledfont.h" #include "delay.h"//OLED模式设置//0: 4线串行模式 (模块的BS1,BS2均接GND)//1: 并行8080模式 (模块的BS1,BS2均接VCC)#define OLED_MODE 1 //---------------------------OLED端口定义-------------------------- #define OLED_CS PDout(6)#define OLED_RST PGout(15) #define OLED_RS PDout(3)#define OLED_WR PGout(14) #define OLED_RD PGout(13) //PC0~7,作为数据线#define DATAOUT(x) GPIOC->ODR=(GPIOC->ODR&0xff00)|(x&0x00FF); //输出 //使用4线串行接口时使用 #define OLED_SCLK PCout(0)#define OLED_SDIN PCout(1) #define OLED_CMD 0 //写命令#define OLED_DATA 1 //写数据//OLED控制用函数void OLED_WR_Byte(u8 dat,u8 cmd); void OLED_Display_On(void);void OLED_Display_Off(void);void OLED_Refresh_Gram(void); void OLED_Init(void);void OLED_Clear(void);void OLED_DrawPoint(u8 x,u8 y,u8 t);void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size); //OLED的显存//存放格式如下.//[0]0 1 2 3 ... 127 //[1]0 1 2 3 ... 127 //[2]0 1 2 3 ... 127 //[3]0 1 2 3 ... 127 //[4]0 1 2 3 ... 127 //[5]0 1 2 3 ... 127 //[6]0 1 2 3 ... 127 //[7]0 1 2 3 ... 127 u8 OLED_GRAM[128][8]; //更新显存到LCD void OLED_Refresh_Gram(void){ u8 i,n; for(i=0;i<8;i++) { OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7) OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); } }#if OLED_MODE==1 //8080并口 //向SSD1306写入一个字节。//dat:要写入的数据/命令//cmd:数据/命令标志 0,表示命令;1,表示数据;void OLED_WR_Byte(u8 dat,u8 cmd){ DATAOUT(dat); OLED_RS=cmd; OLED_CS=0; OLED_WR=0; OLED_WR=1; OLED_CS=1; OLED_RS=1; } #else//向SSD1306写入一个字节。//dat:要写入的数据/命令//cmd:数据/命令标志 0,表示命令;1,表示数据;void OLED_WR_Byte(u8 dat,u8 cmd){ u8 i; OLED_RS=cmd; //写命令 OLED_CS=0; for(i=0;i<8;i++) { OLED_SCLK=0; if(dat&0x80)OLED_SDIN=1; else OLED_SDIN=0; OLED_SCLK=1; dat<<=1; } OLED_CS=1; OLED_RS=1; } #endif //开启OLED显示 void OLED_Display_On(void){ OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON}//关闭OLED显示 void OLED_Display_Off(void){ OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF} //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!! void OLED_Clear(void) { u8 i,n; for(i=0;i<8;i++)for(n=0;n<128;n++)OLED_GRAM[n][i]=0X00; OLED_Refresh_Gram();//更新显示}//画点 //x:0~127//y:0~63//t:1 填充 0,清空 void OLED_DrawPoint(u8 x,u8 y,u8 t){ u8 pos,bx,temp=0; if(x>127||y>63)return;//超出范围了. pos=7-y/8; bx=y%8; temp=1<<(7-bx); if(t)OLED_GRAM[x][pos]|=temp; else OLED_GRAM[x][pos]&=~temp; }//x1,y1,x2,y2 填充区域的对角坐标//确保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63 //dot:0,清空;1,填充 void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot) { u8 x,y; for(x=x1;x<=x2;x++) { for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot); } OLED_Refresh_Gram();//更新显示}//在指定位置显示一个字符,包括部分字符//x:0~127//y:0~63//mode:0,反白显示;1,正常显示 //size:选择字体 12/16/24void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode){ u8 temp,t,t1; u8 y0=y; u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数 chr=chr-' ';//得到偏移后的值 for(t=0;t { if(size==12)temp=asc2_1206[chr][t]; //调用1206字体 else if(size==16)temp=asc2_1608[chr][t]; //调用1608字体 else if(size==24)temp=asc2_2412[chr][t]; //调用2412字体 else return; //没有的字库 for(t1=0;t1<8;t1++) { if(temp&0x80)OLED_DrawPoint(x,y,mode); else OLED_DrawPoint(x,y,!mode); temp<<=1; y++; if((y-y0)==size) { y=y0; x++; break; } } } }//m^n函数u32 mypow(u8 m,u8 n){ u32 result=1; while(n--)result*=m; return result;} //显示2个数字//x,y :起点坐标 //len :数字的位数//size:字体大小//mode:模式 0,填充模式;1,叠加模式//num:数值(0~4294967295); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size){ u8 t,temp; u8 enshow=0; for(t=0;t { temp=(num/mypow(10,len-t-1))%10; if(enshow==0&&t<(len-1)) { if(temp==0) { OLED_ShowChar(x+(size/2)*t,y,' ',size,1); continue; }else enshow=1; } OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); }} //显示字符串//x,y:起点坐标 //size:字体大小 //*p:字符串起始地址 void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size){ while((*p<='~')&&(*p>=' '))//判断是不是非法字符! { if(x>(128-(size/2))){x=0;y+=size;} if(y>(64-size)){y=x=0;OLED_Clear();} OLED_ShowChar(x,y,*p,size,1); x+=size/2; p++; } } //初始化SSD1306 void OLED_Init(void){ RCC->APB2ENR|=1<<4; //使能PORTC时钟 RCC->APB2ENR|=1<<5; //使能PORTD时钟 RCC->APB2ENR|=1<<8; //使能PORTG时钟 GPIOD->CRL&=0XF0FF0FFF;//PD3,6 推挽输出 GPIOD->CRL|=0X03003000; GPIOD->ODR|=1<<3; GPIOD->ODR|=1<<6; #if OLED_MODE==1 //8080并口模式 GPIOC->CRL=0X33333333; //PC0~7 OUT GPIOC->ODR|=0X00FF; GPIOG->CRH&=0X000FFFFF; //PG13,14,15 OUT GPIOG->CRH|=0X33300000; GPIOG->ODR|=7<<13; #else //4线SPI模式 GPIOC->CRL&=0XFFFFFF00; //PC0,1 OUT GPIOC->CRL|=0X00000033; GPIOC->ODR|=3<<0; GPIOG->CRH&=0X0FFFFFFF; //RST GPIOG->CRH|=0X30000000; GPIOG->ODR|=1<<15;#endif OLED_CS=1; OLED_RS=1; OLED_RST=0; delay_ms(100); OLED_RST=1; OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示 OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率 OLED_WR_Byte(80,OLED_CMD); //[3:0],分频因子;[7:4],震荡频率 OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数 OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移 OLED_WR_Byte(0X00,OLED_CMD); //默认为0 OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数. OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置 OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭 OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式 OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127; OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置 OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置 OLED_WR_Byte(0x81,OLED_CMD); //对比度设置 OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮) OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期 OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2; OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率 OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示 OLED_WR_Byte(0xAF,OLED_CMD); //开启显示 OLED_Clear();} 4.3 DHT11温湿度模块驱动代码#include "dht11.h"#include "delay.h"//IO方向设置#define DHT11_IO_IN() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=8<<12;}#define DHT11_IO_OUT() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=3<<12;}////IO操作函数 #define DHT11_DQ_OUT PGout(11) //数据端口 PG11 #define DHT11_DQ_IN PGin(11) //数据端口 PG11u8 DHT11_Init(void); //初始化DHT11u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度u8 DHT11_Read_Byte(void); //读出一个字节u8 DHT11_Read_Bit(void); //读出一个位u8 DHT11_Check(void); //检测是否存在DHT11void DHT11_Rst(void); //复位DHT11 //复位DHT11void DHT11_Rst(void) { DHT11_IO_OUT(); //SET OUTPUT DHT11_DQ_OUT=0; //拉低DQ delay_ms(20); //拉低至少18ms DHT11_DQ_OUT=1; //DQ=1 delay_us(30); //主机拉高20~40us}//等待DHT11的回应//返回1:未检测到DHT11的存在//返回0:存在u8 DHT11_Check(void) { u8 retry=0; DHT11_IO_IN();//SET INPUT while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; else retry=0; while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; return 0;}//从DHT11读取一个位//返回值:1/0u8 DHT11_Read_Bit(void) { u8 retry=0; while(DHT11_DQ_IN&&retry<100)//等待变为低电平 { retry++; delay_us(1); } retry=0; while(!DHT11_DQ_IN&&retry<100)//等待变高电平 { retry++; delay_us(1); } delay_us(40);//等待40us if(DHT11_DQ_IN)return 1; else return 0; }//从DHT11读取一个字节//返回值:读到的数据u8 DHT11_Read_Byte(void) { u8 i,dat; dat=0; for (i=0;i<8;i++) { dat<<=1; dat|=DHT11_Read_Bit(); } return dat;}//从DHT11读取一次数据//temp:温度值(范围:0~50°)//humi:湿度值(范围:20%~90%)//返回值:0,正常;1,读取失败u8 DHT11_Read_Data(u8 *temp,u8 *humi) { u8 buf[5]; u8 i; DHT11_Rst(); if(DHT11_Check()==0) { for(i=0;i<5;i++)//读取40位数据 { buf[i]=DHT11_Read_Byte(); } if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { *humi=buf[0]; *temp=buf[2]; } }else return 1; return 0; }//初始化DHT11的IO口 DQ 同时检测DHT11的存在//返回1:不存在//返回0:存在 u8 DHT11_Init(void){ RCC->APB2ENR|=1<<8; //使能PORTG口时钟 GPIOG->CRH&=0XFFFF0FFF;//PORTG.11 推挽输出 GPIOG->CRH|=0X00003000; GPIOG->ODR|=1<<11; //输出1 DHT11_Rst(); return DHT11_Check();}五、总结本项目设计一种基于STM32微控制器的智能通信机房空调与新风系统联动装置,以提升通信机房的环境管理效率和自动化水平。该装置能够实时监测机房内的环境温度和湿度,并根据预设阈值自动控制风扇进行散热,从而保持机房内部环境的适宜条件。系统利用合宙Air724UG 4G模块将采集到的数据上传至华为云IOT物联网云平台,支持远程监控及控制功能。通过Windows客户端界面,管理员可以实时查看机房内环境参数,调整设定值,并控制风扇的工作状态。此外,本地操作也得到了考虑,通过简单的按键输入即可完成风扇控制和工作模式的切换。为了方便现场人员查看当前状态,设备还配备了一块0.96寸的OLED显示屏,用于显示环境参数、设置值及运行状态等信息。整个系统由一个5V 2A的稳压电源模块供电,确保了系统的稳定性和可靠性。此项目的实施有助于提高通信机房的智能化管理水平,减少人工干预,降低维护成本。
  • [技术干货] 华为云开发者云主机体验
    一、前言云主机是华为云为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具体系,让每一位开发者拥有一台云主机,基于华为根生态开发、创新。二、领取云主机领取地址:cid:link_0申请之后,会提示加入开发者空间,根据提示选择配置就行了。配置之后。在这里可以进去云主机的公众空间。第一次配置,初始化需要一点时间。初始化完毕之后,就可以进去桌面体验了。下面是进入桌面的提示状态。进去桌面后的效果。直接在浏览器里访问很方面。 非常方便在Linux环境下做项目开发测试。对于复制粘贴,本地电脑不能直接copy,需要使用工具进行安全的复制粘贴。三、快速体验YOLOV3YOLOv3(You Only Look Once version 3)是一种高效的目标检测算法,由Joseph Redmon等人开发。它是YOLO系列算法的第三个版本,旨在实现快速而准确的对象检测。与传统的两阶段目标检测方法(如R-CNN系列算法)不同,YOLOv3采用了一次性(single-shot)检测方法,这意味着它可以在一个单一的神经网络前向传播过程中同时完成对象分类和边界框回归。YOLOv3的主要特性包括:主干网络:YOLOv3使用Darknet-53作为其基础网络,这是对YOLOv2中Darknet-19的一个深度增强版,提供了更好的特征提取能力。多尺度检测:YOLOv3采用了特征金字塔网络(Feature Pyramid Network,FPN)的概念,实现了在三个不同的尺度上进行检测,分别是13x13、26x26和52x53的特征图分辨率,这有助于检测不同大小的对象。锚框(Anchor Boxes):YOLOv3使用预定义的锚框来预测对象的位置,这些锚框是通过对训练数据集中对象的尺寸进行聚类得到的。分类器:在对象分类方面,YOLOv3使用Logistic回归来预测每个类别的概率,这允许它处理包含多个标签的对象,即一个对象可能属于多个类别的情况。损失函数:YOLOv3的损失函数综合考虑了边界框坐标、对象存在性和类别预测的误差。训练和预测:YOLOv3能够在单张图像上同时预测多个对象,且由于其一次性检测的特性,它能实现实时处理速度,非常适合实时视频流处理等场景。YOLOv3因其在速度与精度之间的良好平衡而在工业界和学术界得到了广泛应用。随着后续版本如YOLOv4和YOLOv5的推出,虽然YOLOv3可能不再是最新版本,但它仍然是理解和实现现代目标检测算法的重要基准。YOLO算法官网介绍:https://pjreddie.com/darknet/yolo/You only look once (YOLO) is a state-of-the-art, real-time object detection system. On a Pascal Titan X it processes images at 30 FPS and has a mAP of 57.9% on COCO test-dev.You Only Look Once (YOLO) 是最先进的实时目标检测系统。在 Pascal Titan X 上,它以 30 FPS 处理图像,并且在 COCO 测试开发上的 mAP 为 57.9%。Comparison to Other Detectors YOLOv3 is extremely fast and accurate. In mAP measured at .5 IOU YOLOv3 is on par with Focal Loss but about 4x faster. Moreover, you can easily tradeoff between speed and accuracy simply by changing the size of the model, no retraining required!与其他探测器的比较 YOLOv3 非常快速且准确。在 mAP 中,测量结果为 0.5 IOU YOLOv3 与 Focal Loss 相当,但速度快约 4 倍。此外,只需更改模型的大小即可轻松在速度和准确性之间进行权衡,无需重新训练!在Linux下快速体验YOLO算法的目标检测(采用官方的模型)。(1)安装darknetgit clone https://github.com/pjreddie/darknetcd darknetmake如果克隆失败,多试几次即可。编译中:(2)下载权重文件wget https://pjreddie.com/media/files/yolov3.weightsyolov3.weights 是 YOLOv3 网络训练得到的权重文件,存储了神经网络中每个层次的权重和偏置信息。在cfg/目录下已经包含了yolov3对应的配置文件。权重文件下载中:(3)运行detector./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg运行输出的信息:layer filters size input output 0 conv 32 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 32 0.299 BFLOPs 1 conv 64 3 x 3 / 2 416 x 416 x 32 -> 208 x 208 x 64 1.595 BFLOPs ....... 105 conv 255 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 255 0.353 BFLOPs 106 detectiontruth_thresh: Using default '1.000000'Loading weights from yolov3.weights...Done!data/dog.jpg: Predicted in 0.029329 seconds.dog: 99%truck: 93%bicycle: 99%运行过程:查看原来图片:查看识别成功的图片:识别测试2:./darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights <video file>如果想实时识别视频,可以运行下面的命令:./darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights <video file>
  • [技术干货] 【华为云IOT】基于香橙派 AIpro 设计的医院人脸红外测温系统(从0开始开发)
    一、前言在公共卫生事件频发的当下,尤其是在全球性疫情爆发后,国家对公共空间的健康监测和管理提出了更高的要求。医院、疾病防控中心和发热门诊作为疫情防控的第一线,需要高效且精准地对进出人员进行健康筛查,以防止病毒传播,保障医护人员及患者的安全。传统的手动体温检测方式不仅效率低下,而且存在交叉感染的风险,开发一种能够自动、快速、准确地进行人体温度监测与身份识别的系统显得非常的重要。当前文章会完整的介绍,如何采用香橙派AIpro设计出一套医院人脸红外测温系统。香橙派AIpro是一款高性价比的边缘计算设备,搭载了昇腾 AI 处理器,可提供 8TOPS INT8 的计算能力,能够运行Ubuntu 22.04操作系统,这为部署复杂的深度学习算法提供了硬件基础。本系统利用OpenCV和SSD算法模型进行人脸检测,通过各方面的模型训练,能确保在复杂光线和遮挡条件下仍能有效识别个体;结合红外测温技术,可以非接触式地测量额头温度,避免了传统接触式测文章温可能带来的卫生问题。考虑到环境因素对测温结果的影响,系统还配备了温湿度传感器,以实时监测并校准测温数据。为了实现数据的实时监控与分析,系统通过MQTT协议将收集到的信息上传至华为云物联网云平台,便于远程监控和数据分析,有助于疫情趋势的预测和资源的合理调配。本项目整体提供了一个智能化、自动化的人脸识别与体温监测解决方案,以提高公共卫生领域的响应速度和防控效率,减少人力资源的投入,同时降低潜在的感染风险,为构建安全健康的医疗环境贡献力量。本项目在完成最终的功能开发前,会先单个完成模块的功能开发,实现了单个模块功能之后,最终在整体合在一起实现最终的项目开发。整体项目会从搭建环境开始, 一步一步实现最终的项目效果。下面的开发出来的最终人脸检测设备最终设计效果:二、主控板介绍香橙派 AIpro开发板是香橙派联合华为精心打造的高性能 AI 开发板,搭载了昇腾 AI 处理器,可提供 8TOPS INT8 的计算能力,内存提供了 8GB 和 16GB两种版本。可以实现图像、视频等多种数据分析与推理计算,可广泛用于教育、机器人、无人机等场景。下面是香橙派 AIpro开发板的配置说明:模块规格昇腾 AI 处理器4 核 64 位 Arm 处理器 + AI 处理器AI 算力半精度(FP16):4 TFLOPS整数精度(INT8):8 TOPS内存类型:LPDDR4X 容量:8GB 或 16GB存储板载 32MB 的 SPI FlashMicro SD 卡插槽eMMC 插座:可外接 eMMC 模块M.2 M-Key 接口:可接 2280 规格的 NVMe SSD 或 SATA SSD以太网支持 10/100/1000Mbps板载 PHY 芯片:RTL8211FWi-Fi+蓝牙支持 2.4G 和 5G 双频 WIFIBT4.2模组:欧智通 62221BUUCUSB2 个 USB3.0 Host 接口1 个 Type-C 接口(只支持 USB3.0,不支持 USB2.0)摄像头2 个 MIPI CSI 2 Lane 接口显示2 个 HDMI 接口 1 个 MIPI DSI 2 Lane 接口音频一个 3.5mm 耳机孔,支持音频输入输出2 个 HDMI 音频输出40 pin 扩展口用于扩展 UART、I2C、SPI、PWM 和 GPIO 等接口按键一个复位键,一个关机键,一个升级按键拨码开关2 个拨码开关:用于控制 SD 卡、eMMC 和 SSD 启动选项电源支持 Type-C 供电,20V PD-65W 适配器LED 灯一个电源指示灯和一个软件可控指示灯风扇接口4pin,0.8mm 间距,用于接 12V 风扇,支持 PWM 控制电池接口2pin,2.54mm 间距,用于接 3 串电池,支持快充调试串口Micro USB 接口的调试串口支持的操作系统Ubuntu 22.04 和 openEuler 22.03外观规格介绍产品尺寸:107*68mm 重量:82g下面是香橙派 AIpro开发板的功能模块介绍:三、搭建开发环境3.1 准备需要的配件(1)准备一张至少32G的TFT卡,用来烧写系统。(2)准备一个读卡器,方便插入TFT卡,好方便插入到电脑上拷贝系统(3)香橙派 AIpro 主板一个(4)一根网线(方便插路由器上与香橙派 AIpro 连接)(5)一根type-C的电源线 + 电源插头(3A电流),这个主板买回来是带了电源的。 也可以用自己Android手机的数据线就行,拿手机充电器供电,因为目前Android手机电源线都是都是type-C 也支持快充的,电流也是满足需求的。(6)一个USB摄像头,用于后续项目开发里获取周围的实时图像,识别人脸。 (项目开发需要使用)(7)一个串口协议的红外测温传感器,用于后续项目开发里测量体温。(项目开发需要使用)(8)一个外放音箱,支持3.5mm的耳机插孔,方便后续项目开发里播放语音提示。(项目开发需要使用)(9)一块显示屏(这个不是必须的,可以直接Windows远程桌面访问系统,对前期开发来说没有任何影响,只要做成最终的产品才需要配屏幕)。3.2 开发板实物图拿回来的香橙派 AIpro 开发板实物是这样的。3.3 下载开发板资料拿到板子之后,第一件事肯定是先去官网下载板子对应的相关的资料。比如:用户手册、系统镜像、原理图、开发工具什么的。官网地址:cid:link_1翻到下面,找到资料下载地址,直接下载就行,下载会跳转到网盘。系统镜像我选择的 ubuntu22.04。资料下载下来之后,可以看到有一份官方的说明文档,指导板子的基本使用。如何烧写系统,如何启动系统等等。3.4 下载系统烧写工具链接:cid:link_0下载下来之后,直接双击正常安装,安装好之后打开的界面如下。 (选择本地制作)然后将 TF卡通过读卡器插到电脑上,准备烧写系统(就算有些电脑自带了TF卡的插槽也建议用USB读卡器,这个电脑自带的TF卡槽烧写系统无法启动)。 TF卡的容量至少要32G,最好是64G。选择要烧写的系统镜像文件(就是刚刚通过网盘下载的ubuntu系统镜像)。然后点击烧录镜像。弹出提示框,选择确认。然后可以看到,系统正在烧写中了,精心等待即可。烧录成功之后,会弹窗提示弹出SD卡。将TF卡从电脑上弹出,拔掉就行了。3.5 设置开发板启动模式香橙派 AIpro开发板支持从 TF 卡、eMMC 和 SSD(支持 NVMe SSD 和 SATA SSD)启动。具体从哪个设备启动是由开发板背面的两个拨码(BOOT1 和 BOOT2)开关来控制的。BOOT1 和 BOOT2 两个拨码开关都支持左右两种设置状态,所以总共有 4 种设置状态,开发板目前只使用了其中的三种。不同的设置状态对应的启动设备如下表所示:将BOOT1 和 BOOT2 两个拨码开关 全部拨到靠右的位置就可以选择从TF卡启动了。3.6 启动系统【1】将烧写好的TF卡插在板子上。【2】插好网线。网线一端接开发板的网口,一端接路由器,自己的笔记本电脑也是接的同一个路由器,让板子与电脑在同一个局域网内,方便接下来远程登录开发板的系统。【3】插好电源板子是没有电源开关的,电源线插好之后,系统就启动了。刚启动的时候风扇的声音会比较大,等待几秒就正常了。按下开发板左上角的PWR_OFF可以关闭系统,点击旁边的RESET可以重启系统。3.7 SSH远程登录系统系统启动之后,会自动请求路由器分配IP地址,我们只需要登录到开发板连接的路由器后台,就可以看到新接入的设备。我的用是小米路由器。直接在浏览器里输入:192.168.31.1 即可进入到路由器的后台终端。从下面图片里可以看到,香橙派 AIpro 已经分配到IP地址了,192.168.31.136。为了方便远程登录到系统终端,可以下载安装一个FinalShell 软件。下载地址:cid:link_4软件安装打开后,建立一个新的SSH链接,具体看下图的操作。这里面的主机IP地址就是从路由器后台看到的,分配给香橙派 AIpro开发板的IP地址。 端口号是固定的22,这是SSH协议的固定端口。用户名是root,密码是:Mind@123 这是烧写的香橙派 AIpro系统固定的用户名和密码,也就是系统内置的。双击刚才建立好的链接,就可以登录到系统终端。 进去终端之后基本上就可以进行正常的开发了。3.8 安装xdrp工具为了方便图形化方式开发,可以使用windows系统通过远程桌面登录香橙派 AIpro,就可以看到界面了,不过需要先安装工具。进入到香橙派 AIpro终端之后,输入安装命令:sudo apt-get install xrdp按下回车之后,会弹出确认窗口。输入 y之后,按下回车,继续安装。下面是完整的命令安装过程: 按顺序执行就行了。#安装xrdpsudo apt-get install xrdp#安装vnc4serversudo apt-get install vnc4server tightvncserver#安装xubuntu-desktopsudo apt-get install xubuntu-desktop#向xsession中写入xfce4-sessionecho “xfce4-session” >~/.xsession#开启xrdp服务sudo service xrdp restart注意,如果之后断电了,远程桌面无法链接上。可以先卸载xrdp,再重新安装即可。sudo apt-get remove --purge xrdp3.9 Window远程登录在windows上打开运行命令的窗口,输入mstsc来打开远程桌面。输入mstsc,点击确定。弹出窗口后,填入IP地址(这就是你的香橙派 AIpro 开发板的IP地址),点击连接。正常登录之后,就可以看到远程桌面的界面了。输入账号和密码。用户名是root,密码是:Mind@123 这是烧写的香橙派 AIpro系统固定的用户名和密码,也就是系统内置的。登录之后的界面:好了。接下来就可以进行项目的正式开发了。3.10 取消自动休眠Ubuntu桌面镜像会自动休眠,输入以下指令禁用休眠。sudo systemctl status sleep.targetsudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target四、安装Qt开发环境因为最终的项目需要使用界面,我的项目准备采用Qt进行开发,这一章节进行安装Qt开发环境。4.1 安装qtcreator在命令行终端分别输入以下命令安装qtcreator:root@orangepiaipro:~# sudo apt-get updateroot@orangepiaipro:~# sudo apt-get install qtcreatorroot@orangepiaipro:~# sudo apt-get install qtmultimedia5-devroot@orangepiaipro:~# sudo apt-get install libqt5serialport5-dev4.2 启动qtcreator安装好之后,就可以看到Qt软件了,点击即可打开。这是启动之后的效果。4.3 配置编译器默认安装Qt之后,编译器是配置错误,无法正常使用。【1】打开设置页面【2】选择编译器套件【3】配置编译器套件。将编译器改为GCC。4.4 新建工程Qt环境【1】新建工程【2】设置工程名称和路径(自己单独建立一个文件夹存放工程)【3】选择继承的基类【4】选择编译器套件【5】创建完成这就是创建好的工程。【6】点击左下角绿色三角形编译运行。下面是正常运行的效果,已经弹窗窗口,说明Qt的环境已经OK了。五、开发板初步测试为了了解下开发板本身的性能,先采用开发板开发一些小项目测试测试效果。现在系统根目录创建一个work目录,方便存放接下来的项目文件。5.1 项目1:开发一个基于HTTP协议的网络摄像头【1】项目介绍本项目主要采用C语言开发,实现了一个网络摄像头项目,在橙派 AIpro开发板上实现了一个HTTP服务器,,处理浏览器的请求,当浏览器访问过来时,就将本地采集到的摄像头画面发送给浏览器,与浏览器建立长连接通信,直接传输JPG图片,实现摄像头画面实时显示效果。支持登录页面,做了一个账号登录界面,访问服务器之后需要输入账号密码才可以正常进入服务器查看共享的画面。如果分享个摄像头画面,那是非常的方便的,想要查看分享的摄像头画面只需要浏览器里输入服务器的IP地址登录进去就可以看画面了。通过本项目的测试,可以了解到USB摄像头的读取效果,网络传输的效果。 为后续的其他项目开发做一个参考。【2】编写项目代码项目开发,代码编写先在Windows下进行,开发完毕,再拷贝到橙派 AIpro开发板上。这是在Windows下开发好的项目代码:【3】上传项目代码打开FinalShell终端,可以直接将开发好的项目源码,整个目录上传到香橙派 AIpro系统。通过FinalShell终端可以很方便的将香橙派 AIpro系统文件下载到本地,也可以将本地的文件很方便的上传上去。在开发项目的阶段是很方便的。【4】插入USB摄像头将USB摄像头插入到开发板的USB口,然后ls /dev/video* 查看摄像头的设备节点,确定摄像头是否识别成功。【4】编译运行项目项目里已经构建好了Makefile文件,直接make就可以编译。编译之后,运行项目。(base) root@orangepiaipro:~/work/http_camera# make(base) root@orangepiaipro:~/work/http_camera# ./http_app ./server <server_port> </dev/videoX>(base) root@orangepiaipro:~/work/http_camera# ./http_app 666 /dev/video0 运行命令的含义:./http_app 666 /dev/video0 666表示服务器的端口号。 /dev/video0是摄像头的端口号。【5】浏览器访问在自己电脑浏览器地址栏里输入:http://192.168.31.136:666就可以看到登录页面。登录成功之后,可以看到摄像头的实时画面。5.2 项目2: 基于华为云设计的智能家居控制系统【1】项目介绍基于香橙派 AIpro开发板设计的智能家居控制系统,通过MQTT协议连接华为云物联网云平台;通过DHT11传感器读取环境温湿度,将数据上传到华为云物联网云平台。在华为云云平台上也可以远程控制硬件端连接的LED灯,控制3种颜色显示。通过本项目的完整开发测试,可以掌握香橙派的GPIO口的基本使用以及网络的测试。为后续的其他项目开发做一个参考测试。【2】安装wiringPi(1)安装 wiringOP 前,请先确保 Linux 系统中存在/etc/orangepi-release 这个配置文件,里面的内容为:BOARD=orangepiaipro。(base) root@orangepiaipro:~/work/# cat /etc/orangepi-releaseBOARD=orangepiaipro(2)如果 Linux 中没有/etc/orangepi-release 这个配置文件,可以使用下面的命令创建一个。(base) root@orangepiaipro:~/work/# echo "BOARD=orangepiaipro" | sudo tee /etc/orangepi-release(3)下载 wiringOP 的代码。(base) root@orangepiaipro:~/work/# sudo apt-get update(base) root@orangepiaipro:~/work/# sudo apt-get install -y git(base) root@orangepiaipro:~/work/# git clone https://github.com/orangepi-xunlong/wiringOP.git -b next(4)然后编译安装 wiringOP。(base) root@orangepiaipro:~/work/# sudo apt-get install -y gcc make build-essential(base) root@orangepiaipro:~/work/# cd wiringOP(base) root@orangepiaipro:~/work/wiringOP# sudo ./build clean(base) root@orangepiaipro:~/work/wiringOP# sudo ./build(5)编译完之后,可以看到生成的文件查看GPIO口信息。(base) root@orangepiaipro:~/work/wiringOP# cd gpio/(base) root@orangepiaipro:~/work/wiringOP/gpio# ./gpio readall +------+-----+----------+--------+---+ AI PRO +---+--------+----------+-----+------+ | GPIO | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | GPIO | +------+-----+----------+--------+---+----++----+---+--------+----------+-----+------+ | | | 3.3V | | | 1 || 2 | | | 5V | | | | 76 | 0 | SDA7 | OFF | 0 | 3 || 4 | | | 5V | | | | 75 | 1 | SCL7 | OFF | 0 | 5 || 6 | | | GND | | | | 226 | 2 | GPIO7_02 | OFF | 0 | 7 || 8 | 0 | OFF | UTXD0 | 3 | 14 | | | | GND | | | 9 || 10 | 0 | OFF | URXD0 | 4 | 15 | | 82 | 5 | GPIO2_18 | OFF | 0 | 11 || 12 | 0 | OFF | GPIO7_03 | 6 | 227 | | 38 | 7 | GPIO1_06 | IN | 1 | 13 || 14 | | | GND | | | | 79 | 8 | GPIO2_15 | IN | 1 | 15 || 16 | 1 | IN | GPIO2_16 | 9 | 80 | | | | 3.3V | | | 17 || 18 | 0 | IN | GPIO0_25 | 10 | 25 | | 91 | 11 | SPI0_SD0 | OFF | 0 | 19 || 20 | | | GND | | | | 92 | 12 | SPI0_SDI | OFF | 0 | 21 || 22 | 1 | IN | GPIO0_02 | 13 | 2 | | 89 | 14 | SPI0_CLK | OFF | 0 | 23 || 24 | 0 | OFF | SPI0_CS | 15 | 90 | | | | GND | | | 25 || 26 | 0 | IN | GPIO2_19 | 16 | 83 | | | | SDA6 | | | 27 || 28 | | | SCL6 | | | | 231 | 17 | URXD7 | OFF | 0 | 29 || 30 | | | GND | | | | 84 | 18 | GPIO2_20 | IN | 0 | 31 || 32 | 0 | IN | GPIO1_01 | 19 | 35 | | 128 | 20 | GPIO4_00 | IN | 1 | 33 || 34 | | | GND | | | | 228 | 21 | GPIO7_04 | OFF | 0 | 35 || 36 | 0 | OFF | GPIO2_17 | 22 | 81 | | 3 | 23 | GPIO0_03 | IN | 1 | 37 || 38 | 0 | IN | GPIO7_06 | 24 | 230 | | | | GND | | | 39 || 40 | 0 | OFF | GPIO7_05 | 25 | 229 | +------+-----+----------+--------+---+----++----+---+--------+----------+-----+------+ | GPIO | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | GPIO | +------+-----+----------+--------+---+ AI PRO +---+--------+----------+-----+------+可以通过命令行测试GPIO口:(base) root@orangepiaipro:~/work# ./gpio mode 2 out 设置 wPi 为2的这个IO口为输出模式(base) root@orangepiaipro:~/work# ./gpio write 2 0 设置 wPi 为2的这个IO口输出0 (低电平)(base) root@orangepiaipro:~/work# ./gpio write 2 1 设置 wPi 为2的这个IO口输出1 (高电平)【3】GPIO口布局【4】控制LED灯编写的测试代码:#include <stdio.h>#include <wiringPi.h>#include <stdlib.h>#include <string.h>/*控制继电器高低电平亮灯*/#define LEDG 0#define LEDB 1#define LEDR 2int main(){ wiringPiSetup(); //置引脚编号方式为wiringPi编码 pinMode(LEDG,OUTPUT); pinMode(LEDB,OUTPUT); pinMode(LEDR,OUTPUT); while(1) { //全部关闭 digitalWrite(LEDG,LOW); digitalWrite(LEDB,LOW); digitalWrite(LEDR,LOW); //亮蓝色 digitalWrite(LEDG,HIGH); sleep(1); //全部关闭 digitalWrite(LEDG,LOW); digitalWrite(LEDB,LOW); digitalWrite(LEDR,LOW); //亮绿色 digitalWrite(LEDB,HIGH); sleep(1); //全部关闭 digitalWrite(LEDG,LOW); digitalWrite(LEDB,LOW); digitalWrite(LEDR,LOW); //亮红色 digitalWrite(LEDR,HIGH); sleep(1); } return 0;}编译代码:(base) root@orangepiaipro:~/work# gcc led.c -lwiringPi运行代码:(base) root@orangepiaipro:~/work# ./a.out 运行效果:【5】DHT11温湿度传感器数据读取代码:#include <wiringPi.h>#include <stdio.h>#include <stdlib.h> //编译:gcc -Wall -o dht11 dht11.c -lwiringPi -o app typedef unsigned char uint8;typedef unsigned int uint16;typedef unsigned long uint32; #define HIGH_TIME 32 int pinNumber = 5;uint32 databuf; uint8 readSensorData(void){ uint8 crc; uint8 i; pinMode(pinNumber, OUTPUT); // set mode to output digitalWrite(pinNumber, 0); // output a high level delay(25); digitalWrite(pinNumber, 1); // output a low level pinMode(pinNumber, INPUT); // set mode to input pullUpDnControl(pinNumber, PUD_UP); delayMicroseconds(27); if (digitalRead(pinNumber) == 0) //SENSOR ANS { while (!digitalRead(pinNumber)) ; //wait to high for (i = 0; i < 32; i++) { while (digitalRead(pinNumber)) ; //data clock start while (!digitalRead(pinNumber)) ; //data start delayMicroseconds(HIGH_TIME); databuf *= 2; if (digitalRead(pinNumber) == 1) //1 { databuf++; } } for (i = 0; i < 8; i++) { while (digitalRead(pinNumber)) ; //data clock start while (!digitalRead(pinNumber)) ; //data start delayMicroseconds(HIGH_TIME); crc *= 2; if (digitalRead(pinNumber) == 1) //1 { crc++; } } return 1; } else { return 0; }} int main(void){ printf("PIN:%d\n", pinNumber); wiringPiSetup(); //置引脚编号方式为wiringPi编码 pinMode(pinNumber, OUTPUT); // set mode to output digitalWrite(pinNumber, 1); // output a high level printf("Starting...\n"); while (1) { pinMode(pinNumber, OUTPUT); // set mode to output digitalWrite(pinNumber, 1); // output a high level delay(3000); if (readSensorData()) { printf("Sensor data read ok!\n"); printf("RH:%d.%d\n", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff); printf("TMP:%d.%d\n", (databuf >> 8) & 0xff, databuf & 0xff); databuf = 0; } else { printf("Sensor dosent ans!\n"); databuf = 0; } } return 0;}编译代码:(base) root@orangepiaipro:~/work# gcc dht11.c -lwiringPi运行代码:(base) root@orangepiaipro:~/work# ./a.out PIN:5Starting...RH:68.4TMP:30.4RH:68.5TMP:30.2RH:68.2TMP:30.4RH:68.1TMP:30.3RH:68.1TMP:30.6RH:68.1TMP:30.4实物图:【6】注册华为云设备华为云物联网平台的整体就不再详细展示了,可以直接看视频。B站的视频链接:cid:link_2(1)注册产品(2)注册设备(3)创建命令(4)得到MQTT三元组IP地址:117.78.5.125端口号:1883ClientId 6693872aa559ef6226685350_dev1_0_0_2024071408Username 6693872aa559ef6226685350_dev1Password 8ce1b26a6fac2c52402d2911a9a951efe6026f71041037a963bebdd4a099190f订阅主题:$oc/devices/6693872aa559ef6226685350_dev1/sys/messages/down发布主题:$oc/devices/6693872aa559ef6226685350_dev1/sys/properties/report发布数据:{"services": [{"service_id": "stm32","properties":{"DHT11_T":23,"DHT11_H":80}}]}【7】编写整体项目接下来就编写代码,连接华为云物联网平台,完成数据上传。 将采集的温湿度数据上传到华为云物联网云平台。 同时支持在华为云物联网平台下发命令远程控制设备端的LED灯。代码是采用纯C语言编写,实现了MQTT协议,完成了与物联网云平台交互。关于MQTT协议的整体编写过程,可以直接看视频:cid:link_3(1)这是写好的项目代码完整的代码:#include <stdio.h>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h> /* superset of previous */#include <arpa/inet.h>#include <poll.h>#include <unistd.h>#include <stdlib.h>#include <pthread.h>#include <signal.h>#include "mqtt.h"#include "main.h"#include <netdb.h>#include <stdio.h>#include <wiringPi.h>#include <stdlib.h>#include <string.h>/*控制继电器高低电平亮灯*/#define LEDG 0#define LEDB 1#define LEDR 2//服务器IP#define SERVER_IP "117.78.5.125"#define SERVER_PORT 1883 //端口号//MQTT三元组#define ClientID "6693872aa559ef6226685350_dev1_0_0_2024071408"#define Username "6693872aa559ef6226685350_dev1"#define Password "8ce1b26a6fac2c52402d2911a9a951efe6026f71041037a963bebdd4a099190f"//密文 //订阅主题:#define SET_TOPIC "$oc/devices/6693872aa559ef6226685350_dev1/sys/messages/down"//订阅//发布主题:#define POST_TOPIC "$oc/devices/6693872aa559ef6226685350_dev1/sys/properties/report"//发布char mqtt_message[1024*1024];//上报数据缓存区char request_id[100];char mqtt_cmd_message[100];char mqtt_cmd_data[100];int sockfd;/*获取平台下发数据*/void *pth_work_func(void *arg){ char buff[1024]; int size=0; int i=0; while(1) { size=Client_GetData(buff); printf("size=%d\r\n",size); if(size<0)break; for(i=0;i<size;i++) { printf("%c ",buff[i]); } buff[size]='\0'; if(size>5) { printf("%s\r\n",buff+5); if(strstr((char*)&buff[5],"sys/commands/request_id=")) { char *p=NULL; p=strstr((char*)&buff[5],"request_id"); if(p) { //解析数据 //$oc/devices/6210e8acde9933029be8facf_dev1/sys/properties/get/request_id=5f359b5c-542f-460e-9f51-85e82150ff4a{"service_id":"gps"} strncpy(request_id,p,47); } //上报数据 sprintf(mqtt_cmd_message,"{\"result_code\":0,\"response_name\":\"COMMAND_RESPONSE\",\"paras\":{\"result\":\"success\"}}"); sprintf(mqtt_cmd_data,"$oc/devices/6693872aa559ef6226685350_dev1/sys/commands/response/%s", request_id); MQTT_PublishData(mqtt_cmd_data,mqtt_cmd_message,0); printf("应答-发布主题:%s\r\n",mqtt_cmd_data); printf("应答-发布数据:%s\r\n",mqtt_cmd_message); } if(strstr((char*)&buff[5],"\"LED_SW\":1")) { //全部关闭 digitalWrite(LEDG,LOW); digitalWrite(LEDB,LOW); digitalWrite(LEDR,LOW); //亮蓝色 digitalWrite(LEDG,HIGH); } if(strstr((char*)&buff[5],"\"LED_SW\":2")) { //全部关闭 digitalWrite(LEDG,LOW); digitalWrite(LEDB,LOW); digitalWrite(LEDR,LOW); //亮绿色 digitalWrite(LEDB,HIGH); } if(strstr((char*)&buff[5],"\"LED_SW\":3")) { //全部关闭 digitalWrite(LEDG,LOW); digitalWrite(LEDB,LOW); digitalWrite(LEDR,LOW); //亮红色 digitalWrite(LEDR,HIGH); } if(strstr((char*)&buff[5],"\"LED_SW\":0")) { //全部关闭 digitalWrite(LEDG,LOW); digitalWrite(LEDB,LOW); digitalWrite(LEDR,LOW); } } printf("\r\n"); }} /*信号处理函数*/ void signal_func(int sig){ //printf("捕获的信号:%d\n",sig); if(sig==SIGALRM) { MQTT_SentHeart();//心跳包 alarm(5); }}typedef unsigned char uint8;typedef unsigned int uint16;typedef unsigned long uint32; #define HIGH_TIME 32 int pinNumber = 5;uint32 databuf; uint8 readSensorData(void){ uint8 crc; uint8 i; pinMode(pinNumber, OUTPUT); // set mode to output digitalWrite(pinNumber, 0); // output a high level delay(25); digitalWrite(pinNumber, 1); // output a low level pinMode(pinNumber, INPUT); // set mode to input pullUpDnControl(pinNumber, PUD_UP); delayMicroseconds(27); if (digitalRead(pinNumber) == 0) //SENSOR ANS { while (!digitalRead(pinNumber)) ; //wait to high for (i = 0; i < 32; i++) { while (digitalRead(pinNumber)) ; //data clock start while (!digitalRead(pinNumber)) ; //data start delayMicroseconds(HIGH_TIME); databuf *= 2; if (digitalRead(pinNumber) == 1) //1 { databuf++; } } for (i = 0; i < 8; i++) { while (digitalRead(pinNumber)) ; //data clock start while (!digitalRead(pinNumber)) ; //data start delayMicroseconds(HIGH_TIME); crc *= 2; if (digitalRead(pinNumber) == 1) //1 { crc++; } } return 1; } else { return 0; }} unsigned int DHT11_T;// 环境温度unsigned int DHT11_H;// 环境湿度int main(){ wiringPiSetup(); //置引脚编号方式为wiringPi编码 pinMode(LEDG,OUTPUT); pinMode(LEDB,OUTPUT); pinMode(LEDR,OUTPUT); //DHT11温湿度初始化 pinMode(pinNumber, OUTPUT); // set mode to output digitalWrite(pinNumber, 1); // output a high level int stat; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd==-1) { printf("网络套接字打开失败\n"); return 0; } signal(SIGPIPE,SIG_IGN);/*忽略SIGPIPE信号*/ signal(SIGALRM,signal_func);/*闹钟信号*/ /*连接服务器*/ struct sockaddr_in addr; addr.sin_family=AF_INET;//IPV4 addr.sin_port=htons(SERVER_PORT);/*端口号*/ addr.sin_addr.s_addr=inet_addr(SERVER_IP);//inet_addr(ip);//服务器IP if(connect(sockfd, (struct sockaddr *)&addr,sizeof(struct sockaddr_in))==0) { printf("server connect ok\n"); MQTT_Init(); while(1) { /*登录服务器*/ if(MQTT_Connect(ClientID,Username,Password)==0) { break; } sleep(1); printf("server connect ....\n"); } printf("MQTT_Connect OK\r\n"); //订阅物联网平台数据 stat=MQTT_SubscribeTopic(SET_TOPIC,1,1); if(stat) { close(sockfd); printf("MQTT_SubscribeTopic ERROR\r\n"); exit(0); } printf("MQTT_SubscribeTopic ok\r\n"); /*创建线程*/ pthread_t id; pthread_create(&id, NULL,pth_work_func,NULL); pthread_detach(id);//设置分离属性 //发送心跳包 // alarm(5);//闹钟函数,时间到达会产生SIGALRM信号 while(1) { //读取DHT11温湿度数据 pinMode(pinNumber, OUTPUT); // set mode to output digitalWrite(pinNumber, 1); // output a high level delay(3000); if (readSensorData()) { printf("DHT11 Sensor data read ok!\n"); printf("RH:%d.%d\n", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff); printf("TMP:%d.%d\n", (databuf >> 8) & 0xff, databuf & 0xff); //温度整数部分 DHT11_H=((databuf >> 24) & 0xff); printf("DHT11_T:%d\r\n",DHT11_T); //湿度整数部分 DHT11_T=((databuf >> 8) & 0xff); printf("DHT11_H:%d\r\n",DHT11_H); databuf = 0; } else { printf("Sensor dosent ans!\n"); databuf = 0; } //组合传感器状态数据 sprintf(mqtt_message,"{\"services\": [{\"service_id\": \"stm32\",\"properties\":{\"DHT11_T\":%d,\"DHT11_H\":%d}}]}",DHT11_T,DHT11_H);//温度 //上报数据 MQTT_PublishData(POST_TOPIC,mqtt_message,0); printf("MQTT_PublishData....\r\n"); sleep(2); } }}(2)这是编译运行后的效果(3)在华为云物联网平台后台,可以看到设备已经在线了,同时也实时收到设备端上传的数据。(4)下发命令测试。 通过命令下发控制设备端的LED灯。5.3 项目3:OpenCV+卷积神经网络实现人脸识别本项目通过OpenCV加载训练好的SSD模型,实现人脸检测,能够在图像中找到并标记出人脸的位置和置信度。通过本项目,可以验证整个系统的算法运行速度。为后续的项目开发做参考。(1)安装python (烧写的系统本身自带了完整的Python环境,可以不需要安装,如果没有才需要安装)sudo apt updatesudo apt install python3(2)编写代码加载模型识别人脸import cv2import numpy as npimport timeprototxt_path = "./deploy.prototxt.txt"model_path = "./res10_300x300_ssd_iter_140000_fp16.caffemodel"image_path = "6.jpg"# 加载模型model = cv2.dnn.readNetFromCaffe(prototxt_path, model_path)# 读取图像image = cv2.imread(image_path)h, w = image.shape[:2]# 准备模型输入的 blobblob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0), swapRB=False)# 设置 blob 作为模型的输入model.setInput(blob)# 进行推断并获取输出start_time = time.time()output = model.forward()end_time = time.time()# 遍历检测结果font_scale = 1.0for i in range(output.shape[2]): confidence = output[0, 0, i, 2] # 通过置信度阈值过滤弱检测结果 if confidence > 0.5: box = output[0, 0, i, 3:7] * np.array([w, h, w, h]) (start_x, start_y, end_x, end_y) = box.astype("int") # 绘制边界框和置信度 cv2.rectangle(image, (start_x, start_y), (end_x, end_y), (255, 0, 0), 2) text = f"{confidence * 100:.2f}%" cv2.putText(image, text, (start_x, start_y - 10), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 0, 0), 2)# 显示和保存带有检测结果的图像cv2.imwrite("beauty_detected.jpg", image)# 输出识别耗时print(f"识别耗时:{end_time - start_time:.3f} 秒")(3)运行效果(4)将图片下载下来打开5.4 项目4:OpenCV+YOLOv3实现目标检测本项目通过OpenCV加载YOLOV3官方的模型,实现目标。通过本项目,可以验证整个系统的算法运行速度。为后续的项目开发做参考。实现代码:#include <opencv2/opencv.hpp>#include <opencv2/dnn.hpp>#include <fstream>#include <iostream>#include <algorithm>#include <cstdlib>using namespace std;using namespace cv;using namespace cv::dnn;void image_detection();String yolo_cfg = "./yolov3.cfg";String yolo_model = "./yolov3.weights";int main(int argc, char** argv){ image_detection();}void image_detection() { //加载网络模型 Net net = readNetFromDarknet(yolo_cfg, yolo_model); //net.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE); net.setPreferableTarget(DNN_TARGET_CPU); std::vector<String> outNames = net.getUnconnectedOutLayersNames(); for (int i = 0; i < outNames.size(); i++) { printf("output layer name : %s\n", outNames[i].c_str()); } vector<string> classNamesVec; ifstream classNamesFile("./coco.names"); if (classNamesFile.is_open()) { string className = ""; while (std::getline(classNamesFile, className)) classNamesVec.push_back(className); } // 加载图像 Mat frame = imread("6.jpg"); Mat inputBlob = blobFromImage(frame, 1 / 255.F, Size(416, 416), Scalar(), true, false); net.setInput(inputBlob); // 检测 std::vector<Mat> outs; net.forward(outs, outNames); vector<double> layersTimings; double freq = getTickFrequency() / 1000; double time = net.getPerfProfile(layersTimings) / freq; ostringstream ss; ss << "detection time: " << time << " ms"; putText(frame, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255)); vector<Rect> boxes; vector<int> classIds; vector<float> confidences; for (size_t i = 0; i < outs.size(); ++i) { // Network produces output blob with a shape NxC where N is a number of // detected objects and C is a number of classes + 4 where the first 4 // numbers are [center_x, center_y, width, height] float* data = (float*)outs[i].data; for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols) { Mat scores = outs[i].row(j).colRange(5, outs[i].cols); Point classIdPoint; double confidence; minMaxLoc(scores, 0, &confidence, 0, &classIdPoint); if (confidence > 0.5) { int centerX = (int)(data[0] * frame.cols); int centerY = (int)(data[1] * frame.rows); int width = (int)(data[2] * frame.cols); int height = (int)(data[3] * frame.rows); int left = centerX - width / 2; int top = centerY - height / 2; classIds.push_back(classIdPoint.x); confidences.push_back((float)confidence); boxes.push_back(Rect(left, top, width, height)); } } } vector<int> indices; NMSBoxes(boxes, confidences, 0.5, 0.2, indices); for (size_t i = 0; i < indices.size(); ++i) { int idx = indices[i]; Rect box = boxes[idx]; String className = classNamesVec[classIds[idx]]; putText(frame, className.c_str(), box.tl(), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 0, 0), 2, 8); rectangle(frame, box, Scalar(0, 0, 255), 2, 8, 0); } # 保存结果图片 cv2.imwrite('detections.jpg', frame) waitKey(0); return;}将识别的图片结果拷贝下来,查看效果。五、测温项目开发5.1 外设模块选型需要用到的传感器如下:(1)LU90614非接触式红外测温模块(串口协议),用于测量体温。(2)DHT11温湿度传感器,用于测量环境的温湿度。(3)USB摄像头,用于捕获图像,检测人脸。(4)一块香橙派 AIpro主控板。(5)一个三色LED灯,用于显示检测的体温状态。 红、绿、蓝 三种颜色。5.2 整体的项目代码整体项目是采用Qt开发的,因为需要通过显示屏展示界面,在界面上显示人脸的识别效果,温度测量效果等信息。【1】技术实现方式说明(1)这里面的LU90614非接触式红外测温模块 采用USB-TTL模块接入系统的。在/dev目录下的节点是ttyUSB0。没有使用开发板本身的IO口。(2)本项目是先在Windows下开发完成后,再上传到香橙派 AIpro开发板运行,在香橙派 AIpro里安装了Qt的开发环境。(3)体温传感器的串口数据读取,没有采用Qt本身的串口接口,而是采用了标准Linux下的方式读取串口数据。(4)摄像头的采集没有采用Qt的内置接口,而是采用了Linux下V4L2框架完成的图像采集。(5)MQTT协议没有采用第三方库,是自己基于Linux下的socket,从0开始编写的。(6)显示屏采用HDMI接口的7寸显示屏。作为整个项目的界面终端。【2】摄像头图像采集代码下面是采集USB实时画面的代码。#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <errno.h>#include <sys/ioctl.h>#include <sys/mman.h>#include <linux/videodev2.h>#define WIDTH 640#define HEIGHT 480struct buffer { void *start; size_t length;};int pthread_run(){ int fd; struct v4l2_format fmt; struct v4l2_requestbuffers req; struct v4l2_buffer buf; enum v4l2_buf_type type; struct buffer *buffers; unsigned char *rgb888_buffer; // 打开摄像头设备 fd = open("/dev/video0", O_RDWR); if (fd == -1) { perror("打开/dev/video0失败"); return 1; } // 设置格式 memset(&fmt, 0, sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = WIDTH; fmt.fmt.pix.height = HEIGHT; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 使用YUYV格式,常见于USB摄像头 fmt.fmt.pix.field = V4L2_FIELD_NONE; if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { perror("设置格式失败"); close(fd); return 1; } // 请求缓冲区 memset(&req, 0, sizeof(req)); req.count = 1; // 缓冲区数量 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { perror("请求缓冲区失败"); close(fd); return 1; } // 分配并映射缓冲区 buffers = calloc(req.count, sizeof(*buffers)); if (!buffers) { perror("分配缓冲区内存失败"); close(fd); return 1; } // 查询缓冲区 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = 0; if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { perror("查询缓冲区失败"); close(fd); return 1; } // 内存映射 buffers[0].length = buf.length; buffers[0].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffers[0].start == MAP_FAILED) { perror("内存映射失败"); close(fd); return 1; } // 开始流式传输 type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) { perror("开始流式传输失败"); close(fd); return 1; } // 捕获循环(示例:捕获一帧) while (1) { // 入队缓冲区 if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { perror("入队缓冲区失败"); close(fd); return 1; } // 出队缓冲区 if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { perror("出队缓冲区失败"); close(fd); return 1; } // 处理帧(转换为RGB888格式) // 示例:将YUYV转换为RGB888 rgb888_buffer = (unsigned char *)malloc(WIDTH * HEIGHT * 3); for (int i = 0, j = 0; i < WIDTH * HEIGHT * 2; i += 4, j += 6) { // YUYV到RGB888的简化转换(实际应用中可能需要更复杂的算法) unsigned char Y0 = ((unsigned char *)buffers[0].start)[i + 0]; unsigned char U = ((unsigned char *)buffers[0].start)[i + 1]; unsigned char Y1 = ((unsigned char *)buffers[0].start)[i + 2]; unsigned char V = ((unsigned char *)buffers[0].start)[i + 3]; rgb888_buffer[j + 0] = Y0 + 1.402 * (V - 128); // 红色分量 rgb888_buffer[j + 1] = Y0 - 0.344 * (U - 128) - 0.714 * (V - 128); // 绿色分量 rgb888_buffer[j + 2] = Y0 + 1.772 * (U - 128); // 蓝色分量 rgb888_buffer[j + 3] = Y1 + 1.402 * (V - 128); // 红色分量 rgb888_buffer[j + 4] = Y1 - 0.344 * (U - 128) - 0.714 * (V - 128); // 绿色分量 rgb888_buffer[j + 5] = Y1 + 1.772 * (U - 128); // 蓝色分量 } // 使用rgb888_buffer进行进一步处理(如保存到文件、显示) // 示例:保存到文件 FILE *fp = fopen("frame.rgb", "wb"); if (fp) { fwrite(rgb888_buffer, 1, WIDTH * HEIGHT * 3, fp); fclose(fp); } free(rgb888_buffer); break; // 示例中仅处理一帧,所以退出循环 } // 停止流式传输 type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) { perror("停止流式传输失败"); close(fd); return 1; } // 解除内存映射 munmap(buffers[0].start, buf.length); // 清理和关闭 free(buffers); close(fd); return 0;}【3】体温数据采集(串口)下面是采集体温传感器的代码。#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <errno.h>#include <sys/select.h>#include <poll.h>#define SERIAL_DEVICE "/dev/ttyUSB0"#define BAUDRATE B9600int temp_read_pthread() { int fd; char *sendbuf = "\xFA\xC5\xBF"; // 发送体温模式指令 char recvbuf[8]; struct termios options; struct pollfd pfd; int timeout = 1000; // 超时时间,单位毫秒 // 打开串口设备文件 if ((fd = open(SERIAL_DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) { perror("open serial port failed"); exit(1); } // 设置串口参数 tcgetattr(fd, &options); cfmakeraw(&options); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_oflag &= ~OPOST; cfsetispeed(&options, BAUDRATE); cfsetospeed(&options, BAUDRATE); tcsetattr(fd, TCSANOW, &options); // 初始化poll结构体 pfd.fd = fd; pfd.events = POLLIN; // 发送命令 write(fd, sendbuf, 3); while (1) { // 使用poll等待数据 if (poll(&pfd, 1, timeout) > 0) { int nread = read(fd, recvbuf, sizeof(recvbuf)); if (nread > 0) { printf("Received data: "); for (int i = 0; i < nread; i++) { printf("%02x ", recvbuf[i]); } printf("\n"); } else { printf("Read error: %s\n", strerror(errno)); } } else { printf("No data received in %d ms\n", timeout); } } close(fd); return 0;}【4】人脸识别图像处理下面是完成人脸识别处理的代码。#include "image_handle.h"#pragma execution_character_set("utf-8")//关闭线程void ImageHandle::close(){ run_flag=0; this->quit(); this->wait();}//线程执行函数void ImageHandle::run(){ QImage use_image; while(run_flag) { //如果没有图像可以处理 if(start_run==0) { //休眠100毫秒 msleep(100); continue; } //表示已经处理过 start_run=0; //表示开始处理图像 Handle_flag=1; //调用图像处理算法 对 image 的图像进行处理 //1. 人脸识别 opencv_face(m_image); //处理完毕之后 //将图像传出去给UI界面显示 emit HandleSend(m_image); //处理完毕 Handle_flag=0; }}//传入待处理的图片数据void ImageHandle::SetImage(QImage &image){ if(Handle_flag==0) { start_run=1; //表示有图像可以处理了 //保存待处理的原图像 m_image=image; }}void printMatInfo(const cv::Mat& mat){ QTextStream out(stdout); out << "Type: " << mat.type() << endl; out << "Channels: " << mat.channels() << endl; out << "Size: " << mat.size().width << "x" << mat.size().height << endl; out << "Depth: " << mat.depth() << endl; out << "Element Size: " << mat.elemSize() << " bytes" << endl; out << "Total Size: " << mat.total() * mat.elemSize() << " bytes" << endl;}bool saveMatToFile(const cv::Mat& mat, const std::string& filename){ // 将cv::Mat保存为图像文件 bool success = cv::imwrite(filename, mat); if (!success) { // 保存失败时输出错误信息 std::cerr << "Failed to save image: " << filename << std::endl; } return success;}// 绘制马赛克void drawMosaic(Mat& image, Rect roi) { // 将人脸区域缩小为一定比例,以增加马赛克效果 Rect smallRoi = roi; smallRoi.x += smallRoi.width * 0.1; smallRoi.y += smallRoi.height * 0.1; smallRoi.width -= smallRoi.width * 0.2; smallRoi.height -= smallRoi.height * 0.2; // 对缩小后的人脸区域进行马赛克处理 Mat mosaic = image(smallRoi); //可以调整数字,调整马赛克的像素大小 resize(mosaic, mosaic, Size(smallRoi.width / 20, smallRoi.height / 20), INTER_NEAREST); resize(mosaic, image(smallRoi), smallRoi.size(), 0, 0, INTER_NEAREST);}#include "widget.h"//人脸检测代码void ImageHandle::opencv_face(QImage qImage){ QTime time; time.start(); //(1)包含必要的头文件和命名空间: //(2)加载人脸检测模型 std::string cnn_file_path= OpenCV_CNN_MODEL_FILE_PATH; //CNN模型文件路径 std::string prototxt_path = cnn_file_path+"/deploy.prototxt.txt"; std::string model_path = cnn_file_path+"/res10_300x300_ssd_iter_140000_fp16.caffemodel"; cv::dnn::Net model = cv::dnn::readNetFromCaffe(prototxt_path, model_path); //(3)加载图片: //Mat frame = imread("D:\\1.png"); // 替换为你的图片路径 Mat frame = QImage_to_cvMat(qImage); if (frame.empty()) { ss_log_text("待识别的图片加载失败...\n"); // 处理图片加载失败的情况 return; } int h = frame.rows; int w = frame.cols; cv::Mat blob = cv::dnn::blobFromImage(frame, 1.0, cv::Size(300, 300), cv::Scalar(104.0, 177.0, 123.0)); //(4)进行人脸检测: model.setInput(blob); cv::Mat output = model.forward(); cv::Mat detectionMat(output.size[2], output.size[3], CV_32F, output.ptr<float>()); //(5)给每个检测到的人脸绘制马赛克: int face_number=0; for (int i = 0; i < detectionMat.rows; ++i) { float confidence = detectionMat.at<float>(i, 2); if (confidence > 0.5) { //记录人脸数量 face_number++; int start_x = static_cast<int>(detectionMat.at<float>(i, 3) * w); int start_y = static_cast<int>(detectionMat.at<float>(i, 4) * h); int end_x = static_cast<int>(detectionMat.at<float>(i, 5) * w); int end_y = static_cast<int>(detectionMat.at<float>(i, 6) * h); // 马赛克处理 cv::Rect roi(start_x, start_y, end_x - start_x, end_y - start_y); cv::Mat face_roi = frame(roi); cv::resize(face_roi, face_roi, cv::Size(), 0.05, 0.05, cv::INTER_LINEAR); cv::resize(face_roi, frame(roi), roi.size(), 0, 0, cv::INTER_NEAREST); // 绘制边框和文字 cv::rectangle(frame, cv::Point(start_x, start_y), cv::Point(end_x, end_y), cv::Scalar(255, 0, 0), 2); std::ostringstream ss; ss << confidence * 100 << "%"; cv::putText(frame, ss.str(), cv::Point(start_x, start_y - 5), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 0, 0), 2); } } //传递出人脸数量 emit ss_face_number(face_number); // 在图像上显示识别消耗的时间 std::ostringstream time_ss; time_ss << "Time: " << time.elapsed() << " ms"; cv::putText(frame, time_ss.str(), cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 0, 0), 2); //转为QImage QImage out_image=Mat_to_QImage(frame); ss_log_text(tr("耗时:%1 ms\n").arg(time.elapsed())); //qDebug()<<"子线程:"<<QThread::currentThread(); //保存结果 m_image=out_image.copy();}QImage convertToRGB888(const QImage& image){ if (image.format() == QImage::Format_RGB888) { return image; // Already in RGB888 format } QImage convertedImage = image.convertToFormat(QImage::Format_RGB888); return convertedImage;}//可以用。 OpenCV4.0已测试。//Mat ImageHandle::QImage_to_cvMat(QImage image){ cv::Mat mat; //qDebug() << image.format(); switch(image.format()) { case QImage::Format_ARGB32: case QImage::Format_RGB32: case QImage::Format_ARGB32_Premultiplied: mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine()); break; case QImage::Format_RGB888: mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine()); //opencv 3.x以及一下,颜色用 CV_BGR2RGB cv::cvtColor(mat, mat, COLOR_BGR2RGB); break; case QImage::Format_Indexed8: mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine()); break; } return mat;}QImage ImageHandle::Mat_to_QImage(Mat mat){#if 0 QImage image; // 检查矩阵是否有效 if (!mat.empty()) { // 创建QImage对象,并分配内存 image = QImage(mat.cols, mat.rows, QImage::Format_ARGB32); // 根据Mat的类型和通道数来设置Qt图像格式 switch (mat.type()) { case CV_8UC4: image = QImage(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_ARGB32); break; case CV_8UC3: image = QImage(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_RGB888); break; case CV_8UC1: image = QImage(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_Indexed8); break; } // 对象回收 if (image.format() != QImage::Format_RGB32) { image = image.convertToFormat(QImage::Format_RGB32); } }#else // Check if the image is valid if (mat.empty()) return QImage(); // Convert the image color space cv::Mat rgbMat; cv::cvtColor(mat, rgbMat, cv::COLOR_BGR2RGB); // Create the QImage QImage image(rgbMat.data, rgbMat.cols, rgbMat.rows, static_cast<int>(rgbMat.step), QImage::Format_RGB888);#endif return image.copy();}​六、总结本项目利用香橙派 AIpro开发了一个创新的健康监测解决方案,能够提升医院、疾病防控中心和发热门诊等关键场所的公共卫生管理水平。系统利用香橙派AIpro的强大计算能力,搭载Ubuntu 22.04操作系统,实现了高效的人脸识别与非接触式体温测量功能,显著增强了疾病早期预警和控制的能力。通过整体项目开发完成后,这块基于香橙派 AIpro的性能是完全满足了要求;运行了10几个小时, 整个板子不发烫,只是启动的时候风扇有明显噪声,正常进入系统之后,风扇的声音就正常,基本处于静音状态。 板子构造小巧,很容易集成,进行项目开发。
  • [云实验室] 这个实验卡到最后几步了,GaussDB(DWS)云原生数仓:极致弹性重构传统数仓资源,有没有大佬把这个实验成功了
    卡到cd /opt/tpch_parallel这步了,报错无文件
  • [常见问题汇总帖] 无法生成vmlinux.h
    租了一个HECS(云耀云服务器)。版本下图所示。CONFIG_DEBUG_INFO_BTF状态开启。这是bpftool的版本也存在vmlinux文件这是我内核源码文件的截图。但是生成vmlinux.h时总会报错。
  • [问题求助] 基于openHarmony的智能可穿戴户外定位系统
    开发基于openHarmony的智能可穿戴户外定位系统,应该去哪里查看要购买的相应设备
  • [技术干货] 基于STM32+华为云IOT设计的智能垃圾桶
    一、项目介绍在商业街、小吃街和景区等人流密集的场所,垃圾桶的及时清理对于提供良好的游客体验至关重要。然而,传统的垃圾桶清理方式通常是定时或定期进行,无法根据实际情况进行及时响应,导致垃圾桶溢满,影响环境卫生,给游客带来不便和不满。为了解决这一问题,本项目基于STM32F103ZET6主控芯片和华为云物联网平台,设计了一套智能垃圾桶管理系统。该系统通过NBIOT-BC26模块连接到华为云物联网平台,实现了垃圾桶数据的实时采集和上传。在本地,垃圾桶通过多种传感器进行数据采集。使用DHT11模块实时监测环境温度和湿度,以了解垃圾桶所处环境的状态。采用中科微电子出品的GPS模块,通过串口输出GPS数据,实现垃圾桶的定位功能。垃圾桶口还配备了红外传感器,用于检测垃圾桶是否已满。通过NBIOT-BC26模块,采集到的数据被实时上传到华为云物联网平台。在保洁人员管理中心,开发了一个数据大屏,采用Qt开发,运行在Windows系统下。数据大屏展示了该区域内垃圾桶的详细情况,包括环境温度、湿度、GPS定位和垃圾桶的满溢状态。当垃圾桶满了时,上位机会实时发送短信通知保洁人员进行清理,并提供垃圾桶的位置信息,以便保洁人员快速响应并进行清理操作。通过这套智能垃圾桶管理系统,垃圾桶的清理可以根据实际情况进行及时调度,提高了垃圾桶的使用效率,改善了环境卫生状况,提升了游客的体验感。同时,保洁人员能够更加高效地管理垃圾桶,提升工作效率,减少资源浪费。整个系统的设计旨在提供一个智能、高效的垃圾桶管理解决方案,为公共场所的环境卫生管理带来便利和改进。二、设计思路总结2.1 硬件选型【1】主控芯片:STM32F103ZET6STM32F103系列是意法半导体(STMicroelectronics)推出的低功耗、高性能的32位ARM Cortex-M3微控制器系列。选择STM32F103ZET6作为主控芯片,是因为它具有较高的计算能力和丰富的外设接口,能够满足项目的需求。【2】通信模块:NBIOT-BC26NBIOT-BC26是一种窄带物联网(NB-IoT)通信模块,支持低功耗、广覆盖、远距离的物联网通信。它能够将垃圾桶采集到的数据通过NB-IoT网络上传到云平台,实现实时监测和远程管理。【3】传感器模块:DHT11模块:用于采集环境温度和湿度数据。DHT11是一种低成本、数字式温湿度传感器,具有简单的接口和良好的性能,适用于本项目的温湿度监测需求。GPS模块:用于实现垃圾桶的定位功能。选择中科微电子出品的GPS模块,通过串口输出GPS数据,能够准确获取垃圾桶的位置信息。红外传感器:用于检测垃圾桶是否已满。红外传感器能够通过红外线的反射来判断垃圾桶内是否有垃圾,从而判断垃圾桶的满溢状态。【5】数据大屏:采用Qt开发,运行在Windows系统下。数据大屏通过图形界面展示垃圾桶的详细情况,包括环境温度、湿度、GPS定位和垃圾桶的满溢状态。项目的硬件选型包括主控芯片、通信模块、传感器模块、红外传感器和数据大屏。这些硬件组件相互配合,实现了智能垃圾桶管理系统的功能,包括数据采集、通信传输、定位功能和信息展示。2.2 硬件设计【1】主控芯片选择: 作为整个系统的核心,选择了STM32F103ZET6作为主控芯片。该芯片具有较高的计算能力和丰富的外设接口,能够满足项目的需求。主控芯片负责与各个硬件模块进行通信和数据处理,同时控制通信模块的数据传输。【2】通信模块选择: 为了实现垃圾桶数据的实时采集和上传,选择了NBIOT-BC26通信模块。NBIOT-BC26支持窄带物联网通信,具有低功耗、广覆盖和远距离传输的特点,能够将采集到的数据通过NB-IoT网络上传到华为云物联网平台。【3】传感器模块选择:DHT11模块用于采集环境温度和湿度数据。DHT11是一种低成本、数字式温湿度传感器,通过数字信号输出温湿度数值,具有简单的接口和良好的性能,适用于本项目的温湿度监测需求。GPS模块用于实现垃圾桶的定位功能。选择中科微电子出品的GPS模块,通过串口输出GPS数据,能够准确获取垃圾桶的位置信息。红外传感器设计: 为了检测垃圾桶是否已满,采用红外传感器。红外传感器能够通过红外线的反射来判断垃圾桶内是否有垃圾,从而判断垃圾桶的满溢状态。红外传感器与主控芯片相连,通过数字输入口接收传感器的信号,并进行处理判断。【4】数据大屏设计: 数据大屏采用Qt开发,运行在Windows系统下。通过图形界面展示垃圾桶的详细情况,包括环境温度、湿度、GPS定位和垃圾桶的满溢状态。主控芯片通过与数据大屏的通信接口实时传输数据,数据大屏根据接收到的数据进行展示。整个硬件设计思路是将各个硬件模块与主控芯片相连接,通过主控芯片的控制和数据处理,实现数据的采集、通信传输、定位功能和信息展示。通过合理选择硬件组件,并进行适当的连接和接口设计,实现了智能垃圾桶管理系统的功能。2.3 软件设计【1】系统架构设计: 软件设计的第一步是确定系统的整体架构。根据项目需求,可以采用分层架构设计,将系统划分为应用层、业务逻辑层和驱动层。应用层负责与用户交互,业务逻辑层处理具体的业务逻辑,驱动层与硬件模块进行通信和控制。【2】硬件驱动设计: 针对每个硬件模块,需要编写相应的驱动程序。主控芯片与通信模块、传感器模块和红外传感器进行通信,通过串口、I2C、SPI等接口与它们进行数据交互。每个硬件模块的驱动程序应包括初始化、数据采集和控制等功能。【3】数据处理与逻辑控制: 主控芯片负责接收来自各个硬件模块的数据,并进行处理和逻辑控制。例如,从DHT11传感器读取温湿度数据后,可以进行数据的校验和转换,然后根据设定的阈值判断是否需要进行烘干操作。同时,主控芯片还负责控制红外传感器进行垃圾桶满溢状态的检测。【4】通信与数据上传: 通过NBIOT-BC26通信模块,将采集到的数据通过NB-IoT网络上传到华为云物联网平台。主控芯片与通信模块进行通信,将需要上传的数据打包成相应的格式,并通过串口等接口发送给通信模块,实现数据的上传。【5】用户界面设计: 软件还需要设计用户界面,以便用户可以直观地查看垃圾桶的状态和数据信息。可以使用Qt等工具进行界面设计,展示环境温度、湿度、GPS定位和垃圾桶的满溢状态等信息。用户界面与主控芯片进行通信,接收数据并进行展示。整个软件设计思路是基于系统架构设计,通过硬件驱动、数据处理与逻辑控制、通信与数据上传以及用户界面设计等模块的开发,实现智能垃圾桶管理系统的功能。软件设计需要与硬件设计相结合,保证数据的采集、处理、传输和展示的协调运作。2.4 系统交互流程【1】用户打开智能垃圾桶管理系统的应用程序。【2】系统初始化:系统进行硬件初始化,包括主控芯片、通信模块、传感器模块和红外传感器的初始化。确保通信模块连接到NB-IoT网络,并与华为云物联网平台建立通信连接。【3】环境监测:系统开始监测环境温度和湿度,并获取垃圾桶的定位信息。主控芯片通过DHT11传感器获取温湿度数据,通过GPS模块获取垃圾桶的位置信息。【4】数据处理与逻辑控制:主控芯片对采集到的数据进行处理和逻辑控制。可以根据温度和湿度数据判断垃圾桶是否需要进行清理,并通过红外传感器检测垃圾桶的满溢状态。【5】数据上传:主控芯片将处理后的数据通过通信模块上传到华为云物联网平台。数据可以包括环境温度、湿度、GPS定位和垃圾桶的满溢状态等信息。上传的数据可以以JSON等格式进行打包,通过NB-IoT网络传输到华为云物联网平台。【6】数据展示:用户界面接收从主控芯片传输过来的数据,并进行展示。用户可以在界面上查看环境温度、湿度、GPS定位和垃圾桶的满溢状态等信息。数据展示可以通过图表、文字、图像等形式进行呈现,以便用户直观地了解系统的状态和数据信息。整个系统的交互流程涉及到硬件模块的数据采集、主控芯片的数据处理与逻辑控制、通信模块的数据上传以及用户界面的数据展示和用户交互。通过这些步骤,用户可以方便地监测和管理智能垃圾桶的状态和数据信息。三、部署华为云物联网平台华为云官网: cid:link_12打开官网,搜索物联网,就能快速找到 设备接入IoTDA。3.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。3.2 开通物联网服务地址: cid:link_10进来默认会提示开通标准版,在2023的1月1号年之后没有基础版了。开通之后,点击总览,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。总结:端口号: MQTT (1883)| MQTTS (8883) 接入地址: 7445c6bcd3.st1.iotda-app.cn-north-4.myhuaweicloud.com根据域名地址得到IP地址信息:Microsoft Windows [版本 10.0.19044.2728](c) Microsoft Corporation。保留所有权利。​C:\Users\11266>ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com​正在 Ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:来自 117.78.5.125 的回复: 字节=32 时间=42ms TTL=30来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=30来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30​117.78.5.125 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),往返行程的估计时间(以毫秒为单位): 最短 = 35ms,最长 = 42ms,平均 = 37ms​C:\Users\11266>MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。3.3 创建产品(1)创建产品点击产品页,再点击左上角创建产品。(2)填写产品信息根据自己产品名字填写。(3)产品创建成功(4)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。先点击自定义模型。再创建一个服务ID。接着点击新增属性。3.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备(2)根据自己的设备填写(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。(4) 设备创建完成3.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_8业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_8对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down​最终的格式:$oc/devices/6419627e40773741f9fbdac7_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_3根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$oc/devices/{device_id}/sys/properties/report 最终的格式:$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/report发布主题时,需要上传数据,这个数据格式是JSON格式。​上传的JSON数据格式如下:​{ "services": [ { "service_id": <填服务ID>, "properties": { "<填属性名称1>": <填属性值>, "<填属性名称2>": <填属性值>, .......... } } ]}根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。​根据这个格式,组合一次上传的属性数据:{"services": [{"service_id": "stm32","properties":{"DS18B20":18,"motor_water":1,"motor_oxygen":1,"temp_max":10,"water_hp":130,"motor_food":0,"time_food":0,"oxygen_food":3}}]}3.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_2MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)华为云的MQTT服务器地址:114.116.232.138域名:7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com华为云的MQTT端口号:1883(2)生成MQTT三元组华为云提供了一个在线工具,用来生成MQTT鉴权三元组: cid:link_9打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。ClientId 6419627e40773741f9fbdac7_dev1_0_0_2023032108Username 6419627e40773741f9fbdac7_dev1Password 861ac9e6a579d36888b2aaf97714be7af6c77017b017162884592bd68b086a6e3.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。点击详情页面,可以看到上传的数据。到此,云平台的部署已经完成,设备已经可以正常上传数据了。四、上位机开发为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。4.1 Qt开发环境安装Qt的中文官网: cid:link_13QT5.12.6的下载地址:cid:link_11打开下载链接后选择下面的版本进行下载:qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details软件安装时断网安装,否则会提示输入账户。安装的时候,第一个复选框里勾选一个mingw 32编译器即可,其他的不管默认就行,直接点击下一步继续安装。说明: 我这里只是介绍PC端的环境搭建(这个比较简单)。 Android的开发环境比较麻烦,可以去我的博客里看详细文章。选择MinGW 32-bit 编译器:4.2 创建IAM账户创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。地址: cid:link_4获取Token时,除了AIM账号外,还需要项目凭证:faa0973835ab409ab48182e2590f4ad3鼠标点击自己昵称,点击统一身份认证。点击左上角创建用户。创建成功:4.3 获取影子数据帮助文档:cid:link_5设备影子介绍:设备影子是一个用于存储和检索设备当前状态信息的JSON文档。每个设备有且只有一个设备影子,由设备ID唯一标识设备影子仅保存最近一次设备的上报数据和预期数据无论该设备是否在线,都可以通过该影子获取和设置设备的属性简单来说:设备影子就是保存,设备最新上传的一次数据。我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。设备影子接口返回的数据如下:{ "device_id": "6419627e40773741f9fbdac7_dev1", "shadow": [ { "service_id": "stm32", "desired": { "properties": null, "event_time": null }, "reported": { "properties": { "DS18B20": 18, "motor_water": 1, "motor_oxygen": 1, "temp_max": 10, "water_hp": 130, "motor_food": 0, "time_food": 0, "oxygen_food": 3 }, "event_time": "20230321T081126Z" }, "version": 0 } ]}4.4 修改设备属性地址: cid:link_6接口说明设备的产品模型中定义了物联网平台可向设备下发的属性,应用服务器可调用此接口向指定设备下发属性。平台负责将属性以同步方式发送给设备,并将设备执行属性结果同步返回。修改设备属性的接口,可以让服务器给设备下发指令,如果需要控制设备。在线调试地址:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties修改设备属性是属于同步命令,需要设备在线才可以进行调试,先使用MQTT客户端登录服务器,模拟设备上线。然后进行调试,测试数据远程下发给设备。【1】利用MQTT客户端先登录设备 (这是同步命令,必须在线才能调试)【2】点击调试{"services":{"temp_max":100}}【4】可以看到,MQTT客户端软件上已经收到了服务器下发的消息由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程,设备响应了服务器才能确定数据下发成功。MQTT设备端如何响应呢?设备响应格式说明:cid:link_7下面进行实操:当服务器通过在线调试,发送指令下来之后,客户端将请求ID复制下来,添加到发布主题的格式里,再回复回去,服务器收到了响应,一次属性修改就完美完成了。就是成功的状态:下面是请求的总结: (响应服务器的修改设备属性请求)上报主题的格式:$oc/devices/{device_id}/sys/properties/set/response/request_id=$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/set/response/request_id=响应的数据:{"result_code": 0,"result_desc": "success"}4.5 设计上位机前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。【1】新建Qt工程选择工程路径,放在英文路径下。创建完毕。新建Android的模板:【2】界面设计【4】代码设计:配置参数读取与保存/*功能: 保存数据到文件*/void Widget::SaveDataToFile(QString text){ /*保存数据到文件,方便下次加载*/ QString file; file=QCoreApplication::applicationDirPath()+"/"+ConfigFile; QFile filesrc(file); filesrc.open(QIODevice::WriteOnly); QDataStream out(&filesrc); out << text; //序列化写字符串 filesrc.flush(); filesrc.close();}/*功能: 从文件读取数据*/QString Widget::ReadDataFile(void){ //读取配置文件 QString text,data; text=QCoreApplication::applicationDirPath()+"/"+ConfigFile; //判断文件是否存在 if(QFile::exists(text)) { QFile filenew(text); filenew.open(QIODevice::ReadOnly); QDataStream in(&filenew); // 从文件读取序列化数据 in >> data; //提取写入的数据 filenew.close(); } return data; //返回值读取的值}【3】代码设计:云端数据解析//解析反馈结果void Widget::replyFinished(QNetworkReply *reply){ QString displayInfo; int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); //读取所有数据 QByteArray replyData = reply->readAll(); qDebug()<<"状态码:"<<statusCode; qDebug()<<"反馈的数据:"<<QString(replyData); //更新token if(function_select==3) { displayInfo="token 更新失败."; //读取HTTP响应头的数据 QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs(); qDebug()<<"HTTP响应头数量:"<<RawHeader.size(); for(int i=0;i<RawHeader.size();i++) { QString first=RawHeader.at(i).first; QString second=RawHeader.at(i).second; if(first=="X-Subject-Token") { Token=second.toUtf8(); displayInfo="token 更新成功."; //保存到文件 SaveDataToFile(Token); break; } } QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok); return; } //判断状态码 if(200 != statusCode) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QString error_str=""; QJsonObject obj = document.object(); QString error_code; //解析错误代码 if(obj.contains("error_code")) { error_code=obj.take("error_code").toString(); error_str+="错误代码:"; error_str+=error_code; error_str+="\n"; } if(obj.contains("error_msg")) { error_str+="错误消息:"; error_str+=obj.take("error_msg").toString(); error_str+="\n"; } //显示错误代码 QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok); } } return; } //设置属性 if(function_select==12 || function_select==13) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); if(obj.contains("response")) { QJsonObject obj1=obj.take("response").toObject(); int val=0; QString success; if(obj1.contains("result_code")) { val=obj1.take("result_code").toInt(); } if(obj1.contains("result_desc")) { success=obj1.take("result_desc").toString(); } if(val==0 && success =="success") { //显示状态 QMessageBox::information(this,"提示","远程命令操作完成.",QMessageBox::Ok,QMessageBox::Ok); return; } else { //显示状态 QMessageBox::information(this,"提示","设备未正确回应.请检查设备网络.",QMessageBox::Ok,QMessageBox::Ok); return; } } } } } //查询设备属性 if(function_select==0) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); if(obj.contains("shadow")) { QJsonArray array=obj.take("shadow").toArray(); for(int i=0;i<array.size();i++) { QJsonObject obj2=array.at(i).toObject(); if(obj2.contains("reported")) { QJsonObject obj3=obj2.take("reported").toObject(); if(obj3.contains("properties")) { QJsonObject properties=obj3.take("properties").toObject(); qDebug()<<"开始解析数据...."; } } } } } } return; }}五、代码实现5.1 BC26连接云平台实现代码下面是使用STM32F103ZET6和BC26连接华为云物联网平台实现MQTT设备登录、主题订阅和主题发布的实现代码:#include "stdio.h"#include "string.h"#include "stdlib.h"// 定义华为云物联网平台的服务器地址、端口号、设备ID和设备密码#define MQTT_SERVER "mqtt://xxxxxx.iotplatform.com" // 请替换为实际的服务器地址#define MQTT_PORT 1883 // 请根据实际情况修改端口号#define DEVICE_ID "your_device_id" // 请替换为实际的设备ID#define DEVICE_PASSWORD "your_device_password" // 请替换为实际的设备密码// 定义MQTT相关的参数#define MQTT_CLIENT_ID "your_client_id" // 请替换为实际的客户端ID#define MQTT_TOPIC "your_topic" // 请替换为实际的主题// 定义MQTT消息接收回调函数void mqtt_message_received(char *topic, char *payload) { printf("Received message on topic: %s\n", topic); printf("Payload: %s\n", payload);}// 建立MQTT连接void mqtt_connect() { // 连接到华为云物联网平台的MQTT服务器 // 这里使用的是MQTT的QoS 1级别 // 请根据实际情况修改QoS级别和其他参数 char command[256]; sprintf(command, "AT+QMTCFG="aliauth",0,%d,"%s","%s"", MQTT_PORT, DEVICE_ID, DEVICE_PASSWORD); printf("Sending command: %s\n", command); // 发送AT指令连接到MQTT服务器 // ... // 订阅主题 sprintf(command, "AT+QMTSUB=0,1,"%s",1", MQTT_TOPIC); printf("Sending command: %s\n", command); // 发送AT指令订阅主题 // ...}// 发布MQTT消息void mqtt_publish(char *payload) { // 发布消息到指定的主题 char command[256]; sprintf(command, "AT+QMTPUB=0,0,0,0,"%s"", MQTT_TOPIC); printf("Sending command: %s\n", command); // 发送AT指令设置发布的主题 // ... sprintf(command, "AT+QMTPUB=0,1,0,0,%d", strlen(payload)); printf("Sending command: %s\n", command); // 发送AT指令设置消息的长度 // ... printf("Sending payload: %s\n", payload); // 发送消息的内容 // ... // 等待MQTT服务器返回发布结果 // ...}int main() { // 初始化串口和其他硬件模块 // ... // 连接到华为云物联网平台的MQTT服务器 mqtt_connect(); // 进入主循环 while (1) { // 处理其他任务 // ... // 检查是否有需要发布的消息 // 如果有,调用mqtt_publish函数发布消息 // ... // 检查是否有接收到的MQTT消息 // 如果有,调用mqtt_message_received函数处理消息 // ... } return 0;}5.2 BC26模块的MQTT协议指令BC26模块是一款支持NB-IoT通信技术的物联网模块,可以通过AT指令与外部设备进行通信和控制。下面是BC26模块与MQTT协议相关的一些常用AT指令及其功能:【1】AT+QMTOPEN:打开MQTT客户端连接。功能:通过该指令连接到MQTT服务器。参数:服务器地址、端口号、用户名和密码等。示例:AT+QMTOPEN=0,"mqtt://xxxxxx.iotplatform.com",1883【2】AT+QMTCLOSE:关闭MQTT客户端连接。功能:通过该指令关闭与MQTT服务器的连接。参数:无。示例:AT+QMTCLOSE=0【3】AT+QMTCONN:建立MQTT连接。功能:通过该指令建立与MQTT服务器的连接。参数:客户端ID、用户名、密码等。示例:AT+QMTCONN=0,"your_client_id","your_username","your_password"【4】AT+QMTDISC:断开MQTT连接。功能:通过该指令断开与MQTT服务器的连接。参数:无。示例:AT+QMTDISC=0【5】AT+QMTSUB:订阅MQTT主题。功能:通过该指令订阅指定的MQTT主题。参数:主题、QoS级别等。示例:AT+QMTSUB=0,1,"your_topic",1【6】AT+QMTUNS:取消订阅MQTT主题。功能:通过该指令取消订阅指定的MQTT主题。参数:主题。示例:AT+QMTUNS=0,1,"your_topic"【7】AT+QMTPUB:发布MQTT消息。功能:通过该指令发布消息到指定的MQTT主题。参数:主题、消息内容、QoS级别等。示例:AT+QMTPUB=0,0,0,0,"your_topic"【8】AT+QMTRECV:接收MQTT消息。功能:通过该指令接收从MQTT服务器接收到的消息。参数:无。示例:AT+QMTRECV=0这些是BC26模块中与MQTT协议相关的一些常用AT指令。5.3 读取DHT11传感器的温湿度数据以下是使用STM32F103ZET6读取DHT11传感器的温湿度数据的实现代码:#include "stm32f10x.h"#include "dht11.h"int main(void){ // 初始化DHT11传感器 DHT11_Init(); while (1) { // 读取DHT11传感器的温湿度数据 DHT11_Result result = DHT11_Read(); if (result.status == DHT11_OK) { // 温度数据 uint8_t temperature = result.temperature; // 湿度数据 uint8_t humidity = result.humidity; // 在这里进行温湿度数据的处理和使用 // ... // 延时一段时间后再次读取 DelayMs(2000); } else { // 读取失败,可以进行相应的错误处理 // ... } }}在主函数中,通过循环不断读取DHT11传感器的温湿度数据。如果读取成功,可以从result结构体中获取温度和湿度数据,并进行相应的处理。如果读取失败,可以根据需要进行错误处理。5.4 DHT11.c和DHT11.h代码dht11.h:#ifndef DHT11_H#define DHT11_H#include "stm32f10x.h"typedef struct{ uint8_t status; // 读取状态,0表示成功,其他表示失败 uint8_t humidity; // 湿度值 uint8_t temperature; // 温度值} DHT11_Result;void DHT11_Init(void);DHT11_Result DHT11_Read(void);#endifdht11.c:#include "dht11.h"#define DHT11_PORT GPIOA#define DHT11_PIN GPIO_Pin_0static void DHT11_Delay(uint32_t us){ uint32_t count = us * 8; while (count--) { __NOP(); }}static void DHT11_SetOutput(void){ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, &GPIO_InitStructure);}static void DHT11_SetInput(void){ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DHT11_PORT, &GPIO_InitStructure);}static uint8_t DHT11_ReadByte(void){ uint8_t byte = 0; for (uint8_t i = 0; i < 8; i++) { while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待低电平结束 } DHT11_Delay(30); if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { byte |= (1 << (7 - i)); } while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待高电平结束 } } return byte;}void DHT11_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, &GPIO_InitStructure); GPIO_SetBits(DHT11_PORT, DHT11_PIN);}DHT11_Result DHT11_Read(void){ DHT11_Result result; result.status = 1; DHT11_SetOutput(); GPIO_ResetBits(DHT11_PORT, DHT11_PIN); DHT11_Delay(18000); GPIO_SetBits(DHT11_PORT, DHT11_PIN); DHT11_Delay(20); DHT11_SetInput(); if (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待低电平结束 } while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待高电平结束 } uint8_t data[5]; for (uint8_t i = 0; i < 5; i++) { data[i] = DHT11_ReadByte(); } uint8_t sum = data[0] + data[1] + data[2] + data[3]; if (sum == data[4]) { result.status = 0; result.humidity = data[0]; result.temperature = data[2]; } } return result;}dht11.h文件定义了DHT11传感器的初始化函数DHT11_Init()和读取函数DHT11_Read(),以及DHT11_Result结构体用于存储读取结果。dht11.c文件实现了DHT11传感器的初始化和读取函数。在初始化函数中,配置了DHT11引脚的GPIO模式和速度。在读取函数中,通过发送开始信号和接收数据的方式读取DHT11传感器的温湿度数据,并进行校验。5.5 GPS数据解析在STM32F103ZET6上通过串口2读取GPS模块返回的定位数据并解析经纬度和定位状态。#include "stm32f10x.h"#include <stdio.h>#include <string.h>// 定义串口2接收缓冲区大小#define RX_BUFFER_SIZE 256// 定义GPS数据解析状态typedef enum { GPS_STATE_IDLE, // 空闲状态 GPS_STATE_RECEIVING, // 接收中状态 GPS_STATE_COMPLETE // 接收完成状态} GPS_State;// 定义接收缓冲区和接收状态变量char rxBuffer[RX_BUFFER_SIZE];volatile uint16_t rxIndex = 0;volatile GPS_State gpsState = GPS_STATE_IDLE;// 处理接收到的GPS数据void processGPSData() { // 在这里进行GPS数据解析和处理 // 解析经纬度和定位状态等信息 // 根据需要进行相应的操作或显示 // 例如,打印经纬度和定位状态 printf("Latitude: %s\n", latitude); printf("Longitude: %s\n", longitude); printf("Position Fix Status: %s\n", positionStatus);}// 串口2接收中断处理函数void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { char data = USART_ReceiveData(USART2); // 接收到回车换行符表示一条完整的GPS数据 if (data == '\n') { rxBuffer[rxIndex] = '\0'; rxIndex = 0; gpsState = GPS_STATE_COMPLETE; } else { // 将接收到的数据存储到缓冲区中 rxBuffer[rxIndex] = data; rxIndex++; // 接收缓冲区溢出时进行处理 if (rxIndex >= RX_BUFFER_SIZE) { rxIndex = 0; gpsState = GPS_STATE_IDLE; } } }}int main(void) { // 初始化串口2和GPIO引脚 // 设置串口2的波特率、数据位、停止位等参数 // 使能串口2接收中断 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); NVIC_EnableIRQ(USART2_IRQn); while (1) { // 如果接收到完整的GPS数据 if (gpsState == GPS_STATE_COMPLETE) { // 处理接收到的GPS数据 processGPSData(); // 处理完成后,重置接收状态为IDLE gpsState = GPS_STATE_IDLE; } }}代码中的串口初始化和中断处理部分是基于标准库的使用方式。
  • [技术干货] 基于STM32+华为云IOT设计的智能衣柜
    一、项目介绍随着智能家居的发展,人们对于家居设备的智能化和远程控制需求越来越高。智能衣柜作为智能家居的一部分,可以提供衣物存储和保护的功能,并通过传感器和互联网技术实现对衣柜内部环境的监测和控制,为用户提供更好的使用体验。本项目基于STM32F103ZET6主控芯片设计了一个智能衣柜系统,主要功能包括温度和湿度的监测以及烘干控制。为了实现温湿度的监测,采用了DHT11传感器,可以准确地测量环境的温度和湿度。通过将传感器连接到STM32F103ZET6,可以实时获取衣柜内部的温湿度数据。为了实现烘干功能,系统使用加热丝和小风扇来制造热气并循环衣柜内部的空气,以去除湿气并防止衣物发霉。加热丝的控制采用继电器来控制加热丝的通断,从而控制烘干的开关。通过与STM32F103ZET6的连接,可以实现对加热丝和小风扇的控制。为了实现远程监控和控制,系统采用了ESP8266-WIFI模块将采集到的温湿度数据上传到华为云物联网平台。用户可以通过在Android手机上开发的Qt应用程序远程查看衣柜的实时温度和湿度,并设置湿度阀值。如果湿度超出阀值,系统会通过本地蜂鸣器报警和手机APP提示用户,以防止衣物发霉。此外,用户还可以通过手机APP远程控制衣柜的烘干系统,去除湿气,防止衣物发霉或出现霉味。整个系统通过将传感器、主控芯片、继电器、ESP8266-WIFI模块和手机APP进行集成,实现了智能衣柜的温湿度监测和远程控制功能,为用户提供了便捷、智能的衣物存储和保护解决方案。二、设计思路总结2.1 硬件选型在该项目中,以下是一些可能的硬件选型:【1】主控芯片:STM32F103ZET6,它是一款性能强大的32位ARM Cortex-M3微控制器,具有丰富的外设和存储器,适合用作智能衣柜系统的主控芯片。【2】温湿度传感器:DHT11,能够准确地测量环境的温度和湿度。【3】网络通信模块:ESP8266-WIFI模块,可以实现与互联网的连接,用于将采集到的温湿度数据上传到华为云物联网平台。【4】继电器:用于控制加热丝的通断,从而控制烘干的开关。选择合适的继电器型号和规格,以适应加热丝的电流和电压要求。【5】蜂鸣器:用于本地报警,当湿度超出阀值时发出报警声音。【5】Android手机:作为用户界面,通过Qt开发的Android手机APP实现远程查看和控制衣柜的温湿度以及烘干系统的开关控制。2.2 硬件设计使用STM32F103ZET6作为主控芯片,连接温湿度传感器DHT11,继电器,蜂鸣器和ESP8266-WIFI模块。将DHT11传感器连接到主控芯片的GPIO口,以实时获取衣柜内部的温湿度数据。通过继电器控制加热丝的通断,以控制烘干系统的开关。连接蜂鸣器到主控芯片的GPIO口,当湿度超出阀值时发出报警声音。将ESP8266-WIFI模块连接到主控芯片的串口,以实现与华为云物联网平台的通信。2.3 软件设计使用STM32的开发环境进行固件开发,编写相应的代码来实现温湿度传感器的读取、继电器的控制和蜂鸣器的报警功能。编写与ESP8266-WIFI模块通信的代码,实现将采集到的温湿度数据上传到华为云物联网平台。在华为云物联网平台上创建相应的设备和数据通道,以接收和存储来自智能衣柜的温湿度数据。开发基于Qt的Android手机APP,通过与华为云物联网平台的接口,实现远程查看衣柜的实时温湿度和控制烘干系统的开关。在手机APP上设置湿度阀值,当湿度超出阀值时,触发本地蜂鸣器报警和手机APP的提示功能。2.4 系统交互流程STM32主控芯片读取DHT11传感器的温湿度数据。根据采集到的数据,控制继电器开关加热丝和小风扇,实现烘干功能。将温湿度数据通过ESP8266-WIFI模块上传到华为云物联网平台。用户通过Qt开发的Android手机APP远程访问华为云物联网平台,获取衣柜的实时温湿度数据。如果湿度超出设定的阀值,系统发出本地蜂鸣器报警和手机APP的提示,提醒用户。用户可以通过手机APP远程控制烘干系统的开关,去除湿气,防止衣物发霉或出现霉味。通过上述系统设计思路,实现了智能衣柜的温湿度监测和远程控制功能,提供了更智能、便捷的衣物存储和保护解决方案。三、部署华为云物联网平台华为云官网: cid:link_11打开官网,搜索物联网,就能快速找到 设备接入IoTDA。3.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。3.2 开通物联网服务地址: cid:link_10进来默认会提示开通标准版,在2023的1月1号年之后没有基础版了。开通之后,点击总览,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。总结:端口号: MQTT (1883)| MQTTS (8883) 接入地址: 7445c6bcd3.st1.iotda-app.cn-north-4.myhuaweicloud.com根据域名地址得到IP地址信息:Microsoft Windows [版本 10.0.19044.2728](c) Microsoft Corporation。保留所有权利。​C:\Users\11266>ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com​正在 Ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:来自 117.78.5.125 的回复: 字节=32 时间=42ms TTL=30来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=30来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30​117.78.5.125 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),往返行程的估计时间(以毫秒为单位): 最短 = 35ms,最长 = 42ms,平均 = 37ms​C:\Users\11266>MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。3.3 创建产品(1)创建产品点击产品页,再点击左上角创建产品。(2)填写产品信息根据自己产品名字填写。(3)产品创建成功(4)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。先点击自定义模型。再创建一个服务ID。接着点击新增属性。3.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备(2)根据自己的设备填写(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。(4) 设备创建完成3.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_8业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_8对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down​最终的格式:$oc/devices/6419627e40773741f9fbdac7_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_3根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$oc/devices/{device_id}/sys/properties/report 最终的格式:$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/report发布主题时,需要上传数据,这个数据格式是JSON格式。​上传的JSON数据格式如下:​{ "services": [ { "service_id": <填服务ID>, "properties": { "<填属性名称1>": <填属性值>, "<填属性名称2>": <填属性值>, .......... } } ]}根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。​根据这个格式,组合一次上传的属性数据:{"services": [{"service_id": "stm32","properties":{"DS18B20":18,"motor_water":1,"motor_oxygen":1,"temp_max":10,"water_hp":130,"motor_food":0,"time_food":0,"oxygen_food":3}}]}3.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_2MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)华为云的MQTT服务器地址:114.116.232.138域名:7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com华为云的MQTT端口号:1883(2)生成MQTT三元组华为云提供了一个在线工具,用来生成MQTT鉴权三元组: cid:link_9打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。ClientId 6419627e40773741f9fbdac7_dev1_0_0_2023032108Username 6419627e40773741f9fbdac7_dev1Password 861ac9e6a579d36888b2aaf97714be7af6c77017b017162884592bd68b086a6e3.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。点击详情页面,可以看到上传的数据。到此,云平台的部署已经完成,设备已经可以正常上传数据了。四、上位机开发为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。4.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编译器即可,其他的不管默认就行,直接点击下一步继续安装。说明: 我这里只是介绍PC端的环境搭建(这个比较简单)。 Android的开发环境比较麻烦,可以去我的博客里看详细文章。选择MinGW 32-bit 编译器:4.2 创建IAM账户创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。地址: cid:link_4获取Token时,除了AIM账号外,还需要项目凭证:faa0973835ab409ab48182e2590f4ad3鼠标点击自己昵称,点击统一身份认证。点击左上角创建用户。创建成功:4.3 获取影子数据帮助文档:cid:link_5设备影子介绍:设备影子是一个用于存储和检索设备当前状态信息的JSON文档。每个设备有且只有一个设备影子,由设备ID唯一标识设备影子仅保存最近一次设备的上报数据和预期数据无论该设备是否在线,都可以通过该影子获取和设置设备的属性简单来说:设备影子就是保存,设备最新上传的一次数据。我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。设备影子接口返回的数据如下:{ "device_id": "6419627e40773741f9fbdac7_dev1", "shadow": [ { "service_id": "stm32", "desired": { "properties": null, "event_time": null }, "reported": { "properties": { "DS18B20": 18, "motor_water": 1, "motor_oxygen": 1, "temp_max": 10, "water_hp": 130, "motor_food": 0, "time_food": 0, "oxygen_food": 3 }, "event_time": "20230321T081126Z" }, "version": 0 } ]}4.4 修改设备属性地址: cid:link_6接口说明设备的产品模型中定义了物联网平台可向设备下发的属性,应用服务器可调用此接口向指定设备下发属性。平台负责将属性以同步方式发送给设备,并将设备执行属性结果同步返回。修改设备属性的接口,可以让服务器给设备下发指令,如果需要控制设备。在线调试地址:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties修改设备属性是属于同步命令,需要设备在线才可以进行调试,先使用MQTT客户端登录服务器,模拟设备上线。然后进行调试,测试数据远程下发给设备。【1】利用MQTT客户端先登录设备 (这是同步命令,必须在线才能调试)【2】点击调试{"services":{"temp_max":100}}【4】可以看到,MQTT客户端软件上已经收到了服务器下发的消息由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程,设备响应了服务器才能确定数据下发成功。MQTT设备端如何响应呢?设备响应格式说明:cid:link_7下面进行实操:当服务器通过在线调试,发送指令下来之后,客户端将请求ID复制下来,添加到发布主题的格式里,再回复回去,服务器收到了响应,一次属性修改就完美完成了。就是成功的状态:下面是请求的总结: (响应服务器的修改设备属性请求)上报主题的格式:$oc/devices/{device_id}/sys/properties/set/response/request_id=$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/set/response/request_id=响应的数据:{"result_code": 0,"result_desc": "success"}4.5 设计上位机前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。【1】新建Qt工程选择工程路径,放在英文路径下。创建完毕。新建Android的模板:【2】界面设计【4】代码设计:配置参数读取与保存/*功能: 保存数据到文件*/void Widget::SaveDataToFile(QString text){ /*保存数据到文件,方便下次加载*/ QString file; file=QCoreApplication::applicationDirPath()+"/"+ConfigFile; QFile filesrc(file); filesrc.open(QIODevice::WriteOnly); QDataStream out(&filesrc); out << text; //序列化写字符串 filesrc.flush(); filesrc.close();}/*功能: 从文件读取数据*/QString Widget::ReadDataFile(void){ //读取配置文件 QString text,data; text=QCoreApplication::applicationDirPath()+"/"+ConfigFile; //判断文件是否存在 if(QFile::exists(text)) { QFile filenew(text); filenew.open(QIODevice::ReadOnly); QDataStream in(&filenew); // 从文件读取序列化数据 in >> data; //提取写入的数据 filenew.close(); } return data; //返回值读取的值}【3】代码设计:云端数据解析//解析反馈结果void Widget::replyFinished(QNetworkReply *reply){ QString displayInfo; int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); //读取所有数据 QByteArray replyData = reply->readAll(); qDebug()<<"状态码:"<<statusCode; qDebug()<<"反馈的数据:"<<QString(replyData); //更新token if(function_select==3) { displayInfo="token 更新失败."; //读取HTTP响应头的数据 QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs(); qDebug()<<"HTTP响应头数量:"<<RawHeader.size(); for(int i=0;i<RawHeader.size();i++) { QString first=RawHeader.at(i).first; QString second=RawHeader.at(i).second; if(first=="X-Subject-Token") { Token=second.toUtf8(); displayInfo="token 更新成功."; //保存到文件 SaveDataToFile(Token); break; } } QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok); return; } //判断状态码 if(200 != statusCode) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QString error_str=""; QJsonObject obj = document.object(); QString error_code; //解析错误代码 if(obj.contains("error_code")) { error_code=obj.take("error_code").toString(); error_str+="错误代码:"; error_str+=error_code; error_str+="\n"; } if(obj.contains("error_msg")) { error_str+="错误消息:"; error_str+=obj.take("error_msg").toString(); error_str+="\n"; } //显示错误代码 QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok); } } return; } //设置属性 if(function_select==12 || function_select==13) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); if(obj.contains("response")) { QJsonObject obj1=obj.take("response").toObject(); int val=0; QString success; if(obj1.contains("result_code")) { val=obj1.take("result_code").toInt(); } if(obj1.contains("result_desc")) { success=obj1.take("result_desc").toString(); } if(val==0 && success =="success") { //显示状态 QMessageBox::information(this,"提示","远程命令操作完成.",QMessageBox::Ok,QMessageBox::Ok); return; } else { //显示状态 QMessageBox::information(this,"提示","设备未正确回应.请检查设备网络.",QMessageBox::Ok,QMessageBox::Ok); return; } } } } } //查询设备属性 if(function_select==0) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); if(obj.contains("shadow")) { QJsonArray array=obj.take("shadow").toArray(); for(int i=0;i<array.size();i++) { QJsonObject obj2=array.at(i).toObject(); if(obj2.contains("reported")) { QJsonObject obj3=obj2.take("reported").toObject(); if(obj3.contains("properties")) { QJsonObject properties=obj3.take("properties").toObject(); qDebug()<<"开始解析数据...."; } } } } } } return; }}五、代码实现5.1 ESP8266连接云平台实现代码以下是使用STM32F103ZET6和ESP8266连接华为云物联网平台,通过MQTT协议实现设备登录、主题订阅和主题发布的实现代码:#include "stm32f10x.h"#include "stdio.h"#include "string.h"// 定义ESP8266的串口USART_TypeDef* ESP_USARTx = USART1;// 定义MQTT服务器的地址和端口const char* MQTT_SERVER = "mqtt.eclipse.org";const int MQTT_PORT = 1883;// 定义设备ID和设备密码const char* DEVICE_ID = "your_device_id";const char* DEVICE_PASSWORD = "your_device_password";// 定义订阅的主题const char* SUBSCRIBE_TOPIC = "your_subscribe_topic";// 定义发布的主题const char* PUBLISH_TOPIC = "your_publish_topic";// 定义接收缓冲区和发送缓冲区的大小#define RX_BUFFER_SIZE 1024#define TX_BUFFER_SIZE 1024// 定义接收缓冲区和发送缓冲区char rxBuffer[RX_BUFFER_SIZE];char txBuffer[TX_BUFFER_SIZE];// 定义接收缓冲区的索引和标志位volatile uint16_t rxIndex = 0;volatile uint8_t rxComplete = 0;// 发送数据到ESP8266void ESP8266_SendData(const char* data) { sprintf(txBuffer, "%s\r\n", data); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n'); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n'); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n'); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n'); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n'); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n'); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n'); USART_SendData(ESP_USARTx, (uint16_t)'\r'); USART_SendData(ESP_USARTx, (uint16_t)'\n');}// 从ESP8266接收数据void ESP8266_ReceiveData(uint16_t size) { while (size--) { rxBuffer[rxIndex++] = USART_ReceiveData(ESP_USARTx); } if (rxIndex >= RX_BUFFER_SIZE) { rxComplete = 1; rxIndex = 0; }}// 处理接收到的数据void ProcessReceivedData() { // TODO: 根据接收到的数据进行处理}// ESP8266串口中断处理函数void USART1_IRQHandler(void) { if (USART_GetITStatus(ESP_USARTx, USART_IT_RXNE) != RESET) { ESP8266_ReceiveData(1); }}// 连接到MQTT服务器void MQTT_Connect() { // 发送连接请求 sprintf(txBuffer, "AT+CIPSTART="TCP","%s",%d\r\n", MQTT_SERVER, MQTT_PORT); ESP8266_SendData(txBuffer); // 等待连接成功 while (!strstr(rxBuffer, "CONNECTED")) { if (rxComplete) { ProcessReceivedData(); rxComplete = 0; } } // 发送MQTT连接请求 sprintf(txBuffer, "AT+MQTTCONNECT="%s","%s"\r\n", DEVICE_ID, DEVICE_PASSWORD); ESP8266_SendData(txBuffer); // 等待连接成功 while (!strstr(rxBuffer, "CONNECTED")) { if (rxComplete) { ProcessReceivedData(); rxComplete = 0; } }}// 订阅主题void MQTT_Subscribe() { sprintf(txBuffer, "AT+MQTTSUBSCRIBE="%s"\r\n", SUBSCRIBE_TOPIC); ESP8266_SendData(txBuffer);}// 发布消息void MQTT_Publish(const char* message) { sprintf(txBuffer, "AT+MQTTPUBLISH="%s","%s"\r\n", PUBLISH_TOPIC, message); ESP8266_SendData(txBuffer);}int main(void) { // 初始化ESP8266的串口 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; 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(ESP_USARTx, &USART_InitStructure); USART_Cmd(ESP_USARTx, ENABLE); USART_ITConfig(ESP_USARTx, USART_IT_RXNE, ENABLE); NVIC_EnableIRQ(USART1_IRQn); // 连接到MQTT服务器 MQTT_Connect(); // 订阅主题 MQTT_Subscribe(); while (1) { if (rxComplete) { ProcessReceivedData(); rxComplete = 0; } // TODO: 处理其他业务逻辑 // 发布消息 MQTT_Publish("Hello, MQTT!"); // 延时一段时间 delay_ms(1000); }}以上代码用于演示使用STM32F103ZET6和ESP8266连接华为云物联网平台,通过MQTT协议实现设备登录、主题订阅和主题发布的基本功能。5.2 ESP8266的MQTT协议指令ESP8266通过MQTT协议连接到服务器的相关AT指令主要有以下几个:【1】AT+CIPSTART:建立TCP连接功能:使用TCP协议连接到远程服务器用法:AT+CIPSTART="TCP","<服务器地址>",<服务器端口>示例:AT+CIPSTART="TCP","mqtt.eclipse.org",1883【2】AT+MQTTCONNECT:连接到MQTT服务器功能:使用MQTT协议连接到MQTT服务器用法:AT+MQTTCONNECT="<设备ID>","<设备密码>"示例:AT+MQTTCONNECT="your_device_id","your_device_password"【3】AT+MQTTPUBLISH:发布消息功能:向指定主题发布消息用法:AT+MQTTPUBLISH="<主题>","<消息内容>"示例:AT+MQTTPUBLISH="your_publish_topic","Hello, MQTT!"【4】AT+MQTTSUBSCRIBE:订阅主题功能:订阅指定的主题用法:AT+MQTTSUBSCRIBE="<主题>"示例:AT+MQTTSUBSCRIBE="your_subscribe_topic"【5】AT+CIPCLOSE:关闭TCP连接功能:关闭当前的TCP连接用法:AT+CIPCLOSE这些AT指令可以通过串口与ESP8266进行通信,实现与MQTT服务器的连接、消息发布和订阅等功能。通过这些指令,可以在嵌入式设备上实现与云端的通信和数据交换,从而实现物联网应用。5.3 读取DHT11传感器的温湿度数据以下是使用STM32F103ZET6读取DHT11传感器的温湿度数据的实现代码:#include "stm32f10x.h"#include "dht11.h"int main(void){ // 初始化DHT11传感器 DHT11_Init(); while (1) { // 读取DHT11传感器的温湿度数据 DHT11_Result result = DHT11_Read(); if (result.status == DHT11_OK) { // 温度数据 uint8_t temperature = result.temperature; // 湿度数据 uint8_t humidity = result.humidity; // 在这里进行温湿度数据的处理和使用 // ... // 延时一段时间后再次读取 DelayMs(2000); } else { // 读取失败,可以进行相应的错误处理 // ... } }}在主函数中,通过循环不断读取DHT11传感器的温湿度数据。如果读取成功,可以从result结构体中获取温度和湿度数据,并进行相应的处理。如果读取失败,可以根据需要进行错误处理。5.4 DHT11.c和DHT11.h代码dht11.h:#ifndef DHT11_H#define DHT11_H#include "stm32f10x.h"typedef struct{ uint8_t status; // 读取状态,0表示成功,其他表示失败 uint8_t humidity; // 湿度值 uint8_t temperature; // 温度值} DHT11_Result;void DHT11_Init(void);DHT11_Result DHT11_Read(void);#endifdht11.c:#include "dht11.h"#define DHT11_PORT GPIOA#define DHT11_PIN GPIO_Pin_0static void DHT11_Delay(uint32_t us){ uint32_t count = us * 8; while (count--) { __NOP(); }}static void DHT11_SetOutput(void){ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, &GPIO_InitStructure);}static void DHT11_SetInput(void){ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DHT11_PORT, &GPIO_InitStructure);}static uint8_t DHT11_ReadByte(void){ uint8_t byte = 0; for (uint8_t i = 0; i < 8; i++) { while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待低电平结束 } DHT11_Delay(30); if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { byte |= (1 << (7 - i)); } while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待高电平结束 } } return byte;}void DHT11_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, &GPIO_InitStructure); GPIO_SetBits(DHT11_PORT, DHT11_PIN);}DHT11_Result DHT11_Read(void){ DHT11_Result result; result.status = 1; DHT11_SetOutput(); GPIO_ResetBits(DHT11_PORT, DHT11_PIN); DHT11_Delay(18000); GPIO_SetBits(DHT11_PORT, DHT11_PIN); DHT11_Delay(20); DHT11_SetInput(); if (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待低电平结束 } while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { // 等待高电平结束 } uint8_t data[5]; for (uint8_t i = 0; i < 5; i++) { data[i] = DHT11_ReadByte(); } uint8_t sum = data[0] + data[1] + data[2] + data[3]; if (sum == data[4]) { result.status = 0; result.humidity = data[0]; result.temperature = data[2]; } } return result;}dht11.h文件定义了DHT11传感器的初始化函数DHT11_Init()和读取函数DHT11_Read(),以及DHT11_Result结构体用于存储读取结果。dht11.c文件实现了DHT11传感器的初始化和读取函数。在初始化函数中,配置了DHT11引脚的GPIO模式和速度。在读取函数中,通过发送开始信号和接收数据的方式读取DHT11传感器的温湿度数据,并进行校验。
  • [赛事资讯] CodeCraft云上先锋黑客松-软件精英实战营赛题发布
    赛题简介本次大赛的赛题需要选手通过代码操控4个机器人完成物资递送任务,以实现收益最大化。赛题模拟了真实机器人的操作接口并通过物理引擎来模拟真实世界的物理法则,如惯性、摩擦、碰撞等。         比赛所使用的判题器和数据集完全开放给大家下载,并且我们给判题器做了图形化界面设计+键盘操作机器人的能力,让大家可以低门槛的上手本次赛题。         为了照顾到使用不同平台的选手,本次大赛特地对判题器做了跨平台设计,可以支持Windows、Linux、MacOS三个平台,选手可以根据自身习惯选择其中一款判题器下载。         每次运行判题器,都会产生一个回放文件置于replay目录中,可以用判题器自带的播放器进行播放,以分析过往比赛的表现。附件下载1. 赛题文档.zip:===页面下方===包含:复赛任务书、判题器使用说明、编译运行环境说明2. WindowsRelease.zip :===下载链接===包含:Windows版本的判题器、播放器、数据集、SDK3. LinuxRelease.zip :===下载链接===包含:Linux版本的判题器、播放器、数据集、SDK4. MacOSRelease.zip :===下载链接===包含:MacOS版本的判题器、播放器、数据集、SDK,该版本可同时支持x86和M1芯片。5.SDK :===页面下方===包含:python、java、C++、C4种语言
  • [技术干货] 基于STM32设计的智能门锁2(采用华为云IOT平台)
    1. 前言随着智能家居的快速发展,智能门锁作为家庭安全的重要组成部分,受到了越来越多用户的关注和需求。为了满足用户对安全和便捷的需求,决定设计一款基于STM32的智能门锁,并将其与华为云IOT平台相结合。传统的门锁存在一些弊端,比如使用钥匙容易丢失、开锁过程繁琐等。而智能门锁的出现,有效地解决了这些问题。我选择使用STM32作为智能门锁的核心控制器,因为STM32系列具有低功耗、高性能和丰富的外设接口等优点,非常适合嵌入式应用。华为云IOT平台作为一个强大的云服务平台,提供了丰富的物联网解决方案和强大的数据处理能力。将智能门锁与华为云IOT平台相结合,可以实现远程控制、数据监测和智能化的功能,为用户带来更加便捷和安全的居家体验。智能门锁设计具有以下主要特点和功能:安全可靠:采用先进的加密算法和身份验证机制,确保门锁的安全性。用户可以通过手机APP、指纹识别或密码等方式进行开锁,有效防止非法入侵。远程控制:通过与华为云IOT平台的连接,用户可以通过手机APP在任何地方实现对门锁的远程控制。比如,可以远程开关门锁、查看开锁记录等。多种开锁方式:除了传统的钥匙开锁方式外,我们的智能门锁还支持多种开锁方式,如指纹识别、密码输入、手机APP控制等。用户可以根据自己的需求选择最方便的开锁方式。实时监测:智能门锁可以实时监测门锁状态、开锁记录等信息,并将这些数据上传到华为云IOT平台进行存储和分析。用户可以通过手机APP查看相关数据,了解家庭安全状况。智能化功能:基于华为云IOT平台的数据处理能力,我们的智能门锁还可以实现一些智能化的功能。比如,可以设置自动开锁时间、远程授权开锁等。2. 设备硬件与功能介绍这篇文章就介绍如何使用华为物联网云平台实现智能锁的应用场景构建,硬件采用STM32F103ZET6 + ESP8266+步进电机实现。在华为云IOT物联网平台构建智能锁项目,配置好云端,设备端通过ESP8266连接华为物联网平台,实现数据上报,交互,实现远程开锁、关锁、获取锁的状态等功能,不用担心忘记出门关锁,也不用担心忘记带钥匙无法开门的情况。ESP8266是物联网解决方案里比较热门的WIFI设备,支持串口+AT指令控制,任意支持串口的单片机都可以使用ESP8266快速实现联网。步进电机采用常规28BYJ-48来模拟当做门锁的电机,驱动板采用ULN2003。3. 创建云端设备登录官网: cid:link_3直接搜索物联网,打开页面。选择设备接入:选择免费试用:在产品页面,点击右上角创建产品:填上产品信息:得到产品ID,保存好ID,点击查看详情:产品ID为:61b9ba3a2b2aa20288c1e7f1.点击设备页面,注册设备:填充信息进行注册:保存设备密匙和设备ID,点击保存关闭会自动下载文件保存,后面生成密码和登录账号需要使用关闭后就看到创建好的设备了:点击产品页面,选择刚才创建的产品:选择自定义模型---创建数据模型服务:选择新增属性,创建设备的属性4. 创建MQTT登录账号和密匙设备创建完成接来下生成MQTT登录账号、密匙,方便设备登录云端平台。官网工具地址: cid:link_1打开刚才创建设备时,下载的密匙文件,把内容复制出来对应的填进去,生成即可。5. 拼接主题订阅与发布的格式官方文档介绍: cid:link_0在产品页面可以,看到主题的全部格式:总结的格式如下: 格式: $oc/devices/{device_id}/sys/messages/down //订阅主题: 平台下发消息给设备 $oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/messages/down ​ ​ 格式: $oc/devices/{device_id}/sys/properties/report //设备上报数据 $oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/properties/report ​ 上属性的数据格式: //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "lock","properties":{"门锁":1}}]}上面属性里的服务ID和属性里的名称,在设备页面,影子设备页面查看。6. MQTT客户端模拟设备登录云端下面使用MQTT客户端模拟设备登录服务器测试,看设备创建的是否OK。服务器的IP地址是: 121.36.42.100端口号是: 1883打开MQTT客户端软件,按照提示,输入相关参数后,点击连接,然后再点击订阅主题,发布主题即可:查看云端服务器的情况: 可以看到设备已经在线了,并且收到上传的数据。修改一下锁的状态,上报属性再查看:发现云端的状态也已经改变,现在设备上报已经OK。接下来测试命令下发,实现远程开锁关锁的功能:打开产品页面,新增加命令:命令添加成功:在设备页面,选择同步命令下发:点击确定后,查看MQTT客户端,发现已经收到数据了:$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497/sys/commands/request_id=88e2626f-290d-405e-962d-51554445a8fd{"paras":{"lock":1},"service_id":"lock","command_name":"lock"}设备端解析收到的数据,就可以完成多步进电机的控制,完成开锁关锁。7. STM32+ESP8266连接云端工程是keil5工程项目源码与视频演示:cid:link_2main函数代码如下: #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include < string.h > #include "timer.h" #include "bluetooth.h" #include "esp8266.h" #include "mqtt.h" ​ //华为物联网服务器的设备信息 #define MQTT_ClientID "61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510" #define MQTT_UserName "61b9ba3a2b2aa20288c1e7f1_QQ1126626497" #define MQTT_PassWord "385ce91dfe7da5b7431868d5d87e7998163c493344040935d5a00024d6324242" ​ //订阅与发布的主题 #define SET_TOPIC "$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/messages/down" //订阅 #define POST_TOPIC "$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/properties/report" //发布 ​ char mqtt_message[200];//上报数据缓存区 ​ int main() { u32 time_cnt=0; u32 i; u8 key; LED_Init(); BEEP_Init(); KEY_Init(); USART1_Init(115200); TIMER1_Init(72,20000); //超时时间20ms USART2_Init(9600);//串口-蓝牙 TIMER2_Init(72,20000); //超时时间20ms USART3_Init(115200);//串口-WIFI TIMER3_Init(72,20000); //超时时间20ms USART1_Printf("正在初始化WIFI请稍等.\\n"); if(ESP8266_Init()) { USART1_Printf("ESP8266硬件检测错误.\\n"); } else { //非加密端口 USART1_Printf("WIFI:%d\\n",ESP8266_STA_TCP_Client_Mode("CMCC-Cqvn","99pu58cb","121.36.42.100",1883,1)); } //2. MQTT协议初始化 MQTT_Init(); //3. 连接华为服务器 while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord)) { USART1_Printf("服务器连接失败,正在重试...\\n"); delay_ms(500); } USART1_Printf("服务器连接成功.\\n"); //3. 订阅主题 if(MQTT_SubscribeTopic(SET_TOPIC,0,1)) { USART1_Printf("主题订阅失败.\\n"); } else { USART1_Printf("主题订阅成功.\\n"); } while(1) { key=KEY_Scan(0); if(key==2) { time_cnt=0; sprintf(mqtt_message,"{"services": [{"service_id": "lock","properties":{"门锁":1}}]}"); MQTT_PublishData(POST_TOPIC,mqtt_message,0); USART1_Printf("发送状态1\\r\\n"); } else if(key==3) { time_cnt=0; sprintf(mqtt_message,"{"services": [{"service_id": "lock","properties":{"门锁":0}}]}"); MQTT_PublishData(POST_TOPIC,mqtt_message,0); USART1_Printf("发送状态0\\r\\n"); } ​ if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\\0'; for(i=0;i< USART3_RX_CNT;i++) { USART1_Printf("%c",USART3_RX_BUFFER[i]); } USART3_RX_CNT=0; USART3_RX_FLAG=0; } ​ //定时发送心跳包,保持连接 delay_ms(10); time_cnt++; if(time_cnt==500) { MQTT_SentHeart();//发送心跳包 time_cnt=0; } } }
总条数:76 到第
上滑加载中