SIM900A GPRS无线通信

一.模块介绍

1.基本概况

SIM900A是一款高性能工业级 GSM/GPRS 模块。工作频段双频:900/1800Mhz,可以低功耗实现语音、SMS(短信,不支持彩信)、数据和传真信息的传输(GPRS功能)。SIM900A 模块支持通过串口与单片机进行通信,采用AT指令集的开发方式,并带硬件流控制,使得该模块可以非常方便的与单片机之间进行连接,从而实现语音、短信和 GPRS 数据传输等功能。该模块在使用之前需要安装通信运营商的SIM卡,以通过流量实现网络通信功能,这里我们采用的是一张中国移动SIM卡(需开通GPRS功能)。
AT指令不过多介绍,这里列出几个常用的AT指令:
1, AT+CPIN?
该指令用于查询 SIM 卡的状态,主要是 PIN 码,如果该指令返回:+CPIN:READY,则表明 SIM 卡状态正常,返回其他值,则有可能是没有 SIM 卡。
2, AT+CSQ
该指令用于查询信号质量,返回 SIM900A 模块的接收信号强度,如返回:+CSQ: 24,0,表示信号强度是 24(最大有效值是 31)。如果信号强度过低,则要检查天线是否接好了?
3, AT+COPS?
该指令用于查询当前运营商,该指令只有在连上网络后,才返回运营商,否则返回空,如返回:+COPS:0,0, “CHINA MOBILE”,表示当前选择的运营商是中国移动。
4, AT+CGMI
该指令用于查询模块制造商,如返回:SIMCOM_Ltd,说明 SIM900A 模块是 SIMCOM公司生产的。
5, AT+CGMM
该指令用于查询模块型号,如返回:SIMCOM_SIM900A,说明模块型号是 SIM900A。
6, AT+CGSN
该指令用于查询产品序列号(即 IMEI 号),每个模块的 IMEI 号都是不一样的,具有全球唯一性,如返回:869988012018905,说明模块的产品序列号是:869988012018905。
7, AT+CNUM
该指令用于查询本机号码,必须在 SIM 卡在位的时候才可查询,如返回:+CNUM: “”,“15902020353”,129,7,4,则表明本机号码为:15902020353。另外,不是所有的 SIM 卡都支持这个指令,有个别 SIM 卡无法通过此指令得到其号码。
8, ATE1
该指令用于设置回显模式(默认开启),即模块将收到的 AT 指令完整的返回给发送端,启用该功能,有利于调试模块。如果不需要开启回显模式,则发送 ATE0 指令即可关闭,这样收到的指令将不再返回给发送端,这样方便程序控制。

发送给模块的指令,如果执行成功,则会返回对应信息和"OK",如果执行失败/指令无效,则会返回"ERROR"。

2.GPRS通信开发说明

对于语音通话和短信的收发本文不做介绍,这里仅介绍一下GPRS网络通信,基于MSP430F149单片机实现,并在第二部分附上源码。实现GPRS通信(TCP,UDP连接通信)将要用到的指令有如下 等 10 条 AT 指令:
1.AT+CGCLASS,用于设置移动台类别
SIM900A 模块仅支持类别"B"和"CC",发送:AT+CGCLASS=“B”,设置移动台台类别为 B。即,模块支持包交换和电路交换模式,但不能同时支持。
2.AT+CGDCONT,用于设置 PDP 上下文
发送AT+CGDCONT=1,“IP”,“CMNET”,设置 PDP 上下文标标志为 1,采用互联网协议(IP),接入点为"CMNET"。
3.AT+CGATT,用于设置附着和分离 GPRS 业务
发送:AT+CGATT=1,附着 GPRS 业务。
4.AT+CIPCSGP,用于设置 CSD 或 GPRS 链接模式
发送:AT+CIPCSGP=1, “CMNET”,设置为 GPRS 连接,接入点为”CMNET”。
5.AT+CLPORT,用于设置本地端口号
发送:AT+CLPORT=“TCP”,“8888”,即设置 TCP
连接本地端口号为 8888。
6.AT+CIPSTART,用于建立 TCP 连接或注册 UDP 端口号。
发送: AT+CIPSTART=“TCP”,“113.111.214.69”,“8086”,模块将建立一个 TCP 连接,连接目标地址为:113.111.214.69,连接端口为 8086,连接成功会返回:CONNECT OK。
7.AT+CIPSEND,用于发送数据
在连接成功以后发送:AT+CIPSEND,模块返回:>,此时可以输入要发送的数据,最大可以一次发送 1352 字节,数据输入完后,同发短信一样,输入十六进制的:1A(0X1A),启动发送数据。在数据发送完成后,模块返回:SEND OK,表示发送成功。
8.AT+CIPSTATUS,用于查询当前连接状态
发送:AT+CIPSTATUS,模块即返回当前连接状态。
9.AT+CIPCLOSE,用于关闭 TCP/UDP 连接
发送:AT+CIPCLOSE=1,即可快速关闭当前 TCP/UDP 连接。
10.AT+CIPSHUT,用于关闭移动场景
发送:AT+SHUT,则可以关闭移动场景,关闭场景后连接状态为:IP INITIAL,可以通过发送:AT+CIPSTATUS,查询。另外,在连接建立后,如果收到:+PDP: DEACT,则必须发送:AT+CIPSHUT,关闭场景后,才能实现重连。

