物联网之NB-IoT技术实践开发三

NB-IoT驱动开发一

1、驱动框架设计

2、AT指令发送

3、AT指令接收

驱动框架设计

数据结构设计

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

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

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

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

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

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


 
 
  1. typedef enum //枚举类型:接收的状态
  2. {
  3. SUCCESS_REC = 0, //成功
  4. TIME_OUT, //超时
  5. NO_REC //未收到
  6. }teATStatus;
  7. typedef struct //定义的数据结构,用数据结构来完成AT指令的发送、应答、超时、状态、重发
  8. {
  9. char *ATSendStr; //向NB-IOT发送字符串(AT命令)
  10. char *ATRecStr; //NB-IOT返回给MCU的字符串
  11. uint16_t TimeOut; //设置超时
  12. teATStatus ATStatus; //接收状态
  13. uint8_t RtyNum; //重发次数
  14. }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 地址


 
 
  1. tsATCmds ATCmds[] =
  2. {
  3. //参数分别为 向NB-IOT发送字符串(AT命令)、NB-IOT返回给MCU的字符串、设置超时(毫秒)、接收状态、设置重发次数
  4. { "AT+CFUN=0\r\n", "OK", 2000,NO_REC, 3}, //关闭射频功能(不进行无线通讯)
  5. { "AT+CGSN=1\r\n", "OK", 2000,NO_REC, 3}, //查询IMEI号(一般出厂已经设置好)
  6. { "AT+NRB\r\n", "OK", 8000,NO_REC, 3}, //软重启。重启使用时间比较长,所以这里设置为8秒钟
  7. { "AT+NCDP=180.101.147.115,5683\r\n", "OK", 2000,NO_REC, 3}, //设置 IoT 平台 IP 地址(非 COAP 协议可以不配置)
  8. { "AT+CFUN=1\r\n", "OK", 2000,NO_REC, 3}, //开启射频功能
  9. { "AT+CIMI\r\n", "OK", 2000,NO_REC, 3}, //查询SIM卡信息
  10. { "AT+CMEE=1\r\n", "OK", 2000,NO_REC, 3}, //开启错误提示
  11. { "AT+CGDCONT=1,\"IP\",\"ctnb\"\r\n", "OK", 2000,NO_REC, 3}, //设置APN.注意AT命令中字符串的书写格式:\"IP\",\"ctnb\"(表示电信)
  12. { "AT+NNMI=1\r\n", "OK", 2000,NO_REC, 3}, //开启下行数据通知
  13. { "AT+CGATT=1\r\n", "OK", 2000,NO_REC, 3}, //自动搜网
  14. { "AT+CGPADDR\r\n", "+CGPADDR:1,1", 2000,NO_REC, 30}, //查询核心网分配的 ip 地址
  15. { "AT+NMGS=", "OK", 3000,NO_REC, 3}, //发送数据
  16. };

