MODBUS串口通信连接池 在工业控制应用中,在设备仪器管理理中,在一些自动控制中,串口通信仍被广泛用于实现面板控制和上位机控制管理。然而,由于通信的不稳定性和通信的冲突,开发人员对程序的异常情况和不稳定性直接影响程序的质量感到非常苦恼。 众所周知,串口通信是最原始的通信方式,不支持多用户访问模式或共享服务设备。因此,它容易发生冲突,不能平行处理。因此,在编程中,许多人使用临界锁(EnterCriticalSection)虽然技术有所改善,但并没有从根本上彻底解决串口通信的不稳定性问题,只是降低了出错概率。 串口通信的不稳定性是使用串口通信技术编程的一个主要问题。在此,我提出了串口通信连接池的处理方法,可以从根本上解决串口通信带来的各种问题,消除串口通信影响程序质量的问题。 串口通信连接池的提法可能是我提出的第一个解决方案。我希望你能理解。
1、 基本原理 串口通信连接池的基本理念是建立一个排队处理通信任务,避免串口通信冲突,解决更直接的临界区锁,更有效的串口通信隔离处理。
2、 基本框架
3、 定义通信数据结构
typedef struct _SERIAL_PORT_COMM {
unsigned char comm[8]; unsigned char result[1024]; int length; } pSerialPort;
4、 定义事件
#define WM_SERIAL_PORT_EVENT WM_USER 100
5、 异步串口通信 1) RS232.h文件
#ifndef _RS232_H_ #define _RS232_H_ #pragma once class RS232 {
public: RS232(); ~RS232(); public: HANDLE m_hCom; OVERLAPPED m_ovWrite;// 用于写入数据 OVERLAPPED m_ovRead; // 读取数据 OVERLAPPED m_ovWait; // 等待数据 public: bool OpenCOM(char* portNo, DWORD baud, char parity, UINT databits, UINT stopsbits, DWORD dwCommEvents); // 打开串口. void CloseCOM(); // 关闭串口 bool Flush(); // 读写缓冲区. bool WriteData(unsigned char* buff, int &len); // 写数据. bool ReadData(unsigned char* receive, int &len); // 读数据.
};
#endif /* _RS232_H_ */
2) RS232.cpp文件
// RS232异步通讯程序.
//
#include "pch.h"
#include "RS232.h"
RS232::RS232()
{
m_hCom = INVALID_HANDLE_VALUE;
m_ovWrite = {
0};// 用于写入数据
m_ovRead = {
0}; // 用于读取数据
m_ovWait = {
0}; // 用于等待数据
}
RS232::~RS232()
{
CloseCOM();
}
// 配置串口
bool RS232::OpenCOM(char* portNo, DWORD baud, char parity, UINT databits, UINT stopsbits, DWORD dwCommEvents)
{
TRACE("RS232 interface..................%s\n", portNo);
TRACE("baud %d\n", baud);
TRACE("parity %d\n", parity);
TRACE("data bits %d\n", databits);
TRACE("stop bits %d\n", stopsbits);
m_hCom = CreateFile(CString(portNo), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,//设置异步标识
NULL);
if (INVALID_HANDLE_VALUE == m_hCom)
{
TRACE("Create file failed.\n");
return FALSE;
}
if (!SetupComm(m_hCom, 2048, 2048)) //设置发送接收缓存
{
TRACE("Setup Comm failed.\n");
CloseHandle(m_hCom);
return FALSE;
}
DCB dcb; // 基本定义参数为96 n 8 1模式
GetCommState(m_hCom, &dcb);
dcb.DCBlength = sizeof(dcb);
dcb.BaudRate = baud; // 波特率
dcb.StopBits = stopsbits; // ONESTOPBIT;//停止位数为1位
dcb.Parity = parity; // 校验方式为无校验
dcb.ByteSize = databits; // 数据位为8位
if (!SetCommState(m_hCom, &dcb)) // 配置串口
{
TRACE("Setup comm state failed.\n");
CloseHandle(m_hCom);
return FALSE;
}
PurgeComm(m_hCom, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT); // 清缓冲区.
COMMTIMEOUTS ct;
ct.ReadIntervalTimeout = MAXDWORD; // 两字符之间最大的延时,读取无延时,因为有WaitCommEvent等待数据
ct.ReadTotalTimeoutConstant = 0; // 读时间常量
ct.ReadTotalTimeoutMultiplier = 0; // 读时间系数
ct.WriteTotalTimeoutMultiplier = 500; // 写时间系数
ct.WriteTotalTimeoutConstant = 5000; // 写时间常量
if (!SetCommTimeouts(m_hCom, &ct)) // 配置读写超时
{
TRACE("Set comm timeout failed.\n");
CloseHandle(m_hCom);
return FALSE;
}
// 创建事件对象
m_ovRead.hEvent = CreateEvent(NULL, false, false, NULL);
m_ovWrite.hEvent = CreateEvent(NULL, false, false, NULL);
m_ovWait.hEvent = CreateEvent(NULL, false, false, NULL);
if (!SetCommMask(m_hCom, EV_ERR | EV_RXCHAR)) // 设置接受事件
{
TRACE("Set comm mask failed.\n");
CloseHandle(m_hCom);
return FALSE;
}
TRACE("Connect successed................%s\n", portNo);
return TRUE;
}
void RS232::CloseCOM()
{
TRACE("COM disconnect.\n");
if (INVALID_HANDLE_VALUE != m_hCom)
{
CloseHandle(m_hCom);
m_hCom = INVALID_HANDLE_VALUE;
}
if (NULL != m_ovRead.hEvent)
{
CloseHandle(m_ovRead.hEvent);
m_ovRead.hEvent = NULL;
}
if (NULL != m_ovWrite.hEvent)
{
CloseHandle(m_ovWrite.hEvent);
m_ovWrite.hEvent = NULL;
}
if (NULL != m_ovWait.hEvent)
{
CloseHandle(m_ovWait.hEvent);
m_ovWait.hEvent = NULL;
}
}
bool RS232::Flush()
{
DWORD dwFlag = PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR;
if (PurgeComm(m_hCom, dwFlag))
return TRUE;
else
return FALSE;
}
bool RS232::WriteData(unsigned char* buff, int &len)
{
BOOL rtn = FALSE;
DWORD WriteSize = 0;
//TRACE("Send data.......................\n");
PurgeComm(m_hCom, PURGE_TXCLEAR | PURGE_TXABORT);
m_ovWait.Offset = 0;
rtn = WriteFile(m_hCom, buff, len, &WriteSize, &m_ovWrite);
len = 0;
if (FALSE == rtn && GetLastError() == ERROR_IO_PENDING) // 后台读取
{
// 等待数据发送完成
if (FALSE == ::GetOverlappedResult(m_hCom, &m_ovWrite, &WriteSize, TRUE))
{
return FALSE;
}
}
len = WriteSize;
//TRACE("Send data complete..............%d\n", len);
return TRUE;
}
bool RS232::ReadData(unsigned char *receive, int &len)
{
DWORD WaitEvent = 0;
m_ovWait.Offset = 0;
BOOL Status = FALSE;
DWORD Error;
DWORD Bytes = 0;
COMSTAT cs = {
0 };
TRACE("Receive data....................\n");
Status = WaitCommEvent(m_hCom, &WaitEvent, &m_ovWait);
// WaitCommEvent也是一个异步命令,所以需要等待
if (FALSE == Status && GetLastError() == ERROR_IO_PENDING)//
{
// 如果缓存中无数据线程会停在此,如果hCom关闭会立即返回False
Status = GetOverlappedResult(m_hCom, &m_ovWait, &Bytes, TRUE);
}
ClearCommError(m_hCom, &Error, &cs);
if (TRUE == Status // 等待事件成功
&& WaitEvent & EV_RXCHAR // 缓存中有数据到达
&& cs.cbInQue > 0) // 有数据
{
Bytes = 0;
m_ovRead.Offset = 0;
//memset(receive, 0x00, sizeof(receive));
// 数据已经到达缓存区,ReadFile不会当成异步命令,而是立即读取并返回True
Status = ReadFile(m_hCom, receive, len, &Bytes, &m_ovRead);
if (!Status)
{
DWORD dwError = GetLastError(); // 获取错误码,可以根据该错误码查出错误原因
PurgeComm(m_hCom, PURGE_RXCLEAR | PURGE_RXABORT);// 清空串口缓冲区
return FALSE;
}
}
TRACE("Receive complete................%d\n", Bytes);
return TRUE;
}
6、 串口通讯连接池 1) SerialPortPool.h文件
#ifndef _SERIAL_PORT_POOL_H_
#define _SERIAL_PORT_POOL_H_
#include "RS232.h"
#pragma once
class SerialPortPool
{
public:
SerialPortPool(CDialogEx* pan);
~SerialPortPool();
public:
RS232* pSerial;
vector<pSerialPort> comm;
int m_th;
CDialogEx* panel;
public:
bool OpenSerialPort(char* portNo, DWORD baud, char parity, UINT databits, UINT stopsbits, DWORD dwCommEvents);
void CloseSerialPort();
void SendCommand(unsigned char* cmd, int len);
};
#endif /* _SERIAL_PORT_POOL_H_ */
2) SerialPortPool.cpp文件
#include "pch.h"
#include "SerialPortPool.h"
DWORD WINAPI PoolThread(LPVOID lpParameter);
SerialPortPool::SerialPortPool(CDialogEx *pan)
{
panel = pan;
}
SerialPortPool::~SerialPortPool()
{
}
bool SerialPortPool::OpenSerialPort(char* portNo, DWORD baud, char parity, UINT databits, UINT stopsbits, DWORD dwCommEvents)
{
pSerial = new RS232();
bool res = pSerial->OpenCOM(portNo, baud, parity, databits, stopsbits, dwCommEvents);
m_th = true;
HANDLE Handle_PL = CreateThread(NULL, 0, PoolThread, (void*)this, 0, 0);
if (Handle_PL != NULL) CloseHandle(Handle_PL);
return res;
}
void SerialPortPool::CloseSerialPort()
{
m_th = false;
if (pSerial != NULL) {
pSerial->CloseCOM();
delete pSerial;
}
pSerial = NULL;
}
void SerialPortPool::SendCommand(unsigned char* cmd, int len)
{
pSerialPort comm1;
comm1.command[0] = cmd[0];
comm1.command[1] = cmd[1];
comm1.command[2] = cmd[2];
comm1.command[3] = cmd[3];
comm1.command[4] = cmd[4];
comm1.command[5] = cmd[5];
comm1.command[6] = cmd[6];
comm1.command[7] = cmd[7];
comm1.length = len;
comm.push_back(comm1);
}
DWORD WINAPI PoolThread(LPVOID lpParameter)
{
SerialPortPool* _this = (SerialPortPool*)lpParameter;
int len;
while (_this->m_th)
{
//TRACE("Command number.................%d\n", _this->comm.size());
if (_this->comm.size() > 0)
{
pSerialPort* comm1 = new pSerialPort();
memset(comm1, 0x00, sizeof(pSerialPort));
memcpy(comm1->command, _this->comm.at(0).command, sizeof(comm1->command));
comm1->length = _this->comm.at(0).length;
len = 8;
_this->pSerial->WriteData(comm1->command, len);
Sleep(800);
_this->pSerial->ReadData(comm1->result, comm1->length);
for (int i = 0; i < comm1->length; i ++)
TRACE("%02x\n", comm1->result[i]);
vector<pSerialPort>::iterator vi = _this->comm.begin();
_this->comm.erase(vi);
if ((comm1->result[0] == 0x08) && (comm1->result[1] == 0x03))
_this->panel->SendMessage(WM_SERIAL_PORT_EVENT, (WPARAM)comm1, NULL);
}
Sleep(300);
}
return TRUE;
}
7、