簡單介紹和基本的幾個函數解析:
傳送門:https://www.cnblogs.com/HPAHPA/p/7809445.html
序列槽通信是異步通信,是以,端口可以在一根線上發送資料的同時在另一根線上接收資料
序列槽通信最重要的參數是波特率、資料位、停止位和奇偶校驗。對于兩個進行通信的端口,這些參數必須比對。
(1)波特率:傳輸速率。如每秒鐘傳送240個字元,而每個字元格式包含10位(1個起始位,1個停止位,8個資料位),這時的波特率為240Bd,比特率為10位*240個/秒=2400bps。
(2)資料位:資料包中發送端想要發送的資料
(3)停止位:用于表示單個包的最後一位,結束标志以及校正時鐘同步
(4)奇偶校驗:檢錯方式。一共有四種檢錯方式:偶、奇、高和低。
序列槽操作總體流程::打開序列槽,配置序列槽,讀寫序列槽,關閉序列槽
打開序列槽:
(1)打開序列槽:使用CreateFile函數:
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName,//要打開或建立的檔案名
_In_ DWORD dwDesiredAccess,//通路類型
_In_ DWORD dwShareMode,//共享方式
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全屬性
_In_ DWORD dwCreationDisposition,//指定要打開的檔案已存在或不存在的動作
_In_ DWORD dwFlagsAndAttributes,//檔案屬性和标志
_In_opt_ HANDLE hTemplateFile//一個指向模闆檔案的句柄
);
參數說明:
1).lpFileName:要打開或建立的檔案名
2).dwDesiredAccess:通路類型。0為裝置查詢通路;GENERIC_READ為讀通路;GENERIC_WRITE為寫通路;
3).dwShareMode:共享方式。0表示檔案不能共享,試圖打開檔案的操作都會失敗;FILE_SHARE_READ表示允許其它讀操作;FILE_SHARE_WRITE表示允許寫操作;FILE_SHARE_DELETE表示允許删除操作。
4).lpSecurityAttributes:安全屬性。一個指向SECURITY_ATTRIBUTES結構的指針。一般傳入NULL
5).dwCreationDisposition:建立或打開檔案時的動作。 OPEN_ALWAYS:打開檔案,如果檔案不存在則建立;TRUNCATE_EXISTING 打開檔案,且将檔案清空(故需要GENERIC_WRITE權限),若檔案不存在則失敗;OPEN_EXISTING打開檔案,檔案若不存在則會失敗;CREATE_ALWAYS建立檔案,如果檔案已存在則清空;CREATE_NEW建立檔案,如檔案存在則會失敗;
6).dwFlagsAndAttributes:檔案标志屬性。FILE_ATTRIBUTE_NORMAL正常屬性; FILE_FLAG_OVERLAPPED異步I/O标志,如果不指定此标志則預設為同步IO;FILE_ATTRIBUTE_READONLY檔案為隻讀; FILE_ATTRIBUTE_HIDDEN檔案為隐藏。FILE_FLAG_DELETE_ON_CLOSE所有檔案句柄關閉後檔案被删除
7). hTemplateFile:一個檔案的句柄,且該檔案必須是以GENERIC_READ通路方式打開的。如果此參數不是NULL,則會使用hTemplateFile關聯的檔案的屬性和标志來建立檔案。如果是打開一個現有檔案,則該參數被忽略。
注意點: 使用該函數打開序列槽時需要注意的是:檔案名直接寫序列槽号名,如“COM1”,;共享方式應為0,即序列槽應為獨占方式;打開時的動作應為OPEN_EXISTING,即序列槽必須存在。
配置序列槽:
主要包含三部分内容:設定逾時和設定緩沖區,設定序列槽配置資訊
擷取、設定逾時:GetCommTimeouts SetCommTimeouts
逾時設定的資訊體:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //讀兩個字元間的事件間隔,若兩個字元之間的讀操作間隔超過此間隔則立即傳回
DWORD ReadTotalTimeoutMultiplier; //讀操作在讀取每個字元時的逾時
DWORD ReadTotalTimeoutConstant; //讀操作的固定逾時
DWORD WriteTotalTimeoutMultiplier; //寫操作在寫每個字元時的逾時
DWORD WriteTotalTimeoutConstant; //寫操作的固定逾時
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
SetupComm()函數用來設定序列槽的發送/接受緩沖區的大小
GetCommState()和SetCommState()分别用來獲得和設定串的配置資訊,如波特率、校驗方式、資料位個數、停止位個數等。一般也是先調用GetCommState()獲得序列槽配置資訊到一個DCB結構中去,再對這個結構自定義後調用SetCommState()進行設定。
讀寫序列槽:
主要包括三部分内容:清空緩沖, 清除錯誤,讀寫序列槽資料
清空緩沖:序列槽第一次使用或者序列槽長時間沒用,再次使用時。讀寫序列槽之前,都需要進行清空緩沖
BOOL PurgeComm(HANDLE hFile, DWORD dwFlags );
第二個參數dwFlags指定序列槽執行的動作,可以是以下值的組合:
-PURGE_TXABORT:停止目前所有的傳輸工作立即傳回不管是否完成傳輸動作。
-PURGE_RXABORT:停止目前所有的讀取工作立即傳回不管是否完成讀取動作。
-PURGE_TXCLEAR:清除發送緩沖區的所有資料。
-PURGE_RXCLEAR:清除接收緩沖區的所有資料。
如清除序列槽的所有操作和緩沖:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
清除錯誤:ClearCommError
讀寫序列槽:WriteFile()向序列槽中寫資料,ReadFile()從序列槽讀資料
關閉序列槽:
BOOL WINAPI CloseHandle(HANDLE hObject);
代碼:
用法
1.m_SerialPort.open(comInfo);
2.BYTE bufSend[] = { 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x8C, 0x3A };
m_SerialPort.write(bufSend, 8);
3.if (m_SerialPort.readAll(buf, len) < 0||len==0)
{
continue;
}
if (init)
{
memcpy(m_revBuf + m_nBufLen, buf, len);
m_nBufLen += len;
}
else
{
memcpy(m_revBuf, buf+8, len-8);
m_nBufLen += (len-8);
init=true;
}
//MySerialPort.h
#pragma once
#include "windows.h"
/*
* 内 容:自用序列槽類
* 功 能:EXPORT
*/
#include <tchar.h>
#define LOGE printf
#define LOGW printf
#define LOGD printf
//序列槽停止位
enum STOPBITS {
COM_ONESTOPBITS = 0,
COM_ONEHALFBITS,
COM_TWOSTOPBITS,
};
//校驗方式
enum PARITY {
COM_NOPARITY=0,
COM_ODDPARITY,
COM_EVENPARITY,
COM_MARKPARITY,
COM_SPACEPARITY,
};
//流控制
enum FLOWCONTROL {
COM_DTR_DSR=0,
COM_RTS_CTS,
COM_XON_XOFF,
COM_NO_HANDSHAKE,
};
// 序列槽相關資訊
#define COM_NAME_LEN 8
#define COM_FLAG_LEN 64
typedef struct BY_COM_INFO {
TCHAR portName[COM_NAME_LEN]; // = COM6
int baudRate; // = 9600 波特率 other 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
int dataBits; // = 8 資料位 other 7, 8
//
int stopBits; // = 1, 停止位 0=1, 1=1.5, 2=2
int parity; // = n, 校驗模式 0 =不校驗,1=Odd, 2=Even, 3= Mark,4 = 空格
int flowControl; // = 3, 序列槽流控制模式 0= DTR/DSR, 1=RTS/CTS,2 = Xon/Xoff, 3= None
int readTimeout; //讀逾時 // = 3000
int writeTimeout; //寫逾時 // = 3000
}ByCominfo;
//
typedef struct DATA_INFO {
unsigned int lenOfData;//實際長度
unsigned int lenOfMem; //最大記憶體
unsigned char data[1];
}DataInfo;
/*
@ 我的序列槽類
*/
class MySerialPort {
private:
ByCominfo m_config;
bool connected;
HANDLE m_handle;
BOOL processData(unsigned char *data, unsigned int dataLen, unsigned char *subData, unsigned int subDataLen);
public:
MySerialPort();
~MySerialPort();
bool isConnected() { return connected; };
int open(ByCominfo byCfg);//連接配接 打開序列槽
int read(unsigned char *receive_buf, unsigned int recv_buf_len,
unsigned char *stopReadFlag, int stopReadFlagLen);
int readAll(unsigned char *buf, int &len);//有多少資料讀多少資料
int comRead(unsigned char *receive_buf, unsigned int recv_buf_len,int needReadLen);
int write(unsigned char *data_buf, unsigned int data_len);//寫資料
void close();
};
//MySerialPort.cpp
#include "MySerialPort.h"
#include "stdio.h"
/*
@ constructor
*/
MySerialPort::MySerialPort()
{
this->m_config = {0};
this->connected =false;
this->m_handle = INVALID_HANDLE_VALUE;
}
/*
@ destructor
*/
MySerialPort::~MySerialPort()
{
}
/*
@ 打開序列槽
*/
int MySerialPort::open(ByCominfo comCfg)
{
//
if (connected)
{
close();
}
//
if (_tcsclen(comCfg.portName) == 0)
{
LOGE("comCfg.PortName is null pointer");
return -1;
}
//複制入參,友善重連
m_config = comCfg;
//拼接擷取連接配接名
const int COM_MAX_SIZE = 4096;
TCHAR comName[COM_MAX_SIZE];
_tcscpy_s(comName, COM_MAX_SIZE, _T("\\\\.\\"));
_tcscat_s(comName, COM_MAX_SIZE - 4, comCfg.portName);
//序列槽操作,以序列槽連接配接名建立檔案,對檔案讀寫實作互動
m_handle = CreateFile(comName,
GENERIC_READ | GENERIC_WRITE, //通路類型 0為裝置查詢通路;GENERIC_READ為讀通路;GENERIC_WRITE為寫通路;
0, //共享方式 0表示檔案不能共享,試圖打開檔案的操作都會失敗;FILE_SHARE_READ表示允許其它讀操作;FILE_SHARE_WRITE表示允許寫操作;FILE_SHARE_DELETE表示允許删除操作。
NULL, //安全屬性 一般傳入NULL
OPEN_EXISTING, //指定要打開的檔案已存在或不存在的動作 OPEN_ALWAYS:打開檔案,如果檔案不存在則建立;TRUNCATE_EXISTING 打開檔案,且将檔案清空(故需要GENERIC_WRITE權限),若檔案不存在則失敗;OPEN_EXISTING打開檔案,檔案若不存在則會失敗;CREATE_ALWAYS建立檔案,如果檔案已存在則清空;CREATE_NEW建立檔案,如檔案存在則會失敗;
0, //檔案屬性和标志 FILE_ATTRIBUTE_NORMAL正常屬性; FILE_FLAG_OVERLAPPED異步I/O标志,如果不指定此标志則預設為同步IO;FILE_ATTRIBUTE_READONLY檔案為隻讀; FILE_ATTRIBUTE_HIDDEN檔案為隐藏。FILE_FLAG_DELETE_ON_CLOSE所有檔案句柄關閉後檔案被删除
NULL //一個指向模闆檔案的句柄
);
//注意點: 使用該函數打開序列槽時需要注意的是:檔案名直接寫序列槽号名,如“COM1”,;共享方式應為0,即序列槽應為獨占方式;打開時的動作應為OPEN_EXISTING,即序列槽必須存在。
if (INVALID_HANDLE_VALUE == m_handle) {
LOGE("INVALID_HANDLE_VALUE == m_handle");
return -2;
}
//
// 配置序列槽
SetupComm(m_handle, COM_MAX_SIZE, COM_MAX_SIZE);//設定緩存
// 逾時設定(first)
COMMTIMEOUTS timeouts;
if (!GetCommTimeouts(m_handle, &timeouts)) {
LOGE("GetCommTimeouts失敗,GetLastError = %d", GetLastError());
return -5;
}
timeouts.ReadIntervalTimeout = 20; // 設定讀間隔逾時為最大值
timeouts.ReadTotalTimeoutMultiplier = 0; // 讀逾時時間系數
timeouts.ReadTotalTimeoutConstant = comCfg.readTimeout; // 讀逾時時間常量
timeouts.WriteTotalTimeoutMultiplier = 0; // 寫逾時時間系數
timeouts.WriteTotalTimeoutConstant = comCfg.writeTimeout; // 寫逾時時間常量
if (!SetCommTimeouts(m_handle, &timeouts)) {
LOGE("SetCommTimeouts失敗,GetLastError = %d", GetLastError());
return -6;
}
//DCB結構
DCB dcb;
// 設定端口的參數
if (!GetCommState(m_handle, &dcb)) {//讀取序列槽設定
LOGE("GetCommState失敗");
return -3;
}
//
dcb.DCBlength = sizeof(DCB);
//
dcb.BaudRate = comCfg.baudRate; //波特率
dcb.ByteSize = comCfg.dataBits; //資料位
//
dcb.StopBits = comCfg.stopBits; //停止位
dcb.Parity = comCfg.parity; //
if (comCfg.parity != NOPARITY) {
dcb.fParity = TRUE;
}
//
if (comCfg.flowControl == COM_DTR_DSR) {
//DTR/DSR
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = TRUE;
dcb.fDsrSensitivity = FALSE;
}
else if (comCfg.flowControl == COM_RTS_CTS) {
//RTS/CTS
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fOutxCtsFlow = TRUE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDsrSensitivity = FALSE;
}
else if (comCfg.flowControl == COM_XON_XOFF) {
// XON/XOFF
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fOutX = TRUE;
dcb.fInX = TRUE;
dcb.XonChar = 0x11;
dcb.XoffChar = 0x13;
dcb.fDsrSensitivity = FALSE;
dcb.fTXContinueOnXoff = FALSE;
}
else if (comCfg.flowControl == COM_NO_HANDSHAKE) {
//NO
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
}
//
if (!SetCommState(m_handle, &dcb)) { //設定COM口裝置控制塊
LOGE("SetCommState失敗,GetLastError = %d", GetLastError());
return -4;
}
//清空緩存
PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
//
connected = true;
return 0;
}
/*
@ 讀序列槽
@ 傳回正确接收的資料長度,如果<=0 都是錯誤!
*/
int MySerialPort::read(unsigned char *receive_buf, unsigned int recv_buf_len,unsigned char *stopReadFlag, int stopReadFlagLen)
{
//
if (m_handle == INVALID_HANDLE_VALUE) {
LOGE("handle_port is NULL.");
return -1;
}
//
if ((NULL == receive_buf) || (recv_buf_len <= 0) || (NULL == stopReadFlag) || (stopReadFlagLen <= 0)) {
LOGE("Input parameter is invalid.");
return -2;
}
//
DWORD dwErrorMask = 0;
COMSTAT cs;
//查詢逾時時間
DWORD dwBegin = GetTickCount();
DWORD dwEnd = dwBegin + m_config.readTimeout;
DWORD hasReadLength = 0, curNeedReadLength, curRealReadLength;
int tt = GetTickCount();
int read_success_flag =FALSE;
//
while (dwBegin <= dwEnd) {
if (FALSE == ClearCommError(m_handle, &dwErrorMask, &cs)) {
LOGE("ClearCommError failed");
return -3;
}
//
if (cs.cbInQue > 0) {
if (hasReadLength + cs.cbInQue > recv_buf_len) {
curNeedReadLength = recv_buf_len - hasReadLength;
}
else {
curNeedReadLength = cs.cbInQue;
}
//
if (!ReadFile(m_handle, receive_buf + hasReadLength, curNeedReadLength, &curRealReadLength, NULL)) {
LOGE("ReadFile failed");
return -3;
}
//
hasReadLength += curRealReadLength;
//
if (processData(receive_buf, hasReadLength, stopReadFlag, stopReadFlagLen)) {
read_success_flag = TRUE;
break;
}
//
if (hasReadLength >= recv_buf_len) {
break;
}
//
dwEnd = GetTickCount() + m_config.readTimeout;
continue;
}
//
Sleep(10);
dwBegin = GetTickCount();
}
//
LOGD("Read cost time:%d.", GetTickCount() - tt);
//
//PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
if (read_success_flag == FALSE) {
LOGW("未讀取到結束辨別符.");
return -4;
}
//
return hasReadLength;
}
/*
@ 有多少讀多少
@ 傳回值 0 為正常,<0 為異常
*/
int MySerialPort::readAll(unsigned char *buf,int &len)
{
DWORD ReadSize = 0;
BOOL rtn = FALSE;
//設定讀取1個位元組資料,當緩存中有資料到達時則會立即傳回,否則直到逾時
rtn = ReadFile(m_handle, buf, 1000, &ReadSize, NULL);
//如果是逾時rtn=true但是ReadSize=0,如果有資料到達,會讀取一個位元組ReadSize=1
if ((rtn == TRUE) && (ReadSize>0))
{
len = ReadSize;
}
//
//PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
//
return 0;
}
/*
@
*/
int MySerialPort::comRead(unsigned char *receive_buf, unsigned int recv_buf_len, int needReadLen)
{
//
int hasReadLength = 0;
int read_success_flag = FALSE;
//
if (m_handle == INVALID_HANDLE_VALUE) {
LOGE("handle_port is NULL.");
return -1;
}
//
DWORD ReadSize = 0;
BOOL rtn = FALSE;
//設定讀取1個位元組資料,當緩存中有資料到達時則會立即傳回,否則直到逾時
rtn = ReadFile(m_handle, receive_buf, 1, &ReadSize, NULL);
++hasReadLength;
//如果是逾時rtn=true但是ReadSize=0,如果有資料到達,會讀取一個位元組ReadSize=1
if (rtn == TRUE && 1 == ReadSize)
{
if (receive_buf[0] != 0x1b)
{
LOGE("receive_buf[0] !=0x1b");
PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
return -1;
}
DWORD Error;
COMSTAT cs = { 0 };
int ReadLen = 0;
while (hasReadLength < needReadLen)
{
//查詢剩餘多少位元組未讀取,存儲于cs.cbInQue中
ClearCommError(m_handle, &Error, &cs);
ReadLen = (cs.cbInQue > (needReadLen-hasReadLength)) ? (needReadLen - hasReadLength) : cs.cbInQue;
if (ReadLen > 0)
{
//由于之前等待時以讀取一個位元組,所欲buf+1
rtn = ReadFile(m_handle, receive_buf + hasReadLength, ReadLen, &ReadSize, NULL);
if (rtn)
{
hasReadLength = ReadLen + 1;
}
}
}
}
//
if (hasReadLength !=3)
{
LOGW("未讀取到結束辨別符.");
return -4;
}
//
PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
return hasReadLength;
}
/*
@ 寫序列槽
@ 傳回正确發出的資料長度,如果<=0 都是錯誤!
*/
int MySerialPort::write(unsigned char *data_buf, unsigned int data_len) {
//
if (m_handle == INVALID_HANDLE_VALUE) {
LOGE("handle_port is NULL.");
return -1;
}
//
if ((data_len <=0) || (NULL == data_buf) ) {
LOGE("Input parameter is invalid.");
return -2;
}
//清空緩存
//PurgeComm(m_handle, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT);
//
const int BytesOnce = 1024;
DWORD dwBytesWritten = 0;
int nCount = data_len / BytesOnce;
int remain = data_len % BytesOnce;
int real_write_len = 0;
//
for (int i = 0; i < nCount; i++) {
WriteFile(m_handle, data_buf + i * BytesOnce, BytesOnce, (LPDWORD)&real_write_len, 0);
dwBytesWritten += real_write_len;
if (real_write_len != BytesOnce) {
real_write_len = dwBytesWritten;
LOGE("write failed.real_write_len(%d) != BytesOnce(%d).", real_write_len, BytesOnce);
return -3;
break;
}
}
//
if (remain != 0) {
WriteFile(m_handle, data_buf + nCount * BytesOnce, remain, (LPDWORD)&real_write_len, 0);
dwBytesWritten += real_write_len;
if (real_write_len != remain) {
real_write_len = dwBytesWritten;
LOGE("write failed.real_write_len(%d) != remain(%d).", real_write_len, remain);
return -3;
}
}
//
return dwBytesWritten;
}
/*
@ 關閉視窗
*/
void MySerialPort::close() {
if (INVALID_HANDLE_VALUE != m_handle) {
CloseHandle(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
connected = false;
}
//
BOOL MySerialPort::processData(unsigned char *data, unsigned int dataLen, unsigned char *subData, unsigned int subDataLen) {
unsigned char *tmpData = data, *tmpSubData = subData;
//
if (data == NULL || subData== NULL || subDataLen<= 0 || dataLen <= 0|| dataLen < subDataLen) {
return FALSE;
}
//
for (tmpData = data; tmpData < data + dataLen; tmpData++) {
unsigned char * pSrc = tmpData;
for (tmpSubData = subData; tmpSubData < subData + subDataLen; tmpSubData++, pSrc++) {
if (*pSrc != *tmpSubData) {
break;
}
}
//
if (tmpSubData == subData + subDataLen) {
return TRUE;
}
}
//
return FALSE;
}