程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 深入淺出VC++串口編程之短信應用開發

深入淺出VC++串口編程之短信應用開發

編輯:關於VC++

前面數次連載我們以較長的篇幅講解了串口通信的硬件原理、DOS平台控制以及基於WIN32 API、控件和第三方類的串口編程。作為本系列文章的最後一次連載,本章將給出一個典型的 應用實例:西門子短信服務模塊TC35的串口控制。

1.短信控制終端

作為短信 (Short Message Service,SMS)一族,想必你有這樣的體會:用手機編輯短信息十分不便、 容易出錯,而且修改費時,若能用計算機來收發短信則方便許多。注意,本文所說的用計算 機收發短信並不是說通過"網易短信王"等方式在Internet上收發短信,而是直接 用計算機控制運行了GSM通信系統的短信終端進行收發,因而其收發短信的原理與手機是本質 相同的。

實際上,一大堆的垃圾短信也是采用這種短信終端發出來的!

我們 來介紹一款GSM模塊,它就是西門子公司的TC35,它由GSM基帶處理器、電源專用集成電路、 射頻電路和閃速存儲器等部分組成,負責處理GSM蜂窩設備中的音頻、數據和信號,內嵌的軟 件部分執行應用接口和所有GSM協議棧的功能。TC35支持中文短信息,工作在EGSM900和 GSM1800雙頻段,電源范圍為3.3~5.5V,可傳輸語音和數據信號,消耗功率在EGSM900(4類)和 GSM1800(1類)分別為2W和1W,通過接口連接器和天線連接器分別連接SIM卡讀卡器和天線。 TC35的數據接口(CMOS電平)通過AT命令可雙向傳輸指令和數據,可選波特率為 300bit/s~115kbit/s,自動波特率為1.2k~115kbit/s。它支持文本和PDU格式的,可通過AT命 令或關斷信號實現重啟和故障恢復。

我們需要利用以TC35模塊為主的硬件組成一個 TC35終端設備,並與電腦通過RS-232C串口相連,並自行編制在PC上運行的短信息收發軟件, 就可以組成一個短信收發系統。TC35終端電路如下圖所示:

TC35的控制主要 包含如下幾類指令:

(1)初始化指令

設置短消息發送格式 AT+CMGF=1<CR>,設置1代表PDU模式,<CR>是回車符號,也就是0x0d,指令正確 則模塊返回<CRLF>OK<CRLF>,<CRLF>是回車換行符號。

(2)設 置/讀取短消息中心

短消息中心號碼由移動運營商提供。

設置短消息中心的指 令格式為:

AT+CSCA=″+8613800531500″(短消息中心) <CR>

設置正確則模塊返回<CRLF>OK<CRLF>。

讀取短消息 服務中心則使用命令:

AT+CSCA=?<CR>

TC35模塊應該返回:

<CRLF>+CSCA:″8613800531500″<CRLF>。

(3)設 置短消息到達自動提示

設置短消息到達自動提示的指令格式為:

AT+CNMI=1,1,0,0,1<CR>

設置正確則TC35模塊返回:

<CRLF>OK<CRLF>。

設置此命令可使模塊在短消息到達後向串口 發送指令:

<CRLF>+CMTI:″SM″,INDEX(信息存儲位置) <CRLF>。

通過TC35發送短消息的方法為:

PC上的控制軟件按照PDU的格 式發送和接收數據,短消息的內容可以是中文或者其他字符。在PDU模式,如果發送短消息, 則首先發送短消息數據的長度:

AT+CMGS=<length><CR>

等待 TC35模塊返回ASCII字符">",則可以將PDU數據輸入,PDU數據以<Z>(也 就是0x1a)作為結束符。短消息發送成功,模塊返回:

<CRLF>OK<CRLF>

通過TC35接收短消息的方法為:

短消息 到來後,串口上會接收到指令

<CRLF>+CMTI:″SM″,INDEX(信息存 儲位置)<CRLF>

PC上的控制軟件通過讀取PDU數據的AT命令