AT指令发送


 
 
  1. typedef enum //枚举类型,用于与上方的数组 ATCmds 下标相对应
  2. {
  3. AT_CFUN0 = 0, //ATCmds[AT_CFUN0] 就是 ATCmds[0],代表数据结构 {"AT+CFUN=0\r\n","OK",2000,NO_REC,3} 以下类推
  4. AT_CGSN,
  5. AT_NRB,
  6. AT_NCDP,
  7. AT_CFUN1,
  8. AT_CIMI,
  9. AT_CMEE,
  10. AT_CGDCONT,
  11. AT_NNMI,
  12. AT_CGATT,
  13. AT_CGPADDR,
  14. AT_NMGS,
  15. AT_IDIE
  16. }teATCmdNum;


 
 
  1. #include "time.h"
  2. #include "led.h"
  3. static tsTimeType TimeNB; //获取定时器的起始时间和时间间隔,具体见下面讲解
  4. char NbSendData[ 100]; //发送数据指令中数据的存储区
  5. void ATSend(teATCmdNum ATCmdNum)
  6. {
  7. //清空接收缓存区
  8. memset(Usart2type.Usart2RecBuff, 0,USART2_REC_SIZE);
  9. ATCmds[ATCmdNum].ATStatus = NO_REC;
  10. ATRecCmdNum = ATCmdNum; //ATRecCmdNum是在nbiotdriver.c中定义的静态全局变量
  11. if(ATCmdNum == AT_NMGS) //判断是否为发送数据的指令
  12. {
  13. memset(NbSendData, 0, 100); //清空数据的存储区
  14. //第一个%s为发送数据的指令:"AT+NMGS="
  15. //第二个%d为发送数据的个数是两个(字节的长度)
  16. //第三和第四个%x是两个要发送的16进制的数据
  17. //最终得到NbSendData的数据为:AT+NMGS=2,0x10,0x10\r\n
  18. sprintf(NbSendData, "%s%d,%x%x\r\n",ATCmds[ATCmdNum].ATSendStr, 2, 0x10, 0x10); //发送NbSendData到NB芯片
  19. HAL_UART_Transmit(&huart2,( uint8_t*)NbSendData, strlen(NbSendData), 100); //发送NbSendData到NB芯片
  20. HAL_UART_Transmit(&huart1,( uint8_t*)NbSendData, strlen(NbSendData), 100); //发送NbSendData到串口1,用于调试
  21. }
  22. else
  23. {
  24. HAL_UART_Transmit(&huart2,( uint8_t*)ATCmds[ATCmdNum].ATSendStr, strlen(ATCmds[ATCmdNum].ATSendStr), 100);
  25. HAL_UART_Transmit(&huart1,( uint8_t*)ATCmds[ATCmdNum].ATSendStr, strlen(ATCmds[ATCmdNum].ATSendStr), 100);
  26. }
  27. //打开超时定时器,这里主要用来判断接收超时使用
  28. SetTime(&TimeNB,ATCmds[ATCmdNum].TimeOut); //获取定时器的起始时间和时间间隔,具体见下面讲解
  29. //打开发送指示灯,配合LedTask函数的使用可以产生一个100毫秒的亮灯,具体函数下文有讲解
  30. //如果100毫秒之内又有数据发送,则定时器重新计时,LED灯继续延长点亮时间
  31. SetLedRun(LED_TX);
  32. }

AT指令接收

串口接收

串口回调函数

AT指令解析

 


 
 
  1. #define USART2_DMA_REC_SIZE 256
  2. #define USART2_REC_SIZE 1024
  3. typedef struct //用来处理DMA接收到的数据,解析缓存区的数据
  4. {
  5. uint8_t Usart2RecFlag; //数据接收到的标志位
  6. uint16_t Usart2DMARecLen; //获取接收DMA数据的长度
  7. uint16_t Usart2RecLen; //获取解析缓存区的长度
  8. uint8_t Usart2DMARecBuff[USART2_DMA_REC_SIZE]; //DMA的缓冲区
  9. uint8_t Usart2RecBuff[USART2_REC_SIZE]; //用于解析接收数据的缓存区
  10. }tsUsart2type;
  11. extern tsUsart2type Usart2type; //该变量在uart.c中声明,但是在其他文件中需要使用,所以需要外部声明


 
 
  1. //uint8_t Usart1Rx = 0;
  2. //uint8_t Usart2Rx = 0;
  3. // __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);//打开UART1接收中断(接收寄存器不为空则产生中断)
  4. //
  5. // HAL_UART_Receive_IT(&huart1, &Usart1Rx, 1);//UART1接收使能,Usart1Rx:接收数据的缓存区
  6. //
  7. // __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);//打开UART2接收中断(接收寄存器不为空则产生中断)
  8. //
  9. // HAL_UART_Receive_IT(&huart2, &Usart2Rx, 1);//UART2接收使能,Usart2Rx:接收数据的缓存区


 
 
  1. uint8_t Usart1Rx = 0;
  2. tsUsart2type Usart2type; //该数据类型用来设置DMA的缓冲区和解析接收数据的缓冲区,上面已经给出具体定义
  3. void EnableUartIT(void)
  4. {
  5. //串口1
  6. __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //打开UART1接收中断(接收寄存器不为空则产生中断)
  7. HAL_UART_Receive_IT(&huart1, &Usart1Rx, 1); //UART1接收使能,Usart1Rx:接收数据的缓存区
  8. //串口2
  9. //串口空闲中断:当连续接收字符时不会产生中断,但是如果中途出现一个字节的空闲则就产生中断(避免每接收一个字节就出现一次中断)
  10. __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); //需要注意:每次设备一上电就会产生一次空闲中断
  11. __HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除UART2的空闲中断标志
  12. HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuff,USART2_DMA_REC_SIZE); //开启DMA的接收
  13. /*(以上三行代码功能:将uart2接收到的数据通过DMA传递给Usart2type.Usart2DMARecBuff,然后产生串口空闲中断,
  14. 在中断中做进一步处理)*/
  15. }

