STM32+移远MC20模块采用MQTT协议登录OneNet上传GPS数据

简介: STM32+移远MC20模块采用MQTT协议登录OneNet上传GPS数据

一、环境介绍

MCU:  STM32F103C8T6


GSM模块: 移远MC20 (MT2503D)(GSM+GPS共存)功能很强大


开发软件: Keil5


MQTT协议采用OneNet的旧版协议,登录OneNet控制台创建应用时要选择旧版本。


如果想使用新版本的标准MQTT协议连接OnetNet请参考这里:  https://blog.csdn.net/xiaolong1126626497/article/details/107385118


完整源代码下载:  https://download.csdn.net/download/xiaolong1126626497/18245206


二、MC20模块

image.png

image.png

image.png

MC20模块采用联发科技最新推出的多功能通信定位芯片研制而成。它是一款集成LCC封装、四频段GSM/GPRS和先进算法GNSS引擎于一体的全功能通信模块,具有超小体积、低功耗、双卡单待等优势。MC20不仅内嵌丰富的网络协议(如 TCP、UDP、PPP、FTP、HTTP以及SSL),还集成了多星座卫星系统(如北斗、GPS、QZSS),因此能提供无线移动通信以及精准的导航定位功能。


除具备GSM/GPRS无线通信功能外,MC20模块还支持先进的GNSS技术。它集成了EPOTM(用户无需自设服务器,直接从MTK服务器获取EPO数据)、秒定等技术,能够实现快速首次定位。由于支持北斗、GPS、QZSS等多星座卫星系统解调算法,其定位更加精准,抗多路径干扰能力更强,比传统GPS模块具有更多优势。另外,MC20模块中内置LNA和低功耗算法:前者使其接收灵敏度提升至-149dBm;后者使其在低功耗模式(GLP Mode)下的耗流仅为正常工作模式的40%。


MC20模块较传统GSM+GNSS方案体积减少40%,使其在各种应用中占具更大优势。其主要应用领域为:可穿戴设备(智能手表)、宠物追踪、财产追踪及行车记录仪等等。

主要优势

● 超小体积: 18.7mm × 16.0mm × 2.1mm

● 多卫星导航系统: GPS/BeiDou/QZSS

● GNSS 接收机通道: 99 路捕获通道/33 路跟踪通道

● 支持多种 AGPS 技术,如 EASYTM 、EPOTM 、秒定等

● 内置 LNA 大大提升 GNSS 接收机灵敏度(-167dBm@跟踪模式):可使用无源 GNSS 天线而无需任何外部低噪声放大器

● 支持增强型 GNSS 功能,如 SDK 命令、LOCUSTM 、AIC 和 GLP

● 多功能四频段 GSM模块: 850/900/1800/1900MHz

● 内嵌丰富网络协议: TCP/UDP/PPP/HTTP/FTP/SSL

● 支持语音、短信、QuecFOTATM 、双卡单待以及 OpenCPU 功能

● 支持蓝牙 V3.0 以及 SPP & HFP-AG 配置文件



三、代码功能

使用STM32F103C8T6 通过串口+AT指令控制MC20模块+MQTT协议,登录OneNet服务器上传GPS数据,LED控制(网页按钮控制开发板上的LED灯)。


四、核心代码

4.1 main.c

