AT指令发送PDU短信详解

 

本文以一个实例来解说AT指令发送PDU短信的全过程,假如我要发送下面的短信:
接收号码:"+8613602433649
短信内容:工作愉快!

短信中心号码:+8613800200500

一、短信中心号码处理:用字符串 addr 表示

1
、将短信息中心号码去掉+号,看看长度是否为偶数,如果不是,最后添加F
addr = "+8613800200500"
=> addr = "8613800200500F"
2
、将奇数位和偶数位交换。

=> addr = " 683108200005F 0"
3
、将短信息中心号码前面加上字符9191是国际化的意思
=> addr = "91683108200005F0"
4
、算出 addr 长度,结果除2,格式化成2位的16进制字符串,16 / 2 = 8 => "08"
=> addr = "0891683108200005F0"

二、手机号码处理:用字符串 phone

1
、将手机号码去掉+号,看看长度是否为偶数,如果不是,最后添加F
phone = "+8613602433649"
=> phone = "8613602433649F"
2
、将手机号码奇数位和偶数位交换。

=> phone = " 683106423346F 9"

三、短信息部分处理:用字符串 msg 表示
1
、转字符串转换为Unicode代码,例如工作愉快!unicode代码为 5DE 54F 5C 61095FEBFF01
(
转换函数见最后附录)
2
、将 msg 长度除2,保留两位16进制数,即 5DE 54F 5C 61095FEBFF01 = 20 / 2 => " 0A ",再加上
msg
=> msg = " 0A 5DE 54F 5C 61095FEBFF01"

四、组合

1
、手机号码前加上字符串 11000D911100:固定,0D:手机号码的长度,不算+号,十六进制表示,91:发送到手机为91,发送到小灵通为81),
phone = "11000D91" + phone
=> 11000D91683106423346F9
2
、手机号码后加上 000800 和刚才的短信息内容,000800也写死就可以了

phone = phone + "000800" + msg
11000D91683106423346F9 + 000800 + 0A 5DE 54F 5C 61095FEBFF01
=> phone = 11000D91683106423346F 90008000A 5DE 54F 5C 61095FEBFF01
3
phone 长度除以2,格式化成2位的十进制数

11000D91683106423346F 90008000A 5DE 54F 5C 61095FEBFF01 => 50 / 2 => 25

五、所要发送的内容为

AT+CMGF=0 <
回车>
OK
AT+CMGS=25<
回车
>
> addr+phone <Ctrl+Z
发送
>

六、如果返回不是ERROR,恭喜你,发送成功了,代码如下

