本节介绍
在工业控制中,工控机(一般都基于Windows平台)经常需要与智能仪表通过串口进行通信。串口通信方便易行,应用广泛。
一般情况下,工控机和各智能仪表通过RS485总线进行通信。RS485的通信方式是半双工的,只能由作为主节点的工控PC机依次轮询网络.上的各智能控制单元子节点。每次通信都是由PC机通过串口向智能控制单元发布命令,智能控制单元在接收到正确的命令后作出应答。
在Win32下,可以使用两种编程方式实现串口通信,其一是使用ActiveX控件,这种方法程序单,但欠灵活。其二是调用Windows的API函数,这种方法可以清楚地掌握串口通信的机制,并且自由灵活。本文我们只介绍API串口通信部分。
接下来我们来实现一个串口调试助手!
ADO访问数据库
➢1、 安装串口虚拟软件
➢2、 打开串口
➢3、设置串口属性(波特率、奇偶校验等)
➢4、 读写串口
➢5、 校验(求和校验、CRC校验 )
➢6、 通信协议
建立工程
项目是在visual studio2005编译器内创建,点击“文件”->“新建”->“项目”
按照图标提示选择,点击下一步。
单击下一步,下一步,完成。
点击完成后:
接下来开始构建项目:
编辑如图所示的窗口:
在WinDemoDlg.h中声明初始化函数
public:
void InitComboBox();
在WinDemoDlg.cpp中实现改函数:
void CWinDemoDlg::InitComboBox(){
CComboBox* pComboComm=(CComboBox*)GetDlgItem(IDC_COMBO_COMM);
ASSERT(pComboComm);
for(int i=1;i<=8;i++){
CString strComm;
strComm.Format(_T("COM%d"),i);
pComboComm->AddString(strComm);
}
pComboComm->SetCurSel(0);
//波特率
CComboBox* pComboBaudrate=(CComboBox*)GetDlgItem(IDC_COMBO_BAUDRATE);
ASSERT(pComboBaudrate);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("300")),300);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("600")),600);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("1200")),1200);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("2400")),2400);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("4800")),4800);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("9600")),9600);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("19200")),19200);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("43000")),43000);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("56000")),56000);
pComboBaudrate->SetItemData(pComboBaudrate->AddString(_T("1152000")),1152000);
pComboBaudrate->SetCurSel(5);
//校验位
CComboBox* pComboCheckBit=(CComboBox*)GetDlgItem(IDC_COMBO_CHECKBIT);
ASSERT(pComboCheckBit);
pComboCheckBit->SetItemData(pComboCheckBit->AddString(_T("无None")),NOPARITY);
pComboCheckBit->SetItemData(pComboCheckBit->AddString(_T("奇ODD")),ODDPARITY);
//pComboCheckBit->SetItemData(pComboCheckBit->AddString(_T("偶EUEN")),EUENPARITY);
pComboCheckBit->SetCurSel(0);
//数据位
CComboBox* pComboDateBit=(CComboBox*)GetDlgItem(IDC_COMBO_DATABIT);
ASSERT(pComboDateBit);
pComboDateBit->SetItemData(pComboDateBit->AddString(_T("6")),6);
pComboDateBit->SetItemData(pComboDateBit->AddString(_T("7")),7);
pComboDateBit->SetItemData(pComboDateBit->AddString(_T("8")),8);
pComboDateBit->SetCurSel(0);
//停止位
CComboBox* pComboStopBit=(CComboBox*)GetDlgItem(IDC_COMBO_STOPBIT);
ASSERT(pComboStopBit);
pComboStopBit->SetItemData(pComboStopBit->AddString(_T("1")),ONESTOPBIT);
pComboStopBit->SetItemData(pComboStopBit->AddString(_T("2")),TWOSTOPBITS);
pComboStopBit->SetCurSel(0);
}
并将初始化函数放入OnInitDialog()函数中:
添加一个串口类,这个串口类用来实现具体的串口通信:
点击“项目”->“添加类”
添加一般的C++类即可
将类名设置为:CSerialPort
其中CSerialPort.h代码如下:
#pragma once
class CSerialPort
{
public:
CSerialPort(void);
public:
~CSerialPort(void);
public:
BOOL OpenComm(CString strComm);
BOOL SetCommState(DWORD dwBaudrate,BYTE byParity,BYTE byByteSize,BYTE byStopBits);
BOOL SetupComm(DWORD dwInQueue,DWORD dwOutQueue);
BOOL PurgeComm(DWORD dwFlags);
BOOL SetCommMask(DWORD dwEvtMask);
BOOL WriteFile(IN LPCVOID lpBuffer,IN DWORD nNumberOfBytesToWrite,OUT LPDWORD lpNumberOfBytesWritten,IN LPOVERLAPPED lpOverlapped);
BOOL ReadFile(OUT LPVOID lpBuffer,IN DWORD nNumberOfBytesToRead,OUT LPDWORD lpNumberOfBytesRead,IN LPOVERLAPPED lpOverlapped);
BOOL ClearCommError(OUT LPDWORD lpErrors,OUT LPCOMSTAT lpStat);
BOOL GetOverlappedResult(IN LPOVERLAPPED lpoverlapped,OUT LPDWORD lpNumberOfByterTransferred,IN BOOL bWait);
void CloseComm(); //关闭窗口
public:
HANDLE m_hComm;
};
CSerialPort.cpp代码如下:
#include "StdAfx.h"
#include "SerialPort.h"
CSerialPort::CSerialPort(void)
{
m_hComm=NULL;
}
CSerialPort::~CSerialPort(void)
{
}
BOOL CSerialPort::OpenComm(CString strComm){
if(NULL==m_hComm){
m_hComm=CreateFile((TCHAR*)(LPCTSTR)strComm,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,0);
if(INVALID_HANDLE_VALUE==m_hComm){
int nError=GetLastError();
m_hComm=NULL;
return FALSE;
}
return TRUE;
}
return FALSE;
}
BOOL CSerialPort::SetCommState(DWORD dwBaudrate,BYTE byParity,BYTE byByteSize,BYTE byStopBits){
if(NULL==m_hComm) return FALSE;
DCB dcb;
BOOL bRet= ::GetCommState(m_hComm,&dcb);
if(!bRet){
if(m_hComm){
//CloseHandle(m_hComm);
m_hComm=NULL;
}
return FALSE;
}
dcb.BaudRate=dwBaudrate;
dcb.ByteSize=byByteSize;
dcb.Parity=byParity;
dcb.StopBits=byStopBits;
bRet=::SetCommState(m_hComm,&dcb);
if(!bRet){
if(m_hComm){
CloseHandle(m_hComm);
m_hComm=NULL;
}
return FALSE;
}
return TRUE;
}
BOOL CSerialPort::SetupComm(DWORD dwInQueue,DWORD dwOutQueue){
if(NULL==m_hComm) return FALSE;
return ::SetupComm(m_hComm,dwInQueue,dwOutQueue);
}
BOOL CSerialPort::PurgeComm(DWORD dwFlags){
if(NULL==m_hComm) return FALSE;
return ::PurgeComm(m_hComm,dwFlags);
}
BOOL CSerialPort::SetCommMask(DWORD dwEvtMask){
if(NULL==m_hComm) return FALSE;
return ::SetCommMask(m_hComm,dwEvtMask);
}
BOOL CSerialPort::WriteFile(IN LPCVOID lpBuffer,IN DWORD nNumberOfBytesToWrite,OUT LPDWORD lpNumberOfBytesWritten,IN LPOVERLAPPED lpOverlappe)
{
if(NULL==m_hComm) return FALSE;
return ::WriteFile(m_hComm,lpBuffer,nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlappe);
}
BOOL CSerialPort::ReadFile(OUT LPVOID lpBuffer,IN DWORD nNumberOfBytesToRead,OUT LPDWORD lpNumberOfBytesRead,IN LPOVERLAPPED lpOverlapped)
{
if(NULL==m_hComm) return FALSE;
return ::ReadFile(m_hComm,lpBuffer,nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
}
BOOL CSerialPort::ClearCommError(OUT LPDWORD lpErrors,OUT LPCOMSTAT lpStat)
{
if(NULL==m_hComm) return FALSE;
return ::ClearCommError(m_hComm,lpErrors,lpStat);
}
BOOL CSerialPort::GetOverlappedResult(IN LPOVERLAPPED lpoverlapped,OUT LPDWORD lpNumberOfByterTransferred,IN BOOL bWait)
{
if(NULL==m_hComm) return FALSE;
return ::GetOverlappedResult(m_hComm,lpoverlapped,lpNumberOfByterTransferred,bWait);
}
//关闭窗口
void CSerialPort::CloseComm(){
if(m_hComm){
CloseHandle(m_hComm);
m_hComm=NULL;
}
}
这时就封装好了一个串口类,一旦串口开始工作的时候,需要一个线程,用这个线程来进行收发数据;线程运行时通过ReadFile()函数从串口中读出数据,如果有数据需要将数据放到接受框中显示出来。
下面创建一个线程,一个串口对应一个线程对象,创建C++类,类名为:CThread。
CThread.h代码:
#pragma once
class CThread
{
public:
CThread(void);
public:
~CThread(void);
public:
void Start();
void Stop();
public:
virtual void SetThreadData(DWORD dwParam);
virtual DWORD GetThreadData();
public:
virtual void run();
public:
static DWORD ThreadProc(LPVOID pParam);
public:
HANDLE m_hThread;
bool m_bExit;
DWORD m_dwParam;
};
CThread.cpp代码:
#include "StdAfx.h"
#include "Thread.h"
CThread::CThread(void)
{
m_bExit=FALSE;
m_dwParam=0;
m_hThread=NULL;
}
CThread::~CThread(void)
{
if(!m_bExit){
Stop();
}
}
void CThread::Start()
{
DWORD dwThreadID; //获取的线程ID
HANDLE hThread=::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,this,0,&dwThreadID);
ASSERT(hThread);
m_hThread=hThread;
}
DWORD CThread::ThreadProc(LPVOID pParam)
{
CThread* pThis=(CThread*)pParam;
ASSERT(pThis);
while(!pThis->m_bExit)
{
pThis->run();
}
return TRUE;
}
void CThread::Stop()
{
if(m_hThread)
{
m_bExit=true;
::WaitForSingleObject(m_hThread,INFINITE);
::CloseHandle(m_hThread);
m_hThread=NULL;
}
}
void CThread::run()
{
Sleep(100);
}
void CThread::SetThreadData(DWORD dwParam)
{
if(m_dwParam!=dwParam)
{
m_dwParam=dwParam;
}
}
DWORD CThread::GetThreadData()
{
return m_dwParam;
}
接下来需要从这个线程派生出一个基类,创建C++类,类名为:CThreadComm; CThreadComm用来处理串口数据的收发线程。
CThreadComm.h代码:
CThreadComm.cpp代码: