NB-IoT驱动开发一

1、驱动框架设计

2、AT指令发送

3、AT指令接收

驱动框架设计

Nocodb 技术架构 nb-iot架构_#include

数据结构设计

如何用数据结构来完成AT指令的发送、应答、超时、状态、重发:(新建 nbiotdriver.h 和 nbiotdriver.c 文件用于AT相关)

发送->其实就是发送字符串“AT\r\n”

解析->其实就是接收字符串“OK”

超时->其实就是超时时间

状态->其实就是 成功,超时,未收到

重发->其实就是 重发次数

Nocodb 技术架构 nb-iot架构_#include_02

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 地址

Nocodb 技术架构 nb-iot架构_IT_03

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指令发送

Nocodb 技术架构 nb-iot架构_缓存_04

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_05

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;

Nocodb 技术架构 nb-iot架构_IT_06

#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\n
		sprintf(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指令接收

串口接收

Nocodb 技术架构 nb-iot架构_IT_07

串口回调函数

Nocodb 技术架构 nb-iot架构_#include_08

AT指令解析

Nocodb 技术架构 nb-iot架构_缓存_09

 

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_10

#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中声明,但是在其他文件中需要使用,所以需要外部声明

Nocodb 技术架构 nb-iot架构_#include_11

//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:接收数据的缓存区

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_12

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,然后产生串口空闲中断,
    在中断中做进一步处理)*/
}

Nocodb 技术架构 nb-iot架构_IT_13

extern void EnableUartIT(void);

 

Nocodb 技术架构 nb-iot架构_缓存_14

EnableUartIT();

 

Nocodb 技术架构 nb-iot架构_缓存_15

Nocodb 技术架构 nb-iot架构_#include_16

 

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_17

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;//数据标志位的置位
	}
}

Nocodb 技术架构 nb-iot架构_缓存_18

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:

Nocodb 技术架构 nb-iot架构_缓存_19

设置定时器

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_20

比较定时器

Nocodb 技术架构 nb-iot架构_缓存_21

Nocodb 技术架构 nb-iot架构_缓存_22

#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

Nocodb 技术架构 nb-iot架构_IT_23

#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闪烁任务

Nocodb 技术架构 nb-iot架构_#include_24

Nocodb 技术架构 nb-iot架构_缓存_25

#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

Nocodb 技术架构 nb-iot架构_缓存_26

#include "led.h"
#include "time.h"

const uint16_t LedPins[LED_NUMBER] = 
{
	GPIO_PIN_0, //对应LED_NET
	GPIO_PIN_1, //对应LED_RX
	GPIO_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;
			}
		}
	}
}

 

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_27

#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);
}

Nocodb 技术架构 nb-iot架构_IT_28

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 地址

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_29

NB-IoT入网设计

NB-IoT入网任务算法

Nocodb 技术架构 nb-iot架构_#include_30

有限状态机编程

    -->裸机编程效率最高的编程模式

入网任务状态机(空闲、指令发送,等待响应、入网完成)

Nocodb 技术架构 nb-iot架构_IT_31

typedef enum //NB任务的状态
{
	NB_IDIE = 0, //NB空闲
	NB_SEND,   //NB的发送
	NB_WAIT,  //NB的等待
	NB_ACCESS  //NB的入网完成
}teNB_TaskStatus;

指令发送

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_32

等待响应

Nocodb 技术架构 nb-iot架构_#include_33

AT超时

Nocodb 技术架构 nb-iot架构_#include_34

NB初始化

Nocodb 技术架构 nb-iot架构_缓存_35

入网完成

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_36

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_37

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;//如果不是入网指令,则当前指令加1
						
						ATNextCmdNum = ATCurrentCmdNum+1;//下一条指令在当前指令的基础上再加1
						NB_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;//下一条指令设置为第二条指令
}

主程序:测试

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_38

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通信开发

发展背景

互联网

Nocodb 技术架构 nb-iot架构_#include_39

移动互联网

Nocodb 技术架构 nb-iot架构_IT_40

物联网

Nocodb 技术架构 nb-iot架构_#include_41

新的协议

Nocodb 技术架构 nb-iot架构_IT_42

Nocodb 技术架构 nb-iot架构_#include_43

HTTP协议

HTTP介绍:

Nocodb 技术架构 nb-iot架构_缓存_44

什么是超文本(HyperText)?

包含有超链接(Link)和各种多媒体元素标记(Markup)的文本。这些超文本文件彼此链接,形成网状(Web),因此又被称为网页(Web Page)。这些链接使用URL表示。最常见的超文本格式是超文本标记语言HTML。

什么是URL?

URL即统一资源定位符(Uniform Resource Locator),用来唯一地标识万维网中的某一个文档。URL由协议、主机和端口(默认为80)以及文件名三部分构成。如:

Nocodb 技术架构 nb-iot架构_缓存_45

什么是超文本传输协议HTTP?

是一种按照URL指示,将超文本文档从一台主机(Web服务器)传输到另一台主机(浏览器)的应用层协议,以实现超链接的功能。

HTTP工作原理

Nocodb 技术架构 nb-iot架构_IT_46

请求/响应交互模型

在用户点击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码。

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_47

响应报文:即从Web服务器到客户机(浏览器)的应答。报文的所有字段都是ASCII码。

Nocodb 技术架构 nb-iot架构_IT_48

请求报文中的方法:方法(Method)是对所请求对象所进行的操作,也就是一些命令。请求报文中的操作有:

Nocodb 技术架构 nb-iot架构_缓存_49

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协议模型

Nocodb 技术架构 nb-iot架构_#include_50

逻辑上分为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通信开发流程

Nocodb 技术架构 nb-iot架构_IT_51

Nocodb 技术架构 nb-iot架构_缓存_52

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"
};

Nocodb 技术架构 nb-iot架构_缓存_53

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;

Nocodb 技术架构 nb-iot架构_IT_54

#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\n
		sprintf(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);
}

Nocodb 技术架构 nb-iot架构_Nocodb 技术架构_37

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;//如果不是入网指令,则当前指令加1
						
						ATNextCmdNum = ATCurrentCmdNum+1;//下一条指令在当前指令的基础上再加1
						NB_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;//下一条指令设置为第二条指令
}