通過串口收發短消息(上)

admin @ 2014-03-26 , reply:0

Q 用串口連接GSM手機發送和接收短消息,在應用程序中如何編程實現?

 

Q 我們打算開發一個基於GSM短消息方式的GPS系統,如何利用SMS進行數據通信?

A 首先,我們要對由ESTI制訂的SMS規範有所了解。與我們討論的短消息收發有關的規範主要包括GSM 03.38、GSM 03.40和GSM 07.05。前二者著重描述SMS的技術實現(含編碼方式),後者則規定了SMS的DTE-DCE介面標準(AT命令集)。
一共有三種方式來發送和接收SMS信息:Block Mode, Text Mode和PDU Mode。Block Mode已是昔日黃花,目前很少用了。Text Mode是純文本方式,可使用不同的字符集,從技術上說也可用於發送中文短消息,但國內手機基本上不支持,主要用於歐美地區。PDU Mode被所有手機支持,可以使用任何字符集,這也是手機默認的編碼方式。Text Mode比較簡單,而且不適合做自定義數據傳輸,我們就不討論了。下面介紹的內容,是在PDU Mode下發送和接收短消息的實現方法。
PDU串表面上是一串ASCII碼,由‘0’-‘9’、 ‘A’-‘F’這些數字和字母組成。它們是8位位元組的十六進位數,或者BCD碼十進位數。PDU串不僅包含可顯示的消息本身,還包含很多其它信息,如 SMS服務中心號碼、目標號碼、回復號碼、編碼方式和服務時間等。發送和接收的PDU串,結構是不完全相同的。我們先用兩個實際的例子說明PDU串的結構和編排方式。

例1 發送:SMSC號碼是+8613800250500,對方號碼是13851872468,消息內容是“Hello!”。從手機發出的PDU串可以是
08 91 68 31 08 20 05 05 F0 11 00 0D 91 68 31 58 81 27 64 F8 00 00 00 06 C8 32 9B FD 0E 01
對照規範,具體分析:

分段 含義 說明
08 SMSC地址信息的長度 共8個八位位元組(包括91)
91 SMSC地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,補‘F’湊成偶數個
11 基本參數(TP-MTI/VFP) 發送,TP-VP用相對格式
00 消息基準值(TP-MR) 0
0D 目標地址數字個數 共13個十進位數(不包括91和‘F’)
91 目標地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 58 81 27 64 F8 目標地址(TP-DA) 8613851872468,補‘F’湊成偶數個
00 協議標識(TP-PID) 是普通GSM類型,點到點方式
00 用戶信息編碼方式(TP-DCS) 7-bit編碼
00 有效期(TP-VP) 5分鐘
06 用戶信息長度(TP-UDL) 實際長度6個位元組
C8 32 9B FD 0E 01 用戶信息(TP-UD) “Hello!”

例2 接收:SMSC號碼是+8613800250500,對方號碼是13851872468,消息內容是“你好!”。手機接收到的PDU串可以是
08 91 68 31 08 20 05 05 F0 84 0D 91 68 31 58 81 27 64 F8 00 08 30 30 21 80 63 54 80 06 4F 60 59 7D 00 21
對照規範,具體分析:

分段 含義 說明
08 地址信息的長度 個八位位元組(包括91)
91 SMSC地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,補‘F’湊成偶數個
84 基本參數(TP-MTI/MMS/RP) 接收,無更多消息,有回復地址
0D 回復地址數字個數 共13個十進位數(不包括91和‘F’)
91 回復地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 58 81 27 64 F8 回復地址(TP-RA) 8613851872468,補‘F’湊成偶數個
00 協議標識(TP-PID) 是普通GSM類型,點到點方式
08 用戶信息編碼方式(TP-DCS) UCS2編碼
30 30 21 80 63 54 80 時間戳(TP-SCTS) 2003-3-12 08:36:45  +8時區
06 用戶信息長度(TP-UDL) 實際長度6個位元組
4F 60 59 7D 00 21 用戶信息(TP-UD) “你好!”

若基本參數的最高位(TP-RP)為0,則沒有回復地址的三個段。從Internet上發出的短消息常常是這種情形。
注意號碼和時間的表示方法,不是按正常順序順著來的,而且要以‘F’將奇數補成偶數。


Q 上面兩例中已經出現了7-bit和UCS2編碼,請詳細介紹一下這些編碼方式?

