首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

透過序列埠收發簡訊(上):PDU 模式深度解析與三種編碼演算法

     編輯:關於VC++

利用序列埠連接 GSM 手機收發簡訊,核心在於理解 ETSI 制定的 SMS 規範:GSM 03.38(字元集)、GSM 03.40(PDU 結構與編碼)、GSM 07.05(AT 指令集)。三種傳輸模式中,PDU Mode 為所有手機支援、預設模式,可使用任意字元集,適合中文環境與自訂資料傳輸。本文深入剖析 PDU 結構、三種編碼演算法(7-bit、8-bit、UCS2)與字串/位元組互轉工具。

PDU 結構實例解析

PDU 表面為 ASCII 碼(0-9, A-F),實為 8 位元組十六進制或 BCD 碼。包含 SMSC 號碼、目標號碼、編碼方式、時間戳記等元資料。發送與接收 PDU 結構略有不同。

實例 1:發送 PDU

SMSC: +8613800250500,目標: 13851872468,內容: Hello!

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
欄位含義說明
08SMSC 地址長度含 91 共 8 位元組
91TON/NPI國際格式(+ 號)
68 31 08 20 05 05 F0SMSC 號碼8613800250500,補 F 湊偶數
11TP-MTI/VPF發送,TP-VP 相對格式
00TP-MR訊息參考值 0
0D目標地址數字數13 位十進制數字
91目標 TON/NPI國際格式
68 31 58 81 27 64 F8TP-DA8613851872468,補 F
00TP-PID普通 GSM 點對點
00TP-DCS7-bit 編碼
00TP-VP有效期 5 分鐘
06TP-UDL使用者資料長度 6 字元
C8 32 9B FD 0E 01TP-UD"Hello!" 7-bit 編碼結果

實例 2:接收 PDU

SMSC: +8613800250500,來源: 13851872468,內容: 你好! (UCS2)

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
欄位含義說明
84TP-MTI/MMS/RP接收,無更多訊息,含回覆地址
08TP-DCSUCS2 編碼
30 30 21 80 63 54 80TP-SCTS2003-03-12 08:36:45 +8 時區
06TP-UDL使用者資料長度 6 位元組
4F 60 59 7D 00 21TP-UD"你好!" UCS2 編碼

關鍵觀察:號碼與時間戳記皆採「兩兩顛倒」BCD 格式,奇數長度補 'F'。

三種編碼方式詳細演算法

1. 7-bit 編碼(GSM 03.38)

適用 ASCII 字元,將 7-bit 字元壓縮裝入 8-bit 位元組,每 8 字元壓縮為 7 位元組。TP-UDL 為編碼前字元數。最大容量 160 字元。

7-bit 編碼過程圖解

C 實作:

// 7-bit 編碼
int gsmEncode7bit(const char* pSrc, unsigned char* pDst, int nSrcLength) {
    int nSrc = 0, nDst = 0, nChar; unsigned char nLeft = 0;
    while (nSrc < nSrcLength) {
        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 解碼
int gsmDecode7bit(const unsigned char* pSrc, char* pDst, int nSrcLength) {
    int nSrc = 0, nDst = 0, nByte = 0; unsigned char nLeft = 0;
    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;
}

注意:GSM 7-bit 字元集與 ANSI 不完全相同,0x20 以下含可列印字元,但英數字與常用符號位置相同。法德西等特殊字元需查表(參閱 GSM 03.38)。

2. 8-bit 編碼

無特定演算法,位元組直接對應,用於二進位資料(鈴聲、圖片)。最大容量 140 位元組。

3. UCS2 編碼(Unicode)

每字元轉為 16-bit Unicode(ISO/IEC 10646)。Windows 下可呼叫 API 簡化實作。最大容量 70 字元(含中文)。

// UCS2 編碼 (Windows API)
int gsmEncodeUcs2(const char* pSrc, unsigned char* pDst, int nSrcLength) {
    WCHAR wchar[128];
    int 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 解碼
int gsmDecodeUcs2(const unsigned char* pSrc, char* pDst, int nSrcLength) {
    WCHAR wchar[128];
    for (int i = 0; i < nSrcLength/2; i++) {
        wchar[i] = *pSrc++ << 8;
        wchar[i] |= *pSrc++;
    }
    return WideCharToMultiByte(CP_ACP, 0, wchar, nSrcLength/2, pDst, 160, NULL, NULL);
}

可列印字串與位元組陣列互轉工具

PDU 字串為十六進制 ASCII,需與位元組陣列互轉。避免 sscanf/sprintf 以利嵌入式系統移植:

// "C8329BFD0E01" → {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01}
int gsmString2Bytes(const char* pSrc, unsigned char* pDst, int nSrcLength) {
    for (int i = 0; i < nSrcLength; i++) {
        if (*pSrc >= '0' && *pSrc <= '9') *pDst = (*pSrc - '0') << 4;
        else *pDst = (*pSrc - 'A' + 10) << 4;
        pSrc++;
        if (*pSrc >= '0' && *pSrc <= '9') *pDst |= *pSrc - '0';
        else *pDst |= *pSrc - 'A' + 10;
        pSrc++; pDst++;
    }
    return nSrcLength / 2;
}

// {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} → "C8329BFD0E01"
int gsmBytes2String(const unsigned char* pSrc, char* pDst, int nSrcLength) {
    const char tab[] = "0123456789ABCDEF";
    for (int i = 0; i < nSrcLength; i++) {
        *pDst++ = tab[*pSrc >> 4];
        *pDst++ = tab[*pSrc & 0x0F];
        pSrc++;
    }
    *pDst = '\0'; return nSrcLength * 2;
}

GSM 03.42 壓縮演鮮少見實際應用,此不贅述。下篇將介紹完整 PDU 封裝、AT 指令流程與序列埠通訊實作。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved