前言
为了实现串口号的自动连接,本人自写了一个串口通信程序,希望对在写串口的您有所帮助。
一、查找函数 CreateFile。
在串口通信中一般设备都是手动输入串口号实现串口通信的,为了能够实现串口号的自动输入,这里采用CreateFile函数来实现:
m_hCom = CreateFile(comStr, GENERIC_READ |
GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL);
这里的CreateFile函数起了很大的作用,可以用来创建系统设备文件,如果该设备不存在或者被占用,则会返回一个错误,然后对比 INVALID_HANDLE_VALUE ,据此可以判断可使用性。详细参见MSDN中的介绍。
m_hCom = CreateFile(comStr, GENERIC_READ |
GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL); // 这里的CreateFile函数起了很大的作用,可以用来创建系统设备文件,如果该设备不存在或者被占用,则会返回一个错误
//下面的 INVALID_HANDLE_VALUE ,据此可以判断可使用性。
if (m_hCom != INVALID_HANDLE_VALUE) // 如果没有该设备,或者被其他应用程序在用
{
return FALSE;
}
else
{
SetCommMask(m_hCom, 0);// 关闭文件句柄,后面我们采用控件,不用API
CloseHandle(m_hCom);
}
最后,把检测到的数据记录下来就可以实现串口号的查找了。
二、 串口号的范围:
在Visual Studio的Microsoft Communications Control工具中,使用WIN32 API 打开当COM号大于10的时候,会出现打开错误或失败,一般解决的办法是人工修改USB 串口的com的名称让它的com号是单位数(如: COM1~COM9),但这样较麻烦。而且在设备接入大量串口设备时,没办法解决此问题。
产生这种奇怪现象的原因是:微软预定义的标准设备中含有“COM1”-“COM9”。所以,“COM1”-“COM9”作为文件名传递给函数时操作系统会自动地将之解析为相应的设备。但对于COM10及以上的串口,“COM10”之类的文件名系统只视之为一般意义上的文件,而非串行设备。
为了增加对COM10及以上串行端口的支持,微软规定,如果要访问这样的设备,应使用这样的文件名(以COM10为例):\\.\COM10。所以,串口赋值代码如下:
if (ComPortNum < 10)
{
comStr.Format("COM%d", ComPortNum );
}
else
{
comStr.Format("\\\\.\\COM%d", ComPortNum );//大于10就要改变输入方式,否则找不到
}
然而,原有的MSCOMM32.OCX控件只能输入串口号小于16的串口,当串口号大于16时就会报错。找到的可用的破解方法:
1、在c:\windows\system32中找到MSCOMM32.OCX;
2、备份之(为安全起见);
3、使用HEXEDIT反汇编工具,打开它;
4、找到 "66 3D 10 00 "(这是文件中唯一一处) 将其修改为 "66 3D FF 00";
5、保存该文件即可实现支持256个串口。以下为更改后的mscomm32.ocx资源链接,支持COM1~COM255:https://download.csdn.net/download/wjy27/88333486
三、串口接收中断:
在实际的串口项目中串口接收我们一般都是通过串口中断OnOncommMycom()函数来实现的:
ON_EVENT(CBCFZSYSMFCDlg, IDC_MYCOM, 1, CBCFZSYSMFCDlg :: OnOncommMycom , VTS_NONE);
然而在OnOncommMycom()函数中,我们可能要运行一些程序,例如再利用串口发送一些回应数据等,这样就会使得串口中断在前一次中断没有执行完之前被再次触发,这样造成中断程序的崩溃,为了避免这个错误,我们在进入中断时,把put_RThreshold置0,串口中断设置为不触发。在中断执行完成后,再给put_RThreshold赋值。这样一个稳定的串口中断接收程序就写好了。
void CBCFZSYSMFCDlg::OnOncommMycom()
{
// TODO: 在此处添加消息处理程序代码
m_mscomm.put_RThreshold(0);//不触发中断
... ...
... ...
... ...
m_mscomm.put_RThreshold(1);
UpdateData(FALSE);//更新编辑框的内容
}
四、函数的实现:
首先,要编写一个查找函数 iGetCom() 来实现存在可用串口号的查找:
/*查找串口号,FindComNum=0,从COM 1~16 搜索,非0时,判断其是否存在*/
BOOL CMcomDlg::iGetCom(int FindComNum)
{
CString comStr;
int iTmp;
int jTmp;
//m_com = 0;
int kTmp = 0;
HANDLE m_hCom;
if (FindComNum == 0) // 如果串口存在,则执行相应的初始化(采用控件)
{
jTmp = 1;
//iTmp = 16;
comPortStr.Empty();//清除
for (iTmp = 0; iTmp < 16; iTmp++)
ComNum[iTmp] = 0;
for (; jTmp <= iTmp; jTmp++)
{
if (jTmp < 10)
{
comStr.Format(_T("COM%d"), jTmp);
}
else
{
comStr.Format(_T("\\\\.\\COM%d"), jTmp);//大于10就要改变输入方式,否则找不到
}
m_hCom = CreateFile(comStr, GENERIC_READ |
GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL); // 这里的CreateFile函数起了很大的作用,可以用来创建系统设备文件,如果该设备不存在或者被占用,则会返回一个错误,
//即下面的 INVALID_HANDLE_VALUE ,据此可以判断可使用性。详细参见MSDN中的介绍。
if (m_hCom != INVALID_HANDLE_VALUE) // 如果没有该设备,或者被其他应用程序在用
{
/*查找可用串口*/
comStr.Format(_T("COM%d"), jTmp);
comPortStr += comStr; //全局记录字符串,edit用于显示
comPortStr += " ";
m_combobox1.AddString(comStr); //写入combobox
ComNum[kTmp++] = jTmp;
if (kTmp % 8 == 0)
comPortStr += "\r\n ";
}
SetCommMask(m_hCom, 0);// 关闭文件句柄,后面我们采用控件,不用API
CloseHandle(m_hCom);
}
if (jTmp)
return TRUE;
else
return FALSE;
}
else
{
if (FindComNum < 10)
{
comStr.Format(_T("COM%d"), FindComNum);
}
else
{
comStr.Format(_T("\\\\.\\COM%d"), FindComNum);//大于10就要改变输入方式,否则找不到
}
m_hCom = CreateFile(comStr, GENERIC_READ |
GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL); // 这里的CreateFile函数起了很大的作用,可以用来创建系统设备文件,如果该设备不存在或者被占用,则会返回一个错误,
//即下面的 INVALID_HANDLE_VALUE ,据此可以判断可使用性。详细参见MSDN中的介绍。
if (m_hCom != INVALID_HANDLE_VALUE) // 如果没有该设备,或者被其他应用程序在用
{
return FALSE;
}
else
{
SetCommMask(m_hCom, 0);// 关闭文件句柄,后面我们采用控件,不用API
CloseHandle(m_hCom);
}
}
return TRUE;
}
其次,在找到可用串口号后,我们必须逐个测试哪个串口是连接我们设备的,这里就要求上位机与串口通信设备间存在应答的程序,这样在开启串口后,我们就可以通过发送测试数据,来获取回应,如果回应的数据是对的则判断此串口为我们需要连接的设备。
流程图如下:
具体代码实现:
void CMcomDlg::OnBnClickedBtnSend()
{
// TODO: 在此添加控件通知处理程序代码
iGetCom(0);//查找串口
iNum = 0;
SetTimer(1, 800, NULL);//开启定时器1 ,定时逐个判断存在串口的可用性
}
void CMcomDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nIDEvent)//nIDEvent 为定时器事件ID,1,2,3
{
case 1:
if (ComNum[iNum])
{
SetComPort(ComNum[iNum]);
iNum++;
CString str;
GetDlgItem(IDC_EDIT1)->GetWindowText(str);
if (m_radioascii.GetCheck())
{
//SendMscommData(str);
m_com.put_Output(COleVariant(str));//发送数据
}
else
{
m_com.put_Output(HexM2OleVariant(str));
}
}
else
{
iNum = 0;
KillTimer(1);//关闭定时器
if (m_com.get_PortOpen()) //释放串口
{
m_com.put_PortOpen(FALSE);
}
}
break;
}
CDialogEx::OnTimer(nIDEvent);
}
void CMcomDlg::SetComPort(int ComPort )
{
if (m_com.get_PortOpen()) // m_ctrlMscom是控件的一个实例
{
m_com.put_PortOpen(FALSE);
}
CString cstr;
CString ComSetStr;
cstr.Format(_T("COM%d"), ComPort);
m_combobox1.SelectString(0, cstr);
m_baud.GetLBText(m_baud.GetCurSel(), cstr);
ComSetStr += cstr + ",";
m_crcbit.GetLBText(m_crcbit.GetCurSel(), cstr);
ComSetStr += (CString)cstr.GetAt(0) + ",";
m_databit.GetLBText(m_databit.GetCurSel(), cstr);
ComSetStr += cstr + ",";
m_stopbit.GetLBText(m_stopbit.GetCurSel(), cstr);
ComSetStr += cstr;
m_com.put_CommPort(ComPort);//写串口值
m_com.put_InBufferSize(1024); // 设置输入缓冲区的大小,Bytes
m_com.put_OutBufferSize(1024); // 设置输入缓冲区的大小,Bytes//
m_com.put_Settings(ComSetStr);
m_com.put_InputMode(1);
m_com.put_RThreshold(1);//接受缓冲区又1个及1个以上的字符时,引发接受数据的OnComm事件
m_com.put_InputLen(0);//设置当前接收区数据长度为0,表示全部读取
//m_mscomm.get_Input();
m_com.put_SThreshold(0); //每发送一个字符时,不触发OnComm事件
if (!m_com.get_PortOpen())
{
m_com.put_PortOpen(TRUE);
}
}
最后,串口的编写,代码并不复杂,但是要想它能很好的与串口设备稳定连接,就需要用耐心去调试,有时候一个很小的细节,就会让串口通信变得不稳定。