extern void EnableUartIT(void);
 
 

 

EnableUartIT();
 
 

 

 


 
 
  1. extern DMA_HandleTypeDef hdma_usart2_rx; /*hdma_usart2_rx在CubeMX中添加UART2的DMA时创建的一个变量,在uart.c中定义,
  2. 所以需要外部声明之后才能在该文件中使用*/
  3. void USART2_IRQHandler(void)
  4. {
  5. uint32_t temp;
  6. if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) == SET) //判断UART2是否为空闲中断
  7. {
  8. __HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除空闲中断标志位
  9. HAL_UART_DMAStop(&huart2); //停止DMA接收数据
  10. temp = huart2.Instance->ISR;
  11. temp = huart2.Instance->RDR; //以上两行代码用于清除DMA的接收中断(只需要读取一次ISR和RDR寄存器的值)
  12. temp = USART2_DMA_REC_SIZE - hdma_usart2_rx.Instance->CNDTR; /*CNDTR为DMA通道接收数据的计数器(注意是一个递减计数器,
  13. 所以需要将DMA的缓存区的总长度减去该计数器的值才是DMA通道接收数据的长度)*/
  14. Usart2type.Usart2DMARecLen = temp; //将DMA接收数据的长度赋值给上面定义的结构体变量
  15. HAL_UART_RxCpltCallback(&huart2); //串口中断的回调函数,具体函数见下方
  16. }
  17. HAL_UART_IRQHandler(&huart2);
  18. HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuff,USART2_DMA_REC_SIZE);
  19. }
  20. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口中断的回调函数
  21. {
  22. if(huart->Instance == USART2)
  23. {
  24. if(Usart2type.Usart2RecLen > 0) //判断解析缓存区是否有未处理的数据
  25. {
  26. memcpy(&Usart2type.Usart2RecBuff[Usart2type.Usart2RecLen],Usart2type.Usart2DMARecBuff,Usart2type.Usart2DMARecLen);
  27. Usart2type.Usart2RecLen += Usart2type.Usart2DMARecLen;
  28. }
  29. else
  30. {
  31. memcpy(Usart2type.Usart2RecBuff,Usart2type.Usart2DMARecBuff,Usart2type.Usart2DMARecLen);
  32. Usart2type.Usart2RecLen = Usart2type.Usart2DMARecLen;
  33. }
  34. memset(Usart2type.Usart2DMARecBuff, 0,Usart2type.Usart2DMARecLen); //清空DMA的接收缓存区
  35. Usart2type.Usart2RecFlag = 1; //数据标志位的置位
  36. }
  37. }


 
 
  1. void ATRec(void)
  2. {
  3. if(Usart2type.Usart2RecFlag) //是否接收到一个完整的数据包
  4. {
  5. //判断解析缓存区中是否存在对应指令返回的正确参数(字符串),strstr的使用方法见下方
  6. if( strstr(( const char*)Usart2type.Usart2RecBuff,ATCmds[ATRecCmdNum].ATRecStr) != NULL)
  7. {
  8. ATCmds[ATRecCmdNum].ATStatus = SUCCESS_REC; //接收状态赋值为成功
  9. }
  10. SetLedRun(LED_RX); //打开接收指示灯,配合LedTask函数的使用可以产生一个100毫秒的亮灯,具体见下文
  11. HAL_UART_Transmit(&huart1,Usart2type.Usart2RecBuff,Usart2type.Usart2RecLen, 100); //打印到串口
  12. Usart2type.Usart2RecFlag = 0; //清空标志位
  13. Usart2type.Usart2RecLen = 0; //设置解析缓存区字符串长度为0
  14. }
  15. }
  16. strstr
  17. 原型: extern char *strstr(char *haystack, char *needle);
  18. 用法: #include <string.h>
  19. 功能:从字符串haystack中寻找needle第一次出现的位置(不比较结束符 NULL)。
  20. 说明:返回指向第一次出现needle位置的指针,如果没找到则返回 NULL

NB-IoT驱动开发二

1、软件定时器设计

2、LED驱动设计

软件定时器设计

软件定时器需求

AT指令超时判断

定时采集传感器

定时上报数据

软件定时器设计:(新建两个文件 time.c 和 time.h 用于存储定时器相关)

设置定时器

比较定时器

参考HAL_Delay:

设置定时器

