承接上篇《透過序列埠收發簡訊(上)》的 7-bit、8-bit、UCS2 編碼演算法,本文將完整呈現 PDU 完整字串的編解碼模組、GSM 07.05 AT 指令收發流程、序列埠通訊底層實作,以及實務常見問題解答。為確保程式碼可移植性,盡量使用 ANSI C 標準庫,避免 MFC 類別依賴。
// 使用者資料編碼方式
#define GSM_7BIT 0
#define GSM_8BIT 4
#define GSM_UCS2 8
// 簡訊參數結構(編碼/解碼共用)
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]; // 服務中心時間戳記 (接收用)
char TP_UD[161]; // 原始使用者資料 (編碼前/解碼後)
char index; // 簡訊索引 (讀取用)
} SM_PARAM;
PDU 中號碼與時間採「兩兩顛倒」的 BCD 格式,奇數長度補 'F'。提供雙向轉換函式:
// 正常順序 → 兩兩顛倒(奇數補 F)
// "8613851872468" → "683158812764F8"
int gsmInvertNumbers(const char* pSrc, char* pDst, int nSrcLength);
// 兩兩顛倒 → 正常順序(移除尾部 F)
// "683158812764F8" → "8613851872468"
int gsmSerializeNumbers(const char* pSrc, char* pDst, int nSrcLength);
int gsmEncodePdu(const SM_PARAM* pSrc, char* pDst) {
unsigned char buf[256];
int nDstLength = 0, nLength;
// SMSC 地址資訊段
nLength = strlen(pSrc->SCA);
buf[0] = (char)(((nLength & 1) == 0 ? nLength : nLength + 1) / 2 + 1);
buf[1] = 0x91; // 國際格式
nDstLength += gsmBytes2String(buf, pDst, 2);
nDstLength += gsmInvertNumbers(pSrc->SCA, &pDst[nDstLength], nLength);
// TPDU 基本參數與目標地址
nLength = strlen(pSrc->TPA);
buf[0] = 0x11; // TP-MTI=01 (發送), TP-VPF=10 (相對格式)
buf[1] = 0; // TP-MR=0
buf[2] = (char)nLength;
buf[3] = 0x91; // 國際格式
nDstLength += gsmBytes2String(buf, &pDst[nDstLength], 4);
nDstLength += gsmInvertNumbers(pSrc->TPA, &pDst[nDstLength], nLength);
// 協定識別碼、編碼方式、有效期、使用者資料
nLength = strlen(pSrc->TP_UD);
buf[0] = pSrc->TP_PID;
buf[1] = pSrc->TP_DCS;
buf[2] = 0; // 有效期 5 分鐘
if (pSrc->TP_DCS == GSM_7BIT) {
buf[3] = nLength; // 編碼前長度
nLength = gsmEncode7bit(pSrc->TP_UD, &buf[4], nLength+1) + 4;
} else if (pSrc->TP_DCS == GSM_UCS2) {
buf[3] = gsmEncodeUcs2(pSrc->TP_UD, &buf[4], nLength);
nLength = buf[3] + 4;
} else {
buf[3] = gsmEncode8bit(pSrc->TP_UD, &buf[4], nLength);
nLength = buf[3] + 4;
}
nDstLength += gsmBytes2String(buf, &pDst[nDstLength], nLength);
return nDstLength;
}
int gsmDecodePdu(const char* pSrc, SM_PARAM* pDst) {
unsigned char tmp, buf[256];
// 解析 SMSC
gsmString2Bytes(pSrc, &tmp, 2);
tmp = (tmp - 1) * 2;
pSrc += 4;
gsmSerializeNumbers(pSrc, pDst->SCA, tmp);
pSrc += tmp;
// 解析 TPDU 基本參數與回覆地址 (TP-RA)
gsmString2Bytes(pSrc, &tmp, 2);
pSrc += 2;
if (tmp & 0x80) { // 包含回覆地址
gsmString2Bytes(pSrc, &tmp, 2);
if (tmp & 1) tmp += 1;
pSrc += 4;
gsmSerializeNumbers(pSrc, pDst->TPA, tmp);
pSrc += tmp;
}
// 解析 PID、DCS、時間戳記、使用者資料長度
gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_PID, 2); pSrc += 2;
gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_DCS, 2); pSrc += 2;
gsmSerializeNumbers(pSrc, pDst->TP_SCTS, 14); pSrc += 14;
gsmString2Bytes(pSrc, &tmp, 2); pSrc += 2; // TP-UDL
// 依編碼方式解碼使用者資料
if (pDst->TP_DCS == GSM_7BIT) {
int nDstLength = gsmString2Bytes(pSrc, buf, tmp & 7 ? (int)tmp * 7 / 4 + 2 : (int)tmp * 7 / 4);
gsmDecode7bit(buf, pDst->TP_UD, nDstLength);
return tmp;
} else if (pDst->TP_DCS == GSM_UCS2) {
int nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2);
return gsmDecodeUcs2(buf, pDst->TP_UD, nDstLength);
} else {
int nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2);
return gsmDecode8bit(buf, pDst->TP_UD, nDstLength);
}
}
BOOL gsmSendMessage(const SM_PARAM* pSrc) {
char pdu[512], cmd[16], ans[128];
int nPduLength = gsmEncodePdu(pSrc, pdu);
strcat(pdu, "\x1A"); // Ctrl-Z 結束
unsigned char nSmscLength;
gsmString2Bytes(pdu, &nSmscLength, 2);
nSmscLength++; // 含長度位元組本身
sprintf(cmd, "AT+CMGS=%d\r", nPduLength / 2 - nSmscLength);
WriteComm(cmd, strlen(cmd));
int nLength = ReadComm(ans, 128);
if (nLength == 4 && strncmp(ans, "\r\n> ", 4) == 0) {
WriteComm(pdu, strlen(pdu));
nLength = ReadComm(ans, 128);
if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
return TRUE;
}
return FALSE;
}
int gsmReadMessage(SM_PARAM* pMsg) {
char ans[1024], cmd[16] = "AT+CMGL\r";
WriteComm(cmd, strlen(cmd));
int nLength = ReadComm(ans, 1024);
if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0) {
int nMsg = 0;
char* ptr = ans;
while ((ptr = strstr(ptr, "+CMGL:")) != NULL) {
ptr += 6;
sscanf(ptr, "%d", &pMsg->index);
ptr = strstr(ptr, "\r\n") + 2;
gsmDecodePdu(ptr, pMsg);
pMsg++; nMsg++;
}
return nMsg;
}
return 0;
}
BOOL gsmDeleteMessage(int index) {
char cmd[16], ans[128];
sprintf(cmd, "AT+CMGD=%d\r", index);
WriteComm(cmd, strlen(cmd));
int nLength = ReadComm(ans, 128);
return (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0);
}
HANDLE hComm;
BOOL OpenComm(const char* pPort, int nBaudRate, int nParity, int nByteSize, int nStopBits) {
DCB dcb;
COMMTIMEOUTS timeouts = {100, 3, 2, 3, 2}; // 讀寫超時參數
hComm = CreateFile(pPort, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hComm == INVALID_HANDLE_VALUE) return FALSE;
GetCommState(hComm, &dcb);
dcb.BaudRate = nBaudRate; dcb.StopBits = nStopBits;
dcb.Parity = nParity; dcb.ByteSize = nByteSize;
SetCommState(hComm, &dcb);
SetCommTimeouts(hComm, &timeouts);
return TRUE;
}
// WriteFile / ReadFile / CloseHandle 發送、接收、關閉
+CMS ERROR,應檢測回應失敗後重發。建議將收發封裝於工作執行緒,避免阻塞 UI。