以上就是我们可能将要用到的一些 AT 指令的简单介绍,要实现模块与电脑的GPRS 通信,需要确保所用电脑具有公网 IP,否则无法实现通信,推荐在 ADSL 网络下进行测试,并最好关闭防火墙/杀毒软件。
对于 ADSL 用户(没用路由器),直接拥有 1 个公网 IP,你可以通过百度,搜索:IP,第一个条目,就是本机 IP。

二.TCP连接实现及其源码

1.TCP连接实现方法

TCP 是基于连接的协议,在收发数据前,必须先和对方建立可靠连接,是一种可靠的数据传输方式。我们将在SIM900A 模块和电脑调试助手(这里需要配置网络的映射规则DMZ,要将私网IP映射到公网才能进行网络通信)之间之间建立一个 TCP 连接,并实现数据的互相收发。我们也可以直接与云服务平台之间建立连接,不需要网络映射,更加方便。这里在我们配置好网络映射规则后建立TCP连接的主要指令步骤如下:

AT+CGCLASS=“B”
AT+CGDCONT=1,“IP”,“CMNET”
AT+CGATT=1
AT+CIPCSGP=1,“CMNET”
这几条指令前面已经介绍过,用于设置移动台类别、连接方式、接入点和附着 GPRS业务等。起到一个前期准备的作用。

之后,发送:AT+CLPORT=“TCP”,“2000”,设置本地 TCP 连接端口为 2000,然后发送:AT+CIPSTART=“TCP”,“113.111.214.69”,“8086”,建立 TCP 连接,连接到 IP:113.111.214.69,连接端口为:8086。等待 TCP 连接成功建立,模块返回:CONNECT OK。此时,SIM900A 模块和电脑便建立了一个 TCP 连接,可以互相发送数据了。

首先,我们来看如何通过SIM900A 模块给电脑发送数据
通过给模块发送:AT+CIPSEND,此时模块返回:>,然后我们发送字符串(不用发送新行):SIM900A TCP 连接测试,最后发送十六进制的:1A,启动数据发送。然后等待模块回应:SEND OK,说明发送成功。
同样,我们通过电脑的网络调试助手向模块发送数据,模块也会受到。TCP数据的收发成功!

注意,TCP 连接需要心跳维持,如果长时间没有数据的收发,那么 TCP 连接很可能会被断开,下次数据通信,又得重新连接,所以实际应用的时候,都是需要添加心跳,来维持当前 TCP 连接的(在后面的代码中有具体实现方法,可仔细查看)。

最后,我们要关闭 TCP 连接,发送:AT+CIPCLOSE=1,关闭当前 TCP 连接,再发送:AT+CIPSHUT,关闭场景。

2.程序源码(基于MSP430F149单片机)

各源文件与头文件分类及实现功能如下:
1.main.c(主程序)
2.Config.h
Config.c(基础配置文件,包括IO口初始化,按键扫描,串口,时钟等基础配置)
3.SIM900A.h
SIM900A.c(SIM900A模块配置的主文件,包括一些配置函数及实现方法)

1.main.c

#include"SIM900A.h"    //包含各种头文件,请依次向下查找
/*
解释说明:
1.注意:模块供电不足可能会一直回复IIII!
2.可以连接网页的云端服务器(如安信可)
3.可以通过开通隧道连接网络调试助手(即实现私网IP向公网的映射)
4.只要连接到网络后就可以访问网络服务器以进行各种操作(SNAT:获取实时网络时间,SMTP:进行邮件发送)等等
5.TCP连接通信过程中,不断和服务器进行心跳连接,每6S发送一次心跳信号,等待服务器回应,根据服务器返回的数据判断心跳是否成功,具体原理看程序。
*/
void main( void )
{
      WDT_Init_OFF();              //关闭看门狗
      Clock_Init_XT2();            //系统时钟初始化函数,XT2
      Port_Init();                 //P6口初始化,包括LED和按键KEY
      UART0_Init();                //MSP430串口0初始化
      UART1_Init();                //MSP430串口1初始化
      
      while(1)
      {
        sim900a_test(); //sim900a主测试程序
        delay_ms(1000);
      }   
}

2.Config.h及Config.c

/*************************Config.h*********************************/
#define u8  unsigned char  //无符号8位宏定义
#define u16 unsigned long //无符号16位宏定义
#define u32 unsigned int  //无符号32位宏定义
#define CPU_F         ((double)8000000)                                   //内部延时函数所需的时钟大小,单位为Hz
#define delay_ms(x)   __delay_cycles((long)(CPU_F*(double)x/1000.0))      //调用系统自定义函数__delay_cycles计算时钟
#define delay_us(x)   __delay_cycles((long)(CPU_F*(double)x/1000000.0))   //调用系统自定义函数__delay_cycles计算时钟
#define S1_PRE 1       //按键S1
#define S2_PRE 2       //按键S2
#define S3_PRE 3       //按键S3
#define S4_PRE 4       //按键S4

void WDT_Init_OFF();//看门狗初始化函数,关闭看门狗

void Port_Init();//IO口配置,包括LED和按键

u8 KEY_Scan(u8 mode);//*按键扫描函数,返回按键值,mode:0:不支持连按;1:支持连按