比较定时器


 
 
  1. #ifndef _TIME_H
  2. #define _TIME_H
  3. #include "stm32f0xx.h"
  4. typedef struct
  5. {
  6. uint32_t TimeStart; //获取起始时间
  7. uint32_t TimeInter; //间隔时间
  8. }tsTimeType;
  9. void SetTime(tsTimeType *TimeType,uint32_t TimeInter); //打开超时定时器
  10. uint8_t CompareTime(tsTimeType *TimeType); //比较函数
  11. #endif


 
 
  1. #include "time.h"
  2. void SetTime(tsTimeType *TimeType,uint32_t TimeInter)
  3. {
  4. TimeType->TimeStart = HAL_GetTick(); //获取起始时间
  5. TimeType->TimeInter = TimeInter; //获取间隔时间
  6. }
  7. uint8_t CompareTime(tsTimeType *TimeType) //每隔1毫秒,计数器就会增加1
  8. {
  9. return ((HAL_GetTick()-TimeType->TimeStart) >= TimeType->TimeInter);
  10. }

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


 
 
  1. #ifndef _LED_H
  2. #define _LED_H
  3. #include "stm32f0xx.h"
  4. #define LED_NUMBER 3 //定义LED数量
  5. typedef enum //枚举类型,LED对应功能
  6. {
  7. LED_NET = 0,
  8. LED_RX,
  9. LED_TX
  10. }teLedNums;
  11. typedef enum //LED闪烁任务状态
  12. {
  13. LED_STOP = 0,
  14. LED_RUN,
  15. LED_DELAY
  16. }teLedTaskStatus;
  17. void LedOn(teLedNums LedNums); //打开LED灯
  18. void LedOff(teLedNums LedNums); //关闭LED灯
  19. void LedInit(void); //LED灯的初始化
  20. void SetLedRun(teLedNums LedNums); //设置LED为运行态
  21. void LedTask(void); //指示灯如果在运行态在,则闪烁一次
  22. #endif


 
 
  1. #include "led.h"
  2. #include "time.h"
  3. const uint16_t LedPins[LED_NUMBER] =
  4. {
  5. GPIO_PIN_0, //对应LED_NET
  6. GPIO_PIN_1, //对应LED_RX
  7. GPIO_PIN_2 //对应LED_TX
  8. };
  9. static tsTimeType TimeLeds[LED_NUMBER]; //获取起始时间和间隔时间
  10. static teLedTaskStatus LedTaskStatus[LED_NUMBER]; //LED任务状态
  11. void LedOn(teLedNums LedNums) //打开对应LED灯
  12. {
  13. HAL_GPIO_WritePin(GPIOB,LedPins[LedNums],GPIO_PIN_RESET);
  14. }
  15. void LedOff(teLedNums LedNums) //关闭对应LED灯
  16. {
  17. HAL_GPIO_WritePin(GPIOB,LedPins[LedNums],GPIO_PIN_SET);
  18. }
  19. void LedInit(void)//LED灯初始化,关闭所有灯
  20. {
  21. int i;
  22. for(i = 0;i < LED_NUMBER;i++)
  23. {
  24. LedOff(i);
  25. }
  26. }
  27. void SetLedRun(teLedNums LedNums)//设置对应LED为运行态
  28. {
  29. LedTaskStatus[LedNums] = LED_RUN;
  30. }
  31. void LedTask(void)//指示灯如果在运行态在,则闪烁一次
  32. {
  33. int i;
  34. for(i = 0;i < LED_NUMBER;i++)
  35. {
  36. if(LedTaskStatus[i] == LED_RUN)
  37. {
  38. LedOn(i);
  39. SetTime(&TimeLeds[i], 100);
  40. LedTaskStatus[i] = LED_DELAY;
  41. }
  42. else if(LedTaskStatus[i] == LED_DELAY)
  43. {
  44. if(CompareTime(&TimeLeds[i]))
  45. {
  46. LedOff(i);
  47. LedTaskStatus[i] = LED_STOP;
  48. }
  49. }
  50. }
  51. }

 


 
 
  1. #include "time.h"
  2. #include "led.h"
  3. static tsTimeType TimeNB; //获取定时器的起始时间和时间间隔
  4. void ATSend(teATCmdNum ATCmdNum)
  5. {
  6. //清空接收缓存区
  7. memset(Usart2type.Usart2RecBuff, 0,USART2_REC_SIZE);
  8. ATCmds[ATCmdNum].ATStatus = NO_REC;
  9. ATRecCmdNum = ATCmdNum;
  10. if(ATCmdNum == AT_NMGS)
  11. {
  12. memset(NbSendData, 0, 100);
  13. sprintf(NbSendData, "%s%d,%x%x\r\n",ATCmds[ATCmdNum].ATSendStr, 2, 0x10, 0x10);
  14. HAL_UART_Transmit(&huart2,( uint8_t*)NbSendData, strlen(NbSendData), 100);
  15. HAL_UART_Transmit(&huart1,( uint8_t*)NbSendData, strlen(NbSendData), 100);
  16. }
  17. else
  18. {
  19. HAL_UART_Transmit(&huart2,( uint8_t*)ATCmds[ATCmdNum].ATSendStr, strlen(ATCmds[ATCmdNum].ATSendStr), 100);
  20. HAL_UART_Transmit(&huart1,( uint8_t*)ATCmds[ATCmdNum].ATSendStr, strlen(ATCmds[ATCmdNum].ATSendStr), 100);
  21. }
  22. //打开超时定时器,这里主要用来判断接收超时使用
  23. SetTime(&TimeNB,ATCmds[ATCmdNum].TimeOut); //获取定时器的起始时间和时间间隔
  24. //打开发送指示灯,配合LedTask函数的使用可以产生一个100毫秒的亮灯
  25. //如果100毫秒之内又有数据发送,则定时器重新计时,LED灯继续延长点亮时间
  26. SetLedRun(LED_TX);
  27. }


 
 
  1. void ATRec(void)//AT接收字符串的解析
  2. {
  3. if(Usart2type.Usart2RecFlag) //是否接收到一个完整的数据包
  4. {
  5. //判断解析缓存区中是否存在对应指令返回的正确参数(字符串),strstr的使用方法见下方
  6. if( strstr(( const char*)Usart2type.Usart2RecBuff,ATCmds[ATRecCmdNum].ATRecStr) != NULL)
  7. {
  8. ATCmds[ATRecCmdNum].ATStatus = SUCCESS_REC; //接收状态赋值为成功
  9. }
  10. SetLedRun(LED_RX); //打开接收指示灯,配合LedTask函数的使用可以产生一个100毫秒的亮灯
  11. HAL_UART_Transmit(&huart1,Usart2type.Usart2RecBuff,Usart2type.Usart2RecLen, 100); //打印到串口
  12. Usart2type.Usart2RecFlag = 0; //清空标志位
  13. Usart2type.Usart2RecLen = 0; //设置解析缓存区字符串长度为0
  14. }
  15. }
  16. strstr
  17. 原型: extern char *strstr(char *haystack, char *needle);
  18. 用法: #include <string.h>
  19. 功能:从字符串haystack中寻找needle第一次出现的位置(不比较结束符 NULL)。
  20. 说明:返回指向第一次出现needle位置的指针,如果没找到则返回 NULL

 
 
  1. main函数中测试:
  2. ...
  3. LedInit(); //初始化:所有灯关闭
  4. HAL_Delay( 2000); //延时2秒
  5. SetLedRun( 0); //设置入网指示灯为运行态
  6. SetLedRun( 1); //设置接收指示灯为运行态
  7. SetLedRun( 2); //设置发送指示灯为运行态
  8. while( 1)
  9. {
  10. LedTask();
  11. }
  12. ...

 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入网任务算法

