模块介绍
WH-LTE-7S1 是为实现串口设备与网络服务器,通过LTE Cat-1和GPRS网络相互传输数据而开发的产品,通过简单的 AT 指令进行设置,即可轻松使用本产品实现串口到网络的双向数据透明传输
相关资料获取
官方设置软件:https://www.usr.cn/Download/986.html
说明文档:https://www.usr.cn/Download/index/keyword/WH-LTE-7S1
1. 云平台设置
有人云官网:https://mp.usr.cn
1.1 添加设备
添加设备并完善自己设备的基础信息
将设备的基本信息填写完毕后,点击选择模板
选择Modbus模板,并设置名称
添加网关,并自动生成SN
设备信息全部设置完成后的界面是这样的,然后点击保存
1.2 添加变量
点击设备管理
—设备模板
选项卡,找到刚才添加的Modbus模板,点击"编辑"
在新的界面中点击“添加变量”按钮
设置你要上传数据的变量
我根据我需要写的项目添加了多个变量,实际的项目也不会是只有一个变量的,添加完变量后别忘了点击保存
2. 软件配置
2.1 模块电路连接
2.2 配置串口
我这里使用串口1进行串口打印操作,而4G模块的收发信息是通过串口3完成的
由于该4G模块需要通过AT指令进行设置,因此我通过串口1发送AT指令,再将这些指令转发到串口3,以实现对4G模块的控制和配置
串口1配置:
/**
* @brief 初始化打印串口1
* USART1_TXD --- PA9
* USART1_RXD --- PA10
*/
void Usart1_Config(void) {
// 开启PA9,PA10,USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE);
// 配置IO口
GPIO_InitTypeDef GPIO_InitStruct;
// PA9 -- TX -- 复用推挽
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
// PA10 -- RX -- 浮空输入
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStruct);
// 配置串口:
USART_InitTypeDef USART_InitStruct ={0};
// 配置数据8位
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
// 配置校验位0位
USART_InitStruct.USART_Parity = USART_Parity_No;
// 配置停止位1位
USART_InitStruct.USART_StopBits = USART_StopBits_1;
// 配置波特率
USART_InitStruct.USART_BaudRate = 115200;
// 打开发送接收
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用硬件控制流
USART_Init(USART1, &USART_InitStruct);
// 使能串口
USART_Cmd(USART1, ENABLE);
// 打开串口中断
USART1->CR1 |= 0x1<<5; // 打开接收缓存区非空中断
USART1->CR1 |= 0x1<<4; // 打开总线空闲中断
// NVIC配置
NVIC_SetPriority(USART1_IRQn, 3); // 设置优先级 0101抢占:01 次级:01
NVIC_EnableIRQ(USART1_IRQn); // 使能中断
}
/**
* @brief 串口1中断函数
*/
void USART1_IRQHandler(void) {
uint8_t data = 0;
// 判断中断挂起位
if((USART1->SR & 0x1<<5) != 0) {
// 读数据操作,同使也是清标志操作
data = USART1->DR;
USART3->DR = data; // 将AT指令转发给4G模块
}
if((USART1->SR & 0x1<<4) != 0) {
// 读数据操作,同使也是清空闲标志操作
data = USART1->DR;
}
}
串口3配置:
typedef struct{
uint8_t RX_DataBuf[256]; //接收数据缓冲区
uint8_t DataBuf_Count; //接收数据缓冲区数量
uint8_t RXDataOK_Flag; //接收数据完成标志位
}_DATA4G;
/**
* @brief 初始化4G模块
* USART3_TXD --- PB10
* USART3_RXD --- PB11
*/
void Wire4G_Config(void) {
// 启用 GPIOB 外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 启用 USART3 外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
// 定义结构体变量以初始化 GPIO, USART 和 NVIC
GPIO_InitTypeDef GPIO_InitStructure = {0};
USART_InitTypeDef USART_InitStructure = {0};
NVIC_InitTypeDef NVIC_InitStructure = {0};
// 复位 USART3 外设
USART_DeInit(USART3);
// 配置 USART3 TX (PB.10) 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 设置引脚为 PB.10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置 GPIO 速度为 50 MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置模式为复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化 PB.10
// 配置 USART3 RX (PB.11) 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // 设置引脚为 PB.11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置模式为浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化 PB.11
// 配置 USART3 的 NVIC(中断向量控制器)
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; // 设置中断通道为 USART3
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级为 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 子优先级为 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 启用中断通道
NVIC_Init(&NVIC_InitStructure); // 初始化 NVIC
// 初始化 USART3
USART_InitStructure.USART_BaudRate = 115200; // 设置波特率为 115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 设置字长为 8 位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 设置停止位为 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(USART3, &USART_InitStructure); // 初始化 USART3
// 启用 USART3 的接收中断
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); // 开启接收非空中断
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); // 开启空闲中断
// 使能 USART3
USART_Cmd(USART3, ENABLE);
}
/**
* @brief 串口3中断函数,负责接收4G模块下发的数据请求
*/
void USART3_IRQHandler(void) {
uint8_t data = 0;
// 接收中断
if(USART_GetITStatus(USART3,USART_IT_RXNE) == 1) {
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
data = USART3->DR; // 接收数据
USART1->DR = data; // 通过串口1回显
GetData4G.RX_DataBuf[GetData4G.DataBuf_Count++] = data; // 将串口接收的数据存放至数组
}
// 空闲中断
if(USART_GetITStatus(USART3,USART_IT_IDLE) == 1) {
// 清理中断标志
data = USART3->SR;
data = USART3->DR;
GetData4G.RXDataOK_Flag = 1; // 接收完毕模块的请求
}
}
2.3 4G模块设置
打开设置软件,选择相应的串口,设置波特率
点击有人云中的 ”网关管理“ — ”网关列表“ 选项卡,选择添加好的网关,点击”查看“
将”网关详情“中的网管信息添加到设置软件中
将设备编号(SN)和密码分别填置对应的位置,(图中我只是忘了填)
确定设置好串口工作后,将程序烧录置开发板中,随后点击图中指引的操作对4G模块进行配置
首先进入配置状态,当返回”执行完毕“后表示成功进入了配置状态 (图中的设备编号和密码我只是忘了填)
随后点击”设置并保存所有参数“,这时候软件会执行多条AT指令对4G模块进行设置,并且会出现进度条(图中的设备编号和密码我只是忘了填)
最后将设备重启,将软件中的打印格式显示为Hex,重启完成后等待1分钟左右,模块会下发数据收集指令(图中的设备编号和密码我只是忘了填)
3. Modbus通信协议 [重要]
此部分内容几乎完全照搬,大家可以直接去查看这个原文章连接,作者讲的很好我就不详细写了:https://blog.csdn.net/tiandiren111/article/details/118347661
Modbus是一种主从方式的通信协议,这意味着通信不能同步进行。总线上每次只能传输一个数据包,即由主机发送数据,从机进行应答。如果主机不发送数据,总线上将没有数据通信。
例如,当主机需要读取从机的温度数据时,主机首先发送一个读取温度的请求指令到总线上。从机接收到请求后,会回复温度数据给主机。整个过程中,总线上每次只有一个数据包在传输。
查看4G模块给我们发送过来的请求,图中画圈的部分就是Modbus格式的数据,先解释这一段数据:
这段数据的格式为:
帧结构 = 地址 + 功能码+ 数据 + 校验
- 地址: 占用一个字节,范围0-255,其中有效范围是1-247,其他有特殊用途,比如255是广播地址(广播地址就是应答所有地址,正常的需要两个设备的地址一样才能进行查询和回复)。
- 功能码:占用一个字节,功能码的意义就是,知道这个指令是干啥的,比如你可以查询从机的数据,也可以修改数据,所以不同功能码对应不同功能。
- 数据:根据功能码不同,有不同结构,在下面的实例中有说明。
- 校验:为了保证数据不错误,增加这个,然后再把前面的数据进行计算看数据是否一致,如果一致,就说明这帧数据是正确的,我再回复;如果不一样,说明你这个数据在传输的时候出了问题,数据不对的,所以就抛弃了
4. 相关协议和数据格式
4.1 模块回复内容
在2.3
步骤中可以看到模块下发的信息收集指令,这个指令的各个部分代表着什么呢?
可以查看相关的文档,通过文档提供的实例我们可以看到:
其中:
- 包头固定为 0xAA 0xFD 0x55
- 长度是指整个数据包中的有效数据的长度,从参数区(包含参数区)开始到和校验之前(不包含和校验)的所有字节数的总和,其实际数值相当于数据字节长度+2,即 n+2
- 常用参数区为 Socket 编码,采用 0x61 开始的编码进行表示,若序号为 0x61,代表数据发向 Socket A;若为 0x62,代表数据发向 Socket B 的连接对象,其他 Socket 编号依次类推,即 0x63、0x64 代表 Socket C、Socket D
- 这里的数据只是抽象为了0xCC,实际上这个数据为Modbus的格式,上文中已经对Modbus进行了解释
- CheckSum 校验和,从参数区(包含参数区)开始算起,到校验字节之前,加和取最后一个字节作为校验字节
4.2 如何发送数据[关键]
官方文档给我们提供的例子上有固定的格式:
因此我们只需要确定我们想要上传的数据即可
通过第三步了解Modbus协议后才可以很顺利的写出数据的上传格式,我这里给个例子进行演示:
目标是将有人云上的8个变量相应的设置为:
室内温度:32℃ ;室内湿度:56%
大气压:5120Pa ; 二氧化碳:652PPM ; 光照强度:152LUX
氮:4mg/kg ; 磷:6mg/kg ; 钾:13mg/kg
将这些数值分别转换为16进制的数:
32(十进制) — 0X20(十六进制)— 00 20
56(十进制) — 0X38(十六进制)— 00 38
剩下的数我就不举例子了…
将这些数据转换为十六进制后按照Modbus的形式进行拼接:
01 03 数据长度 00 20 00 38 14 00 02 8C 00 99 00 04 00 06 00 0D 校验位
数据长度是如何计算的: 需要上传的数据个数 + 2,本文章的例子中数据长度就是8 + 2 = 10
校验位是如何计算的: 将数据长度计算完成后,通过CRC算法进行计算,也可以通过点击在线CRC计算,将校验位计算出后,最后的数据也就是:(注意低位在左 高位在右)
01 03 10 00 20 00 38 14 00 02 8C 00 99 00 04 00 06 00 0D E6 C8
CRC在线计算链接::http://www.ip33.com/crc.html
最后别忘了加上有人云的数据格式,包头和校验位
55 FD AA 00 数据长度 61 00 01 03 10 00 20 00 38 14 00 02 8C 00 99 00 04 00 06 00 0D E6 C8 校验位
数据长度是如何计算的: 从数据长度这一位开始数,一直数到校验位,也就是23位
61 00 01 03 10 00 20 00 38 14 00 02 8C 00 99 00 04 00 06 00 0D E6 C8
一共是23位,23的十六进制表示为17,因此数据长度这一位应该填写17
校验位是如何计算的: 从61开始一直到校验位之前的所有值相加
61+00+01+03+10+00+20+00+38+14+00+02+8C+00+99+00+04+00+06+00+0D+E6+C8=3CD
发现计算出来是3位数,只需要保留后2位就行了,也就是3CD只需要保留CD这两位
最终发送的数据格式为:
55 FD AA 00 17 61 00 01 03 10 00 20 00 38 14 00 02 8C 00 99 00 04 00 06 00 0D E6 C8 CD
4.3 数据上报
将上一步手动计算出的数据帧通过串口1发送至串口3,实现数据主动上报
数据上报成功: 发送成功是不会有任何回复的,可以看到数据成功被上传至有人云
数据上报失败: 数据发送失败则会返回消息
4.4 通过代码上报
如果你明白了4.3的内容,这个代码对你来说是没什么问题的
注意: CRC校验函数省略,网上搜索一大堆
uint8_t tem = 32;
uint8_t hum = 56;
uint16_t atmos = 5120;
uint16_t co2 = 652;
uint8_t light = 153;
uint8_t n = 4;
uint8_t p = 6;
uint8_t k = 13;
/**
* @brief 将需要上传的数据进行拼接回复给4G模块
*/
void Wire4G_Anser03Data(void) {
// 初始化发送缓冲区
uint8_t Answer_buf[32] = {0x55, 0xFD, 0xAA, 0x00, 0x17, 0x61, 0x00, 0x01, 0x03, 0x10};
// 初始化计数器和数据帧长度
uint8_t buf_count = 10;
uint16_t CRC_send = 0;
// 将各个数据值拆分为高位和低位,并添加到发送缓冲区中
Answer_buf[buf_count++] = tem >> 8;
Answer_buf[buf_count++] = tem & 0XFF;
Answer_buf[buf_count++] = hum >> 8;
Answer_buf[buf_count++] = hum & 0XFF;
Answer_buf[buf_count++] = atmos >> 8;
Answer_buf[buf_count++] = atmos & 0XFF;
Answer_buf[buf_count++] = co2 >> 8;
Answer_buf[buf_count++] = co2 & 0XFF;
Answer_buf[buf_count++] = light >> 8;
Answer_buf[buf_count++] = light & 0XFF;
Answer_buf[buf_count++] = n >> 8;
Answer_buf[buf_count++] = n & 0XFF;
Answer_buf[buf_count++] = p >> 8;
Answer_buf[buf_count++] = p & 0XFF;
Answer_buf[buf_count++] = k >> 8;
Answer_buf[buf_count++] = k & 0XFF;
// 计算 CRC 校验值并添加到发送缓冲区中
uint16_t crc_ = ModBus_crc16_list(&Answer_buf[7], 19);
Answer_buf[buf_count++] = crc_ >> 8;
Answer_buf[buf_count++] = crc_ & 0XFF;
// 计算校验和并添加到发送缓冲区中
uint16_t sum = 0;
for(uint8_t i = 5; i < buf_count; i++) {
sum += Answer_buf[i];
}
Answer_buf[buf_count++] = sum & 0XFF;
// 通过串口3发送数据帧
Wire4G_SendStr(Answer_buf, buf_count);
}