/*****************************定时器配置**************************************************/
void TimerA_Init_MC0(void);//TIMERA初始化程序,增计数模式MC0

void TimerA_Init_MC1(void);//TIMERA初始化程序,连续计数模式MC1

/*****************************串口配置****************************************************/
//串口波特率计算,当BRCLK=CPU_F时用下面的公式可以计算,否则要根据设置加入分频系数
#define baud           115200                                //设置波特率的大小
#define baud_setting   (u32)((u16)CPU_F/((u16)baud))  //波特率计算公式
#define baud_h         (u8)(baud_setting>>8)            //提取高位
#define baud_l         (u8)(baud_setting)               //低位

/***********************MSP430串口0初始化*************************/
extern u8 USART_RX_BUF[200];                                //串口0接收缓冲区,最大200个字节
extern u32 USART_RX_COUNT;                                //串口0接收字节计数

extern u8 USART1_RX_BUF[200];                                //串口1接收缓冲区,最大200个字节
extern u32 USART1_RX_COUNT;                                //串口1接收字节计数
extern u8 USART1_TX_BUF[200]; 	                              //串口1最大发送字节缓存区

void UART0_Init();//MSP430串口0初始化

void Send_Byte(u8 data);//串口0发送数据函数

u8 UART0_RX_SR(void);//判断串口0是否接收完成

/***********************MSP430串口1初始化*************************/
void UART1_Init();//MSP430串口1初始化

void UART1_Setbaud(u16 bps);    //串口1设置波特率函数

void UART1_Send_Byte(u8 data);//串口1发送数据函数

u8 UART1_RX_SR(void);//判断串口1是否接收完成

//串口1,printf函数
//确保一次性发送的数据不超过200个字节
void u3_printf(char* fmt,...);

int putchar(int c);//向终端输出一个字符,为了支持printf函数
/*****************************时钟配置***********************************************************/
void Clock_Init_DCO();//设置内部DCO振荡器初始化函数

void Clock_Init_XT2();//设置外部高频XT2振荡器时钟初始化函数

void Clock_Init_XT1();//设置外部低频XT1振荡器时钟初始化函数



/*************************Config.c*********************************/
#include"stdarg.h"	 	 
#include"stdio.h"
#include"string.h"
#include"Config.h"
#include<msp430x14x.h>

u8 INT_flag=0;                                        //定时器中断标志

u8 USART_RX_BUF[200];                                //串口0接收缓冲区,最大200个字节
u32 USART_RX_COUNT=0;                                //串口0接收字节计数

u8 USART1_RX_BUF[200];                                //串口1接收缓冲区,最大200个字节
u32 USART1_RX_COUNT=0;                                //串口1接收字节计数
u8 USART1_TX_BUF[200]; 	                              //串口1最大发送字节缓存区

