基于对话框的API串口软件开发
一、项目需求
应用API函数实现一个基于对话框的串口软件,要求软件启动后显示软件界面,在界面中能选择串口、波特率、校验位、数据位、停止位等串口参数,能打开串口和关闭串口,能输入发送数据,发送数据,显示接收数据,显示接收字节数的累计值,显示发送字节数的累计值。
一、软件开发过程
串口界面效果图
API串口通讯软件的5个模块:
(1) 串口初始化程序:设置串口参数,设置超时,Overlapped变量,清理串口,启动串口监测线程;
(2) 串口监测线程序;
(3) 接收数据程序:读取接收缓冲区数据;
(4) 发送数据程序:发送数据写入缓冲区;
(5) 关闭串口程序:关闭串口,停止和撤销串口监测线程;
1、串口初始化程序:
/*******串口参数控件初始化*********/
m_ctrlPort.SetCurSel(0);
m_ctrlBaud.SetCurSel(3);
m_ctrlParity.SetCurSel(1);
m_ctrlDataBits.SetCurSel(0);
m_ctrlStopBits.SetCurSel(0);
m_nBaud = 9600;
m_nComPort = 1;
m_cParity = 'N';
m_nDataBits = 8;
m_nStopBits = 0;
OpenPortStatus = FALSE;//串口处于关闭状态
m_ctrlClosePort.EnableWindow(FALSE);//“关闭串口”按钮无效
RX_Count = 0;//接收数据字节计数器置0
TX_Count = 0;//发送数据字节计数器置0
打开串口程序:
int errorCode;//错误码
CString str;
m_nComPort = m_ctrlPort.GetCurSel() + 1;//获得串口号
m_nBaud = m_ctrlBaud.GetCurSel();//获得波特率列表序号
switch (m_nBaud)//根据波特率列表序号求得波特率
{
case 0: m_nBaud = 115200;
break;
case 1: m_nBaud = 1200;
break;
case 2: m_nBaud = 19200;
break;
case 3: m_nBaud = 9600;
}
m_cParity = m_ctrlParity.GetCurSel();//获得校验位列表序号
switch (m_cParity)//根据校验位列表序号求得校验位字符
{
case 0: m_cParity = 'E';
break;
case 1: m_cParity = 'N';
break;
case 2: m_cParity = 'O';
}
m_nDataBits = m_ctrlDataBits.GetCurSel();//获得数据位列表序号
switch (m_nDataBits)//根据数据位列表序号求得数据位字符
{
case 0: m_nDataBits = 6;
break;
case 1: m_nDataBits = 7;
break;
case 2: m_nDataBits = 8;
}
m_nStopBits = m_ctrlStopBits.GetCurSel();//获得停止位列表序号
if (!OpenPortStatus)//串口未打开
{
errorCode = InitialComPort(m_nComPort, m_hCom);//调用串口初始化函数
if (errorCode)//串口初始化未成功,则显示信息和错误码,并返回
{
str.Format("%d", errorCode);
AfxMessageBox("不能打开串口\r\nerrorCode" + str);
return;
}
m_ctrlOpenPort.EnableWindow(FALSE);//置“打开串口”按钮无效
m_ctrlClosePort.EnableWindow(TRUE);//置“关闭串口”按钮有效
OpenPortStatus = TRUE;//串口处于打开状态
m_ctrlPort.EnableWindow(FALSE);//置串口组合框控件无效
m_ctrlBaud.EnableWindow(FALSE);//置波特率组合框控件无效
m_ctrlParity.EnableWindow(FALSE);//置校验位组合框控件无效
m_ctrlDataBits.EnableWindow(FALSE);//置数据位组合框控件无效
m_ctrlStopBits.EnableWindow(FALSE);//置停止位组合框控件无效
}
初始化串口函数的主要由以下几个部分构成:
CreateFile//创建串口句柄
SetCommTimeouts//设置串口超时
SetupComm//设置串口缓冲区
SetCommMask//设置串口事件允许字
GetCommState//得到串口DCB
SetCommState//设置串口DCB
CreateEvent//初始化Overlapped变量
PurgeComm//清理串口
AfxBeginThread//启动串口监测线程
相应的代码为:
int CAPICOMTestDlg::InitialComPort(int m_nPort, HANDLE hCom)
{
BOOL b_setOK;
CString szPort;
szPort.Format("COM%d", m_nComPort);//获得串口标识符
/***********创建串口句柄,即打开串口*************/
hCom = CreateFile(szPort, //打开的文件名为szPort
GENERIC_READ | GENERIC_WRITE, //文件的操作属性为可读可写
0, //文件的共享属性为不共享
NULL, //文件的安全属性为NULL
OPEN_EXISTING,//文件必须已经存在,由设备提出
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //默认属性,允许对文件进行重叠操作
NULL);
if (hCom == INVALID_HANDLE_VALUE)//创建串口句柄失败
{
AfxMessageBox("CreateFile失败(串口不存在或已被占用)" + szPort);
return 1;
}
/*设置超时*/
COMMTIMEOUTS TimeOuts;//定义超时结构变量
TimeOuts.ReadIntervalTimeout = MAXDWORD;//设置读操作间隔超时数据
TimeOuts.ReadTotalTimeoutMultiplier = 0;//设置读操作超时系数
TimeOuts.ReadTotalTimeoutConstant = 0;//设置读操作超时常数
TimeOuts.WriteTotalTimeoutMultiplier = 50;//设置写操作超时系数
TimeOuts.WriteTotalTimeoutConstant = 2000;//设置写操作超时常数
b_setOK = SetCommTimeouts(hCom, &TimeOuts);//串口超时设置
if (!b_setOK)
{
AfxMessageBox("SetCommTimeouts失败");
return 2;
}
/*设置缓冲区*/
b_setOK = SetupComm(hCom, 8192, 4096);//接收缓冲区为8192个字节,发送缓冲区为4096个字节,仅指定推荐的大小
//实际的接收缓冲区为8192字节,发送缓冲区0字节(不受限制),系统用推荐的缓冲区大小来优化性能和避免缓冲区超限
if (!b_setOK)
{
AfxMessageBox("SetupComm失败");
return 3;
}
/*设置串口事件允许字*/
b_setOK = SetCommMask(hCom, EV_RXCHAR);//允许产生接收数据事件消息
if (!b_setOK)
{
AfxMessageBox("SetCommMask失败");
return 4;
}
/*设置串口参数(设置DCB)*/
DCB m_dcb;
b_setOK = GetCommState(hCom, &m_dcb);//得到串口的DCB,并存入m_dcb
if (!b_setOK)
return 5;
//开始设置应用层DCB
m_dcb.fBinary = TRUE;//通信数据模式为二进制
m_dcb.BaudRate = m_nBaud;//设置波特率
m_dcb.ByteSize = m_nDataBits;//设置数据位
m_dcb.fParity = TRUE;//设置允许奇偶校验
switch (m_cParity)//设置校验位
{
case 'N':
m_dcb.Parity = NOPARITY;
break;
case 'E':
m_dcb.Parity = EVENPARITY;
break;
case 'O':
m_dcb.Parity = ODDPARITY;
break;
default:;
}
switch (m_nStopBits)//设置停止位
{
case 0:
m_dcb.StopBits = ONESTOPBIT;
break;
case 1:
m_dcb.StopBits = ONE5STOPBITS;
break;
case 2:
m_dcb.StopBits = TWOSTOPBITS;
break;
default:;
}
//结束应用层DCB的设置
b_setOK = SetCommState(hCom, &m_dcb);//设置串口的DCB,即将应用层的DCB写入串口的DCB
if (!b_setOK)
{
AfxMessageBox("SetCommState失败");
return 6;
}
/*初始化(串口事件监测,读结束数据操作,写接收数据操作)OVERLAPPED*/
memset(&m_ovCom, 0, sizeof(OVERLAPPED));
m_ovCom.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL);
memset(&m_ovread, 0, sizeof(OVERLAPPED));
m_ovread.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
memset(&m_ovwrite, 0, sizeof(OVERLAPPED));
m_ovwrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
/*清理通信端口*/
PurgeComm(hCom, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
/*启动串口监测线程*/
m_hCom = hCom;//将句柄赋给m_hCom
m_pOwner = this->GetSafeHwnd();//获得当前窗口句柄
if (!(p_ComThread = AfxBeginThread(CommThread, this)))
return 7;
return 0;
}
2、串口监测线程的开发
主要由以下几个部分组成:
WaitCommEvent//等待串口事件函数
GetLastError()//继续查明前面的overlapped方式函数的执行情况
GetOverlappedResult//获得前面overlapped方式函数执行的最终结果
::PostMessage//发出消息响应
CloseHandle//关闭串口
AfxEndThread//停止并撤销线程
相应的代码为:
UINT CAPICOMTestDlg::CommThread(LPVOID pParam)
{
BOOL bResult = TRUE;
BOOL ExitThread = FALSE;
DWORD dwComEventMask;//存储所发生的串口事件掩码
DWORD dwCommOvTrans;
DWORD dwError = 0;
DWORD length = 0;
CAPICOMTestDlg* pDlg = (CAPICOMTestDlg*) pParam;//将LPVOID类型转换为本软件的类型,
//以获得本软件对话框类的指针且类型与本软件定义相同
while (1)//进行无线循环,对串口事件进行监测
{
dwComEventMask = 0;
bResult = WaitCommEvent(pDlg->m_hCom, &dwComEventMask, &pDlg->m_ovCom);//等待串口事件函数
if (!bResult)
{
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING://没有串口事件发生
GetOverlappedResult(pDlg->m_hCom, &pDlg->m_ovCom, &dwCommOvTrans, TRUE);//等待串口事件
if (dwComEventMask && EV_RXCHAR)//是否有串口事件发生
{
if (!ExitThread)
{
::PostMessage(pDlg->m_pOwner, WM_COMM_RXCHAR, EV_RXCHAR, length);//发出WM_COMM_RXCHAR消息
ExitThread = TRUE;
}
else
{
ExitThread = FALSE;
}
}
else
{
if (CloseStatus)//是否点击了“关闭串口”按钮
{
CloseStatus = FALSE;
CloseHandle(pDlg->m_hCom);//关闭串口
pDlg->m_hCom = NULL;
AfxEndThread(Thread_exitCode);//停止并撤销线程
}
}
break;
case 87:
break;
default:
AfxMessageBox("WaitCommEvent执行出错");
break;
}
}
}
return 0;
}
3、接收数据程序
ClearCommError
ReadFile//从串口缓冲区读取数据,并将读得的数据存入lpBuffer数组中
相应的代码为:
BOOL CAPICOMTestDlg::ReadCommData(HANDLE hCommPort, char* readBuffer, DWORD* readlength)
{
DWORD dwErrorFlags;
DWORD charlength = 0;
ClearCommError(m_hCom, &dwErrorFlags, &ComStat);
charlength = ComStat.cbInQue;
if (charlength == 0)
return FALSE;
ReadFile(hCommPort, readBuffer, charlength, readlength, &m_ovread);
return TRUE;
}
4、发送数据程序
WriteFile//将lpBuffer数组中的数据写入串口发送缓冲区,由此实现串口发送数据
相应的代码为:
void CAPICOMTestDlg::OnButtonSend()
{
// TODO: Add your control notification handler code here
if (!OpenPortStatus)
{
AfxMessageBox("串口未打开");
return;
}
else
{
m_ctrlSendData.GetWindowText(m_strSendData);//获得发送数据
DWORD len = m_strSendData.GetLength();
for (int i=0; (DWORD)i<len; i++)
{
writeBuff[i] = m_strSendData[i];//CString型转换为字节数组
}
writelength = sendCommDataBlock(m_hCom, writeBuff, len);//发送数据块函数
TX_Count += len;
CString strTemp;
strTemp.Format("TX:%d", TX_Count);
m_ctrlTXCount.SetWindowText(strTemp);
}
}
DWORD CAPICOMTestDlg::sendCommDataBlock(HANDLE hCommPort, char* writeBuffer, DWORD writelength)
{
BOOL fwritestate;
DWORD realwrite_length;
fwritestate = WriteFile(m_hCom, writeBuffer, writelength, &realwrite_length, &m_ovwrite);
//writeBuffer中数据写入串口缓冲区,所写长度由writelength决定
return realwrite_length;
}
5、关闭串口程序
GetExitCodeThread
SetCommMask
SetEvent
实现步骤:先获得串口监测线程的退出码;屏蔽所以串口事件;强行产生串口Overlapped事件;串口监测线程响应这个强行发送的Overlapped事件,并执行关闭串口和撤销线程的API函数。
相应的代码为:
void CAPICOMTestDlg::OnButtonClosePort()
{
// TODO: Add your control notification handler code here
BOOL ClosPortFlag;
if (ClosPortFlag = ClosePort(m_hCom))
{
OpenPortStatus = FALSE;
m_ctrlClosePort.EnableWindow(FALSE);
m_ctrlOpenPort.EnableWindow(TRUE);
m_ctrlPort.EnableWindow(TRUE);
m_ctrlBaud.EnableWindow(TRUE);
m_ctrlParity.EnableWindow(TRUE);
m_ctrlDataBits.EnableWindow(TRUE);
m_ctrlStopBits.EnableWindow(TRUE);
}
}
BOOL CAPICOMTestDlg::ClosePort(HANDLE hCommPort)
{
if (!OpenPortStatus)
return TRUE;
if (!GetExitCodeThread(p_ComThread->m_hThread, &Thread_exitCode))//获得退出码
{
AfxMessageBox("获得线程退出码错误");
return FALSE;
}
p_DuThread = p_ComThread;//保存线程对象到一个全局型对象中
CloseStatus = TRUE;
SetCommMask(hCommPort, 0);//禁止串口的所以事件
return TRUE;
}