#include "stm32f10x.h"
#include "beep.h"
#include "delay.h"
#include "led.h"
#include "sys.h"
#include "usart.h"
#include <string.h>
#include <stdio.h>
#include "timer.h"
#include "mc20.h"
//网络协议层
#include "onenet.h"
//协议封装文件
#include "dStream.h"
//产品ID
char PROID[]="231174";
//鉴权信息
char AUTH_INFO[]="1234567890";
//设备ID
char DEVID[]="523369555";
//API KEY
char API_KEY[]="k6vtrrEd1H7UMddiF3DzripS47w=";
//缓冲区
char onenet_http_cmd[1024];
//服务器IP地址
#define TCP_SERVER_IP_ADDR "183.230.40.39"
//服务器端口号
#define TCP_SERVER_PORT 6002
//数据流结构
DATA_STREAM data_stream[1]=
{
  {"gps","88.88",TYPE_JSON,1},
};
/*
  STM32开发板接线说明:
  STM32         MC20
  3.3V  ------> V_IO
  GND   <-----> GND
  PA3   <------ GSM_TX
  PA2   ------> GSM_RX
*/
int main()
{   
  u32 time_cnt=0;
  u32 cnt=0;
  double Longitude; //经度
  double latitude;  //纬度
  LED_Init();
  BEEP_Init();
  USART_X_Init(USART1,72,115200);
  TIM2_Init(72,20000);          //辅助串口2接收,超时时间为20ms
  USART_X_Init(USART2,36,9600); //连接着MC20(GPS+GPRS)
  printf("串口准备就绪.....\r\n");
  DelayMs(500);
  printf("程序修改时间: %s\r\n",__TIME__);
  while(1)
  {
     u8 stat;
    /*初始化MC20,并连接到指定服务器*/
     MC20_InitConnect(TCP_SERVER_IP_ADDR,TCP_SERVER_PORT);
     /*登录OneNET服务器,上线设备*/
     stat=OneNet_DevLink();
     if(stat)printf("ERROR:%d,接入OneNET失败:%d\r\n",stat,cnt++);
     else break; //登录成功
     LED1=!LED1;
     delay_ms(200); 
     break;//失败也退出继续运行下面代码
  }
  printf("6. OneNET服务器登录成功!\r\n");
  delay_ms(100);
  while(1)
  { 
    /*6. 向OneNet服务器5秒发送一次数据*/
     time_cnt++;
     DelayMs(1);
     if(time_cnt>=5000)
     {
          time_cnt=0;
          /*获取一次GPS输出的经纬度信息*/
          switch(MC20_GetGPS_Data(&Longitude,&latitude))
          {
           case 0: printf("经度:%f,纬度:%f\r\n",Longitude,latitude); break;
           case 1: printf("ERROR:GPS数据接收失败!\r\n"); break;
           case 2: printf("ERROR:GPS定位数据解码失败!<请将GPS拿到空旷位置定位>\r\n"); break;
          }
          //组装数据格式
          sprintf(onenet_http_cmd,"{\"lon\":%f,\"lat\":%f}",Longitude,latitude);
          data_stream[0].dataPoint=onenet_http_cmd; //赋值GPS数据
          //向云端发送数据流
          OneNet_SendData(FORMAT_TYPE1,DEVID,API_KEY,data_stream,1);
     }
     /*实时接收MC20收到的数据,进行解析*/
     if(USART2_RX_FLAG)
     {
         USART2_RX_BUFF[USART2_RX_CNT]='\0';
          printf("USART2_RX_BUFF=%s\r\n",USART2_RX_BUFF); //向串口打印信息
         //解析平台返回的数据
         OneNet_RevPro(USART2_RX_BUFF);
         USART2_RX_CNT=0;
         USART2_RX_FLAG=0;
         memset(USART2_RX_BUFF,0,sizeof(USART2_RX_BUFF));
     }
  }
}

4.2  mc20.c