/*****************************看门狗配置**************************************************/
//看门狗初始化函数,关闭看门狗
void WDT_Init_OFF()
{
  WDTCTL=WDTPW+WDTHOLD;        //将WDTCTL寄存器的WDTPW和WDTHOLD位置1
}
/*****************************IO口配置,包括LED和按键****************************************************/
void Port_Init()
{
  P1SEL = 0x00;                   //P1普通IO功能,KEY,KEY,KEY
  P1DIR = 0xF0;                   //P10~P13输入模式,外部电路已接上拉电阻
  
  P6SEL = 0x00;                   //P6口普通IO功能,LED,LED,LED
  P6DIR = 0xFF;                   //P6口输出模式
  P6OUT = 0XFF;                   //设置P6初始值输出为0xFF
}
/******************************按键扫描函数,返回按键值,mode:0:不支持连按;1:支持连按***********************/
u8 KEY_Scan(u8 mode)
{	 
    u8 key_check;
	static u8 key_up=1;//按键松开标志
	if(mode)key_up=1;  //支持连按		  
    key_check=P1IN;  //读取IO口状态
    key_check &= 0x0F; 
	if(key_up&&key_check!=0x0F) //判断是否有键按下
	{
	    delay_ms(10);//键盘消抖,延时10MS
        key_up=0;    
	    if(key_check==0x0E)return 1;
	    else if(key_check==0x0D)return 2;
	    else if(key_check==0x0B)return 3;
        else if(key_check==0x07)return 4;
	}
        else if(key_check!=0x0E&&key_check!=0x0D&&key_check!=0x0B&&key_check!=0x07)key_up=1;//必须确认按键全部松开,才能使按键松开标志置1,很重要!!!	    
 	    return 0;//无按键按下
}
/*****************************定时器配置**************************************************/
//TIMERA初始化程序,增计数模式MC0
void TimerA_Init_MC0(void)
{
      TACTL=TASSEL0+TACLR;       //设置时钟源来自ACLK=32.768KHz,并清零计数器,需要先初始化时钟XT1
      TACTL |= MC0;              //设置增计数模式MC0
      TACCR0=32768-1;           //设置计时时间为一秒:32768/32768s
      TACTL |= TAIE;            //允许计时器溢出中断
      _EINT();                  //开总中断
}
//TIMERA初始化程序,连续计数模式MC1
void TimerA_Init_MC1(void)
{
      TACTL=TASSEL0+TACLR;       //设置时钟源来自ACLK=32.768KHz,并清零计数器,需要先初始化时钟XT1
      TACTL |= MC1;              //设置增计数模式MC1,65535/32768s
      TACTL |= TAIE;            //允许计时器溢出中断
      _EINT();                  //开总中断
}
//中断服务子函数
#pragma vector=TIMERA1_VECTOR             
__interrupt void TIMERA_IRQ(void)
{
    switch(TAIV)                //需要判断中断的类型
  {
  case 2:break;
  case 4:break;
  case 10:INT_flag=1;break;     //设置标志位Flag
  }
}
/*****************************串口0配置****************************************************/
/MSP430串口0初始化
void UART0_Init()
{
  U0CTL|=SWRST;               //复位SWRST
  U0CTL|=CHAR;                //8位数据模式 
  U0TCTL|=SSEL1;              //SMCLK为串口时钟
  U0BR1=baud_h;               //BRCLK=8MHZ,Baud=BRCLK/N
  U0BR0=baud_l;               //N=UBR+(UxMCTL)/8
  U0MCTL=0x00;                //微调寄存器为0,波特率9600bps
  ME1|=UTXE0;                 //UART0发送使能
  ME1|=URXE0;                 //UART0接收使能
  U0CTL&=~SWRST;
  //IE1|=URXIE0;                //接收中断使能位
  
  P3SEL|= BIT4 + BIT5;        //设置3.4,3.5为功能端口,UART口功能
  P3DIR|= BIT4;               //设置3.4口方向为输出
}
//串口0发送数据函数
void Send_Byte(u8 data)
{
    U0TXBUF=data;
    while((IFG1&UTXIFG0)==0);          //等待中断标志位复位,应该会自动复位
}
//处理来自串口 0 的接收中断
#pragma vector=UART0RX_VECTOR
__interrupt void UART0_RX_ISR(void)
{
  USART_RX_BUF[USART_RX_COUNT]=U0RXBUF;  //将收到的数据放在缓存区
  USART_RX_COUNT++;                  //收到的字节数加一
}
//判断串口0是否接收完成
u8 UART0_RX_SR(void)             
{
   u32 temp=0;
   temp=USART_RX_COUNT;                //当前计数值赋给temp
   delay_ms(10);                       //延时用来判断接收是否完成
   if(temp==USART_RX_COUNT&&USART_RX_COUNT!=0)            //判断计数值是否等于当前值,即判断是否接收完成
     return 1;                         //接收完成,返回1
   else 
     return 0;                         //未接收完成,返回0
}
//处理来自串口 0 的发送中断,预留
#pragma vector=UART0TX_VECTOR
__interrupt void UART0_TX_ISR(void)
{
  
}
//向终端输出一个字符,为了支持printf函数
int putchar(int c)
{
     if(c=='\n')
     {
        U0TXBUF='\r';
        while((IFG1&UTXIFG0)==0);        //发送寄存器空的时候发送数据
     }
     U0TXBUF=c;
     while((IFG1&UTXIFG0)==0);          //发送寄存器空的时候发送数据
     
     return c;
}
/*****************************串口1配置****************************************************/
void UART1_Init()
{
  U1CTL|=SWRST;               //复位SWRST
  U1CTL|=CHAR;                //8位数据模式 
  U1TCTL|=SSEL1;              //SMCLK为串口时钟
  U1BR1=baud_h;               //BRCLK=8MHZ,Baud=BRCLK/N
  U1BR0=baud_l;               //N=UBR+(UxMCTL)/8
  U1MCTL=0x00;                //微调寄存器为0,波特率9600bps
  ME2|=UTXE1;                 //UART1发送使能
  ME2|=URXE1;                 //UART1接收使能
  U1CTL&=~SWRST;
  IE2|=URXIE1;                //接收中断使能位
  _EINT();                   //开中断 ,为串口接收中断服务
  
  P3SEL|= BIT6 + BIT7;        //设置3.4,3.5为功能端口,UART口功能
  P3DIR|= BIT6;               //设置3.4口方向为输出
}

void UART1_Setbaud(u16 bps)    //串口1设置波特率函数
{
  u32 baud_set=(u32)((u16)CPU_F/((u16)bps));  //波特率计算公式
  u8 bps_h=(u8)(baud_set>>8);                 //提取高位
  u8 bps_l=(u8)(baud_set);                    //低位
  U1BR1=bps_h;                                //BRCLK=8MHZ,Baud=BRCLK/N
  U1BR0=bps_l;                                //N=UBR+(UxMCTL)/8
  U1MCTL=0x00;                                //微调寄存器为0
}

