文章目录
- 前言
- 一、组网概述
- 二、产品特性
- 三、电气特性
- 四、引脚配置
- 五、UART通信协议
- 5.1 UART参数
- 5.2 包分割
- 5.3 端口
- 5.4 举例通信
- 5.4.1 一个节点给另一个节点发送数据
- 5.4.2 一个节点给另一个节点的内部端口发送数据
- 5.4.3 一个节点给自己的内部端口发送数据
- 5.4.4 不推荐的数据传输情况
- 5.5 内部端口
- 5.5.1 红灯闪烁控制端口
- 5.5.2 基本信息管理端口
- 5.5.3 错误报告端口
- 六 参考示例
- 总结
前言
DL-LN3X 系列模块是新晋推出的无线通信模块,该模块专为需要自动组网多跳传输的应用场合设计。相对于其他常见的自组网无线通信解决方案,本方案更加灵活、可靠,可长期稳定工作;用户可以抛开复杂的协议栈和芯片手册,只需要掌握简单的串口通讯便可驾驭无线多跳传输。
一、组网概述
DL-LN3X 模块是一种自组网多跳无线通信模块。模块无线频率为 2.4GHz~2.45GHz,属于全球免费的无线频段。该模块工作时,会与周围的模块自动组成一个无线多跳网络,此网络为对等网络,不需要中心节点,网络包含以下可配置参数:
将多个 DL-LN3X 模块配置成地址不相同,信道和网络 ID 相同的状态,模块将组成一个网络。微控制器(MCU)或者电脑通过 Uart 告诉模块目标地址和待发送的数据,模块会通过网络选择最优的路径,将信息传输给目标模块,而目标模块将通过Uart 输出源地址和上述的数据。
DL-LN3X 模块使用定向扩散协议寻找路由,这种路由算法会记录网络的状态,每个节点平均可记录 190 个目标节点的路由,在网络建立后传输速度和传输延时可到达最优。但这种算法网络建立较慢,在节点刚刚启动时,网络需要 1~5 分钟的时间重新生成路由,在这段时间内网络使用洪泛路由进行数据通信,此时网络的传输速度较慢。
二、产品特性
- 定向扩散型自组网协议
模块上电后会自动组成多跳网状网络,完全不需要用户干预。
每个模块都可以给网络中任意一个节点发送数据。
带有确认传输功能,无线传输使用 CRC 校验,最多重传 15 次。
网络中任何节点故障不影响整个网络的运行,具有很强的抗毁性。
最大可支持 190 个模块组成网络,模块地址可通过 Uart 进行修改。
单个包长可达 63 字节,带有数据包缓冲机制。 - 用户接口简单易学
使用 uart 作为交互接口。
波特率可调。
使用长度可变的包传输数据,使用安全的数据分包协议。
支持端口分割机制。 - 程序工作稳定
操作系统基于线程切片,工作稳定。
使用内存池代替堆完成动态内存分配,长期工作不产生内存碎片。 - 带有指示灯
模块带有收/发包指示灯。
模块带有定位指示灯,可以远程点亮,方便寻找。
三、电气特性
参数 | 值 |
---|---|
工作电压 / 电流 | 2.5~3.6V / 小于30mA |
无线发送功率 | 4.5dBm |
工作频率 | 2400~2450MHz |
传输速率 | 因为发送包的路由信息会占用一定的带宽,每个包的长度越长,发送效率越高。每个包包含 3Byte 数据时,2400Bit/s。每个包包含 30Byte 数据时,10KBit/s。 |
天线接口 | 板载 PCB 天线 |
工作信道 | 符合 IEEE802.15.4 协议的 16 个信道划分 |
通信接口 | UART 通信(支持 8 种波特率:2400/4800/9600/14400/19200/28800/38400/57600/115200/230400/125000/250000/500000) |
接收灵敏度 | -97dBm |
组网最大跳数 / 节点数 / 包长度 | 15 跳 / 典型值为 190 个点 / 63Byte |
丢包重传次数 | 最多 15 次,网络负载高时,最少 5 次。 |
空中速率 / 延时 | 250KBit/s / 节点间单挑,小于 10ms。 |
工作温度 | -40°C~85°C |
模块尺寸 | 18*23.5mm |
传输距离 | 70 米(空旷无遮挡) |
四、引脚配置
五、UART通信协议
5.1 UART参数
DL-LN3X 模块使用 Uart 接口作为数据交互接口,接口的参数如下:
数据位 8 位
起始位 1 位
停止位 1 位
校验位 无校验
Uart 接口的波特率可以被用户设置为以下值:UART
2400 4800 9600 14400 19200 28800 38400 57600 115200 230400 125000 250000 500000
几乎任何单片机的 Uart 输出都可以和 DL-LN3X 模块的 UART 进行通讯,电脑串口则可以使用 MAX3232 芯片转换为 UART 与 DL-LN3X 进行通信。
5.2 包分割
在通信过程中,最常见的场合是单片机通过UART告诉模块这样的信息:
“将数据 00 AE 13 33 发往地址为 0003 的模块,目标端口为 90,源端口为 91。”
对于单片机,需要将这些信息整理成一个包,通过UART发给模块:FE 08 91 90 03 00 00 AE 13 33 FF
此包的说明如下表:
远程地址长度为 2byte,使用小端模式进行传输,即先传输低 8 位,再传输高 8 位。
传输过程中如果遇到数据部分、地址或者端口号中出现 FF,则使用 FE FD 来代替;如果出现 FE,则用 FE FC 来代替。以免传输过程中出现的包头和包尾,使接收方误判断。在传输中这种替换称为“转义”。
包长度不会受到转义的影响,例如发送的数据为 09 FF 时,替换为 09 FE FD,但包头中的数据长度仍然按照 2+4来计算,这样,发送的包如下:FE 06 91 90 03 00 09 FE FD FF
虽然一共传输了 7 个字节,但包长为 6。如果地址、端口号中出现了 FF、FE 也需要进行转义。
5.3 端口
DL-LN3X 模块设计了端口的概念,接收方收到一个包时,会根据包的端口号,选择对应的程序处理包。端口号的取值范围是0x00 ~ 0xFF,其中 0x00 ~ 0x7F端口由模块内部程序占用,用于调试设计, 0x80~0xFF 端口开放给 UART 连接的 MCU 或者电脑。
当 MCU 给一个模块发送数据时,如果源端口号填写了小于 0x80 的值,则包无法发出;如果目的端口号填写了小于 0x80 的值,接收方模块的内部程序将处理这个包并执行相关的动作,而不是从 UART 发出这个包。
例如发送这个包:FE 05 91 20 03 00 0A FF
则会让地址为 03 00 的模块自带的红灯点亮 1 秒,而他的 UART 不会输出数据。
5.4 举例通信
5.4.1 一个节点给另一个节点发送数据
例如将多个节点组成如下网络,在本文中节点特指 PC 或 MCU 和 DL-LN3X 模块组成的硬件设备。MCU 采集到温湿度为温度 23℃,湿度 60%,则无线传输的数据是 0x17,0x3C。节点和电脑都使用 A0 端口传输温度,A1 端口传输湿度,MCU 已知连接电脑的模块地址为 0x000F,则 MCU 发给模块的数据为:
FE 05 A0 A0 0F 00 17 FF FE 05 A1 A1 0F 00 3C FF
则电脑串口收到的数据为:
FE 05 A0 A0 01 00 17 FF FE 05 A1 A1 01 00 3C FF
电脑串口收到的数据中远程地址被替换为了源节点的地址。
5.4.2 一个节点给另一个节点的内部端口发送数据
寻找当前网络地址节点,如寻找地址为 0x0002 的节点时,PC 命令此模块的红灯点亮 5 秒,则 PC 发送:
FE 05 A3 20 02 00 32 FF
可以看到地址为 0x0002 的模块红灯点亮 5 秒。
5.4.3 一个节点给自己的内部端口发送数据
模块可以给自己的端口发送数据。详细说明看后面介绍。
5.4.4 不推荐的数据传输情况
使用当前节点网络,不推荐的传输情况有以下两种。
- 模块使用小于 80 的端口号作为源端口号,例如模块发送 FE 05 20 20 02 00 32 FF 则模块会收到一个端口号错误报告包:FE 06 22 20 02 00 E0 20 FF,实际上,模块不会传送任何数据,所以这样的传输是不推荐的。
- 模块给自己的某个端口传输数据。例如地址为 0x000F 的节点,传输数据给自己的 80 端口,模块发送FE 05 81 80 0F 00 32 FF,则自己会收到 FE 05 81 80 0F 00 32 FF,节点的单片机自己给自己传输了一条数据,这显然是不必要的,所以这样的传输是不推荐的。
5.5 内部端口
模块内部已经规定的端口,包括这些端口可以接受的包,以及这些这些端口会发出的包。在对包进行说明时,仅对数据部分进行说明。例如:
此包是一个端口 21 可以接受的包,则实际通过UART 发出的数据是:FE 07 91 21 00 00 12 98 88 FF,其中 91 可以是任意端口号,00 00 是目标地址,12 为命令,98 99 为新网络 ID。
5.5.1 红灯闪烁控制端口
端口 0x20 用于控制模块的红色 LED 点亮,发送此包可以使模块的红色 LED 点亮一定时间。发出数据是: FE 05 80 20 00 00 01 FF,其中 20 是控制LED的端口号,00 00 是目标地址,01 为点亮时间。
此端口可接收以下包:
发送这个数据给此模块可以点亮红色 LED,用户既可以给本地模块发送这个包,也可以给远程模块发送这个包。
这一功能用于测试一个指定地址的模块是否包含在网络中,如果想从许多节点中迅速找到某个特定地址的节点,也可以使用此功能。
5.5.2 基本信息管理端口
端口 0x21 用于配置模块的基本参数,包括 地址,网络 ID,信道和波特率。此端口只接受远程地址填写 0x0000 的包,因此,这些信息的读取和修改只能通过本模块的 UART进行,不能远程操作。
如果不知道模块配置的波特率,可以将 BaudReset 引脚连接到 GND,这样便可以使用 115200 波特率对模块进
行配置。
注意: 只有使用 0x0000 作为目标地址才能与 21 端口进行通信,0x0000 即模块的本地地址。
向模块发送配置命令后,模块会返回 FE 05 21 90 00 00 00 FF,表示配置完成,返回信息会指示发包的错误,错误信息详见响应包 。
最后如果配置信息确认无误,向模块发送 FE 05 90 21 00 00 10 FF,模块会进行重启,然后使用新的参数进行工作。
5.5.3 错误报告端口
端口 0x22 用于报告通信错误,用户不能向这个端口发送数据,当用户发送数据使用不合法的地址时,这个端口会发送错误报告包:
当用户发送源地址小于 0x80 的包时,将会收到来自这个端口的错误报告
六 参考示例
1.准备工作:两个以上DL-LN33组网模块(一主多从),两个个USB转TTL,一个DHT11温湿度传感器、STM32F103C8T6最小系统板。
2.引脚接线
DL-LN33、DHT11 | USB-TTL、STM32最小系统板 |
---|---|
LN33 / DHT11-VCC | 3.3V |
LN33 / DHT11-GND | GND |
LN33-TX | USB-TTL:RX,PB11 |
LN33-RX | USB-TTL:TX,PB10 |
DHT11-D | PB8 |
3.两个USB转TTL分别连接两个组网模块,通过拓扑软件观察组网情况和收发包数据,(红色节点为主节点)
注: 组网测试拓扑,1. 拓扑软件会占用网络资源传输组网信息,会让网络传输速度变慢,适合用于检测网络连接情况,不适合长期使用;2. 拓扑软件会自动避让正常通信,让通信先进行,在网络较大时会出现拓扑获取失败,但不意味网络出现异常
4.获取主节点地址(接USB转TTL为主节点,接最小系统板为从节点)
5.参考例程
/** main.c **/
#include "led.h"
#include "lee.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "dht11.h"
#include "package.h"
#include "DL_LN3X.h"
#include "usart3.h"
#include "string.h"
#include <stdio.h>
//温湿度采集实验
void recievePkg(sPkg* pkg);
void loopReceive(void);
void loopAll(void);
void initAll(void);
extern void uartRevieveByte(u8 data);
u8 temperature;
u8 humidity;
//newPkg(num)是一个宏,这个宏展开后是一个包结构体,包的数据部分长度是num
//使用下面的语句可以在RAM中生成一个带有3个数据的包
//newPkg(3) redPkg={
// .length = 7,
// .src_port = 0x90,
// .dis_port = 0x32,
// .remote_addr = 0x0000,
// .data = {0,10,20}
//};
newPkg(3) redPkg={7,0x90,0x32,0x00,0x00,{0,10,20}};
char buf1[7]={0x05,0x90,0x21,0x00,0x00,0x01,0xFF};//读取模块的地址指令,注意最前面的0xFE不加到数组里面,因为sendPkg函数里面自动会发0xfe
char buf2[7]={0x05,0x90,0x21,0x00,0x00,0x02,0xFF};//读取模块的网络ID指令
char buf3[7]={0x05,0x90,0x21,0x00,0x00,0x03,0xFF};//读取模块的信道指令
char buf4[7]={0x05,0x90,0x21,0x00,0x00,0x04,0xFF};//读取模块的波特率指令
newPkg(1) THPkg={5,0x90,0x32,0xCB,0x36,{0}};//注意包格式,0xCB,0x36为主节点地址
void loopAll()
{
u16 i;
u8 reclen=0;
u8 m=0;
while (1)
{
DHT11_Read_Data(&temperature,&humidity);//读取温湿度值
THPkg.dis_port = 0xa0;
THPkg.data[0] = temperature;
sendPkg((sPkg*)(&THPkg));
for(i = 0;i<100;i++)
{
delay_ms(10);
loopReceive();
}
THPkg.dis_port = 0xa1;
THPkg.data[0] = humidity;
sendPkg((sPkg*)(&THPkg));
if(USART3_RX_STA&0X8000) //接收到一次数据了
{
reclen=USART3_RX_STA&0X7FFF; //得到数据长度
for(i=0;i<reclen;i++)
uartRevieveByte(USART3_RX_BUF[i]);
USART3_RX_STA=0;
}
for(i = 0;i<100;i++)
{
delay_ms(10);
loopReceive();
}
}
}
//这个函数需要在工作中不断被调用,它会尝试一次接收包,
//如果接收成功就交给recievePkg处理,并再次尝试,直到收不到新的包
void loopReceive(void)
{
sPkg* pkg;
pkg = getNextPkg();
while(pkg != NULL)
{
recievePkg(pkg);
pkg = getNextPkg();
}
}
//收到一个包后会调用这个函数,这个函数根据包的目的端口选择相应的程序进行处理
void recievePkg(sPkg* pkg)
{
//printf((char*) pkg);//通过串口1发送给电脑,用于测试
switch(pkg->dis_port)
{
case 0xb0:
if(pkg->data[0] == 0x01)
{
// greenTog();
LED0=!LED0;//闪烁LED,提示系统正在运行.
}
break;
default:
break;
}
}
void initAll()
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
uart_init(115200); //串口1初始化为9600
usart3_init(115200);//串口3初始化为9600
LED_Init(); //初始化与LED连接的硬件接口
}
int main(void)
{
initAll();
loopAll();
}
/** DL_LN3X.c **/
#include "lee.h"
#include "led.h"
#include "package.h"
#include "usart.h"
#include "sys.h"
//接收包使用的结构体,将端口和地址都融入了data中
typedef struct sPkgBase__
{
u8 length;
u8 data[63];
}sPkgBase;
//收到包头函数
static void recvHead(u8 totle);
//收到包尾0xff
static void recvTerminal(void);
//收到数据函数
static void recvData(u8 data);
//这个标志在上一个收到了0xfe时赋值为yes,否则为0
static u8 escape = no;
//串口收到1byte数据时,中断中调用此函数
void uartRevieveByte(u8 data)
{
switch(data)
{
case 0xff://收到结束符
recvTerminal();
break;
case 0xfe://收到转义字符
escape = yes;
break;
default://收到一般数据
if(escape == yes)
{//如果前一个是转义字符
escape = no;
if(data <=63)
{
recvHead(data);
}
else
{//收到转义数据的规律是0xfe后面的数据+2还原数据
recvData(data+2);
}
}
else
{//前一个不是转义字符,直接收到一个数据
recvData(data);
}
break;
}
}
//接收包使用的双缓冲,其中一个供程序使用,另一个用来装入下一个包.
static sPkgBase recv_temp[2];
//确定双缓冲中哪个用于接收,哪个程序分析的变量
static volatile u8 Loading = 0;
//用于接收装入数据的缓冲
#define Load_pkg recv_temp[Loading]
//用于程序分析的缓冲
#define User_pkg recv_temp[(Loading+1)&1]
//接收计数器,表示一个包收到了多少个数据
#define RS_IDLE 0XFF//表示包还没有收到包头和长度
#define RS_DONE 0xA0//表示包已经完成了接收
static volatile u8 Recv_counter = RS_IDLE;
//如果User_pkg装有一个未处理的包,此变量为 yes
static volatile u8 Received = no;
//如果用户正在分析User_pkg中的数据此变量为 yes
static volatile u8 Locked = no;
//调用这个函数说明上一个包已经处理完了,并尝试接收一个包,
//如果已经收到包,则返回一个指向包的指针,否则返回null
sPkg* getNextPkg(void)
{
// cli();
sPkg* rev;
Locked = no;
if(Received == yes)
{//两个缓冲区交换过,receive就是yes进入这里
Received =no;
Locked = yes;
rev = (sPkg*)(&User_pkg);
}
else
{//还没有交换,那么是否应该交换呢?
if(Recv_counter == RS_DONE)
{//如果另一个缓冲已经收完了就应该交换
Loading++;
Loading&=1;
Recv_counter = RS_IDLE;
Locked = yes;
rev = (sPkg*)(&User_pkg);
}
else
{//否则返回空,说明没有包待处理
rev = NULL;
}
}
//sei();
return rev;
}
static void recvHead(u8 totle)
{
if(Recv_counter == RS_IDLE)
{
Load_pkg.length = totle;
Recv_counter = 0;
}
}
static void recvData(u8 data)
{
if(Recv_counter < Load_pkg.length)
{
Load_pkg.data[Recv_counter] = data;
Recv_counter++;
}
else
{
Recv_counter = RS_IDLE;
}
}
static void recvTerminal(void)
{
if(Recv_counter == Load_pkg.length)
{//收到完成的包
if(Locked == no)
{//调换缓冲区
Loading++;
Loading&=1;
Received = yes;
Recv_counter = RS_IDLE;
}
else
{//不调换缓冲区
Recv_counter = RS_DONE;
}
}
else
{ //收到不完成的包,需要重新收
Recv_counter = RS_IDLE;
}
}
6.测试结果
总结
- 网络ID和信道相同可以组成网络互相通信
- 两个网络信道相同,网络ID不同,则不能相互通信,如果两个网络距离较近,则两个网络共享信道,传输速率下降
- 两个网络信道不同,则不能相互通信、互不干扰
- 网络ID不可以是0x0000和0xFFFF
- 信道必须是0x0B到0x1A之间的一个