单片机通过短信猫收发短信的方法
短信在现今的生活中起着非常重要的作用,我们每天都会使用它来进行信息的接收与发送,为我们的沟通提供新的手段。从本质上说,其实是一种数据传输的机制,通过GSM(全球移动通信系统)作为其传输的道路,从而实现了数据的远距离传输。如果我们把它运用于单片机上,就可以实现单片机上的数据远距离传输,这在实际的应用中是十分有用的。如数据的远程采集与传输、环境的监测与报警等。
然而要实现单片机发送短信,就要依赖于专用的硬件――短信猫(GSM Modem),,它用来负责与GSM网络进行通信,而单片机则负责短信的编解码。下面介绍短信的编码,其实这些编码我们每天都在使用,只是它藏匿于手机或终端设备中,不被我们熟知。
短信编码
以一个发送实例来进行讲解:
笔者位于哈尔滨市,其短信中心号码为13800451500,短信接收方号码采用我本人的号码15846003114,短信内容(最多为140个字节,如果是中文则最多为70个字)为“大家好!!!”。
先列出最终的编码,然后再来慢慢分析:
0891683108401505F011000B815148063011F40008A70C59275BB6597DFF01FF01FF01
将以上编码通过串口写入短信猫,再辅以相应的AT指令与附加信息就可以成功发送短信。
1.08:短信中心地址长度(可以固定不变)
2.91:短信中心号码类型(可以固定不变)
3.68:中国地区代码(在中国范围内固定不变)
4.3108401505F0:短信服务中心号码 13800451500
(根据地域的不同进行变动,实际情况下,可以固定不变,不论在中国任何地域都通过哈尔滨短信中心,但使用当地的服务中心收发会更快。)
可以看到,短信服务中心号码采用了一种比较特殊的表示方法。其实也很简单就是在短信服务中心号码后加一个F,号码长度就变成了12位,然后对它每两位中的字符进行对调。在后面的接收方号码也是采用此种方法进行表示的,对这一点的理解的至关重要的,它将直接关系到短信发送的正确与否。
5.1100:发送短信的编码方式(可以固定不变)
6.0B:目的地址长度(可以固定不变)
7.81:目的地址类型(可以固定不变)
8. 5148063011F4:目的地址,即接收方号码 15846003114
如前面所说,接收方号码亦采用此种方法。
9.0008:发送中文字符方式
10.A7:(可以固定不变)
11.0C:短信内容长度
12. 59275BB6597DFF01FF01FF01:发送中文字符的UNICODE码
(根据发送信息内容进行变动)
中文的发送采用UNICODE编码。由于手机上对编程的解码也是采用UNICODE的,因此只有只用UNICODE编码才能使短信内容成功显示在手机上。其次,在向手机发送短信时,短信内容需要使用内存方式的显示表示,如01FF为一个字符的UNICODE码,在编码中它为4个ASCII码,而它表示是0x01ff,为两个字节,这一点至关重要,不采用这种表示方法或表示有错,则会导致在接收方手机上无显示或显示不正常。
此编码段是单片机进行数据传输的核心部分,它是真正的数据。在日常的中文短信编码中均采用UNICODE,但其本身并不局限于UNICODE,如果接收终端是支持GB2312的,而不支持UNICODE,那么就可以在这里放相应的GB码。脱离开字符编码,其实可以放入任何数据,只是要在上一个编码段中设置相应的数据长度即可。
在笔者的短信收发系统中,采用西门子的TC35i作为短信猫,由于使用了一种支持GB2312硬件字库的液晶显示器,因此在数据编码中直接使用GB码,成功实现短信发送与接收,并在显示器上对解码后的内容进行显示,达到了较好的效果。
14.:发送结束标志(十六进制为0x1A),表示短信码结束。
结束标志在实际的操作中很容易遗忘,请读者加以注意。
UNICODE与GB2312码
与短信收发相关的AT指令
短信编码还要辅以相应的AT指令,才能实现短信的发送。AT指令如下:
AT:用以与Modem的握手,返回OK则说明握手成功。
AT+CMGR=X:用以读取第X条短信。
AT+CMGD=X:用以删除第X条短信。
AT+CMGS=X:设置发送短信的字节数为X。
实际的AT指令集功能非常丰富,在应用中也是非常重要,尤其在手机开发中更如此。在这里只是实现简单的短信收发,只涉及到以上几条AT指令。读者如果想进行深入的了解,可以翻阅相应的手册。
短信的发送过程(以下过程中>代表发送,<代表接收)
通讯握手
>AT(回车 十六进制的0x0d 0x0a)
收到OK,说明握手成功。
2. 设置短信字节数
>AT+CMGS=26(回车)
设置短信字节数,由笔者的使用经验,后面的数值为短信内容的字节数+14。
如上面的编码中内容为12个字节,则后面的数值为12+14=26
3. 发送短信编码
<>
收到>符号,说明猫正在等待接收短信编码
>0891683108401505F011000B815148063011F40008A70C59275BB6597DFF01FF01FF01
发送短信编码,送送完毕后,如果短信猫已经接收,则启动短信发送。
4.短信发送结果检测
<+CMGS:45
在短信发送成功后,会返回“+CMGS”,后面的45,是表示第45条短信。如果没有返回此字符串则说明发送失败。
以上过程可以通过串口调试工具手工进行,而这里需要实现的是单片机的自动发送。握手、设置短信字节数、发送短信编码、发送结果检测等都需要单片机来实现。
(5)单片机实现短信自动发送
进行短信发送的前提是短信内容的正确编码。经过以上对短信发送过程的分析,可以通过单片机对其进行实现。下面是实现程序例程:
/*-------------------------------------------------------------------------
函数名:PDU_SMS()
功能 :发送短信
参数说明:SMS_Center为短信中心号码 11位
SMS_Telenum为短信接收方的号码 11位
SMS_Context为短信的内容
--------------------------------------------------------------------------------*/
int PDU_SMS(char *SMS_Center, char *SMS_Telenum,
char *SMS_Context,char is_GB)
{
int i,j;
unsigned char len,time;
char lens[3];
time=0;
for(i=0;i<300;i++) PDU_Code[i]=PDU_t[i];
/*----------设置短信中心号码--------------*/
for (i = 0, j = 0; i < strlen(SMS_Center) / 2; i++)
{
PDU_Code[6+(j++)] = SMS_Center[2 *i + 1];
PDU_Code[6+(j++)] = SMS_Center[2 *i];
}
PDU_Code[6+j++] = 'F'; //在最后补上的F
PDU_Code[6+j] = SMS_Center[strlen(SMS_Center) - 1];
/*---------------------------------------------*/
/*----------设置接收号码--------------*/
for (i = 0, j = 0; i < strlen(SMS_Telenum) / 2; i++)
{
PDU_Code[26+(j++)] = SMS_Telenum[2 *i + 1];
PDU_Code[26+(j++)] = SMS_Telenum[2 *i];
}
PDU_Code[26+j++] = 'F';
PDU_Code[26+j] = SMS_Telenum[strlen(SMS_Telenum) - 1];
/*---------------------------------------------*/
/*----------设置短信内容长度--------------*/
if(is_GB==0)
len = strlen(SMS_Context) *2;
else
len = strlen(SMS_Context);
PDU_Code[44] = (len >> 4) > 9 ? (len >> 4) + 55: (len >> 4) + 48;
PDU_Code[45] = (len &0x0f) > 9 ? (len &0x0f) + 55: (len &0x0f) + 48;
/*---------------------------------------------*/
/*----------编码短信内容--------------*/
if(is_GB==0)//如果不是GB码,短信内容为ascii码字符串
{
for (i = 0,j=0; i
{
szzh16(SMS_Context[j++],lens);
PDU_Code[46+i*4] = '0';
PDU_Code[46+i*4+1] = '0';
PDU_Code[46+i*4+2] = lens[0];
PDU_Code[46+i*4+3] = lens[1];
}
PDU_Code[46+i*4] = 0x1a;
PDU_Code[46+i*4+1] = 0xff;
}
else
//短信内容为GB码,如果要使手机能够显示,改到UNICODE编码
{
for (i = 0,j=0; i
{
szzh16((int)SMS_Context[j++],lens);
PDU_Code[46+i*2] = lens[0];
PDU_Code[46+i*2+1] = lens[1];
}
PDU_Code[46+i*2] = 0x1a;
PDU_Code[46+i*2+1] = 0xff;
}
/*---------------------------------------------*/
if(PDU_HandShake())
{
do
{
//LCD_PutChn(5,96,"SS...");
//if(is_GB)
PDU_EnablePDU();
if(is_GB==0)
PDU_SetLength(Strlen(SMS_Context)*2);
else
PDU_SetLength(Strlen(SMS_Context));
PDU_Send(PDU_Code);
//LCD_PutEng(5,96,"SS");
for(i=0;i<25;i++)
delay(20000);
sbuf[counter]=0;
//LCD_PutNum16(5,96,time);
counter=0;
time++;
}
//判断是否发送成功,如果不成功继续发送,最多4次,如仍不成功,返回0
while(strpos(sbuf+strlen(sbuf)-20,'G')==-1&&time<4);
if(strpos(sbuf+strlen(sbuf)-20,'G')==-1)
return 0;
else
return 1; //成功的话返回0
}
else
{
//LCD_PutEng(5,96,"LL");
sbuf[counter]=0;
//LCD_PutEng(0,0,sbuf);
counter=0;
return 0;
}
}
以上程序成功实现短信的发送,其中的一些函数限于篇幅可自行实现。
(6)单片机对短信的读取与解码
单片机可以通过AT指令对短信猫中的短信进行读取,并对读入的短信数据进行分析与解码。
读出的短信格式与发送时的短信编码大致是相同的。下面给出相应的程序例程,读者可以在自行实验中对照验证。
读取某一条短信,并将其进行显示
/*-------------------------------------------------------
函数名:LAD_SMS()(short for "Load And Display the Short MessageS")
功能:用户函数,读取第n条短信,并在LCD的(x,y)位置显示出来
作者:于振南
----------------------------------------------------------*/
unsigned char LAD_SMS(unsigned char n,unsigned char x,unsigned char y)
{
unsigned char i,len,t;
char temp[5];
char temp1[3];
//IN_Draw_BlankorBlackRect(0,20,30,72,0);
szzh10(n,temp1);
//将n转为相应的字符串,如n=21,则字符串为"21",用以与AT指令拼接。
t=85;
clear_sbuf();
counter=0;
send_s("AT+CMGR=");//AT+CMGR为读取短信的AT指令
send_s(temp1);//上面所得的字符串
send(0x0d);
send(0x0a);
for(i=0;i<10;i++) delay(10000); //等待读取完毕
sbuf[counter]=0; //在收到的数据末尾附加'\0'
temp[0]=sbuf[23];
<, SPAN style="mso-spacerun: yes"> temp[1]=sbuf[24];
temp[2]=sbuf[25];
temp[3]=0;
if(sbuf[25]!=0x0d) t++;
for(i=0;i
len=atoi(temp); //获取收到的短信内容长度
//LCD_PutEng(23,76,"(SM:");
//LCD_PutNum16(27,76,n);
//LCD_PutEng(29,76,")");
if(len==0)
{
//LCD_PutEng(5,96,"EMP");
//LCD_PutChn(x,y,">短信空");
delay(50000);
return 0;
}
len-=20;
if(len>90)
{
//LCD_PutEng(5,96,"MTL");
//LCD_PutChn(x,y,">短信太长");
delay(50000);
return 0;
}
for(i=0;i
{
temp[0]=sbuf[t+2*i];
temp[1]=sbuf[t+2*i+1];
temp[2]=0;
sbuf[i]=_hex_(temp);
//收到的短信内容是内存方式的显示表示,转为十六进制数
}
sbuf[i]=0;
Analysis_Pro();//解码后的内容在sbuf中,此函数对其进行显示输出
//LCD_PutEng(x,y,inf_bw.Date);
//LCD_PutEng(x,y,sbuf+29);
delay(50000);
clear_sbuf();
counter=0;
return 1;
}
检测新短信
/*-------------------------------------------------------
函数名:Check_New()
功能:用户函数,检测有无新的短信,如果有返回1,否则返回0
作者:于振南
----------------------------------------------------------*/
unsigned char Check_New()
{int i;
send_s("AT+CMGL=0"); //AT+CMGL=0为读取新短信的AT指令
send(0x0d);
send(0x0a); //发送回车
delay(10000); //等待接收完毕
if(sbuf[12]=='O') return 0xff;
if(sbuf[12]=='+')
{
for(i=18;i<23;i++)
if(sbuf[i]==',') sbuf[i]=0;
return atoi(sbuf+19); //返回新短信的位置
}
}
删除某条短信
/*-------------------------------------------------------
函数名:Delete()
功能:用户函数,删除第n条短信
作者:于振南
----------------------------------------------------------*/
unsigned char Delete(unsigned char n)
{
char t[10];
char t1[5];
strcpy(t,"AT+CMGD="); //AT+CMGD为删除短信的AT指令
szzh10(n,t1);
strcpy(t+8,t1);
while(Send_AT_CMD(t)!=1);
//LCD_PutEng(5,96,"SM");
//LCD_PutNum16(7,96,n);
//LCD_PutEng(10,96,"De");
delay(60000);
return 1;
}