因工作的需要,采用了基于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);
}
}
原文地址:http://blog.csdn.net/hwb_1988/article/details/46342423