基于STM32F103最小系统板和DL-LN33 2.4G通信 ZigBee无线串口自组网采集温湿度


前言

  DL-LN3X 系列模块是新晋推出的无线通信模块,该模块专为需要自动组网多跳传输的应用场合设计。相对于其他常见的自组网无线通信解决方案,本方案更加灵活、可靠,可长期稳定工作;用户可以抛开复杂的协议栈和芯片手册,只需要掌握简单的串口通讯便可驾驭无线多跳传输。

一、组网概述

  DL-LN3X 模块是一种自组网多跳无线通信模块。模块无线频率为 2.4GHz~2.45GHz,属于全球免费的无线频段。该模块工作时,会与周围的模块自动组成一个无线多跳网络,此网络为对等网络,不需要中心节点,网络包含以下可配置参数:
  将多个 DL-LN3X 模块配置成地址不相同,信道和网络 ID 相同的状态,模块将组成一个网络。微控制器(MCU)或者电脑通过 Uart 告诉模块目标地址和待发送的数据,模块会通过网络选择最优的路径,将信息传输给目标模块,而目标模块将通过Uart 输出源地址和上述的数据。

  DL-LN3X 模块使用定向扩散协议寻找路由,这种路由算法会记录网络的状态,每个节点平均可记录 190 个目标节点的路由,在网络建立后传输速度和传输延时可到达最优。但这种算法网络建立较慢,在节点刚刚启动时,网络需要 1~5 分钟的时间重新生成路由,在这段时间内网络使用洪泛路由进行数据通信,此时网络的传输速度较慢。

二、产品特性

  1. 定向扩散型自组网协议
     模块上电后会自动组成多跳网状网络,完全不需要用户干预。
     每个模块都可以给网络中任意一个节点发送数据。
     带有确认传输功能,无线传输使用 CRC 校验,最多重传 15 次。
     网络中任何节点故障不影响整个网络的运行,具有很强的抗毁性。
     最大可支持 190 个模块组成网络,模块地址可通过 Uart 进行修改。
     单个包长可达 63 字节,带有数据包缓冲机制。
  2. 用户接口简单易学
     使用 uart 作为交互接口。
     波特率可调。
     使用长度可变的包传输数据,使用安全的数据分包协议。
     支持端口分割机制。
  3. 程序工作稳定
     操作系统基于线程切片,工作稳定。
     使用内存池代替堆完成动态内存分配,长期工作不产生内存碎片。
  4. 带有指示灯
     模块带有收/发包指示灯。
     模块带有定位指示灯,可以远程点亮,方便寻找。

三、电气特性

参数
工作电压 / 电流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 不推荐的数据传输情况

使用当前节点网络,不推荐的传输情况有以下两种。

  1. 模块使用小于 80 的端口号作为源端口号,例如模块发送 FE 05 20 20 02 00 32 FF 则模块会收到一个端口号错误报告包:FE 06 22 20 02 00 E0 20 FF,实际上,模块不会传送任何数据,所以这样的传输是不推荐的。
  2. 模块给自己的某个端口传输数据。例如地址为 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、DHT11USB-TTL、STM32最小系统板
LN33 / DHT11-VCC3.3V
LN33 / DHT11-GNDGND
LN33-TXUSB-TTL:RX,PB11
LN33-RXUSB-TTL:TX,PB10
DHT11-DPB8

  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.测试结果

总结

  1. 网络ID和信道相同可以组成网络互相通信
  2. 两个网络信道相同,网络ID不同,则不能相互通信,如果两个网络距离较近,则两个网络共享信道,传输速率下降
  3. 两个网络信道不同,则不能相互通信、互不干扰
  4. 网络ID不可以是0x0000和0xFFFF
  5. 信道必须是0x0B到0x1A之间的一个
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值