文章目录
一.模块介绍
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分把,感谢各位大佬支持!