//串口1发送数据函数
void UART1_Send_Byte(u8 data)
{
    U1TXBUF=data;
    while((IFG2&UTXIFG1)==0);          //等待中断标志位复位,应该会自动复位
}
//处理来自串口 1 的接收中断
#pragma vector=UART1RX_VECTOR
__interrupt void UART1_RX_ISR(void)
{
  USART1_RX_BUF[USART1_RX_COUNT]=U1RXBUF;  //将收到的数据放在缓存区
  USART1_RX_COUNT++;                  //收到的字节数加一
}
//判断是否接收完成
u8 UART1_RX_SR(void)             
{
   u32 temp=0;
   temp=USART1_RX_COUNT;                //当前计数值赋给temp
   delay_ms(10);                       //延时用来判断接收是否完成
   if(temp==USART1_RX_COUNT&&USART1_RX_COUNT!=0)            //判断计数值是否等于当前值,即判断是否接收完成
     return 1;                         //接收完成,返回1
   else 
     return 0;                         //未接收完成,返回0
}
//处理来自串口 1 的发送中断,预留
#pragma vector=UART1TX_VECTOR
__interrupt void UART1_TX_ISR(void)
{
  
}
//串口1数据发送函数(主要用来和ESP8266之间的通信)
//确保一次性发送的数据不超过200个字节
void u3_printf(char* fmt,...)  
{  
	u16 i,j;
	va_list ap;
	va_start(ap,fmt);
	vsprintf((char*)USART1_TX_BUF,fmt,ap);
	va_end(ap);
	i=strlen((const char*)USART1_TX_BUF);//得到此次发送数据的长度
	for(j=0;j<i;j++)                      //循环发送数据
	{
	      U1TXBUF=USART1_TX_BUF[j];
              while((IFG2&UTXIFG1)==0);      //等待中断标志位复位,应该会自动复位
	}
}
/*****************************时钟配置***********************************************************/
//设置内部DCO振荡器初始化函数
void Clock_Init_DCO()              
{
  DCOCTL=DCO0+DCO1+DCO2;       //DCO选为最大的频率,0,1,2三位均输出为1
  BCSCTL1|=XT2OFF;             //关闭外部高频晶体
  BCSCTL1=RSEL0+RSEL1+RSEL2;   //最大标称频率,大概5MHz
}
//设置外部高频XT2振荡器时钟初始化函数
void Clock_Init_XT2()
{
   u8 i;
   BCSCTL1 &=~XT2OFF;           //打开XT2晶振,XT2OFF写0
   BCSCTL2 |=SELM1+SELS;        //MCLK=SMCLK=XT2,8MHz,0x80+0x08
   /*判断晶振是否稳定*/
   do
   {
   IFG1 &=~OFIFG;               //清除OFIFG标志位
   for(i=0;i<100;i++)           //等待晶振稳定
   _NOP();
   }
   while((IFG1 & OFIFG)!=0);
   IFG1 &=~OFIFG;               //清除OFIFG标志位
}
//设置外部低频XT1振荡器时钟初始化函数
void Clock_Init_XT1()
{
   u8 i;
   BCSCTL1 |= XT2OFF;           //关闭XT2晶振,XT1默认打开
   BCSCTL2 |=SELM1+SELM0;       //MCLK=XT1,32.768KHz,0x80+0x40,SMCLK默认来自DCO备注:SMCLK时钟不能由XT1提供!!!!!!!
     /*判断晶振是否稳定*/
   do
   {
   IFG1 &=~OFIFG;               //清除OFIFG标志位
   for(i=0;i<100;i++)           //等待晶振稳定
   _NOP();
   }
   while((IFG1 & OFIFG)!=0);
   IFG1 &=~OFIFG;               //清除OFIFG标志位
}

3.SIM900A.h及3.SIM900A.c

/********************************SIM900A.h*********************************/
#include"Config.h"

void sim900a_test(void);                          //sim900a主测试程序

u8 sim900a_send_cmd(u8 *data,u8 *ack,u16 waittime);//向SIM900A发送指定命令(带打印回显)

u8 sim900a_send_data(u8 *data,u8 *ack,u16 waittime);//向SIM900A发送指定数据(不带打印回显)

u8* sim900a_send_check_cmd(u8 *str);               //SIM900A发送命令后,检测接收到的应答

void sim900a_mtest_ui(void);                      //显示一些模块信息

u8 sim900a_gsminfo_show(void);                    //GSM信息显示(运营商,信号质量,电池电量,日期时间),返回0正常

u8 sim900a_gprs_test(const u8 *ip,const u8 *port); //sim900a GPRS测试,返回0正常
/********************************SIM900A.c*********************************/
#include"SIM900A.h"
/***************GPRS连接的IP地址和端口PORT*********************/
//备注:1.可以连接网页的云端服务器(如安信可)
//      2.可以通过开通隧道连接网络调试助手
//连接端口号:8086,根据实际情况修改为其他端口.
const u8* portnum="8086";		 
//定义为连接的IP地址(注意要是公网IP)
const u8 *IP_address="113.111.214.69";

