簡單的小工具是VS2015 + Qt5.6.1實現的,界面部分是Qt實現,串口是封裝的WinAPI,把串口收發模塊封裝成了個Serialport.dll 供Qt界面調用。
由於VS2015需要Universal CRT運行環境,因此把Qt編譯成了靜態的版本。
一、串口收發是封裝的Win32,單獨封裝成了一個Serialport.dll.
包括串口通信類:
class CSerialport { public: CSerialport(); ~CSerialport(); BOOL openComm(const string & name); BOOL closeComm(); BOOL setCommState(const DCB & dcb)const; BOOL getCommState(DCB & dcb)const; BOOL setCommTimeouts(const COMMTIMEOUTS & commtimeOuts)const; BOOL purgeComm(DWORD flags = PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT)const; BOOL setupComm(DWORD dwInQueue, DWORD dwOutQueue)const; int readFile(vector<char> & buffer, DWORD nNumberOfBytesToRead,DWORD & lpNumberOfBytesRead, LPOVERLAPPED pLoverlapped = NULL); int writeFile(vector<char> & buffer, DWORD nNumberOfBytesToRead, DWORD & lpNumberOfBytesRead, LPOVERLAPPED pLoverlapped = NULL); string getPortName()const; HANDLE getHandle()const; // private: HANDLE m_hspCom; string m_commName; };
此類負責基本的串口通信。
線程類:
1 class CBaseThread 2 { 3 public: 4 CBaseThread(void); 5 virtual ~CBaseThread(void); 6 public: 7 virtual void start(); //創建線程
8 virtual void end(); //結束線程
9 virtual void resume(); //重啟線程
10 virtual void suspend(); //暫停線程 11 // 12 virtual int getThreadID() const; //獲得線程ID
13 virtual BOOL isRun() const; //判斷線程是否運行
14 virtual void runTask() = 0; //子類實現此函數完成業務邏輯 15 // 16 static unsigned _stdcall threadFunc(void* pParam); //線程函數,調RunTask邏輯
17
18 protected: 19 HANDLE m_hEndEvent; //設置退出線程處理
20 HANDLE m_hExitEvent; //線程RunTask結束時設置,確保線程正常退出
21 HANDLE m_hThreadHandle; 22 unsigned int m_uThreadID; 23 // 24 };
線程基類,封裝了_beginthreadex()。
串口線程調度線程:
typedef int (*pGET_DATA_CAAL_BACK)(list<char> &); class CSerialportThread : public CBaseThread { public: CSerialportThread(void); virtual ~CSerialportThread(void); void setCommConfig(const char* com, int baudRate, char byteSize, char parity, char stopBits); static CSerialportThread * getInstance(); int writeFile(const char * writeBuffer,int size); void initCallBack(void *); protected: virtual void runTask(); void initComm(); protected: CSerialport m_serialport; // string m_com; int m_baudRate; char m_byteSize; char m_parity; char m_stopBits; bool m_bInit; pGET_DATA_CAAL_BACK m_addDataCallBack; };
繼承CBaseThread實現runTask()線程函數,由CSerialport 類成員變量進行串口的通信的管理,並提供一個回調接口,將接收到的數據回調給接收數據維護的類。
串口數據接收維護類:
class CCommDataHolder { public: CCommDataHolder(); ~CCommDataHolder(); // static std::shared_ptr<CCommDataHolder> getInstance(); static int getDataCallBackS(list<char> & buffer); int getCommData(char * buffer,int len); protected: int getDataCallBack(list<char> & buffer); // private: static std::shared_ptr<CCommDataHolder> s_pInstance; static std::mutex s_mt; // list<char> m_listData; std::mutex m_mt; };
數據維護類,上層應用來這里取數據即可。
Serialport.dll導出接口:
1 #pragma once
2
3 #define SERIALPORT_DLL_EXPORT __declspec(dllexport)
4
5 #ifdef __cplusplus 6 extern "C"
7 { 8 #endif //
9
10 SERIALPORT_DLL_EXPORT int commReadData(char* buffer,int size); 11 SERIALPORT_DLL_EXPORT int commWriteData(const char* buffer, int size); 12 SERIALPORT_DLL_EXPORT void setCommConfig(const char* com,int baudRate,char byteSize,char parity,char stopBits); 13 SERIALPORT_DLL_EXPORT void start(); 14 SERIALPORT_DLL_EXPORT void end(); 15
16 #ifdef __cplusplus 17 } 18 #endif //
二、Qt界面實現
界面實現主要是設置串口的通信的參數,然后設置串口通信的收發區域,這里使用textEdit控件,然后設置了一下數據的展現方式,分ASCII和HEX(16進制發送),
16進制發送的形式應該為: 61 25 AA 7A 5B的這種形式,然后選擇Hex選項,發送。
串口列表:自動枚舉系統的所有已存在串口enumPort(QStringList & strList)
1 void SerialPortTools::enumPort(QStringList & strList) 2 { 3 HKEY hKey; 4 LPCTSTR lpSubKey = _T("HARDWARE\\DEVICEMAP\\SERIALCOMM\\"); 5
6 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_READ, &hKey) != ERROR_SUCCESS) 7 { 8 return; 9 } 10 WCHAR szValueName[100] = { 0 }; 11 WCHAR szPortName[100] = { 0 }; 12 LONG status; 13 DWORD dwIndex = 0; 14 DWORD dwSizeValueName = 100; 15 DWORD dwSizeofPortName = 100; 16 DWORD Type; 17 dwSizeValueName = 100; 18 dwSizeofPortName = 100; 19 do
20 { 21 status = RegEnumValue(hKey, dwIndex++, szValueName, &dwSizeValueName, NULL, &Type, (PUCHAR)szPortName, &dwSizeofPortName); 22 if ((status == ERROR_SUCCESS)) 23 { 24 QString tmp = CCodecUtils::str2qstr(CEcoder::wstringToString(szPortName)); 25 strList << tmp; 26 } 27 dwSizeValueName = 100; 28 dwSizeofPortName = 100; 29 } while ((status != ERROR_NO_MORE_ITEMS)); 30 RegCloseKey(hKey); 31 }
設置好串口參數,串口號,波特率,數據位,校驗位,停止位等參數后setCommConfig(com.c_str(), baudRate, byteSize, parity, stopBit);
,打開串口start();開啟調度串口通信線程。
打開串口需要引用Serialport.dll的接口函數
1 void SerialPortTools::openPort() 2 { 3 QString strCom = ui.comboBox->currentText(); 4 QString strBaudRate = ui.comboBox_2->currentText(); 5 QString strByteSize = ui.comboBox_3->currentText(); 6 // 7 string com = CCodecUtils::qstr2str(strCom); 8 int baudRate = strBaudRate.toInt(); 9 int byteSize = strByteSize.toInt(); 10 int parity = ui.comboBox_4->currentIndex(); 11 int stopBit = ui.comboBox_5->currentIndex(); 12 setCommConfig(com.c_str(), baudRate, byteSize, parity, stopBit); 13 // 14 start(); 15 m_timer.start(); 16 MessageBoxInfo(tr("提示"), tr(" 串口打開成功 ")); 17 ui.openaction->setEnabled(false); 18 ui.closeaction->setEnabled(true); 19 }
數據接收的實現是設置了一個定時器,不斷去調用commReadData(char* buffer,int size) 去讀取dll數據緩存區的數據,讀到之后更新到數據接收區的界面。
發送則很簡單,獲取數據發送區的內容,點擊發送,直接調用commWriteData(const char *buffer,int size)發送串口數據。
定時器:
1 m_timer.setInterval(2000); 2 connect(&m_timer, SIGNAL(timeout()), this, SLOT(reciveData()));
數據收:
1 void SerialPortTools::reciveData() 2 { 3 char szRead[1024] = { 0 }; 4 memset(&szRead, 0, 1024); 5 int nRet = commReadData(szRead, 1024); 6 m_strRec = CCodecUtils::qstr2str(ui.textEdit->toPlainText()); 7 if (nRet != 0) 8 { 9 string str = szRead; 10 m_strRec += str; 11 if (ui.radioButton->isChecked()) 12 { 13 ui.textEdit->setText(CCodecUtils::str2qstr(m_strRec)); 14 } 15 else if (ui.radioButton_2->isChecked()) 16 { 17 char sz[2048]; 18 memset(&sz, 0, 2048); 19 ui.textEdit->setText(CCodecUtils::str2qstr(CCodecUtils::byte2HexCpp(m_strRec))); 20 } 21 } 22 }
數據發:
1 void SerialPortTools::sendData() 2 { 3 QString str = ui.textEdit_2->toPlainText(); 4 string strSend = CCodecUtils::qstr2str(str); 5 if (!strSend.empty()) 6 { 7 // 8 if (ui.radioButton_3->isChecked()) 9 { 10 commWriteData(strSend.c_str(), strSend.length() + 1); 11 } 12 else if (ui.radioButton_4->isChecked()) 13 { 14 string strtmp = CCodecUtils::hexStr2Str(CCodecUtils::eraseSpace(strSend)); 15 commWriteData(strtmp.c_str(),strtmp.length()+1); 16 // 17 } 18 } 19 }
串口關閉end();
1 void SerialPortTools::closePort() 2 { 3 end(); 4 ui.openaction->setEnabled(true); 5 ui.closeaction->setEnabled(false); 6 }
軟件運行:
用虛擬串口工具打開COM1和COM2兩個串口對,打開兩次SerialPortTools.exe ,分別打開COM1和COM2,進行簡單的數據通信測試。
源碼地址:https://github.com/karllen/SerialPortTools