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、