一、项目背景
随着智能家居技术的不断发展,人们对于家居生活的需求也越来越高。智能窗帘作为智能家居领域的重要组成部分,为用户提供了更便捷、舒适的生活体验。本项目基于 STM32 主控芯片和华为云物联网平台,设计一款智能窗帘控制系统,以满足家庭和商业场所的需求。
在本项目中,选择了 STM32F103ZET6 作为主控芯片具有强大的处理能力和丰富的外设接口,适合用于物联网设备的控制和通信。通过与 ESP8266-WIFI 模块的连接,可以实现智能窗帘与华为云物联网平台的互联互通,实现远程控制和监测。
为了方便用户的操作和控制,使用 Qt 开发了 Android 手机 APP 和 Windows 上位机软件,用户可以通过这些应用程序进行窗帘的远程控制。同时,本地窗帘也支持手动控制,用户可以通过物理按钮或开关来操作窗帘的开关、升降等功能。
在智能化方面,引入了语音识别技术(LD3320 模块),用户可以通过语音指令来控制窗帘的运行。这为用户提供了更加便捷、智能的控制方式,使得窗帘的操作更加自然和智能化。
除了远程控制和智能化功能,还引入了自动模式。在自动模式下,系统会根据环境条件进行智能判断和控制。例如,当检测到阳光强度超过设定阈值时,系统会自动关闭窗帘,以避免阳光直射室内;在晚上时,系统也会自动拉上窗帘,提供更好的隐私和安全性。
本智能窗帘控制系统基于 STM32 主控芯片和华为云物联网平台,结合语音识别、智能家居控制等功能,旨在为家庭和 1 商业场所提供便捷、舒适的智能化服务。通过远程控制、自动模式和智能化功能,用户可以实现对窗帘的灵活、智能的控制,提升生活质量和用户体验。
二、系统设计
2.1 硬件选型
在设计智能窗帘控制系统的硬件方案时,需要考虑主控芯片、通信模块和传感器等关键组件的选型。
以下是当前系统的具体硬件选型:
【1】主控芯片:采用 STM32F103ZET6 作为主控芯片。具备强大的处理能力和丰富的外设接口,适合用于物联网设备的控制和通信。可以驱动各种传感器和执行器,并与 ESP8266-WIFI 模块和 LD3320 语音识别模块进行通信。
【2】通信模块:选择了 ESP8266-WIFI 模块作为通信模块,用于连接华为云物联网平台。ESP8266-WIFI 模块具有低功耗、高集成度和稳定的无线连接能力,能够实现智能窗帘与互联网的互联互通。
【3】光照传感器:采用 BH1750 光照传感器来检测光照强度。BH1750 是一种数字式光强度传感器,能够准确测量环境光的强度。通过获取光照强度数据,系统可以根据设定的阈值来判断是否需要自动拉窗帘。
【4】语音识别模块:选择了 LD3320 语音识别模块,用于实现语音控制功能。LD3320 是一种高性能语音识别芯片,能够实现对语音指令的识别和解析。通过语音识别模块,用户可以通过语音指令来控制窗帘的开合和模式切换。
【5】电机和驱动模块:选择了 28BYJ40 步进电机作为窗帘控制的电机,并使用 ULN2003 驱动模块来驱动电机。28BYJ40 步进电机具有较高的精度和稳定性,适合用于窗帘的控制。ULN2003 是一种高电压、高电流驱动芯片,能够提供足够的电流和电压来驱动步进电机。
【6】用户界面设备:采用 Qt 开发 Android 手机 APP 和 Windows 上位机来实现用户界面。通过这两个界面,用户可以进行远程控制窗帘的操作,包括开关窗帘、调整窗帘的开合程度和切换窗帘的工作模式。
2.2 设计思路
智能窗帘控制系统的软件设计主要包括主控程序、通信模块驱动、传感器驱动和用户界面等部分。
以下是系统软件设计的思路:
【1】主控程序:主控程序是系统的核心,负责控制窗帘的运行、处理传感器数据、与通信模块进行通信等。主控程序需要实现以下功能:
初始化各个硬件模块,包括通信模块、传感器和电机驱动等。
循环读取传感器数据,根据数据进行窗帘的控制和判断。
处理用户的控制指令,包括远程控制指令和本地控制指令。
与通信模块进行通信,实现与华为云物联网平台的互联互通。
实现自动模式下的智能判断和控制逻辑。
【2】通信模块驱动:通信模块驱动负责与华为云物联网平台进行通信,实现远程控制和数据传输。通信模块驱动需要实现以下功能:
【3】传感器驱动:传感器驱动负责与光敏传感器、时间传感器等传感器进行交互,获取环境数据。传感器驱动需要实现以下功能:
【4】用户界面:用户界面是用户与系统进行交互的界面,可以通过 Android 手机 APP 或 Windows 上位机软件实现。用户界面需要实现以下功能:
三、部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA
。
3.1 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括 3 部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi 等多种网络接入物联网平台,并使用 LWM2M/CoAP、MQTT、HTTPS 协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的 API,实现设备数据采集、命令下发、设备管理等业务场景。
3.2 开通物联网服务
地址: https://www.huaweicloud.com/product/iothub.html
进来默认会提示开通标准版,在 2023 的 1 月 1 号年之后没有基础版了。
开通之后,点击总览
,查看接入信息。 我们当前设备准备采用 MQTT 协议接入华为云平台,这里可以看到 MQTT 协议的地址和端口号等信息。
总结:
端口号: MQTT (1883)| MQTTS (8883)
接入地址: 7445c6bcd3.st1.iotda-app.cn-north-4.myhuaweicloud.com
复制代码
根据域名地址得到 IP 地址信息:
Microsoft Windows [版本 10.0.19044.2728]
(c) Microsoft Corporation。保留所有权利。
C:\Users\11266>ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=42ms TTL=30
来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=30
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30
117.78.5.125 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 35ms,最长 = 42ms,平均 = 37ms
C:\Users\11266>
复制代码
MQTT 协议接入端口号有两个,1883 是非加密端口,8883 是证书加密端口,单片机无法加载证书,所以使用 1883 端口比较合适。 接下来的 ESP8266 就采用 1883 端口连接华为云物联网平台。
3.3 创建产品
(1)创建产品
点击产品页,再点击左上角创建产品。
(2)填写产品信息
根据自己产品名字填写。
(3)产品创建成功
(4)添加自定义模型
产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。
先点击自定义模型。
再创建一个服务 ID。
接着点击新增属性。
3.4 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成 MQTT 三元组的时候需要使用。
(4) 设备创建完成
3.5 MQTT 协议主题订阅与发布
(1)MQTT 协议介绍
当前的设备是采用 MQTT 协议与华为云平台进行通信。
MQTT 是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT 是专门针对物联网开发的轻量级传输协议。MQTT 协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前 MQTT 拥有各种平台和设备上的客户端,已经形成了初步的生态系统。
MQTT 是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT 协议是工作在 TCP/IP 协议上;由 TCP/IP 协议提供稳定的网络连接;所以,只要具备 TCP 协议栈的网络设备都可以使用 MQTT 协议。 本次设备采用的 ESP8266 就具备 TCP 协议栈,能够建立 TCP 连接,所以,配合 STM32 代码里封装的 MQTT 协议,就可以与华为云平台完成通信。
华为云的 MQTT 协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
业务流程:
(2)华为云平台 MQTT 协议使用限制
(3)主题订阅格式
帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/6419627e40773741f9fbdac7_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/6419627e40773741f9fbdac7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。
上传的JSON数据格式如下:
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值>,
..........
}
}
]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"DS18B20":18,"motor_water":1,"motor_oxygen":1,"temp_max":10,"water_hp":130,"motor_food":0,"time_food":0,"oxygen_food":3}}]}
复制代码
3.6 MQTT 三元组
MQTT 协议登录需要填用户 ID,设备 ID,设备密码等信息,就像我们平时登录 QQ,微信一样要输入账号密码才能登录。MQTT 协议登录的这 3 个参数,一般称为 MQTT 三元组。
接下来介绍,华为云平台的 MQTT 三元组参数如何得到。
(1)MQTT 服务器地址
要登录 MQTT 服务器,首先记得先知道服务器的地址是多少,端口是多少。
帮助文档地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT 协议的端口支持 1883 和 8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用 1883 端口进连接的。
根据上面的域名和端口号,得到下面的 IP 地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写 IP 地址。 (IP 地址就是域名解析得到的)
华为云的MQTT服务器地址:114.116.232.138
域名:7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com
华为云的MQTT端口号:1883
复制代码
(2)生成 MQTT 三元组
华为云提供了一个在线工具,用来生成 MQTT 鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到 MQTT 的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过 MQTT 协议登录鉴权的时候,填入参数即可。
ClientId 6419627e40773741f9fbdac7_dev1_0_0_2023032108
Username 6419627e40773741f9fbdac7_dev1
Password 861ac9e6a579d36888b2aaf97714be7af6c77017b017162884592bd68b086a6e
复制代码
3.7 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到 MQTT 登录信息。 接下来就用 MQTT 客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
(1)填入登录信息
打开 MQTT 客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
(2)打开网页查看
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据。
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
四、上位机开发
为了方便查看设备上传的数据,对设备进行远程控制,接下来利用 Qt 开发一款 Android 和 windows 系统的上位机。
使用华为云平台提供的 API 接口获取设备上传的数据,也可以给设备下发指令,控制设备。
为了方便查看设备上传的数据,对设备进行远程控制,接下来利用 Qt 开发一款 Android 和 windows 系统的上位机。
使用华为云平台提供的 API 接口获取设备上传的数据,也可以给设备下发指令,控制设备。
4.1 Qt 开发环境安装
Qt 的中文官网: https://www.qt.io/zh-cn/
QT5.12.6 的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6
打开下载链接后选择下面的版本进行下载:
qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details
软件安装时断网安装,否则会提示输入账户。
安装的时候,第一个复选框里勾选一个mingw 32
编译器即可,其他的不管默认就行,直接点击下一步继续安装。
说明: 我这里只是介绍 PC 端的环境搭建(这个比较简单)。 Android 的开发环境比较麻烦,可以去我的博客里看详细文章。
选择 MinGW 32-bit 编译器:
4.2 创建 IAM 账户
创建一个 IAM 账户,因为接下来开发上位机,需要使用云平台的 API 接口,这些接口都需要 token 进行鉴权。简单来说,就是身份的认证。 调用接口获取 Token 时,就需要填写 IAM 账号信息。所以,接下来演示一下过程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
获取 Token 时,除了 AIM 账号外,还需要项目凭证:
faa0973835ab409ab48182e2590f4ad3
复制代码
鼠标点击自己昵称,点击统一身份认证。
点击左上角创建用户
。
创建成功:
4.3 获取影子数据
帮助文档: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": "6419627e40773741f9fbdac7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DS18B20": 18,
"motor_water": 1,
"motor_oxygen": 1,
"temp_max": 10,
"water_hp": 130,
"motor_food": 0,
"time_food": 0,
"oxygen_food": 3
},
"event_time": "20230321T081126Z"
},
"version": 0
}
]
}
复制代码
4.4 修改设备属性
地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html
接口说明
设备的产品模型中定义了物联网平台可向设备下发的属性,应用服务器可调用此接口向指定设备下发属性。平台负责将属性以同步方式发送给设备,并将设备执行属性结果同步返回。
复制代码
修改设备属性的接口,可以让服务器给设备下发指令,如果需要控制设备。
在线调试地址:
https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties
修改设备属性是属于同步命令,需要设备在线才可以进行调试,先使用 MQTT 客户端登录服务器,模拟设备上线。
然后进行调试,测试数据远程下发给设备。
【1】利用 MQTT 客户端先登录设备 (这是同步命令,必须在线才能调试)
【2】点击调试
{"services":{"temp_max":100}}
复制代码
【4】可以看到,MQTT 客户端软件上已经收到了服务器下发的消息
由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程,设备响应了服务器才能确定数据下发成功。
MQTT 设备端如何响应呢?
设备响应格式说明:https://support.huaweicloud.com/api-iothub/iot_06_v5_3008.html
下面进行实操:
当服务器通过在线调试,发送指令下来之后,客户端将请求 ID 复制下来,添加到发布主题的格式里,再回复回去,服务器收到了响应,一次属性修改就完美完成了。
就是成功的状态:
**下面是请求的总结: ** (响应服务器的修改设备属性请求)
上报主题的格式:$oc/devices/{device_id}/sys/properties/set/response/request_id=
$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/set/response/request_id=
响应的数据:
{"result_code": 0,"result_desc": "success"}
复制代码
4.5 设计上位机
前面 2 讲解了需要用的 API 接口,接下来就使用 Qt 设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建 Qt 工程
选择工程路径,放在英文路径下。
创建完毕。
新建 Android 的模板:
【2】界面设计
【4】代码设计:配置参数读取与保存
/*
功能: 保存数据到文件
*/
void Widget::SaveDataToFile(QString text)
{
/*保存数据到文件,方便下次加载*/
QString file;
file=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
QFile filesrc(file);
filesrc.open(QIODevice::WriteOnly);
QDataStream out(&filesrc);
out << text; //序列化写字符串
filesrc.flush();
filesrc.close();
}
/*
功能: 从文件读取数据
*/
QString Widget::ReadDataFile(void)
{
//读取配置文件
QString text,data;
text=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
//判断文件是否存在
if(QFile::exists(text))
{
QFile filenew(text);
filenew.open(QIODevice::ReadOnly);
QDataStream in(&filenew); // 从文件读取序列化数据
in >> data; //提取写入的数据
filenew.close();
}
return data; //返回值读取的值
}
复制代码
【3】代码设计:云端数据解析
//解析反馈结果
void Widget::replyFinished(QNetworkReply *reply)
{
QString displayInfo;
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
//读取所有数据
QByteArray replyData = reply->readAll();
qDebug()<<"状态码:"<<statusCode;
qDebug()<<"反馈的数据:"<<QString(replyData);
//更新token
if(function_select==3)
{
displayInfo="token 更新失败.";
//读取HTTP响应头的数据
QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs();
qDebug()<<"HTTP响应头数量:"<<RawHeader.size();
for(int i=0;i<RawHeader.size();i++)
{
QString first=RawHeader.at(i).first;
QString second=RawHeader.at(i).second;
if(first=="X-Subject-Token")
{
Token=second.toUtf8();
displayInfo="token 更新成功.";
//保存到文件
SaveDataToFile(Token);
break;
}
}
QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok);
return;
}
//判断状态码
if(200 != statusCode)
{
//解析数据
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判断是否是对象,然后开始解析数据
if(document.isObject())
{
QString error_str="";
QJsonObject obj = document.object();
QString error_code;
//解析错误代码
if(obj.contains("error_code"))
{
error_code=obj.take("error_code").toString();
error_str+="错误代码:";
error_str+=error_code;
error_str+="\n";
}
if(obj.contains("error_msg"))
{
error_str+="错误消息:";
error_str+=obj.take("error_msg").toString();
error_str+="\n";
}
//显示错误代码
QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok);
}
}
return;
}
//设置属性
if(function_select==12 || function_select==13)
{
//解析数据
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判断是否是对象,然后开始解析数据
if(document.isObject())
{
QJsonObject obj = document.object();
if(obj.contains("response"))
{
QJsonObject obj1=obj.take("response").toObject();
int val=0;
QString success;
if(obj1.contains("result_code"))
{
val=obj1.take("result_code").toInt();
}
if(obj1.contains("result_desc"))
{
success=obj1.take("result_desc").toString();
}
if(val==0 && success =="success")
{
//显示状态
QMessageBox::information(this,"提示","远程命令操作完成.",QMessageBox::Ok,QMessageBox::Ok);
return;
}
else
{
//显示状态
QMessageBox::information(this,"提示","设备未正确回应.请检查设备网络.",QMessageBox::Ok,QMessageBox::Ok);
return;
}
}
}
}
}
//查询设备属性
if(function_select==0)
{
//解析数据
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判断是否是对象,然后开始解析数据
if(document.isObject())
{
QJsonObject obj = document.object();
if(obj.contains("shadow"))
{
QJsonArray array=obj.take("shadow").toArray();
for(int i=0;i<array.size();i++)
{
QJsonObject obj2=array.at(i).toObject();
if(obj2.contains("reported"))
{
QJsonObject obj3=obj2.take("reported").toObject();
if(obj3.contains("properties"))
{
QJsonObject properties=obj3.take("properties").toObject();
qDebug()<<"开始解析数据....";
}
}
}
}
}
}
return;
}
}
复制代码
五、代码实现
5.1 ESP8266 连接云平台实现代码
以下是使用 STM32F103ZET6 和 ESP8266 连接华为云物联网平台,通过 MQTT 协议实现设备登录、主题订阅和主题发布的实现代码:
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
// 定义ESP8266的串口
USART_TypeDef* ESP_USARTx = USART1;
// 定义MQTT服务器的地址和端口
const char* MQTT_SERVER = "mqtt.eclipse.org";
const int MQTT_PORT = 1883;
// 定义设备ID和设备密码
const char* DEVICE_ID = "your_device_id";
const char* DEVICE_PASSWORD = "your_device_password";
// 定义订阅的主题
const char* SUBSCRIBE_TOPIC = "your_subscribe_topic";
// 定义发布的主题
const char* PUBLISH_TOPIC = "your_publish_topic";
// 定义接收缓冲区和发送缓冲区的大小
#define RX_BUFFER_SIZE 1024
#define TX_BUFFER_SIZE 1024
// 定义接收缓冲区和发送缓冲区
char rxBuffer[RX_BUFFER_SIZE];
char txBuffer[TX_BUFFER_SIZE];
// 定义接收缓冲区的索引和标志位
volatile uint16_t rxIndex = 0;
volatile uint8_t rxComplete = 0;
// 发送数据到ESP8266
void ESP8266_SendData(const char* data) {
sprintf(txBuffer, "%s\r\n", data);
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
USART_SendData(ESP_USARTx, (uint16_t)'\r');
USART_SendData(ESP_USARTx, (uint16_t)'\n');
}
// 从ESP8266接收数据
void ESP8266_ReceiveData(uint16_t size) {
while (size--) {
rxBuffer[rxIndex++] = USART_ReceiveData(ESP_USARTx);
}
if (rxIndex >= RX_BUFFER_SIZE) {
rxComplete = 1;
rxIndex = 0;
}
}
// 处理接收到的数据
void ProcessReceivedData() {
// TODO: 根据接收到的数据进行处理
}
// ESP8266串口中断处理函数
void USART1_IRQHandler(void) {
if (USART_GetITStatus(ESP_USARTx, USART_IT_RXNE) != RESET) {
ESP8266_ReceiveData(1);
}
}
// 连接到MQTT服务器
void MQTT_Connect() {
// 发送连接请求
sprintf(txBuffer, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", MQTT_SERVER, MQTT_PORT);
ESP8266_SendData(txBuffer);
// 等待连接成功
while (!strstr(rxBuffer, "CONNECTED")) {
if (rxComplete) {
ProcessReceivedData();
rxComplete = 0;
}
}
// 发送MQTT连接请求
sprintf(txBuffer, "AT+MQTTCONNECT=\"%s\",\"%s\"\r\n", DEVICE_ID, DEVICE_PASSWORD);
ESP8266_SendData(txBuffer);
// 等待连接成功
while (!strstr(rxBuffer, "CONNECTED")) {
if (rxComplete) {
ProcessReceivedData();
rxComplete = 0;
}
}
}
// 订阅主题
void MQTT_Subscribe() {
sprintf(txBuffer, "AT+MQTTSUBSCRIBE=\"%s\"\r\n", SUBSCRIBE_TOPIC);
ESP8266_SendData(txBuffer);
}
// 发布消息
void MQTT_Publish(const char* message) {
sprintf(txBuffer, "AT+MQTTPUBLISH=\"%s\",\"%s\"\r\n", PUBLISH_TOPIC, message);
ESP8266_SendData(txBuffer);
}
int main(void) {
// 初始化ESP8266的串口
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(ESP_USARTx, &USART_InitStructure);
USART_Cmd(ESP_USARTx, ENABLE);
USART_ITConfig(ESP_USARTx, USART_IT_RXNE, ENABLE);
NVIC_EnableIRQ(USART1_IRQn);
// 连接到MQTT服务器
MQTT_Connect();
// 订阅主题
MQTT_Subscribe();
while (1) {
if (rxComplete) {
ProcessReceivedData();
rxComplete = 0;
}
// TODO: 处理其他业务逻辑
// 发布消息
MQTT_Publish("Hello, MQTT!");
// 延时一段时间
delay_ms(1000);
}
}
复制代码
以上代码用于演示使用 STM32F103ZET6 和 ESP8266 连接华为云物联网平台,通过 MQTT 协议实现设备登录、主题订阅和主题发布的基本功能。
5.2 ESP8266 的 MQTT 协议指令
ESP8266 通过 MQTT 协议连接到服务器的相关 AT 指令主要有以下几个:
【1】AT+CIPSTART:建立 TCP 连接
功能:使用 TCP 协议连接到远程服务器
用法:AT+CIPSTART="TCP","<服务器地址>",<服务器端口>
示例:AT+CIPSTART="TCP","mqtt.eclipse.org",1883
【2】AT+MQTTCONNECT:连接到 MQTT 服务器
功能:使用 MQTT 协议连接到 MQTT 服务器
用法:AT+MQTTCONNECT="<设备 ID>","<设备密码>"
示例:AT+MQTTCONNECT="your_device_id","your_device_password"
【3】AT+MQTTPUBLISH:发布消息
功能:向指定主题发布消息
用法:AT+MQTTPUBLISH="<主题>","<消息内容>"
示例:AT+MQTTPUBLISH="your_publish_topic","Hello, MQTT!"
【4】AT+MQTTSUBSCRIBE:订阅主题
【5】AT+CIPCLOSE:关闭 TCP 连接
功能:关闭当前的 TCP 连接
用法:AT+CIPCLOSE
这些 AT 指令可以通过串口与 ESP8266 进行通信,实现与 MQTT 服务器的连接、消息发布和订阅等功能。通过这些指令,可以在嵌入式设备上实现与云端的通信和数据交换,从而实现物联网应用。
5.2 步进电机控制代码
以下是使用 STM32F103ZET6 单片机通过 ULN2003 驱动芯片控制 28BYJ-48 步进电机实现角度控制和速度控制的实现代码:
#include "stm32f10x.h"
#include "delay.h"
// 定义步进电机控制引脚
#define IN1_PIN GPIO_Pin_0
#define IN2_PIN GPIO_Pin_1
#define IN3_PIN GPIO_Pin_2
#define IN4_PIN GPIO_Pin_3
#define IN_PORT GPIOA
// 定义步进电机角度和速度参数
#define ANGLE_1 512 // 控制步进电机转动一圈的步数
#define SPEED_1 5 // 控制步进电机转动的速度
// 步进电机转动顺序
const uint8_t stepSequence[8] = {0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09};
// 步进电机当前角度和速度
volatile uint16_t currentAngle = 0;
volatile uint8_t currentSpeed = 0;
// 初始化步进电机控制引脚
void StepperMotor_Init() {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = IN1_PIN | IN2_PIN | IN3_PIN | IN4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IN_PORT, &GPIO_InitStructure);
}
// 控制步进电机转动一步
void StepperMotor_Step() {
static uint8_t stepIndex = 0;
GPIO_Write(IN_PORT, stepSequence[stepIndex]);
stepIndex = (stepIndex + 1) % 8;
}
// 控制步进电机转动到指定角度
void StepperMotor_MoveToAngle(uint16_t targetAngle) {
uint16_t steps = targetAngle - currentAngle;
uint16_t absSteps = steps > 0 ? steps : -steps;
uint8_t direction = steps > 0 ? 1 : -1;
for (uint16_t i = 0; i < absSteps; i++) {
StepperMotor_Step();
delay_ms(2); // 控制步进电机转动的速度
}
currentAngle = targetAngle;
}
// 控制步进电机以指定速度连续转动
void StepperMotor_MoveWithSpeed(uint8_t speed) {
currentSpeed = speed;
while (1) {
StepperMotor_Step();
delay_ms(20 - currentSpeed); // 控制步进电机转动的速度
}
}
int main(void) {
// 初始化步进电机控制引脚
StepperMotor_Init();
// 控制步进电机转动到指定角度
StepperMotor_MoveToAngle(ANGLE_1);
// 控制步进电机以指定速度连续转动
StepperMotor_MoveWithSpeed(SPEED_1);
while (1) {
// 主循环中可以添加其他逻辑代码
}
}
复制代码
5.3 LD3320 识别代码
以下是使用 STM32F103 的串口 2 接收 LD3320 语音识别结果并进行判断控制的代码:
#include "stm32f10x.h"
#include <stdio.h>
// 定义LD3320串口通信引脚
#define LD3320_RX_PIN GPIO_Pin_2
#define LD3320_RX_PORT GPIOA
#define LD3320_USART USART2
// 定义接收缓冲区大小
#define BUFFER_SIZE 128
// 接收缓冲区
volatile char rxBuffer[BUFFER_SIZE];
volatile uint8_t rxIndex = 0;
volatile uint8_t rxComplete = 0;
// 初始化LD3320串口通信引脚
void LD3320_UART_Init() {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
GPIO_InitStructure.GPIO_Pin = LD3320_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(LD3320_RX_PORT, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx;
USART_Init(LD3320_USART, &USART_InitStructure);
USART_ITConfig(LD3320_USART, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(LD3320_USART, ENABLE);
}
// 串口2中断处理函数
void USART2_IRQHandler() {
if (USART_GetITStatus(LD3320_USART, USART_IT_RXNE) != RESET) {
char data = USART_ReceiveData(LD3320_USART);
if (rxIndex < BUFFER_SIZE - 1) {
rxBuffer[rxIndex++] = data;
}
if (data == '\n') {
rxComplete = 1;
}
}
}
// 处理接收到的LD3320识别结果
void ProcessLD3320Result() {
// 在这里进行LD3320识别结果的判断和控制逻辑
// 可以根据接收到的字符串进行判断,例如使用strcmp()函数进行比较
// 示例:if (strcmp(rxBuffer, "ON") == 0) { // 执行打开操作 }
// 清空接收缓冲区
rxIndex = 0;
rxComplete = 0;
}
int main(void) {
// 初始化LD3320串口通信引脚
LD3320_UART_Init();
while (1) {
if (rxComplete) {
ProcessLD3320Result();
}
}
}
复制代码
以上代码使用 STM32F103 的串口 2 接收 LD3320 语音识别结果并进行判断控制。
代码中使用了串口 2 的接收中断来接收 LD3320 的识别结果。在中断处理函数USART2_IRQHandler()
中,将接收到的数据存储到接收缓冲区rxBuffer
中,并通过检测换行符\n
来判断一条完整的识别结果是否接收完成。当识别结果接收完成时,调用ProcessLD3320Result()
函数进行识别结果的判断和控制逻辑处理。
在ProcessLD3320Result()
函数中,可以根据接收到的字符串进行判断和控制逻辑的实现。例如,使用字符串比较函数strcmp()
来比较接收到的字符串与预设的控制命令是否匹配,从而执行相应的操作。在这个函数中,可以添加你需要的控制逻辑,例如打开或关闭某个设备,执行特定的动作等。
评论