A 在PDU Mode中,可以採用三種編碼方式來對發送的內容進行編碼,它們是7-bit、8-bit和UCS2編碼。7-bit編碼用於發送普通的ASCII字元,它將一串7-bit的字元(最高位為0)編碼成8-bit的數據,每8個字元可“壓縮”成7個;8-bit編碼通常用於發送數據消息,比如圖片和鈴聲等;而UCS2編碼用於發送Unicode字元。PDU串的用戶信息(TP-UD)段最大容量是140位元組,所以在這三種編碼方式下,可以發送的短消息的最大字元數分別是160、140和70。這裡,將一個英文字母、一個漢字和一個數據位元組都視為一個字元。
需要注意的是,PDU串的用戶信息長度(TP -UDL),在各種編碼方式下意義有所不同。7-bit編碼時,指原始短消息的字元個數,而不是編碼后的位元組數。8-bit編碼時,就是位元組數。UCS2 編碼時,也是位元組數,等於原始短消息的字元數的兩倍。如果用戶信息(TP-UD)中存在一個頭(基本參數的TP-UDHI為1),在所有編碼方式下,用戶信息長度(TP-UDL)都等於頭長度與編碼后位元組數之和。如果採用GSM 03.42所建議的壓縮演算法(TP-DCS的高3位為001),則該長度也是壓縮編碼后位元組數或頭長度與壓縮編碼后位元組數之和。

下面以一個具體的例子說明7-bit編碼的過程。我們對英文簡訊“Hello!”進行編碼:

將源串每8個字元分為一組(這個例子中不滿8個)進行編碼,在組內字元間壓縮,但每組之間是沒有什麼聯繫的。

用C實現7-bit編碼和解碼的演算法如下:

// 7-bit編碼    // pSrc: 源字元串指針    // pDst: 目標編碼串指針    // nSrcLength: 源字元串長度    // 返回: 目標編碼串長度    int gsmEncode7bit(const char* pSrc, unsigned char* pDst, int nSrcLength)    {        int nSrc;        // 源字元串的計數值        int nDst;        // 目標編碼串的計數值        int nChar;       // 當前正在處理的組內字元位元組的序號,範圍是0-7        unsigned char nLeft;    // 上一位元組殘餘的數據                // 計數值初始化        nSrc = 0;        nDst = 0;                // 將源串每8個位元組分為一組,壓縮成7個位元組        // 循環該處理過程,直至源串被處理完        // 如果分組不到8位元組,也能正確處理        while(nSrc<nSrcLength)        {            // 取源字元串的計數值的最低3位            nChar = nSrc & 7;                    // 處理源串的每個位元組            if(nChar == 0)            {                // 組內第一個位元組,只是保存起來,待處理下一個位元組時使用                nLeft = *pSrc;            }            else            {                // 組內其它位元組,將其右邊部分與殘餘數據相加,得到一個目標編碼位元組                *pDst = (*pSrc << (8-nChar)) | nLeft;                        // 將該位元組剩下的左邊部分,作為殘餘數據保存起來                nLeft = *pSrc >> nChar;                // 修改目標串的指針和計數值 pDst++;                nDst++;             }                         // 修改源串的指針和計數值            pSrc++; nSrc++;        }                // 返回目標串長度        return nDst;     }            // 7-bit解碼    // pSrc: 源編碼串指針    // pDst: 目標字元串指針    // nSrcLength: 源編碼串長度    // 返回: 目標字元串長度    int gsmDecode7bit(const unsigned char* pSrc, char* pDst, int nSrcLength)    {        int nSrc;        // 源字元串的計數值        int nDst;        // 目標解碼串的計數值        int nByte;       // 當前正在處理的組內位元組的序號,範圍是0-6        unsigned char nLeft;    // 上一位元組殘餘的數據                // 計數值初始化        nSrc = 0;        nDst = 0;                // 組內位元組序號和殘餘數據初始化        nByte = 0;        nLeft = 0;                // 將源數據每7個位元組分為一組,解壓縮成8個位元組        // 循環該處理過程,直至源數據被處理完        // 如果分組不到7位元組,也能正確處理        while(nSrc<nSrcLength)        {            // 將源位元組右邊部分與殘餘數據相加,去掉最高位,得到一個目標解碼位元組            *pDst = ((*pSrc << nByte) | nLeft) & 0x7f;            // 將該位元組剩下的左邊部分,作為殘餘數據保存起來            nLeft = *pSrc >> (7-nByte);                    // 修改目標串的指針和計數值            pDst++;            nDst++;                    // 修改位元組計數值            nByte++;                    // 到了一組的最後一個位元組            if(nByte == 7)            {                // 額外得到一個目標解碼位元組                *pDst = nLeft;                        // 修改目標串的指針和計數值                pDst++;                nDst++;                        // 組內位元組序號和殘餘數據初始化                nByte = 0;                nLeft = 0;            }                    // 修改源串的指針和計數值            pSrc++;            nSrc++;        }                *pDst = 0;                // 返回目標串長度        return nDst;    }    

需要指出的是,7-bit的字符集與ANSI標準字符集不完全一致,在0x20以下也排布了一些可列印字元,但英文字母、阿拉伯數字和常用符號的位置兩者是一樣的。用上面介紹的演算法收發純英文短消息,一般情況應該是夠用了。如果是法語、德語、西班牙語等,含有 “å”、 “é”這一類字元,則要按上面編碼的輸出去查表,請參閱GSM 03.38的規定。