有限状态机编程

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

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


 
 
  1. typedef enum //NB任务的状态
  2. {
  3. NB_IDIE = 0, //NB空闲
  4. NB_SEND, //NB的发送
  5. NB_WAIT, //NB的等待
  6. NB_ACCESS //NB的入网完成
  7. }teNB_TaskStatus;

指令发送

等待响应

AT超时

NB初始化

入网完成


 
 
  1. static uint8_t CurrentRty; //当前重发的次数
  2. static teATCmdNum ATRecCmdNum; //
  3. static teATCmdNum ATCurrentCmdNum; //当前的指令
  4. static teATCmdNum ATNextCmdNum; //下一条指令
  5. static teNB_TaskStatus NB_TaskStatus; //声明任务的状态
  6. static tsTimeType TimeNBSendData; //NB发送数据时的起始时间获取 和 接收超时时间的设置
  7. void NB_Task(void)//NB不同任务状态的处理程序,一般开始时NB_TaskStatus状态为NB_SEND,故可以从NB_SEND开始分析
  8. {
  9. while( 1) //死循环,为了高效率处理
  10. {
  11. switch(NB_TaskStatus)
  12. {
  13. case NB_IDIE:
  14. //if(CompareTime(&TimeNBSendData))//判断发送指令是否超时
  15. //{
  16. // ATCurrentCmdNum = AT_NMGS;//当前指令设置为AT_NMGS
  17. // ATNextCmdNum = AT_IDIE;//下一条指令设置为空闲指令
  18. // NB_TaskStatus = NB_SEND;//任务状态设置为发送
  19. // SetTime(&TimeNBSendData,10000);//发送超时设置
  20. // break;//跳转到发送状态
  21. //}
  22. return;
  23. case NB_SEND:
  24. if(ATCurrentCmdNum != ATNextCmdNum) //如果当前指令不等于下一条指令,则该指令是第一次运行
  25. {
  26. CurrentRty = ATCmds[ATCurrentCmdNum].RtyNum; //获取当前指令的重发次数
  27. }
  28. ATSend(ATCurrentCmdNum); //发送指令
  29. NB_TaskStatus = NB_WAIT; //更改为等待态
  30. return; //因为有超时
  31. case NB_WAIT:
  32. ATRec(); //AT接收字符串的解析
  33. if(ATCmds[ATCurrentCmdNum].ATStatus == SUCCESS_REC) //判断接收状态是否成功
  34. {
  35. if(ATCurrentCmdNum == AT_CGPADDR) //判断当前指令是否为入网指令
  36. {
  37. NB_TaskStatus = NB_ACCESS; //如果是则状态设置为入网完成
  38. break; //跳转指令
  39. }
  40. // else if(ATCurrentCmdNum == AT_NMGS)//判断当前指令是否为AT_NMGS指令
  41. // {
  42. // NB_TaskStatus = NB_IDIE;//设置任务状态为空闲状态
  43. // return;
  44. // }
  45. else
  46. {
  47. ATCurrentCmdNum += 1; //如果不是入网指令,则当前指令加1
  48. ATNextCmdNum = ATCurrentCmdNum+ 1; //下一条指令在当前指令的基础上再加1
  49. NB_TaskStatus = NB_SEND; //设置为发送状态
  50. break; //跳转指令
  51. }
  52. }
  53. else if(CompareTime(&TimeNB)) //判断发送指令之后接收是否超时
  54. {
  55. ATCmds[ATCurrentCmdNum].ATStatus = TIME_OUT; //改变当前指令的状态:设置超时
  56. if(CurrentRty > 0) //判断当前重发的次数是否大于零
  57. {
  58. CurrentRty--;
  59. ATNextCmdNum = ATCurrentCmdNum; //下一条指令等于当前指令
  60. NB_TaskStatus = NB_SEND; //改变任务状态为发送状态
  61. break; //跳转到发送状态的处理程序
  62. }
  63. else //否则重发次数已经达到最高的重发次数限制
  64. {
  65. NB_Init(); //NB初始化,函数具体实现见下方
  66. return;
  67. }
  68. }
  69. return;
  70. case NB_ACCESS: //如果是入网完成的状态
  71. LedOn(LED_NET); //打开入网完成的指示灯
  72. NB_TaskStatus = NB_IDIE; //任务状态设置为空闲状态
  73. break; //跳转到空闲状态
  74. // ATCurrentCmdNum = AT_NMGS;//当前指令设置为AT_NMGS
  75. // ATNextCmdNum = AT_IDIE;//下一条指令设置为空闲指令
  76. // NB_TaskStatus = NB_SEND;//任务状态设置为发送状态
  77. // SetTime(&TimeNBSendData,10000);//发送指令超时设置
  78. // break;//跳转到发送状态
  79. default:
  80. return;
  81. }
  82. }
  83. }
  84. void NB_Init(void)//NB初始化
  85. {
  86. NB_TaskStatus = NB_SEND; //任务状态设置为发送状态
  87. ATCurrentCmdNum = AT_CFUN0; //当前指令设置为第一条指令
  88. ATNextCmdNum = AT_CGSN; //下一条指令设置为第二条指令
  89. }

