單片機通過短信貓收發短信的方法
短信在現今的生活中起著非常重要的作用,我們每天都會使用它來進行信息的接收與發送,為我們的溝通提供新的手段。從本質上說,其實是一種數據傳輸的機制,通過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< />
收到OK,說明握手成功。
2. 設置短信字節數
>AT+CMGS=26(回車)
設置短信字節數,由筆者的使用經驗,后面的數值為短信內容的字節數+14。
如上面的編碼中內容為12個字節,則后面的數值為12+14=26
3. 發送短信編碼
<>
收到>符號,說明貓正在等待接收短信編碼
>0891683108401505F011000B815148063011F40008A70C59275BB6597DFF01FF01FF01
發送短信編碼,送送完畢后,如果短信貓已經接收,則啟動短信發送。
4.短信發送結果檢測
<+CMGS:45
<ok< />
在短信發送成功后,會返回“+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<strlen(SMS_Context);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<strlen(SMS_Context);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<strlen(temp);i++) if(temp[i]==0x0d) temp[i]=0;
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<len;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;
}