目录
下面的内容是关于主机与从机之间利用LORA传输数据
下面是关于传感器数据采集的介绍
前言:
本文是我的学习笔记,我是一个初学者,不可避免有些不足或错误,如果大家发现了,请帮我指出,谢谢。
一、摘要
这个项目除了最小系统板,其它的电路是我自己画的,从硬件到软件都实现了一遍。如果大家只对代码感兴趣,可以自己买模块。(我主要完成代码实现,硬件电路是用顺带画的。)
本项目用到的主要硬件
不自己画电路就用这些模块:STM32F103C8T6最小系统板,有人云4G模块,正点原子LORA模块,OLED屏幕(4脚),INA226电压电流监测模块,MAX485模块,5V供电模块,LED灯(可直接用最小系统板上那个PC13的灯)。
自己画电路:STM32F103C8T6最小系统板(自己焊接太费时间了,直接用的成品),有人云4G模块,正点原子LORA模块,OLED屏幕(4脚),INA226电压电流监测芯片,MAX485芯片,MP2315SGJ-Z稳压芯片,AOD4184N沟道MOS管(或用LED直接连单片机,模拟负载开关),3V5脚继电器(用LED可省略这个)。
使用的是KEIL5+标准库实现
实现功能:
从机:
--通过IIC,RS485,ADC,等原理收集温湿度,光照,风速,风向,太阳辐射,光伏板电压电流功率,风机电压电流功率和电池电压电流功率等信息。将信息按自定义格式打包,通过LORA无线发送至主机(1KM以上距离)。
--接收来自主机的控制信息,做出负载开关动作。
主机:
--通过定时器设置轮询时间间隔,通过LORA模块轮询从机,命令从机返回所收集的信息,
--将从机返回信息通过4G模块的MQTT协议模式上传至华为云,
--接收来自云端的指令,给云端返回响应,并将指令下发给从机。
完整版main.c逻辑展示:
从机:
主机:
二、相关链接
百度网盘资料:
所有资料https://pan.baidu.com/s/1jZ6KuBWUZWtfUZW7b9rmmg?pwd=1234gitee仓库:Wang Jiasong/STM32_MQTT_4G_华为云_LORA
https://gitee.com/wang_jiasong_HB/stm32mqtt4g-huawei-cloudlora.git
参考博客:
MQTT协议文档https://mcxiaoke.gitbooks.io/mqtt-cn/content/mqtt/02-ControlPacketFormat.html
三、原理图展示
主机:
从机:
四、硬件连接
引脚连接如下:
引脚名称 | 功能(主机) | 简介(主机) | 功能(从机) | 简介(从机) |
PA2 | USART2_TX | 串口2:4G串口 | USART2_TX | 串口2:调试串口 |
PA3 | USART2_RX | USART2_RX | ||
PA4 | SCL | 软件IIC:获取电压电流值 | ||
PA5 | SDA | |||
PA6 | ADC | 读取风速模拟量 | ||
PA7 | LOAD | 负载开关控制引脚 | LOAD | 负载开关控制引脚 |
PB1 | 485RE | MAX485芯片使能 | ||
PB10 | USART3_TX | 串口3:调试串口 | USART3_TX | RS485通信串口:获取传感器信息 |
PB11 | USART3_RX | USART3_RX | ||
PB12 | SCL | OLED屏幕 | SCL | OLED屏幕 |
PB13 | SDA | SDA | ||
PA9 | USART1_TX | 串口1:LORA串口 | USART1_TX | 串口1:LORA串口 |
PA10 | USART1_RX | USART1_RX | ||
PB6 | KEY | 按键 | KEY | 按键 |
五、华为云注册与创建
1.注册登录,实名认证
2.找到控制台,找到“设备接入loTDA”,
2.开通免费单元
3.创建产品
4.创建模型
5.添加属性和命令
6.创建产品(创建完之后,会自动下载一个text文本,那是你的设备id和密码)
7.点击详情,进如产品详情界面,到这里,云端的东西都创建好了。
然后打开华为云官方网站,生成MQTT的3个重要秘钥:
把刚刚的设备id和密码复制进去,选择不校验时间戳(这个可以在单片机里一直用,不用更新),把生成的3个数据复制保存起来:
获取华为云MQTT 接入地址 和 端口号 :1883
现在手上有3个重要秘钥,接入地址以及端口号了。下面开始单片机的操作。
六、主机通过4G模块连接MQTT服务器
4G模块我用的是有人云的WH-LTE-7S1 Cat-1-N40,买回来后直接连接好单片机上电就行。
因为源码都给了,所以我只对关键部分做介绍,具体请看代码。
连接MQTT服务器靠FourG.c.h中的函数FourG_MQTT_Config(); main.c中这样就可以让4G模块连接上服务器了。
#include "stm32f10x.h"
#include "Delay.h"
#include "FourG.h"
#include "usart3.h"
int main(void)
{
FourG_Init(115200); /*串口2:4G串口初始化*/
Usart3_Init(115200); /*串口3:调试串口初始化*/
FourG_MQTT_Config(); /*配置4G模块连接华为云服务器*/
printf("Waiting for restart !\r\n");
Delay_s(10); /*等待10秒,让4G模块重启*/
printf("Restart Successfully !\r\n");
while (1)
{}
}
FourG_MQTT_Config(); 中就是这些函数组成的:
我先介绍FourG_MQTT_Config();,然后再介绍FourG_SendCmd();的实现。
FourG_MQTT_Config();中需要注意的有这几条代码:
0.这是我根据《WH-LTE-7S1WH-LTE-7S1-GN 说明书》P35写的,属于4G模块本身的规则。
2.填华为云MQTT接入地址,每个账号有一点区别,不是统一的
3.4.5.填之前获取的ClientId, Username 和 Password
6.如果连接的是3.1.1就填4
9.本项目需要实现命令的下发和响应,所以要配置为分发模式,具体参考《ASR1606_Series_MQTT 操作指南V1.0.1》P22 中的3.1.3. MQTT 分发模式
16.设置 MQTT 订阅主题,即要符合华为云订阅主题格式,本项目只用到了3个格式,如下:
18.设备重启,一般重启需要10秒左右。
void FourG_MQTT_Config(void)
{
// 清空(初始化)串口接收缓存区
FourG_Clear();
printf("Configuring 4G...\r\n");
// 0. 设置模块工作模式为配置模式
printf("0. +++\r\n");
while(FourG_SendCmd("+++", "a")) {
Delay_ms(100);
}
while(FourG_SendCmd("a", "+ok")) {
Delay_ms(100);
}
// 1. 设置 MQTT 工作模式为常规模式
printf("1. AT+WKMOD=MQTT,NOR\r\n");
while(FourG_SendCmd("AT+WKMOD=MQTT,NOR\r\n", "OK")) {
Delay_ms(500);
}
// 2. 设置 MQTT 服务器地址和端口
printf("2. AT+MQTTSVR=20df243370.st1.iotda-device.cn-north-4.myhuaweicloud.com,1883\r\n");
while(FourG_SendCmd("AT+MQTTSVR=20df243370.st1.iotda-device.cn-north-4.myhuaweicloud.com,1883\r\n", "OK")) {
Delay_ms(500);
}
// 3. 设置 MQTT 客户用户名
printf("3. AT+MQTTUSER=67d3983eceaf2e5a8fbe2db5_stm32\r\n");
while(FourG_SendCmd("AT+MQTTUSER=67d3983eceaf2e5a8fbe2db5_stm32\r\n", "OK")) {
Delay_ms(500);
}
// 4. 设置 MQTT 客户用户密码
printf("4. AT+MQTTPSW=24fd768f869f630d4a7e73f4af57725b31edb27b14ba6e3793693f02849c0553\r\n");
while(FourG_SendCmd("AT+MQTTPSW=24fd768f869f630d4a7e73f4af57725b31edb27b14ba6e3793693f02849c0553\r\n", "OK")) {
Delay_ms(500);
}
// 5. 设置 MQTT 客户端 ID
printf("5. AT+MQTTCID=67d3983eceaf2e5a8fbe2db5_stm32_0_0_2025031007\r\n");
while(FourG_SendCmd("AT+MQTTCID=67d3983eceaf2e5a8fbe2db5_stm32_0_0_2025031007\r\n", "OK")) {
Delay_ms(500);
}
// 6. 设置 MQTT 版本
printf("6. AT+MQTTVER=4\r\n");
while(FourG_SendCmd("AT+MQTTVER=4\r\n", "OK")) {
Delay_ms(500);
}
// 7. 设置 Socket 重连时间间隔
printf("7. AT+SOCKRSTIM=10\r\n");
while(FourG_SendCmd("AT+SOCKRSTIM=10\r\n", "OK")) {
Delay_ms(500);
}
// 8. 设置 Socket 最大重连次数
printf("8. AT+SOCKRSNUM=60\r\n");
while(FourG_SendCmd("AT+SOCKRSNUM=60\r\n", "OK")) {
Delay_ms(500);
}
// // 9. 设置 MQTT 串口传输模式为 透传模式
// printf("9. AT+MQTTMOD=0\r\n");
// while(FourG_SendCmd("AT+MQTTMOD=0\r\n", "OK")) {
// Delay_ms(500);
// }
// 9. 设置 MQTT 串口传输模式为 分发模式
printf("9. AT+MQTTMOD=1\r\n");
while(FourG_SendCmd("AT+MQTTMOD=1\r\n", "OK")) {
Delay_ms(500);
}
// 10. 设置 MQTT 心跳包和清除缓存标
printf("10. AT+MQTTCFG=60,0\r\n");
while(FourG_SendCmd("AT+MQTTCFG=60,0\r\n", "OK")) {
Delay_ms(500);
}
// 11. 开启心跳包
printf("11. AT+HEARTEN=ON\r\n");
while(FourG_SendCmd("AT+HEARTEN=ON\r\n", "OK")) {
Delay_ms(500);
}
// 12. 设置心跳包发送间隔为30秒
printf("12. AT+HEARTTM=30\r\n");
while(FourG_SendCmd("AT+HEARTTM=30\r\n", "OK")) {
Delay_ms(500);
}
// 13. 设置心跳包发送方式为 NET(网络)
printf("13. AT+HEARTTP=NET\r\n");
while(FourG_SendCmd("AT+HEARTTP=NET\r\n", "OK")) {
Delay_ms(500);
}
// 14. 设置心跳包数据类型为 USER(自定义数据)
printf("14. AT+HEARTSORT=USER\r\n");
while(FourG_SendCmd("AT+HEARTSORT=USER\r\n", "OK")) {
Delay_ms(500);
}
// 15. 设置心跳包数据为 www.usr.cn
printf("15. AT+HEARTDT=7777772E7573722E636E\r\n");
while(FourG_SendCmd("AT+HEARTDT=7777772E7573722E636E\r\n", "OK")) {
Delay_ms(500);
}
// 16. 设置 MQTT 订阅主题(例如,订阅到 $oc/devices/67c8fd1624d7723255231822_stm32/sys/messages/down)
printf("16. AT+MQTTSUBTP=1,1,$oc/devices/67d3983eceaf2e5a8fbe2db5_stm32/sys/messages/down,0\r\n");
while(FourG_SendCmd("AT+MQTTSUBTP=1,1,$oc/devices/67d3983eceaf2e5a8fbe2db5_stm32/sys/messages/down,0\r\n", "OK")) {
Delay_ms(500);
}
// 17. 设置 MQTT 发布主题
printf("17. AT+MQTTPUBTP=1,1,$oc/devices/67d3983eceaf2e5a8fbe2db5_stm32/sys/properties/report,0,0\r\n");
while(FourG_SendCmd("AT+MQTTPUBTP=1,1,$oc/devices/67d3983eceaf2e5a8fbe2db5_stm32/sys/properties/report,0,0\r\n", "OK")) {
Delay_ms(500);
}
// 18. 保存配置并重启
printf("18. AT+S\r\n");
while(FourG_SendCmd("AT+S\r\n", "OK")) {
Delay_ms(500);
}
// // 19. 保存配置并重启
// printf("18. AT+S\r\n");
// while(FourG_SendCmd("AT+S\r\n", "OK")) {
// Delay_ms(500);
// }
printf("MQTT Configuration Done!\r\n");
}
FourG_SendCmd();的代码如下:
其中,FourG_printf();就是把C语言学习时期的那个printf();重定义为向串口2(和4G模块连接的哪个串口)打印,也就是向串口2发送格式化字符串,在这里利用这个函数向4G模块发送AT指令。
更准确的解释是,实现了一个自定义的格式化输出函数FourG_printf(),用于通过 USART2 串口发送格式化数据。它的核心功能是将格式化的字符串写入缓冲区,然后通过 USART2 逐字节发送出去。
其中,FourG_WaitRecive(void);用于循环检测串口2的接收情况,这个函数需要循环调用(也就是代码里的while(timeOut--)),如果检测到串口2接收了完整的数据,就返回0值。
然后通过strstr((const char *)FourG_BUF, res)来检测4G模块接收到AT指令后是否返回了对的值(例如OK)。
/********************************************************
*简介: 发送AT指令并判断返回是否合规
*参数: cmd:命令
res:需要检查的返回指令
*返回: 0-成功 1-失败
*********************************************************/
_Bool FourG_SendCmd(char *cmd, char *res)
{
unsigned char timeOut = 200;
FourG_printf(cmd); /* 串口向FourG发送AT指令 */
while(timeOut--)
{
if(FourG_WaitRecive() == 0) /* 判断是否收到完整数据 */
{
if(strstr((const char *)FourG_BUF, res) != NULL) /* 如果检索到关键词 */
{
FourG_Clear(); /* 清空缓存 */
return 0;
}
}
Delay_ms(10);
}
return 1;
}
以上的代码可以实现4G模块连接MQTT服务器,连接好串口助手,连接好4G模块,打开串口助手:
下载程序后串口助手会显示:
连接上之后,华为云显示在线:
七、主机上传数据到华为云
我的项目因为实际需要,在华为云设置了15个属性变量,温度湿度电压电流什么的,如果是学习的话,设置一个温度,一个LED开关即可,但是我这篇文章就按照我的实际项目来介绍吧,所以下面来自华为云的截图是我另一个账号的,多了很多属性变量。
首先,如果没接LORA和实际传感器的话,那就在主机程序里定义一个数组uint8_t LoraArrRx[38]用来缓存模拟的传感器数据。
/*模拟 传感器数值缓存数组*/
uint8_t LoraArrRx[38] = {0x00,0x01,0x01,0x74,0xFE,0xF1,0x00,0xFB,0xC5,0x20,0x07,0x1D,0x00,0x3E,
0x22,0x7A,0x43,0x36,0x04,0x14,0x25,0x34,0x6C,0xBA,0x07,0xAF,0x25,0xE0,
0x6F,0x5C,0x07,0x6D,0x00,0x1A,0x00,0x00,0x00,0x00}; /*Lora接收数据缓存区*/
数据格式如下(我自定义的,可以不要最后的CRC校验码,但是在实际现场使用的时候还是加上,因为设备在室外,信号没有测试的时候好,有可能出现数据错误,这里模拟的话校验码就直接设置为00了):
先看这一步的主函数:
上传数据主要用到的有:
DATA_Read(LoraArrRx);功能是把LoraArrRx[38]中的数据转换为实际的温湿度值,并将这些值更新到TEMP,HUMI等变量中。
OneNet_FillBuf(buf);的作用主要是组合MQTT协议的“有效载荷”,其格式是JSON格式,最终把组合好的"有效载荷"放入buf[512]中。这个函数中,组合的数据的第一句是:strcpy(buf, "1,{\"services\": [{\"service_id\": \"stm32\",\"properties\":{");,前面的1的意思是分发模式下发给主题1,请参考《ASR1606_Series_MQTT 操作指南V1.0.1》P22 中的3.1.3. MQTT 分发模式。
//JSON格式
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值> //最后一项没逗号
..........
}
}
]
}
FourG_TxData(buf,512);这是发送函数,即通过串口2,把数据发送给4G模块,4G模块会自动组合之前设置好的发布主题,然后自动把数据发送到华为云,数据长度建议把整个buf[512]全发过去,不要计算buf中字符串的长度,那样有可能计算错。
main.c:
#include "stm32f10x.h"
#include "Delay.h"
#include "FourG.h"
#include "usart3.h"
/*模拟 传感器数值缓存数组*/
uint8_t LoraArrRx[38] = {0x00,0x01,0x01,0x74,0xFE,0xF1,0x00,0xFB,0xC5,0x20,0x07,0x1D,0x00,0x3E,
0x22,0x7A,0x43,0x36,0x04,0x14,0x25,0x34,0x6C,0xBA,0x07,0xAF,0x25,0xE0,
0x6F,0x5C,0x07,0x6D,0x00,0x1A,0x00,0x01,0x00,0x00}; /*Lora接收数据缓存区*/
/*定义初始变量,这些变量的值直接被上传到华为云*/
float TEMP = 0.0, HUMI = 0.0, WDSPEED = 0.0, WDDER = 0.0;
float CELLU = 0.0 ,PVPOWER = 0.0, WDPOWER = 0.0, PVU = 0.0;
float PVI = 0.0, WDU = 0.0, WDI = 0.0, CELLI = 0.0, CELLPOWER = 0.0;
uint32_t LUX = 0, RAD = 0;
uint8_t load = 0;/*负载默认是关闭状态*/
unsigned char buf[512]; /*缓存温湿度上报数据*/
int main(void)
{
uint32_t timeout = 0;
FourG_Init(115200); /*串口2:4G串口初始化*/
Usart3_Init(115200); /*串口3:调试串口初始化*/
FourG_MQTT_Config(); /*配置4G模块连接华为云服务器*/
printf("Waiting for restart !\r\n");
Delay_s(10); /*等待10秒,让4G模块重启*/
printf("Restart Successfully !\r\n");
while (1)
{
memset(buf,0,sizeof(buf));
while(timeout++)
{
if(timeout >= 500)
{
DATA_Read(LoraArrRx); /* 将数据按对应格式存放在定义好的变量中*/
memset(buf,0,sizeof(buf)); /*TEMP等变量值已经为最新值,下面上传温湿度信息*/
OneNet_FillBuf(buf); /*组合有效载荷,并缓存到buf[]中*/
FourG_TxData(buf,512); /*直接将512个数据全部上传,
避免长度计算错误,MQTT会自动截取有用信息*/
timeout = 0;
}
}
Delay_ms(10);
}
}
注意几个点:
1.创建这些属性的时候,选好数据类型(整型或小数或布尔),设置好数据范围,设置好数据步长,否则可能出错。
2.最后那个控制LED的,你此时没接LED的话,你需要把OneNet_FillBuf函数的最后一句直接改成true,不加那个结构体判断LED状态。
memset(text, 0, sizeof(text));
sprintf(text, "\"LOADSTA\":%s","true");
strcat(buf, text);
strcat(buf, "}}]}");
以上代码实现之后,华为云是这个样子:
八,华为云下达指令和单片机回复响应
先看实现到这里的main.c:
加了这些头文件:
#include "CmdProcessing.h" 这个之后再讲
#include "Load.h" 这个就是一个控制GPIO高低电平的,注意检查IO口是不是连接的你的LED,不然到时候其它都正常,灯就是不亮。
#include "stm32f10x.h"
#include "Delay.h"
#include "FourG.h"
#include "usart3.h"
#include "CmdProcessing.h"
#include "Load.h"
/*模拟 传感器数值缓存数组*/
uint8_t LoraArrRx[38] = {0x00,0x01,0x01,0x74,0xFE,0xF1,0x00,0xFB,0xC5,0x20,0x07,0x1D,0x00,0x3E,
0x22,0x7A,0x43,0x36,0x04,0x14,0x25,0x34,0x6C,0xBA,0x07,0xAF,0x25,0xE0,
0x6F,0x5C,0x07,0x6D,0x00,0x1A,0x00,0x00,0x00,0x00}; /*Lora接收数据缓存区*/
/*定义初始变量,这些变量的值直接被上传到华为云*/
float TEMP = 0.0, HUMI = 0.0, WDSPEED = 0.0, WDDER = 0.0;
float CELLU = 0.0 ,PVPOWER = 0.0, WDPOWER = 0.0, PVU = 0.0;
float PVI = 0.0, WDU = 0.0, WDI = 0.0, CELLI = 0.0, CELLPOWER = 0.0;
uint32_t LUX = 0, RAD = 0;
uint8_t load = 0;/*负载默认是关闭状态*/
unsigned char buf[512]; /*缓存温湿度上报数据*/
int main(void)
{
uint8_t LOADSTATION = 128; /*记录负载开关指令:0为关,1为开,128为错误*/
char request_id_buf[256]; /*缓存request_id*/
char output_buffer[256]; /*缓存用于存储组合好的主题*/
unsigned char buf[512]; /*缓存温湿度上报数据*/
char buff[256]; /*缓存回复命令主题的AT指令*/
memset(request_id_buf,0,sizeof(request_id_buf));
memset(output_buffer,0,sizeof(output_buffer));
memset(buff,0,sizeof(buff));
memset(buf,0,sizeof(buf));
uint32_t timeout = 0;
FourG_Init(115200); /*串口2:4G串口初始化*/
Usart3_Init(115200); /*串口3:调试串口初始化*/
FourG_MQTT_Config(); /*配置4G模块连接华为云服务器*/
printf("Waiting for restart !\r\n");
Delay_s(10); /*等待10秒,让4G模块重启*/
printf("Restart Successfully !\r\n");
while (1)
{
memset(buf,0,sizeof(buf));
while(timeout++)
{
if(timeout >= 500)
{
DATA_Read(LoraArrRx); /* 将数据按对应格式存放在定义好的变量中*/
memset(buf,0,sizeof(buf)); /*TEMP等变量值已经为最新值,下面上传温湿度信息*/
OneNet_FillBuf(buf); /*组合有效载荷,并缓存到buf[]中*/
FourG_TxData(buf,512); /*直接将512个数据全部上传,
避免长度计算错误,MQTT会自动截取有用信息*/
timeout = 0;
}
}
/*处理云端命令*/
if(SEEK_FourGRxOK() == 0) /*如果4G完成云端命令的接收*/
{
printf("FourG_BUF: %s\r\n", FourG_BUF); /*打印云端命令*/
LOADSTATION = Look_LOADSTA(FourG_BUF); /*将单片机开关指令发送至从机*/
if(LOADSTATION != 128)
{
Look_REID(request_id_buf, FourG_BUF);/*提取request_id到request_id_buf中*/
printf("request_id_buf: %s\r\n", (char *)request_id_buf);
FourG_MQTT_PUBTEM(request_id_buf, output_buffer, /*利用request_id配置临时发布主题*/
sizeof(output_buffer));
memset(buf,0,sizeof(buf)); /*组合回复报文并发送*/
OneNet_CmdRequestBuf(buf, output_buffer);
FourG_TxData(buf, 256); /*发送响应信息至云端*/
}
}
Delay_ms(10);
}
}
以上代码中增加的是这些函数:
SEEK_FourGRxOK();
这个函数循环调用FourG_WaitRecive()检查云端是否有消息发过来。
Look_LOADSTA(FourG_BUF);
这个是CmdProcessing.c.h中的函数,用来从FourG_BUF中解析出云端控制 LOAD 的命令并判断其值是true还是false, 是true就调用Load_Set(1);开灯,反之类似。
到这里,其实主要功能就实现了,但是华为云那边还要判断设备是否接收到了响应,所有下面的函数是单片机返回给华为云一个响应。
Look_REID(request_id_buf, FourG_BUF);
这个函数的传入参数FourG_BUF是串口2的接收缓存区,这时候这里面存的就是4G模块接收到的云端指令,长这样:$oc/devices/12312324d712312312822_stm32/sys/commands/request_id=d49f0bb9-ba87-4c9b-b915-98a1f0fcf689{"paras":{"LOAD":true},"service_id":"LOAD","command_name":"负载开关"}这里面的request_id是我们需要的。现在把储存了这些内容的缓存区数组传入Look_REID()函数,该函数提取出request_id的内容,并将其存放在request_id_buf[]中。
FourG_MQTT_PUBTEM(request_id_buf, output_buffer, sizeof(output_buffer));
接着,把request_id_buf[]传入FourG_MQTT_PUBTEM(),该函数会让4G模块进入配置模式,给4G模块设置一个“临时发布主题”,临时发布主题中需要request_id,用以回复华为云。最后这个函数将“临时发布主题”存入output_buffer[].
OneNet_CmdRequestBuf(buf, output_buffer);
设置好临时发布主题后,这个函数用来组合回复报文里的“有效载荷(payload)”,有效载荷的格式也是JSON格式,但是发给4G模块的数据除了有效载荷之外,还有其它内容,参考《ASR1606_Series_MQTT 操作指南V1.0.1》P22
FourG_TxData(buf, 256);
这个是数据发送函数,组合好报文之后,现在报文被存放在buf[]中,把buf[]发送出去就好了,可以填256,也可以填512,只要长度够包含所有报文内容就行,只能长不能短。
/*处理云端命令*/
if(SEEK_FourGRxOK() == 0) /*如果4G完成云端命令的接收*/
{
printf("FourG_BUF: %s\r\n", FourG_BUF); /*打印云端命令*/
LOADSTATION = Look_LOADSTA(FourG_BUF); /*将单片机开关指令发送至从机*/
if(LOADSTATION != 128)
{
Look_REID(request_id_buf, FourG_BUF);/*提取request_id到request_id_buf中*/
printf("request_id_buf: %s\r\n", (char *)request_id_buf);
FourG_MQTT_PUBTEM(request_id_buf, output_buffer, /*利用request_id配置临时发布主题*/
sizeof(output_buffer));
memset(buf,0,sizeof(buf)); /*组合回复报文并发送*/
OneNet_CmdRequestBuf(buf, output_buffer);
FourG_TxData(buf, 256); /*发送响应信息至云端*/
}
}
上面的代码实现之后的效果:
单片机运行,4G模块重启成功,上传数据成功,然后华为云显示在线,
这时下发一个命令试试,:
这样就好了,如果单片机没有回复华为云,这里会显示设备没有回复响应。
九、华为云的数据保存
因为我这篇文章主要的介绍内容是单片机与华为云之间的数据传输,至于数据上传之后怎么用怎么存储,这里就不多介绍。如果想获取历史数据,可以用下面这个方法:
可以在云端运行日志中找到你上传的数据,你需要开通云端运行日志,当然是免费的,最多5000条数据:
下载为EXCEL,里面有你想要的数据:
然后这里,设置按什么分隔符分页,最后选择确定,就能把数据分好了,
你如果想把数字单独一列的话,你就选择按冒号:分割
下面的内容是关于主机与从机之间利用LORA传输数据
十、 主机通过LORA轮询从机
把串口1配置为单片机与LORA模块通信的串口,然后在LORA.c.h中实现以下函数和定义变量:
关于LORA的配置:
我用的是正点原子的LORA模块,配置资料我已给出,请参考《ATK-LORA-01无线串口模块用户手册_V1.3》,如果要组一组多从的LORA网,直接把主机LORA的“模块地址”配置为65535,工作模式配置为一般模式,空中速率配置为9.6k,从机只需要保证空中速率,工作模式,发送状态和主机一致就可以了。
这样配置完之后,首先,主机的单片机向串口1发送任何内容,LORA直接广播出去,从机LORA发送的任何内容,主机直接在串口1中断中收到一样的内容。
从机也是一样,单片机向串口发什么,LORA就发什么,然后主机会监听到。
然后主机main.c就是我最终的代码了:
主要增加的有:
Timer_Init(); 定时器,定时问询LORA,也就是主机每隔5分钟,问询从机,从机上传数据,主机判断有数据进来了且数据接收完毕,主机把数据上传到华为云。
OLED_Show(LoraArrRx);在OLED屏幕上展示数据,并有翻页功能
在Look_LOADSTA(FourG_BUF);函数中,如果云端下发命令了,解析出LOAD是true还是false,是true就把开指令下发到从机,反之类似,代码如下:发送8次0xAA代表只让从机返回信息,发送0xBB代表让从机打开设备,发送0xCC代表让从机关闭设备。(0xAA 0xBB 0xCC是我自己规定的,你也可以发别的,只有你从机代码里约定好就行)
注意:如果想同时控制N个从机,并指定某一个从机上的某一个设备的开启与关闭,原理是一样的,只不过你不能只发AA BB CC了,你需要自定义某个位代表什么意思,从机接收到这一串消息后,根据每个位不同的值来做出不同的动作。
// 判断是否是 true 或 false
if (strncmp(start_pos, "true", 4) == 0)
{
Load_Set(1);
for(i=0;i<8;i++){Lora_SendByte(0xBB);} /*向从机发送开负载指令*/
return 1;
}
else if (strncmp(start_pos, "false", 5) == 0)
{
Load_Set(0);
for(i=0;i<8;i++){Lora_SendByte(0xCC);} /*向从机发送关负载指令*/
return 0;
}
else
{
printf("Error: Invalid LOAD value\r\n");
}
#include "stm32f10x.h"
#include "Delay.h"
#include "FourG.h"
#include "usart3.h"
#include "CmdProcessing.h"
#include "Load.h"
#include "stm32f10x.h"
#include "CmdProcessing.h"
#include "OLED.h"
#include "Delay.h"
#include "LORA.h"
#include "FourG.h"
#include "CRCMODBUS.h"
#include "key.h"
#include "OLED_Show.h"
#include "usart3.h"
#include "Timer.h"
/*定义初始变量,这些变量的值直接被上传到华为云*/
float TEMP = 0.0, HUMI = 0.0, WDSPEED = 0.0, WDDER = 0.0;
float CELLU = 0.0 ,PVPOWER = 0.0, WDPOWER = 0.0, PVU = 0.0;
float PVI = 0.0, WDU = 0.0, WDI = 0.0, CELLI = 0.0, CELLPOWER = 0.0;
uint32_t LUX = 0, RAD = 0;
uint8_t load = 0;/*负载默认是关闭状态*/
uint8_t LoraArrRx[100]; /*Lora接收数据缓存区*/
uint8_t LoraDataNum = 0; /*Lora接收数据的下标*/
uint8_t Lora_RxFlag = 0; /*Lora的串口接收数据标志位*/
uint8_t Time_Out = 0; /*定时时间到 标志位*/
uint8_t mode = 0; /*OLED屏幕翻页 0,1,2,3页*/
int main(void)
{
uint8_t LOADSTATION = 128; /*记录负载开关指令:0为关,1为开,128为错误*/
char request_id_buf[256]; /*缓存request_id*/
char output_buffer[256]; /*缓存用于存储组合好的主题*/
unsigned char buf[512]; /*缓存温湿度上报数据*/
char buff[256]; /*缓存回复命令主题的AT指令*/
memset(request_id_buf,0,sizeof(request_id_buf));
memset(output_buffer,0,sizeof(output_buffer));
memset(buff,0,sizeof(buff));
memset(buf,0,sizeof(buf));
memset(LoraArrRx,0,sizeof(LoraArrRx));
Lora_Init(9600); /*串口1:LORA串口初始化*/
FourG_Init(115200); /*串口2:4G串口初始化*/
Usart3_Init(115200); /*串口3:调试串口初始化*/
OLED_Init(); /*OLED屏幕初始化*/
Load_Init(); /*负载状态初始化*/
OLED_ShowString(1, 1, "MQTT Init...");
FourG_MQTT_Config(); /*配置4G模块连接华为云服务器*/
OLED_ShowString(2, 1, "MQTT OK !");
OLED_ShowString(3, 1, "4G Init...");
printf("Waiting for restart !\r\n");
Delay_s(10); /*等待10秒,让4G模块重启*/
printf("Restart Successfully !\r\n");
OLED_ShowString(4, 1, "4G OK !");
Timer_Init(); /*重启后再开始计时,定时器初始化*/
OLED_Clear();
OLED_Show(LoraArrRx); /*屏幕展示数据*/
while (1)
{
/*获取温湿度等信息并上传*/
if(Time_Out == 1) /*定时时间到,定时时间为Per_Time秒*/
{
uint8_t i = 0; /*向1号LORA从机发送问询信息 0xAA*/
for(i=0;i<8;i++){Lora_SendByte(0xAA);}
printf("Lora_SendByte ok \r\n");
if(Lora_RxFlag == 1) /*如果LORA接收完成*/
{
OLED_Show(LoraArrRx); /*屏幕展示数据*/
DATA_Read(LoraArrRx); /* 将数据按对应格式存放在定义好的变量中*/
memset(buf,0,sizeof(buf)); /*TEMP等变量值已经为最新值,下面上传温湿度信息*/
OneNet_FillBuf(buf); /*组合有效载荷,并缓存到buf[]中*/
FourG_TxData(buf,512); /*直接将512个数据全部上传,避免长度计算错误,MQTT会自动截取有用信息*/
// memset(LoraArrRx,0,sizeof(LoraArrRx)); /*为了展示从LORA处获得的数据,每次上传云端后可不清零*/
Lora_RxFlag = 0;
}
Time_Out = 0; /*清除定时器标志位*/
}
/*处理云端命令*/
if(SEEK_FourGRxOK() == 0) /*如果4G完成云端命令的接收*/
{
printf("FourG_BUF: %s\r\n", FourG_BUF); /*打印云端命令*/
LOADSTATION = Look_LOADSTA(FourG_BUF); /*将单片机开关指令发送至从机*/
if(LOADSTATION != 128)
{
Look_REID(request_id_buf, FourG_BUF);/*提取request_id到request_id_buf中*/
printf("request_id_buf: %s\r\n", (char *)request_id_buf);
FourG_MQTT_PUBTEM(request_id_buf, output_buffer, /*利用request_id配置临时发布主题*/
sizeof(output_buffer));
memset(buf,0,sizeof(buf)); /*组合回复报文并发送*/
OneNet_CmdRequestBuf(buf, output_buffer);
FourG_TxData(buf, 256); /*发送响应信息至云端*/
}
}
}
}
十一、从机接收主机问询命令并回复信息
从机也是先定义好串口1与LORA模块之间的串口数据传输。
逻辑是,如果从机收到了主机发来的命令,如果是8个0xAA,则把收集到的数据通过LORA发送出去,如果是0xBB就开启负载,如果是0xCC就关闭负载。
从机代码里的逻辑是这样的,采集到的数据被存放在DataArr[]中:
/*如果LORA接收到命令*/
if(Lora_RxFlag == 1)
{
/*0xAA表示仅发送数据回主机*/
if(LoraArrRx[0] == 0xAA && LoraArrRx[7] == 0xAA)
{
Lora_SendArray(DataArr, 38);
for(i=0;i<20;i++){LoraArrRx[i] = 0;} /*清除LORA接收指令缓存区*/
Lora_RxFlag = 0; /*清除LORA接收指令标志位*/
}
/*0xBB表示打开负载*/
if(LoraArrRx[0] == 0xBB && LoraArrRx[7] == 0xBB)
{
OLED_ShowString(4, 11, "L_ON ");
Load_Set(Load_ON);
for(i=0;i<20;i++){LoraArrRx[i] = 0;} /*清除LORA接收指令缓存区*/
Lora_RxFlag = 0;
}
/*0xCC表示关闭负载*/
if(LoraArrRx[0] == 0xCC && LoraArrRx[7] == 0xCC)
{
OLED_ShowString(4, 11, "L_OFF");
Load_Set(Load_OFF);
for(i=0;i<20;i++){LoraArrRx[i] = 0;} /*清除LORA接收指令缓存区*/
Lora_RxFlag = 0;
}
Lora_RxFlag = 0;
}
需要注意的是:
1.从机发送出去的信息,其它从机是收不到的,因为他们地址不一样,主机是收得到的,因为主机地址是65535,可以收到任何模块的信息(只要他们空中速率一样)。
2.如果2个从机LORA同时给主机发送消息怎么办?这种状况是不可能发生的,因为实际的代码的逻辑是,主机到时间后,先问询从机1,从机1返回消息或在规定文献次数内未返回信息,主机才问询从机2。
下面是关于传感器数据采集的介绍
十二、RS485总线采集多个传感器数据
之后在另一篇文章介绍。
十三、IIC读取INA226电压电流监测模块数据
我的另一篇博客有详细介绍:
十四、风速模拟量的采集与转换ADC
这个比较简单,就是AD转换,不多介绍
总结
其实有人云4G模块还可以直接用透传模式直接连接有人云官方平台,实现数据上传和存储
也可以快速实现一些数据展示:
不过这各有利弊,如果仅仅是做个毕设或者自己的小项目,用有人云官网的就够了。
另外,MQTT的内容还有很多,这里只是实现了最简单的功能,入个门,以后具体使用的时候还要深入学习。
建议:
如果是想实现MQTT,建议仔细阅读MQTT中文使用说明,
如果想实现LORA组网,建议详细阅读LORA的用户手册。