串口在工业应用是极为普遍的,我用API封装了同步和异步的串口类,以及一个具有监视线程的异步串口类;使用简单高效,具有工业强度,我在BC, BCB, VC, BCBX, GCC下编译通过,相信足够应付大多数情况,而且还可以继承扩展,下面简单介绍使用方法, 后附源代码(_com.h);
库的层次结构:
_base_com:虚基类,基本接口,可自行扩展自己的串口类
_sync_com:_base_com 的子类, 同步应用,适合简单应用
_asyn_com:_base_com 的子类, 异步应用(重叠I/O),适合较高效应用,NT平台
_thread_com:_asyn_com 的子类, 异步应用,监视线程,适合较复杂应用,窗口通知消息和继承扩展的使用方式;
几个问题:
结束线程
如何从WaitCommEvent(pcom->_com_handle, &mask, &pcom->_wait_o)这个API退出以便顺利结束线程:
方案1:
SetCommMask(_com_handle, 0); 这个方法在MSDN有载,当在一些情况下并不完全有效,原因未知;
方案2:
SetEvent(_wait_o.hEvent); 直接激活重叠IO结构中的事件句柄,绝对有效; 这份代码我两种都用;
打开10以上的COM端口
在NT/2000下打开编号10以上端口用
_com_handle = CreateFile(
“COM10“,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //重叠I/O
NULL
);
将提示错误, 这样就OK:
_com_handle = CreateFile(
“\\\\.\\COM10“,//对应的就是\\.\COM10
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //重叠I/O
NULL
);
线程中循环的低效率问题
使用SetCommMask(pcom->_com_handle, EV_RXCHAR | EV_ERR)监视接受字符和错误消息;一旦有个字符来就会激活WaitCommEvent 通常作以下接受操作:
if(!WaitCommEvent(pcom->_com_handle, &mask, &pcom->_wait_o))
{
if(GetLastError() == ERROR_IO_PENDING)
{
GetOverlappedResult(pcom->_com_handle, &pcom->_wait_o, &length, true);
}
}
if(mask & EV_ERR) // == EV_ERR
ClearCommError(pcom->_com_handle, &error, &stat);
if(mask & EV_RXCHAR) // == EV_RXCHAR
{
pcom->on_receive();//接收到字符
//或发送到窗口消息
}
这样频繁的函数调用或接受发送消息,效率低下,我添加扫描缓冲区的代码,当字符数超过设定的字符数才作接受字符的操作;
if(mask & EV_RXCHAR) // == EV_RXCHAR
{
ClearCommError(pcom->_com_handle, &error, &stat);
if(stat.cbInQue > pcom->_notify_num) //_notify_num 是设定得字符数
pcom->on_receive();
}
类似于流的输出方式
我编了一个简单的写串口的方式,可以类似于流将简单的数据类型输出
template<typename T>
_asyn_com& operator << (T x)
{
strstream s;
s << x ;
write(s.str(), s.pcount());
return *this;
}
就可以这样使用
_sync_com com1;
com1.open(1, 9600);
com1 << “ then random() 's return value is “<< rand() << “ .\n“ ;
com1.close();
本串口类库的主要接口
class _base_com
{
bool open(int port);
bool open(int port, int baud_rate);
bool open(int port, char * set_str); // set_str : “9600, 8, n, 1“
bool set_state(int BaudRate, int ByteSize = 8, int Parity = NOPARITY, int StopBits = ONESTOPBIT)
//设置内置结构串口参数:波特率,停止位
bool set_state(char *set_str)
bool is_open();
HANDLE get_handle();
virtual bool open_port()=0; //继承用的重要函数
virtual close();
}
class _sync_com :public _base_com //同步
{
int read(char *buf, int buf_size); //自动补上'\0',将用去一个字符的缓冲区
int write(char *buf, int len);
int write(char *buf);
}
class _asyn_com :public _base_com //异步
{
int read(char *buf, int buf_size); //自动补上'\0',将用去一个字符的缓冲区
int write(char *buf, int len);
int write(char *buf);
}
class _thread_com :public _asyn_com //线程
{
virtual void on_receive() //供线程接受到字符时调用, 可继承替换之
{
if(_notify_hwnd)
PostMessage(_notify_hwnd, ON_COM_RECEIVE, WPARAM(_port), LPARAM(0));
else
{
if(_func)
_func(_port);
}
}
void set_hwnd(HWND hwnd); //设置窗口句柄, 发送 ON_COM_RECEIVE WM_USER + 618
void set_func(void (*f)(int)); //设置调用函数 ,窗口句柄优先
void set_notify_num(int num); //设定发送通知, 接受字符最小值
}
一些应用范例
当然首先 #include "_com.h"
一、打开串口1同步写
char str[] = "com_class test";
_sync_com com1; //同步
com1.open(1); // 相当于 com1.open(1, 9600); com1.open(1, "9600,8,n,1");
for(int i=0; i<100; i++)
{
Sleep(500);
com1.write(str); //也可以 com1.write(str, strlen(str));
}
com1.close();
二、打开串口2异步读
char str[100];
_asyn_com com2; //异步
com2.open(2); // 相当于 com2.open(2, 9600); com2.open(2, "9600,8,n,1");
if(!com2.is_open())
cout << "COM2 not open , error : " << GetLastError() << endl;
/*
也可以如下用法
if(!com2.open(2))
cout << "COM2 not open , error : " << GetLastError() << endl;
*/
for(int i=0; i<100; i++)
{
Sleep(500);
if(com2.read(str, 100) > 0) //异步读,返回读取字符数
cout << str;
}
com2.close();
三、扩展应用具有监视线程的串口类
class _com_ex : public thread_com
{
public:
virtual on_receive()
{
char str[100];
if(read(str, 100) > 0) //异步读,返回读取字符数
cout << str;
}
};
int main(int argc, char *argv[])
{
try
{
char str[100];
_com_ex com2; //异步扩展
com2.open(2);
Sleep(10000);
com2.close();
}
catch(exception &e)
{
cout << e.what() << endl;
}
return 0;
}
四、桌面应用可发送消息到指定窗口(在C++ Builder 和 VC ++ 测试通过)
VC ++
接受消息
BEGIN_MESSAGE_MAP(ComDlg, CDialog)
//{{AFX_MSG_MAP(ComDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_DESTROY()
//}}AFX_MSG_MAP
ON_MESSAGE(ON_COM_RECEIVE, On_Receive)
END_MESSAGE_MAP()
打开串口,传递窗口句柄
_thread_com com2;
com2.open(2);
com2.set_hwnd(ComDlg->m_hWnd);
处理消息
LRESULT ComDlg::On_Receive(WPARAM wp, LPARAM lp)
{
char str[100];
com2.read(str, 100);
char com_str[10];
strcpy(com_str, "COM");
ltoa((long)wp, com_str + 3, 10); // WPARAM 保存端口号
MessageBox(str, com_str, MB_OK);
return 0;
}
C++ Builder
class TForm1 : public TForm
{
__published: // IDE-managed Components
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall FormCreate(TObject *Sender);
private: // User declarations
public: // User declarations
void On_Receive(TMessage& Message);
__fastcall TForm1(TComponent* Owner);
_thread_com com2;
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(ON_COM_RECEIVE, TMessage, On_Receive)
END_MESSAGE_MAP(TForm)
};
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
com2.close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
com2.open(2);
com2.set_hwnd(Handle);
}
//---------------------------------------------------------------------------
void TForm1::On_Receive(TMessage& Message)
{
char xx[20];
int port = Message.WParam;
if(com2.read(xx, 20) > 0)
ShowMessage(xx);
}
错误和缺陷在所难免,欢迎来信批评指正;
附完整源代码 _com.h
/*
串口基础类库(WIN32) ver 0.1
编译器 : BC++ 5; C++ BUILDER 4, 5, 6, X; VC++ 5, 6; VC.NET; GCC;
class _base_com : 虚基类 基本串口接口;
class _sync_com : 同步I/O 串口类;
class _asyn_com : 异步I/O 串口类;
class _thread_com : 异步I/O 辅助读监视线程 可转发窗口消息 串口类(可继承虚函数on_receive用于读操作);
class _com : _thread_com 同名
copyright(c) 2004.8 */
/*
Example :
*/
#ifndef _COM_H_
#define _COM_H_
#pragma warning(disable: 4530)
#pragma warning(disable: 4786)
#pragma warning(disable: 4800)
#include <cassert>
#include <strstream>
#include <algorithm>
#include <exception>
#include <iomanip>
using namespace std;
#include <windows.h>
class _base_com //虚基类 基本串口接口
{
protected:
volatile int _port; //串口号
volatile HANDLE _com_handle;//串口句柄
char _com_str[20];
DCB _dcb; //波特率,停止位,等
COMMTIMEOUTS _co; // 超时时间
virtual bool open_port() = 0;
void init() //初始化
{
memset(_com_str, 0, 20);
memset(&_co, 0, sizeof(_co));
memset(&_dcb, 0, sizeof(_dcb));
_dcb.DCBlength = sizeof(_dcb);
_com_handle = INVALID_HANDLE_VALUE;
}
virtual bool setup_port()
{
if(!is_open())
return false;
if(!SetupComm(_com_handle, 8192, 8192))
return false; //设置推荐缓冲区
if(!GetCommTimeouts(_com_handle, &_co))
return false;
_co.ReadIntervalTimeout = 0xFFFFFFFF;
_co.ReadTotalTimeoutMultiplier = 0;
_co.ReadTotalTimeoutConstant = 0;
_co.WriteTotalTimeoutMultiplier = 0;
_co.WriteTotalTimeoutConstant = 2000;
if(!SetCommTimeouts(_com_handle, &_co))
return false; //设置超时时间
if(!PurgeComm(_com_handle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ))
return false; //清空串口缓冲区
return true;
}
inline void set_com_port(int port)
{
char p[12];
_port = port;
strcpy(_com_str, "\\\\.\\COM");
ltoa(_port, p, 10);
strcat(_com_str, p);
}
public:
_base_com()
{
init();
}
virtual ~_base_com()
{
close();
}
//设置串口参数:波特率,停止位,等 支持设置字符串 "9600, 8, n, 1"
bool set_state(char *set_str)
{
if(is_open())
{
if(!GetCommState(_com_handle, &_dcb))
return false;
if(!BuildCommDCB(set_str, &_dcb))
return false;
return SetCommState(_com_handle, &_dcb) == TRUE;
}
return false;
}
//设置内置结构串口参数:波特率,停止位
bool set_state(int BaudRate, int ByteSize = 8, int Parity = NOPARITY, int StopBits = ONESTOPBIT)
{
if(is_open())
{
if(!GetCommState(_com_handle, &_dcb))
return false;
_dcb.BaudRate = BaudRate;
_dcb.ByteSize = ByteSize;
_dcb.Parity = Parity;
_dcb.StopBits = StopBits;
return SetCommState(_com_handle, &_dcb) == TRUE;
}
return false;
}
//打开串口 缺省 9600, 8, n, 1
inline bool open(int port)
{
return open(port, 9600);
}
//打开串口 缺省 baud_rate, 8, n, 1
inline bool open(int port, int baud_rate)
{
if(port < 1 || port > 1024)
return false;
set_com_port(port);
if(!open_port())
return false;
if(!setup_port())
return false;
return set_state(baud_rate);
}
//打开串口
inline bool open(int port, char *set_str)
{
if(port < 1 || port > 1024)
return false;
set_com_port(port);
if(!open_port())
return false;
if(!setup_port())
return false;
return set_state(set_str);
}
inline bool set_buf(int in, int out)
{
return is_open() ? SetupComm(_com_handle, in, out) : false;
}
//关闭串口
inline virtual void close()
{
if(is_open())
{
CloseHandle(_com_handle);
_com_handle = INVALID_HANDLE_VALUE;
}
}
//判断串口是或打开
inline bool is_open()
{
return _com_handle != INVALID_HANDLE_VALUE;
}
//获得串口句炳
HANDLE get_handle()
{
return _com_handle;
}
operator HANDLE()
{
return _com_handle;
}
};
class _sync_com : public _base_com
{
protected:
//打开串口
virtual bool open_port()
{
if(is_open())
close();
_com_handle = CreateFile(
_com_str,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL ,
NULL
);
assert(is_open());
return is_open();//检测串口是否成功打开
}
public:
_sync_com()
{
}
//同步读
int read(char *buf, int buf_len)
{
if(!is_open())
return 0;
buf[0] = '\0';
COMSTAT stat;
DWORD error;
if(ClearCommError(_com_handle, &error, &stat) && error > 0) //清除错误
{
PurgeComm(_com_handle, PURGE_RXABORT | PURGE_RXCLEAR); /*清除输入缓冲区*/
return 0;
}
unsigned long r_len = 0;
buf_len = min(buf_len - 1, (int)stat.cbInQue);
if(!ReadFile(_com_handle, buf, buf_len, &r_len, NULL))
r_len = 0;
buf[r_len] = '\0';
return r_len;
}
//同步写
int write(char *buf, int buf_len)
{
if(!is_open() || !buf)
return 0;
DWORD error;
if(ClearCommError(_com_handle, &error, NULL) && error > 0) //清除错误
PurgeComm(_com_handle, PURGE_TXABORT | PURGE_TXCLEAR);
unsigned long w_len = 0;
if(!WriteFile(_com_handle, buf, buf_len, &w_len, NULL))
w_len = 0;
return w_len;
}
//同步写
inline int write(char *buf)
{
assert(buf);
return write(buf, strlen(buf));
}
//同步写, 支持部分类型的流输出
template<typename T>
_sync_com& operator << (T x)
{
strstream s;
s << x;
write(s.str(), s.pcount());
return *this;
}
};
class _asyn_com : public _base_com
{
protected:
OVERLAPPED _ro, _wo; // 重叠I/O
virtual bool open_port()
{
if(is_open())
close();
_com_handle = CreateFile(
_com_str,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //重叠I/O
NULL
);
assert(is_open());
return is_open();//检测串口是否成功打开
}
public:
_asyn_com()
{
memset(&_ro, 0, sizeof(_ro));
memset(&_wo, 0, sizeof(_wo));
_ro.hEvent = CreateEvent(NULL, true, false, NULL);
assert(_ro.hEvent != INVALID_HANDLE_VALUE);
_wo.hEvent = CreateEvent(NULL, true, false, NULL);
assert(_wo.hEvent != INVALID_HANDLE_VALUE);
}
virtual ~_asyn_com()
{
close();
if(_ro.hEvent != INVALID_HANDLE_VALUE)
CloseHandle(_ro.hEvent);
if(_wo.hEvent != INVALID_HANDLE_VALUE)
CloseHandle(_wo.hEvent);
}
//异步读
int read(char *buf, int buf_len, int time_wait = 20)
{
if(!is_open())
return 0;
buf[0] = '\0';
COMSTAT stat;
DWORD error;
if(ClearCommError(_com_handle, &error, &stat) && error > 0) //清除错误
{
PurgeComm(_com_handle, PURGE_RXABORT | PURGE_RXCLEAR); /*清除输入缓冲区*/
return 0;
}
if(!stat.cbInQue)// 缓冲区无数据
return 0;
unsigned long r_len = 0;
buf_len = min((int)(buf_len - 1), (int)stat.cbInQue);
if(!ReadFile(_com_handle, buf, buf_len, &r_len, &_ro)) //2000 下 ReadFile 始终返回 True
{
if(GetLastError() == ERROR_IO_PENDING) // 结束异步I/O
{
//WaitForSingleObject(_ro.hEvent, time_wait); //等待20ms
if(!GetOverlappedResult(_com_handle, &_ro, &r_len, false))
{
if(GetLastError() != ERROR_IO_INCOMPLETE)//其他错误
r_len = 0;
}
}
else
r_len = 0;
}
buf[r_len] = '\0';
return r_len;
}
//异步写
int write(char *buf, int buf_len)
{
if(!is_open())
return 0;
DWORD error;
if(ClearCommError(_com_handle, &error, NULL) && error > 0) //清除错误
PurgeComm(_com_handle, PURGE_TXABORT | PURGE_TXCLEAR);
unsigned long w_len = 0, o_len = 0;
if(!WriteFile(_com_handle, buf, buf_len, &w_len, &_wo))
if(GetLastError() != ERROR_IO_PENDING)
w_len = 0;
return w_len;
}
//异步写
inline int write(char *buf)
{
assert(buf);
return write(buf, strlen(buf));
}
//异步写, 支持部分类型的流输出
template<typename T>
_asyn_com& operator << (T x)
{
strstream s;
s << x ;
write(s.str(), s.pcount());
return *this;
}
};
//当接受到数据送到窗口的消息
#define ON_COM_RECEIVE WM_USER + 618 // WPARAM 端口号
class _thread_com : public _asyn_com
{
protected:
volatile HANDLE _thread_handle; //辅助线程
volatile HWND _notify_hwnd; // 通知窗口
volatile long _notify_num;//接受多少字节(>_notify_num)发送通知消息
volatile bool _run_flag; //线程运行循环标志
void (*_func)(int port);
OVERLAPPED _wait_o; //WaitCommEvent use
//线程收到消息自动调用, 如窗口句柄有效, 送出消息, 包含窗口编号
virtual void on_receive()
{
if(_notify_hwnd)
PostMessage(_notify_hwnd, ON_COM_RECEIVE, WPARAM(_port), LPARAM(0));
else
{
if(_func)
_func(_port);
}
}
//打开串口,同时打开监视线程
virtual bool open_port()
{
if(_asyn_com::open_port())
{
_run_flag = true;
DWORD id;
_thread_handle = CreateThread(NULL, 0, com_thread, this, 0, &id); //辅助线程
assert(_thread_handle);
if(!_thread_handle)
{
CloseHandle(_com_handle);
_com_handle = INVALID_HANDLE_VALUE;
}
else
return true;
}
return false;
}
public:
_thread_com()
{
_notify_num = 0;
_notify_hwnd = NULL;
_thread_handle = NULL;
_func = NULL;
memset(&_wait_o, 0, sizeof(_wait_o));
_wait_o.hEvent = CreateEvent(NULL, true, false, NULL);
assert(_wait_o.hEvent != INVALID_HANDLE_VALUE);
}
~_thread_com()
{
close();
if(_wait_o.hEvent != INVALID_HANDLE_VALUE)
CloseHandle(_wait_o.hEvent);
}
//设定发送通知, 接受字符最小值
void set_notify_num(int num)
{
_notify_num = num;
}
int get_notify_num()
{
return _notify_num;
}
//送消息的窗口句柄
inline void set_hwnd(HWND hWnd)
{
_notify_hwnd = hWnd;
}
inline HWND get_hwnd()
{
return _notify_hwnd;
}
inline void set_func(void (*f)(int))
{
_func = f;
}
//关闭线程及串口
virtual void close()
{
if(is_open())
{
_run_flag = false;
SetCommMask(_com_handle, 0);
SetEvent(_wait_o.hEvent);
if(WaitForSingleObject(_thread_handle, 100) != WAIT_OBJECT_0)
TerminateThread(_thread_handle, 0);
CloseHandle(_com_handle);
CloseHandle(_thread_handle);
_thread_handle = NULL;
_com_handle = INVALID_HANDLE_VALUE;
ResetEvent(_wait_o.hEvent);
}
}
/*辅助线程控制*/
//获得线程句柄
HANDLE get_thread()
{
return _thread_handle;
}
//暂停监视线程
bool suspend()
{
return _thread_handle != NULL ? SuspendThread(_thread_handle) != 0xFFFFFFFF : false;
}
//恢复监视线程
bool resume()
{
return _thread_handle != NULL ? ResumeThread(_thread_handle) != 0xFFFFFFFF : false;
}
//重建监视线程
bool restart()
{
if(_thread_handle) /*只有已有存在线程时*/
{
_run_flag = false;
SetCommMask(_com_handle, 0);
SetEvent(_wait_o.hEvent);
if(WaitForSingleObject(_thread_handle, 100) != WAIT_OBJECT_0)
TerminateThread(_thread_handle, 0);
CloseHandle(_thread_handle);
_run_flag = true;
_thread_handle = NULL;
DWORD id;
_thread_handle = CreateThread(NULL, 0, com_thread, this, 0, &id);
return (_thread_handle != NULL); //辅助线程
}
return false;
}
private:
//监视线程
static DWORD WINAPI com_thread(LPVOID para)
{
_thread_com *pcom = (_thread_com *)para;
if(!SetCommMask(pcom->_com_handle, EV_RXCHAR | EV_ERR))
return 0;
COMSTAT stat;
DWORD error;
for(DWORD length, mask = 0; pcom->_run_flag && pcom->is_open(); mask = 0)
{
if(!WaitCommEvent(pcom->_com_handle, &mask, &pcom->_wait_o))
{
if(GetLastError() == ERROR_IO_PENDING)
{
GetOverlappedResult(pcom->_com_handle, &pcom->_wait_o, &length, true);
}
}
if(mask & EV_ERR) // == EV_ERR
ClearCommError(pcom->_com_handle, &error, &stat);
if(mask & EV_RXCHAR) // == EV_RXCHAR
{
ClearCommError(pcom->_com_handle, &error, &stat);
if(stat.cbInQue > pcom->_notify_num)
pcom->on_receive();
}
}
return 0;
}
};
typedef _thread_com _com; //名称简化
#endif //_COM_H_