AT+CMGR=INDEX<CRLF>

將TC35模塊中PDU格式的短消息內容讀出。如果 用+CMGL代替+CMGR,則可一次性讀出全部短消息。

通過TC35刪除短消息的方法為:

PC上的控制軟件收到一條短消息並處理後,需要將其在SIM卡上刪除,以防止SIM卡飽 和。刪除短消息的指令為:

AT+CMGD=INDEX<CR>

刪除後模塊返回

<CRLF>OK<CRLF>

2.程序實例

由於本文的宗旨在於講解串 口通信,因此,我們屏蔽圖形用戶界面的細節,制作一個簡單的短信收發軟件,它包含了控 制短信終端的所有串口通信內容。實際上,一個理想的短信收發軟件的界面應類似於Outlook 或Foxmail,包含收件箱、發件箱、已發送短信箱等內容,但是這些東西都與我們要介紹的串 口通信無關,因此,下面的軟件界面雖"敗絮其外",但仍可稱得上"金玉其 中":

關於界面上控件的描述如下:

BEGIN
 EDITTEXT IDC_SMSCONTENT_EDIT,39,61,242,38,ES_AUTOHSCROLL
 PUSHBUTTON "發送 ",IDC_SEND_BUTTON,316,80,45,18
 GROUPBOX "接收短消息 ",IDC_STATIC,28,124,361,167
 LTEXT "對方手機號 ",IDC_STATIC,41,35,42,11
 EDITTEXT IDC_PHONENUM_EDIT,88,30,192,17,ES_AUTOHSCROLL
 PUSHBUTTON "清除 ",IDC_CLEAR_BUTTON,316,30,45,18
 GROUPBOX "發送短消息 ",IDC_STATIC,29,19,361,95
 LISTBOX IDC_RECVSMS_LIST,43,137,331,127,LBS_SORT |
 LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
 PUSHBUTTON "接收 ",IDC_RECV_BUTTON,77,269,55,16
 PUSHBUTTON "清空 ",IDC_DELETEALL_BUTTON,273,268,45,14
END

對話框類的消息映 射為:

BEGIN_MESSAGE_MAP(CSMSControlDlg, CDialog)
//{{AFX_MSG_MAP (CSMSControlDlg)
 ON_WM_SYSCOMMAND()
 ON_WM_PAINT()
  ON_WM_QUERYDRAGICON()
 ON_BN_CLICKED(IDC_CLEAR_BUTTON, OnClearButton)
 ON_BN_CLICKED(IDC_SEND_BUTTON, OnSendButton)
 ON_BN_CLICKED (IDC_RECV_BUTTON, OnRecvButton)
 ON_BN_CLICKED(IDC_DELETEALL_BUTTON, OnDeleteallButton)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

感謝 《通過串口收發短消息》一文的作者bhw98,他為我們編寫了數個獨立於操作系統平台的C函 數,使得我們可以在應用程序中直接對這些函數進行調用。在本控制軟件中,也對這些函數 進行了充分利用。

下面是對本例程軟件的主要數據結構和核心函數的介紹:

數據結構

// 用戶信息編碼方式
#define GSM_7BIT 0
#define GSM_8BIT 4
#define GSM_UCS2 8
// 短消息參數結構,編碼/解碼共用
// 其中,字符串以0結尾
typedef struct
{
 char SCA[16]; // 短消息服務 中心號碼(SMSC地址)
 char TPA[16]; // 目標號碼或回復號碼(TP-DA或TP-RA)
 char TP_PID; // 用戶信息協議標識(TP-PID)
 char TP_DCS; // 用戶信息編碼方 式(TP-DCS)
 char TP_SCTS[16]; // 服務時間戳字符串(TP_SCTS), 接收時用到
 char TP_UD[161]; // 原始用戶信息(編碼前或解碼後的TP-UD)
 char index; // 短消息序號,在讀取時用到
} SM_PARAM;

發送短消息

發送按鈕 對應的函數為CSMSControlDlg::OnSendButton,它讀取用戶輸出並根據目標電話號碼和短信 息內容形成SM_PARAM(源PDU參數)的內容,接著進行發送:

