当我们在用VS进行串口编程时,在打开串口前,经常想知道当前PC上存在多少个串口,哪些串口可用?哪些串口已经打开了,最好是在一个Combo Box中列表系统当前所有可用的串口以供选择,然而如何获取系统当前可用的串口有哪些呢?
这里介绍的方法也是最简单也是最笨的一种方法,即一个一个去试,试完了就知道了。至于还有没有其它更好的方法,暂不在本文所讨论的范围之内。
一个在对话框的OnInitDialog函数内做如下操作:
BOOL Ctbox_debug_viewDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here AddCom();//向组合框中添加串口设备 m_CombolPort.SetCurSel(0); //... }
AddCom函数如下定义:
void Ctbox_debug_viewDlg::AddCom(void) { EnumerateSerialPorts(ports,portse,portsu); unsigned short uicounter; unsigned short uisetcom; CString str; //获取可用串口个数 uicounter = portse.GetSize(); //如果个数大于0 if(uicounter > 0) { //初始化串口列表框 for(int i=0; i<uicounter; i++) { uisetcom = portse.ElementAt(i); str.Format(_T("COM%d "),uisetcom); m_CombolPort.AddString(str); } } }
EnumerateSerialPorts函数如下:
void Ctbox_debug_viewDlg::EnumerateSerialPorts(CUIntArray& ports, CUIntArray& portse, CUIntArray& portsu) { //清除串口数组内容 ports.RemoveAll(); portse.RemoveAll(); portsu.RemoveAll(); //因为至多有255个串口,所以依次检查各串口是否存在 //如果能打开某一串口,或打开串口不成功,但返回的是 ERROR_ACCESS_DENIED错误信息, //都认为串口存在,只不过后者表明串口已经被占用 //否则串口不存在 for (int i=1; i<256; i++) { //Form the Raw device name CString sPort; sPort.Format(_T("\\\\.\\COM%d"), i); //Try to open the port BOOL bSuccess = FALSE; HANDLE hPort = ::CreateFile(sPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); if (hPort == INVALID_HANDLE_VALUE) { DWORD dwError = GetLastError(); if (dwError == ERROR_ACCESS_DENIED) { bSuccess = TRUE; portsu.Add(i); //已占用的串口 } } else { //The port was opened successfully bSuccess = TRUE; portse.Add(i); ////可用的串口 //Don't forget to close the port, since we are going to do nothing with it anyway CloseHandle(hPort); } //Add the port number to the array which will be returned if (bSuccess) ports.Add(i); //所有存在的串口 } }
如上操作即可将系统当前可用的所有串口添加到CombolPort所对应的Combol Box中了。
代码如下:
void Ctbox_debug_viewDlg::OnBnClickedBtOpen() { // TODO: Add your control notification handler code here if(m_mscomm.get_PortOpen()) { m_mscomm.put_PortOpen(FALSE); GetDlgItem(IDC_BT_OPEN)->SetWindowText(_T("打开")); ShowInfo(_T("关闭串口成功!")); m_OpenStatus =false; return; } UpdateData(TRUE); m_OpenStatus =true; //当前端口号 int curPort =portse.ElementAt(m_CombolPort.GetCurSel()); m_mscomm.put_CommPort(curPort);//端口号 m_mscomm.put_InBufferSize(1024);//接收缓冲区 m_mscomm.put_OutBufferSize(1024);//发送缓冲区 m_mscomm.put_InputLen(0);//设置当前接收区数据长度为0,表示全部读取 m_mscomm.put_InputMode(1);//以二进制方式读写数据 m_mscomm.put_RThreshold(1);//接收缓冲区有1个及1个以上字符时,将引发接收数据的OnComm事件 //波特率 DWORD baudrate; switch(m_CombolBaudrate.GetCurSel()) { case 0: baudrate =115200; break; case 1: baudrate =9600; break; default: ASSERT(FALSE); break; } CString setting; setting.Format(_T("%d,n,8,1"),baudrate); m_mscomm.put_Settings(setting/*_T("115200,n,8,1")*/);//波特率,无校验,8个数据位,1个停止1位 m_mscomm.put_PortOpen(TRUE);//打开串口 GetDlgItem(IDC_BT_OPEN)->SetWindowText(_T("关闭")); ShowInfo(_T("打开串口成功!")); }
这里主要利用Cwnd的ON_WM_DEVICECHANGE消息来处理。
ON_WM_DEVICECHANGE消息在VS2010中好像只能通过手动来添加,反正我没找到可以通过对话的形式来添加的,后续有知道的兄台麻烦告知我一下。
因此,首先得手动添加ON_WM_DEVICECHANGE消息:
第一步:在消息映射BEGIN_MESSAGE_MAP(Ctbox_debug_viewDlg, CDialogEx)中添加:
ON_WM_DEVICECHANGE()
第二步:在头文件类的定义中添加函数声明:
afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD dwData);
第三步:实现函数
BOOL Ctbox_debug_viewDlg::OnDeviceChange(UINT nEventType,DWORD dwData) { //DEV_BROADCAST_DEVICEINTERFACE* dbd = (DEV_BROADCAST_DEVICEINTERFACE*) dwData; switch (nEventType) { case DBT_DEVICEREMOVECOMPLETE://移除设备 case DBT_DEVICEARRIVAL://添加设备 RefreshCom();//刷新组合框的内容 break; default: break; } return TRUE; }
RefreshCom函数如下:
void Ctbox_debug_viewDlg::RefreshCom(void) { int count =m_CombolPort.GetCount(); for(int i=0;i<count;i++) { m_CombolPort.DeleteString(count -1 -i); } AddCom(); m_CombolPort.SetCurSel(0); }
第四步:在.cpp文件中包含头文件
#include <Dbt.h>
这是因为DEV_BROADCAST_DEVICEINTERFACE,DBT_DEVICEREMOVECOMPLETE,DBT_DEVICEARRIVAL这几个东东在头文件Dbt.h中定义的,这样才能编译通过。
而AddCom函数已经在之前讲到过,回过头去看看。
这里的思想是通过检测到设备的插入和拔出时刷新组合框的内容,是通过重新测试串口的方式来实现的。
另:需要注意的是,当插入和拔出设备时,系统会检测到两次"设备改变事件",因此会两次调用OnDeviceChange函数,第一次调用时传入的函数nEventType的值固定为0x07,第二次调用时nEventType才是上述代码switch中所定义的事件,这与网上所说的需要注册是相悖的,不知道为什么网上有些文章会说一定需要注册什么的才能检测到设备改变的事件,反正我这里不需要,我用的是VS2010.
完!