天天看點

序列槽通訊-C++

簡單介紹和基本的幾個函數解析:

傳送門: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;
}
           

繼續閱讀