VC++串列埠程式設計之簡訊應用開發(轉)
VC++串列埠程式設計之簡訊應用開發(轉)[@more@]前面數次連載我們以較長的篇幅講解了串列埠通訊的硬體原理、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,設定1代表PDU模式,是回車符號,也就是0x0d,指令正確則模組返回OK,是回車換行符號。
(2)設定/讀取短訊息中心
短訊息中心號碼由移動運營商提供。
設定短訊息中心的指令格式為:
AT+CSCA=″+8613800531500″(短訊息中心)
設定正確則模組返回OK。
讀取短訊息服務中心則使用命令:
AT+CSCA=?
TC35模組應該返回:
+CSCA:″8613800531500″。
(3)設定短訊息到達自動提示
設定短訊息到達自動提示的指令格式為:
AT+CNMI=1,1,0,0,1
設定正確則TC35模組返回:
OK。
設定此命令可使模組在短訊息到達後向串列埠傳送指令:
+CMTI:″SM″,INDEX(資訊儲存位置)。
透過TC35傳送短訊息的方法為:
PC上的控制軟體按照PDU的格式傳送和接收資料,短訊息的內容可以是中文或者其他字元。在PDU模式,如果傳送短訊息,則首先傳送短訊息資料的長度:
AT+CMGS=
等待TC35模組返回ASCII字元">",則可以將PDU資料輸入,PDU資料以(也就是0x1a)作為結束符。短訊息傳送成功,模組返回:
OK
透過TC35接收短訊息的方法為:
短訊息到來後,串列埠上會接收到指令
+CMTI:″SM″,INDEX(資訊儲存位置)
PC上的控制軟體透過讀取PDU資料的AT命令
AT+CMGR=INDEX
將TC35模組中PDU格式的短訊息內容讀出。如果用+CMGL代替+CMGR,則可一次性讀出全部短訊息。
透過TC35刪除短訊息的方法為:
PC上的控制軟體收到一條短訊息並處理後,需要將其在SIM卡上刪除,以防止SIM卡飽和。刪除短訊息的指令為:
AT+CMGD=INDEX
刪除後模組返回
OK
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 ", nPduLength / 2-nSmscLength); // 生成命令
WriteComm(cmd, strlen(cmd)); // 先輸出命令串
nLength = ReadComm(ans, 128); // 讀應答資料
// 根據能否找到" > "決定成功與否
if (nLength == 4 && strncmp(ans, " > ", 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
{
m_recvlist.AddString(CString(smsNum.TPA)+smsNum.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 "); // 生成命令,用+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, " "); // 找下一行
ptr += 2; // 跳過" "
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 ", 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, // 讀字元間隔超時時間: 100 ms
1, // 讀操作時每字元的時間: 1 ms (n個字元總共為n ms)
500, // 基本的(額外的)讀超時時間: 500 ms
1, // 寫操作時每字元的時間: 1 ms (n個字元總共為n ms)
100
}; // 基本的(額外的)寫超時時間: 100 ms
hComm = CreateFile(pPort, // 串列埠名稱或裝置路徑
GENERIC_READ | GENERIC_WRITE, // 讀寫方式
0, // 共享方式:獨佔
NULL, // 預設的安全描述符
OPEN_EXISTING, // 建立方式
0, // 不需設定檔案屬性
NULL); // 不需參照模板檔案
if (hComm == INVALID_HANDLE_VALUE)
return FALSE;
// 開啟串列埠失敗
GetCommState(hComm, &dcb); // 取DCB
dcb.BaudRate = nBaudRate;
dcb.ByteSize = nByteSize;
dcb.Parity = nParity;
dcb.StopBits = nStopBits;
SetCommState(hComm, &dcb); // 設定DCB
SetupComm(hComm, 4096, 1024); // 設定輸入輸出緩衝區大小
SetCommTimeouts(hComm, &timeouts); // 設定超時
return TRUE;
}
// 關閉串列埠
BOOL CloseComm()
{
return CloseHandle(hComm);
}
// 寫串列埠
// pData: 待寫的資料緩衝區指標
// nLength: 待寫的資料長度
void WriteComm(void *pData, int nLength)
{
DWORD dwNumWrite; // 串列埠發出的資料長度
WriteFile(hComm, pData, (DWORD)nLength, &dwNumWrite, NULL);
}
// 讀串列埠
// pData: 待讀的資料緩衝區指標
// nLength: 待讀的最大資料長度
// 返回: 實際讀入的資料長度
int ReadComm(void *pData, int nLength)
{
DWORD dwNumRead; // 串列埠收到的資料長度
ReadFile(hComm, pData, (DWORD)nLength, &dwNumRead, NULL);
return (int)dwNumRead;
}
編/解碼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.簡訊控制終端
作為簡訊 (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,設定1代表PDU模式,是回車符號,也就是0x0d,指令正確則模組返回OK,是回車換行符號。
(2)設定/讀取短訊息中心
短訊息中心號碼由移動運營商提供。
設定短訊息中心的指令格式為:
AT+CSCA=″+8613800531500″(短訊息中心)
設定正確則模組返回OK。
讀取短訊息服務中心則使用命令:
AT+CSCA=?
TC35模組應該返回:
+CSCA:″8613800531500″。
(3)設定短訊息到達自動提示
設定短訊息到達自動提示的指令格式為:
AT+CNMI=1,1,0,0,1
設定正確則TC35模組返回:
OK。
設定此命令可使模組在短訊息到達後向串列埠傳送指令:
+CMTI:″SM″,INDEX(資訊儲存位置)。
透過TC35傳送短訊息的方法為:
PC上的控制軟體按照PDU的格式傳送和接收資料,短訊息的內容可以是中文或者其他字元。在PDU模式,如果傳送短訊息,則首先傳送短訊息資料的長度:
AT+CMGS=
等待TC35模組返回ASCII字元">",則可以將PDU資料輸入,PDU資料以(也就是0x1a)作為結束符。短訊息傳送成功,模組返回:
OK
透過TC35接收短訊息的方法為:
短訊息到來後,串列埠上會接收到指令
+CMTI:″SM″,INDEX(資訊儲存位置)
PC上的控制軟體透過讀取PDU資料的AT命令
AT+CMGR=INDEX
將TC35模組中PDU格式的短訊息內容讀出。如果用+CMGL代替+CMGR,則可一次性讀出全部短訊息。
透過TC35刪除短訊息的方法為:
PC上的控制軟體收到一條短訊息並處理後,需要將其在SIM卡上刪除,以防止SIM卡飽和。刪除短訊息的指令為:
AT+CMGD=INDEX
刪除後模組返回
OK
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 ", nPduLength / 2-nSmscLength); // 生成命令
WriteComm(cmd, strlen(cmd)); // 先輸出命令串
nLength = ReadComm(ans, 128); // 讀應答資料
// 根據能否找到" > "決定成功與否
if (nLength == 4 && strncmp(ans, " > ", 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
{
m_recvlist.AddString(CString(smsNum.TPA)+smsNum.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 "); // 生成命令,用+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, " "); // 找下一行
ptr += 2; // 跳過" "
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 ", 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, // 讀字元間隔超時時間: 100 ms
1, // 讀操作時每字元的時間: 1 ms (n個字元總共為n ms)
500, // 基本的(額外的)讀超時時間: 500 ms
1, // 寫操作時每字元的時間: 1 ms (n個字元總共為n ms)
100
}; // 基本的(額外的)寫超時時間: 100 ms
hComm = CreateFile(pPort, // 串列埠名稱或裝置路徑
GENERIC_READ | GENERIC_WRITE, // 讀寫方式
0, // 共享方式:獨佔
NULL, // 預設的安全描述符
OPEN_EXISTING, // 建立方式
0, // 不需設定檔案屬性
NULL); // 不需參照模板檔案
if (hComm == INVALID_HANDLE_VALUE)
return FALSE;
// 開啟串列埠失敗
GetCommState(hComm, &dcb); // 取DCB
dcb.BaudRate = nBaudRate;
dcb.ByteSize = nByteSize;
dcb.Parity = nParity;
dcb.StopBits = nStopBits;
SetCommState(hComm, &dcb); // 設定DCB
SetupComm(hComm, 4096, 1024); // 設定輸入輸出緩衝區大小
SetCommTimeouts(hComm, &timeouts); // 設定超時
return TRUE;
}
// 關閉串列埠
BOOL CloseComm()
{
return CloseHandle(hComm);
}
// 寫串列埠
// pData: 待寫的資料緩衝區指標
// nLength: 待寫的資料長度
void WriteComm(void *pData, int nLength)
{
DWORD dwNumWrite; // 串列埠發出的資料長度
WriteFile(hComm, pData, (DWORD)nLength, &dwNumWrite, NULL);
}
// 讀串列埠
// pData: 待讀的資料緩衝區指標
// nLength: 待讀的最大資料長度
// 返回: 實際讀入的資料長度
int ReadComm(void *pData, int nLength)
{
DWORD dwNumRead; // 串列埠收到的資料長度
ReadFile(hComm, pData, (DWORD)nLength, &dwNumRead, NULL);
return (int)dwNumRead;
}
編/解碼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.總結
串列埠程式設計的核心在於串列埠通訊方式(傳送、接收和握手)的控制,而具體的應用領域反而是次要的。掌握了根本的原理,就可以靈活地將其應用於任意領域,綜合例項中的例子"簡訊控制終端"只是冰山一角。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-957366/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android之串列埠程式設計Android串列埠程式設計
- Linux串列埠程式設計Linux串列埠程式設計
- VC++ MFC程式設計版本資訊控制C++C程式程式設計
- ROS串列埠程式設計學習筆記ROS串列埠程式設計筆記
- 小型plc串列埠通訊簡介串列埠
- VC++程式設計師成長--之必看書籍C++程式設計師
- 串列埠通訊利器:SerialPortStream庫詳解,輕鬆實現C#串列埠開發串列埠C#
- 03_QT上位機開發之串列埠助手QT串列埠
- 實驗3 轉移指令跳轉原理及其簡單應用程式設計程式設計
- 虛擬串列埠工具MCGS開發除錯的靈活應用教程串列埠除錯
- 串列埠通訊串列埠
- 串列埠屏開發曲線串列埠
- 響應式程式設計簡介之:Reactor程式設計React
- 移動應用程式開發簡介!
- Android 串列埠通訊Android串列埠
- linux 串列埠通訊Linux串列埠
- 如何使用Java串列埠進行資料通訊及應用案例Java串列埠
- 串列埠資料抓取及串列埠通訊模擬串列埠
- STM32應用DMA——串列埠收發不定長資料串列埠
- Xamarin.Forms-手機串列埠除錯程式開發文件ORM串列埠除錯
- 迪文屏OS彙編程式碼開發-串列埠篇串列埠
- 玩轉 PHP 網路程式設計全套之 libevent 框架多人聊天應用PHP程式設計框架
- 簡單學:併發程式設計之 ThreadLocal程式設計thread
- 11. 串列埠通訊串列埠
- (10)uart串列埠通訊串列埠
- 串列埠通訊型別串列埠型別
- 串列埠通訊協議串列埠協議
- Java程式設計——伺服器設計方案之應用限流Java程式設計伺服器
- SSH原理常見應用升級及埠轉發
- 【程式設計開發】之開發解決的“坑“程式設計
- Flutter開發之非同步程式設計Flutter非同步程式設計
- 【程式設計開發】之 OAuth2程式設計OAuth
- ESP32 wifi 串列埠轉發資料 UART micropythonWiFi串列埠Python
- 串列埠無法正常通訊串列埠
- C# SerialPort 串列埠通訊C#串列埠
- 7寸串列埠屏在微波爐上的應用串列埠
- 7寸串列埠屏在破壁機上的應用串列埠
- VC++視覺化程式設計第一個程式設計例項出錯C++視覺化程式設計
- 《OpenCV 4.5計算機視覺開發實戰(基於VC++)》簡介OpenCV計算機視覺C++