//sim900a主测试程序
void sim900a_test(void)
{
	u8 key=0; 
	u8 timex=0;
        u8 sim_ready=0;
        printf("SIM900A 测试程序,GPRS测试\n");
	while(sim900a_send_cmd("AT","OK",100))//检测是否应答AT指令 
	{
            printf("未检测到模块!!!\n");
            delay_ms(800);
            printf("尝试连接模块...\n");
            delay_ms(400);  
	}
        printf("SIM900A连接成功\n");
	sim900a_send_cmd("ATE0","OK",200);//关闭回显
	sim900a_mtest_ui();               //加载主页面,显示制造商,模块名称,序列号
        if(sim900a_gsminfo_show()==0)sim_ready=1;//SIM卡就绪(显示运营商,信号质量,电池电量,日期时间)
        printf("\nS3_PRE:GPRS测试\n");
	while(1)
	{
          if(sim_ready==1)//SIM卡就绪(显示运营商,信号质量,电池电量,日期时间)
          {
            key=KEY_Scan(0); 
            if(key)
            {
                switch(key)
                {
                    case S1_PRE:
                      //sim900a_call_test();//拨号测试
                      break;
                    case S2_PRE:
                      //sim900a_sms_test(); //短信测试
                      break;
                    case S3_PRE:
                      sim900a_gprs_test(IP_address,portnum);//GPRS测试(入口参数为IP地址和端口号)
                      break;
                }
                timex=0;
            } 			
          }
          if((timex%20)==0)P6OUT^=BIT0;//200ms闪烁 
          timex++;	 
	} 	
}
//显示一些模块信息
void sim900a_mtest_ui(void)
{
        u8 *p1;
        u8 p[50];
	if(sim900a_send_data("AT+CGMI","OK",200)==0)				//查询制造商名称
	{ 
		p1=(u8*)strstr((const char*)(USART1_RX_BUF+2),"\r\n");//查找回车
		p1[0]=0;                                                        //加入结束符,目的是吧\r\n后面数据截断
		sprintf((char*)p,"制造商:%s",USART1_RX_BUF+2);
		printf("%s\n",p);
		USART1_RX_COUNT=0;		                                //串口1发送数据个数清零
	} 
	if(sim900a_send_data("AT+CGMM","OK",200)==0)//查询模块名字
	{ 
		p1=(u8*)strstr((const char*)(USART1_RX_BUF+2),"\r\n"); //查找回车
		p1[0]=0;                                  //加入结束符,目的是吧\r\n后面数据截断
		sprintf((char*)p,"模块型号:%s",USART1_RX_BUF+2);
		printf("%s\n",p);
		USART1_RX_COUNT=0;		                       //串口1发送数据个数清零	
	} 
	if(sim900a_send_data("AT+CGSN","OK",200)==0)                    //查询产品序列号
	{ 
		p1=(u8*)strstr((const char*)(USART1_RX_BUF+2),"\r\n");//查找回车
		p1[0]=0;                                               //加入结束符,目的是吧\r\n后面数据截断
		sprintf((char*)p,"序列号:%s",USART1_RX_BUF+2);
		printf("%s\n",p);
		USART1_RX_COUNT=0;		//串口1发送数据个数清零			
	}
}


//SIM900A发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果
//    其他,期待应答结果的位置(str的位置)
u8* sim900a_send_check_cmd(u8 *str)
{
	char *strx=0;
	if(UART1_RX_SR())		//接收到一次数据了
	{ 
		USART1_RX_BUF[USART1_RX_COUNT]=0;//添加结束符
		strx=strstr((const char*)USART1_RX_BUF,(const char*)str);
	} 
	return (u8*)strx;
}
//向SIM900A发送指定数据
//data:发送的数据(不需要添加回车了)
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
//       1,发送失败
u8 sim900a_send_cmd(u8 *data,u8 *ack,u16 waittime)
{
	u8 res=0; 
	USART1_RX_COUNT=0;              //串口1发送数据个数清零
        if((u32)data<=0XFF)              //需要发送的是数据
	{
	      U1TXBUF=(u16)data;          //将数据通过串口发送出去
              while((IFG2&UTXIFG1)==0);      //等待中断标志位复位,应该会自动复位
	}
        else  u3_printf("%s\r\n",data); //需要发送的是命令
	if(ack&&waittime)		//需要等待应答
	{
            while(--waittime)	//等待倒计时
            {
                delay_ms(10);
                if(UART1_RX_SR())//接收到期待的应答结果
                {
                    if(sim900a_send_check_cmd(ack))
                    {
                       printf("%s_ack:%s\r\n",data,(u8*)ack);
                       break;//得到有效数据 
                    }
                    USART1_RX_COUNT=0;//串口1接受数据清零
                } 
            }
            if(waittime==0)res=1; 
	}
	return res;
}
//向SIM900A发送指定数据
//data:发送的数据(不需要添加回车了)
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
//       1,发送失败
u8 sim900a_send_data(u8 *data,u8 *ack,u16 waittime)
{
	u8 res=0; 
	USART1_RX_COUNT=0;       //串口1发送数据个数清零
        if((u32)data<=0XFF)              //需要发送的是数据
	{
	      U1TXBUF=(u16)data;          //将数据通过串口发送出去
              while((IFG2&UTXIFG1)==0);      //等待中断标志位复位,应该会自动复位
	}
        else u3_printf("%s\r\n",data);	 //发送命令
	if(ack&&waittime)		//需要等待应答
	{
            while(--waittime)	//等待倒计时
            {
                delay_ms(10);
                if(UART1_RX_SR())//接收到期待的应答结果
                {
                    if(sim900a_send_check_cmd(ack))
                    break;
                    USART1_RX_COUNT=0;//串口1接受数据清零
                } 
            }
            if(waittime==0)res=1; 
	}
	return res;
}
//GSM信息显示(运营商,信号质量,电池电量,日期时间)
//返回值:0,正常
//    其他,错误代码
u8 sim900a_gsminfo_show(void)
{
	u8 *p1,*p2;
        u8 p[50];
	u8 res=0;
	USART1_RX_COUNT=0;                                      //串口1接受数据清零
        printf("\n");
	if(sim900a_send_data("AT+CPIN?","OK",200))res|=1<<0;	//查询SIM卡是否在位 
	USART1_RX_COUNT=0;                                      //串口1接受数据清零 
	if(sim900a_send_data("AT+COPS?","OK",200)==0)		//查询运营商名字
	{ 
		p1=(u8*)strstr((const char*)(USART1_RX_BUF),"\""); 
		if(p1)//有有效数据
		{
			p2=(u8*)strstr((const char*)(p1+1),"\"");
			p2[0]=0;//加入结束符			
			sprintf((char*)p,"运营商:%s",p1+1);
			printf("%s\n",p);
		} 
		USART1_RX_COUNT=0;                              //串口1接受数据清零		
	}else res|=1<<1;
	if(sim900a_send_data("AT+CSQ","+CSQ:",200)==0)		//查询信号质量
	{ 
		p1=(u8*)strstr((const char*)(USART1_RX_BUF),":");
		p2=(u8*)strstr((const char*)(p1),",");
		p2[0]=0;//加入结束符
		sprintf((char*)p,"信号质量:%s",p1+2);
		printf("%s\n",p);
		USART1_RX_COUNT=0;                                      //串口1接受数据清零		
	}else res|=1<<2;
	if(sim900a_send_data("AT+CBC","+CBC:",200)==0)		//查询电池电量
	{ 
		p1=(u8*)strstr((const char*)(USART1_RX_BUF),",");
		p2=(u8*)strstr((const char*)(p1+1),",");
		p2[0]=0;p2[5]=0;//加入结束符
		sprintf((char*)p,"电池电量:%s%%  %smV",p1+1,p2+1);
		printf("%s\n",p);
		USART1_RX_COUNT=0;                                      //串口1接受数据清零		
	}else res|=1<<3; 
	if(sim900a_send_data("AT+CCLK?","+CCLK:",200)==0)		//查询电池电量
	{ 
		p1=(u8*)strstr((const char*)(USART1_RX_BUF),"\"");
		p2=(u8*)strstr((const char*)(p1+1),":");
		p2[3]=0;//加入结束符
		sprintf((char*)p,"日期时间:%s",p1+1);
		printf("%s\n",p);
		USART1_RX_COUNT=0;                                      //串口1接受数据清零		
	}else res|=1<<4; 
	return res;
} 