8-bit編碼其實沒有規定什麼具體的演算法,不需要介紹。

UCS2編碼是將每個字元(1-2個位元組)按照ISO/IEC10646的規定,轉變為16位的Unicode寬字元。在Windows系統中,特別是在2000/XP中,可以簡單地調用API 函數實現編碼和解碼。如果沒有系統的支持,比如用單片機控制手機模塊收發短消息,只好用查表法解決了。

Windows環境下,用C實現UCS2編碼和解碼的演算法如下:

// UCS2編碼    // pSrc: 源字元串指針    // pDst: 目標編碼串指針    // nSrcLength: 源字元串長度    // 返回: 目標編碼串長度    int gsmEncodeUcs2(const char* pSrc, unsigned char* pDst, int nSrcLength)    {        int nDstLength;        // UNICODE寬字元數目        WCHAR wchar[128];      // UNICODE串緩衝區                // 字元串-->UNICODE串        nDstLength = ::MultiByteToWideChar(CP_ACP, 0, pSrc, nSrcLength, wchar, 128);                // 高低位元組對調,輸出        for(int i=0; i<nDstLength; i++)        {            // 先輸出高位位元組            *pDst++ = wchar[i] >> 8;            // 后輸出低位位元組            *pDst++ = wchar[i] & 0xff;        }                // 返回目標編碼串長度        return nDstLength * 2;    }            // UCS2解碼    // pSrc: 源編碼串指針    // pDst: 目標字元串指針    // nSrcLength: 源編碼串長度    // 返回: 目標字元串長度    int gsmDecodeUcs2(const unsigned char* pSrc, char* pDst, int nSrcLength)    {        int nDstLength;        // UNICODE寬字元數目        WCHAR wchar[128];      // UNICODE串緩衝區                // 高低位元組對調,拼成UNICODE        for(int i=0; i<nSrcLength/2; i++)        {            // 先高位位元組            wchar[i] = *pSrc++ << 8;                    // 后低位位元組            wchar[i] |= *pSrc++;        }                // UNICODE串-->字元串        nDstLength = ::WideCharToMultiByte(CP_ACP, 0, wchar, nSrcLength/2, pDst, 160, NULL, NULL);                // 輸出字元串加個結束符            pDst[nDstLength] = '\0';                    // 返回目標字元串長度        return nDstLength;    }    

用以上編碼和解碼模塊,還不能將短消息字元串編碼為PDU串需要的格式,也不能直接將PDU串中的用戶信息解碼為短消息字元串,因為還差一個在可列印字元串和位元組數據之間相互轉換的環節。可以循環調用sscanf和sprintf函數實現這種變換。下面提供不用這些函數的演算法,它們也適用於單片機、 DSP編程環境。

// 可列印字元串轉換為位元組數據    // 如:"C8329BFD0E01" --> {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01}    // pSrc: 源字元串指針    // pDst: 目標數據指針    // nSrcLength: 源字元串長度    // 返回: 目標數據長度    int gsmString2Bytes(const char* pSrc, unsigned char* pDst, int nSrcLength)    {        for(int i=0; i<nSrcLength; i+=2)        {            // 輸出高4位            if(*pSrc>='0' && *pSrc<='9')            {                *pDst = (*pSrc - '0') << 4;            }            else            {                *pDst = (*pSrc - 'A' + 10) << 4;            }                    pSrc++;                    // 輸出低4位            if(*pSrc>='0' && *pSrc<='9')            {                *pDst |= *pSrc - '0';            }            else            {                *pDst |= *pSrc - 'A' + 10;            }            pSrc++;            pDst++;        }                // 返回目標數據長度        returnnSrcLength / 2;    }            // 位元組數據轉換為可列印字元串    // 如:{0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} --> "C8329BFD0E01"     // pSrc: 源數據指針    // pDst: 目標字元串指針    // nSrcLength: 源數據長度    // 返回: 目標字元串長度    int gsmBytes2String(const unsigned char* pSrc, char* pDst, int nSrcLength)    {        const char tab[]="0123456789ABCDEF";    // 0x0-0xf的字元查找表                for(int i=0; i<nSrcLength; i++)        {            // 輸出低4位            *pDst++ = tab[*pSrc >> 4];                    // 輸出高4位            *pDst++ = tab[*pSrc & 0x0f];                    pSrc++;        }                // 輸出字元串加個結束符        *pDst = '\0';                // 返回目標字元串長度        return nSrcLength * 2;    }



[admin via 研發互助社區 ] 通過串口收發短消息(上)已經有2622次圍觀

http://www.cocdig.com/docs/show-post-45168.html