分享

物联网之LoRa开发与应用六(LoRa自组网络设计)

 袁先森lemon 2019-05-23

深入了解LoRaWAN

内容概要

1、LoRaWAN概述

2、LoRaWAN终端(重点掌握

3、LoRaWAN服务器

LoRaWAN是什么

LoRaWAN采用星型无线拓扑:End Nodes(节点)、Gateway(网关)、Network Server(网络服务器)、Application Server(应用服务器)

LoRaWAN通信协议

低功耗、可扩展、高服务质量、安全的长距离无线网络

LoRaWAN通信机制

LoRaWAN与其他组网协议对比

LoRaWAN网关SX1301:(8通道的LoRa接口用于LoRa节点的接入、一个FSK接口用于FSK节点的接入、1个LoRa网关间通讯的接口

大容量的网络规模、高速度的通信机制

有三种节点类型:Class A、Class B、Class C

LoRaWAN终端Class A:(平时处于休眠模式,当他需要工作的时候才会去发送数据包,所以功耗比较低。但是实时性较差,间隔一段时间才能下行通信

LoRaWAN终端Class B:(当需要节点去响应实时性问题的时候,首先网关会发送一个信标,告诉节点要加快通讯,快速工作,节点收到信标之后,会在128秒内去打开多个事件窗口,每个窗口在3-160ms,在128秒内可以实时对节点进行监控

LoRaWAN终端Class C:(如果不发送数据的情况下,节点一直打开接收窗口,既保证了实时性,也保证了数据的收发,但是功耗非常高

LoRaWAN服务器框架

LoRaWAN服务器通信接口

LoRaWAN服务器通信协议

LoRa自组网架构设计

内容概要

1、MAC协议设计

2、LoRa自组网协调器设计

3、LoRa自组网节点设计

MAC协议重要性:解决信号冲突的问题、尽可能地节省电能、保证通信的健壮和稳定性

MAC协议种类

1、信道划分的MAC协议:时分(TDMA)、频分(FDMA)、码分(CDMA)

2、随机访问MAC协议:

     ALOHA,S-ALOHA,CSMA,CSMA/CD

     CSMA/CD应用于以太网

     CSMA/CA应用于802.11无线局域网

3、轮讯访问MAC协议:

     主节点轮询

     工业Modbus通信协议

时分复用:(在一定的事件内去分配时间槽,每个时间槽分给一个节点,使节点在这个时间槽里通信,如果不在这个时间槽是不能通信的。和电脑CPU的时间片是一个道理

优点:节省电能、最大化使用带宽

缺点:所有节点需要精确的时钟源,并且需要周期性校时;

          向网络中添加和删除节点都要有时隙分配和回收算法。

频分复用:(CPU是多核,多任务同时进行:不同频率的通信可以同时进行

优点:增加通信容量、提高通信可靠性

缺点:物理通道增加,成本增加

轮询访问

优点:协议简单,易开发

缺点:通讯效率低、网络规模小(只能接入1-247个节点)

基于时分复用LoRa自组网设计

入网机制:(随机访问,竞争入网)

时分复用:(每个节点在规定的时间槽内通信)

 LoRa自组网协调器设计

LoRa自组网节点设计

LoRa自组网集中器程序开发

内容概要

1、通信协议

2、工程修改

3、搭建框架

4、源码分析

通信协议

 

LoRa自组网协调器设计

根据协调器业务流程需要在之前工程里添加两个外设:定时器(用于节点入网超时的判断,后面有配置说明)、RTC(实时时钟,用于时钟同步,后面有配置说明)

IAR工程修改

添加外设需要修改STM32CubeMX工程,需要把我们编写的代码放在BEGIN和END中间

RTC外设配置

1、修改RTC时钟源为外部高速时钟

2、配置RTC分频系数

3、初始化日期和时间

4、配置Alarm参数

5、使能RTC全局中断

定时器外设配置

1、配置TIM2分频系数

2、使能TIM2定时器中断

搭建框架

1、RTC任务

2、定时器任务

3、通信协议

4、数据处理任务

5、网络处理任务

RTC任务

1、RTC初始化

/**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;

 /**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));

2、Alarm中断任务

//**********************************//////函数名称: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; gAlarm.AlarmTime.Minutes = masterTime.Minutes; gAlarm.AlarmTime.Seconds = masterTime.Seconds; gAlarm.AlarmTime.SubSeconds = masterTime.SubSeconds; #else 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;}

定时器任务

1、定时器初始化:CubeMX重初始化已经完成,这里不需要修改

2、定时器中断任务

//**********************************//////函数名称: HAL_TIM_PeriodElapsedCallback////函数描述: 定时器2溢出中断回调函数////函数参数: TIM_HandleTypeDef *htim////返回值: 无////创建者: //*******************************//void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){//判断是否为定时器2中断//累加全局计数值 if(htim->Instance == htim2.Instance) { JionNodeTimeCount++; }}

通信协议

1、CRC8校验函数

/* protocol.c */#include 'protocol.h'/******************************************************************************* Name:    CRC-8               x8+x2+x+1* Poly:    0x07* Init:    0x00* Refin:   False* Refout:  False          函数功能:生成CRC代码* 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;	}}

2、协议数据结构

数据处理任务:(先了解大致框架,后面具体分析

串口任务

    -->串口接收

无线任务

    -->无线接收

        -->主机协议解析

            -->网络数据包解析

            -->入网请求解析

/* dataprocess.c */#include 'dataprocess.h'#include 'usart.h'#include 'led.h'#include 'protocol.h'#include 'rtc.h'#include 'string.h'#include 'stdio.h'//sx1278#include 'platform.h'#include 'radio.h'#include 'sx1276-Hal.h'#include 'sx1276-LoRa.h'#include 'sx1276-LoRaMisc.h'extern uint16_t BufferSize;extern uint8_t Buffer[BUFFER_SIZE];#if defined(MASTER)extern uint8_t EnableMaster;#elif defined(SLAVE)extern uint8_t EnableMaster;#endifextern tRadioDriver *Radio;extern uint32_t Master_RxNumber;extern uint32_t Master_TxNumber;extern uint32_t Slave_RxNumber;extern uint32_t Slave_TxNumber;extern volatile uint8_t SendDataOkFlag;uint8_t startUpDateHours = 0;uint8_t startUpDateMinute = 0;uint8_t startUpDateSeconds = 0;uint16_t startUpDateSubSeconds = 0;//Master存储入网的设备信息SlaveInfo slaveNetInfo_t[NodeNumber];//Salve入网信息包SlaveJionNet jionPacke_t;//Salve保存自己的地址SlaveInfo slaveNativeInfo_t;//节点数据SlaveDataNet DataPacke_t;//**********************************//////函数名称: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++;}//**********************************//////函数名称: 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;}//**************从**************////**************机**************////**********************************//////函数名称: SendJionNetPacke////函数描述: 从机入网数据发送////函数参数: 无////返回值: 无////创建者: //*******************************//void SendJionNetPacke(void){ uint16_t addr = ADDR; 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) { if (buff[1] == HI_UINT16(PAN_ID) && buff[2] == LO_UINT16(PAN_ID)) { Crc8Data = crc8(&buff[3], len - 4); if (Crc8Data != buff[len - 1]) { memset(buff, 0, len); return 0; } if (buff[3] == 0x21) { printf('Slave_NETDATA\n'); } 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(); return 0xFF; } } return 1;}//**********************************//////函数名称: SendSensorDataUP////函数描述: 上传节点传感器数据////函数参数: 无////返回值: 无////创建者: //*******************************//void SendSensorDataUP(void){ printf('SendSensorDataUP\n'); 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 = 0x09; 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] = 0x1; DataPacke_t.buff[1] = 0x2; DataPacke_t.buff[2] = 0x3; DataPacke_t.buff[3] = 0x4; //校验码 DataPacke_t.crcCheck = crc8((uint8_t *)&DataPacke_t,DataPacke_t.dataLength + 4); //发送数据包 Radio->SetTxPacket((uint8_t *)&DataPacke_t, DataPacke_t.dataLength + 5);}//**************主**************////**************机**************////**********************************//////函数名称: 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) { 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] == DATAHEAD) { NetDataProtocolAnalysis(&buff[3], len - 3); } } else return 0; } else if(buff[0] == JIONREQUEST) { 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;}//**********************************//////函数名称: 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: if (buff[status] == JIONREQUEST) { status = JION_LENGHT; } else { goto ERR; } break; case JION_LENGHT: if (buff[status] == 0x06) { status = JION_TYPE; } else { goto ERR; } break; case JION_TYPE: if (buff[status] == '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; 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;ERR: memset(buff, 0, lenOld); status = JION_HEADER; return -1;}//**********************************//////函数名称: 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');}

网络处理任务:(先了解大致框架,后面具体分析

1、等待入网完成

2、主机发送时钟同步数据包

/* netprocess.c */#include 'netprocess.h'#include 'dataprocess.h'#include 'tim.h'#include 'rtc.h'#include 'adc.h'#include 'protocol.h'#include 'math.h'#include 'stdlib.h'#include 'string.h'#include 'stdio.h'//sx1278#include 'platform.h'#include 'radio.h'#include 'sx1276-Hal.h'#include 'sx1276-LoRa.h'#include 'sx1276-LoRaMisc.h'//所有节点的更新周期(在Time内上传所有数据) 单位Msvolatile 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;uint8_t startUpTimeHours = 0;uint8_t startUpTimeMinute = 0;uint8_t startUpTimeSeconds = 0;uint32_t startUpTimeSubSeconds = 0;uint8_t DataUpTimeHours = 0;uint8_t DataUpTimeMinute = 0;uint8_t DataUpTimeSeconds = 0;uint32_t DataUpTimeSubSeconds = 0;//时钟同步SlaveRtcSync rtcSync_t;//初始化网络状态volatile DeviceJionStatus NetStatus = NO_JION;extern tRadioDriver *Radio;//**************从**************////**************机**************////**********************************//////函数名称:   RandomNumber////函数描述:   生成随机数////函数参数:   无////返回值:     随机数////创建者:    //*******************************//uint16_t RandomNumber(void){    uint16_t randNumber = 0;    float adcValue = 0;    uint32_t u32adcValue = 0;        //开启DMA转换ADC    HAL_ADC_Start_DMA(&hadc, (uint32_t*)ADC_DMA_Value, ADC_NUM);        HAL_Delay(100);//    printf('ADC_DMA_Value[0] = %d\n',ADC_DMA_Value[0]);//    printf('ADC_DMA_Value[1] = %d\n',ADC_DMA_Value[1]);//    printf('ADC_DMA_Value[2] = %d\n',ADC_DMA_Value[2]);//    printf('ADC_DMA_Value[3] = %d\n',ADC_DMA_Value[3]);//    printf('ADC_DMA_Value[4] = %d\n',ADC_DMA_Value[4]);    //转换为mv值    adcValue = ADC_DMA_Value[ADC_IN5];    adcValue = (adcValue * 3300) / 4096;          printf('adcValue = %f\n',adcValue);        u32adcValue = (uint32_t)((adcValue-floor(adcValue))*1000000);    printf('u32adcValue = %d\n',u32adcValue);    //获取随机数    srand(u32adcValue);    for(int i = 0;i< 10;i++)    randNumber += (uint8_t)rand();    return randNumber;}//**********************************//////函数名称:   SlaveJionNetFuction////函数描述:   从机加入网络////函数参数:   无////返回值:     入网状态////创建者: //*******************************//uint8_t SlaveJionNetFuction(void){  switch(NetStatus)  {    case NO_JION:          SendJionNetPacke();          //if(Radio->Process( ) == RF_TX_DONE)          NetStatus = JIONING;          currentTime = HAL_GetTick();    break;    case JIONING:          if(Sx127xDataGet() == 0xFF)          {            NetStatus = JIONDONE;            printf('Slave_JIONDONE\n');          }          else          {            if ((HAL_GetTick() - currentTime) > 6000)            NetStatus = JIONTIMEOUT;          }    break;    case JIONTIMEOUT:        NetStatus = NO_JION;    break;    case JIONDONE:          Radio->StartRx();          return 0;    break;    default:    break;  }return 1;}//**********************************//////函数名称:   SlaveGetSendTime////函数描述:   节点获取时间片////函数参数:   无//      //返回值:     无     ////创建者:   //*******************************//void SlaveGetSendTime(void){  float TransTimeUP = 0;		//数据传输时间  TransTimeUP = SX1276LoRaGetTransferTime();  DataUpTime  = Sx127xGetSendTime(NodeNumber,TransTimeUP, DataUpTimePeriod);  printf('DataUpTime = %d\n',DataUpTime);  if (DataUpTime == 0)  {    startUpTimeHours = startUpTimeMinute = 0;    startUpTimeSeconds = startUpTimeSubSeconds = 0;  }  else  {    GetTimeHMS(DataUpTime, &startUpTimeHours, &startUpTimeMinute, &startUpTimeSeconds, &startUpTimeSubSeconds);    printf('DataUpTime->H:%d,M:%d,S:%d,SUB:%d\n', startUpTimeHours, startUpTimeMinute, startUpTimeSeconds, startUpTimeSubSeconds);  }  GetTimeHMS(DataUpTimePeriod, &DataUpTimeHours, &DataUpTimeMinute, &DataUpTimeSeconds, &DataUpTimeSubSeconds);  printf('DataUpTimePeriod->H:%d,M:%d,S:%d,SUB:%d\n', DataUpTimeHours, DataUpTimeMinute, DataUpTimeSeconds, DataUpTimeSubSeconds);}//**************主**************////**************机**************////**********************************//////函数名称:  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;      }    }//等待加入网络    }  }//**********************************//////函数名称:   MasterSendClockData////函数描述:   主机发送同步时钟////函数参数:   无////返回值:     无////创建者:  //*******************************//void MasterSendClockData(void){  RTC_TimeTypeDef thisTime;    rtcSync_t.msgHead = JIONREQUEST;  rtcSync_t.dataLength = 0x09;  rtcSync_t.netType = 'T';  rtcSync_t.netPanid[0] = HI_UINT16(PAN_ID);  rtcSync_t.netPanid[1] = LO_UINT16(PAN_ID);    //获取当前时间  HAL_RTC_GetTime(&hrtc, &thisTime, RTC_FORMAT_BIN);    rtcSync_t.timeData[0] = thisTime.Hours;  rtcSync_t.timeData[1] = thisTime.Minutes;  rtcSync_t.timeData[2] = thisTime.Seconds;  rtcSync_t.timeData[3] = (thisTime.SubSeconds >> 8) & 0xFF;  rtcSync_t.timeData[4] = thisTime.SubSeconds & 0xFF;  //计算校验码  rtcSync_t.crcCheck = crc8((uint8_t *)&rtcSync_t, rtcSync_t.dataLength + 1);  //发送数据包  Radio->SetTxPacket((uint8_t *)&rtcSync_t, rtcSync_t.dataLength + 2);}

源码分析(具体分析:该源码涵盖了集中器 和 节点 的源代码,可以从main函数开始分析,先屏蔽掉节点的代码,只分析集中器的相关代码,然后屏蔽掉集中器的代码,只分析节点代码。分析过程中有遇到没见过的或者不知道的函数,直接追入分析即可)

main函数相关代码分析

//所有设备加入网络的当前情况DeviceJionFlag JionDeviceStatu = No_Node_Jion_Flag;//时间同步标志volatile uint8_t MasterSendTimeSliceFlag = 0;volatile uint8_t SendDataOkFlag = 0;extern volatile uint8_t SendClockFlag;/* main */#if SLAVE //获取随机入网时间 DelayTime = RandomNumber(); printf('JionTime = %d\n',DelayTime); HAL_Delay(DelayTime); //等待入网成功 while (SlaveJionNetFuction()); //获取节点发送时间片 SlaveGetSendTime(); #else //主机直接初始化RTC MX_RTC_Init();--------------------------------------------->第一步:主机初始化RTC HAL_TIM_Base_Start_IT(&htim2);----------------------------->第二步:主机初始化定时器(包括中断)#endif while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ Sx127xDataGet(); #if SLAVE if(sendUpDataFlag == 1) { SendSensorDataUP(); sendUpDataFlag = 0; }#else UartDmaGet();------------------------------------------->第三步:串口数据获取,并把数据发送出去 //等待节点入网 if (JionDeviceStatu != Node_Jion_Finish_Flag)----------->第四步:如果没有入网完成 { printf('main 等待加入网络\n'); JionDeviceStatu = WaitJionNetFinish(10); } /* 有新节点加入 */ if (currentDeviceNumber != oldNodeNumber) { printf('main 新节点加入网络\n'); HAL_TIM_Base_Start_IT(&htim2); JionDeviceStatu = New_Node_Jion_Flag; SendClockFlag = 0; //发送分时时间片 } /* 有旧节点加入 */ for (int i = 0; i < currentDeviceNumber;i++) { /* 查询是否有旧节点重新加入*/ if (slaveNetInfo_t[i].deviceNetStatus == AGAIN_JION_NET) { printf('main 旧节点加入网络\n'); slaveNetInfo_t[i].deviceNetStatus = JIONDONE; JionDeviceStatu = New_Node_Jion_Flag; SendClockFlag = 0; //发送分时时间片 HAL_TIM_Base_Start_IT(&htim2); } } /* 给从机分发时间片 */ if ((JionDeviceStatu == Node_Jion_Finish_Flag)&&(SendClockFlag == 0) &&(currentDeviceNumber != 0)) { if (SendDataOkFlag == 1) { SendDataOkFlag = 0; printf('main 发送时钟同步\n'); //告诉所有节点开始上传数据 MasterSendClockData(); SendClockFlag = 1; while(!SendDataOkFlag) //等待发送完成 { Sx127xDataGet(); } SendDataOkFlag = 1; } }#endif if(EnableMaster == true) { MLCD_Show(); } else { SLCD_Show(); } }

LoRa自组网节点程序开发

内容概要

1、工程修改

2、搭建框架

3、源码分析

4、组网实验

LoRa自组网节点设计

根据节点业务流程需要在之前工程里添加一个外设用于随机数发生:ADC

ADC外设配置

1、配置ADC为连续采集

2、配置DMA通道

3、配置ADC标签

搭建框架

1、网络处理任务

2、数据处理任务

数据处理任务

数据解析任务

    -->从机数据解析

        -->网络数据包解析

        -->网络应答包解析

        -->时间同步包解析

数据上传任务

    -->入网信息上传

    -->数据信息上传

网络处理任务

1、入网随机时间获取

2、无线加入网络

3、获取数据包发送时长

4、获取节点时间片

硬件准备:LoRa设备X3、STlinkX1、USBmini线X3

程序烧写

1、烧写Master程序

2、烧写Slave程序:配置从机设备地址,分别烧录

实验现象

1、从机入网请求

2、主机入网应答

3、从机1分钟定时上传数据

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多