1.消息编码与解码
用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
     {
         // 取源字符串的计数值的最低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
     {
         // 将源字节右边部分与残余数据相加,去掉最高位,得到一个目标解码字节
         *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
     {
         // 先输出高位字节
         *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
     {
         // 先高位字节
         wchar[i] = *pSrc++ << 8;
    
         // 后低位字节
         wchar[i] |= *pSrc++;
     }
    
     // UNICODE串-->字符串
     nDstLength = ::WideCharToMultiByte(CP_ACP, 0, wchar, nSrcLength/2, pDst, 160, NULL, NULL);
    
     // 输出字符串加个结束符    
     pDst[nDstLength] = '/0';    
    
     // 返回目标字符串长度
     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
     {
         // 输出高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++;
     }
    
     // 返回目标数据长度
     returnnSrcLength / 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
     {
         // 输出低4位
         *pDst++ = tab[*pSrc >> 4];
    
         // 输出高4位
         *pDst++ = tab[*pSrc & 0x0f];
    
         pSrc++;
     }
    
     // 输出字符串加个结束符
     *pDst = '/0';
    
     // 返回目标字符串长度
     return nSrcLength * 2;
}


2消息发送
// 用户信息编码方式
#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;

大家已经注意到PDU串中的号码和时间,都是两两颠倒的字符串。利用下面两个函数可进行正反变换:

// 正常顺序的字符串转换为两两颠倒的字符串,若长度为奇数,补'F'凑成偶数
// 如:"8613851872468" --> "683158812764F8"
// pSrc: 源字符串指针
// pDst: 目标字符串指针
// nSrcLength: 源字符串长度
// 返回: 目标字符串长度
int gsmInvertNumbers(const char* pSrc, char* pDst, int nSrcLength)
{
     int nDstLength;    // 目标字符串长度
     char ch;           // 用于保存一个字符
    
     // 复制串长度
     nDstLength = nSrcLength;
    
     // 两两颠倒
     for(int i=0; i
     {
         ch = *pSrc++;         // 保存先出现的字符
         *pDst++ = *pSrc++;    // 复制后出现的字符
         *pDst++ = ch;         // 复制先出现的字符
     }
    
     // 源串长度是奇数吗?
     if(nSrcLength & 1)
     {
         *(pDst-2) = 'F';      // 补'F'
         nDstLength++;         // 目标串长度加1
     }
    
     // 输出字符串加个结束符
     *pDst = '/0';
    
     // 返回目标字符串长度
     return nDstLength;
}
    
// 两两颠倒的字符串转换为正常顺序的字符串
// 如:"683158812764F8" --> "8613851872468"
// pSrc: 源字符串指针
// pDst: 目标字符串指针
// nSrcLength: 源字符串长度
// 返回: 目标字符串长度
int gsmSerializeNumbers(const char* pSrc, char* pDst, int nSrcLength)
{
     int nDstLength;    // 目标字符串长度
     char ch;           // 用于保存一个字符
    
     // 复制串长度
     nDstLength = nSrcLength;
    
     // 两两颠倒
     for(int i=0; i
     {
         ch = *pSrc++;         // 保存先出现的字符
         *pDst++ = *pSrc++;    // 复制后出现的字符
         *pDst++ = ch;         // 复制先出现的字符
     }
    
     // 最后的字符是'F'吗?
     if(*(pDst-1) == 'F')
     {
         pDst--;
         nDstLength--;         // 目标字符串长度减1
     }
    
     // 输出字符串加个结束符
     *pDst = '/0';
    
     // 返回目标字符串长度
     return nDstLength;
}

以下是PDU全串的编解码模块。为简化编程,有些字段用了固定值。

// PDU编码,用于编制、发送短消息
// pSrc: 源PDU参数指针
// pDst: 目标PDU串指针
// 返回: 目标PDU串长度
int gsmEncodePdu(const SM_PARAM* pSrc, char* pDst)
{
     int nLength;              // 内部用的串长度
     int nDstLength;           // 目标PDU串长度
     unsigned char buf[256]; // 内部用的缓冲区
    
     // SMSC地址信息段
     nLength = strlen(pSrc->SCA);     // SMSC地址字符串的长度    
     buf[0] = (char)((nLength & 1) == 0 ? nLength : nLength + 1) / 2 + 1;     // SMSC地址信息长度
     buf[1] = 0x91;         // 固定: 用国际格式号码
     nDstLength = gsmBytes2String(buf, pDst, 2);         // 转换2个字节到目标PDU串
     nDstLength += gsmInvertNumbers(pSrc->SCA, &pDst[nDstLength], nLength);     // 转换SMSC到目标PDU串
    
     // TPDU段基本参数、目标地址等
     nLength = strlen(pSrc->TPA);     // TP-DA地址字符串的长度
     buf[0] = 0x11;             // 是发送短信(TP-MTI=01),TP-VP用相对格式(TP-VPF=10)
     buf[1] = 0;                // TP-MR=0
     buf[2] = (char)nLength;    // 目标地址数字个数(TP-DA地址字符串真实长度)
     buf[3] = 0x91;             // 固定: 用国际格式号码
     nDstLength += gsmBytes2String(buf, &pDst[nDstLength], 4); // 转换4个字节到目标PDU串
     nDstLength += gsmInvertNumbers(pSrc->TPA, &pDst[nDstLength], nLength); // 转换TP-DA到目标PDU串
    
     // TPDU段协议标识、编码方式、用户信息等
     nLength = strlen(pSrc->TP_UD);     // 用户信息字符串的长度
     buf[0] = pSrc->TP_PID;         // 协议标识(TP-PID)
     buf[1] = pSrc->TP_DCS;         // 用户信息编码方式(TP-DCS)
     buf[2] = 0;             // 有效期(TP-VP)为5分钟
     if(pSrc->TP_DCS == GSM_7BIT)    
     {
         // 7-bit编码方式
         buf[3] = nLength;             // 编码前长度
         nLength = gsmEncode7bit(pSrc->TP_UD, &buf[4], nLength+1) + 4;     // 转换TP-DA到目标PDU串
     }
     else if(pSrc->TP_DCS == GSM_UCS2)
     {
         // UCS2编码方式
         buf[3] = gsmEncodeUcs2(pSrc->TP_UD, &buf[4], nLength);     // 转换TP-DA到目标PDU串
         nLength = buf[3] + 4;         // nLength等于该段数据长度
     }
     else
     {
         // 8-bit编码方式
         buf[3] = gsmEncode8bit(pSrc->TP_UD, &buf[4], nLength);     // 转换TP-DA到目标PDU串
         nLength = buf[3] + 4;         // nLength等于该段数据长度
     }
     nDstLength += gsmBytes2String(buf, &pDst[nDstLength], nLength);         // 转换该段数据到目标PDU串
    
     // 返回目标字符串长度
     return nDstLength;
}
    
// PDU解码,用于接收、阅读短消息
// pSrc: 源PDU串指针
// pDst: 目标PDU参数指针
// 返回: 用户信息串长度
int gsmDecodePdu(const char* pSrc, SM_PARAM* pDst)
{
     int nDstLength;           // 目标PDU串长度
     unsigned char tmp;        // 内部用的临时字节变量
     unsigned char buf[256]; // 内部用的缓冲区
    
     // SMSC地址信息段
     gsmString2Bytes(pSrc, &tmp, 2);     // 取长度
     tmp = (tmp - 1) * 2;     // SMSC号码串长度
     pSrc += 4;               // 指针后移
     gsmSerializeNumbers(pSrc, pDst->SCA, tmp);     // 转换SMSC号码到目标PDU串
     pSrc += tmp;         // 指针后移
    
     // TPDU段基本参数、回复地址等
     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);     // 取TP-RA号码
         pSrc += tmp;         // 指针后移
     }
    
     // TPDU段协议标识、编码方式、用户信息等
     gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_PID, 2);     // 取协议标识(TP-PID)
     pSrc += 2;         // 指针后移
     gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_DCS, 2);     // 取编码方式(TP-DCS)
     pSrc += 2;         // 指针后移
     gsmSerializeNumbers(pSrc, pDst->TP_SCTS, 14);         // 服务时间戳字符串(TP_SCTS)
     pSrc += 14;        // 指针后移
     gsmString2Bytes(pSrc, &tmp, 2);     // 用户信息长度(TP-UDL)
     pSrc += 2;         // 指针后移
     if(pDst->TP_DCS == GSM_7BIT)    
     {
         // 7-bit解码
         nDstLength = gsmString2Bytes(pSrc, buf, tmp & 7 ? (int)tmp * 7 / 4 + 2 : (int)tmp * 7 / 4); // 格式转换
         gsmDecode7bit(buf, pDst->TP_UD, nDstLength);     // 转换到TP-DU
         nDstLength = tmp;
     }
     else if(pDst->TP_DCS == GSM_UCS2)
     {
         // UCS2解码
         nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2);         // 格式转换
         nDstLength = gsmDecodeUcs2(buf, pDst->TP_UD, nDstLength);     // 转换到TP-DU
     }
     else
     {
         // 8-bit解码
         nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2);         // 格式转换
         nDstLength = gsmDecode8bit(buf, pDst->TP_UD, nDstLength);     // 转换到TP-DU
     }
    
     // 返回目标字符串长度
     return nDstLength;
}

 

