LoRa自组网络设计 6

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 中的码分复用通过以下方式实现:

  1. 唯一的扩频因子(Spreading Factor): 在 LoRa 中,每个终端设备使用唯一的扩频因子,这个扩频因子决定了数据信号的频带扩展程度。不同的扩频因子对应着不同的码片序列。每个终端设备在发送数据时,使用其唯一的扩频因子进行调制,因此即使在相同的频率上,不同的终端设备也可以同时发送数据而不会相互干扰。

  2. 自适应数据速率(Adaptive Data Rate,ADR): LoRa 网络可以根据终端设备的距离和环境条件动态调整扩频因子和发送功率,以最大程度地提高通信的可靠性和覆盖范围。

  3. 碰撞避免技术: 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 制定通信协议

入网请求

从机->入网请求
名称字节数描述举例
帧头10x3C0x3C
长度1最长126字节;长度范围内不检测包头;计算整个帧长度0x0c
网络类型1字符<J>代表入网请求J
网络标识符2PANID,用于网络区分,只有PANID一样才可以组网通信0x0102
设备地址2设备唯一地址标识0x1235
CRC8校验1数据包校验,整个数据包,除校验位0x08
主机->入网应答
名称字节数描述举例
帧头10x3C0x3C
长度1最长126字节;长度范围内不检测包头;计算整个帧长度0x0c
网络类型1字符<A>代表入网成功A
网络标识符2PANID,用于网络区分,只有PANID一样才可以组网通信0x0102
设备地址2设备唯一地址标识0x1235
设备序号1设备是第几个入网1
CRC8校验1数据包校验,整个数据包,除校验位0x08

        设备标识符 PANID和zigbee组网一样,相同才能在一个网通信 

时间同步

        设备序号:第几个入网的,同时给节点分时间片

网络数据包

网络数据包
名称字节数描述举例
帧头1字符<N>代表网络数据包N
网络标识符2PANID,用于网络区分,只有PANID一样才可以组网通信0x0102
数据包包头10x210x21
包长1数据包长度,代表数据域数据长度1
数据类型10x00:数据、0x01:命令0x00
设备地址2设备标识符0x0001
传感器类型10x01:温湿度、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';
  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);

}

 3.2.7.3 生成随机数
//**********************************//
//函数名称:   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;
}

库函数 放到时间数里面,循环读10次,转换为uint8 ,最大是2550,返回(0-2.5s) 

 

3.2.7.4 从机加入网络
//**********************************//
//函数名称:   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;
}
3.2.7.5 从机获取时间片
//**********************************//
//函数名称:   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);
}

3.2.8  获取数据发送时长

lora获取时间片的同时需要考虑数据发送的时长

 

//**********************************//
//函数名称: SX1276LoRaGetTransferTime  
//函数描述: 获取数据发送时长
//函数参数:   无
//返回值:     float
//*******************************//
float SX1276LoRaGetTransferTime( void )
{
	uint16_t PayloadSymNb_Ceil,SF, PayloadSymNb_Ceil_DenoMinator;
	float Tsym = 0,Tpreamble = 0,PayloadSymNb = 0,Tpayload=0,Tpacket=0;
	bool H;
	uint8_t DE = 0;
	//计算符号速率
    SF = (2 << LoRaSettings.SpreadingFactor-1);
	Tsym = (SF*1000 / (float)SignalBw[LoRaSettings.SignalBw]);
//	PRINTF2("Tsym:%0.3f\n", Tsym);
	//前导码时间
	Tpreamble = (SX1276LoRaGetPreambleLength() + 4.25)*Tsym;
//	PRINTF2("Tpreamble:%0.3f\n", Tpreamble);
	//有效负载符号数
	H = !LoRaSettings.ImplicitHeaderOn;
	DE = SX1276LoRaGetLowDatarateOptimize();
	PayloadSymNb_Ceil_DenoMinator = (((8 * LoRaSettings.PayloadLength) - (4 * LoRaSettings.SpreadingFactor) + 28 + 16 - (20 * H)));
//	PRINTF2("PayloadSymNb_Ceil_DenoMinator:%d\n", PayloadSymNb_Ceil_DenoMinator);
	PayloadSymNb_Ceil = (uint16_t)ceil(((double)PayloadSymNb_Ceil_DenoMinator) / (4 * (LoRaSettings.SpreadingFactor - 2 * DE)));
//	PRINTF2("PayloadSymNb_Ceil:%d\n", PayloadSymNb_Ceil);
	PayloadSymNb = 8 + max((PayloadSymNb_Ceil)*(LoRaSettings.ErrorCoding + 4), 0);
	Tpayload = PayloadSymNb * Tsym;
//	PRINTF2("Tpayload:%0.3f\n", Tpayload);
	//计算传输时间
	Tpacket = (Tpreamble + Tpayload)/1000;
	return Tpacket;
}

