1 深入了解LoRaWan
1.1 LoRaWan概述
LoRaWAN采用星型无线拓扑
End Nodes 节点
Gateway 网关
Network Server 网络服务器
Application Server 应用服务器
LoRa联盟是2015年3月Semtech牵头成立的一个开放的、非盈利的组织,发起成员还有法国Actility,中国AUGTEK和荷兰皇家电信kpn等企业。至2016年4月,联盟已经发展成员公司290余家,其中不乏IBM、思科、法国Orange等重量级产商
1.2 LoRaWan通信协议
128AES加密功能
节点与server之间的加密通信:
HAL驱动SPI驱动物理层phy,物理层通过lora或者fsk协议再与网关通信,网关可以通过spi或usb与网关模块通信
网关也可以通过3Gwifi与我们网络服务器通讯
1.3 LoRaWAN与其他组网协议对比
zigbee属于mesh
1.4 LoRawan终端
1.4.1 LoRaWAN网关SX1301
拓扑图
大容量的网络规模高速度的通信机制
8通道Lora
IF9 1个FSk
IF8 1个网关间通信
非常适合做网关 200元左右,需要Arm9高速处理器,我们只需要了解,我们学习节点
1.4.2 LoRaWAN终端Class A
LoRaWAN Server选择最佳Gateway下行通信,都是通过LoRaServer选择,
开启两个接收窗口,平时处于休眠模式,当他需要工作的时候才会去发送数据包,功耗最低,实时性不高,比如1小时才能发送1次信息,控制不太合适,采集信息用classA最合适
1.4.3 LoRaWAN终端Class B
解决classA实时性不高(当需要节点去响应实时性问题的时候,首先网关会发送一个信标,告诉节点要加快通讯,快速工作,节点收到信标之后,会在128秒内去打开多个事件窗口,每个窗口在3-160ms,在128秒内可以实时对节点进行监控)
1.4.4 LoRaWAN终端Class C
既保持实时性,也保证了数据收发,一直会打开接收窗口,缺点能耗高
1.5 LoRawan服务器
1.5.1 LoRaWAN服务器框架
与ABCclass建立了Star通讯
网络服务器与网关建立通讯
控制网络服务器协议算法是通过控制服务器做得,服务器如何决定通过那个网关进行通讯
应用服务器根据行业需求,布置不同应用,使用接口比较单一
客户服务器 二次开发,人机交互等在此开发
1.5.2 不同server的通信
tcp udp
客户服务网、应用服务器、网络服务器、控制服务器都是通过TCP进行通讯,可靠性
网关、节点和网络服务器是通过UDP,保证实时性
1.5.3 LoRaWAN服务器通信协议
TCP、UDP、网关通过JSON字符串进行交互的
思考 如何设计Class C终端进行私有组网
1.只能联网内才开发loraWan
2.loraWan网关成本高
3.要具备Sever开发能力
2 LoRa自组网络架构设计
2.1 MAC协议的重要性
类似交通信号灯,无线信道只有1个,不设计会产生冲突
解决信号冲突的问题
尽可能地节省电能
保证通信的健壮和稳定性
2.2 MAC协议种类
设计要基于3种协议
2.3 时分复用
用时间片的思想,多任务。(在一定的事件内去分配时间槽,每个时间槽分给一个节点,使节点在这个时间槽里通信,如果不在这个时间槽是不能通信的。和电脑CPU的时间片是一个道理)
时分多路复用是将时间划分为一段段等长的时分复用帧(TDM帧),每个用户在每个TDM帧中占用固定序号的时隙。
每个用户所占用的时隙是周期性出现(其周期就是TDM帧的长度)。
时分复用的所有用户是在不同的时间占用相同的频带宽度。
2.4 频分复用
1301 芯片
频分多路复用的个用户占用不同的带宽资源(这里的“带宽”是频率带宽(单位:Hz)而不是数据的发送速率)。
用户在分配到一定的频带后,在通信过程中自始至终都占用这个频道。
应用:有线电视网络。
2.5 码分复用
(CPU是多核,多任务同时进行:不同频率的通信可以同时进行)
SF扩频,SF不同,进行不同的通信
LoRa 中的码分复用通过以下方式实现:
-
唯一的扩频因子(Spreading Factor): 在 LoRa 中,每个终端设备使用唯一的扩频因子,这个扩频因子决定了数据信号的频带扩展程度。不同的扩频因子对应着不同的码片序列。每个终端设备在发送数据时,使用其唯一的扩频因子进行调制,因此即使在相同的频率上,不同的终端设备也可以同时发送数据而不会相互干扰。
-
自适应数据速率(Adaptive Data Rate,ADR): LoRa 网络可以根据终端设备的距离和环境条件动态调整扩频因子和发送功率,以最大程度地提高通信的可靠性和覆盖范围。
-
碰撞避免技术: LoRa 中还采用了碰撞避免技术,通过随机选取发送时间和采用随机退避算法来减少终端设备之间的碰撞,进一步提高了网络的性能。
总的来说,LoRa 中的码分复用技术使得多个终端设备可以在同一时间和频率上进行通信,从而实现了低功耗、远距离和大规模连接的物联网应用场景。
2.6 轮询访问
modbus只有一个主机,节点可以是1-247,只允许主机发送,从机应答。实时性差
2.7 我们的设计
时间 随机访问,竞争入网
信号(协调器)
节点1 发送收到
节点2 先判断网络是否冲突,再延时发送 接收
节点3 先判断网络是否冲突,再延时发送 接收
时间槽分配、或者时间片,在规定时间内进行收发rx、tx,从而实现整个网络的通信
同时设置冗余、revered slot和空闲任务used idle
2.7.1 LoRa自组网协调器设计
串口:我们需要牧场监控内的信息,控制器要与协调器进行数据通信
无线数据:入网没完成等待,是否有新节点加入,断电是否有旧节点加入
解决时钟飘移缺点,时钟同步
2.7.2 LoRa自组网节点设计
思考 基于时分复用MAC协议如何开发协调器与节点程序
3 LoRa自组网集中器程序开发(网关)
3.1 制定通信协议
入网请求
从机->入网请求 | |||
名称 | 字节数 | 描述 | 举例 |
帧头 | 1 | 0x3C | 0x3C |
长度 | 1 | 最长126字节;长度范围内不检测包头;计算整个帧长度 | 0x0c |
网络类型 | 1 | 字符<J>代表入网请求 | J |
网络标识符 | 2 | PANID,用于网络区分,只有PANID一样才可以组网通信 | 0x0102 |
设备地址 | 2 | 设备唯一地址标识 | 0x1235 |
CRC8校验 | 1 | 数据包校验,整个数据包,除校验位 | 0x08 |
主机->入网应答 | |||
名称 | 字节数 | 描述 | 举例 |
帧头 | 1 | 0x3C | 0x3C |
长度 | 1 | 最长126字节;长度范围内不检测包头;计算整个帧长度 | 0x0c |
网络类型 | 1 | 字符<A>代表入网成功 | A |
网络标识符 | 2 | PANID,用于网络区分,只有PANID一样才可以组网通信 | 0x0102 |
设备地址 | 2 | 设备唯一地址标识 | 0x1235 |
设备序号 | 1 | 设备是第几个入网 | 1 |
CRC8校验 | 1 | 数据包校验,整个数据包,除校验位 | 0x08 |
设备标识符 PANID和zigbee组网一样,相同才能在一个网通信
时间同步
设备序号:第几个入网的,同时给节点分时间片
网络数据包
网络数据包 | ||||
名称 | 字节数 | 描述 | 举例 | |
帧头 | 1 | 字符<N>代表网络数据包 | N | |
网络标识符 | 2 | PANID,用于网络区分,只有PANID一样才可以组网通信 | 0x0102 | |
数据包 | 包头 | 1 | 0x21 | 0x21 |
包长 | 1 | 数据包长度,代表数据域数据长度 | 1 | |
数据类型 | 1 | 0x00:数据、0x01:命令 | 0x00 | |
设备地址 | 2 | 设备标识符 | 0x0001 | |
传感器类型 | 1 | 0x01:温湿度、0x02:三轴、0x03:风机、0x04:水表、0x05:地磁 0x06:灌溉 | 0x01 | |
数据 | 4 | 每种传感器数值用一个字节标识,比如温湿度占两个字节 | 0x01298113 | |
CRC8校验 | 1 | 数据包校验,整个数据包,除校验位 | 0x08 |
3.2 工程模板修改
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7|GPIO_PIN_15, GPIO_PIN_SET);
3.2.1 Cubmx RTC外设配置
修改RTC时钟源为外部高速时钟
配置RTC分频系数
初始化日期和时间
配置Alarm参数
使能RTC全局中断
1s为单位.rtc时钟为250hz
3.2.2 Cubmx 定时器外设配置
配置TIM2分频系数
使能TIM2定时器中断
1s为单位
3.2.3 Cubmx 串口和ADC外设配置
把main函数的功能移到task文件夹下,数据解析、网络解析、协议等
如果是从机增加一个ADC,用于生成随机数
配置ADC为连续采集
配置DMA通道
配置ADC标签
3.2.4 RTC任务
主要任务:RTC 提供实时时钟,用于时钟同步,开启闹钟中断
sTime的初值和sAlarm的初值都为0
void MX_RTC_Init(void)
{
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
RTC_AlarmTypeDef sAlarm;
/**Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 125-1;
hrtc.Init.SynchPrediv = 2000-1;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Initialize RTC and set the Time and Date
*/
sTime.Hours = startUpDateHours; // <--------
sTime.Minutes = startUpDateMinute; // <--------
sTime.Seconds = startUpDateSeconds; // <--------
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sDate.WeekDay = RTC_WEEKDAY_MONDAY;
sDate.Month = RTC_MONTH_APRIL;
sDate.Date = 0x1;
sDate.Year = 0x18;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x32F2){
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2);
}
/**Enable the Alarm A
*/
sAlarm.AlarmTime.Hours = DataUpTimeHours; // <--------
sAlarm.AlarmTime.Minutes = DataUpTimeMinute; // <--------
sAlarm.AlarmTime.Seconds = DataUpTimeSeconds; // <--------
sAlarm.AlarmTime.SubSeconds = DataUpTimeSubSeconds; // <--------
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
sAlarm.AlarmDateWeekDay = 0x1;
sAlarm.Alarm = RTC_ALARM_A;
memcpy(&gAlarm, &sAlarm, sizeof(sAlarm));
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) //设置闹钟中断
{
_Error_Handler(__FILE__, __LINE__);
}
}
闹钟事件回调函数,触发事件用于处理任务
协调器(主机):
同步时钟标志(因为每加入的设备都需要同步时钟)
获取时间,设置下次闹钟时间+5小时
节点(从机):
发送更新数据标志
闹钟发送数据的时间赋值,使能闹钟中断
毫秒单位转换为时分秒
//**********************************//
//函数名称:HAL_RTC_AlarmAEventCallback
//函数描述: 闹钟事件回调函数
//函数参数: RTC_HandleTypeDef *hrtc
//返回值: 无
//*******************************//
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
RTC_TimeTypeDef masterTime;
RTC_TimeTypeDef SlaveTime;
RTC_DateTypeDef masterDate;
#if MASTER
//置位同步时钟标志
SendClockFlag = 0;
//获取下次闹钟时间
HAL_RTC_GetTime(hrtc, &masterTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(hrtc, &masterDate, RTC_FORMAT_BIN);
gAlarm.AlarmTime.Hours = masterTime.Hours + CLOCKHOURS; //+5hour
gAlarm.AlarmTime.Minutes = masterTime.Minutes;
gAlarm.AlarmTime.Seconds = masterTime.Seconds;
gAlarm.AlarmTime.SubSeconds = masterTime.SubSeconds;
#else //SLAVER
sendUpDataFlag = 1;
HAL_RTC_GetTime(hrtc, &SlaveTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(hrtc, &masterDate, RTC_FORMAT_BIN);
gAlarm.AlarmTime.Hours = SlaveTime.Hours + DataUpTimeHours;
gAlarm.AlarmTime.Minutes = SlaveTime.Minutes + DataUpTimeMinute;
gAlarm.AlarmTime.Seconds = SlaveTime.Seconds + DataUpTimeSeconds;
gAlarm.AlarmTime.SubSeconds = SlaveTime.SubSeconds + DataUpTimeSubSeconds;
#endif
if (gAlarm.AlarmTime.Seconds > 59)
{
gAlarm.AlarmTime.Seconds -= 60;
gAlarm.AlarmTime.Minutes += 1;
}
if ( gAlarm.AlarmTime.Minutes >59)
{
gAlarm.AlarmTime.Minutes -= 60;
gAlarm.AlarmTime.Hours += 1;
}
if (gAlarm.AlarmTime.Hours > 23)
{
gAlarm.AlarmTime.Hours -= 24;
}
printf("RTC\n");
//使能闹钟中断
if (HAL_RTC_SetAlarm_IT(hrtc, &gAlarm, RTC_FORMAT_BIN) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
时分秒转换函数
//**********************************//
//函数名称: GetTimeHMS
//函数描述: 时分秒转换
//函数参数: uint32_t timeData,uint8_t *hours,uint8_t *minute,uint8_t *seconds,uint32_t *subSeconds
//返回值: 无
//*******************************//
void GetTimeHMS(uint32_t timeData,uint8_t *hours,uint8_t *minute,uint8_t *seconds,uint32_t *subSeconds)
{
/* 获得亚秒 */
*subSeconds = timeData % 1000;
/* 获得秒钟*/
timeData = timeData / 1000;
*seconds = timeData % 60;
/* 获得分钟*/
timeData = timeData / 60;
*minute = timeData % 60;
/* 获得小时 */
*hours = timeData / 60;
}
3.2.5 定时器任务
定时器 用来节点超时的判断
定时器初始化
开启定时器中断
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 480-1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 100*1000-1;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
定时器定时事件任务
定时器中断溢出,要在里面做一些处理,判断节点是否入网超时
//**********************************//
//函数名称: HAL_TIM_PeriodElapsedCallback
//函数描述: 定时器2溢出中断回调函数
//函数参数: TIM_HandleTypeDef *htim
//返回值: 无
//*******************************//
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//判断是否为定时器2中断
//累加全局计数值
if(htim->Instance == htim2.Instance)
{
JionNodeTimeCount++;
}
}
3.2.5 CRC校验码及通信协议宏定义
protocol.c
1生成crc8校验码
2判断crc8校验码是否正确
#include "protocol.h"
/******************************************************************************
* Name: CRC-8 x8+x2+x+1
* Poly: 0x07
* Init: 0x00
* Refin: False
* Refout: False
* Xorout: 0x00
* Note:
*****************************************************************************/
uint8_t crc8(uint8_t *data, uint8_t length)
{
uint8_t i;
uint8_t crc = 0; // Initial value
while(length--)
{
crc ^= *data++; // crc ^= *data; data++;
for ( i = 0; i < 8; i++ )
{
if ( crc & 0x80 )
crc = (crc << 1) ^ 0x07;
else
crc <<= 1;
}
}
return crc;
}
//**********************************//
//函数名称: DataCrcVerify
//函数描述: CRC8校验
//函数参数: uint8_t * buff, uint8_t len
//返回值: uint8_t
//*******************************//
uint8_t DataCrcVerify(uint8_t * buff, uint8_t len)
{
uint8_t Crc8Data = 0;
//验证数据是否正确
Crc8Data = crc8(buff, len - 1);
if (Crc8Data == buff[len - 1])
{
// PRINTF1("CRC8 Success!\n");
return 1;
}
else
{
// PRINTF1("CRC8 Failed!\n");
return 0;
}
}
#ifndef _PROTOCOL_H
#define _PROTOCOL_H
#include "stm32f0xx.h"
#define JIONREQUEST 0x3C
#define NETDATA 'N'
#define DATAHEAD 0x21
#define PAN_ID 0x1010
#ifdef MASTER
#define ADDR 0xFFFF
#else
#define ADDR 0x1201 //0x1202 0x1203 ...
#endif
#define HI_UINT16(a) (((a) >> 8) & 0xFF)
#define LO_UINT16(a) ((a) & 0xFF)
uint8_t DataCrcVerify(uint8_t * buff, uint8_t len);
uint8_t crc8(uint8_t *data, uint8_t length);
#endif
3.2.6 数据处理任务
dataprocess.c
主机
从机
3.2.6.1 串口数据获取并无线发出去
//**********************************//
//函数名称:UartDmaGet
//函数描述:串口数据获取
//函数参数: 无
//返回值: 无
//*******************************//
void UartDmaGet(void)
{
if(UsartType1.receive_flag == 1)//如果过新的数据,在串口中断回调函数中实现
{
//串口接收到的数据原封发给SX1278
Radio->SetTxPacket(UsartType1.usartDMA_rxBuf, UsartType1.Usart_rx_len);
memset(UsartType1.usartDMA_rxBuf,0,UsartType1.Usart_rx_len);
UsartType1.receive_flag = 0; //接收数据标志清零,
}
}
接收数据包计数、发送数据包计数
//**********************************//
//函数名称: RxDataPacketNum
//函数描述: 接收数据包计数
//函数参数: 无
//返回值: 无
//*******************************//
void RxDataPacketNum(void)
{
if(EnableMaster == true)
Master_RxNumber++;
else
Slave_RxNumber++;
}
//**********************************//
//函数名称: TxDataPacketNum
//函数描述: 发送数据包计数
//函数参数: 无
//返回值: 无
//*******************************//
void TxDataPacketNum(void)
{
if(EnableMaster == true)
Master_TxNumber++;
else
Slave_TxNumber++;
}
3.2.6.2 读取无线射频数据
//**********************************//
//函数名称: Sx127xDataGet
//函数描述: 读取sx127x射频射频数据
//函数参数: 无
//返回值: 无
//*******************************//
uint8_t Sx127xDataGet(void)
{
uint8_t status = 0;
switch( Radio->Process( ) )
{
case RF_RX_TIMEOUT: //超时
printf("RF_RX_TIMEOUT\n");
break;
case RF_RX_DONE: //接收完成 主机和从机完成解析
Radio->GetRxPacket( Buffer, ( uint16_t* )&BufferSize );
if(EnableMaster == true)
printf("master Rx Len = %d\n",BufferSize);
else
printf("slave Rx Len = %d\n",BufferSize);
if( BufferSize > 0 )//&& (BufferSize == strlen((char*)Buffer)))
{
//接收数据闪烁
LedBlink( LED_RX );
//计算接收数据的个数
RxDataPacketNum();
//清空sx127x接收缓冲区
#ifdef MASTER
status = MasterProtocolAnalysis(Buffer,BufferSize); //主机协议解析
#else
status = SlaveProtocolAnalysis(Buffer, BufferSize); //从机协议解析
#endif
memset(Buffer,0,BufferSize);
}
break;
case RF_TX_DONE: //发送完成,更新发送标志位
//发送闪烁
LedBlink( LED_TX );
//计算发送数据的个数
TxDataPacketNum();
Radio->StartRx( );
SendDataOkFlag = 1;
break;
case RF_TX_TIMEOUT: //发送超时
printf("RF_TX_TIMEOUT\n");
break;
default:
break;
}
return status;
}
根据radio.h process的不同枚举类型进行业务处理
typedef enum
{
RF_IDLE, //空闲
RF_BUSY,
RF_RX_DONE,
RF_RX_TIMEOUT,
RF_TX_DONE,
RF_TX_TIMEOUT,
RF_LEN_ERROR,
RF_CHANNEL_EMPTY,
RF_CHANNEL_ACTIVITY_DETECTED,
}tRFProcessReturnCodes;
3.2.6.3 主机协议解析
//**********************************//
//函数名称: MasterProtocolAnalysis
//函数描述: 主机协议解析
//函数参数: uint8_t *buff,uint8_t len
//返回值: uint8_t
//*******************************//
uint8_t MasterProtocolAnalysis(uint8_t *buff,uint8_t len)
{
uint8_t Crc8Data,deviceID;
uint8_t SendAck[12];
printf("MasterProtocolAnalysis\n");
for (int i = 0; i < len; i++)
{
printf("0x%x ",buff[i]);
}
printf("\n");
if(buff[0] == NETDATA) //'N'
{
if((buff[1] == HI_UINT16(PAN_ID))&&(buff[2] == LO_UINT16(PAN_ID))) // 0x10 0x10
{
Crc8Data = crc8(&buff[0], len - 1); //减去校验
if(Crc8Data != buff[len - 1])
{
memset(buff,0,len);//清空缓存区
return 0;
}
if(buff[3] == DATAHEAD) //0x21
{
NetDataProtocolAnalysis(&buff[3], len - 3); //网络数据包解析
}
}
else
return 0;
}
else if(buff[0] == JIONREQUEST) //0x3C
{
deviceID = JionNetProtocolAnalysis(buff, len); //入网协议解析
printf("deviceID = %d\n",deviceID);
if(deviceID >= 0)
{
SendAck[0] = JIONREQUEST;
SendAck[1] = 1;
SendAck[2] = 'A';
SendAck[3] = HI_UINT16(PAN_ID);
SendAck[4] = LO_UINT16(PAN_ID);
SendAck[5] = slaveNetInfo_t[deviceID].deviceAddr[0];
SendAck[6] = slaveNetInfo_t[deviceID].deviceAddr[1];
SendAck[7] = deviceID;
SendAck[8] = crc8(SendAck, 8);
Radio->SetTxPacket(SendAck, 9); //发送网络数据包
printf("MasterAck\n");
for (int i = 0; i < 9; i++)
{
printf("0x%x ",SendAck[i]);
}
printf("\n");
}
}
return 1;
}
入网协议解析(新设备添加入网表)
/************************************************************************/
/* 入网协议分析状态 */
/************************************************************************/
typedef enum
{
JION_HEADER = 0,
JION_LENGHT,
JION_TYPE,
JION_PANID,
JION_ADDR,
JION_CRC
}JionProtocol
//**********************************//
//函数名称: JionNetProtocolAnalysis
//函数描述: 入网协议解析
//函数参数: uint8_t *buff,uint8_t len
//返回值: uint8_t
//*******************************//
uint8_t JionNetProtocolAnalysis(uint8_t *buff,uint8_t len)
{
uint8_t i = 0, dataLen = 0;
uint8_t status = 0, lenOld = len;
printf("JionNetProtocolAnalysis\n");
for (int i = 0; i < len; i++)
{
printf("0x%x ",buff[i]);
}
printf("\n");
while(len--)
{
switch(status)
{
case JION_HEADER: //0
if (buff[status] == JIONREQUEST) //0x3C
{
status = JION_LENGHT;
}
else
{
goto ERR;
}
break;
case JION_LENGHT: //1
if(buff[status] == 0x06) //6个字节
{
status = JION_TYPE;
}
else
{
goto ERR;
}
break;
case JION_TYPE:
if (buff[status] == 'J') //J代表入网请求
{
status = JION_PANID;
}
else
{
goto ERR;
}
break;
case JION_PANID: //网络标识符
if (buff[status] == HI_UINT16(PAN_ID) && buff[status + 1] == LO_UINT16(PAN_ID))
{
status = JION_ADDR;
}
else
{
goto ERR;
}
break;
case JION_ADDR:
//旧节点加入
for (i = 0; i < currentDeviceNumber; i++)
{
if ((slaveNetInfo_t[i].deviceAddr[0] == buff[status + 1]) &&
(slaveNetInfo_t[i].deviceAddr[1] == buff[status + 2]))
{
slaveNetInfo_t[i].deviceNetStatus = AGAIN_JION_NET;
status = JION_CRC;
printf("AGAIN_JION_NET i = %d\n",i);
printf("deviceId=%x\n",slaveNetInfo_t[i].deviceId);
printf("deviceAddr[0]=%x\n",slaveNetInfo_t[i].deviceAddr[0]);
printf("deviceAddr[1]=%x\n",slaveNetInfo_t[i].deviceAddr[1]);
break;
}
}
//新节点加入
if(i == currentDeviceNumber)
{
currentDeviceNumber++;//新增加入节点
slaveNetInfo_t[i].deviceId = i;
slaveNetInfo_t[i].deviceAddr[0] = buff[status + 1];
slaveNetInfo_t[i].deviceAddr[1] = buff[status + 2];
status = JION_CRC;
printf("CURRENT_JION_NET i = %d\n",i);
printf("deviceId=%x\n",slaveNetInfo_t[i].deviceId);
printf("deviceAddr[0]=%x\n",slaveNetInfo_t[i].deviceAddr[0]);
printf("deviceAddr[1]=%x\n",slaveNetInfo_t[i].deviceAddr[1]);
}
break;
case JION_CRC:
//更新节点入网状态
if (slaveNetInfo_t[i].deviceNetStatus != AGAIN_JION_NET)
{
slaveNetInfo_t[i].deviceNetStatus = JIONDONE;
status = JION_HEADER; //0
printf("JIONDONE i = %d\n",i);
printf("deviceId=%x\n",slaveNetInfo_t[i].deviceId);
printf("deviceAddr[0]=%x\n",slaveNetInfo_t[i].deviceAddr[0]);
printf("deviceAddr[1]=%x\n",slaveNetInfo_t[i].deviceAddr[1]);
}
break;
default:
break;
}
}
return i;
}
网络数据包解析
//**********************************//
//函数名称: NetDataProtocolAnalysis
//函数描述: 网络数据包解析
//函数参数: uint8_t *buff,uint8_t len
//返回值: 无
//*******************************//
void NetDataProtocolAnalysis(uint8_t *buff,uint8_t len)
{
printf("NetDataProtocolAnalysis\n");
for (int i = 0; i < len; i++)
{
printf("0x%x ",buff[i]);
}
printf("\n");
}
3.2.6.4 从机协议解析
//**********************************//
//函数名称: SendJionNetPacke
//函数描述: 从机入网数据发送
//函数参数: 无
//返回值: 无
//*******************************//
void SendJionNetPacke(void)
{
uint16_t addr = ADDR; //0xFFFF 主机地址
jionPacke_t.msgHead = 0x3C;
jionPacke_t.dataLength = 0x06;
jionPacke_t.netType = 'J';
jionPacke_t.netPanid[0] = HI_UINT16(PAN_ID);
jionPacke_t.netPanid[1] = LO_UINT16(PAN_ID);
jionPacke_t.deviceAddr[0] = HI_UINT16(ADDR);
jionPacke_t.deviceAddr[1] = LO_UINT16(ADDR);
//校验码
jionPacke_t.crcCheck = crc8((uint8_t *)&jionPacke_t,jionPacke_t.dataLength + 1);
printf("SendJionNetPacke addr = %d\n",addr);
//发送数据包
Radio->SetTxPacket((uint8_t *)&jionPacke_t, jionPacke_t.dataLength + 2);
}
从机协议解析
//**********************************//
//函数名称: SlaveProtocolAnalysis
//函数描述: 从机协议解析
//函数参数: uint8_t *buff,uint8_t len
//返回值: uint8_t
//*******************************//
uint8_t SlaveProtocolAnalysis(uint8_t *buff,uint8_t len)
{
uint8_t Crc8Data;
printf("SlaveProtocolAnalysis\n");
for (int i = 0; i < len; i++)
{
printf("0x%x ",buff[i]);
}
printf("\n");
if (buff[0] == NETDATA) //'N' 网络数据包
{
if (buff[1] == HI_UINT16(PAN_ID) && buff[2] == LO_UINT16(PAN_ID))
{
Crc8Data = crc8(&buff[0], len - 1);
if (Crc8Data != buff[len - 1])
{
memset(buff, 0, len);
return 0;
}
if (buff[3] == 0x21) //DATAHEAD
{
printf("Slave_NETDATA\n");
if(buff[5] == 0x1) //0x01 命令 0x00数据
{
if (buff[6] == HI_UINT16(ADDR) && buff[7] == LO_UINT16(ADDR))
{
if(buff[8] == 0x3) //传感器类型
{
#if defined(FAN)
if(buff[9] == true)
{
FanOn();
}
else
{
FanOff();
}
#endif
}
}
}
}
return 0;
}
}
else if((buff[0] == 0x3C) && (buff[2] == 'A')) //主机应答
{
if (DataCrcVerify(buff, len) == 0)
{
return 0;
}
if (buff[3] == HI_UINT16(PAN_ID) && buff[4] == LO_UINT16(PAN_ID))
{
if (buff[5] == jionPacke_t.deviceAddr[0] && buff[6] == jionPacke_t.deviceAddr[1])
{
slaveNativeInfo_t.deviceId = buff[7];
printf("Slave_ACK\n");
return 0xFF;
}
}
}
else if((buff[0] == 0x3C) && (buff[2] == 'T')) //与主机事件同步
{
if (DataCrcVerify(buff, len) == 0)
{
return 0;
}
if (buff[3] == HI_UINT16(PAN_ID) && buff[4] == LO_UINT16(PAN_ID))
{
uint32_t alarmTime = 0;
startUpTimeHours = buff[5];
startUpTimeMinute = buff[6];
startUpTimeSeconds = buff[7];
startUpTimeSubSeconds = buff[8] <<8 | buff[9];
printf("Slave_CLOCK\n");
printf("H:%d,M:%d,S:%d,SUB:%d\n", startUpTimeHours, startUpTimeMinute, startUpTimeSeconds, startUpTimeSubSeconds);
alarmTime = ((DataUpTimeHours * 60 + DataUpTimeMinute) * 60
+ DataUpTimeSeconds) * 1000 + (DataUpTimeSubSeconds / 2) + DataUpTime;
GetTimeHMS(alarmTime, &DataUpTimeHours, &DataUpTimeMinute, &DataUpTimeSeconds, &DataUpTimeSubSeconds);
printf("DataUpTime->H:%d,M:%d,S:%d,SUB:%d\n", DataUpTimeHours, DataUpTimeMinute, DataUpTimeSeconds, DataUpTimeSubSeconds);
//使能RTC
MX_RTC_Init(); //RTC同步
return 0xFF;
}
}
return 1;
}
3.2.6.5 从机数据上传
//**********************************//
//函数名称: SendSensorDataUP
//函数描述: 上传节点传感器数据
//函数参数: 无
//返回值: 无
//*******************************//
void SendSensorDataUP(void)
{
printf("SendSensorDataUP\n");
#if defined(MPU6050)
mpu6050_ReadData(&Mx,&My,&Mz);
printf("Mx = %.3f\n",Mx);
printf("My = %3f\n",My);
printf("Mz = %3f\n",Mz);
DataPacke_t.netmsgHead = 'N';
DataPacke_t.netPanid[0] = HI_UINT16(PAN_ID);
DataPacke_t.netPanid[1] = LO_UINT16(PAN_ID);
DataPacke_t.msgHead = 0x21;
DataPacke_t.dataLength = 0x08;
DataPacke_t.dataType = 0;
DataPacke_t.deviceAddr[0] = HI_UINT16(ADDR);
DataPacke_t.deviceAddr[1] = LO_UINT16(ADDR);
DataPacke_t.sensorType = 0x2;
DataPacke_t.buff[0] = (int8_t)(Mx*10);
DataPacke_t.buff[1] = (int8_t)(My*10);
DataPacke_t.buff[2] = (int8_t)(Mz*10);
//校验码
DataPacke_t.crcCheck = crc8((uint8_t *)&DataPacke_t,DataPacke_t.dataLength + 4);
//发送数据包
Radio->SetTxPacket((uint8_t *)&DataPacke_t, DataPacke_t.dataLength + 5);
#elif defined(DHT11)
DHT11_TEST();
printf("TEMP = %d\n",ucharT_data_H);
printf("HUM = %d\n",ucharRH_data_H);
DataPacke_t.netmsgHead = 'N';
DataPacke_t.netPanid[0] = HI_UINT16(PAN_ID);
DataPacke_t.netPanid[1] = LO_UINT16(PAN_ID);
DataPacke_t.msgHead = 0x21;
DataPacke_t.dataLength = 0x07;
DataPacke_t.dataType = 0;
DataPacke_t.deviceAddr[0] = HI_UINT16(ADDR);
DataPacke_t.deviceAddr[1] = LO_UINT16(ADDR);
DataPacke_t.sensorType = 0x1;
DataPacke_t.buff[0] = ucharT_data_H;
DataPacke_t.buff[1] = ucharRH_data_H;
//校验码
DataPacke_t.crcCheck = crc8((uint8_t *)&DataPacke_t,DataPacke_t.dataLength + 4);
//发送数据包
Radio->SetTxPacket((uint8_t *)&DataPacke_t, DataPacke_t.dataLength + 5);
#elif defined(FAN)
FanStaus = FanReadStaus();
DataPacke_t.netmsgHead = 'N';
DataPacke_t.netPanid[0] = HI_UINT16(PAN_ID);
DataPacke_t.netPanid[1] = LO_UINT16(PAN_ID);
DataPacke_t.msgHead = 0x21;
DataPacke_t.dataLength = 0x06;
DataPacke_t.dataType = 0;
DataPacke_t.deviceAddr[0] = HI_UINT16(ADDR);
DataPacke_t.deviceAddr[1] = LO_UINT16(ADDR);
DataPacke_t.sensorType = 0x3;
DataPacke_t.buff[0] = FanStaus;
//校验码
DataPacke_t.crcCheck = crc8((uint8_t *)&DataPacke_t,DataPacke_t.dataLength + 4);
//发送数据包
Radio->SetTxPacket((uint8_t *)&DataPacke_t, DataPacke_t.dataLength + 5);
#endif
}
3.2.7 网络处理任务
netprocess.c
主机
从机
netprocess.h
#ifndef _NETPROCESS_H
#define _NETPROCESS_H
#include "stm32f0xx.h"
#include "stdbool.h"
#define NodeNumber 20
extern volatile uint16_t currentDeviceNumber; //当前设备数量
extern volatile uint16_t oldNodeNumber;
extern volatile uint32_t DataUpTime;
extern uint8_t startUpTimeHours; //启动时间
extern uint8_t startUpTimeMinute;
extern uint8_t startUpTimeSeconds;
extern uint32_t startUpTimeSubSeconds;
extern uint8_t DataUpTimeHours; //更新时间
extern uint8_t DataUpTimeMinute;
extern uint8_t DataUpTimeSeconds;
extern uint32_t DataUpTimeSubSeconds;
/************************************************************************/
/* 定义设备入网时的状态 */
/************************************************************************/
typedef enum
{
NO_JION = 0, //未加入网络
JIONING, //正在加入网络
JIONTIMEOUT, //入网超时
JIONDONE, //入网完成
AGAIN_JION_NET
}DeviceJionStatus;
/************************************************************************/
/* 入网协议分析状态 */
/************************************************************************/
typedef enum
{
JION_HEADER = 0,
JION_LENGHT,
JION_TYPE,
JION_PANID,
JION_ADDR,
JION_CRC
}JionProtocol;
/************************************************************************/
/* 定义设备节点加入标志 */
/************************************************************************/
typedef enum
{
No_Node_Jion_Flag = 0,
Node_Jion_Finish_Flag,
Node_Jion_No_Finish_Flag,
New_Node_Jion_Flag
}DeviceJionFlag;
/************************************************************************/
/* 设备入网,发送的消息体 */
/************************************************************************/
typedef struct
{
uint8_t msgHead; //入网消息头0x3C
uint8_t dataLength; //数据长度 type~crc
uint8_t netType; //模块的网络类型
uint8_t netPanid[2]; //设备的PANID
uint8_t deviceAddr[2]; //模块的设备地址
uint8_t crcCheck; //数据校验(整个数据包,除校验位)
}SlaveJionNet;
/************************************************************************/
/* 设备数据,发送的消息体 */
/************************************************************************/
typedef struct
{
uint8_t netmsgHead; //入网消息头0x3C
uint8_t netPanid[2]; //设备的PANID
uint8_t msgHead; //数据消息头0x21
uint8_t dataLength; //数据长度 type~crc
uint8_t dataType; //模块的数据类型
uint8_t deviceAddr[2]; //模块的设备地址
uint8_t sensorType; //模块的传感器类型
uint8_t buff[4]; //每种传感器数值用两个字节标识,比如温湿度占四个字节
uint8_t crcCheck; //数据校验(整个数据包,除校验位)
}SlaveDataNet;
/************************************************************************/
/* 更新RTC,发送的消息体 */
/************************************************************************/
typedef struct
{
uint8_t msgHead; //入网消息头0x3C
uint8_t dataLength; //数据长度 type~crc
uint8_t netType; //模块的网络类型
uint8_t netPanid[2]; //设备的PANID
uint8_t timeData[5]; //模块的设备地址
uint8_t crcCheck; //数据校验(整个数据包,除校验位)
}SlaveRtcSync;
/************************************************************************/
/* 设备信息 */
/************************************************************************/
typedef struct
{
uint8_t deviceType; //模块的设备类型
DeviceJionStatus deviceNetStatus; //设备的网络状态
uint8_t deviceAddr[2]; //模块的设备地址
uint8_t deviceId; //表示在网表中加入第几个设备
uint8_t deviceData[20];
}SlaveInfo;
uint16_t RandomNumber(void);
uint8_t SlaveJionNetFuction(void);
void SlaveGetSendTime(void);
DeviceJionFlag WaitJionNetFinish(uint8_t timout);
void MasterSendClockData(void);
#endif
netprocess.c
//所有节点的更新周期(在Time内上传所有数据) 单位Ms
volatile uint32_t DataUpTimePeriod = 1000 * 60 * 1; //1分钟
volatile static uint32_t currentTime = 0;
//当前加入设个的个数
volatile uint16_t currentDeviceNumber = 0;
//保存当前加入节点
volatile uint16_t oldNodeNumber = 0;
//节点时间片
volatile uint32_t DataUpTime = 0;
//节点入网状态
volatile DeviceJionFlag JionNodeTimeOutFlag = No_Node_Jion_Flag;
//时钟同步
SlaveRtcSync rtcSync_t;
//初始化网络状态
volatile DeviceJionStatus NetStatus = NO_JION;
extern tRadioDriver *Radio;
3.2.7.1 主机等待从机入网完成
//**********************************//
//函数名称: WaiitJionNetFinish
//函数描述: 等待入网完成
//函数参数: 超时时间
//返回值: 无
//*******************************//
DeviceJionFlag WaitJionNetFinish(uint8_t timout)
{
JionNodeTimeCount = 0;
while(1)
{
Sx127xDataGet();
if (JionNodeTimeCount > timout)
{
if (oldNodeNumber == currentDeviceNumber)
{
printf("无新节点加入\r\n");
//无新节点加入
JionNodeTimeOutFlag = Node_Jion_Finish_Flag;
//停止定时器
HAL_TIM_Base_Stop_IT(&htim2);
return JionNodeTimeOutFlag;
}
else
{
//有新节点加入
printf("有新节点加入\r\n");
JionNodeTimeOutFlag = Node_Jion_No_Finish_Flag;
//保存当前节点数量
oldNodeNumber = currentDeviceNumber;
}
}//等待加入网络
}
}
3.2.7.2 主机发送同步时钟
//**********************************//
//函数名称: MasterSendClockData
//函数描述: 主机发送同步时钟
//函数参数: 无
//返回值: 无
//*******************************//
void MasterSendClockData(void)
{
RTC_TimeTypeDef thisTime;
rtcSync_t.msgHead = JIONREQUEST; //0x3c
rtcSync_t.dataLength = 0x09;
rtcSync_t.netType = 'T';