依照GSM 07.05,发送短消息用AT+CMGS命令,阅读短消息用AT+CMGR命令,列出短消息用AT+CMGL命令,删除短消息用AT+CMGD命令。但AT+CMGL命令能够读出所有的短消息,所以我们用它实现阅读短消息功能,而没用AT+CMGR。下面是发送、读取和删除短消息的实现代码:

// 发送短消息
// pSrc: 源PDU参数指针
BOOL gsmSendMessage(const SM_PARAM* pSrc)
{
     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;
}
    
// 读取短消息
// 用+CMGL代替+CMGR,可一次性读出全部短消息
// 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");     // 生成命令
    
     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);     // 读取序号
             TRACE(" index=%d/n",pMsg->index);
    
             ptr = strstr(ptr, "/r/n");     // 找下一行
             ptr += 2;         // 跳过"/r/n"
                
             gsmDecodePdu(ptr, pMsg);     // PDU串解码
             pMsg++;         // 准备读下一条短消息
             nMsg++;         // 短消息计数加1
         }
     }
    
     return nMsg;
}
    
// 删除短消息
// 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;
}

以上发送AT命令过程中用到了WriteComm和ReadComm函数,它们是用来读写串口的,依赖于具体的操作系统。在Windows环境下,除了用MSComm控件,以及某些现成的串口通信类之外,也可以简单地调用一些Windows API用实现。以下是利用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;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值