//**********************************//
//函数名称:   Sx127xGetSendTime
//函数描述:   获取节点发送时间片
//函数参数:   uint8_t num, float timeUp, uint32_t dataUpTimePeriod
//返回值:     时间片
//*******************************//

uint16_t Sx127xGetSendTime(uint8_t num, float timeUp, uint32_t dataUpTimePeriod)
{
	uint16_t startTime = 0;

	/* 连个节点之间数据传输间隔最小为500Ms */
	if ( ((timeUp + 500) * num ) > dataUpTimePeriod)
	{
	}

	/* 每个节点的所占间隙的时间长度,已经包含发送时间和空闲时间 */
	startTime = dataUpTimePeriod / num;
	/* 获得计算数据的整数部分,向上取整*/
	return ((uint16_t)ceil((double)startTime * slaveNativeInfo_t.deviceId));
}

 

3.2.8 主程序

主机:

等待入网
是否是新节点,如是改变节点入网状态未完成
是否旧节点,如是改变节点入网状态未完成
时钟同步时间是否到,到了时钟同步

从机:

节点都已经在上面部分实现,只需要等待接收数据,数据解析
判断是否到达定时发送时间,发送数据,并清空标志位。

1.adc读取,返回随机时间
2.打印地址
3.发送完后收到应答包,加入完成
4.时间片是0,因为第一个
5.一分钟一个rtc闹钟
6网络同步
7收到同步包
8设置了时间
9 1分钟传一个数据,上传一次数据
10 还有另一个设备发送的,节点也会收到。

int main(void)
{

  
  uint8_t RegVersion = 0;
  uint8_t str[20] = {0};
  uint16_t addr = ADDR;

  HAL_Init();

  uint32_t DelayTime = 0;

  SystemClock_Config();

  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC_Init();
  MX_USART1_UART_Init();
  MX_SPI1_Init();
  MX_RTC_Init();
  MX_TIM2_Init();
  
#if defined (MPU6050)
  
 //初始化三轴传感器  
  MX_I2C1_Init();
  InitMpu6050();  
  
#elif defined (FAN)
  D1_OUT_GPIO_Init();
#endif

  
   Lcd_Init();
 // showimage(gImage_logo);
  HAL_Delay(500);
  Lcd_Clear(YELLOW);

  Gui_DrawFont_GBK16(0,0,RED,GREEN,"  LoRa Topology  ");
#if defined (SLAVE)  
  Gui_DrawFont_GBK16(0,16,RED,GREEN,"     Slave      ");
  

#if defined (MPU6050)  
  //三轴传感器显示
  Gui_DrawFont_GBK16(0,48,BLACK,YELLOW,"X:");

  Gui_DrawFont_GBK16(0,64,BLACK,YELLOW,"Y:");

  Gui_DrawFont_GBK16(0,80,BLACK,YELLOW,"Z:");
#elif defined (DHT11)
  //空气温湿度传感器显示
  Gui_DrawFont_GBK16(0,48,BLACK,YELLOW,"TEMP:");

  Gui_DrawFont_GBK16(0,64,BLACK,YELLOW,"HUM:");

#elif defined (FAN)
  //风扇传感器显示
  Gui_DrawFont_GBK16(0,48,BLACK,YELLOW,"FAN:");
  
#endif
  
  
#elif defined (MASTER)
  Gui_DrawFont_GBK16(0,16,RED,GREEN,"     Master     ");
  Gui_DrawFont_GBK16(0,48,BLACK,YELLOW,"RX:");
  Gui_DrawFont_GBK16(0,64,BLACK,YELLOW,"TX:");
  
#endif
  Gui_DrawFont_GBK16(0,32,BLACK,YELLOW,"ADDR:");
  sprintf((char*)str,"%x",addr);
  Gui_DrawFont_GBK16(64,32,BLACK,YELLOW,str);

  HAL_SPI_DeInit(&hspi1);
  MX_SPI1_Init();
  
  //启动串口1,使能串口空闲中断  
  __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); 
  HAL_UART_Receive_DMA(&huart1,a_Usart1_RxBuffer,RXLENGHT); 

  SX1276Read( REG_LR_VERSION, &RegVersion );
  
  if(RegVersion != 0x12)
  {
    printf("LoRa read Error!\r\n");
    printf("LoRa RegVersion = %d!\r\n",RegVersion);
  
  }
  else
  {
    printf("LoRa read Ok!\r\n");
    printf("LoRa RegVersion = %d!\r\n",RegVersion);
  }
  
  //读到版本号后,关闭3种灯
  LedOff(LED_RX);
  LedOff(LED_TX);
  LedOff(LED_NT);

  Radio = RadioDriverInit();
  Radio->Init();

  
  printf("systerm init ok!\n");
  Radio->StartRx( );  
  

#if SLAVE
  //获取随机入网时间
  DelayTime = RandomNumber();
  printf("JionTime = %d\n",DelayTime);
  HAL_Delay(DelayTime);
  //等待入网成功
  while (SlaveJionNetFuction());
  //获取节点发送时间片
  SlaveGetSendTime();
  
#else
    //主机直接初始化RTC
    MX_RTC_Init();
    HAL_TIM_Base_Start_IT(&htim2);
  
#endif

  while (1)
  {
    
    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();
    }
  }
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/519592.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[C#]OpenCvSharp使用帧差法或者三帧差法检测移动物体

关于C版本帧差法可以参考博客 [C]OpenCV基于帧差法的运动检测-CSDN博客https://blog.csdn.net/FL1768317420/article/details/137397811?spm1001.2014.3001.5501 我们将参考C版本转成opencvsharp版本。 帧差法&#xff0c;也叫做帧间差分法&#xff0c;这里引用百度百科上的…

C语言数据结构专题(3应用-通讯录的实现)

前言 前面的两节我们弄清了顺序表是什么&#xff1f;顺序表是怎么实现的&#xff1f;此时大家可能有疑问了&#xff1a;顺序表被创造出来具体有什么用呢&#xff1f;那么本节就给大家带来顺序表的应用--通讯录的实现&#xff0c;废话不多说&#xff0c;我们正式进入本节的学习 …

探寻马来西亚服务器托管的优势与魅力

随着全球跨境业务的不断增加&#xff0c;境外服务器成为越来越受欢迎的选择。在这其中&#xff0c;马来西亚服务器备受关注&#xff0c;其机房通常位于马来西亚首都吉隆坡。对于客户群体主要分布在东南亚、澳大利亚和新西兰等地区的用户来说&#xff0c;马来西亚服务器是一个理…

MATLAB近红外光谱分析技术应用

郁磊副教授&#xff0c;主要从事MATLAB编程、机器学习与数据挖掘、数据可视化和软件开发、生理系统建模与仿真、生物医学信号处理&#xff0c;具有丰富的实战应用经验&#xff0c;主编《MATLAB智能算法30个案例分析》、《MATLAB神经网络43个案例分析》相关著作。已发表多篇高水…

JVM基础:类的生命周期详解

JDK版本&#xff1a;jdk8 IDEA版本&#xff1a;IntelliJ IDEA 2022.1.3 文章目录 一. 生命周期概述二. 加载阶段(Loading)2.1 加载步骤2.2 查看内存中的对象 三. 连接阶段(Linking)3.1 连接之验证3.2 连接之准备3.3 连接阶段之解析 四. 初始化阶段(Initialization)4.1 单个类的…

约数与倍数-第12届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第45讲。 约数与倍数&#…

rust 面向对象编程特性、模式与模式匹配、高级特征

面向对象编程OOP 学习了结构体、枚举&#xff0c;它们可以包含自定义数据字段&#xff0c;也可以定义内部方法&#xff0c;它们提供了与对象相同的功能。 面向对象的四大特征&#xff1a;封装、继承、多态 通过pub标记为公有的结构体&#xff0c;在其他模块中可以访问使用这…

python爬虫———post请求方式(第十四天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

C语言【编译和链接】

1.程序执行过程 C语言的编译和链接是将源代码转换为可执行程序的过程。下面是C语言编译和链接的基本步骤&#xff1a; 预处理&#xff1a;在编译前&#xff0c;预处理器会对源代码进行。它会处理以"#"开头的预处理指令&#xff0c;#include和#define&#xff0c;并将…

算法笔记————ST表

运用了倍增思想&#xff0c;从小到大处理 1.【模板】ST 表 // Problem: // P3865 【模板】ST 表 // // Contest: Luogu // URL: https://www.luogu.com.cn/problem/P3865 // Memory Limit: 125 MB // Time Limit: 800 ms // // Powered by CP Editor (https://cpedi…

Kotlin学习日志(一)TextView、Button、Toast的使用(1)

android:layout_width“wrap_content” android:layout_height“wrap_content”/> import kotlinx.android.synthetic.main.activity_main.* 这句话的意思是引进Kotlin的的控件变量自动映射功能&#xff0c;接下来只要是这个activity_main.xml文件中的控件&#xff0c;我…

非关系型数据库——Redis基本操作

目录 一、Redis数据库常用命令 1.Set——存放数据 2.Get——获取数据 3.Keys——获取符合条件的键值 4.Exists——判断键值是否存在 5.Del——删除指定键值 6.Type——获取键值对应的类型 7.Rename——对已有键值重命名&#xff08;覆盖&#xff09; 8.Renamenx——对…

160 Linux C++ 通讯架构实战14,epoll 反应堆模型

到这里&#xff0c;我们需要整理一下之前学习的epoll模型&#xff0c;并根据之前的epoll模型&#xff0c;提出弊端&#xff0c;进而整理epoll反应堆模型&#xff0c;进一步深刻理解&#xff0c;这是因为epoll实在是太重要了。 复习之前的epoll的整体流程以及思路。 参考之前写…

虚幻UE5智慧城市全流程开发教学

一、背景 这几年&#xff0c;智慧城市/智慧交通/智慧水利等飞速发展&#xff0c;骑士特意为大家做了一个这块的学习路线。 二、这是学习大纲 1.给虚幻UE5初学者准备的智慧城市/数字孪生蓝图开发教程 https://www.bilibili.com/video/BV1894y1u78G 2.UE5数字孪生蓝图开发教学…

【软件工程】测试规格

1. 引言 1.1简介 本次的测试用例是基于核心代码基本开发完毕&#xff0c;在第一代系统基本正常运行后编写的&#xff0c;主要目的是为了后续开发与维护的便利性。 该文档主要受众为该系统后续开发人员&#xff0c;并且在阅读此文档前最后先阅读本系统的需求文档、概要设计文…

海外视频网站推广实战需掌握的10个关键性数据指标-华媒舍

在海外视频网站推广实战中&#xff0c;了解和掌握一些关键性数据指标是非常重要的。这些指标可以帮助我们评估视频网站的推广效果&#xff0c;优化推广策略&#xff0c;提升用户体验。以下是推广人员在实战中应该了解和关注的十个关键性数据指标&#xff1a; 1. 视频创意点击率…

PS入门|规规矩矩的图形怎么抠出来?

前言 上一次讲解到用魔棒工具蒙版可以把需要的区域抠出来&#xff0c;但仅适用于边缘锐利的类型。 但魔棒工具并不适用于边缘区域有过渡色的内容&#xff0c;比如下面这张照片&#xff1a; 如果直接使用魔棒工具进行选择&#xff0c;就会出现下面这种情况&#xff1a; 在边界…

数据挖掘入门项目二手交易车价格预测之建模调参

文章目录 目标步骤1. 调整数据类型&#xff0c;减少数据在内存中占用的空间2. 使用线性回归来简单建模3. 五折交叉验证4. 模拟真实业务情况5. 绘制学习率曲线与验证曲线6. 嵌入式特征选择6. 非线性模型7. 模型调参&#xff08;1&#xff09; 贪心调参&#xff08;2&#xff09;…

内表GROUP BY

内表GROUP BY REPORT z_test_table_lhy. DATA: price TYPE sflight-price. SELECT MIN( price ) AS m,carridINTO DATA(t_temp)FROM sflightGROUP BY carridHAVING MAX( price ) > 10. "Having从句中比较统计结果时&#xff0c;需要将统计函数重写一遍&#xff0c;而不…

Android数据存储技术

一、文件存储 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:layout_width"match_parent"android:layout_height"match_parent" ><EditTextandroid:id&qu…