因工作的需要,采用了基于VC开发项目,因需要用到串口,这里面没用到windows的MSCOMM空间和CSerialPot的类,而是专门利用windows api函数的同步机制来封装此类,类的接口模式有点模仿QT的Win_QextSerialPort。本库可以直接用在MFC上,当然也可以移植到QT上面。
#pragma once #include<Windows.h> #include <math.h> #define MAX_RECV_BUFFER 1024 #define MAX_SEND_BUFFER 1024 typedef struct { DWORD baudRate; BYTE parity; BYTE stopBits; BYTE bytesize; }PortSettings; typedef void (*SerialPortCallBack)(char *, DWORD); class SerialPortClass { private: HANDLE m_hCom; PortSettings portSet; CString portName; SerialPortCallBack recvCb; HANDLE rThread; char recvBuffer[MAX_RECV_BUFFER]; bool recvRun; private: void SetDefaultPortSettings(PortSettings &port) { port.baudRate = 115200; port.parity = 0; port.stopBits = 0; port.bytesize = 8; } public: SerialPortClass() { portName = _T(""); SetDefaultPortSettings(portSet); m_hCom = INVALID_HANDLE_VALUE; } SerialPortClass(const CString &name) { portName = name; SetDefaultPortSettings(portSet); m_hCom = INVALID_HANDLE_VALUE; } SerialPortClass(PortSettings &port) { portName = _T(""); portSet = port; m_hCom = INVALID_HANDLE_VALUE; } SerialPortClass(const CString &name, PortSettings &port) { portName = name; portSet = port; m_hCom = INVALID_HANDLE_VALUE; } ~SerialPortClass() { if (m_hCom != INVALID_HANDLE_VALUE) { CloseHandle(m_hCom); } recvRun = false; } void SetPortName(const CString &name) { portName = name; } bool IsOpen() { if (m_hCom == INVALID_HANDLE_VALUE) { return false; } return true; } bool SetPortSetttings(PortSettings &port) { DCB dcb; if (m_hCom == INVALID_HANDLE_VALUE) //如果没有打开,直接返回,设置也没用 { return false; } double tmp = 1 + port.bytesize; tmp += port.bytesize; if (port.parity > 0) //0:无校验 1:奇 2:偶 3:标记校验 { tmp += 1; } if (port.stopBits == 0) //0:1个停止位 1:1.5个停止位 2:2两个停止位 { tmp += 1; } else if (port.stopBits == 1) { tmp += 1.5; } else { tmp += 2; } GetCommState(m_hCom, &dcb); dcb.BaudRate = port.baudRate; dcb.Parity = port.parity; dcb.StopBits = port.stopBits; dcb.ByteSize = port.bytesize; if (!SetCommState(m_hCom, &dcb)) { return false; } SetupComm(m_hCom, MAX_RECV_BUFFER, MAX_SEND_BUFFER); //设置缓冲区大小 PurgeComm(m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR); COMMTIMEOUTS timeouts; //这10000本来的数字是3500,这个是数字是根据modbus协议超时机制来设置的也就是两个字符之间时间间隔 //为3.5个字符,但测试效果不佳,原始是这个时间间隔太短,取整的话也是1ms,相当于如果两个字符时间 //如果接收的一个字符,过了1ms还没有收到字符,这回调函数直接响应,确实时间间隔太小,不过没关系, //我们自己私定以下,字符间隔时间设置为10,这相当于是这里的10000了,反复效果很理想,ReadIntervalTimeout //这个变量是其实和ReadFile有关系的,如果该变量设置为0,就是一直等待接收到指定数量的字符才返回, //本例子只针对于串口同步机制,想了解更清楚,还是要看具体的api函数。这个地方也是难点和重点 //如果下位机发送部分写的不好的话,这时间间隔还可以设置大一点,我串口发送采用的是中断机制,还算可以 tmp = (tmp * (double)10000) / (double)(port.baudRate);//超时,有点像modbus协议 timeouts.ReadIntervalTimeout = (DWORD)ceil(tmp); timeouts.ReadTotalTimeoutMultiplier = 0; timeouts.ReadTotalTimeoutConstant = 0; timeouts.WriteTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutConstant = 0; if(!SetCommTimeouts(m_hCom, &timeouts)) { return false; } return true; } bool Open(SerialPortCallBack cb, CString name = _T("")) { if (m_hCom != INVALID_HANDLE_VALUE) //如果已经打开,则直接返回 { return false; } if (name != _T("")) { portName = name; } if (_ttoi(portName.Mid(1, portName.GetLength() - 3)) > 9) { portName = _T("\\\\.\\") + portName; } m_hCom = CreateFile(portName, //端口号 GENERIC_READ|GENERIC_WRITE, //权限可读写 0, NULL, OPEN_EXISTING, //打开存在的端口 NULL, //以同步方式打开 NULL); if (!SetPortSetttings(portSet)) //设置波特率等失败 { CloseHandle(m_hCom); //但是此时串口是打开的,因此关闭 m_hCom = INVALID_HANDLE_VALUE; return false; } recvCb = cb; //保存回调函数指针,当有数据来临时,方便调用 rThread = CreateThread(NULL, 0, CommRecvThread, this, 0, NULL); //新建监听线程 CloseHandle(rThread); recvRun = true; return true; } void Close() { if (m_hCom != INVALID_HANDLE_VALUE) { CloseHandle(m_hCom); recvRun = false; //停止接收线程 } } DWORD Write(char *str, DWORD len) { DWORD dwBytesWrite; if (m_hCom == INVALID_HANDLE_VALUE) { return false; } WriteFile(m_hCom, str, len, &dwBytesWrite, NULL); PurgeComm(m_hCom, PURGE_TXCLEAR); return dwBytesWrite; } static DWORD WINAPI CommRecvThread(LPVOID lpParameter) { SerialPortClass *p = (SerialPortClass *)lpParameter; p->RecvThread(); return 0; } void RecvThread() { DWORD dwBytesRead, dwErrorFlags; COMSTAT comStat; while (1) { ClearCommError(m_hCom, &dwErrorFlags, &comStat); if (comStat.cbInQue != 0) //缓冲区的个数 { if (ReadFile(m_hCom, recvBuffer, MAX_RECV_BUFFER, &dwBytesRead, NULL)) { recvCb(recvBuffer, dwBytesRead); //响应回调函数 PurgeComm(m_hCom, PURGE_RXCLEAR); //清缓冲区 } } } } };
使用步骤如下:
1、在工作的头文件中引用头文件申明, #include "SerialPortClass.h"
2、定义类对象指针SerialPortClass *pSerialPortCls;
3、写一个接收函数,当串口初始化并且打开后,该函数会只想响应的。
4、贴出使用代码,哈哈,本例程后续本人还将维护,有问题的可以直接留言给我。
void RecvHwb(char *str, DWORD l) { for (int i = 0; i < l; i++) { _cprintf("%d = %02x\r\n", i, str[i]); } } void CControlCardDlg::OnBnClickedButton2() { // TODO: 在此添加控件通知处理程序代码 char t[] = {0x00, 0x01, 0x00, 0x70, 0x50}; PortSettings settings = {115200, 0, 0, 8}; pSerialPortCls = new SerialPortClass(); pSerialPortCls->SetPortName(L"COM4"); pSerialPortCls->SetPortSetttings(settings); if (pSerialPortCls->Open(RecvHwb)) { DWORD writed = pSerialPortCls->Write(t, sizeof(t)); _cprintf("write byte = %d\r\n", writed); } }
时间: 2024-10-21 19:17:32