程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 通過串口收發短消息(上)

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

編輯:關於VC++

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

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

首先,我們要對由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-OA) 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) “你好!”

注意號碼和時間的表示方法,不是按正常順序來的,而且要以‘F''將奇數補成偶數。

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

在PDU Mode中,可以采用三種編碼方式來對發送的內容進行編碼,它們是7-bit、8-bit和UCS2編碼。7-bit編碼用於發送普通的ASCII字符,它將一串7-bit的字符(最高位為0)編碼成8-bit的數據,每8個字符可“壓縮”成7個;8-bit編碼通常用於發送數據消息,比如圖片和鈴聲等;而UCS2編碼用於發送Unicode字符。在這三種編碼方式下,PDU串的用戶信息(TP-UD)段最大容量(可以發送的短消息的最大字符數)分別是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);

  // 返回目標字符串長度
  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++)
  {
    // 輸出高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++;
  }

  // 返回目標數據長度
  return nSrcLength / 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;
}

關於GSM 03.42中的壓縮算法,至今還沒有發現哪裡用過,這裡我們就不討論了。有興趣的話,可深入研究一下。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved