物聯網之LoRa開發與應用六(LoRa自組網路設計)
深入瞭解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;
#endif
extern 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內上傳所有資料) 單位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;
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分鐘定時上傳資料
相關推薦
物聯網之LoRa開發與應用六(LoRa自組網路設計)
深入瞭解LoRaWAN 內容概要: 1、LoRaWAN概述 2、LoRaWAN終端(重點掌握) 3、LoRaWAN伺服器 LoRaWAN是什麼: LoRaWAN採用星型無線拓撲:End Nodes(節點)、Gateway(閘道器)、Network Server
物聯網之LoRa開發與應用三(Lora人機介面開發)
文章要點: 1、TFT液晶屏工作原理 2、TFT液晶屏驅動開發 3、TFT人機介面開發 TFT液晶屏工作原理 內容概要: 1、TFT液晶屏顯示原理 2、1.44寸TFT液晶屏工作原理 3、1.44寸TFT液晶屏硬體設計 TFT液晶屏如何顯示: 顏色深度
物聯網之LoRa開發與應用一(M0工程建立)
M0工程建立主要分如下四步: 1、IO埠配置 2、時鐘配置 3、外設配置 4、printf函式重定向 IO配置: 1、看懂原理圖 2、建立IO功能對映表 3、通過STM32Cubemx配置IO工作模式 STM32F051K8U6 IO功能對映表 序號
物聯網之LoRa開發與應用二(驅動移植)
LoRa官方韌體下載:https://pan.baidu.com/s/1ftP-HMJTmF9PtA05Lt-Tag 密碼:bc8y IAR程式碼操作快捷鍵 如果要在整個工程中查詢 某個單詞或者其他,則按照如下方式查詢: LoRa驅動框架 硬體介面設計
物聯網之STM32開發三(USART串列埠)
STM32-USART串列埠的應用 內容概要: 序列通訊的基本概念 串列埠暫存器介紹 STM32實現串列埠資料的收發 HAL串列埠庫函式的使用及printf的實現 序列通訊的基本概念: 內容概要: 通訊的基本概念 USART介紹 串列埠的電路連線 串列埠
物聯網之STM32開發四(中斷系統)
STM32-中斷系統 內容概要: STM32中斷系統概述 外部中斷控制器EXTI 按鍵中斷例項 串列埠中斷例項 STM32中斷系統概述: 內容概要: 中斷的基本概念 巢狀向量控制器NVIC 中斷及異常向量表 中斷優先順序 中斷的基本概念: 處理器中
阿里雲物聯網平,Android臺接入問題(couldn't find "libcoap.so")
FATAL EXCEPTION: main Process: com.houkew.projection, PID: 25826
刁肥宅資料結構課設“布隆過濾器的實踐與應用”原始碼(v1.0,永不上交)
程式碼很簡單,寫了一些註釋;加上註釋看就很清楚了。 檔案bloomfilter.cpp: #include "bloomfilter.h" // return a hash range from 0 to 79999 int hash(con
物聯網技術在智慧城市應用中的發展趨勢與前景
物聯網技術 人工智能 大數據 無線傳輸 物聯網發展 智慧城市應用的逐步發展,物聯網技術也在深化應用中產生了新的變形,我所在團隊佳都科技是做視頻監控作為智慧城市建設切入點的,隨著視頻結構化技術視頻大數據技術的不斷沈澱,也讓團隊的物聯網技術得到了新的結合發展,在業內提出“視頻物聯網”這一產
物聯網之NB-IoT技術實踐開發二(NB-IoT開發環境搭建及模組驅動開發)
STM32CubeMX安裝及使用 1、STM32CubeMX介紹 2、STM32CubeMX安裝 3、STM32CubeMX使用 STM32CubeMX介紹 STM32CubeMX簡介: 微控制器圖形化配置 – 自動處理引腳衝突 – 動態設定確定的時鐘樹
桌面應用之electron開發與轉換
桌面應用之electron開發與轉換 一,介紹與需求 1.1,介紹 1. Electron簡介 Electron是用HTML,CSS和JavaScript來構建跨平臺桌面應用程式的一個開源庫。 Electron通過將Chromium和Node.js合併到同一個執行時環境中,並將其打包為Mac,Wind
【安卓與物聯網】Arduino開發板與Android之間通訊
關於Arduino開發板 Arduino是一款開源電子原型平臺,硬體部分是可以用來做電路連線的Arduino電路板。可以通過編寫程式,最後燒寫入開發板,配合上一些感測器,以及富有創造力的設計可以創造出很多神奇的東西。 在大三的時候,對物聯網的感興
物聯網之核心及驅動開發初級五(平臺匯流排開發)
高階驅動--平臺匯流排: Linux裝置驅動模型的由來: 1,實現入口函式 xxx_init()和解除安裝函式 xxx_exit() 2,申請裝置號 register_chrdev (與核心相關) 3,利用udev/mdev機制建立裝置檔案(節點) clas
快消品營銷系統-開發與應用-好處
快消品營銷 營銷系統 不少快消品行業老板都感慨生意越來越難做了,為什麽?產品被假冒、竄貨等原因成了阻礙企業發展的影響因素,為此,贏在移動專門研發了智能營銷系統,助力企業快速實現渠道動銷。 贏在移動智能營銷系統就是高效的營銷工具,它可以幫企業監督竄貨,防偽溯源,快速動銷,收集消費者數據
奶粉導購紅包系統-開發與應用-好處
如今二胎經濟帶火奶粉市場,嬰幼兒奶粉、孕婦奶粉的市場容量非常大,而行業競爭也非常激烈。相對其它行業來講,母嬰門店的導購更能影響消費者的購買。贏在移動提供奶粉導購紅包系統,幫助各大乳企借力導購快速提升奶粉的銷量。 奶粉導購紅包系統讓乳企打破傳統營銷
Docker入門與應用系列(六)Docker私有與公共鏡像倉庫
nbsp one 默認 span epo refers 1.8 png list 1.搭建私有鏡像倉庫Docker Hub作為Docker默認官方公共鏡像;如果想搭建自己的私有鏡像倉庫,官方提供registry鏡像,使搭建私有倉庫非常簡單1.1.1下載registry鏡像並
Docker入門與應用系列(八)Docker圖形界面管理之Shipyard
tps 數據庫 sock blog ocs body mage 代理 cell Shipyard基於Docker API實現的容器圖形管理系統,支持container、images、engine、cluster等功能,可滿足我們基本的容器部署需求可堆棧的Docker管理基於
Docker入門與應用系列(七)Docker圖形界面管理之DockerUI
post 簡單的 技術分享 name mage src 入門 .com 系統 1.dockeruiDockerrUI是一個基於Docker API提供圖形化頁面簡單的容器管理系統,支持容器管理、鏡像管理。1.1 下載鏡像 docker pull abh1nav/doc
試述人力資源管理信息系統開發與應用
人力資源 信息系統 軟件開發 試述人力資源管理信息系統開發與應用 何朱必
Spark筆記整理(三):Spark WC開發與應用部署
大數據 Spark [TOC] Spark WordCount開發 創建的是maven工程,使用的依賴如下: <dependency> <groupId>org.scala-lang</groupId> <artifactId>scal