上章封装了usb hid通讯类,本章讲来封装串口通讯类,采用的是同步机制。本工程是基于vs2010 mfc写的,工程名CommExample.
新建CommClass.h头文件,里面代码:
#pragma once
#include "stdafx.h"
#include
#include
#define DEFAULT_BAUDRATE 115200
#define DEFAULT_PARITY 0
#define DEFAULT_STOPBITS 0
#define DEFAULT_BYTESIZE 8
#define MAX_RECV_BUFFER 256
#define MAX_SEND_BUFFER 256
#define COMM_RECV WM_USER + 2 //自定义的消息
typedef struct
{
DWORD baudRate;
BYTE parity;
BYTE stopBits;
BYTE bytesize;
}PortSettings;
class CommClass
{
private:
HWND curHwnd;
HANDLE comm;
HANDLE thread;
HANDLE hEvent;
PortSettings curPortSetting;
UINT8 curPort;
UINT8 buf[MAX_RECV_BUFFER];
public:
/**
构造函数主要是初始化一些变量
*/
CommClass()
{
comm = INVALID_HANDLE_VALUE;
hEvent = NULL;
curPortSetting.baudRate = DEFAULT_BAUDRATE;
curPortSetting.parity = DEFAULT_PARITY;
curPortSetting.bytesize = DEFAULT_BYTESIZE;
curPortSetting.stopBits = DEFAULT_STOPBITS;
}
/**
析构函数主要是判断接受进程是否打开,如果已打开,则关闭接受线程
*/
~CommClass()
{
if (hEvent)
{
SetEvent(hEvent);
Sleep(50);
}
}
/**
设置串口波特率等参数,如果串口没打开,则保存这些参数,下次打开的时候再按保存的参数进行设置
*/
BOOL SetPortSetting(PortSettings *p)
{
curPortSetting = *p;
if (comm)
{
DCB dcb;
GetCommState(comm, &dcb);
dcb.BaudRate = curPortSetting.baudRate;
dcb.ByteSize = curPortSetting.bytesize;
dcb.Parity = curPortSetting.parity;
dcb.StopBits = curPortSetting.stopBits;
if (!SetCommState(comm, &dcb))
{
return FALSE;
}
}
return TRUE;
}
/**
接受线程,当串口打开这开始监视是否有数据到达,把收到的数据以消息的方式发送出去
*/
static DWORD WINAPI RecvThread(LPVOID para)
{
CommClass *p = (CommClass *)para;
DWORD dwBytesRead, dwErrorFlags;
COMSTAT comStat;
while (WaitForSingleObject(p->hEvent, 5) != WAIT_OBJECT_0)
{
if (p->comm)
{
ClearCommError(p->comm, &dwErrorFlags, &comStat);
if (comStat.cbInQue)
{
if (ReadFile(p->comm, p->buf, MAX_RECV_BUFFER, &dwBytesRead, NULL))
{
PostMessage(p->curHwnd, COMM_RECV, (WPARAM)p->buf, dwBytesRead);
}
}
}
}
if (p->comm)
{
CloseHandle(p->comm);
p->comm = INVALID_HANDLE_VALUE;
}
return 0;
}
/**
打开串口
hwnd是窗口句柄,port是需要打开的端口号,如果超过9的话,需要特殊处理,tmp变量进行了一些特殊处理,
这个地方是模拟modbus通讯,如果接受的某个字节后,过了某个间隔时间,仍没收到字符,则ReadFile就直接
返回。
*/
BOOL Open(HWND hwnd, UINT8 port)
{
CString curPortName;
if (port > 9)
{
curPortName.Format(_T("\\\\.\\COM%d"), port);
}
else
{
curPortName.Format(_T("COM%d"), port);
}
comm = CreateFile(curPortName, //端口号
GENERIC_READ | GENERIC_WRITE, //权限可读写
0,
NULL,
OPEN_EXISTING, //打开存在的端口
NULL, //以同步方式打开
NULL);
if (comm == INVALID_HANDLE_VALUE)
{
return FALSE;
}
if (!SetPortSetting(&curPortSetting)) //设置波特率等参数
{
return FALSE;
}
double tmp = 1 + curPortSetting.bytesize;
tmp += curPortSetting.bytesize;
if (curPortSetting.parity > 0) //0:无校验 1:奇 2:偶 3:标记校验
{
tmp += 1;
}
if (curPortSetting.stopBits == 0) //0:1个停止位 1:1.5个停止位 2:2两个停止位
{
tmp += 1;
}
else if (curPortSetting.stopBits == 1)
{
tmp += 1.5;
}
else
{
tmp += 2; //其实5000这个值应该是3500,可是效果有点不理想,通讯10次,有2~3接受有点丢数据,
} //所以设置大点通过反复测试,不存在丢失数据现象,很理想。(这一点结合modbus协议看看)
tmp = (tmp * (double)5000) / (double)(curPortSetting.baudRate);//超时,有点像modbus协议
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = (DWORD)ceil(tmp);//ceil是向上取整,所以需要家math.h头文件
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
if(!SetCommTimeouts(comm, &timeouts))
{
return false;
}
curHwnd = hwnd;
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //创建时间,手动,无信号状态
SetupComm(comm, MAX_RECV_BUFFER, MAX_SEND_BUFFER); //设置缓冲区大小
PurgeComm(comm, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
thread = CreateThread(NULL, 0, RecvThread, this, 0, 0); //创建接受线程
CloseHandle(thread);
return TRUE;
}
/**
往打开的串口中写入数据
*/
DWORD Write(UINT8 *data, UINT32 len)
{
DWORD dwBytesWrite = 0;
if (comm)
{
WriteFile(comm, data, len, &dwBytesWrite, NULL);
}
return dwBytesWrite;
}
/**
如果串口已打开,则关闭打开的串口并且结束接受线程
*/
void Close()
{
SetEvent(hEvent);
Sleep(50);
}
/**
判断串口是否打开
*/
BOOL IsOpen()
{
if (comm)
{
return TRUE;
}
return FALSE;
}
};
为了调试方便,在stdafx.h文件的末尾添加了
#define MY_DEBUG 1
#if MY_DEBUG
#pragma comment( linker, "/subsystem:console /entry:wWinMainCRTStartup" )
#endif
界面对话框的布局及样式如下:
这上面新建了三个按钮:Open,Close,Send,它们对应的ID号在Resource.h文件中
#define IDC_BUTTON_OPEN 1000
#define IDC_BUTTON_CLOSE 1001
#define IDC_BUTTON_SEND 1002
CommExampleDlg.h文件里面内容如下:
// CommExmapleDlg.h : 头文件
//
#pragma once
#include "CommClass.h"
// CCommExmapleDlg 对话框
class CCommExmapleDlg : public CDialogEx
{
// 构造
public:
CCommExmapleDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_COMMEXMAPLE_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
CommClass commCls;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedButtonOpen(); //打开按钮的响应
afx_msg void OnBnClickedButtonClose();
LRESULT OnRecvAct(WPARAM wParam, LPARAM lParam);
afx_msg void OnBnClickedButtonSend();
};
CommExampleDlg.cpp文件内容如下
// CommExmapleDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "CommExmaple.h"
#include "CommExmapleDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CCommExmapleDlg 对话框
CCommExmapleDlg::CCommExmapleDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CCommExmapleDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CCommExmapleDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CCommExmapleDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(COMM_RECV, &CCommExmapleDlg::OnRecvAct) //消息绑定,就是用来处理usb收到的数据
ON_BN_CLICKED(IDC_BUTTON_OPEN, &CCommExmapleDlg::OnBnClickedButtonOpen)
ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CCommExmapleDlg::OnBnClickedButtonClose)
ON_BN_CLICKED(IDC_BUTTON_SEND, &CCommExmapleDlg::OnBnClickedButtonSend)
END_MESSAGE_MAP()
// CCommExmapleDlg 消息处理程序
BOOL CCommExmapleDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(true); //初始化的时候按钮状态
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(false);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CCommExmapleDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CCommExmapleDlg::OnQueryDragIcon()
{
return static_cast(m_hIcon);
}
void CCommExmapleDlg::OnBnClickedButtonOpen()
{
// TODO: 在此添加控件通知处理程序代码
PortSettings settings = {115200, 0, 0, 8};
commCls.SetPortSetting(&settings);
if (commCls.Open(this->m_hWnd, 3))
{
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(true);
}
}
/**
本函数就是串口接受线程发消息过来响应的
*/
LRESULT CCommExmapleDlg::OnRecvAct(WPARAM wParam, LPARAM lParam)
{
UINT8 *str = (UINT8 *)wParam;
int len = (int)lParam;
printf("***Comm Recv\r\n");
for (int i = 0; i < len; i++)
{
printf("%d=%02x\r\n", i, str[i]);
}
return 0;
}
void CCommExmapleDlg::OnBnClickedButtonClose()
{
// TODO: 在此添加控件通知处理程序代码
if (commCls.IsOpen())
{
commCls.Close();
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(true);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(false);
}
}
void CCommExmapleDlg::OnBnClickedButtonSend()
{
// TODO: 在此添加控件通知处理程序代码
if (commCls.IsOpen())
{
UINT8 str[] = {0xa5, 0x02, 0x00, 0xff, 0x00 ,0xff};
if (commCls.Write(str, 6))
{
printf("###Comm Send\r\n");
for (int i = 0; i < 6; i++)
{
printf("%d=%02x\r\n", i, str[i]);
}
}
}
}
程序运行界面如下:
程序代码:CommExample.zip