一、环境介绍
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模块



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


转载:https://blog.csdn.net/xiaolong1126626497/article/details/116298805