#include "mc20.h"
/*
函数功能:向MC20模块发送指令
函数参数:
        char *cmd  发送的命令
        char *check_data 检测返回的数据
返回值: 0表示成功 1表示失败
*/
u8 MC20_SendCmd(char *cmd,char *check_data)
{
   u16 i,j;
   for(i=0;i<5;i++) //测试的总次数
   {
      USART2_RX_FLAG=0;
      USART2_RX_CNT=0;
      memset(USART2_RX_BUFF,0,sizeof(USART2_RX_BUFF));
      USART_X_SendString(USART2,cmd); //发送指令
      for(j=0;j<500;j++) //等待的时间(ms单位)
      {
          if(USART2_RX_FLAG)
          {
              USART2_RX_BUFF[USART2_RX_CNT]='\0';
              if(strstr((char*)USART2_RX_BUFF,check_data))
              {
                  return 0;
              }
              else break;
          }
          delay_ms(10); //一次的时间
      }
   }
   return 1;
}
/*
函数功能:  MC20初始化检查
*/
u8 MC20_InitCheck(void)
{
    return MC20_SendCmd("AT\r\n","OK\r\n");
}
/*
函数功能: 开启GPS功能
返 回 值:0表示成功  1表示失败
*/
u8 MC20_StartGPS(void)
{
    //先判断GPS功能是否启动
    if(MC20_SendCmd("AT+QGNSSC?\r\n","+QGNSSC: 1")) 
    {
        //没有启动就启动GPS功能
        if(MC20_SendCmd("AT+QGNSSC=1\r\n","OK\r\n"))
        {
            return 1;  //GPS功能启动失败
        }
    }
    return 0;
}
/*
函数功能:从buf里面得到第cnt个逗号所在的位置
返 回 值:0~254,代表逗号所在位置的偏移.
255,代表不存在第cnt个逗号
*/
u8 GPS_GetCommaOffset(char *buf,u8 cnt)
{
  char *p=buf;
  while(cnt)
  {
    if(*buf=='*'||*buf<' '||*buf>'z')return 255;//遇到'*'或者非法字符,则不存在第cx个逗号
    if(*buf==',')cnt--;
    buf++;
  }
  return buf-p; //计算偏移量
}
/*
函数功能: 获取GPS经纬度数据值
函数参数:
        double *Longitude  :经度
        double *latitude   :纬度
返回值: 0表示定位成功,1表示定位失败
说明: 解析$GNRMC命令,得到经纬度
$GNRMC,023705.000,A,2842.4164,N,11549.5713,E,1.73,91.65,150319,,,A*41
转换公式示例:
经度: dddmm.mmmm 东经 11408.4790 114+(08.4790/60)=114.141317
纬度: ddmm.mmmm 北纬 2236.9453 22+(36.9453/60)= 22.615755
*/
u8 GPS_GNRMC_Decoding(char *gps_buffer,double *Longitude,double *latitude)
{
    u8 Offset;
    u32 int_data;
    double s_Longitude,s_latitude;
    char *p;
    /*1. 确定下定位是否成功*/
    p=strstr(gps_buffer,"$GNRMC");
    if(!p)return 1;
    Offset=GPS_GetCommaOffset(p,2);
    if(Offset==255)return 2;
    if(*(p+Offset)!='A')return 3; //定位不准确
    /*2. 得到纬度*/
    Offset=GPS_GetCommaOffset(p,3);
    if(Offset==255)return 4;
    sscanf(p+Offset,"%lf",&s_latitude);
    s_latitude=s_latitude/100;
    int_data=s_latitude;//得到纬度整数部分
    s_latitude=s_latitude-int_data;//得到纬度小数部分
    s_latitude=(s_latitude)*100;
    *latitude=int_data+(s_latitude/60.0); //得到转换后的值
    /*3. 得到经度*/
    Offset=GPS_GetCommaOffset(p,5);
    if(Offset==255)return 5;
    sscanf(p+Offset,"%lf",&s_Longitude);    
    s_Longitude=s_Longitude/100;
    int_data=s_Longitude;//得到经度整数部分
    s_Longitude=s_Longitude-int_data; //得到经度小数部分
    s_Longitude=s_Longitude*100;
    *Longitude=int_data+(s_Longitude/60.0);   
    return 0;
}
/*
函数功能: 获取一次GPS经纬度数据
函数参数:
        double *Longitude  :经度
        double *latitude   :纬度
返回值: 0表示定位成功,1表示数据接收失败,2表示定位失败
*/
u8 MC20_GetGPS_Data(double *Longitude,double *latitude)
{
    /*1. 发送获取GPS数据的指令*/
    if(MC20_SendCmd("AT+QGNSSRD=\"NMEA/RMC\"\r\n", "OK\r\n"))return 1;
    /*2. 对GPS数据进行解码*/
    if(GPS_GNRMC_Decoding((char *)USART2_RX_BUFF,Longitude,latitude))return 2;
    //解码成功
    return 0; 
}
/*
函数功能: 连接服务器
函数参数:
        char *server_ip  服务器IP地址  
        u16 port  服务器端口号
返回值: 0表示连接成功,1表示连接失败
*/
u8 MC20_Connect_TCP_Server(char *server_ip,u16 port)
{
     char send_buf[100]={0};
     sprintf(send_buf,"AT+QIOPEN=\"TCP\",\"%s\",\"%d\"\r\n",server_ip,port);
     //连接至服务器
     if(MC20_SendCmd(send_buf, "CONNECT"))
     {
        return 1; //连接失败
     }
     return 0; //连接成功
}
/*
函数功能: 向服务器发送数据
函数参数: 
          u8 *buffer 发送的数据
          u32 len  发送的长度
返 回 值: 0表示发送成功,1表示准备发送失败,2表示数据发送失败
*/
u8 MC20_ClientSendData(u8 *buffer,u32 len)
{
    char send_buf[2];
    /*1. 准备发送数据*/
    if(MC20_SendCmd("AT+QISEND\r\n",">"))
    {
        printf("AT+QISEND->ERROR Info:%s\r\n",USART2_RX_BUFF);
        return 1; 
    }
    /*2. 开始发送数据*/
    USART_X_SendData(USART2,buffer,len);
    delay_ms(20);
    /*3. 发送结束符*/
    send_buf[0] = 0x1a;
    send_buf[1] = '\0';
    if(MC20_SendCmd(send_buf,"OK\r\n"))
    {
        printf("发送结束符->ERROR Info:%s\r\n",USART2_RX_BUFF);
        return 2;
    }
    return 0;
}
/*
函数功能: MC20初始化检查并连接至服务器 
*/
#include "led.h"
void MC20_InitConnect(char *server_ip,u16 port)
{
  /*1. MC20模块初始化检查*/
  while(MC20_InitCheck())
  {
      LED1=!LED1;
      printf("ERROR:MC20模块初始化检查失败!\r\n");
      delay_ms(100);
  }
  printf("1. MC20模块初始化成功!\r\n");
  delay_ms(100);
  /*2. 查询是否有PIN码锁定*/
  while(MC20_SendCmd("AT+CPIN?\r\n","READY"))
  {
      LED1=!LED1;
      printf("ERROR:PIN码锁定检查失败!\r\n");
      delay_ms(100);
  }
  printf("2. PIN码锁定检查成功!\r\n");
  delay_ms(100);
  /*3. 查询SIM卡网络注册信息*/
  if(MC20_SendCmd("AT+CREG?\r\n",",1")) //本地SIM卡
  {
      if(MC20_SendCmd("AT+CREG?\r\n",",5"))//漫游SIM卡
      {
          printf("ERROR:查询SIM卡网络注册信息失败!\n");
      }
      else  printf("3. 漫游SIM卡网络注册成功!\n");
  }
  else  printf("3. 本地SIM卡网络注册成功!\n");
  delay_ms(100);
  /*4. 启动GPS功能*/
  if(MC20_StartGPS())
  {
      printf("ERROR:GPS功能启动失败!\n");
  }
  else printf("4. GPS功能启动成功!\n");
  delay_ms(100);
  /*5. 连接指定服务器*/
  while(MC20_Connect_TCP_Server(server_ip,port))
  {
      printf("ERROR: 连接TCP服务器失败!\r\n现在正在断开服务器,进行重连!\r\n需要保证服务器地址正确,并且SIM卡可以上网\r\n");
      /*先断开服务器连接 (如果之前没有连接过服务器,这里就会出现错误)*/
      MC20_SendCmd("AT+QICLOSE\r\n","OK\r\n");
      delay_ms(100);
      MC20_SendCmd("AT+QIDEACT\r\n","OK\r\n");
      delay_ms(100);
  }
  printf("5. 连接TCP服务器成功!\n");
  delay_ms(100);
}