//sim900a GPRS测试
//用于测试TCP/UDP连接
//返回值:0,正常
//    其他,错误代码
u8 sim900a_gprs_test(const u8 *ip,const u8 *port)
{
    u8 *p2,*p3;
	u8 key;
	u16 timex=0; 
    u32 rlen=0;
    u8 connectsta=0;			//0,正在连接;1,连接成功;2,连接关闭;3.退出测试; 
	u8 hbeaterrcnt=0;			//心跳错误计数器,连续8次心跳信号无应答,则重新连接,9,退出测试
    u8 p[50];
    u8 p1[50]="华盛顿";                     //要发送的数据缓存区
    u8 p5[50];                              //接受的数据缓存区
    printf("ip=%s\n",ip);           //ip地址显示
    printf("port=%s\n",port);       //端口显示
 	sim900a_send_cmd("AT+CIPCLOSE=1","CLOSE OK",100);	//关闭连接
	sim900a_send_cmd("AT+CIPSHUT","SHUT OK",100);		//关闭移动场景 
        
	if(sim900a_send_cmd("AT+CGCLASS=\"B\"","OK",1000))return 1;		  //设置GPRS移动台类别为B,支持包交换和数据交换 
	if(sim900a_send_cmd("AT+CGDCONT=1,\"IP\",\"CMNET\"","OK",1000))return 2; //设置PDP上下文,互联网接协议,接入点等信息
	if(sim900a_send_cmd("AT+CGATT=1","OK",500))return 3;			  //附着GPRS业务
	if(sim900a_send_cmd("AT+CIPCSGP=1,\"CMNET\"","OK",500))return 4;	  //设置为GPRS连接模式
	if(sim900a_send_cmd("AT+CIPHEAD=1","OK",500))return 5;	 		  //设置接收数据显示IP头(方便判断数据来源)
        
    printf("TCP连接\n");
    sim900a_send_cmd("AT+CLPORT=\"TCP\",\"2000\"","OK",500);//设置本地 TCP 连接,端口为 2000
    sprintf((char*)p,"AT+CIPSTART=\"TCP\",\"%s\",\"%s\"",ip,port);
    if(!sim900a_send_cmd(p,"OK",500))                          //发起连接
      printf("TCP连接成功\r\n");
    else 
      printf("TCP连接失败\r\n");
        
    printf("S2_PRE:退出测试状态下重新进行连接\n");
    printf("S3_PRE:发送数据\n");
    printf("S4_PRE:退出测试\n");
	while(1)
	{
		key=KEY_Scan(0);
                if(key==S2_PRE&&(connectsta==3)&&(hbeaterrcnt==9))                        //退出测试状态下重新进行连接
		{  
                  printf("退出测试状态下重新进行连接\n");
                  connectsta=2;       //正在连接
                }
                if(key==S3_PRE&&(hbeaterrcnt==0))          //发送数据(心跳正常时发送)
		{   
                  printf("\n数据发送中...\n");
                  if(sim900a_send_data("AT+CIPSEND",">",500)==0)		//发送数据
                  { 
                      printf("CIPSEND DATA:%s\r\n",p1);	 		//发送数据打印到串口
                      u3_printf("%s",p1);                               //通过模块向外发送数据
                      delay_ms(10);                                    
                      if(sim900a_send_data((u8*)0X1A,"SEND OK",1000)==0)printf("数据发送成功\n");//发送结束符
                      else printf("数据发送成功\r\n");
                  }
                  else sim900a_send_cmd((u8*)0X1B,0,0);	//ESC,取消发送 
		} 
                if(key==S4_PRE)                        //退出测试
		{   
                    sim900a_send_cmd("AT+CIPCLOSE=1","CLOSE OK",500);	//关闭连接
                    sim900a_send_cmd("AT+CIPSHUT","SHUT OK",500);	//关闭移动场景
                    connectsta=3;                                       //退出测试状态
                    hbeaterrcnt=9;                                      //退出测试状态
                    printf("退出测试,按S2重新连接\n");
		}
                if((timex%20)==0)                     //200ms动作:
		{
			P6OUT^=BIT0;                  //LED闪烁
			if(connectsta==2||hbeaterrcnt==5)//连接中断了,或者连续5次心跳没有正确发送成功,则重新连接
			{
				sim900a_send_cmd("AT+CIPCLOSE=1","CLOSE OK",500);	//关闭连接
				sim900a_send_cmd("AT+CIPSHUT","SHUT OK",500);		//关闭移动场景 
 /*************************重新进行TCP连接***************************************/
                printf("重新进行TCP连接\n");
                sim900a_send_cmd("AT+CLPORT=\"TCP\",\"2000\"","OK",500);//设置本地 TCP 连接,端口为 2000
                sprintf((char*)p,"AT+CIPSTART=\"TCP\",\"%s\",\"%s\"",ip,port);
                if(!sim900a_send_cmd(p,"OK",500))                          //发起连接
                     printf("重新进行TCP连接成功\n");
                else 
                     printf("重新进行TCP连接失败\n");
				connectsta=0;	                       //连接状态指示为正在连接
 				hbeaterrcnt=0;                         //心跳计数清零
			}
		}
		if(connectsta==0&&(timex%200)==0)//连接还没建立的时候,每2秒查询一次CIPSTATUS.
		{
			sim900a_send_cmd("AT+CIPSTATUS","OK",500);	                 //查询连接状态
			if(strstr((const char*)USART1_RX_BUF,"CLOSED"))connectsta=2;    //连接关闭
			if(strstr((const char*)USART1_RX_BUF,"CONNECT OK"))connectsta=1;//连接成功
		}
        if(connectsta==1&&timex>=600)//连接正常的时候,每6秒发送一次心跳
		{
			timex=0;
			if(sim900a_send_data("AT+CIPSEND",">",200)==0)//发送数据
			{
				 sim900a_send_data((u8*)0X00,0,0);	//发送数据:0X00  
				 delay_ms(20);				//必须加延时
				 sim900a_send_data((u8*)0X1A,0,0);	//CTRL+Z,结束数据发送,启动一次传输	
			}else    sim900a_send_data((u8*)0X1B,0,0);      //ESC,取消发送 		
			hbeaterrcnt++;                            //心跳数加一,计数值不为零,则可以在接受中判断心跳应答
			printf("hbeaterrcnt:%d\r\n",hbeaterrcnt); //方便调试代码
		} 
	      delay_ms(10);
              //检查GSM模块发送过来的数据,及时上传给电脑
              if(UART1_RX_SR())		//接收到一次数据了
              {
                rlen=USART1_RX_COUNT;	        //得到本次接收到的数据长度
                USART1_RX_BUF[rlen]=0;		//添加结束符 
                if(hbeaterrcnt)				//需要检测心跳应答
                {
                   if(strstr((const char*)USART1_RX_BUF,"SEND OK"))//如果心跳正常
                    hbeaterrcnt=0;      //心跳计数清零
                }				
                p2=(u8*)strstr((const char*)USART1_RX_BUF,"+IPD");               //获得"+IPD"在USART1_RX_BUF的位置
                if(p2)                       //收到"+IPD",即p2指针不为空,接收到的是TCP/UDP数据
                {
                    p3=(u8*)strstr((const char*)p2,",");
                    p2=(u8*)strstr((const char*)p2,":");
                    p2[0]=0;                            //加入结束符
                    sprintf((char*)p5,"收到%s字节,内容如下:",p3+1);//接收到的字节数
                    printf("%s\n",p5);//显示接收到的数据长度
                    printf("%s\n",p2+1);//显示接收到的数据 
                }
                USART1_RX_COUNT=0;
              }
              timex++;
	}
} 

三.附:内网向外网映射方法

1.若是可以访问路由器,则可通过设置DMZ转发规则,使得内网向外网映射,网上有很多设置教程,不详细讲。
2.若是不可以访问路由器(如教育网,或者公司网络),则可以通过开辟隧道的方式进行映射(ngrok),具体教程网上也有,这里只附上登录网址:ngrok访问网址:https://www.ngrok.cc/user.html
通过该网站上的配置建立TCP连接隧道,然后通过配置文件连接隧道,使隧道在线。

四. 感谢支持

    完结撒花!希望看到这里的小伙伴能点个关注,我后续会持续更新,也欢迎大家广泛交流。
    码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tutu-hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值