概述
NB-IoT驱动开发一
1、驱动框架设计
2、AT指令发送
3、AT指令接收
驱动框架设计
数据结构设计:
如何用数据结构来完成AT指令的发送、应答、超时、状态、重发:(新建 nbiotdriver.h 和 nbiotdriver.c 文件用于AT相关)
发送->其实就是发送字符串“ATrn”
解析->其实就是接收字符串“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=0rn",
"OK",
2000,NO_REC,
3},
//关闭射频功能(不进行无线通讯)
-
{
"AT+CGSN=1rn",
"OK",
2000,NO_REC,
3},
//查询IMEI号(一般出厂已经设置好)
-
{
"AT+NRBrn",
"OK",
8000,NO_REC,
3},
//软重启。重启使用时间比较长,所以这里设置为8秒钟
-
{
"AT+NCDP=180.101.147.115,5683rn",
"OK",
2000,NO_REC,
3},
//设置 IoT 平台 IP 地址(非 COAP 协议可以不配置)
-
{
"AT+CFUN=1rn",
"OK",
2000,NO_REC,
3},
//开启射频功能
-
{
"AT+CIMIrn",
"OK",
2000,NO_REC,
3},
//查询SIM卡信息
-
{
"AT+CMEE=1rn",
"OK",
2000,NO_REC,
3},
//开启错误提示
-
{
"AT+CGDCONT=1,"IP","ctnb"rn",
"OK",
2000,NO_REC,
3},
//设置APN.注意AT命令中字符串的书写格式:"IP","ctnb"(表示电信)
-
{
"AT+NNMI=1rn",
"OK",
2000,NO_REC,
3},
//开启下行数据通知
-
{
"AT+CGATT=1rn",
"OK",
2000,NO_REC,
3},
//自动搜网
-
{
"AT+CGPADDRrn",
"+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=0rn","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,0x10rn
-
sprintf(NbSendData,
"%s%d,%x%xrn",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_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;
-
}
-
}
-
}
-
}
-
#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%xrn",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;
//如果不是入网指令,则当前指令加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;
//下一条指令设置为第二条指令
-
}
主程序:测试
-
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=0rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+CGSN=1rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+NRBrn",
"OK",
8000,NO_REC,
3},
-
{
"AT+NCDP=180.101.147.115,5683rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+CFUN=1rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+CIMIrn",
"OK",
2000,NO_REC,
3},
-
{
"AT+CMEE=1rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+CGDCONT=1,"IP","ctnb"rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+NNMI=1rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+CGATT=1rn",
"OK",
2000,NO_REC,
3},
-
{
"AT+CGPADDRrn",
"+CGPADDR:1,1",
2000,NO_REC,
30},
-
{
"AT+NMGS=",
"OK",
3000,NO_REC,
3},
//发送数据的指令,注意:发送数据是可变的,所以指令后面没有 "rt"
-
};
-
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,0x10rn
-
sprintf(NbSendData,
"%s%d,%x%xrn",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;
//如果不是入网指令,则当前指令加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;
//下一条指令设置为第二条指令
-
}
最后
以上就是大力香水为你收集整理的物联网之NB-IoT技术实践开发三的全部内容,希望文章能够帮你解决物联网之NB-IoT技术实践开发三所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复