一、前言
1.1 项目介绍
【1】项目开发背景
本项目的开发背景源于社会对视障人士出行安全与独立生活需求的不断提升。随着科技的进步与社会对弱势群体的关注度增加,传统的机械式导盲杖已难以满足现代出行环境的复杂需求。传统导盲杖虽然能帮助使用者探测近距离障碍物,但无法识别远处危险,也无法在紧急情况下提供有效的帮助信息,安全隐患较大。特别是在夜间、雨雾天气或人流密集的环境中,视障人士往往面临更多出行风险。因此,研究与开发一种具备智能检测、语音提示、环境感知与远程定位功能的“智能导盲杖”具有重要的现实意义与社会价值。
近年来,嵌入式系统与物联网技术的快速发展为传统导盲设备的智能化提供了良好基础。通过将微控制器、传感器模块、无线通信模块和云计算平台相结合,能够实现环境信息的实时感知、智能判断与远程信息交互,使导盲设备不仅具备基本的导航辅助功能,还能实现信息共享与安全监护。本项目正是基于这种背景,采用STM32单片机作为核心控制单元,结合超声波测距、光照检测、语音提示、GPS定位与4G通信等多种技术,构建出一套集检测、提醒、求救与远程监控于一体的智能导盲系统。
此外,当前老龄化趋势加剧、独居老人数量增加,也进一步扩大了智能导盲设备的应用范围。系统除了服务于视障人士外,也可为老年人、行动不便者提供出行辅助支持。通过在导盲杖中加入一键呼救和GPS定位功能,当使用者遇到紧急情况时,可立即发出语音求助信号并上传位置信息到云平台,监护人可通过上位机APP实时接收求救提示并导航前往现场。这种智能化的安全机制有效缩短了救援反应时间,提高了应急响应效率。
本项目通过将STM32嵌入式控制技术与华为云物联网平台相结合,实现设备端、云端与上位机端的协同工作。设备端完成环境数据采集与处理,云端实现数据的上传与存储,上位机端实现信息展示与监控。该系统不仅展示了嵌入式与物联网技术在辅助设备中的综合应用,也为智能出行、安全监护等领域提供了新的思路与解决方案。项目的开发对于促进智能康复与无障碍技术的普及具有重要的实践意义。
设计好的PCB板子可以安装在导盲杖的内部。
【2】设计实现的功能
(1)距离检测功能:
利用超声波测距模块检测前方障碍物的距离,STM32主控实时计算测距结果。当检测到障碍物距离小于设定的安全阈值时,系统会立即触发语音提示,提醒使用者前方存在障碍,防止碰撞。
(2)语音提示功能:
当检测到障碍物或触发求救状态时,系统通过语音播报模块进行语音提示。在正常行走时提供障碍物警示,在紧急求救时播放“请帮助我”等提示语音,方便周围人员及时提供援助。
(3)环境光检测与自动照明功能:
系统通过BH1750光照传感器检测环境光强。当检测到环境亮度低于设定阈值时,STM32自动开启照明灯光;当光照恢复到正常水平时,照明灯自动关闭,从而提高夜间出行安全性并节省能耗。
(4)报警距离可调功能:
设备上设有功能按键,使用者可根据实际使用环境(如室内、户外或狭窄通道)调节超声波的测距报警阈值。调节后的阈值由STM32保存并用于后续障碍物检测,提升系统灵活性。
(5)一键呼救功能:
当使用者遇到紧急情况时,可通过按下SOS按键触发求救。系统立即发出语音提示“请帮助我”,同时将SOS求救信号上传至华为云物联网平台。上位机APP端会同步弹窗报警,提醒监护人及时查看。
(6)GPS定位与实时上传功能:
系统集成ATGM336H-5N GPS模块,能够实时采集当前经纬度信息。STM32解析定位数据后,通过4G模块将位置信息上传至华为云物联网平台,供上位机APP实时显示设备位置。
(7)数据上云功能:
利用Air780E 4G模块与华为云物联网平台建立MQTT通信连接,实现设备端数据的远程传输。系统定期上传环境光强(BH1750)、SOS状态、GPS定位等关键数据,云端可存储并推送给上位机APP,实现远程监控。
(8)上位机APP监测功能:
监护人可通过Android或Windows版本的上位机APP实时查看导盲杖状态。APP可显示当前光照强度、定位坐标、SOS报警状态等信息;当发生SOS求救时,APP会自动弹窗提示,并可一键调用高德地图进行导航至设备位置。
【3】项目硬件模块组成
(1)主控模块(STM32F103C8T6):
作为系统的核心控制单元,负责各传感器与外设的数据采集、逻辑处理、语音控制、通信管理及数据上传等功能。通过串口、IIC等接口与其他模块进行通信与控制。
(2)超声波测距模块:
用于检测前方障碍物距离,将测得的距离信息反馈给STM32主控。当检测距离小于设定值时,主控触发语音提示模块发出警示。
(3)语音提示模块:
用于语音播报提示,包括障碍物警告提示和SOS呼救语音提示。模块由STM32控制,在检测到危险或按下呼救键时发出语音提醒。
(4)光照检测模块(BH1750):
通过IIC接口与STM32通信,用于检测环境光强度。当光照不足时,主控自动控制照明灯开启,提升夜间行走安全性。
(5)照明灯模块:
采用USB接口LED灯作为照明光源,由主控根据BH1750检测结果自动控制开关,在光线较暗时点亮以增强可见性。
(6)GPS定位模块(ATGM336H-5N):
通过串口与STM32通信,实时采集当前的经纬度定位信息,为设备提供位置数据。用于上传到云平台及上位机APP进行显示与导航。
(7)4G通信模块(Air780E):
通过串口与STM32连接,实现设备与华为云物联网平台之间的数据传输。主要用于上传GPS定位信息、SOS状态和光照数据,并支持MQTT通信协议。
(8)显示模块(0.96寸OLED显示屏):
采用IIC通信接口,用于显示设备运行状态、报警距离设置、光照值或系统提示信息,方便用户直观了解设备工作情况。
(9)按键输入模块:
包含功能调节按键和SOS一键呼救按键。功能按键用于调整超声波报警距离,SOS按键用于紧急求救触发。
(10)蜂鸣器模块:
采用有源蜂鸣器,高电平触发方式,用于在语音提示前提供辅助报警提示音,提高警示效果。
(11)电源模块:
采用两节18650锂电池为系统供电,提供稳定的直流电源。模块包含稳压电路,为主控芯片、传感器及外设提供所需工作电压,保证系统长时间稳定运行。
【4】设计意义
本设计的意义在于充分利用现代嵌入式技术与物联网通信技术,为视障人群提供更安全、更智能的出行辅助工具。传统的导盲杖仅能通过物理触碰来感知障碍物,存在反应滞后、检测范围有限等缺陷。本系统通过在导盲杖中嵌入STM32微控制器、超声波测距模块和语音提示模块,使设备能够主动检测前方障碍物并进行语音预警,从而有效提高行走的安全性与预判能力。
在夜间或光线较弱的环境中,导盲者及周围行人容易发生碰撞。为此,系统设计了环境光检测与自动照明功能,能够根据环境亮度自动开启照明灯,不仅提升了视障者的可见度,也提醒周围人注意避让,增强了夜间出行的安全性和便利性。
此外,导盲杖还集成了GPS定位与4G通信模块,使得监护人可以通过上位机APP实时查看使用者的位置信息。当发生紧急情况或用户按下SOS呼救键时,设备会同时发出语音呼救并上传报警信息至华为云物联网平台,上位机端会弹窗提醒监护人及时响应。这种“本地+云端”的设计极大地提升了紧急事件的处理效率,保障了使用者的人身安全。
综上所述,本设计不仅在硬件层面上实现了导盲杖的智能化升级,也在社会层面上体现了科技助残的意义。通过融合嵌入式控制、传感检测、物联网通信与人性化交互设计,项目为视障人群提供了一种可行、可靠且具备社会价值的智能辅助出行方案。
【5】国内外研究现状
目前,针对视障人士出行的辅助设备研发已成为国内外智能医疗与康复工程领域的重要方向。传统的盲杖虽然简单可靠,但功能单一,无法应对复杂多变的室外环境。随着嵌入式技术、传感器技术以及物联网的快速发展,智能导盲杖的研究正朝着多功能、集成化与智能化的方向演进。
在国内,研究多以具体的功能集成和实用化为导向。主流方案普遍采用微控制器如STM32或单片机作为核心,通过融合多种低成本传感器来实现环境感知。超声波测距因其技术成熟、成本低廉,成为障碍物检测的首选方案,相关研究如智能化超声拐杖的设计已证实其近距离避障的有效性。同时,GPS与GSM/GPRS模块的引入,为导盲杖增添了定位与远程通信能力,出现了诸如具备定位追踪和SOS求救功能的系统设计。此外,研究者们还尝试集成BH1750等环境光传感器以自动控制照明,并利用语音模块进行状态提示,极大地提升了设备的实用性和人性化程度。然而,国内研究在复杂场景下的感知精度、系统整体的功耗优化以及基于云平台的数据深度应用方面,仍有较大的提升空间。
在国外,智能导盲设备的研究则呈现出更加多元化的技术路径。除基础的超声波、红外应用外,研究者们积极探索计算机视觉、激光雷达以及可见光通信等先进技术。例如,有研究将摄像头嵌入设备,利用AI算法进行场景识别与分类,从而为使用者提供更丰富的环境语义信息;基于可见光通信的室内导航系统则为解决GPS信号缺失环境下的高精度定位提供了新思路。在避障算法上,国外研究较早引入了具有前瞻性验证的局部避障算法,提高了移动过程中的安全性。另一方面,国外项目非常注重人机交互的自然性,普遍采用骨传导耳机、精密振动马达等非侵入式反馈方式,确保使用者在获取信息的同时不隔绝环境声音。云平台与移动应用的整合也更为深入,形成了从终端感知到云端数据处理,再到智能手机应用交互的完整生态链。
总体而言,国内外研究均致力于通过技术手段提升视障人士的独立出行能力。国内研究在系统集成和功能实现上取得了显著进展,侧重于设备的实用性与成本控制;而国外研究则更倾向于探索前沿技术在辅助设备上的应用边界,并在算法的智能性、交互的自然性以及系统的整体生态构建上保持领先。未来的发展趋势将是多传感器信息融合、人工智能深度赋能、低功耗设计与云端协同智能的紧密结合,从而开发出更精准、可靠、人性化的新一代智能导盲设备。
【6】摘要
本设计基于STM32单片机,开发了一种面向视障人群的智能导盲杖系统。系统以STM32F103C8T6为核心控制器,集成了超声波测距、语音提示、环境光检测、GPS定位及4G通信等多种功能模块。导盲杖能够通过超声波传感器实时检测前方障碍物,并通过语音模块进行提示,帮助使用者提前规避风险;通过BH1750光照传感器检测环境亮度,自动控制照明灯的开启与关闭,以提高夜间出行的安全性与可见性;系统还配备了GPS定位模块与4G通信模块,实现实时定位与数据上云功能。监护人可通过上位机APP(支持Android与Windows端)查看设备上传的GPS位置、光照信息及SOS求救状态。当用户按下SOS按钮时,设备会语音播报求救信息,并将报警状态上传至华为云物联网平台,上位机端弹窗提醒监护人及时响应。
本设计通过嵌入式技术与物联网技术的结合,实现了导盲杖从被动到主动的智能化升级,极大地提升了视障人群出行的安全性和便利性。该系统具有结构简单、功能实用、可扩展性强等优点,具有较高的应用推广价值。
关键词:
STM32;导盲杖;超声波测距;语音提示;GPS定位;4G通信;华为云物联网平台
1.2 设计思路
本系统的设计思路主要围绕“安全、智能、可靠”三大核心目标展开。以STM32F103C8T6单片机为主控核心,采用模块化设计理念,将超声波测距、语音提示、光照检测、GPS定位、4G通信等功能模块进行合理分工与有机结合,从而实现导盲杖的智能化控制与信息互联。
在硬件设计上,系统通过超声波传感器实现前方障碍物的实时距离检测,当检测到障碍物距离小于设定阈值时,单片机立即触发语音模块进行语音提示,提醒用户注意前方障碍。环境光检测部分利用BH1750光照传感器检测周围亮度,并通过STM32控制照明灯的自动开关,保证夜间出行的安全。系统还通过按键实现测距报警距离的灵活调节,使设备适应不同使用场景。
在安全保障方面,设计中增加了一键呼救功能。用户在遇到紧急情况时,只需按下SOS按钮,系统即可通过语音播报提醒周围人求助,同时利用4G模块将SOS报警信息和实时GPS定位数据上传至华为云物联网平台。监护人可通过上位机APP(Android或Windows版本)实时查看导盲杖的位置信息,并在接收到报警后快速响应。
在软件设计上,STM32端主要使用C语言编写程序,通过串口与GPS模块、4G模块通信,使用IIC协议与BH1750和OLED显示屏进行数据交互。数据处理部分注重实时性和稳定性,确保在复杂环境中系统能够持续工作。上位机APP采用Qt5(C++)开发,提供数据展示、状态提醒和定位导航等功能,实现设备端与云端的数据闭环。
整体而言,本设计在实现硬件与软件的协调配合基础上,充分利用了物联网技术与嵌入式控制技术,实现了导盲杖的智能化、安全化和人性化设计,为视障人群提供了一种可行且可靠的出行辅助方案。
1.3 系统功能总结
| 功能编号 | 功能名称 | 功能描述 | 实现方式与模块说明 |
|---|---|---|---|
| (1) | 超声波测距检测功能 | 通过超声波传感器实时检测前方障碍物距离,当距离小于设定阈值时触发报警提醒,防止碰撞。 | 使用超声波测距模块(HC-SR04),通过STM32测量回波时间计算距离。 |
| (2) | 语音提示功能 | 当检测到障碍物或触发SOS呼救时,系统通过语音模块进行语音播报提示,提醒用户和周围行人注意。 | 使用语音播报模块(如WT588D),由STM32控制播放不同语音内容。 |
| (3) | 环境光检测与照明 | 利用光照传感器检测环境亮度,根据光线强弱自动开启或关闭照明灯,提高夜间出行安全性。 | 使用BH1750光照传感器(IIC通信),由STM32控制USB照明灯。 |
| (4) | 报警距离调节功能 | 用户可通过按键调整超声波报警触发距离,以适应不同使用环境(如室内、室外)。 | 按键输入模块,通过STM32记录并修改报警阈值参数。 |
| (5) | 一键SOS呼救功能 | 当用户遇到紧急情况时,按下SOS按键,系统立即语音提示“需要帮助”,并上传求救信息至云端。 | SOS按键输入模块 + 语音模块 + 4G模块上传至华为云。 |
| (6) | GPS定位功能 | 系统通过GPS模块实时采集经纬度信息,获取使用者当前位置。 | 使用ATGM336H-5N GPS模块,通过串口通信读取定位信息。 |
| (7) | 数据上云功能 | 将GPS定位数据、光照信息及SOS状态通过4G模块上传至华为云物联网平台,实现远程监控与信息共享。 | 采用Air780E 4G模块,通过MQTT协议与华为云物联网服务器通信。 |
| (8) | 上位机APP监控功能 | 监护人可通过上位机APP(Android与Windows版本)实时查看导盲杖上传的数据,包括位置、SOS状态、环境光信息等。 | 上位机使用Qt5(C++)开发,通过MQTT协议接收云端数据并进行可视化显示。 |
| (9) | OLED信息显示功能 | 导盲杖通过OLED显示屏显示关键信息,如系统状态、光照值、GPS连接状态等,方便用户查看设备运行情况。 | 使用0.96寸IIC接口OLED显示屏,与STM32通信显示实时数据。 |
| (10) | 电源供电与管理功能 | 系统采用两节18650锂电池供电,保证设备长时间运行,并可根据使用情况适当进行电源管理。 | 使用18650电池组 + 稳压模块,为主控和外围模块供电。 |
系统整体由STM32单片机作为核心控制单元,通过多种传感器模块与通信模块实现环境感知、语音提示、定位上传、云端交互等功能。系统充分体现了嵌入式控制与物联网技术的结合,具备较高的实用性与社会价值。
1.4 开发工具的选择
【1】设备端开发
硬件设备端的开发主要依赖于C语言,利用该语言直接操作硬件寄存器,确保系统运行的高效性和低延迟。C语言在嵌入式开发中具有广泛的应用,它能够直接访问硬件,满足对资源消耗和响应速度的严格要求。为了编写高效、稳定的代码,开发工具选择了Keil uVision 5作为主要的开发环境。Keil是一个专业的嵌入式开发工具,广泛应用于基于ARM架构的微控制器(如STM32)开发。Keil提供了完善的调试、编译和仿真支持,能够帮助在软件开发过程中高效地进行调试、单步执行以及断点设置,确保开发的稳定性和高效性。
STM32F103RCT6是项目中使用的主控芯片,它基于ARM Cortex-M3架构,拥有强大的计算能力和丰富的外设接口。在硬件编程中,寄存器级编程是常用的方式,这要求开发者对芯片的硬件寄存器有深入的理解。在Keil环境中,通过STM32的寄存器直接控制GPIO、ADC、I2C、SPI等硬件接口,以满足各个硬件模块(如传感器、执行器、显示屏等)与主控芯片的交互。使用寄存器编程能够提供更高效、精确的控制,避免了外部库的开销,同时也能深入调控硬件特性,提升系统性能。
【2】上位机开发
本项目的上位机开发基于Qt 5框架,使用**C**作为主要编程语言。Qt是一个跨平台的应用开发框架,广泛用于开发GUI应用程序。Qt提供了丰富的GUI组件和工具,能够高效地实现图形界面的设计与开发。C则作为Qt的底层语言,具有高效的性能和良好的控制力,非常适合用于处理设备与系统之间的数据交互、通信协议的实现和复杂的计算任务。在项目中,Qt被用于开发Windows平台的桌面应用程序以及Android平台的手机APP。Qt框架的跨平台特性使得开发者能够使用同一套代码在不同操作系统上进行构建和部署,大大提高了开发效率。
为了方便开发和调试,上位机的开发采用了Qt Creator作为主要的集成开发环境(IDE)。Qt Creator是一款由Qt官方提供的开发工具,专为Qt应用程序开发设计,支持C++、QML和JavaScript等语言。Qt Creator提供了丰富的功能,如代码编辑、调试、构建、版本控制集成等,能够显著提升开发者的生产力。在本项目中,Qt Creator为开发者提供了自动化构建、界面设计工具(如Qt Designer)和调试工具(如QDebug和QML调试工具),使得开发过程更加高效和流畅。
上位机与硬件设备端的通信采用了基于TCP/IP协议的数据传输方式。为了实现这一功能,Qt提供了丰富的网络编程支持,尤其是QTcpSocket和QTcpServer类,使得上位机能够轻松地与硬件设备建立TCP连接,进行数据收发。上位机通过WIFI连接ESP8266-WIFI模块,ESP8266模块创建TCP服务器,上位机应用则作为客户端连接到服务器,进行实时的数据传输与控制命令的下发。
为了满足不同用户的需求,本项目需要支持Windows平台的桌面应用和Android平台的移动APP。Qt的跨平台特性使得开发人员能够在一个代码库下完成多平台应用的开发和移植。开发者仅需要编写一次应用逻辑和用户界面,就可以通过Qt的跨平台构建工具生成Windows和Android两个平台的可执行文件。此外,Qt提供了丰富的文档和社区支持,帮助开发者解决平台差异和兼容性问题,确保应用在不同平台上都能稳定运行。
总体而言,上位机开发环境采用了Qt 5框架和C++语言,结合Qt Creator集成开发环境,提供了一个高效、稳定、跨平台的开发工具链。通过Qt强大的GUI设计、网络通信、多线程支持以及数据库管理功能,开发者能够轻松实现与硬件设备的交互、控制设备、处理传感器数据,并为用户提供直观、流畅的操作体验。
1.5 系统框架图
系统主流程图:
1.6 系统原理图
1.7 实物图
1.8 模块的技术详情介绍
1. 主控模块
-
- •
核心芯片
-
- :STM32F103C8T6(ARM Cortex-M3内核)•
工作频率
-
- :72MHz•
关键资源
-
- :具备2个I2C接口(用于OLED显示屏和BH1750传感器)、3个USART串口(分别用于GPS模块、4G模块和调试)、多个通用GPIO(用于控制蜂鸣器、读取按键、触发超声波等)、定时器(用于产生超声波触发信号和精确计时)。•
编程方式
-
- :采用寄存器方式直接操作外设,代码执行效率高,对硬件底层控制力强。•
核心功能
- :作为系统的控制中枢,负责协调所有外设模块的工作,包括数据采集、逻辑判断、业务处理和数据通信。
2. 障碍物检测模块
-
- •
核心器件
-
- :HC-SR04超声波测距模块•
测距原理
-
- :主控芯片通过GPIO发送一个至少10μs的高电平脉冲触发信号,模块自动发射8个40kHz的超声波脉冲,并检测回波。模块的ECHO引脚会输出一个高电平,其持续时间与测距时间成正比。主控通过定时器捕获此高电平的宽度,根据公式
距离 = (高电平时间 * 声速(340m/s)) / 2
-
- 计算得出障碍物距离。•
关键技术
-
- :通过定时器输入捕获模式精确测量回波高电平时间,并采用中位值滤波法等软件算法对多次测量结果进行处理,以抑制偶然误差。•
报警机制
- :将计算出的距离值与用户通过按键设定的报警阈值进行比较,若低于阈值,则立即触发蜂鸣器和语音提示。
3. 定位与无线通信模块
-
- •
GPS定位子模块
-
- :
-
- •
-
核心器件
-
-
- :ATGM336H-5N模块•
-
通信协议
-
-
- :通过USART串口与主控通信,默认波特率9600。•
-
数据协议
-
-
- :输出标准NMEA-0183协议语句(如、GPGGA),主控通过解析这些语句,提取出经纬度、UTC时间、定位状态等有效信息。
-
• 4G通信子模块:
-
-
- •
-
核心器件
-
-
- :Air780E模块•
-
通信协议
-
-
- :通过另一路USART串口与主控通信。•
-
上行协议
下行协议
-
- :同时可订阅云平台下发的指令Topic,实现对设备的远程控制。
4. 环境感知与人机交互模块
-
- •
环境光检测子模块
-
- :
-
- •
-
核心器件
-
-
- :BH1750FVI数字光照传感器•
-
通信协议
-
-
- :I2C通信,地址为0x23或0x5C。•
-
工作流程
-
-
- :主控通过I2C总线发送测量指令,传感器完成一次光照度采集后,主控读取2字节的原始数据,通过换算得到以勒克斯为单位的照度值。系统将此值与预设阈值比较,自动控制USB照明灯的开关。
-
• 语音提示与报警子模块:
-
-
- •
-
核心器件
-
-
- :有源蜂鸣器(高电平触发)或语音播报模块。•
-
实现方式
• 用户输入子模块:
-
-
- •
-
功能
-
- :包含两个物理按键。一个用于切换和设置超声波报警距离,另一个专用于SOS一键呼救。主控通过GPIO中断或循环扫描方式检测按键动作。
5. 显示与电源模块
-
- •
显示模块
-
- :
-
- •
-
核心器件
-
-
- :0.96英寸OLED显示屏(SSD1306驱动芯片)•
-
通信协议
-
-
- :I2C通信。•
-
显示内容
-
-
- :用于实时显示系统状态信息,如当前超声波测距值、环境光照度、GPS定位状态(是否锁定)、报警距离阈值、电池电量等,为用户提供直观的视觉反馈。
-
• 电源管理模块:
-
-
- •
-
供电方案
-
-
- :采用2节18650锂电池串联供电(标称电压7.4V),配合DC-DC降压模块,为STM32核心板及各外设模块提供稳定、干净的5V和3.3V电压。•
-
关键设计
6. 云平台与上位机模块
-
- •
华为云物联网平台
-
- :
-
- •
-
角色
-
-
- :作为设备管理和数据汇聚的中心。•
-
功能
-
-
- :平台为每个设备创建唯一的“产品”和“设备标识”,并定义“属性”(光强、GPS、SOS)。设备通过MQTT协议上报属性数据至平台,平台进行持久化存储和转发。APP通过调用平台的API接口,获取设备的最新状态和数据。
-
• 上位机APP(Qt5开发):
-
-
- •
-
技术框架
-
-
- :使用Qt5(C++)进行跨平台开发,一套代码可编译运行于Android手机和Windows电脑。•
-
核心功能
-
-
- :
-
- 1.
-
-
设备监控
-
-
-
- :以图形化界面实时显示从云平台获取的环境光强度、设备位置(在地图控件上标注)、SOS状态。2.
-
-
告警处理
-
-
-
- :当收到SOS信号时,界面立即弹窗并伴随声音告警,引起监护人注意。3.
-
-
导航集成
-
-
- :通过调用系统API或高德地图/百度地图的开放URI Scheme,实现“一键导航”功能,自动打开地图APP并规划前往设备当前位置的路线。
-
二、部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA。
2.1 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
2.2 开通物联网服务
地址: https://www.huaweicloud.com/product/iothub.html
开通免费单元。
点击立即创建。
正在创建标准版实例,需要等待片刻。
创建完成之后,点击详情。 可以看到标准版实例的设备接入端口和地址。
下面框起来的就是端口号和域名
点击实例名称,可以查看当前免费单元的配置情况。
开通之后,点击接入信息,也能查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。
总结:
端口号: MQTT (1883)| MQTTS (8883)
接入地址: dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com
根据域名地址得到IP地址信息:
打开Windows电脑的命令行控制台终端,使用ping 命令。ping一下即可。
Microsoft Windows [版本 10.0.19045.5011]
(c) Microsoft Corporation。保留所有权利。
C:UsersLenovo>ping dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping dab1a1f2c6.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
来自 117.78.5.125 的回复: 字节=32 时间=37ms TTL=44
117.78.5.125 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 37ms,最长 = 37ms,平均 = 37ms
C:UsersLenovo>
MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口合适。
2.3 创建产品
链接:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-dev/all-product?instanceId=03c5c68c-e588-458c-90c3-9e4c640be7af
(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协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
业务流程:
(2)华为云平台MQTT协议使用限制
| 描述 | 限制 |
|---|---|
| 支持的MQTT协议版本 | 3.1.1 |
| 与标准MQTT协议的区别 | 支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg |
| MQTTS支持的安全等级 | 采用TCP通道基础 + TLS协议(最高TLSv1.3版本) |
| 单帐号每秒最大MQTT连接请求数 | 无限制 |
| 单个设备每分钟支持的最大MQTT连接数 | 1 |
| 单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关 | 3KB/s |
| MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝 | 1MB |
| MQTT连接心跳时间建议值 | 心跳时间限定为30至1200秒,推荐设置为120秒 |
| 产品是否支持自定义Topic | 支持 |
| 消息发布与订阅 | 设备只能对自己的Topic进行消息发布与订阅 |
| 每个订阅请求的最大订阅数 | 无限制 |
(3)主题订阅格式
帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
(4)主题发布格式
对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。
这个操作称为:属性上报。
帮助文档地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:
发布的主题格式:
$oc/devices/{device_id}/sys/properties/report
最终的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。
上传的JSON数据格式如下:
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值>,
..........
}
}
]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字1":30,"你的字段名字2":10,"你的字段名字3":1,"你的字段名字4":0}}]}
2.6 MQTT三元组
MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。
接下来介绍,华为云平台的MQTT三元组参数如何得到。
(1)MQTT服务器地址
要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。
帮助文档地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。
根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)
华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883
如何得到IP地址?如何域名转IP? 打开Windows的命令行输入以下命令。
ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
(2)生成MQTT三元组
华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
2.7 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
MQTT软件下载地址【免费】: https://download.csdn.net/download/xiaolong1126626497/89928772
(1)填入登录信息
打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
(2)打开网页查看
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据:
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
(3)MQTT登录测试参数总结
MQTT服务器: 117.78.5.125
MQTT端口号: 183
//物联网服务器的设备信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
//订阅与发布的主题
#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //订阅
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //发布
发布的数据:
{"services": [{"service_id": "stm32","properties":{"你的字段名字1":30,"你的字段名字2":10,"你的字段名字3":1,"你的字段名字4":0}}]}
2.8 创建IAM账户
创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】获取项目凭证 ** 点击左上角用户名,选择下拉菜单里的我的凭证
项目凭证:
28add376c01e4a61ac8b621c714bf459
【2】创建IAM用户
鼠标放在左上角头像上,在下拉菜单里选择统一身份认证。
点击左上角创建用户。
创建成功:
【3】创建完成
用户信息如下:
主用户名 l19504562721
IAM用户 ds_abc
密码 DS12345678
2.9 获取影子数据
帮助文档:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
设备影子介绍:
设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
每个设备有且只有一个设备影子,由设备ID唯一标识
设备影子仅保存最近一次设备的上报数据和预期数据
无论该设备是否在线,都可以通过该影子获取和设置设备的属性
简单来说:设备影子就是保存,设备最新上传的一次数据。
我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。
如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。
调试完成看右下角的响应体,就是返回的影子数据。
设备影子接口返回的数据如下:
{
"device_id": "663cb18871d845632a0912e7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DHT11_T": 18,
"DHT11_H": 90,
"BH1750": 38,
"MQ135": 70
},
"event_time": "20240509T113448Z"
},
"version": 3
}
]
}
调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用Qt写代码访问此链接,获取影子数据,完成上位机开发。
链接如下:
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow
三、上位机开发
当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
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://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
软件安装时断网安装,否则会提示输入账户。
安装的时候,第一个复选框里的编译器可以全选,直接点击下一步继续安装。
选择编译器: (一定要看清楚了)
3.2 新建上位机工程
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
3.3 切换编译器
在左下角是可以切换编译器的。 可以选择用什么样的编译器编译程序。
目前新建工程的时候选择了2种编译器。 一种是mingw32这个编译Windows下运行的程序。 一种是Android编译器,可以生成Android手机APP。
不过要注意:Android的编译器需要配置一些环境才可以正常使用,这个大家可以网上找找教程配置一下就行了。
windows的编译器就没有这么麻烦,安装好Qt就可以编译使用。
下面我这里就选择的 mingw32这个编译器,编译Windows下运行的程序。
3.4 编译测试功能
创建完毕之后,编译测试一下功能是否OK。
点击左下角的绿色三角形按钮。
正常运行就可以看到弹出一个白色的框框。这就表示工程环境没有问题了。 接下来就可以放心的设计界面了。
3.5 设计UI界面与工程配置
【1】打开UI文件
打开默认的界面如下:
【2】开始设计界面
根据自己需求设计界面。
3.6 开发环境配置
1. 环境要求
- • Qt 5.15.2 或更高版本• Android NDK r21 或更高版本(Android版本)• JDK 11 或更高版本(Android版本)• Windows 10 SDK(Windows版本)
2. 依赖库
QT += core gui network positioning quick location widgets charts svg
QT += multimedia multimediawidgets
3.7 完整代码实现
1. 项目文件 (SmartCaneMonitor.pro)
QT += core gui network positioning quick location widgets charts svg
QT += multimedia multimediawidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES +=
main.cpp
mainwindow.cpp
mqttclient.cpp
devicedata.cpp
mapwidget.cpp
sosalert.cpp
HEADERS +=
mainwindow.h
mqttclient.h
devicedata.h
mapwidget.h
sosalert.h
FORMS +=
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
RESOURCES +=
resources.qrc
android {
QT += androidextras
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
}
win32 {
RC_ICONS = app_icon.ico
}
macx {
ICON = app_icon.icns
}
2. 资源文件 (resources.qrc)
<RCC>
<qresource prefix="/">
<file>icons/gps.png</file>
<file>icons/sos.png</file>
<file>icons/warning.png</file>
<file>icons/info.png</file>
<file>icons/navigation.png</file>
<file>sounds/alert.wav</file>
<file>styles/default.qss</file>
</qresource>
</RCC>
3. 主窗口头文件 (mainwindow.h)
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTimer>
#include <QGeoCoordinate>
#include <QSoundEffect>
#include <QChart>
#include <QLineSeries>
#include <QValueAxis>
#include "mqttclient.h"
#include "devicedata.h"
#include "mapwidget.h"
#include "sosalert.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onConnected();
void onDisconnected();
void onMessageReceived(const QString &topic, const QByteArray &payload);
void onSosButtonClicked();
void onNavigationButtonClicked();
void onDistanceThresholdChanged(int value);
void updateDeviceStatus();
void onSosAlertConfirmed();
private:
void setupUI();
void setupCharts();
void updateLocationOnMap();
void showSosAlert();
void playAlertSound();
private:
Ui::MainWindow *ui;
MqttClient *m_mqttClient;
DeviceData m_deviceData;
MapWidget *m_mapWidget;
SosAlert *m_sosAlert;
QTimer *m_statusTimer;
QSoundEffect *m_alertSound;
// Charts
QChart *m_distanceChart;
QLineSeries *m_distanceSeries;
QValueAxis *m_axisX;
QValueAxis *m_axisY;
int m_timeCounter;
};
#endif // MAINWINDOW_H
4. 主窗口实现 (mainwindow.cpp)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QMessageBox>
#include <QFile>
#include <QStyleFactory>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_mqttClient(new MqttClient(this))
, m_mapWidget(new MapWidget(this))
, m_sosAlert(new SosAlert(this))
, m_statusTimer(new QTimer(this))
, m_alertSound(new QSoundEffect(this))
, m_distanceChart(new QChart())
, m_distanceSeries(new QLineSeries())
, m_axisX(new QValueAxis())
, m_axisY(new QValueAxis())
, m_timeCounter(0)
{
ui->setupUi(this);
setupUI();
setupCharts();
// 设置报警声音
m_alertSound->setSource(QUrl("qrc:/sounds/alert.wav"));
m_alertSound->setVolume(0.8);
// 连接MQTT信号
connect(m_mqttClient, &MqttClient::connected, this, &MainWindow::onConnected);
connect(m_mqttClient, &MqttClient::disconnected, this, &MainWindow::onDisconnected);
connect(m_mqttClient, &MqttClient::messageReceived, this, &MainWindow::onMessageReceived);
// 连接UI信号
connect(ui->sosButton, &QPushButton::clicked, this, &MainWindow::onSosButtonClicked);
connect(ui->navigationButton, &QPushButton::clicked, this, &MainWindow::onNavigationButtonClicked);
connect(ui->distanceThresholdSlider, &QSlider::valueChanged, this, &MainWindow::onDistanceThresholdChanged);
connect(m_sosAlert, &SosAlert::confirmed, this, &MainWindow::onSosAlertConfirmed);
// 状态更新定时器
connect(m_statusTimer, &QTimer::timeout, this, &MainWindow::updateDeviceStatus);
m_statusTimer->start(1000); // 1秒更新一次
// 连接到华为云
m_mqttClient->connectToHost();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::setupUI()
{
// 设置窗口标题和图标
setWindowTitle("智能导盲杖监控系统");
setWindowIcon(QIcon(":/icons/gps.png"));
// 设置样式表
QFile styleFile(":/styles/default.qss");
if (styleFile.open(QFile::ReadOnly)) {
QString styleSheet = QLatin1String(styleFile.readAll());
qApp->setStyleSheet(styleSheet);
}
// 初始化状态标签
ui->statusLabel->setText("正在连接华为云...");
ui->statusLabel->setStyleSheet("color: orange;");
// 设置地图部件
ui->mapLayout->addWidget(m_mapWidget);
// 初始化设备数据
ui->latitudeLabel->setText("--");
ui->longitudeLabel->setText("--");
ui->distanceLabel->setText("--");
ui->lightLevelLabel->setText("--");
ui->batteryLabel->setText("--");
}
void MainWindow::setupCharts()
{
// 设置距离图表
m_distanceChart->addSeries(m_distanceSeries);
m_distanceChart->legend()->hide();
m_distanceChart->setTitle("实时距离监测");
m_axisX->setTitleText("时间 (秒)");
m_axisX->setRange(0, 60);
m_axisX->setLabelFormat("%d");
m_axisY->setTitleText("距离 (cm)");
m_axisY->setRange(0, 500);
m_axisY->setLabelFormat("%d");
m_distanceChart->addAxis(m_axisX, Qt::AlignBottom);
m_distanceChart->addAxis(m_axisY, Qt::AlignLeft);
m_distanceSeries->attachAxis(m_axisX);
m_distanceSeries->attachAxis(m_axisY);
ui->distanceChartView->setChart(m_distanceChart);
ui->distanceChartView->setRenderHint(QPainter::Antialiasing);
}
void MainWindow::onConnected()
{
ui->statusLabel->setText("已连接到华为云");
ui->statusLabel->setStyleSheet("color: green;");
// 订阅设备主题
m_mqttClient->subscribeToDeviceData();
}
void MainWindow::onDisconnected()
{
ui->statusLabel->setText("连接断开");
ui->statusLabel->setStyleSheet("color: red;");
}
void MainWindow::onMessageReceived(const QString &topic, const QByteArray &payload)
{
QJsonDocument doc = QJsonDocument::fromJson(payload);
if (!doc.isObject()) return;
QJsonObject obj = doc.object();
// 更新设备数据
if (obj.contains("latitude") && obj.contains("longitude")) {
double lat = obj["latitude"].toDouble();
double lon = obj["longitude"].toDouble();
m_deviceData.setLocation(lat, lon);
ui->latitudeLabel->setText(QString::number(lat, 'f', 6));
ui->longitudeLabel->setText(QString::number(lon, 'f', 6));
updateLocationOnMap();
}
if (obj.contains("distance")) {
int distance = obj["distance"].toInt();
m_deviceData.setDistance(distance);
ui->distanceLabel->setText(QString::number(distance) + " cm");
// 更新图表
m_distanceSeries->append(m_timeCounter, distance);
if (m_timeCounter > 60) {
m_axisX->setRange(m_timeCounter - 60, m_timeCounter);
}
m_timeCounter++;
// 检查距离报警
int threshold = ui->distanceThresholdSlider->value();
if (distance < threshold) {
ui->distanceLabel->setStyleSheet("color: red; font-weight: bold;");
playAlertSound();
} else {
ui->distanceLabel->setStyleSheet("color: black;");
}
}
if (obj.contains("light_level")) {
int lightLevel = obj["light_level"].toInt();
m_deviceData.setLightLevel(lightLevel);
ui->lightLevelLabel->setText(QString::number(lightLevel) + " lux");
}
if (obj.contains("battery")) {
int battery = obj["battery"].toInt();
m_deviceData.setBatteryLevel(battery);
ui->batteryLabel->setText(QString::number(battery) + " %");
ui->batteryProgressBar->setValue(battery);
}
if (obj.contains("sos") && obj["sos"].toBool()) {
showSosAlert();
}
}
void MainWindow::onSosButtonClicked()
{
// 发送SOS指令到设备
m_mqttClient->sendSosCommand();
QMessageBox::information(this, "SOS求助", "已发送求助信号到导盲杖设备");
}
void MainWindow::onNavigationButtonClicked()
{
if (m_deviceData.isLocationValid()) {
double lat = m_deviceData.getLatitude();
double lon = m_deviceData.getLongitude();
// 打开高德地图进行导航
#ifdef Q_OS_ANDROID
QString url = QString("amapuri://route/plan/?dlat=%1&dlon=%2&dname=导盲杖位置&dev=0&t=0")
.arg(lat).arg(lon);
#else
QString url = QString("https://uri.amap.com/navigation?to=%1,%2,导盲杖位置&mode=car&policy=0")
.arg(lon).arg(lat);
#endif
QDesktopServices::openUrl(QUrl(url));
} else {
QMessageBox::warning(this, "导航", "无法获取有效的设备位置信息");
}
}
void MainWindow::onDistanceThresholdChanged(int value)
{
ui->distanceThresholdLabel->setText(QString::number(value) + " cm");
// 发送新的阈值到设备
m_mqttClient->sendDistanceThreshold(value);
}
void MainWindow::updateDeviceStatus()
{
QDateTime currentTime = QDateTime::currentDateTime();
ui->lastUpdateLabel->setText(currentTime.toString("yyyy-MM-dd hh:mm:ss"));
// 检查设备在线状态(假设5分钟内无数据为离线)
if (m_deviceData.getLastUpdate().secsTo(currentTime) > 300) {
ui->statusLabel->setText("设备离线");
ui->statusLabel->setStyleSheet("color: red;");
}
}
void MainWindow::updateLocationOnMap()
{
if (m_deviceData.isLocationValid()) {
m_mapWidget->setMarkerPosition(m_deviceData.getLatitude(), m_deviceData.getLongitude());
}
}
void MainWindow::showSosAlert()
{
m_sosAlert->showAlert(m_deviceData.getLatitude(), m_deviceData.getLongitude());
playAlertSound();
}
void MainWindow::playAlertSound()
{
if (ui->soundCheckBox->isChecked()) {
m_alertSound->play();
}
}
void MainWindow::onSosAlertConfirmed()
{
// 确认收到SOS报警,可以记录日志或发送确认信息
qDebug() << "SOS报警已确认";
}
5. MQTT客户端头文件 (mqttclient.h)
#ifndef MQTTCLIENT_H
#define MQTTCLIENT_H
#include <QObject>
#include <QMqttClient>
#include <QJsonObject>
#include <QJsonDocument>
class MqttClient : public QObject
{
Q_OBJECT
public:
explicit MqttClient(QObject *parent = nullptr);
~MqttClient();
void connectToHost();
void disconnectFromHost();
void subscribeToDeviceData();
void sendSosCommand();
void sendDistanceThreshold(int threshold);
signals:
void connected();
void disconnected();
void messageReceived(const QString &topic, const QByteArray &payload);
private slots:
void onConnected();
void onDisconnected();
void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic);
private:
QMqttClient *m_client;
QString m_deviceTopic;
QString m_controlTopic;
void setupMqttClient();
};
#endif // MQTTCLIENT_H
6. MQTT客户端实现 (mqttclient.cpp)
#include "mqttclient.h"
#include <QDebug>
MqttClient::MqttClient(QObject *parent) : QObject(parent)
{
m_client = new QMqttClient(this);
setupMqttClient();
// 配置主题(根据实际华为云配置修改)
m_deviceTopic = "$oc/devices/smart_cane_001/sys/messages/down";
m_controlTopic = "$oc/devices/smart_cane_001/sys/messages/up";
}
MqttClient::~MqttClient()
{
disconnectFromHost();
}
void MqttClient::setupMqttClient()
{
// 华为云物联网平台连接参数
m_client->setHostname("your-iot-endpoint.iot-mqtts.cn-north-4.myhuaweicloud.com");
m_client->setPort(1883);
m_client->setClientId("smart_cane_monitor_001");
m_client->setUsername("your-device-id");
m_client->setPassword("your-device-secret");
connect(m_client, &QMqttClient::connected, this, &MqttClient::onConnected);
connect(m_client, &QMqttClient::disconnected, this, &MqttClient::onDisconnected);
connect(m_client, &QMqttClient::messageReceived, this, &MqttClient::onMessageReceived);
}
void MqttClient::connectToHost()
{
m_client->connectToHost();
}
void MqttClient::disconnectFromHost()
{
m_client->disconnectFromHost();
}
void MqttClient::subscribeToDeviceData()
{
auto subscription = m_client->subscribe(m_deviceTopic, 1);
if (!subscription) {
qWarning() << "订阅失败:" << m_deviceTopic;
return;
}
connect(subscription, &QMqttSubscription::messageReceived,
this, [this](QMqttMessage msg) {
emit messageReceived(msg.topic().name(), msg.payload());
});
}
void MqttClient::sendSosCommand()
{
QJsonObject command;
command["cmd"] = "sos_alert";
command["timestamp"] = QDateTime::currentDateTime().toSecsSinceEpoch();
QJsonDocument doc(command);
m_client->publish(m_controlTopic, doc.toJson(), 1, false);
}
void MqttClient::sendDistanceThreshold(int threshold)
{
QJsonObject command;
command["cmd"] = "set_distance_threshold";
command["threshold"] = threshold;
command["timestamp"] = QDateTime::currentDateTime().toSecsSinceEpoch();
QJsonDocument doc(command);
m_client->publish(m_controlTopic, doc.toJson(), 1, false);
}
void MqttClient::onConnected()
{
qDebug() << "MQTT连接成功";
emit connected();
}
void MqttClient::onDisconnected()
{
qDebug() << "MQTT连接断开";
emit disconnected();
}
void MqttClient::onMessageReceived(const QByteArray &message, const QMqttTopicName &topic)
{
qDebug() << "收到消息:" << topic.name() << message;
emit messageReceived(topic.name(), message);
}
7. 设备数据结构 (devicedata.h)
#ifndef DEVICEDATA_H
#define DEVICEDATA_H
#include <QObject>
#include <QDateTime>
class DeviceData : public QObject
{
Q_OBJECT
public:
explicit DeviceData(QObject *parent = nullptr);
// Getters
double getLatitude() const{ return m_latitude; }
double getLongitude() const{ return m_longitude; }
int getDistance() const{ return m_distance; }
int getLightLevel() const{ return m_lightLevel; }
int getBatteryLevel() const{ return m_batteryLevel; }
bool isSosActive() const{ return m_sosActive; }
QDateTime getLastUpdate() const{ return m_lastUpdate; }
bool isLocationValid() const{ return m_latitude != 0.0 && m_longitude != 0.0; }
// Setters
void setLocation(double lat, double lon);
void setDistance(int distance);
void setLightLevel(int level);
void setBatteryLevel(int level);
void setSosActive(bool active);
private:
double m_latitude;
double m_longitude;
int m_distance;
int m_lightLevel;
int m_batteryLevel;
bool m_sosActive;
QDateTime m_lastUpdate;
};
#endif // DEVICEDATA_H
8. 设备数据实现 (devicedata.cpp)
#include "devicedata.h"
DeviceData::DeviceData(QObject *parent) : QObject(parent)
{
m_latitude = 0.0;
m_longitude = 0.0;
m_distance = 0;
m_lightLevel = 0;
m_batteryLevel = 0;
m_sosActive = false;
m_lastUpdate = QDateTime::currentDateTime();
}
void DeviceData::setLocation(double lat, double lon)
{
m_latitude = lat;
m_longitude = lon;
m_lastUpdate = QDateTime::currentDateTime();
emit locationChanged(lat, lon);
}
void DeviceData::setDistance(int distance)
{
m_distance = distance;
m_lastUpdate = QDateTime::currentDateTime();
emit distanceChanged(distance);
}
void DeviceData::setLightLevel(int level)
{
m_lightLevel = level;
m_lastUpdate = QDateTime::currentDateTime();
emit lightLevelChanged(level);
}
void DeviceData::setBatteryLevel(int level)
{
m_batteryLevel = level;
m_lastUpdate = QDateTime::currentDateTime();
emit batteryLevelChanged(level);
}
void DeviceData::setSosActive(bool active)
{
m_sosActive = active;
m_lastUpdate = QDateTime::currentDateTime();
if (active) {
emit sosAlert();
}
}
9. 地图部件 (mapwidget.h)
#ifndef MAPWIDGET_H
#define MAPWIDGET_H
#include <QWidget>
#include <QWebEngineView>
#include <QWebChannel>
class MapWidget : public QWidget
{
Q_OBJECT
public:
explicit MapWidget(QWidget *parent = nullptr);
void setMarkerPosition(double lat, double lon);
private:
QWebEngineView *m_webView;
QWebChannel *m_webChannel;
void setupMap();
};
#endif // MAPWIDGET_H
10. 地图部件实现 (mapwidget.cpp)
#include "mapwidget.h"
#include <QVBoxLayout>
#include <QFile>
#include <QWebEngineSettings>
MapWidget::MapWidget(QWidget *parent) : QWidget(parent)
{
m_webView = new QWebEngineView(this);
m_webChannel = new QWebChannel(this);
m_webView->page()->setWebChannel(m_webChannel);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_webView);
setupMap();
}
void MapWidget::setupMap()
{
// 加载本地HTML地图文件
QFile htmlFile(":/map/map.html");
if (htmlFile.open(QIODevice::ReadOnly)) {
QString htmlContent = htmlFile.readAll();
m_webView->setHtml(htmlContent);
} else {
// 使用在线地图作为备选
m_webView->setUrl(QUrl("https://www.openstreetmap.org/"));
}
}
void MapWidget::setMarkerPosition(double lat, double lon)
{
QString jsCode = QString("updateMarker(%1, %2);").arg(lat).arg(lon);
m_webView->page()->runJavaScript(jsCode);
}
11. SOS报警对话框 (sosalert.h)
#ifndef SOSALERT_H
#define SOSALERT_H
#include <QDialog>
namespace Ui {
class SosAlert;
}
class SosAlert : public QDialog
{
Q_OBJECT
public:
explicit SosAlert(QWidget *parent = nullptr);
~SosAlert();
void showAlert(double lat, double lon);
signals:
void confirmed();
private slots:
void onConfirmButtonClicked();
private:
Ui::SosAlert *ui;
};
#endif // SOSALERT_H
12. SOS报警对话框实现 (sosalert.cpp)
#include "sosalert.h"
#include "ui_sosalert.h"
#include <QSoundEffect>
SosAlert::SosAlert(QWidget *parent) :
QDialog(parent),
ui(new Ui::SosAlert)
{
ui->setupUi(this);
setWindowTitle("紧急求助警报");
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
connect(ui->confirmButton, &QPushButton::clicked, this, &SosAlert::onConfirmButtonClicked);
}
SosAlert::~SosAlert()
{
delete ui;
}
void SosAlert::showAlert(double lat, double lon)
{
ui->locationLabel->setText(QString("纬度: %1, 经度: %2").arg(lat, 0, 'f', 6).arg(lon, 0, 'f', 6));
ui->timeLabel->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
show();
activateWindow();
raise();
}
void SosAlert::onConfirmButtonClicked()
{
emit confirmed();
accept();
}
13. 主函数 (main.cpp)
#include "mainwindow.h"
#include <QApplication>
#include <QTranslator>
#include <QSettings>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置应用程序信息
a.setApplicationName("智能导盲杖监控系统");
a.setApplicationVersion("1.0.0");
a.setOrganizationName("SmartTech");
// 加载翻译文件(可选)
QTranslator translator;
if (translator.load(QLocale(), "smart_cane", "_", ":/i18n")) {
a.installTranslator(&translator);
}
MainWindow w;
w.show();
return a.exec();
}
14. 样式表文件 (styles/default.qss)
QMainWindow {
background-color: #f5f5f5;
}
QGroupBox {
font-weight: bold;
border: 2px solid #cccccc;
border-radius: 5px;
margin-top: 1ex;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top center;
padding: 0 5px;
}
QPushButton {
background-color: #4CAF50;
border: none;
color: white;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #45a049;
}
QPushButton:pressed {
background-color: #3d8b40;
}
QPushButton#sosButton {
background-color: #f44336;
}
QPushButton#sosButton:hover {
background-color: #da190b;
}
QLabel {
font-size: 12px;
}
QLabel[status="warning"] {
color: orange;
font-weight: bold;
}
QLabel[status="error"] {
color: red;
font-weight: bold;
}
QLabel[status="success"] {
color: green;
font-weight: bold;
}
QSlider::groove:horizontal {
border: 1px solid #999999;
height: 8px;
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stransform: translateY(0 #B1B1B1, stop:1 #c4c4c4);
margin: 2px 0;
border-radius: 4px;
}
QSlider::handle:horizontal {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);
border: 1px solid #5c5c5c;
width: 18px;
margin: -2px 0;
border-radius: 9px;
}
QProgressBar {
border: 2px solid grey;
border-radius: 5px;
text-align: center;
}
QProgressBar::chunk {
background-color: #4CAF50;
width: 20px;
}
15. 地图HTML文件 (map.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>设备位置地图</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<style>
#map { height: 400px; }
.custom-marker { background: red; border-radius: 50%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
var map = L.map('map').setView([39.9042, 116.4074], 13);
var marker = null;
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
function updateMarker(lat, lon) {
if (marker) {
map.removeLayer(marker);
}
marker = L.marker([lat, lon]).addTo(map)
.bindPopup('导盲杖当前位置<br>纬度: ' + lat + '<br>经度: ' + lon)
.openPopup();
map.setView([lat, lon], 15);
}
</script>
</body>
</html>
3.7 部署说明
Windows版本部署
- 1. 使用Qt Creator打开项目文件2. 选择MSVC编译器3. 构建Release版本4. 使用windeployqt工具打包依赖
Android版本部署
- 1. 配置Android SDK和NDK2. 连接Android设备或启动模拟器3. 选择Android套件构建4. 生成APK文件并安装
四、STM32代码设计
当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
1. 硬件系统设计
1.1 系统架构
STM32F103C8T6主控
├── 超声波模块(HC-SR04)
├── GPS模块(ATGM336H)
├── 4G模块(Air780E)
├── BH1750光照传感器
├── OLED显示屏(SSD1306)
├── 有源蜂鸣器
├── 照明LED
├── 功能按键x3
└── 18650电池供电
2. 软件开发流程
2.1 开发环境搭建
- • Keil MDK 5.25• STM32CubeMX配置• 寄存器方式编程• 串口调试工具
2.2 功能模块开发顺序
- 1. 系统时钟和GPIO初始化2. 超声波测距模块3. 光照传感器4. OLED显示5. GPS数据解析6. 4G模块通信7. 华为云MQTT协议8. 按键功能9. 系统逻辑整合
完整STM32端代码
1. 主头文件 (smart_cane.h)
#ifndef __SMART_CANE_H
#define __SMART_CANE_H
#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// 系统时钟定义
#define SYSTEM_CLOCK 72000000
// 硬件引脚定义
// 超声波
#define TRIG_PIN GPIO_Pin_0
#define TRIG_PORT GPIOA
#define ECHO_PIN GPIO_Pin_1
#define ECHO_PORT GPIOA
// 蜂鸣器
#define BUZZER_PIN GPIO_Pin_13
#define BUZZER_PORT GPIOC
// LED照明灯
#define LED_PIN GPIO_Pin_14
#define LED_PORT GPIOC
// 按键
#define SOS_BUTTON_PIN GPIO_Pin_0
#define DIST_UP_PIN GPIO_Pin_1
#define DIST_DOWN_PIN GPIO_Pin_2
#define BUTTON_PORT GPIOB
// I2C引脚 (OLED和BH1750共用)
#define I2C_PORT GPIOB
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SDA_PIN GPIO_Pin_7
// 功能参数
#define DEFAULT_DISTANCE_THRESHOLD 50 // 默认距离阈值50cm
#define LIGHT_THRESHOLD 50 // 光照阈值50lux
#define GPS_UPDATE_INTERVAL 5000 // GPS更新间隔5秒
#define CLOUD_UPDATE_INTERVAL 10000 // 云平台更新间隔10秒
// 设备数据结构体
typedef struct {
float latitude; // 纬度
float longitude; // 经度
int distance; // 距离(cm)
int light_level; // 光照强度(lux)
int battery_level; // 电池电量(%)
int distance_threshold; // 距离阈值
bool sos_flag; // SOS标志
bool obstacle_detected; // 障碍物检测标志
} DeviceData_t;
// 函数声明
void System_Init(void);
void Delay_ms(uint32_t ms);
void GPIO_Configuration(void);
void USART_Configuration(void);
void I2C_Configuration(void);
void TIM_Configuration(void);
void NVIC_Configuration(void);
// 功能模块函数
void Ultrasonic_Init(void);
uint32_t Ultrasonic_GetDistance(void);
void BH1750_Init(void);
uint16_t BH1750_ReadLightLevel(void);
void OLED_Init(void);
void OLED_DisplayInfo(DeviceData_t data);
void GPS_Init(void);
bool GPS_ParseData(char *gps_data, DeviceData_t *device_data);
void LTE_Init(void);
void LTE_SendData(DeviceData_t data);
void Buzzer_Beep(uint16_t duration);
void LED_Control(bool state);
void Button_Scan(DeviceData_t *data);
// 华为云通信函数
void HuaweiCloud_Init(void);
void HuaweiCloud_PublishData(DeviceData_t data);
void HuaweiCloud_SubscribeCommands(void);
void MQTT_MessageHandler(char *topic, char *message);
// 系统状态机
typedef enum {
STATE_NORMAL,
STATE_OBSTACLE,
STATE_SOS,
STATE_LOW_LIGHT
} SystemState_t;
extern DeviceData_t g_device_data;
extern SystemState_t g_system_state;
#endif
2. 主程序文件 (main.c)
#include "smart_cane.h"
#include "i2c.h"
#include "ssd1306.h"
#include "bh1750.h"
// 全局变量
DeviceData_t g_device_data = {0};
SystemState_t g_system_state = STATE_NORMAL;
volatile uint32_t g_system_tick = 0;
// 系统时钟配置
void SystemClock_Config(void)
{
// 启用HSE
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
// 配置FLASH
FLASH->ACR |= FLASH_ACR_LATENCY_2 | FLASH_ACR_PRFTBE;
// 配置PLL: HSE * 9 = 72MHz
RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9;
// 配置AHB、APB1、APB2分频
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB = 72MHz
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 = 36MHz
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // APB2 = 72MHz
// 启用PLL
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
// 选择PLL作为系统时钟
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
// SysTick中断服务函数
void SysTick_Handler(void)
{
g_system_tick++;
}
// 延时函数
void Delay_ms(uint32_t ms)
{
uint32_t start_tick = g_system_tick;
while((g_system_tick - start_tick) < ms);
}
// 系统初始化
void System_Init(void)
{
// 配置系统时钟
SystemClock_Config();
// 配置SysTick定时器 (1ms中断)
SysTick_Config(SYSTEM_CLOCK / 1000);
// 初始化各模块
GPIO_Configuration();
USART_Configuration();
I2C_Configuration();
TIM_Configuration();
NVIC_Configuration();
// 初始化外设模块
Ultrasonic_Init();
BH1750_Init();
OLED_Init();
GPS_Init();
LTE_Init();
HuaweiCloud_Init();
// 初始化设备数据
g_device_data.distance_threshold = DEFAULT_DISTANCE_THRESHOLD;
g_device_data.battery_level = 100; // 假设初始电量100%
}
// 主函数
int main(void)
{
// 系统初始化
System_Init();
// 开机提示
Buzzer_Beep(200);
OLED_DisplayWelcome();
uint32_t last_gps_update = 0;
uint32_t last_cloud_update = 0;
uint32_t last_display_update = 0;
while(1)
{
// 扫描按键
Button_Scan(&g_device_data);
// 读取传感器数据
g_device_data.distance = Ultrasonic_GetDistance();
g_device_data.light_level = BH1750_ReadLightLevel();
// 控制LED照明
if(g_device_data.light_level < LIGHT_THRESHOLD) {
LED_Control(true);
g_system_state = STATE_LOW_LIGHT;
} else {
LED_Control(false);
}
// 障碍物检测
if(g_device_data.distance < g_device_data.distance_threshold && g_device_data.distance > 0) {
g_device_data.obstacle_detected = true;
g_system_state = STATE_OBSTACLE;
Buzzer_Beep(100);
Delay_ms(100);
Buzzer_Beep(100);
} else {
g_device_data.obstacle_detected = false;
if(g_system_state == STATE_OBSTACLE) {
g_system_state = STATE_NORMAL;
}
}
// SOS处理
if(g_device_data.sos_flag) {
g_system_state = STATE_SOS;
Buzzer_Beep(500);
Delay_ms(500);
// 立即发送SOS数据到云平台
HuaweiCloud_PublishData(g_device_data);
g_device_data.sos_flag = false; // 单次触发
}
// 定期更新GPS数据 (5秒)
if(g_system_tick - last_gps_update >= GPS_UPDATE_INTERVAL) {
// GPS数据通过串口中断接收,这里只更新显示
last_gps_update = g_system_tick;
}
// 定期上传数据到云平台 (10秒)
if(g_system_tick - last_cloud_update >= CLOUD_UPDATE_INTERVAL) {
HuaweiCloud_PublishData(g_device_data);
last_cloud_update = g_system_tick;
}
// 更新显示 (1秒)
if(g_system_tick - last_display_update >= 1000) {
OLED_DisplayInfo(g_device_data);
last_display_update = g_system_tick;
}
Delay_ms(50); // 主循环延时
}
}
3. GPIO配置 (gpio.c)
#include "smart_cane.h"
void GPIO_Configuration(void)
{
// 启用GPIO时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN;
// 配置超声波TRIG引脚 (PA0 推挽输出)
GPIOA->CRL &= ~(0xF << (0 * 4));
GPIOA->CRL |= (0x3 << (0 * 4)); // 输出模式,最大速度50MHz
GPIOA->CRL |= (0x0 << (0 * 4 + 2)); // 推挽输出
// 配置超声波ECHO引脚 (PA1 浮空输入)
GPIOA->CRL &= ~(0xF << (1 * 4));
GPIOA->CRL |= (0x4 << (1 * 4)); // 输入模式
GPIOA->CRL |= (0x1 << (1 * 4 + 2)); // 浮空输入
// 配置蜂鸣器引脚 (PC13 推挽输出)
GPIOC->CRH &= ~(0xF << (5 * 4));
GPIOC->CRH |= (0x3 << (5 * 4)); // 输出模式
GPIOC->CRH |= (0x0 << (5 * 4 + 2)); // 推挽输出
// 配置LED引脚 (PC14 推挽输出)
GPIOC->CRH &= ~(0xF << (6 * 4));
GPIOC->CRH |= (0x3 << (6 * 4)); // 输出模式
GPIOC->CRH |= (0x0 << (6 * 4 + 2)); // 推挽输出
// 配置按键引脚 (PB0, PB1, PB2 浮空输入)
GPIOB->CRL &= ~(0xFFF << (0 * 4));
GPIOB->CRL |= (0x4 << (0 * 4)); // PB0输入
GPIOB->CRL |= (0x1 << (0 * 4 + 2)); // 浮空输入
GPIOB->CRL |= (0x4 << (1 * 4)); // PB1输入
GPIOB->CRL |= (0x1 << (1 * 4 + 2)); // 浮空输入
GPIOB->CRL |= (0x4 << (2 * 4)); // PB2输入
GPIOB->CRL |= (0x1 << (2 * 4 + 2)); // 浮空输入
// 配置I2C引脚 (PB6, PB7 开漏输出)
GPIOB->CRL &= ~(0xFF << (6 * 4));
GPIOB->CRL |= (0x6 << (6 * 4)); // PB6输出
GPIOB->CRL |= (0x1 << (6 * 4 + 2)); // 开漏输出
GPIOB->CRL |= (0x6 << (7 * 4)); // PB7输出
GPIOB->CRL |= (0x1 << (7 * 4 + 2)); // 开漏输出
// 初始状态
BUZZER_PORT->BSRR = BUZZER_PIN; // 蜂鸣器关闭
LED_PORT->BSRR = LED_PIN; // LED关闭
}
4. 超声波模块 (ultrasonic.c)
#include "smart_cane.h"
void Ultrasonic_Init(void)
{
// 已经在GPIO配置中初始化
}
uint32_t Ultrasonic_GetDistance(void)
{
uint32_t distance = 0;
uint32_t timeout = 0;
// 发送10us的TRIG脉冲
TRIG_PORT->BSRR = TRIG_PIN; // 置高
Delay_ms(1); // 10us延时
TRIG_PORT->BRR = TRIG_PIN; // 置低
// 等待ECHO变高
timeout = 10000; // 超时计数
while((ECHO_PORT->IDR & ECHO_PIN) == 0) {
if(timeout-- == 0) return 0;
}
// 测量高电平持续时间
uint32_t start_time = g_system_tick;
timeout = 30000; // 最大测量距离约5m
while((ECHO_PORT->IDR & ECHO_PIN) != 0) {
if(timeout-- == 0) return 0;
}
uint32_t end_time = g_system_tick;
uint32_t pulse_duration = end_time - start_time;
// 计算距离 (声音速度340m/s = 0.034cm/us)
// 距离 = (时间 * 0.034) / 2
distance = (pulse_duration * 34) / 2000;
// 限制最大距离为500cm
if(distance > 500) distance = 500;
return distance;
}
5. BH1750光照传感器 (bh1750.c)
#include "smart_cane.h"
#include "bh1750.h"
void BH1750_Init(void)
{
// BH1750初始化序列
I2C_Start();
I2C_SendByte(BH1750_ADDRESS << 1); // 写模式
I2C_WaitAck();
I2C_SendByte(BH1750_POWER_ON);
I2C_WaitAck();
I2C_SendByte(BH1750_CONTINUOUS_H_RES_MODE);
I2C_WaitAck();
I2C_Stop();
Delay_ms(10);
}
uint16_t BH1750_ReadLightLevel(void)
{
uint8_t data[2] = {0};
uint16_t light_level = 0;
I2C_Start();
I2C_SendByte((BH1750_ADDRESS << 1) | 0x01); // 读模式
I2C_WaitAck();
data[0] = I2C_ReadByte();
I2C_Ack();
data[1] = I2C_ReadByte();
I2C_NAck();
I2C_Stop();
light_level = (data[0] << 8) | data[1];
light_level = light_level / 1.2; // 转换为lux
return light_level;
}
6. OLED显示 (oled_display.c)
#include "smart_cane.h"
#include "ssd1306.h"
#include "font.h"
void OLED_DisplayWelcome(void)
{
SSD1306_Clear();
SSD1306_SetCursor(0, 0);
SSD1306_PrintString("Smart Cane");
SSD1306_SetCursor(0, 2);
SSD1306_PrintString("Initializing...");
SSD1306_Update();
}
void OLED_DisplayInfo(DeviceData_t data)
{
char buffer[20];
SSD1306_Clear();
// 显示距离信息
SSD1306_SetCursor(0, 0);
SSD1306_PrintString("Dist:");
sprintf(buffer, "%3dcm", data.distance);
SSD1306_PrintString(buffer);
// 显示阈值
SSD1306_SetCursor(70, 0);
SSD1306_PrintString("Th:");
sprintf(buffer, "%2d", data.distance_threshold);
SSD1306_PrintString(buffer);
// 显示光照
SSD1306_SetCursor(0, 2);
SSD1306_PrintString("Light:");
sprintf(buffer, "%4dlux", data.light_level);
SSD1306_PrintString(buffer);
// 显示GPS状态
SSD1306_SetCursor(0, 4);
SSD1306_PrintString("GPS:");
if(data.latitude != 0 && data.longitude != 0) {
SSD1306_PrintString("Valid");
} else {
SSD1306_PrintString("No Sig");
}
// 显示系统状态
SSD1306_SetCursor(0, 6);
switch(g_system_state) {
case STATE_NORMAL:
SSD1306_PrintString("Normal");
break;
case STATE_OBSTACLE:
SSD1306_PrintString("OBSTACLE!");
break;
case STATE_SOS:
SSD1306_PrintString("SOS ALERT!");
break;
case STATE_LOW_LIGHT:
SSD1306_PrintString("Low Light");
break;
}
SSD1306_Update();
}
7. GPS处理 (gps_parser.c)
#include "smart_cane.h"
#define GPS_BUFFER_SIZE 256
char g_gps_buffer[GPS_BUFFER_SIZE];
uint16_t g_gps_index = 0;
// GPS数据解析
bool GPS_ParseData(char *gps_data, DeviceData_t *device_data)
{
char *token;
char *rest = gps_data;
// 查找GPRMC语句
token = strstr(gps_data, "$GPRMC");
if(token == NULL) return false;
// 解析GPRMC数据
token = strtok_r(rest, ",", &rest);
if(token == NULL) return false;
// 跳过语句头
token = strtok_r(NULL, ",", &rest); // 时间
token = strtok_r(NULL, ",", &rest); // 状态 (A=有效, V=无效)
if(token == NULL || token[0] != 'A') return false;
// 纬度
token = strtok_r(NULL, ",", &rest);
if(token != NULL && strlen(token) > 0) {
float lat_deg = (token[0]-'0')*10 + (token[1]-'0');
float lat_min = atof(token+2);
device_data->latitude = lat_deg + lat_min/60.0;
}
token = strtok_r(NULL, ",", &rest); // 纬度方向
if(token != NULL && token[0] == 'S') {
device_data->latitude = -device_data->latitude;
}
// 经度
token = strtok_r(NULL, ",", &rest);
if(token != NULL && strlen(token) > 0) {
float lon_deg = (token[0]-'0')*100 + (token[1]-'0')*10 + (token[2]-'0');
float lon_min = atof(token+3);
device_data->longitude = lon_deg + lon_min/60.0;
}
token = strtok_r(NULL, ",", &rest); // 经度方向
if(token != NULL && token[0] == 'W') {
device_data->longitude = -device_data->longitude;
}
return true;
}
// USART2中断服务函数 (GPS数据接收)
void USART2_IRQHandler(void)
{
if(USART2->SR & USART_SR_RXNE) {
char data = USART2->DR;
if(data == '$') {
g_gps_index = 0;
}
if(g_gps_index < GPS_BUFFER_SIZE - 1) {
g_gps_buffer[g_gps_index++] = data;
}
if(data == 'n') {
g_gps_buffer[g_gps_index] = '';
GPS_ParseData(g_gps_buffer, &g_device_data);
g_gps_index = 0;
}
}
}
8. 4G模块通信 (lte_module.c)
#include "smart_cane.h"
#define LTE_BUFFER_SIZE 512
char g_lte_buffer[LTE_BUFFER_SIZE];
uint16_t g_lte_index = 0;
void LTE_Init(void)
{
// 发送AT指令初始化4G模块
LTE_SendCommand("ATrn");
Delay_ms(1000);
LTE_SendCommand("AT+CPIN?rn");
Delay_ms(1000);
LTE_SendCommand("AT+CSQrn");
Delay_ms(1000);
LTE_SendCommand("AT+CGATT=1rn");
Delay_ms(2000);
// 连接到华为云
HuaweiCloud_Init();
}
void LTE_SendCommand(const char *cmd)
{
// 通过USART1发送命令到4G模块
while(*cmd) {
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = *cmd++;
}
}
void LTE_SendData(DeviceData_t data)
{
char json_buffer[256];
// 构建JSON数据
sprintf(json_buffer,
"{"lat":%.6f,"lon":%.6f,"dist":%d,"light":%d,"batt":%d,"sos":%s}",
data.latitude,
data.longitude,
data.distance,
data.light_level,
data.battery_level,
data.sos_flag ? "true" : "false");
// 通过MQTT发布数据
HuaweiCloud_PublishData(data);
}
// USART1中断服务函数 (4G模块数据接收)
void USART1_IRQHandler(void)
{
if(USART1->SR & USART_SR_RXNE) {
char data = USART1->DR;
if(g_lte_index < LTE_BUFFER_SIZE - 1) {
g_lte_buffer[g_lte_index++] = data;
}
// 处理MQTT消息
if(strstr(g_lte_buffer, "+MQTTSUBRECV:") != NULL) {
// 解析MQTT订阅消息
MQTT_MessageHandler("command", g_lte_buffer);
g_lte_index = 0;
}
// 缓冲区清理
if(g_lte_index >= LTE_BUFFER_SIZE - 1) {
g_lte_index = 0;
}
}
}
9. 华为云通信 (huawei_cloud.c)
#include "smart_cane.h"
void HuaweiCloud_Init(void)
{
// MQTT连接配置
char mqtt_cmd[128];
// 设置MQTT参数
sprintf(mqtt_cmd, "AT+MQTTCFG="%s",%d,"%s","%s",0,0,""rn",
"your-iot-endpoint.iot-mqtts.cn-north-4.myhuaweicloud.com",
1883,
"your-device-id",
"your-device-secret");
LTE_SendCommand(mqtt_cmd);
Delay_ms(2000);
// 连接MQTT服务器
LTE_SendCommand("AT+MQTTCONN=1,30,1,0,0,"",""rn");
Delay_ms(3000);
// 订阅命令主题
HuaweiCloud_SubscribeCommands();
}
void HuaweiCloud_PublishData(DeviceData_t data)
{
char topic[100];
char payload[256];
// 构建发布主题
sprintf(topic, "$oc/devices/%s/sys/properties/report", "your-device-id");
// 构建属性上报数据
sprintf(payload,
"{"services":[{"service_id":"BasicData","properties":{"
""latitude":%.6f,"
""longitude":%.6f,"
""distance":%d,"
""lightLevel":%d,"
""battery":%d,"
""sos":%s,"
""obstacle":%s"
"}}]}",
data.latitude,
data.longitude,
data.distance,
data.light_level,
data.battery_level,
data.sos_flag ? "true" : "false",
data.obstacle_detected ? "true" : "false");
// 发送MQTT发布命令
char mqtt_cmd[512];
sprintf(mqtt_cmd, "AT+MQTTPUB="%s","%s",0,0,0rn", topic, payload);
LTE_SendCommand(mqtt_cmd);
}
void HuaweiCloud_SubscribeCommands(void)
{
char topic[100];
sprintf(topic, "$oc/devices/%s/sys/commands/#", "your-device-id");
char mqtt_cmd[256];
sprintf(mqtt_cmd, "AT+MQTTSUB="%s",1rn", topic);
LTE_SendCommand(mqtt_cmd);
}
void MQTT_MessageHandler(char *topic, char *message)
{
// 解析云平台下发的命令
if(strstr(message, "set_distance_threshold") != NULL) {
// 解析并设置距离阈值
char *threshold_str = strstr(message, ""threshold":");
if(threshold_str != NULL) {
int threshold = atoi(threshold_str + 12);
if(threshold >= 10 && threshold <= 200) {
g_device_data.distance_threshold = threshold;
}
}
}
else if(strstr(message, "sos_alert") != NULL) {
// 远程SOS报警
g_device_data.sos_flag = true;
}
}
10. 按键处理 (button.c)
#include "smart_cane.h"
void Button_Scan(DeviceData_t *data)
{
static uint32_t last_button_time = 0;
// 防抖处理
if(g_system_tick - last_button_time < 200) {
return;
}
// SOS按键 (PB0)
if((BUTTON_PORT->IDR & SOS_BUTTON_PIN) == 0) {
data->sos_flag = true;
last_button_time = g_system_tick;
return;
}
// 距离增加按键 (PB1)
if((BUTTON_PORT->IDR & DIST_UP_PIN) == 0) {
if(data->distance_threshold < 200) {
data->distance_threshold += 10;
}
last_button_time = g_system_tick;
return;
}
// 距离减少按键 (PB2)
if((BUTTON_PORT->IDR & DIST_DOWN_PIN) == 0) {
if(data->distance_threshold > 10) {
data->distance_threshold -= 10;
}
last_button_time = g_system_tick;
return;
}
}
11. 外设控制 (peripheral.c)
#include "smart_cane.h"
void Buzzer_Beep(uint16_t duration)
{
BUZZER_PORT->BRR = BUZZER_PIN; // 蜂鸣器打开
Delay_ms(duration);
BUZZER_PORT->BSRR = BUZZER_PIN; // 蜂鸣器关闭
}
void LED_Control(bool state)
{
if(state) {
LED_PORT->BRR = LED_PIN; // LED打开
} else {
LED_PORT->BSRR = LED_PIN; // LED关闭
}
}
12. 串口配置 (usart.c)
#include "smart_cane.h"
void USART_Configuration(void)
{
// 启用USART时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
// USART1 (4G模块) - PA9(TX), PA10(RX)
GPIOA->CRH &= ~(0xFF << 4); // 清除PA9, PA10配置
GPIOA->CRH |= (0x0B << 4); // PA9 复用推挽输出
GPIOA->CRH |= (0x04 << 8); // PA10 浮空输入
// USART2 (GPS模块) - PA2(TX), PA3(RX)
GPIOA->CRL &= ~(0xFF << 8); // 清除PA2, PA3配置
GPIOA->CRL |= (0x0B << 8); // PA2 复用推挽输出
GPIOA->CRL |= (0x04 << 12); // PA3 浮空输入
// 配置USART1 (4G模块)
USART1->BRR = 72000000 / 115200; // 波特率115200
USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
// 配置USART2 (GPS模块)
USART2->BRR = 36000000 / 9600; // 波特率9600
USART2->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
// 配置NVIC
NVIC_EnableIRQ(USART1_IRQn);
NVIC_EnableIRQ(USART2_IRQn);
}
13. I2C配置 (i2c.c)
#include "smart_cane.h"
void I2C_Configuration(void)
{
// 启用I2C1时钟
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// 配置I2C1
I2C1->CR1 &= ~I2C_CR1_PE; // 禁用I2C
// 配置时钟: 72MHz / 100kHz = 720
I2C1->CR2 = 36; // 36MHz
I2C1->CCR = 180; // 100kHz
I2C1->TRISE = 37; // 1000ns rise time
I2C1->CR1 |= I2C_CR1_PE; // 启用I2C
}
// I2C起始信号
void I2C_Start(void)
{
I2C1->CR1 |= I2C_CR1_START;
while(!(I2C1->SR1 & I2C_SR1_SB));
}
// I2C停止信号
void I2C_Stop(void)
{
I2C1->CR1 |= I2C_CR1_STOP;
while(I2C1->CR1 & I2C_CR1_STOP);
}
// 发送字节
void I2C_SendByte(uint8_t data)
{
I2C1->DR = data;
while(!(I2C1->SR1 & I2C_SR1_TXE));
}
// 读取字节
uint8_t I2C_ReadByte(void)
{
while(!(I2C1->SR1 & I2C_SR1_RXNE));
return I2C1->DR;
}
// 等待ACK
void I2C_WaitAck(void)
{
while(!(I2C1->SR1 & I2C_SR1_AF));
I2C1->SR1 &= ~I2C_SR1_AF;
}
// 发送ACK
void I2C_Ack(void)
{
I2C1->CR1 |= I2C_CR1_ACK;
}
// 发送NACK
void I2C_NAck(void)
{
I2C1->CR1 &= ~I2C_CR1_ACK;
}
14. 定时器配置 (timer.c)
#include "smart_cane.h"
void TIM_Configuration(void)
{
// 启用TIM2时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// 配置TIM2为1ms定时器
TIM2->PSC = 7200 - 1; // 72MHz / 7200 = 10kHz
TIM2->ARR = 10 - 1; // 10kHz / 10 = 1kHz (1ms)
// 启用更新中断
TIM2->DIER |= TIM_DIER_UIE;
// 启动定时器
TIM2->CR1 |= TIM_CR1_CEN;
// 启用TIM2中断
NVIC_EnableIRQ(TIM2_IRQn);
}
void NVIC_Configuration(void)
{
// 设置中断优先级分组
NVIC_SetPriorityGrouping(3);
// 配置各中断优先级
NVIC_SetPriority(SysTick_IRQn, 0);
NVIC_SetPriority(USART1_IRQn, 1);
NVIC_SetPriority(USART2_IRQn, 1);
NVIC_SetPriority(TIM2_IRQn, 2);
}
编译和烧录说明
1. 工程配置
- • 开发工具: Keil MDK 5.25• 编译器: ARMCC V5• 优化等级: -O1• 目标芯片: STM32F103C8T6
2. 烧录设置
- • 接口: SWD• 速度: 4MHz• 复位方式: Hardware Reset
3. 调试配置
- • 使用串口调试助手监视GPS和4G模块数据• 通过OLED显示屏查看系统状态• 使用华为云物联网平台查看设备数据
使用说明
-
- 1.
上电启动
-
- : 设备自动初始化,蜂鸣器短响一声2.
距离检测
-
- : 超声波实时检测前方障碍物,距离小于阈值时报警3.
光照控制
-
- : 环境光暗时自动开启LED照明4.
按键功能
-
- :
-
- • SOS键: 长按发送求助信号• 距离+: 增加报警距离阈值• 距离-: 减少报警距离阈值
-
5. 云平台: 数据自动上传到华为云,可通过APP监控
1930