void CSMSControlDlg::OnSendButton()
{
 // TODO: Add your control notification handler code here
 //獲得用戶輸入
 CString desPhoneNum;
 CString smsContent;
 GetDlgItemText (IDC_PHONENUM_EDIT,desPhoneNum);
 GetDlgItemText (IDC_SMSCONTENT_EDIT,smsContent);
 //填充SM_PARAM結構體內容
 SM_PARAM smParam;
 smParam = CreateSMPARAMStruct(desPhoneNum,smsContent);
 // 發送短信息
 gsmSendMessage(smParam);
}

其中調用的 gsmSendMessage函數體現了串口通信的核心內容,它按照第1節闡述的GSM模塊發送短消息的 串口控制流程進行短信的發送:

BOOL gsmSendMessage(const SM_PARAM *pSrc // pSrc: 源PDU參數指針)
{
 int nPduLength; // PDU串長度
 unsigned char nSmscLength; // SMSC串長度
 int nLength; // 串口收到的數據長度
 char cmd[16]; // 命令串
 char pdu[512]; // PDU串
 char ans[128]; // 應答串
 nPduLength = gsmEncodePdu(pSrc, pdu); // 根據PDU參數,編碼PDU串
 strcat(pdu, "\x01a"); // 以Ctrl-Z結束
 gsmString2Bytes(pdu, &nSmscLength, 2); // 取PDU串中的SMSC信息長度
 nSmscLength++; // 加上長度字節本身
 // 命令中的長度,不包括SMSC信息長度,以數據字節計
 sprintf(cmd, "AT+CMGS=%d\r", nPduLength / 2-nSmscLength); // 生成命令
 WriteComm(cmd, strlen(cmd)); // 先輸出命令串
 nLength = ReadComm(ans, 128); // 讀應答數據
 // 根據能否找到"\r\n> "決定成功與否
 if (nLength == 4 && strncmp(ans, "\r\n> ", 4) == 0)
 {
  WriteComm(pdu, strlen(pdu)); // 得到肯定回答,繼續輸出PDU串
  nLength = ReadComm(ans, 128); // 讀應答數據
  // 根據能否找到"+CMS ERROR"決定成功與否
  if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
  {
   return TRUE;
  }
 }
 return FALSE;
}

讀取短消息

點擊"接收"按鈕會通過gsmReadMessage函 數的調用獲得所有短消息,最後在列表控件中顯示所有短信:

void CSMSControlDlg::OnRecvButton()
{
 // TODO: Add your control notification handler code here
 SM_PARAM smParam[100];//短信緩沖區
  int smsNum;//短信條數
 smsNum = gsmReadMessage(smParam);//讀取短信
 //顯示短信
 for(int i=0;i<smsNum;i++)
 {
   m_recvlist.AddString(CString(smsNum[i].TPA)+smsNum[i].TP_UD);
 }
}

其中調用的gsmReadMessage函數完成最核心的短信接收功能,它按照第1節闡 述的GSM模塊接收短消息的串口控制流程進行短信的接收:

// 參數:pMsg 短消息 緩沖區,必須足夠大
// 返回:短消息條數
int gsmReadMessage(SM_PARAM* pMsg)
{
 int nLength; // 串口收到的數據長度
 int nMsg; // 短消息 計數值
 char* ptr; // 內部用的數據指針
 char cmd[16]; // 命令串
 char ans[1024]; // 應答串
 nMsg = 0;
 ptr = ans;
 sprintf (cmd, "AT+CMGL\r"); // 生成命令,用+CMGL可一次性讀出全部短消息

 WriteComm(cmd, strlen(cmd)); // 輸出命令串
 nLength = ReadComm (ans, 1024); // 讀應答數據
 // 根據能否找到"+CMS ERROR"決定成功與 否
 if(nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
 {
  // 循環讀取每一條短消息, 以"+CMGL:"開頭
   while((ptr = strstr(ptr, "+CMGL:")) != NULL)
  {
    ptr += 6; // 跳過"+CMGL:"
   sscanf(ptr, "%d", &pMsg->index); // 讀取序號

   ptr = strstr(ptr, "\r\n"); // 找下一行
   ptr += 2; // 跳過"\r\n"
    gsmDecodePdu(ptr, pMsg); // PDU串解碼
   pMsg++; // 准備讀下一條短消息
   nMsg++; // 短消息計數加1
  }
 }
  return nMsg;
}

刪除短消息

我們可以在讀取完所有短信息後調用 gsmDeleteMessage函數在GSM模塊上刪除那些已經被接收到PC上的短信息,它按照第1節闡述 的GSM模塊刪除短消息的串口控制流程進行短信的刪除:

// index: 短消息序號, 從1開始
BOOL gsmDeleteMessage(const int index)
{
 int nLength; // 串口收到的數據長度
 char cmd[16]; // 命令串
 char ans[128]; // 應答串
 sprintf(cmd, "AT+CMGD=%d\r", index); // 生成命令
 // 輸出命令串
 WriteComm(cmd, strlen(cmd));
 // 讀應答數據
  nLength = ReadComm(ans, 128);
 // 根據能否找到"+CMS ERROR"決定成 功與否
 if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
 {
  return TRUE;
 }
 return FALSE;
}

在PC控制軟件的短信列表框中刪除所有短消息的"清空"按鈕函數 為:

void CSMSControlDlg::OnDeleteallButton()
{
 // TODO: Add your control notification handler code here
 m_recvlist.ResetContent();
}

設置/讀/寫串口

在應用程序啟動與退出及gsmSendMessage、 gsmReadMessage和gsmDeleteMessage函數中廣泛使用的串口相關函數用WIN32 API實現:

// 串口設備句柄
HANDLE hComm;
// 打開串口
// pPort: 串口 名稱或設備路徑,可用"COM1"或"\\.\COM1"兩種方式,建議用後者
// nBaudRate: 波特率
// nParity: 奇偶校驗
// nByteSize: 數據字節寬度
// nStopBits: 停止位
BOOL OpenComm(const char *pPort, int nBaudRate, int nParity, int nByteSize, int
nStopBits)
{
 DCB dcb; // 串口控 制塊
 COMMTIMEOUTS timeouts =
 {
  // 串口超時控制參數
   100, // 讀字符間隔超時

編/解碼GSM短消息

陷於本文的篇幅,這裡只給出 編解碼函數的原型,具體請參看GSM標准及《通過串口收發短消息》一文。

// UCS2編碼 返回: 目標編碼串長度
int gsmEncodeUcs2(const char *pSrc, // 源字符 串指針
 unsigned char *pDst, // pDst: 目標編碼串指針
 int nSrcLength // nSrcLength: 源字符串長度
);
// UCS2解碼 返回: 目標字符串長度
int gsmDecodeUcs2(const unsigned char *pSrc, //源編碼串指針
char *pDst, // pDst: 目標字符串指針
int nSrcLength // nSrcLength: 源編碼串長度
);
//可打印字符串轉換為字節數據 返回: 目標數據長度
//如: "C8329BFD0E01" --> {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01}
int gsmString2Bytes(const char *pSrc, // pSrc: 源字符串指針
unsigned char *pDst, // pDst: 目標數據指針
int nSrcLength // nSrcLength: 源字符串長度
);
// 字節數據轉換為可打印字符串 返回: 目標字符串長度
// 如:{0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} --> "C8329BFD0E01"
int gsmBytes2String (const unsigned char *pSrc, // pSrc: 源數據指針
char *pDst, // pDst: 目標字 符串指針
int nSrcLength // nSrcLength: 源數據長度
);

3.總 結

串口編程的核心在於串口通信方式(發送、接收和握手)的控制,而具體的應用領 域反而是次要的。掌握了根本的原理,就可以靈活地將其應用於任意領域,綜合實例中的例 子"短信控制終端"只是冰山一角。

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