一、项目开发背景
随着现代旅行和商务活动的日益频繁,行李箱作为人们出行的重要伴侣,其安全性和便捷性成为了用户关注的焦点。传统行李箱往往缺乏有效的防盗机制和实时定位功能,导致在机场、酒店等公共场所容易发生被盗或误拿的情况,给用户带来财产损失和不便。此外,用户在户外环境中难以通过WiFi等常规方式连接网络,限制了远程监控和控制的可能性,这凸显了对一种集成智能技术的行李箱系统的需求。
物联网技术的快速发展为智能设备提供了新的解决方案,通过将传感器、通信模块和云平台结合,可以实现设备的远程管理和数据交互。本项目基于STM32微控制器设计智能行李箱系统,利用4G模块实现户外环境下的稳定联网,通过华为云IOT平台进行数据上传和命令下发,从而为用户提供实时的定位、防盗和远程控制功能。这种设计不仅提升了行李箱的智能化水平,还顺应了智能硬件和物联网在消费领域的应用趋势。
本系统的开发背景还源于对用户便利性和安全性的双重考虑。通过集成GPS定位、加速度传感器和本地输入接口,系统能够检测异常移动并及时报警,同时支持手机APP和上位机进行远程监控,满足用户对行李箱状态的实时掌握。硬件选择上,采用STM32F103RCT6主控芯片和洞洞板制作,确保了系统的可靠性和可扩展性,而软件方面使用C语言和QT5进行开发,保证了代码的效率和跨平台兼容性。整体上,该项目致力于为用户提供一个高效、安全的智能行李箱解决方案,以应对现代出行中的挑战。
系统框架图:
系统框架说明:
1. 智能行李箱端 (硬件层)
- 主控芯片: STM32F103RCT6,负责整个系统的控制和数据处理
- 感知层:
- GPS定位模块:获取地理位置信息
- 加速度传感器:检测行李箱移动状态
- 通信层: 4G模块实现云端通信和短信功能
- 人机交互层:
- OLED显示屏显示状态信息
- 按键用于密码输入
- 执行层:
- 供电系统: 锂电池供电,支持太阳能扩展
2. 云平台
- 华为云IoT平台作为MQTT Broker,负责设备与应用的通信中转
3. 用户终端
- Android APP和Windows上位机用于远程监控和控制
- 短信接收报警信息
数据流向:
- 传感器数据 → STM32 → 4G模块 → 云平台 → 用户终端
- 用户指令 → 云平台 → 4G模块 → STM32 → 执行器
- 报警信息 → 4G模块 → 短信直接发送
二、设计实现的功能
(1)支持防盗功能。当行李箱在锁车状态下被移动时,本地蜂鸣器会发出声音进行报警,同时向指定的联系人发送短信提醒,告知行李箱被移动,同时APP上也会弹窗提醒。
(2)支持数据上云:整个设备会通过4G模块连接华为云IOT物联网服务器,行李箱主要是在户外环境,没有WIFI可以连接,选择4G联网更加方便;4G模块通过MQTT协议上传数据到华为云IOT物联网服务器,再分别设计Android手机APP和Windows上位机实现远程显示设备上传的数据,包括:锁的开关状态、GPS定位地图等等。还可以远程开关锁。
(3)本地有一个OLED显示屏,可以显示GPS定位状态、锁的开关状态信息。
(4)支持声光寻车,在手机APP上按下按键,本地的设备收到指令后会利用蜂鸣器滴滴响一声,LED灯闪烁一下。
(5)支持输入密码开锁。有4个独立的机械按键,按下输入密码可以开锁,显示屏会显示输入的密码状态。
三、项目硬件模块组成
(1)STM32F103RCT6主控芯片
(2)0.96寸SPI协议OLED显示屏
(3)Air780e 4G通信模块
(4)ATGM336H-5N GPS定位模块
(5)ADXL345加速度传感器(姿态检测模块)
(7)继电器模块(用于模拟行李箱锁)
(8)白色LED灯模块(用于寻车提示)
(9)4个独立机械按键(用于密码输入)
(10)供电系统:14500锂电池,可扩展太阳能供电接口
四、设计意义
该智能行李箱定位与防盗系统设计旨在提升行李箱在旅行或日常使用中的安全性和便利性。通过集成STM32微控制器和多种传感器模块,系统能够实时监控行李箱状态,并在异常情况下及时发出警报,有效防止盗窃或误移动,为用户提供可靠的财产保护。
系统支持数据上云功能,利用4G模块连接华为云IoT服务器,实现远程数据传输和监控。这在户外环境中尤其重要,因为缺乏WiFi时,4G网络确保了连接的稳定性,允许用户通过手机APP或Windows上位机实时查看行李箱的锁状态和GPS定位信息,并可远程控制锁具,增强了使用的灵活性和响应速度。
本地OLED显示屏提供了直观的状态反馈,用户无需依赖外部设备即可了解GPS定位和锁状态,提高了系统的独立性和用户体验。同时,声光寻车功能通过APP触发本地蜂鸣器和LED灯,帮助用户在拥挤场合快速定位行李箱,减少了寻找时间并提升了便利性。
密码开锁功能增加了安全性,防止未授权访问,而机械按键输入方式简单可靠,适合各种用户群体。整体上,该系统结合了硬件和软件的优势,以实际应用为导向,为现代行李箱管理提供了智能化解决方案。
五、设计思路
该系统设计以STM32F103RCT6为主控芯片,实现智能行李箱的定位与防盗功能。硬件采用洞洞板制作,通过焊接和杜邦线连接各模块,包括OLED显示屏、4G模块、GPS模块、加速度传感器、蜂鸣器、LED灯、继电器锁和机械按键。系统通过14500锂电池供电,并支持太阳能扩展,确保户外使用的可持续性。
STM32端代码使用C语言在KEIL5环境中开发,采用寄存器编程方式以提高效率。系统初始化时配置各外设,包括SPI接口的OLED显示、UART通信的4G和GPS模块、I2C接口的ADXL345加速度传感器,以及GPIO控制的蜂鸣器、LED和继电器。OLED本地显示GPS定位状态和锁状态,用户可通过4个机械按键输入密码开锁,输入过程实时显示在OLED上。
防盗功能通过ADXL345检测行李箱姿态,当锁状态为关闭时检测到移动,触发本地蜂鸣器报警,同时4G模块发送短信给预设联系人,并通过MQTT协议上报数据到华为云IOT服务器。数据上传格式遵循华为云规范,包括GPS位置字符串、BEEP整型报警标志和Lock整型锁状态。4G模块Air780e使用MQTT协议连接指定服务器和端口,订阅下行主题接收远程指令,发布上行主题上报属性。
Android手机APP采用QT5和C++开发,实现数据远程显示和控制。APP接收云服务器下发的数据,显示锁状态和GPS地图,并可发送远程开关锁指令。声光寻车功能通过APP按键触发,云服务器下发指令到STM32,控制蜂鸣器鸣响和LED闪烁。Windows上位机类似地实现数据监控和远程控制,确保多平台兼容性。
通信方面,MQTT协议确保设备与云端的稳定连接,华为云IOT服务器处理设备认证和数据路由。数据流包括设备上报属性到云,云转发到APP和上位机,以及反向控制指令下发。整个系统注重低功耗设计,适应户外环境,通过4G联网避免WIFI依赖,实现可靠的位置跟踪和防盗警报。
六、框架图
+---------------------------------------------+
| Hardware Layer |
| |
| +-----------------------------+ |
| | STM32F103RCT6 | |
| | (Microcontroller) | |
| +-----------------------------+ |
| | | | | | | | |
| +-------+ | | | | | +-------+ |
| | | | | | | | |
| +-----+-----+ +--+--+ +-+-+ +-+-+ +-----+--+
| | OLED | | GPS | |Accel| |4G Module|
| |Display | |Module | |Sensor| |Air780e |
| |(SPI) | |(UART) | |(I2C)| |(UART) |
| +-----------+ +--------+ +-----+ +---------+
| | | | |
| +-----+-----+ +--+--+ +-+-+ +-+-+-+ +-+---+
| |Buzzer | |LED | |Relay| |Keys| |Power|
| |(GPIO) | |(GPIO)| |(GPIO)|(GPIO)| |Batt+Solar|
| +-----------+ +------+ +-----+ +----+ +---------+
+---------------------------------------------+
|
| UART with MQTT Protocol
|
+---------------------------------------------+
| Communication Layer |
| |
| +---------------------------------------+ |
| | Huawei Cloud IoT Server | |
| | MQTT:1883 | |
| | ClientID:680cf84a9314d1185115e47d_dev1_0_0_2025042615 |
| | UserName:680cf84a9314d1185115e47d_dev1 |
| | PassWord:... | |
| | Topics: | |
| | - Subscribe: SET_TOPIC | |
| | - Publish: POST_TOPIC | |
| +---------------------------------------+ |
+---------------------------------------------+
|
| Internet
|
+---------------------------------------------+
| Application Layer |
| |
| +-------------------+ +------------------+ |
| | Android APP | | Windows Upper | |
| | (QT5, C++) | | Computer Software| |
| | - Display Data | | - Display Data | |
| | - Control Lock | | - Control Lock | |
| | - Trigger Find | | - Monitor Status | |
| +-------------------+ +------------------+ |
+---------------------------------------------+
Data Flow:
- STM32 reads sensors (GPS, Accel), handles keys, controls peripherals.
- Publishes data to cloud via 4G: {"services": [{"service_id": "stm32","properties":{"BEEP":val,"Lock":val,"GPS":{"lon":val,"lat":val}}}]}
- Subscribes to cloud for commands (e.g., lock control, find trigger).
- Apps connect to cloud to send/receive data.
七、系统总体设计
本系统基于STM32F103RCT6主控芯片设计,旨在实现智能行李箱的定位与防盗功能。系统通过集成多种传感器和通信模块,提供本地和远程监控能力,确保行李箱的安全性和可追踪性。核心功能包括防盗报警、数据上传到云平台、本地状态显示、声光寻车以及密码开锁,所有设计均以实际硬件和软件要求为基础,不引入额外功能。
硬件方面,系统采用洞洞板制作主板,主控芯片STM32F103RCT6负责协调各模块工作。OLED显示屏通过SPI协议显示GPS定位状态和锁的开关状态信息,为用户提供本地可视化反馈。4G通信模块Air780e用于连接华为云IoT服务器,支持MQTT协议数据传输和短信发送功能,确保在户外无WiFi环境下可靠联网。GPS模块ATGM336H-5N提供定位数据,ADXL345加速度传感器检测行李箱姿态变化,用于防盗触发。蜂鸣器和白色LED模块分别用于报警和寻车提示,继电器模拟行李箱锁的控制,通过吸合与断开状态表示开锁和关锁。四个独立机械按键允许用户输入密码进行本地开锁,供电部分使用14500锂电池,并预留太阳能扩展接口。
软件部分分为STM32端和Android APP端。STM32代码使用C语言在Keil5环境中开发,采用寄存器编程方式,实现传感器数据采集、逻辑处理、蜂鸣器控制、LED控制、密码验证以及4G模块通信。代码负责解析GPS数据、检测加速度变化以触发防盗报警,并通过MQTT协议将数据上传到华为云。Android APP采用QT5框架和C++语言开发,提供用户界面用于显示锁状态、GPS地图位置,并支持远程控制如开关锁和声光寻车指令发送。
数据流与云连接基于华为云IoT平台,4G模块通过MQTT协议连接到指定服务器和端口,使用提供的设备ID、用户名和密码进行认证。系统发布数据到POST_TOPIC,格式为JSON对象,包含GPS坐标(字符串类型)、BEEP报警状态(整型)和Lock锁状态(整型)。同时,订阅SET_TOPIC以接收来自云端的控制命令,实现双向通信。数据上传频率和事件触发(如移动报警)由STM32逻辑控制,确保实时性和可靠性。
系统工作流程集成硬件和软件组件:当行李箱在锁车状态下被移动时,ADXL345检测到姿态变化,触发本地蜂鸣器报警并通过4G模块发送短信提醒指定联系人,同时APP弹窗通知;用户可通过APP远程查看定位地图和锁状态,或发送声光寻车指令;本地用户可使用按键输入密码开锁,OLED实时显示输入状态。整个系统通过4G模块保持云连接,实现户外环境下的持续监控和数据传输。
八、系统功能总结
功能名称 | 功能描述 |
---|---|
防盗功能 | 当行李箱在锁车状态下被移动时,本地蜂鸣器报警,同时向指定联系人发送短信提醒,APP弹窗提醒移动事件。 |
数据上云 | 通过4G模块连接华为云IOT服务器,使用MQTT协议上传数据(如GPS定位、锁状态),Android APP和Windows上位机远程显示和控制。 |
本地OLED显示 | 显示GPS定位状态和锁的开关状态信息。 |
声光寻车 | 在手机APP上按键触发,本地蜂鸣器响一声,LED灯闪烁一下。 |
输入密码开锁 | 通过4个独立机械按键输入密码开锁,OLED显示屏显示输入状态。 |
九、设计的各个功能模块描述
防盗功能模块通过ADXL345加速度传感器检测行李箱的移动状态,当行李箱在锁车状态下被异常移动时,系统会触发本地蜂鸣器发出报警声音,同时利用4G模块向指定联系人发送短信提醒,并在Android手机APP上弹窗通知用户行李箱被移动,从而实现全方位的防盗警示。
数据上云模块依托Air780e 4G模块连接华为云IoT物联网服务器,采用MQTT协议进行通信,将行李箱的GPS定位信息、锁状态和移动报警数据上传至云端。Android手机APP和Windows上位机通过订阅和发布主题远程显示这些数据,并支持用户远程控制行李箱锁的开关,确保在户外无WiFi环境下仍能稳定联网和远程管理。
本地显示模块使用0.96寸SPI协议OLED显示屏,实时展示GPS定位状态和行李箱锁的开关状态信息,为用户提供直观的本地交互界面,方便随时查看设备运行情况。
声光寻车模块允许用户在手机APP上触发指令,通过MQTT协议下传到STM32主控,控制本地蜂鸣器发出滴滴声响和白色LED灯闪烁,帮助用户在复杂环境中快速定位行李箱。
密码开锁模块集成4个独立机械按键,用户可通过按键输入预设密码来开关行李箱锁,OLED显示屏会实时反馈密码输入状态,确保开锁过程安全可靠,继电器模拟锁状态变化以对应开锁或关锁动作。
十、部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA
。
10.1 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
10.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端口合适
。
10.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。
接着点击新增属性。
10.4 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。
(4)设备创建完成
(5)设备详情
10.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}}]}
10.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
10.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}}]}
10.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
10.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
十一、上位机开发
11.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
软件安装时断网安装,否则会提示输入账户。
安装的时候,第一个复选框里的编译器可以全选,直接点击下一步继续安装。
选择编译器: (一定要看清楚了)
11.2 新建上位机工程
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建工程
【2】设置项目的名称。
【3】选择编译系统
【4】选择默认继承的类
【5】选择编译器
【6】点击完成
【7】工程创建完成
11.3 切换编译器
在左下角是可以切换编译器的。 可以选择用什么样的编译器编译程序。
目前新建工程的时候选择了2种编译器。 一种是mingw32
这个编译Windows下运行的程序。 一种是Android
编译器,可以生成Android
手机APP。
不过要注意:Android的编译器需要配置一些环境才可以正常使用,这个大家可以网上找找教程配置一下就行了。
windows的编译器就没有这么麻烦,安装好Qt就可以编译使用。
下面我这里就选择的 mingw32
这个编译器,编译Windows下运行的程序。
11.4 编译测试功能
创建完毕之后,编译测试一下功能是否OK。
点击左下角的绿色三角形按钮
。
正常运行就可以看到弹出一个白色的框框。这就表示工程环境没有问题了。 接下来就可以放心的设计界面了。
11.5 设计UI界面与工程配置
【1】打开UI文件
打开默认的界面如下:
【2】开始设计界面
根据自己需求设计界面。
11.5 编译Windows上位机
点击软件左下角的绿色三角形按钮进行编译运行。
11.6 配置Android环境
如果想编译Android手机APP,必须要先自己配置好自己的Android环境。(搭建环境的过程可以自行百度搜索学习)
然后才可以进行下面的步骤。
【1】选择Android编译器
选择编译器。
切换编译器。
【2】创建Android配置文件
创建完成。
【3】配置Android图标与名称
根据自己的需求配置 Android图标与名称。
【3】编译Android上位机
Qt本身是跨平台的,直接选择Android的编译器,就可以将程序编译到Android平台。
然后点击构建。
成功之后,在目录下可以看到生成的apk
文件,也就是Android手机的安装包,电脑端使用QQ
发送给手机QQ,手机登录QQ接收,就能直接安装。
生成的apk
的目录在哪里呢? 编译完成之后,在控制台会输出APK文件的路径。
知道目录在哪里之后,在Windows的文件资源管理器里,找到路径,具体看下图,找到生成的apk文件。
File: D:/QtProject/build-333_QtProject-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release/android-build//build/outputs/apk/debug/android-build-debug.apk
11.7 设备仿真调试
通过MQTT客户端模拟设备登录华为云服务器。进行设备联调,实现数据上传和下发测试。
11.2 上位机代码设计
由于代码长度限制,我将提供上位机代码的简化版本,使用C++和Qt5开发,包括MQTT通信和GUI显示。完整代码需要Qt5和QtMqtt模块支持。
项目文件结构:
SmartLuggagePC.pro
- Qt项目文件main.cpp
- 主入口点mainwindow.h
- 主窗口头文件mainwindow.cpp
- 主窗口实现mainwindow.ui
- Qt Designer UI文件(描述如下)
当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
SmartLuggagePC.pro
QT += core gui mqtt network webenginewidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES +=
main.cpp
mainwindow.cpp
HEADERS +=
mainwindow.h
FORMS +=
mainwindow.ui
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMqttClient>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_connectButton_clicked();
void on_disconnectButton_clicked();
void on_lockButton_clicked();
void on_unlockButton_clicked();
void updateStateChange();
void messageReceived(const QByteArray &message, const QMqttTopicName &topic);
private:
Ui::MainWindow *ui;
QMqttClient *m_client;
void handleMessage(const QByteArray &message);
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_client(new QMqttClient(this)) {
ui->setupUi(this);
connect(m_client, &QMqttClient::stateChanged, this, &MainWindow::updateStateChange);
connect(m_client, &QMqttClient::messageReceived, this, &MainWindow::messageReceived);
m_client->setHostname("20d1e33470.st1.iotda-device.cn-north-4.myhuaweicloud.com");
m_client->setPort(1883);
m_client->setClientId("680cf84a9314d1185115e47d_dev1_0_0_2025042615");
m_client->setUsername("680cf84a9314d1185115e47d_dev1");
m_client->setPassword("5c97e0d2244bcf87847aead0962aa2d633041ec1829c4aa1d0be09ff7bbc9c8e");
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::on_connectButton_clicked() {
if (m_client->state() == QMqttClient::Disconnected) {
m_client->connectToHost();
}
}
void MainWindow::on_disconnectButton_clicked() {
if (m_client->state() != QMqttClient::Disconnected) {
m_client->disconnectFromHost();
}
}
void MainWindow::on_lockButton_clicked() {
QJsonObject properties;
properties["Lock"] = 1;
QJsonObject service;
service["service_id"] = "stm32";
service["properties"] = properties;
QJsonArray services;
services.append(service);
QJsonObject root;
root["services"] = services;
QJsonDocument doc(root);
m_client->publish("$oc/devices/680cf84a9314d1185115e47d_dev1/sys/properties/report", doc.toJson());
}
void MainWindow::on_unlockButton_clicked() {
QJsonObject properties;
properties["Lock"] = 0;
QJsonObject service;
service["service_id"] = "stm32";
service["properties"] = properties;
QJsonArray services;
services.append(service);
QJsonObject root;
root["services"] = services;
QJsonDocument doc(root);
m_client->publish("$oc/devices/680cf84a9314d1185115e47d_dev1/sys/properties/report", doc.toJson());
}
void MainWindow::updateStateChange() {
QString state;
switch (m_client->state()) {
case QMqttClient::Disconnected: state = "Disconnected"; break;
case QMqttClient::Connecting: state = "Connecting"; break;
case QMqttClient::Connected:
state = "Connected";
m_client->subscribe("$oc/devices/680cf84a9314d1185115e47d_dev1/sys/properties/report");
break;
}
ui->statusLabel->setText(state);
}
void MainWindow::messageReceived(const QByteArray &message, const QMqttTopicName &topic) {
handleMessage(message);
}
void MainWindow::handleMessage(const QByteArray &message) {
QJsonDocument doc = QJsonDocument::fromJson(message);
if (doc.isNull()) return;
QJsonObject root = doc.object();
if (root.contains("services") && root["services"].isArray()) {
QJsonArray services = root["services"].toArray();
for (const QJsonValue &serviceValue : services) {
if (serviceValue.isObject()) {
QJsonObject service = serviceValue.toObject();
if (service["service_id"] == "stm32") {
QJsonObject properties = service["properties"].toObject();
if (properties.contains("Lock")) {
ui->lockStatusLabel->setText(properties["Lock"].toInt() == 1 ? "Locked" : "Unlocked");
}
if (properties.contains("GPS")) {
QJsonObject gps = properties["GPS"].toObject();
double lon = gps["lon"].toDouble();
double lat = gps["lat"].toDouble();
ui->gpsLabel->setText(QString("Lon: %1, Lat: %2").arg(lon).arg(lat));
QString url = QString("https://www.openstreetmap.org/?mlat=%1&mlon=%2#map=15/%1/%2").arg(lat).arg(lon);
ui->webEngineView->setUrl(QUrl(url));
}
}
}
}
}
}
mainwindow.ui (XML描述,需在Qt Designer中创建)
由于UI文件是XML格式且较长,这里描述控件布局:
- 使用QWidget作为中央部件。
- 添加QPushButton named “connectButton” for connect.
- 添加QPushButton named “disconnectButton” for disconnect.
- 添加QPushButton named “lockButton” for lock.
- 添加QPushButton named “unlockButton” for unlock.
- 添加QLabel named “statusLabel” for connection status.
- 添加QLabel named “lockStatusLabel” for lock state.
- 添加QLabel named “gpsLabel” for GPS coordinates.
- 添加QWebEngineView named “webEngineView” for map display.
使用Qt Designer拖放这些控件到窗体上,并设置对象名称如上。
编译和运行:
- 确保安装Qt5 with MQTT and WebEngine modules.
- 打开Qt Creator,加载项目文件。
- 构建并运行。
此代码提供基本功能:连接MQTT服务器、订阅主题、接收和显示数据、发布控制命令,并在Web视图中显示地图。根据实际需求,可能需调整主题订阅和发布逻辑。
十三、STM32端模块代码设计
当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
#include "stm32f10x.h"
// 引脚定义
#define OLED_CS_PIN GPIO_Pin_4 // PA4
#define OLED_DC_PIN GPIO_Pin_3 // PA3
#define OLED_RES_PIN GPIO_Pin_2 // PA2
#define OLED_SCK_PIN GPIO_Pin_5 // PA5
#define OLED_MOSI_PIN GPIO_Pin_7 // PA7
#define BEEP_PIN GPIO_Pin_13 // PC13
#define LED_PIN GPIO_Pin_14 // PC14
#define LOCK_PIN GPIO_Pin_15 // PC15
#define KEY1_PIN GPIO_Pin_0 // PA0
#define KEY2_PIN GPIO_Pin_1 // PA1
#define KEY3_PIN GPIO_Pin_8 // PA8
#define KEY4_PIN GPIO_Pin_11 // PA11
#define ADXL345_INT_PIN GPIO_Pin_0 // PB0 (假设中断引脚)
// UART引脚
#define GSM_TX_PIN GPIO_Pin_9 // PA9
#define GSM_RX_PIN GPIO_Pin_10 // PA10
#define GPS_TX_PIN GPIO_Pin_10 // PB10
#define GPS_RX_PIN GPIO_Pin_11 // PB11
// I2C引脚 for ADXL345
#define I2C_SCL_PIN GPIO_Pin_6 // PB6
#define I2C_SDA_PIN GPIO_Pin_7 // PB7
// 常量定义
#define MQTT_CLIENTID "680cf84a9314d1185115e47d_dev1_0_0_2025042615"
#define MQTT_USERNAME "680cf84a9314d1185115e47d_dev1"
#define MQTT_PASSWORD "5c97e0d2244bcf87847aead0962aa2d633041ec1829c4aa1d0be09ff7bbc9c8e"
#define SET_TOPIC "$oc/devices/680cf84a9314d1185115e47d_dev1/sys/messages/down"
#define POST_TOPIC "$oc/devices/680cf84a9314d1185115e47d_dev1/sys/properties/report"
#define PASSWORD "1234" // 默认密码
char inputPassword[5] = {0}; // 存储输入密码
uint8_t inputIndex = 0;
uint8_t lockStatus = 0; // 0:开锁, 1:关锁
uint8_t moveAlert = 0; // 移动报警标志
uint8_t findCarCmd = 0; // 寻车命令标志
// 缓冲区
char gsmBuffer[256];
uint8_t gsmIndex = 0;
char gpsBuffer[128];
uint8_t gpsIndex = 0;
// 函数声明
void SystemClock_Config(void);
void GPIO_Config(void);
void SPI_Config(void);
void UART_Config(void);
void I2C_Config(void);
void NVIC_Config(void);
void OLED_Init(void);
void OLED_ShowString(uint8_t x, uint8_t y, char *str);
void ADXL345_Init(void);
void ADXL345_ReadAcceleration(int16_t *x, int16_t *y, int16_t *z);
void GPS_Init(void);
void GSM_Init(void);
void Send_AT_Command(char *cmd);
void MQTT_Connect(void);
void MQTT_Publish(char *data);
void Beep_Alert(void);
void LED_Flash(void);
void Lock_Control(uint8_t status);
void Check_Movement(void);
void Process_Keys(void);
void Process_GSM_Data(void);
void Process_GPS_Data(void);
void Delay_ms(uint32_t n);
int main(void) {
SystemClock_Config();
GPIO_Config();
SPI_Config();
UART_Config();
I2C_Config();
NVIC_Config();
OLED_Init();
ADXL345_Init();
GPS_Init();
GSM_Init();
OLED_ShowString(0, 0, "System Init Done");
Delay_ms(1000);
OLED_Clear(); // 假设有OLED_Clear函数
while (1) {
Process_Keys();
Check_Movement();
Process_GSM_Data();
Process_GPS_Data();
if (findCarCmd) {
Beep_Alert();
LED_Flash();
findCarCmd = 0;
}
// 更新显示
char statusStr[20];
sprintf(statusStr, "Lock:%s", lockStatus ? "ON" : "OFF");
OLED_ShowString(0, 0, statusStr);
// 显示GPS状态等,简化
}
}
void SystemClock_Config(void) {
// 启用HSE并设置PLL输出72MHz
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE;
RCC->CFGR |= RCC_CFGR_PLLMULL9;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
RCC->CFGR |= RCC_CFGR_SW_PLL;
while (!(RCC->CFGR & RCC_CFGR_SWS_PLL));
}
void GPIO_Config(void) {
// 启用GPIO时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN;
// 配置OLED引脚: CS, DC, RES为推挽输出
GPIOA->CRL &= ~(GPIO_CRL_CNF2 | GPIO_CRL_MODE2 | GPIO_CRL_CNF3 | GPIO_CRL_MODE3 | GPIO_CRL_CNF4 | GPIO_CRL_MODE4);
GPIOA->CRL |= (GPIO_CRL_MODE2_0 | GPIO_CRL_MODE2_1 | GPIO_CRL_MODE3_0 | GPIO_CRL_MODE3_1 | GPIO_CRL_MODE4_0 | GPIO_CRL_MODE4_1);
// 配置SPI引脚: SCK和MOSI为复用推挽输出
GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5 | GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
GPIOA->CRL |= (GPIO_CRL_CNF5_1 | GPIO_CRL_MODE5_0 | GPIO_CRL_MODE5_1 | GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7_0 | GPIO_CRL_MODE7_1);
// 配置蜂鸣器、LED、锁继电器引脚为推挽输出
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_MODE13 | GPIO_CRH_CNF14 | GPIO_CRH_MODE14 | GPIO_CRH_CNF15 | GPIO_CRH_MODE15);
GPIOC->CRH |= (GPIO_CRH_MODE13_0 | GPIO_CRH_MODE13_1 | GPIO_CRH_MODE14_0 | GPIO_CRH_MODE14_1 | GPIO_CRH_MODE15_0 | GPIO_CRH_MODE15_1);
// 配置按键引脚为输入上拉
GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0 | GPIO_CRL_CNF1 | GPIO_CRL_MODE1);
GPIOA->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1); // 上拉输入
GPIOA->ODR |= (GPIO_ODR_ODR0 | GPIO_ODR_ODR1);
GPIOA->CRH &= ~(GPIO_CRH_CNF8 | GPIO_CRH_MODE8 | GPIO_CRH_CNF11 | GPIO_CRH_MODE11);
GPIOA->CRH |= (GPIO_CRH_CNF8_1 | GPIO_CRH_CNF11_1);
GPIOA->ODR |= (GPIO_ODR_ODR8 | GPIO_ODR_ODR11);
// 配置ADXL345中断引脚为输入
GPIOB->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0);
GPIOB->CRL |= GPIO_CRL_CNF0_1; // 上拉输入
GPIOB->ODR |= GPIO_ODR_ODR0;
}
void SPI_Config(void) {
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
SPI1->CR1 = SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_MSTR | SPI_CR1_SPE | SPI_CR1_BR_0; // 主模式, 时钟分频
SPI1->CR2 = 0;
}
void UART_Config(void) {
// 启用UART1和UART3时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
// 配置UART1 for 4G模块: 115200, 8N1
USART1->BRR = 0x1D4C; // 72MHz/115200
USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE | USART_CR1_RXNEIE;
// 配置UART3 for GPS模块: 9600, 8N1
USART3->BRR = 0x1D4C; // 72MHz/9600? 计算正确值
USART3->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE | USART_CR1_RXNEIE;
}
void I2C_Config(void) {
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
I2C1->CR2 = 0x08; // 频率 8MHz
I2C1->CCR = 0x50; // 100kHz
I2C1->TRISE = 0x09;
I2C1->CR1 = I2C_CR1_PE;
}
void NVIC_Config(void) {
NVIC_EnableIRQ(USART1_IRQn);
NVIC_EnableIRQ(USART3_IRQn);
NVIC_EnableIRQ(EXTI0_IRQn); // for ADXL345 interrupt
}
void OLED_Init(void) {
// 复位OLED
GPIOA->BSRR = OLED_RES_PIN;
Delay_ms(100);
GPIOA->BRR = OLED_RES_PIN;
Delay_ms(100);
GPIOA->BSRR = OLED_RES_PIN;
Delay_ms(100);
// 发送初始化命令序列
OLED_Write_Command(0xAE); // display off
// 更多初始化命令,简化
OLED_Write_Command(0xAF); // display on
}
void ADXL345_Init(void) {
// 通过I2C配置ADXL345
I2C_Write(0x53, 0x31, 0x0B); // 数据格式: 全分辨率, ±16g
I2C_Write(0x53, 0x2D, 0x08); // 测量模式
I2C_Write(0x53, 0x2E, 0x80); // 启用中断
}
void GPS_Init(void) {
// 无需特殊配置,UART已初始化
}
void GSM_Init(void) {
Send_AT_Command("ATrn");
Delay_ms(1000);
Send_AT_Command("AT+CPIN?rn");
Delay_ms(1000);
Send_AT_Command("AT+CSQrn");
Delay_ms(1000);
MQTT_Connect();
}
void MQTT_Connect(void) {
char cmd[128];
sprintf(cmd, "AT+MQTTCONN="%s","%s","%s"rn", MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD);
Send_AT_Command(cmd);
Delay_ms(2000);
sprintf(cmd, "AT+MQTTSUB="%s",1rn", SET_TOPIC);
Send_AT_Command(cmd);
}
void MQTT_Publish(char *data) {
char cmd[256];
sprintf(cmd, "AT+MQTTPUB="%s","%s",1,0rn", POST_TOPIC, data);
Send_AT_Command(cmd);
}
void Beep_Alert(void) {
GPIOC->BSRR = BEEP_PIN;
Delay_ms(500);
GPIOC->BRR = BEEP_PIN;
}
void LED_Flash(void) {
GPIOC->BSRR = LED_PIN;
Delay_ms(500);
GPIOC->BRR = LED_PIN;
}
void Lock_Control(uint8_t status) {
if (status) {
GPIOC->BSRR = LOCK_PIN; // 吸合继电器
} else {
GPIOC->BRR = LOCK_PIN; // 断开继电器
}
lockStatus = status;
}
void Check_Movement(void) {
int16_t x, y, z;
ADXL345_ReadAcceleration(&x, &y, &z);
static int16_t lastX = 0, lastY = 0, lastZ = 0;
int16_t delta = abs(x - lastX) + abs(y - lastY) + abs(z - lastZ);
if (delta > 1000 && lockStatus) { // 阈值可调
moveAlert = 1;
Beep_Alert();
// 发送短信和MQTT报警
Send_AT_Command("AT+CMGS="+86123456789"rn"); // 假设号码
Delay_ms(1000);
Send_AT_Command("Alert: Luggage moved!rnx1A");
MQTT_Publish("{"services": [{"service_id": "stm32","properties":{"BEEP":1,"Lock":1,"GPS":{"lon":120.21,"lat":30.19}}}]}");
}
lastX = x;
lastY = y;
lastZ = z;
}
void Process_Keys(void) {
if (!(GPIOA->IDR & KEY1_PIN)) {
inputPassword[inputIndex++] = '1';
Delay_ms(300);
}
if (!(GPIOA->IDR & KEY2_PIN)) {
inputPassword[inputIndex++] = '2';
Delay_ms(300);
}
if (!(GPIOA->IDR & KEY3_PIN)) {
inputPassword[inputIndex++] = '3';
Delay_ms(300);
}
if (!(GPIOA->IDR & KEY4_PIN)) {
inputPassword[inputIndex++] = '4';
Delay_ms(300);
}
if (inputIndex == 4) {
if (strcmp(inputPassword, PASSWORD) == 0) {
Lock_Control(0); // 开锁
}
inputIndex = 0;
memset(inputPassword, 0, 5);
}
}
void Process_GSM_Data(void) {
if (strstr(gsmBuffer, "findcar") != NULL) {
findCarCmd = 1;
}
// 解析其他MQTT命令
gsmIndex = 0;
memset(gsmBuffer, 0, sizeof(gsmBuffer));
}
void Process_GPS_Data(void) {
// 解析NMEA句子,获取经纬度
// 简化:假设直接提取
char *gprmc = strstr(gpsBuffer, "$GPRMC");
if (gprmc) {
// 解析经纬度
// 上传到云
MQTT_Publish("{"services": [{"service_id": "stm32","properties":{"BEEP":0,"Lock":0,"GPS":{"lon":120.21,"lat":30.19}}}]}");
}
gpsIndex = 0;
memset(gpsBuffer, 0, sizeof(gpsBuffer));
}
// 中断服务程序
void USART1_IRQHandler(void) {
if (USART1->SR & USART_SR_RXNE) {
char c = USART1->DR;
gsmBuffer[gsmIndex++] = c;
if (c == 'n' || gsmIndex >= sizeof(gsmBuffer)-1) {
gsmBuffer[gsmIndex] = '?';
Process_GSM_Data();
}
}
}
void USART3_IRQHandler(void) {
if (USART3->SR & USART_SR_RXNE) {
char c = USART3->DR;
gpsBuffer[gpsIndex++] = c;
if (c == 'n' || gpsIndex >= sizeof(gpsBuffer)-1) {
gpsBuffer[gpsIndex] = '?';
Process_GPS_Data();
}
}
}
void EXTI0_IRQHandler(void) {
if (EXTI->PR & EXTI_PR_PR0) {
EXTI->PR = EXTI_PR_PR0;
if (lockStatus) {
moveAlert = 1;
Beep_Alert();
}
}
}
void Delay_ms(uint32_t n) {
for (uint32_t i = 0; i < n * 1000; i++) {
__NOP();
}
}
// I2C读写函数
void I2C_Write(uint8_t addr, uint8_t reg, uint8_t data) {
while (I2C1->SR2 & I2C_SR2_BUSY);
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB));
I2C1->DR = addr << 1;
while (!(I2C1->SR1 & I2C_SR1_ADDR));
(void)I2C1->SR2;
I2C1->DR = reg;
while (!(I2C1->SR1 & I2C_SR1_TXE));
I2C1->DR = data;
while (!(I2C1->SR1 & I2C_SR1_BTF));
I2C1->CR1 |= I2C_CR1_STOP;
}
void I2C_Read(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len) {
while (I2C1->SR2 & I2C_SR2_BUSY);
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB));
I2C1->DR = (addr << 1) | 0x00;
while (!(I2C1->SR1 & I2C_SR1_ADDR));
(void)I2C1->SR2;
I2C1->DR = reg;
while (!(I2C1->SR1 & I2C_SR1_TXE));
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB));
I2C1->DR = (addr << 1) | 0x01;
while (!(I2C1->SR1 & I2C_SR1_ADDR));
(void)I2C1->SR2;
for (uint8_t i = 0; i < len; i++) {
if (i == len-1) I2C1->CR1 &= ~I2C_CR1_ACK;
while (!(I2C1->SR1 & I2C_SR1_RXNE));
data[i] = I2C1->DR;
}
I2C1->CR1 |= I2C_CR1_STOP;
}
void ADXL345_ReadAcceleration(int16_t *x, int16_t *y, int16_t *z) {
uint8_t data[6];
I2C_Read(0x53, 0x32, data, 6);
*x = (int16_t)((data[1] << 8) | data[0]);
*y = (int16_t)((data[3] << 8) | data[2]);
*z = (int16_t)((data[5] << 8) | data[4]);
}
// OLED SPI写函数
void OLED_Write_Command(uint8_t cmd) {
GPIOA->BRR = OLED_DC_PIN; // DC low for command
GPIOA->BRR = OLED_CS_PIN; // CS low
SPI1->DR = cmd;
while (!(SPI1->SR & SPI_SR_TXE));
GPIOA->BSRR = OLED_CS_PIN; // CS high
}
void OLED_Write_Data(uint8_t data) {
GPIOA->BSRR = OLED_DC_PIN; // DC high for data
GPIOA->BRR = OLED_CS_PIN; // CS low
SPI1->DR = data;
while (!(SPI1->SR & SPI_SR_TXE));
GPIOA->BSRR = OLED_CS_PIN; // CS high
}
void OLED_ShowString(uint8_t x, uint8_t y, char *str) {
// 设置位置并显示字符串,简化
}
十四、STM32项目核心代码
当前项目使用的相关软件工具、模块源码已经上传到网盘:https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?from=from_copylink
#include "stm32f10x.h"
#include <string.h>
#include <stdio.h>
// 假设其他模块头文件已存在
#include "oled.h"
#include "gps.h"
#include "mqtt.h"
#include "key.h"
#include "accel.h"
// 硬件引脚定义
#define LED_PIN GPIO_Pin_13
#define LED_PORT GPIOC
#define BEEP_PIN GPIO_Pin_0
#define BEEP_PORT GPIOA
#define RELAY_PIN GPIO_Pin_1
#define RELAY_PORT GPIOA
#define KEY1_PIN GPIO_Pin_2
#define KEY2_PIN GPIO_Pin_3
#define KEY3_PIN GPIO_Pin_4
#define KEY4_PIN GPIO_Pin_5
#define KEY_PORT GPIOA
#define ACCEL_INT_PIN GPIO_Pin_8
#define ACCEL_INT_PORT GPIOA
// UART定义
#define UART4G USART2
#define UARTGPS USART3
// 全局变量
volatile uint8_t lock_status = 0; // 0:解锁, 1:上锁
volatile uint8_t alarm_triggered = 0;
volatile uint8_t move_detected = 0;
char gps_data[50] = {0};
float lon = 0.0, lat = 0.0;
volatile uint8_t uart4g_rx_flag = 0;
volatile char uart4g_rx_buffer[100] = {0};
volatile uint8_t uart4g_rx_index = 0;
char password_input[5] = {0}; // 存储输入的密码
uint8_t password_index = 0;
const char correct_password[5] = "1234"; // 假设正确密码为"1234"
// 系统时钟初始化
void SystemClock_Init(void)
{
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE;
RCC->CFGR |= RCC_CFGR_PLLMULL9;
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
// GPIO初始化
void GPIO_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN;
// LED (PC13)
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_MODE13);
GPIOC->CRH |= GPIO_CRH_MODE13_0;
GPIOC->ODR &= ~LED_PIN;
// Beeper (PA0)
GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0);
GPIOA->CRL |= GPIO_CRL_MODE0_0;
GPIOA->ODR &= ~BEEP_PIN;
// Relay (PA1)
GPIOA->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_MODE1);
GPIOA->CRL |= GPIO_CRL_MODE1_0;
GPIOA->ODR &= ~RELAY_PIN;
// Keys (PA2, PA3, PA4, PA5) input with pull-up
GPIOA->CRL &= ~(GPIO_CRL_CNF2 | GPIO_CRL_MODE2 | GPIO_CRL_CNF3 | GPIO_CRL_MODE3 | GPIO_CRL_CNF4 | GPIO_CRL_MODE4 | GPIO_CRL_CNF5 | GPIO_CRL_MODE5);
GPIOA->CRL |= GPIO_CRL_CNF2_1 | GPIO_CRL_CNF3_1 | GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1;
GPIOA->ODR |= KEY1_PIN | KEY2_PIN | KEY3_PIN | KEY4_PIN;
// Accelerometer interrupt pin (PA8)
GPIOA->CRH &= ~(GPIO_CRH_CNF8 | GPIO_CRH_MODE8);
GPIOA->CRH |= GPIO_CRH_CNF8_1;
GPIOA->ODR |= ACCEL_INT_PIN;
}
// UART初始化
void UART_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN;
// USART2 (4G module) - PA2 (TX), PA3 (RX)
GPIOA->CRL &= ~(GPIO_CRL_CNF2 | GPIO_CRL_MODE2 | GPIO_CRL_CNF3 | GPIO_CRL_MODE3);
GPIOA->CRL |= GPIO_CRL_CNF2_1 | GPIO_CRL_MODE2_0;
GPIOA->CRL |= GPIO_CRL_CNF3_1;
UART4G->BRR = 72000000 / 115200;
UART4G->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
// USART3 (GPS module) - PB10 (TX), PB11 (RX)
GPIOB->CRH &= ~(GPIO_CRH_CNF10 | GPIO_CRH_MODE10 | GPIO_CRH_CNF11 | GPIO_CRH_MODE11);
GPIOB->CRH |= GPIO_CRH_CNF10_1 | GPIO_CRH_MODE10_0;
GPIOB->CRH |= GPIO_CRH_CNF11_1;
UARTGPS->BRR = 72000000 / 9600;
UARTGPS->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
NVIC_EnableIRQ(USART2_IRQn);
}
// SPI初始化 (OLED)
void SPI_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5 | GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
GPIOA->CRL |= GPIO_CRL_CNF5_1 | GPIO_CRL_MODE5_0;
GPIOA->CRL |= GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7_0;
SPI1->CR1 |= SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI;
SPI1->CR1 |= SPI_CR1_SPE;
}
// I2C初始化 (ADXL345)
void I2C_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
GPIOB->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_MODE6 | GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
GPIOB->CRL |= GPIO_CRL_CNF6_1 | GPIO_CRL_MODE6_0;
GPIOB->CRL |= GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7_0;
I2C1->CR1 |= I2C_CR1_SWRST;
I2C1->CR1 &= ~I2C_CR1_SWRST;
I2C1->CR2 = 36;
I2C1->CCR = 180;
I2C1->TRISE = 37;
I2C1->CR1 |= I2C_CR1_PE;
}
// EXTI初始化 (加速度传感器中断)
void EXTI_Init(void)
{
AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI8_PA;
EXTI->IMR |= EXTI_IMR_MR8;
EXTI->RTSR |= EXTI_RTSR_TR8;
NVIC_EnableIRQ(EXTI9_5_IRQn);
}
// 初始化所有模块
void init_all(void)
{
SystemClock_Init();
GPIO_Init();
UART_Init();
SPI_Init();
I2C_Init();
EXTI_Init();
oled_init();
gps_init();
accel_init();
mqtt_init();
}
// 发送AT命令到4G模块
void send_at_command(const char* cmd)
{
while(*cmd)
{
while(!(UART4G->SR & USART_SR_TXE));
UART4G->DR = *cmd++;
}
}
// MQTT发布数据
void mqtt_publish_data(void)
{
char json[100];
sprintf(json, "{"services": [{"service_id": "stm32","properties":{"BEEP":%d,"Lock":%d,"GPS":{"lon":%.2f,"lat":%.2f}}}]}",
alarm_triggered, lock_status, lon, lat);
char mqtt_cmd[150];
sprintf(mqtt_cmd, "AT+MQTTPUB="/topic","%s"rn", json);
send_at_command(mqtt_cmd);
}
// 处理按键输入
void handle_key_input(void)
{
if(!(KEY_PORT->IDR & KEY1_PIN)) { password_input[password_index++] = '1'; while(!(KEY_PORT->IDR & KEY1_PIN)); }
if(!(KEY_PORT->IDR & KEY2_PIN)) { password_input[password_index++] = '2'; while(!(KEY_PORT->IDR & KEY2_PIN)); }
if(!(KEY_PORT->IDR & KEY3_PIN)) { password_input[password_index++] = '3'; while(!(KEY_PORT->IDR & KEY3_PIN)); }
if(!(KEY_PORT->IDR & KEY4_PIN)) { password_input[password_index++] = '4'; while(!(KEY_PORT->IDR & KEY4_PIN)); }
if(password_index >= 4)
{
password_input[4] = '?';
if(strcmp(password_input, correct_password) == 0)
{
lock_status = !lock_status;
RELAY_PORT->ODR = lock_status ? (RELAY_PORT->ODR | RELAY_PIN) : (RELAY_PORT->ODR & ~RELAY_PIN);
}
password_index = 0;
memset(password_input, 0, sizeof(password_input));
}
}
// 延迟函数
void delay(uint32_t count)
{
for(uint32_t i = 0; i < count; i++);
}
// 主函数
int main(void)
{
init_all();
while(1)
{
handle_key_input();
gps_read(&lon, &lat);
sprintf(gps_data, "Lon:%.2f Lat:%.2f", lon, lat);
if(move_detected && lock_status)
{
BEEP_PORT->ODR |= BEEP_PIN;
send_at_command("AT+CMGS="+1234567890"rn");
send_at_command("Alarm! Luggage moved!rn");
send_at_command("x1A");
alarm_triggered = 1;
mqtt_publish_data();
alarm_triggered = 0;
move_detected = 0;
delay(1000000);
BEEP_PORT->ODR &= ~BEEP_PIN;
}
if(uart4g_rx_flag)
{
if(strstr(uart4g_rx_buffer, "find_car"))
{
BEEP_PORT->ODR |= BEEP_PIN;
LED_PORT->ODR |= LED_PIN;
delay(100000);
BEEP_PORT->ODR &= ~BEEP_PIN;
LED_PORT->ODR &= ~LED_PIN;
}
uart4g_rx_flag = 0;
memset((char*)uart4g_rx_buffer, 0, sizeof(uart4g_rx_buffer));
}
oled_show_string(0, 0, "GPS Status:");
oled_show_string(0, 1, gps_data);
oled_show_string(0, 2, lock_status ? "Lock:ON" : "Lock:OFF");
delay(100000);
}
}
// 中断服务程序
void USART2_IRQHandler(void)
{
if(UART4G->SR & USART_SR_RXNE)
{
char c = UART4G->DR;
uart4g_rx_buffer[uart4g_rx_index++] = c;
if(c == 'n' || uart4g_rx_index >= 99)
{
uart4g_rx_buffer[uart4g_rx_index] = '?';
uart4g_rx_flag = 1;
uart4g_rx_index = 0;
}
}
}
void EXTI9_5_IRQHandler(void)
{
if(EXTI->PR & EXTI_PR_PR8)
{
EXTI->PR = EXTI_PR_PR8;
move_detected = 1;
}
}
十五、总结
本项目成功设计并实现了一个基于STM32的智能行李箱定位与防盗系统,集成了多种功能以提升行李箱的安全性和用户体验。系统通过STM32主控芯片协调各模块工作,实现了防盗报警、远程数据监控、本地状态显示、声光寻车以及密码开锁等核心特性,确保了在户外环境下的可靠运行。
在技术层面,硬件采用STM32F103RCT6作为核心,搭配OLED显示屏、4G模块、GPS定位、加速度传感器等组件,通过寄存器编程方式优化了性能和响应速度。软件部分使用C语言开发嵌入式代码,并基于QT5设计了Android APP,实现了与华为云IOT服务器的MQTT协议通信,确保了数据的稳定上传和远程控制。
数据上传方面,系统通过4G模块将GPS定位信息、锁状态和报警信号实时传输到云端,用户可通过APP或Windows上位机进行远程监控和操作,增强了系统的智能化和互联性。硬件设计采用洞洞板制作,结合焊接和杜邦线连接,体现了灵活性和可扩展性,同时支持锂电池供电和太阳能扩展,适应了户外使用的需求。
总体而言,该系统不仅提供了高效的防盗保护,还通过物联网技术实现了远程管理功能,适用于旅行、物流等多种场景,具有较高的实用价值和推广潜力。