主程序:测试


 
 
  1. main函数中测试
  2. #include "nbiotdriver.h"
  3. ...
  4. NB_Init(); //NB初始化
  5. printf( "wait 5s NB Reset!\n");
  6. HAL_Delay( 5000); //初始化时需等待NB芯片重启(这里设置5秒钟等待时间)
  7. while ( 1)
  8. {
  9. LedTask();
  10. NB_Task();
  11. }
  12. ...

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


 
 
  1. tsATCmds ATCmds[] =
  2. {
  3. { "AT+CFUN=0\r\n", "OK", 2000,NO_REC, 3},
  4. { "AT+CGSN=1\r\n", "OK", 2000,NO_REC, 3},
  5. { "AT+NRB\r\n", "OK", 8000,NO_REC, 3},
  6. { "AT+NCDP=180.101.147.115,5683\r\n", "OK", 2000,NO_REC, 3},
  7. { "AT+CFUN=1\r\n", "OK", 2000,NO_REC, 3},
  8. { "AT+CIMI\r\n", "OK", 2000,NO_REC, 3},
  9. { "AT+CMEE=1\r\n", "OK", 2000,NO_REC, 3},
  10. { "AT+CGDCONT=1,\"IP\",\"ctnb\"\r\n", "OK", 2000,NO_REC, 3},
  11. { "AT+NNMI=1\r\n", "OK", 2000,NO_REC, 3},
  12. { "AT+CGATT=1\r\n", "OK", 2000,NO_REC, 3},
  13. { "AT+CGPADDR\r\n", "+CGPADDR:1,1", 2000,NO_REC, 30},
  14. { "AT+NMGS=", "OK", 3000,NO_REC, 3}, //发送数据的指令,注意:发送数据是可变的,所以指令后面没有 "\r\t"
  15. };


 
 
  1. typedef enum
  2. {
  3. AT_CFUN0 = 0,
  4. AT_CGSN,
  5. AT_NRB,
  6. AT_NCDP,
  7. AT_CFUN1,
  8. AT_CIMI,
  9. AT_CMEE,
  10. AT_CGDCONT,
  11. AT_NNMI,
  12. AT_CGATT,
  13. AT_CGPADDR,
  14. AT_NMGS,
  15. AT_IDIE //因为AT_NMGS为最后一个指令,所以这里添加一个空闲指令标记
  16. }teATCmdNum;


 
 
  1. #include "time.h"
  2. #include "led.h"
  3. static tsTimeType TimeNB; //获取定时器的起始时间和时间间隔
  4. char NbSendData[ 100]; //发送数据指令中数据的存储区
  5. void ATSend(teATCmdNum ATCmdNum)
  6. {
  7. //清空接收缓存区
  8. memset(Usart2type.Usart2RecBuff, 0,USART2_REC_SIZE);
  9. ATCmds[ATCmdNum].ATStatus = NO_REC;
  10. ATRecCmdNum = ATCmdNum;
  11. if(ATCmdNum == AT_NMGS) //判断是否发送数据的指令
  12. {
  13. memset(NbSendData, 0, 100); //清空数据的存储区
  14. //第一个%s为发送数据的指令:"AT+NMGS="
  15. //第二个%d为发送数据的个数是两个(字节的长度)
  16. //第三和第四个%x是两个要发送的16进制的数据
  17. //最终得到NbSendData的数据为:AT+NMGS=2,0x10,0x10\r\n
  18. sprintf(NbSendData, "%s%d,%x%x\r\n",ATCmds[ATCmdNum].ATSendStr, 2, 0x10, 0x10);
  19. HAL_UART_Transmit(&huart2,( uint8_t*)NbSendData, strlen(NbSendData), 100); //发送NbSendData到NB芯片
  20. HAL_UART_Transmit(&huart1,( uint8_t*)NbSendData, strlen(NbSendData), 100); //发送NbSendData到串口1,用于调试
  21. }
  22. else
  23. {
  24. HAL_UART_Transmit(&huart2,( uint8_t*)ATCmds[ATCmdNum].ATSendStr, strlen(ATCmds[ATCmdNum].ATSendStr), 100);
  25. HAL_UART_Transmit(&huart1,( uint8_t*)ATCmds[ATCmdNum].ATSendStr, strlen(ATCmds[ATCmdNum].ATSendStr), 100);
  26. }
  27. //打开超时定时器,这里主要用来判断接收超时使用
  28. SetTime(&TimeNB,ATCmds[ATCmdNum].TimeOut); //获取定时器的起始时间和时间间隔,具体见下面讲解
  29. //打开发送指示灯,配合LedTask函数的使用可以产生一个100毫秒的亮灯,具体函数下文有讲解
  30. //如果100毫秒之内又有数据发送,则定时器重新计时,LED灯继续延长点亮时间
  31. SetLedRun(LED_TX);
  32. }


 
 
  1. static uint8_t CurrentRty; //当前重发的次数
  2. static teATCmdNum ATRecCmdNum; //
  3. static teATCmdNum ATCurrentCmdNum; //当前的指令
  4. static teATCmdNum ATNextCmdNum; //下一条指令
  5. static teNB_TaskStatus NB_TaskStatus; //声明任务的状态
  6. static tsTimeType TimeNBSendData; //NB发送数据时的起始时间获取 和 接收超时时间的设置
  7. void NB_Task(void)//NB不同任务状态的处理程序,一般开始时NB_TaskStatus状态为NB_SEND,故可以从NB_SEND开始分析
  8. {
  9. while( 1) //死循环,为了高效率处理
  10. {
  11. switch(NB_TaskStatus)
  12. {
  13. case NB_IDIE:
  14. if(CompareTime(&TimeNBSendData)) //判断发送指令是否超时
  15. {
  16. ATCurrentCmdNum = AT_NMGS; //当前指令设置为发送数据指令
  17. ATNextCmdNum = AT_IDIE; //下一条指令设置为空闲指令
  18. NB_TaskStatus = NB_SEND; //任务状态设置为发送
  19. SetTime(&TimeNBSendData, 10000); //每隔10秒发送一次数据
  20. break; //跳转到发送状态
  21. }
  22. return;
  23. case NB_SEND:
  24. if(ATCurrentCmdNum != ATNextCmdNum) //如果当前指令不等于下一条指令,则该指令是第一次运行
  25. {
  26. CurrentRty = ATCmds[ATCurrentCmdNum].RtyNum; //获取当前指令的重发次数
  27. }
  28. ATSend(ATCurrentCmdNum); //发送指令
  29. NB_TaskStatus = NB_WAIT; //更改为等待态
  30. return; //因为有超时
  31. case NB_WAIT:
  32. ATRec(); //AT接收字符串的解析
  33. if(ATCmds[ATCurrentCmdNum].ATStatus == SUCCESS_REC) //判断接收状态是否成功
  34. {
  35. if(ATCurrentCmdNum == AT_CGPADDR) //判断当前指令是否为入网指令
  36. {
  37. NB_TaskStatus = NB_ACCESS; //如果是则状态设置为入网完成
  38. break; //跳转指令
  39. }
  40. else if(ATCurrentCmdNum == AT_NMGS) //判断当前指令是否为发送数据指令
  41. {
  42. NB_TaskStatus = NB_IDIE; //设置任务状态为空闲状态
  43. return;
  44. }
  45. else
  46. {
  47. ATCurrentCmdNum += 1; //如果不是入网指令,则当前指令加1
  48. ATNextCmdNum = ATCurrentCmdNum+ 1; //下一条指令在当前指令的基础上再加1
  49. NB_TaskStatus = NB_SEND; //设置为发送状态
  50. break; //跳转指令
  51. }
  52. }
  53. else if(CompareTime(&TimeNB)) //判断发送指令之后接收是否超时
  54. {
  55. ATCmds[ATCurrentCmdNum].ATStatus = TIME_OUT; //改变当前指令的状态:设置超时
  56. if(CurrentRty > 0) //判断当前重发的次数是否大于零
  57. {
  58. CurrentRty--;
  59. ATNextCmdNum = ATCurrentCmdNum; //下一条指令等于当前指令
  60. NB_TaskStatus = NB_SEND; //改变任务状态为发送状态
  61. break; //跳转到发送状态的处理程序
  62. }
  63. else //否则重发次数已经达到最高的重发次数限制
  64. {
  65. NB_Init(); //NB初始化,函数具体实现见下方
  66. return;
  67. }
  68. }
  69. return;
  70. case NB_ACCESS: //如果是入网完成的状态
  71. LedOn(LED_NET); //打开入网完成的指示灯
  72. ATCurrentCmdNum = AT_NMGS; //当前指令设置为发送数据的指令
  73. ATNextCmdNum = AT_IDIE; //下一条指令设置为空闲指令
  74. NB_TaskStatus = NB_SEND; //任务状态设置为发送状态
  75. SetTime(&TimeNBSendData, 10000); //发送指令超时设置
  76. break; //跳转到发送状态
  77. default:
  78. return;
  79. }
  80. }
  81. }
  82. void NB_Init(void)//NB初始化
  83. {
  84. NB_TaskStatus = NB_SEND; //任务状态设置为发送状态
  85. ATCurrentCmdNum = AT_CFUN0; //当前指令设置为第一条指令
  86. ATNextCmdNum = AT_CGSN; //下一条指令设置为第二条指令
  87. }

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值