4.2 mc20.h

#ifndef  _MC20_H
#define  _MC20_H
#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include <string.h>
u8 MC20_SendCmd(char *cmd,char *check_data);
u8 MC20_InitCheck(void);
u8 MC20_StartGPS(void);
u8 MC20_GetGPS_Data(double *Longitude,double *latitude);
u8 MC20_Connect_TCP_Server(char *server_ip,u16 port);
u8 MC20_ClientSendData(u8 *buffer,u32 len);
void MC20_InitConnect(char *server_ip,u16 port);
#endif

五、OneNet创建产品

链接地址:  https://open.iot.10086.cn/develop/global/product/#/console

image.png

相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
1月前
【STM32】NRF24L01模块的收发调试(三)
【STM32】NRF24L01模块的收发调试
|
1月前
【STM32】NRF24L01模块的收发调试(二)
【STM32】NRF24L01模块的收发调试
|
1月前
【STM32】NRF24L01模块的收发调试(一)
【STM32】NRF24L01模块的收发调试
|
1月前
|
传感器
【STM32】I2C练习,SHT3X温度传感器的数据读取
【STM32】I2C练习,SHT3X温度传感器的数据读取
|
1天前
|
传感器 数据格式
【STM32】DHT11温湿度模块传感器详解&代码
【STM32】DHT11温湿度模块传感器详解&代码
|
1月前
|
Java C语言
STM32使用printf重定向到USART(串口)并打印数据到串口助手
STM32使用printf重定向到USART(串口)并打印数据到串口助手
61 0
|
1月前
|
Java 物联网 网络安全
mqtt问题之STM32F103GPRS模组如何接入物理网平台
MQTT接入是指将设备或应用通过MQTT协议接入到消息服务器,以实现数据的发布和订阅;本合集着眼于MQTT接入的流程、配置指导以及常见接入问题的解决方法,帮助用户实现稳定可靠的消息交换。
|
1天前
|
消息中间件 Java 双11
RocketMQ:揭秘电商巨头背后的消息队列秘密
**RocketMQ概览:**高性能分布式消息队列,适用于有序消息、事务处理、流计算、消息推送、日志处理及Binlog分发。在双11等高流量场景下证明了其性能、稳定性和低延迟。Java开发,利于扩展,性能超RabbitMQ,支持死信队列,但可能有集成兼容性问题。适合Java开发者,为电商等场景优化,每秒处理大量消息。
19 3
RocketMQ:揭秘电商巨头背后的消息队列秘密
|
2天前
|
消息中间件 自然语言处理 负载均衡
RabbitMQ揭秘:轻量级消息队列的优缺点全解析
**RabbitMQ简介** RabbitMQ是源自电信行业的消息中间件,支持AMQP协议,提供轻量、快速且易于部署的解决方案。它拥有灵活的路由配置,广泛的语言支持,适用于异步处理、负载均衡、日志收集和微服务通信等场景。然而,当面临大量消息堆积或高吞吐量需求时,性能可能会下降,并且扩展和开发成本相对较高。
14 0