vc 串口通讯类封装

上章封装了usb hid通讯类,本章讲来封装串口通讯类,采用的是同步机制。本工程是基于vs2010 mfc写的,工程名CommExample.

新建CommClass.h头文件,里面代码:

#pragma once
#include "stdafx.h"
#include<Windows.h>
#include <math.h>
#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<WPARAM>(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<HCURSOR>(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



  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值