物联网之NB-IoT技术实践开发三(NB-IoT网络编程)
NB-IoT驅動開發一
1、驅動框架設計
2、AT指令發送
3、AT指令接收
驅動框架設計
數據結構設計:
如何用數據結構來完成AT指令的發送、應答、超時、狀態、重發:(新建 nbiotdriver.h 和 nbiotdriver.c 文件用于AT相關)
發送->其實就是發送字符串“AT\r\n”
解析->其實就是接收字符串“OK”
超時->其實就是超時時間
狀態->其實就是?成功,超時,未收到
重發->其實就是?重發次數
typedef enum //枚舉類型:接收的狀態
{SUCCESS_REC = 0, //成功TIME_OUT, //超時NO_REC //未收到
}teATStatus;typedef struct //定義的數據結構,用數據結構來完成AT指令的發送、應答、超時、狀態、重發
{char *ATSendStr; //向NB-IOT發送字符串(AT命令)char *ATRecStr; //NB-IOT返回給MCU的字符串uint16_t TimeOut; //設置超時teATStatus ATStatus; //接收狀態uint8_t RtyNum; //重發次數
}tsATCmds;
AT指令:
AT+CFUN=0?關閉射頻功能(不進行無線通訊)
AT+CGSN=1?查詢IMEI號(一般出廠已經設置好)
AT+NRB?軟重啟
AT+NCDP=180.101.147.115,5683?設置?IoT 平臺?IP 地址(非?COAP 協議可以不配置)
AT+CFUN=1?開啟射頻功能
AT+CIMI?查詢SIM卡信息
AT+CMEE=1?開啟錯誤提示
AT+CGDCONT=1,“IP”,“ctnb”?設置APN
AT+NNMI=1?開啟下行數據通知
AT+CGATT=1?自動搜網
AT+CGPADDR?查詢核心網分配的?ip 地址
tsATCmds ATCmds[] =
{//參數分別為 向NB-IOT發送字符串(AT命令)、NB-IOT返回給MCU的字符串、設置超時(毫秒)、接收狀態、設置重發次數{"AT+CFUN=0\r\n","OK",2000,NO_REC,3},//關閉射頻功能(不進行無線通訊){"AT+CGSN=1\r\n","OK",2000,NO_REC,3},//查詢IMEI號(一般出廠已經設置好){"AT+NRB\r\n","OK",8000,NO_REC,3},//軟重啟。重啟使用時間比較長,所以這里設置為8秒鐘{"AT+NCDP=180.101.147.115,5683\r\n","OK",2000,NO_REC,3},//設置 IoT 平臺 IP 地址(非 COAP 協議可以不配置){"AT+CFUN=1\r\n","OK",2000,NO_REC,3},//開啟射頻功能{"AT+CIMI\r\n","OK",2000,NO_REC,3},//查詢SIM卡信息{"AT+CMEE=1\r\n","OK",2000,NO_REC,3},//開啟錯誤提示{"AT+CGDCONT=1,\"IP\",\"ctnb\"\r\n","OK",2000,NO_REC,3},//設置APN.注意AT命令中字符串的書寫格式:\"IP\",\"ctnb\"(表示電信){"AT+NNMI=1\r\n","OK",2000,NO_REC,3},//開啟下行數據通知{"AT+CGATT=1\r\n","OK",2000,NO_REC,3},//自動搜網{"AT+CGPADDR\r\n","+CGPADDR:1,1",2000,NO_REC,30},//查詢核心網分配的 ip 地址{"AT+NMGS=","OK",3000,NO_REC,3},//發送數據
};
AT指令發送
typedef enum //枚舉類型,用于與上方的數組 ATCmds 下標相對應
{AT_CFUN0 = 0, //ATCmds[AT_CFUN0] 就是 ATCmds[0],代表數據結構 {"AT+CFUN=0\r\n","OK",2000,NO_REC,3} 以下類推AT_CGSN,AT_NRB,AT_NCDP,AT_CFUN1,AT_CIMI,AT_CMEE,AT_CGDCONT,AT_NNMI,AT_CGATT,AT_CGPADDR,AT_NMGS,AT_IDIE
}teATCmdNum;
#include "time.h"
#include "led.h"static tsTimeType TimeNB;//獲取定時器的起始時間和時間間隔,具體見下面講解char NbSendData[100];//發送數據指令中數據的存儲區void ATSend(teATCmdNum ATCmdNum)
{//清空接收緩存區memset(Usart2type.Usart2RecBuff,0,USART2_REC_SIZE);ATCmds[ATCmdNum].ATStatus = NO_REC;ATRecCmdNum = ATCmdNum;//ATRecCmdNum是在nbiotdriver.c中定義的靜態全局變量if(ATCmdNum == AT_NMGS)//判斷是否為發送數據的指令{memset(NbSendData,0,100);//清空數據的存儲區//第一個%s為發送數據的指令:"AT+NMGS="//第二個%d為發送數據的個數是兩個(字節的長度)//第三和第四個%x是兩個要發送的16進制的數據//最終得到NbSendData的數據為:AT+NMGS=2,0x10,0x10\r\nsprintf(NbSendData,"%s%d,%x%x\r\n",ATCmds[ATCmdNum].ATSendStr,2,0x10,0x10);//發送NbSendData到NB芯片HAL_UART_Transmit(&huart2,(uint8_t*)NbSendData,strlen(NbSendData),100);//發送NbSendData到NB芯片HAL_UART_Transmit(&huart1,(uint8_t*)NbSendData,strlen(NbSendData),100);//發送NbSendData到串口1,用于調試}else{HAL_UART_Transmit(&huart2,(uint8_t*)ATCmds[ATCmdNum].ATSendStr,strlen(ATCmds[ATCmdNum].ATSendStr),100);HAL_UART_Transmit(&huart1,(uint8_t*)ATCmds[ATCmdNum].ATSendStr,strlen(ATCmds[ATCmdNum].ATSendStr),100);}//打開超時定時器,這里主要用來判斷接收超時使用SetTime(&TimeNB,ATCmds[ATCmdNum].TimeOut);//獲取定時器的起始時間和時間間隔,具體見下面講解//打開發送指示燈,配合LedTask函數的使用可以產生一個100毫秒的亮燈,具體函數下文有講解//如果100毫秒之內又有數據發送,則定時器重新計時,LED燈繼續延長點亮時間SetLedRun(LED_TX);
}
AT指令接收
串口接收:
串口回調函數:
AT指令解析:
?
#define USART2_DMA_REC_SIZE 256
#define USART2_REC_SIZE 1024
typedef struct //用來處理DMA接收到的數據,解析緩存區的數據
{uint8_t Usart2RecFlag;//數據接收到的標志位uint16_t Usart2DMARecLen;//獲取接收DMA數據的長度uint16_t Usart2RecLen;//獲取解析緩存區的長度uint8_t Usart2DMARecBuff[USART2_DMA_REC_SIZE];//DMA的緩沖區uint8_t Usart2RecBuff[USART2_REC_SIZE]; //用于解析接收數據的緩存區
}tsUsart2type;extern tsUsart2type Usart2type;//該變量在uart.c中聲明,但是在其他文件中需要使用,所以需要外部聲明
//uint8_t Usart1Rx = 0;
//uint8_t Usart2Rx = 0;// __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);//打開UART1接收中斷(接收寄存器不為空則產生中斷)
//
// HAL_UART_Receive_IT(&huart1, &Usart1Rx, 1);//UART1接收使能,Usart1Rx:接收數據的緩存區
//
// __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);//打開UART2接收中斷(接收寄存器不為空則產生中斷)
//
// HAL_UART_Receive_IT(&huart2, &Usart2Rx, 1);//UART2接收使能,Usart2Rx:接收數據的緩存區
uint8_t Usart1Rx = 0;tsUsart2type Usart2type;//該數據類型用來設置DMA的緩沖區和解析接收數據的緩沖區,上面已經給出具體定義void EnableUartIT(void)
{//串口1__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);//打開UART1接收中斷(接收寄存器不為空則產生中斷)HAL_UART_Receive_IT(&huart1, &Usart1Rx, 1);//UART1接收使能,Usart1Rx:接收數據的緩存區//串口2//串口空閑中斷:當連續接收字符時不會產生中斷,但是如果中途出現一個字節的空閑則就產生中斷(避免每接收一個字節就出現一次中斷)__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);//需要注意:每次設備一上電就會產生一次空閑中斷__HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除UART2的空閑中斷標志HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuff,USART2_DMA_REC_SIZE);//開啟DMA的接收/*(以上三行代碼功能:將uart2接收到的數據通過DMA傳遞給Usart2type.Usart2DMARecBuff,然后產生串口空閑中斷,在中斷中做進一步處理)*/
}
extern void EnableUartIT(void);
?
EnableUartIT();
?
?
extern DMA_HandleTypeDef hdma_usart2_rx;/*hdma_usart2_rx在CubeMX中添加UART2的DMA時創建的一個變量,在uart.c中定義,所以需要外部聲明之后才能在該文件中使用*/void USART2_IRQHandler(void)
{uint32_t temp;if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) == SET)//判斷UART2是否為空閑中斷{__HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除空閑中斷標志位HAL_UART_DMAStop(&huart2);//停止DMA接收數據temp = huart2.Instance->ISR;temp = huart2.Instance->RDR;//以上兩行代碼用于清除DMA的接收中斷(只需要讀取一次ISR和RDR寄存器的值)temp = USART2_DMA_REC_SIZE - hdma_usart2_rx.Instance->CNDTR;/*CNDTR為DMA通道接收數據的計數器(注意是一個遞減計數器,所以需要將DMA的緩存區的總長度減去該計數器的值才是DMA通道接收數據的長度)*/Usart2type.Usart2DMARecLen = temp;//將DMA接收數據的長度賦值給上面定義的結構體變量HAL_UART_RxCpltCallback(&huart2);//串口中斷的回調函數,具體函數見下方}HAL_UART_IRQHandler(&huart2);HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuff,USART2_DMA_REC_SIZE);
}void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口中斷的回調函數
{if(huart->Instance == USART2){if(Usart2type.Usart2RecLen > 0)//判斷解析緩存區是否有未處理的數據{memcpy(&Usart2type.Usart2RecBuff[Usart2type.Usart2RecLen],Usart2type.Usart2DMARecBuff,Usart2type.Usart2DMARecLen);Usart2type.Usart2RecLen += Usart2type.Usart2DMARecLen;}else{memcpy(Usart2type.Usart2RecBuff,Usart2type.Usart2DMARecBuff,Usart2type.Usart2DMARecLen);Usart2type.Usart2RecLen = Usart2type.Usart2DMARecLen;}memset(Usart2type.Usart2DMARecBuff,0,Usart2type.Usart2DMARecLen);//清空DMA的接收緩存區Usart2type.Usart2RecFlag = 1;//數據標志位的置位}
}
void ATRec(void)
{if(Usart2type.Usart2RecFlag)//是否接收到一個完整的數據包{//判斷解析緩存區中是否存在對應指令返回的正確參數(字符串),strstr的使用方法見下方if(strstr((const char*)Usart2type.Usart2RecBuff,ATCmds[ATRecCmdNum].ATRecStr) != NULL){ATCmds[ATRecCmdNum].ATStatus = SUCCESS_REC;//接收狀態賦值為成功}SetLedRun(LED_RX);//打開接收指示燈,配合LedTask函數的使用可以產生一個100毫秒的亮燈,具體見下文HAL_UART_Transmit(&huart1,Usart2type.Usart2RecBuff,Usart2type.Usart2RecLen,100);//打印到串口Usart2type.Usart2RecFlag = 0;//清空標志位Usart2type.Usart2RecLen = 0;//設置解析緩存區字符串長度為0}
}strstr: 原型:extern char *strstr(char *haystack, char *needle);用法:#include <string.h>功能:從字符串haystack中尋找needle第一次出現的位置(不比較結束符NULL)。說明:返回指向第一次出現needle位置的指針,如果沒找到則返回NULL。
NB-IoT驅動開發二
1、軟件定時器設計
2、LED驅動設計
軟件定時器設計
軟件定時器需求:
AT指令超時判斷
定時采集傳感器
定時上報數據
軟件定時器設計:(新建兩個文件 time.c 和 time.h 用于存儲定時器相關)
設置定時器
比較定時器
參考HAL_Delay:
設置定時器:
比較定時器:
#ifndef _TIME_H
#define _TIME_H#include "stm32f0xx.h"typedef struct
{uint32_t TimeStart;//獲取起始時間uint32_t TimeInter;//間隔時間
}tsTimeType;void SetTime(tsTimeType *TimeType,uint32_t TimeInter);//打開超時定時器
uint8_t CompareTime(tsTimeType *TimeType);//比較函數
#endif
#include "time.h"void SetTime(tsTimeType *TimeType,uint32_t TimeInter)
{TimeType->TimeStart = HAL_GetTick();//獲取起始時間TimeType->TimeInter = TimeInter;//獲取間隔時間
}
uint8_t CompareTime(tsTimeType *TimeType)//每隔1毫秒,計數器就會增加1
{return ((HAL_GetTick()-TimeType->TimeStart) >= TimeType->TimeInter);
}
LED驅動設計
LED需求:
網絡指示燈
接收指示燈
發送指示燈
LED設計:(新建兩個文件 led.h 和 led.c 用于存儲led相關)
LED打開
LED關閉
LED初始化
LED觸發閃爍
LED閃爍任務
LED數據結構:
LED數量(入網、發送、接收)
LED閃爍任務狀態(運行、延時、停止)
LED GPIO封裝(用數組表示LED IO信息)
LED打開/關閉/初始化:
根據原理圖,LED為低電平驅動,上電要全部關閉:
打開->HAL_GPIO_WritePin(X,X,RESET)
關閉-> HAL_GPIO_WritePin(X,X,SET)
LED觸發閃爍:
設置LED狀態為運行
開啟LED定時器
LED閃爍任務:
#ifndef _LED_H
#define _LED_H#include "stm32f0xx.h"#define LED_NUMBER 3 //定義LED數量typedef enum //枚舉類型,LED對應功能
{LED_NET = 0,LED_RX,LED_TX
}teLedNums;typedef enum//LED閃爍任務狀態
{LED_STOP = 0,LED_RUN,LED_DELAY
}teLedTaskStatus;void LedOn(teLedNums LedNums);//打開LED燈
void LedOff(teLedNums LedNums);//關閉LED燈
void LedInit(void);//LED燈的初始化void SetLedRun(teLedNums LedNums);//設置LED為運行態void LedTask(void);//指示燈如果在運行態在,則閃爍一次#endif
#include "led.h"
#include "time.h"const uint16_t LedPins[LED_NUMBER] =
{GPIO_PIN_0, //對應LED_NETGPIO_PIN_1, //對應LED_RXGPIO_PIN_2 //對應LED_TX
};static tsTimeType TimeLeds[LED_NUMBER];//獲取起始時間和間隔時間static teLedTaskStatus LedTaskStatus[LED_NUMBER];//LED任務狀態
void LedOn(teLedNums LedNums) //打開對應LED燈
{HAL_GPIO_WritePin(GPIOB,LedPins[LedNums],GPIO_PIN_RESET);
}void LedOff(teLedNums LedNums) //關閉對應LED燈
{HAL_GPIO_WritePin(GPIOB,LedPins[LedNums],GPIO_PIN_SET);
}void LedInit(void)//LED燈初始化,關閉所有燈
{int i;for(i = 0;i < LED_NUMBER;i++){LedOff(i);}
}void SetLedRun(teLedNums LedNums)//設置對應LED為運行態
{LedTaskStatus[LedNums] = LED_RUN;
}void LedTask(void)//指示燈如果在運行態在,則閃爍一次
{int i;for(i = 0;i < LED_NUMBER;i++){if(LedTaskStatus[i] == LED_RUN){LedOn(i);SetTime(&TimeLeds[i],100);LedTaskStatus[i] = LED_DELAY;}else if(LedTaskStatus[i] == LED_DELAY){ if(CompareTime(&TimeLeds[i])){LedOff(i);LedTaskStatus[i] = LED_STOP;}}}
}
?
#include "time.h"
#include "led.h"static tsTimeType TimeNB;//獲取定時器的起始時間和時間間隔void ATSend(teATCmdNum ATCmdNum)
{//清空接收緩存區memset(Usart2type.Usart2RecBuff,0,USART2_REC_SIZE);ATCmds[ATCmdNum].ATStatus = NO_REC;ATRecCmdNum = ATCmdNum;if(ATCmdNum == AT_NMGS){memset(NbSendData,0,100);sprintf(NbSendData,"%s%d,%x%x\r\n",ATCmds[ATCmdNum].ATSendStr,2,0x10,0x10);HAL_UART_Transmit(&huart2,(uint8_t*)NbSendData,strlen(NbSendData),100);HAL_UART_Transmit(&huart1,(uint8_t*)NbSendData,strlen(NbSendData),100);}else{HAL_UART_Transmit(&huart2,(uint8_t*)ATCmds[ATCmdNum].ATSendStr,strlen(ATCmds[ATCmdNum].ATSendStr),100);HAL_UART_Transmit(&huart1,(uint8_t*)ATCmds[ATCmdNum].ATSendStr,strlen(ATCmds[ATCmdNum].ATSendStr),100);}//打開超時定時器,這里主要用來判斷接收超時使用SetTime(&TimeNB,ATCmds[ATCmdNum].TimeOut);//獲取定時器的起始時間和時間間隔//打開發送指示燈,配合LedTask函數的使用可以產生一個100毫秒的亮燈//如果100毫秒之內又有數據發送,則定時器重新計時,LED燈繼續延長點亮時間SetLedRun(LED_TX);
}
void ATRec(void)//AT接收字符串的解析
{if(Usart2type.Usart2RecFlag)//是否接收到一個完整的數據包{//判斷解析緩存區中是否存在對應指令返回的正確參數(字符串),strstr的使用方法見下方if(strstr((const char*)Usart2type.Usart2RecBuff,ATCmds[ATRecCmdNum].ATRecStr) != NULL){ATCmds[ATRecCmdNum].ATStatus = SUCCESS_REC;//接收狀態賦值為成功}SetLedRun(LED_RX);//打開接收指示燈,配合LedTask函數的使用可以產生一個100毫秒的亮燈HAL_UART_Transmit(&huart1,Usart2type.Usart2RecBuff,Usart2type.Usart2RecLen,100);//打印到串口Usart2type.Usart2RecFlag = 0;//清空標志位Usart2type.Usart2RecLen = 0;//設置解析緩存區字符串長度為0}
}strstr: 原型:extern char *strstr(char *haystack, char *needle);用法:#include <string.h>功能:從字符串haystack中尋找needle第一次出現的位置(不比較結束符NULL)。說明:返回指向第一次出現needle位置的指針,如果沒找到則返回NULL。
main函數中測試:...LedInit();//初始化:所有燈關閉
HAL_Delay(2000);//延時2秒
SetLedRun(0);//設置入網指示燈為運行態
SetLedRun(1);//設置接收指示燈為運行態
SetLedRun(2);//設置發送指示燈為運行態while(1)
{LedTask();
}...
?NB-IoT入網開發
1、NB-IoT入網流程
2、NB-IoT入網設計
NB-IoT入網流程
NB-IoT模塊驅動流程:
AT+CFUN=0?關閉射頻功能(不進行無線通訊)
AT+CGSN=1?查詢IMEI號(一般出廠已經設置好)
AT+NRB?軟重啟
AT+NCDP=180.101.147.115,5683?設置?IoT 平臺?IP 地址(非?COAP 協議可以不配置)
AT+CFUN=1?開啟射頻功能
AT+CIMI?查詢SIM卡信息
AT+CMEE=1?開啟錯誤提示
AT+CGDCONT=1,“IP”,“ctnb”?設置APN
AT+NNMI=1?開啟下行數據通知
AT+CGATT=1?自動搜網
AT+CGPADDR?查詢核心網分配的?ip 地址
NB-IoT入網設計
NB-IoT入網任務算法:
有限狀態機編程
? ? -->裸機編程效率最高的編程模式
入網任務狀態機(空閑、指令發送,等待響應、入網完成)
typedef enum //NB任務的狀態
{NB_IDIE = 0, //NB空閑NB_SEND, //NB的發送NB_WAIT, //NB的等待NB_ACCESS //NB的入網完成
}teNB_TaskStatus;
指令發送:
等待響應:
AT超時:
NB初始化:
入網完成:
static uint8_t CurrentRty;//當前重發的次數
static teATCmdNum ATRecCmdNum;//
static teATCmdNum ATCurrentCmdNum;//當前的指令
static teATCmdNum ATNextCmdNum;//下一條指令
static teNB_TaskStatus NB_TaskStatus;//聲明任務的狀態
static tsTimeType TimeNBSendData;//NB發送數據時的起始時間獲取 和 接收超時時間的設置void NB_Task(void)//NB不同任務狀態的處理程序,一般開始時NB_TaskStatus狀態為NB_SEND,故可以從NB_SEND開始分析
{while(1)//死循環,為了高效率處理{switch(NB_TaskStatus){case NB_IDIE://if(CompareTime(&TimeNBSendData))//判斷發送指令是否超時//{// ATCurrentCmdNum = AT_NMGS;//當前指令設置為AT_NMGS// ATNextCmdNum = AT_IDIE;//下一條指令設置為空閑指令// NB_TaskStatus = NB_SEND;//任務狀態設置為發送// SetTime(&TimeNBSendData,10000);//發送超時設置// break;//跳轉到發送狀態//}return;case NB_SEND:if(ATCurrentCmdNum != ATNextCmdNum)//如果當前指令不等于下一條指令,則該指令是第一次運行{CurrentRty = ATCmds[ATCurrentCmdNum].RtyNum;//獲取當前指令的重發次數}ATSend(ATCurrentCmdNum);//發送指令NB_TaskStatus = NB_WAIT;//更改為等待態return;//因為有超時case NB_WAIT:ATRec();//AT接收字符串的解析if(ATCmds[ATCurrentCmdNum].ATStatus == SUCCESS_REC)//判斷接收狀態是否成功{if(ATCurrentCmdNum == AT_CGPADDR)//判斷當前指令是否為入網指令{NB_TaskStatus = NB_ACCESS;//如果是則狀態設置為入網完成break;//跳轉指令}// else if(ATCurrentCmdNum == AT_NMGS)//判斷當前指令是否為AT_NMGS指令// {// NB_TaskStatus = NB_IDIE;//設置任務狀態為空閑狀態// return;// }else{ATCurrentCmdNum += 1;//如果不是入網指令,則當前指令加1ATNextCmdNum = ATCurrentCmdNum+1;//下一條指令在當前指令的基礎上再加1NB_TaskStatus = NB_SEND;//設置為發送狀態break;//跳轉指令}}else if(CompareTime(&TimeNB))//判斷發送指令之后接收是否超時{ATCmds[ATCurrentCmdNum].ATStatus = TIME_OUT;//改變當前指令的狀態:設置超時if(CurrentRty > 0)//判斷當前重發的次數是否大于零{CurrentRty--;ATNextCmdNum = ATCurrentCmdNum;//下一條指令等于當前指令NB_TaskStatus = NB_SEND;//改變任務狀態為發送狀態break;//跳轉到發送狀態的處理程序}else//否則重發次數已經達到最高的重發次數限制{NB_Init();//NB初始化,函數具體實現見下方return;}}return;case NB_ACCESS://如果是入網完成的狀態LedOn(LED_NET);//打開入網完成的指示燈NB_TaskStatus = NB_IDIE;//任務狀態設置為空閑狀態break;//跳轉到空閑狀態// ATCurrentCmdNum = AT_NMGS;//當前指令設置為AT_NMGS// ATNextCmdNum = AT_IDIE;//下一條指令設置為空閑指令// NB_TaskStatus = NB_SEND;//任務狀態設置為發送狀態// SetTime(&TimeNBSendData,10000);//發送指令超時設置// break;//跳轉到發送狀態default:return;}}
}void NB_Init(void)//NB初始化
{NB_TaskStatus = NB_SEND;//任務狀態設置為發送狀態ATCurrentCmdNum = AT_CFUN0;//當前指令設置為第一條指令ATNextCmdNum = AT_CGSN;//下一條指令設置為第二條指令
}
主程序:測試
main函數中測試#include "nbiotdriver.h"...NB_Init();//NB初始化printf("wait 5s NB Reset!\n");
HAL_Delay(5000);//初始化時需等待NB芯片重啟(這里設置5秒鐘等待時間)while (1){LedTask();NB_Task();}...
NB-IoT網絡CoAP通信
1、發展背景
2、HTTP協議
3、CoAP協議
4、NB-IoT CoAP通信開發
發展背景
互聯網:
移動互聯網:
物聯網:
新的協議:
HTTP協議
HTTP介紹:
什么是超文本(HyperText)?
包含有超鏈接(Link)和各種多媒體元素標記(Markup)的文本。這些超文本文件彼此鏈接,形成網狀(Web),因此又被稱為網頁(Web Page)。這些鏈接使用URL表示。最常見的超文本格式是超文本標記語言HTML。
什么是URL?
URL即統一資源定位符(Uniform Resource Locator),用來唯一地標識萬維網中的某一個文檔。URL由協議、主機和端口(默認為80)以及文件名三部分構成。如:
什么是超文本傳輸協議HTTP?
是一種按照URL指示,將超文本文檔從一臺主機(Web服務器)傳輸到另一臺主機(瀏覽器)的應用層協議,以實現超鏈接的功能。
HTTP工作原理:
請求/響應交互模型
在用戶點擊URL為http://www.makeru.com.cn//course/3172.html的鏈接后,瀏覽器和Web服務器執行以下動作:
1、瀏覽器分析超鏈接中的URL
2、瀏覽器向DNS請求解析www.makeru.com.cn的IP地址
3、DNS將解析出的IP地址202.2.16.21返回瀏覽器
4、瀏覽器與服務器建立TCP連接(80端口)
5、瀏覽器請求文檔:GET /index.html
6、服務器給出響應,將文檔?index.html發送給瀏覽器
7、釋放TCP連接
8、瀏覽器顯示index.html中的內容
HTTP報文結構:
請求報文:即從客戶端(瀏覽器)向Web服務器發送的請求報文。報文的所有字段都是ASCII碼。
響應報文:即從Web服務器到客戶機(瀏覽器)的應答。報文的所有字段都是ASCII碼。
請求報文中的方法:方法(Method)是對所請求對象所進行的操作,也就是一些命令。請求報文中的操作有:
CoAP協議
CoAP是什么:
CoAP是IETF為滿足物聯網,M2M場景制定的協議,特點如下:
類似HTTP,基于REST模型:Servers將Resource通過URI形式呈現,客戶端可以通過諸如GET,PUT,POST,DELETE方法訪問,但是相對HTTP簡化實現降低復雜度(代碼更小,封包更小)
應用于資源受限的環境(內存,存儲,無良好的隨機源),比如CPU為8-bit的單片機,內存32Kb,FLASH 256Kb
針對業務性能要求不高的應用:低速率(10s of kbit/s),低功耗
滿足CoRE環境的HTTP簡化增強版本
CoAP協議模型:
邏輯上分為Message和Request/Response兩層,Request/Response通過Message承載,從封包上不體現這種層次結構
基于UDP,支持組播
基于UDP的類似HTTP的Client/Server交互模型
Client發送Request(攜帶不同method)請求對資源(通過URI表示)的操作,Server返回Response(攜帶資源的representation)和狀態碼
在M2M應用場景,Endpoint實際同時是Server和Client
NB-IoT CoAP通信開發
NB-IoT CoAP通信開發:
COAP數據收發:
CoAP 數據發送無需事先建立?socket(模組內部處理) , 直接發送數據:AT+NMGS=2,A1A2 發送?2 字節數據, 發送成功回復?OK, 否則?ERROR
讀取?CoAP 數據:+NNMI:2,A1A2 收到?2 字節?CoAP 數據
NB-IoT CoAP通信開發流程:
tsATCmds ATCmds[] =
{{"AT+CFUN=0\r\n","OK",2000,NO_REC,3},{"AT+CGSN=1\r\n","OK",2000,NO_REC,3},{"AT+NRB\r\n","OK",8000,NO_REC,3},{"AT+NCDP=180.101.147.115,5683\r\n","OK",2000,NO_REC,3},{"AT+CFUN=1\r\n","OK",2000,NO_REC,3},{"AT+CIMI\r\n","OK",2000,NO_REC,3},{"AT+CMEE=1\r\n","OK",2000,NO_REC,3},{"AT+CGDCONT=1,\"IP\",\"ctnb\"\r\n","OK",2000,NO_REC,3},{"AT+NNMI=1\r\n","OK",2000,NO_REC,3},{"AT+CGATT=1\r\n","OK",2000,NO_REC,3},{"AT+CGPADDR\r\n","+CGPADDR:1,1",2000,NO_REC,30},{"AT+NMGS=","OK",3000,NO_REC,3},//發送數據的指令,注意:發送數據是可變的,所以指令后面沒有 "\r\t"
};
typedef enum
{AT_CFUN0 = 0,AT_CGSN,AT_NRB,AT_NCDP,AT_CFUN1,AT_CIMI,AT_CMEE,AT_CGDCONT,AT_NNMI,AT_CGATT,AT_CGPADDR,AT_NMGS,AT_IDIE//因為AT_NMGS為最后一個指令,所以這里添加一個空閑指令標記
}teATCmdNum;
#include "time.h"
#include "led.h"static tsTimeType TimeNB;//獲取定時器的起始時間和時間間隔char NbSendData[100];//發送數據指令中數據的存儲區void ATSend(teATCmdNum ATCmdNum)
{//清空接收緩存區memset(Usart2type.Usart2RecBuff,0,USART2_REC_SIZE);ATCmds[ATCmdNum].ATStatus = NO_REC;ATRecCmdNum = ATCmdNum;if(ATCmdNum == AT_NMGS)//判斷是否發送數據的指令{memset(NbSendData,0,100);//清空數據的存儲區//第一個%s為發送數據的指令:"AT+NMGS="//第二個%d為發送數據的個數是兩個(字節的長度)//第三和第四個%x是兩個要發送的16進制的數據//最終得到NbSendData的數據為:AT+NMGS=2,0x10,0x10\r\nsprintf(NbSendData,"%s%d,%x%x\r\n",ATCmds[ATCmdNum].ATSendStr,2,0x10,0x10);HAL_UART_Transmit(&huart2,(uint8_t*)NbSendData,strlen(NbSendData),100);//發送NbSendData到NB芯片HAL_UART_Transmit(&huart1,(uint8_t*)NbSendData,strlen(NbSendData),100);//發送NbSendData到串口1,用于調試}else{HAL_UART_Transmit(&huart2,(uint8_t*)ATCmds[ATCmdNum].ATSendStr,strlen(ATCmds[ATCmdNum].ATSendStr),100);HAL_UART_Transmit(&huart1,(uint8_t*)ATCmds[ATCmdNum].ATSendStr,strlen(ATCmds[ATCmdNum].ATSendStr),100);}//打開超時定時器,這里主要用來判斷接收超時使用SetTime(&TimeNB,ATCmds[ATCmdNum].TimeOut);//獲取定時器的起始時間和時間間隔,具體見下面講解//打開發送指示燈,配合LedTask函數的使用可以產生一個100毫秒的亮燈,具體函數下文有講解//如果100毫秒之內又有數據發送,則定時器重新計時,LED燈繼續延長點亮時間SetLedRun(LED_TX);
}
static uint8_t CurrentRty;//當前重發的次數
static teATCmdNum ATRecCmdNum;//
static teATCmdNum ATCurrentCmdNum;//當前的指令
static teATCmdNum ATNextCmdNum;//下一條指令
static teNB_TaskStatus NB_TaskStatus;//聲明任務的狀態
static tsTimeType TimeNBSendData;//NB發送數據時的起始時間獲取 和 接收超時時間的設置void NB_Task(void)//NB不同任務狀態的處理程序,一般開始時NB_TaskStatus狀態為NB_SEND,故可以從NB_SEND開始分析
{while(1)//死循環,為了高效率處理{switch(NB_TaskStatus){case NB_IDIE:if(CompareTime(&TimeNBSendData))//判斷發送指令是否超時{ATCurrentCmdNum = AT_NMGS;//當前指令設置為發送數據指令ATNextCmdNum = AT_IDIE;//下一條指令設置為空閑指令NB_TaskStatus = NB_SEND;//任務狀態設置為發送SetTime(&TimeNBSendData,10000);//每隔10秒發送一次數據break;//跳轉到發送狀態}return;case NB_SEND:if(ATCurrentCmdNum != ATNextCmdNum)//如果當前指令不等于下一條指令,則該指令是第一次運行{CurrentRty = ATCmds[ATCurrentCmdNum].RtyNum;//獲取當前指令的重發次數}ATSend(ATCurrentCmdNum);//發送指令NB_TaskStatus = NB_WAIT;//更改為等待態return;//因為有超時case NB_WAIT:ATRec();//AT接收字符串的解析if(ATCmds[ATCurrentCmdNum].ATStatus == SUCCESS_REC)//判斷接收狀態是否成功{if(ATCurrentCmdNum == AT_CGPADDR)//判斷當前指令是否為入網指令{NB_TaskStatus = NB_ACCESS;//如果是則狀態設置為入網完成break;//跳轉指令}else if(ATCurrentCmdNum == AT_NMGS)//判斷當前指令是否為發送數據指令{NB_TaskStatus = NB_IDIE;//設置任務狀態為空閑狀態return;}else{ATCurrentCmdNum += 1;//如果不是入網指令,則當前指令加1ATNextCmdNum = ATCurrentCmdNum+1;//下一條指令在當前指令的基礎上再加1NB_TaskStatus = NB_SEND;//設置為發送狀態break;//跳轉指令}}else if(CompareTime(&TimeNB))//判斷發送指令之后接收是否超時{ATCmds[ATCurrentCmdNum].ATStatus = TIME_OUT;//改變當前指令的狀態:設置超時if(CurrentRty > 0)//判斷當前重發的次數是否大于零{CurrentRty--;ATNextCmdNum = ATCurrentCmdNum;//下一條指令等于當前指令NB_TaskStatus = NB_SEND;//改變任務狀態為發送狀態break;//跳轉到發送狀態的處理程序}else//否則重發次數已經達到最高的重發次數限制{NB_Init();//NB初始化,函數具體實現見下方return;}}return;case NB_ACCESS://如果是入網完成的狀態LedOn(LED_NET);//打開入網完成的指示燈ATCurrentCmdNum = AT_NMGS;//當前指令設置為發送數據的指令ATNextCmdNum = AT_IDIE;//下一條指令設置為空閑指令NB_TaskStatus = NB_SEND;//任務狀態設置為發送狀態SetTime(&TimeNBSendData,10000);//發送指令超時設置break;//跳轉到發送狀態default:return;}}
}void NB_Init(void)//NB初始化
{NB_TaskStatus = NB_SEND;//任務狀態設置為發送狀態ATCurrentCmdNum = AT_CFUN0;//當前指令設置為第一條指令ATNextCmdNum = AT_CGSN;//下一條指令設置為第二條指令
}
?
總結
以上是生活随笔為你收集整理的物联网之NB-IoT技术实践开发三(NB-IoT网络编程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网络带宽和速度测试windows和lin
- 下一篇: 开发者神器!Windows上最强大的虚拟