MODBUS串口通讯连接池

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、

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Modbus Poll是一个主机仿真器,用来帮助开发人员测试Modbus从设备,或者其它MOdbus协议的测试和仿真。它支持多文档接口,即,可以同时监视多个从设备/数据域。每个窗口简单地设定从设备ID,功能,地址,大小和轮询间隔。你可以从任意一个窗口读写寄存器和线圈。如果你想改变一个单独的寄存器,简单地双击这个值即可。或者你可以改变多个寄存器/线圈值。提供数据的多种格式方式,比如浮点、双精度、长整型(可以字节序列交换)。 状态条显示错误信息。 如果你是一个从设备开发者,你可以通过"test center" 菜单,组织并发送你自己的测试字符串,并以16进制形式检查从设备返回的结果。 为VB,EXCEL等提供了OLE自动化接口。可以用OLE接口解析和显示Modbus数据,然后送达你指定的设备,即,在EXCEL中编辑数据,然后发送到你的从设备!示例参看安装后的Excel example.xls。 支持下列协议: Modbus RTU Modbus ASCII Modbus TCP/IP Modbus RTU Over TCP/IP Modbus ASCII Over TCP/IP Modbus UDP/IP Modbus RTU Over UDP/IP Modbus ASCII Over UDP/IP MODBUS POLL功能: OLE自动化可以简单地与Visual Basic接口,使用起来类似于ActiveX控件。参见 VBExample.vbp 读/写多达125个寄存器 读/写多达2000个输入/线圈 Test Center菜单 (组织你自己的测试字串) 打印和打印预览 监视串行数据流量serial data traffic Data logging to a text file Data logging direct to Excel 上下文敏感的HLP文件 10 Display formats such as float, double etc. Adjustable Address Base (0 or 1). 字体和颜色选项 广播功能(从设备ID=0) Easy control of RS-485 converters with RTS toggle. 支持MODBUS功能: 01: Read coil status 读线圈状态 02: Read input status 读输入状态 03: Read holding register读保持寄存器 04: Read input registers 读输入寄存器 05: Force single coil 强制单线圈 06: Preset single register 预置单寄存器 15: Force multiple coils 强制多线圈 16: Preset multiple registers 预置多寄存器 17: Report slave ID 报告从设备ID 22: Mask write register 屏蔽写寄存器 23: Read/Write registers 读/写寄存器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值