串口.Qt532测试(同步)
环境:Win7x64、Qt5.3.2 MSVC OpenGL(x86)、vs2010(x86)
ZC:这里的例子是 同步的函数操作,貌似 如果子线程在等待 WaitCommEvent(...)或ReadFile(...) 返回的话(即 串口句柄正在被使用中),界面主线程执行 CloseHandle(...) 或 SetCommMask(...) 的话,就会卡在那里... 于是 考虑改用 异步方式
ZC:想到 一个方式,使用同步方式的时候 可以使用 强制关闭线程的方式“TerminateThread(线程句柄, ExitCode);” 来关闭子线程,这样就不会再占用 串口句柄了,CloseHandle(..)也可以顺利执行。问题:TerminateThread(...) 会有一些 动态申请的资源释放,获取的锁释放 等的问题(具体看MSDN的说明,里面还提到了"heap lock",于是在 申请内存的过程中 如果强制结束线程的话 堆锁未释放 别的线程再申请内存的时候就卡在那里了...),还有 在 XP以及之前的Windws版本OS中 强制结束线程 OS不会释放 它的初始栈,造成内存泄漏(我看了一下 XP的资源管理器,确实是这样的现象)。
1、ZC:
1.1、开始时,遇到的问题:在接收信息的线程中,ReadFile(...) 每次都是 立即返回的 但是获取的数据都是0字节的长度,现象就好像是 用了 异步的方式 没等到事件 就立即返回了...
找到问题:(1)、查看了 CreateFile(...)的参数,发现使用的就是 同步的方式(没有指定 参数 FILE_FLAG_OVERLAPPED)
(2)、后来 发现是 COMMTIMEOUTS 设置的不正确的缘故
测试下来,有2种方式 设置COMMTIMEOUTS,来 解决上面的问题:
①、类似如下的参数设置:
COMMTIMEOUTS TimeOuts; //设定读超时 TimeOuts.ReadIntervalTimeout = 100; TimeOuts.ReadTotalTimeoutMultiplier = 5000; TimeOuts.ReadTotalTimeoutConstant = 5000; //设定写超时 TimeOuts.WriteTotalTimeoutMultiplier = 500; TimeOuts.WriteTotalTimeoutConstant = 2000;
②、这样设置:
COMMTIMEOUTS CommTimeOuts; GetCommTimeouts(hCom1, &CommTimeOuts); CommTimeOuts.ReadIntervalTimeout = MAXDWORD; CommTimeOuts.ReadTotalTimeoutMultiplier = 0; CommTimeOuts.ReadTotalTimeoutConstant = 0; CommTimeOuts.WriteTotalTimeoutMultiplier = 10; CommTimeOuts.WriteTotalTimeoutConstant = 1000;
ZC:光是这样设置的话,就会出现上面的问题,ReadFile(...) 立即返回 0字节数据。接收线程 就会 连续不断的去ReadFile(...) ...
ZC:这样设置的话,需要 使用 “SetCommMask(hCom1, EV_RXCHAR);” 和 “DWORD dwMask = EV_RXFLAG; WaitCommEvent(g_hCom, &dwMask, NULL);”,使用 它们 就是 声明和等待EV_RXCHAR事件(该事件 意思是 只要输入缓冲区接收到数据就会触发)
2、测试代码:
2,1、main.cpp
#include "MainWindow.h" #include <QApplication> extern MainWindow* g_pMainWindow; int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); g_pMainWindow = &w; a.installNativeEventFilter(&w);// 注意,不是“a.installEventFilter(w);”,少了 "Native" return a.exec(); }
2.2、MainWindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QAbstractNativeEventFilter> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow, public QAbstractNativeEventFilter { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); public: virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *); private slots: void on_pbtnConn_clicked(); void on_pbtnRecvMsgClear_clicked(); void on_pbtnSendMsgClear_clicked(); void on_pbrnSend_clicked(); public: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
2.3、MainWindow.cpp
#include "MainWindow.h" #include "ui_MainWindow.h" MainWindow* g_pMainWindow = NULL; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // https://blog.csdn.net/horizons_kong/article/details/54412339 // https://blog.csdn.net/zmdsjtu/article/details/78539681 } MainWindow::~MainWindow() { delete ui; } #include <QDebug> #include <windows.h> #include <stdio.h> #include <process.h> #include "thread_z.h" #include "PassInfo_z.h" #include "SeriesPort_z.h" QString TimeNowZ() { SYSTEMTIME sys; GetLocalTime( &sys ); char buf[128] = {0}; sprintf_s( buf, sizeof(buf), "%02d:%02d:%02d.%03d ", sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds ); return QString::fromLocal8Bit(buf); } void ShowMsgZ(const QString& _str) { if (g_pMainWindow != NULL) g_pMainWindow->ui->teRecv->append(_str); } bool MainWindow::nativeEventFilter(const QByteArray &eventType, void *message, long *) { MSG* pMsg = reinterpret_cast<MSG*>(message); if(pMsg->message == WM_PASSINFO) { int iLen = pMsg->wParam; QString strInfo = Info_Recv(ShowMsgZ, iLen, (char*)pMsg->lParam); if (! strInfo.isNull()) //ui->teRecv->append(TimeNowZ() + strInfo); ui->teRecv->append(strInfo); else ui->teRecv->append("\r\n"); return true; } return false; } void MainWindow::on_pbtnConn_clicked() { QString strChuanKouHao = ui->cbChuanKouHao->currentText(); QString strBoTeLv = ui->cbBoTeLv->currentText(); BYTE Parity = (BYTE)ui->cbJiaoYanWei->currentIndex(); BYTE ShuJuWei = (BYTE)(ui->cbShuJuWei->currentIndex() + 4); BYTE TingZhiWei = ((BYTE)ui->cbTingZhiWei->currentIndex() + 2) * 0.5; bool bRtn = SPort_Init(ShowMsgZ, strChuanKouHao.toLocal8Bit().data(), strBoTeLv.toULong(), Parity, ShuJuWei, TingZhiWei); if (! bRtn) { ui->pbtnConn->setChecked(false); return; } ShowMsgZ("After SPort_Init(...)"); _beginthread(Thread_RECV, 0, (void*)this->winId()); ShowMsgZ("After _beginthread(...)"); } void MainWindow::on_pbtnRecvMsgClear_clicked() { ui->teRecv->clear(); //_beginthread(Thread_RECV, 0, (void*)this->winId()); } void MainWindow::on_pbtnSendMsgClear_clicked() { ui->teSend->clear(); } void MainWindow::on_pbrnSend_clicked() { // char pc[] = {128,129}; // if (IsDBCSLeadByte(pc[0])) // qDebug() << "T"; // else // qDebug() << "F"; ui->teRecv->append("\r\n"); }
2.4、PassInfo_z.h
#ifndef PASSINFO_Z_H #define PASSINFO_Z_H #include <QString> #include <Windows.h> #define WM_PASSINFO WM_USER+0x1000 typedef void (__cdecl *TshowMsg)(const QString& _str); void Info_Send(HWND _hWnd, int _iLen, char* _pc); void Info_Send_pc(HWND _hWnd, char* _pc); QString Info_Recv(TshowMsg _funcShowMsg, int _iLen, char* _pc); #endif // PASSINFO_Z_H
2.5、PassInfo_z.cpp
#include "PassInfo_z.h" void Info_Send(HWND _hWnd, int _iLen, char* _pc) { if (_hWnd == 0) return; if (_iLen > 0) { char* pc = new char[_iLen]; memcpy(&pc[0], _pc, _iLen); PostMessage( _hWnd, WM_PASSINFO, WPARAM(_iLen), LPARAM(pc) ); } else PostMessage( _hWnd, WM_PASSINFO, WPARAM(_iLen), 0 ); } void Info_Send_pc(HWND _hWnd, char* _pc) { Info_Send(_hWnd, strlen(_pc), _pc); } QString Info_Recv(TshowMsg _funcShowMsg, int _iLen, char* _pc) { // ZC: 进入到这个函数,前提是 "_iLen > 0" if (_iLen <= 0) return QString::null; return QString::fromLocal8Bit(_pc, _iLen); } // ZC: IsDBCSLeadByte( char ):BOOL; // ZC: 判断是否是 中文/韩文等字符的第1个字节 //char g_infoRecv[1024 * 4] = {0}; //int g_iInfoRecvCnt = 0; //QString Info_Recv(TshowMsg _funcShowMsg, int _iLen, char* _pc) //{ // // ZC: 进入到这个函数,前提是 "_iLen > 0" // _funcShowMsg(QString::number(g_iInfoRecvCnt)); // if (g_iInfoRecvCnt > 0) // { // memcpy(&g_infoRecv[g_iInfoRecvCnt], _pc, _iLen); // g_iInfoRecvCnt += _iLen; // if (! IsDBCSLeadByte(_pc[_iLen - 1]) )// ZC: 判断是否是 中文/韩文等字符的第1个字节 // { // QString str = QString::fromLocal8Bit(g_infoRecv, g_iInfoRecvCnt); // g_iInfoRecvCnt = 0; // return str; // } // } // else // { // if ( IsDBCSLeadByte(_pc[_iLen - 1]) ) // { // memcpy(&g_infoRecv[g_iInfoRecvCnt], _pc, _iLen); // g_iInfoRecvCnt += _iLen; // } // else // return QString::fromLocal8Bit(_pc, _iLen); // } // return QString::null; //}
2.6、SeriesPort_z.h
#ifndef SERIESPORT_Z_H #define SERIESPORT_Z_H #include <Windows.h> #include <QString> extern HANDLE g_hCom; #define SERIES_PORT__IN_QUEUE_SIZE 1024 * 16 #define SERIES_PORT__OUT_QUEUE_SIZE 1024 * 16 typedef void (__cdecl *TshowMsg)(const QString& _str); bool SPort_Init(TshowMsg _funcShowMsg, char* _pcSeriesPortName, DWORD _BaudRate, BYTE _Parity, BYTE _ByteSize, BYTE _StopBits); #endif // SERIESPORT_Z_H
2.7、SeriesPort_z.cpp
#include "SeriesPort_z.h" #include <QDebug> #include <QTextCodec> #include "PassInfo_z.h" //HWND g_hWnd = 0; HANDLE g_hCom = 0; bool SPort_Init(TshowMsg _funcShowMsg, char* _pcSeriesPortName, DWORD _BaudRate, BYTE _Parity, BYTE _ByteSize, BYTE _StopBits) { g_hCom = 0; WCHAR wszSeriesPortName[8] = {0}; MultiByteToWideChar(CP_ACP, 0, _pcSeriesPortName, strlen(_pcSeriesPortName) + 1, wszSeriesPortName, sizeof(wszSeriesPortName) / sizeof(wszSeriesPortName[0])); HANDLE hCom1 = CreateFile(wszSeriesPortName,//COM1口 GENERIC_READ | GENERIC_WRITE, //允许读和写 0, //独占方式 NULL, OPEN_EXISTING, //打开而不是创建 0, //同步方式 NULL); if (hCom1 == INVALID_HANDLE_VALUE) { QTextCodec *pCodec = QTextCodec::codecForName("GBK"); QString strPrint = pCodec->toUnicode("打开COM失败 !"); _funcShowMsg(strPrint); return false; } else { QTextCodec *pCodec = QTextCodec::codecForName("GBK"); QString strPrint = pCodec->toUnicode("COM打开成功 !"); _funcShowMsg(strPrint); } // SetupComm(hCom1, SERIES_PORT__IN_QUEUE_SIZE, SERIES_PORT__OUT_QUEUE_SIZE); //输入缓冲区和输出缓冲区的大小都是1024 // COMMTIMEOUTS TimeOuts; // //设定读超时 // TimeOuts.ReadIntervalTimeout = 100; // TimeOuts.ReadTotalTimeoutMultiplier = 5000; // TimeOuts.ReadTotalTimeoutConstant = 5000; // //设定写超时 // TimeOuts.WriteTotalTimeoutMultiplier = 500; // TimeOuts.WriteTotalTimeoutConstant = 2000; // if (! SetCommTimeouts(hCom1, &TimeOuts)) //设置超时 // { // int iErr = ::GetLastError(); // QTextCodec *pCodec = QTextCodec::codecForName("GBK"); // QString strPrint = pCodec->toUnicode("设置串口读写超时时间失败"); // strPrint += ", last error code is "+QString::number(iErr); // _funcShowMsg(strPrint); // return false; // } // DCB dcb; // GetCommState(hCom1, &dcb); // dcb.BaudRate = _BaudRate;//9600; //波特率为9600 // dcb.ByteSize = _ByteSize;//8; //每个字节有8位 // dcb.Parity = _Parity;//NOPARITY; //无奇偶校验位 // dcb.StopBits = _StopBits;//ONESTOPBIT; //1个停止位 // if (! SetCommState(hCom1, &dcb)) // { // int iErr = ::GetLastError(); // QTextCodec *pCodec = QTextCodec::codecForName("GBK"); // QString strPrint = pCodec->toUnicode("设置串口参数失败"); // strPrint += ", last error code is "+QString::number(iErr); // _funcShowMsg(strPrint); // return false; // } _funcShowMsg( QString::number(_BaudRate)+","+QString::number(_ByteSize) +","+QString::number(_Parity)+","+QString::number(_StopBits) ); DCB dcb; if (! GetCommState(hCom1, &dcb)) { int iErr = ::GetLastError(); QTextCodec *pCodec = QTextCodec::codecForName("GBK"); QString strPrint = pCodec->toUnicode("获取串口当前属性参数失败"); strPrint += ", last error code is "+QString::number(iErr); _funcShowMsg(strPrint); return false; } //配置串口参数 dcb.BaudRate = _BaudRate; //波特率 dcb.fBinary = TRUE; //二进制模式。必须为TRUE dcb.ByteSize = _ByteSize; //数据位。范围4-8 dcb.StopBits = _StopBits; //停止位 if (_Parity == NOPARITY) { dcb.fParity = FALSE; //奇偶校验。无奇偶校验 dcb.Parity = _Parity; //校验模式。无奇偶校验 } else { dcb.fParity = TRUE; //奇偶校验。 dcb.Parity = _Parity; //校验模式。无奇偶校验 } dcb.fOutxCtsFlow = FALSE; //CTS线上的硬件握手 dcb.fOutxDsrFlow = FALSE; //DST线上的硬件握手 dcb.fDtrControl = DTR_CONTROL_ENABLE;//DTR控制 dcb.fDsrSensitivity = FALSE; dcb.fTXContinueOnXoff = FALSE;// dcb.fOutX = FALSE; //是否使用XON/XOFF协议 dcb.fInX = FALSE; //是否使用XON/XOFF协议 dcb.fErrorChar = FALSE; //是否使用发送错误协议 dcb.fNull = FALSE; //停用null stripping dcb.fRtsControl = RTS_CONTROL_ENABLE;// dcb.fAbortOnError = FALSE; //串口发送错误,并不终止串口读写 //设置串口参数 if (! SetCommState(hCom1, &dcb)) { int iErr = ::GetLastError(); QTextCodec *pCodec = QTextCodec::codecForName("GBK"); QString strPrint = pCodec->toUnicode("设置串口参数失败"); strPrint += ", last error code is "+QString::number(iErr); _funcShowMsg(strPrint); return false; } //设置串口事件 SetCommMask(hCom1, EV_RXCHAR);//在缓存中有字符时产生事件 SetupComm(hCom1, SERIES_PORT__IN_QUEUE_SIZE, SERIES_PORT__OUT_QUEUE_SIZE); //设置串口读写时间 COMMTIMEOUTS CommTimeOuts; GetCommTimeouts(hCom1, &CommTimeOuts); CommTimeOuts.ReadIntervalTimeout = MAXDWORD; CommTimeOuts.ReadTotalTimeoutMultiplier = 0; CommTimeOuts.ReadTotalTimeoutConstant = 0; CommTimeOuts.WriteTotalTimeoutMultiplier = 10; CommTimeOuts.WriteTotalTimeoutConstant = 1000; if (!SetCommTimeouts(hCom1, &CommTimeOuts)) { int iErr = ::GetLastError(); QTextCodec *pCodec = QTextCodec::codecForName("GBK"); QString strPrint = pCodec->toUnicode("设置串口读写超时时间失败"); strPrint += ", last error code is "+QString::number(iErr); _funcShowMsg(strPrint); return false; } g_hCom = hCom1; return true; } bool SendData(TshowMsg _funcShowMsg, HANDLE _hComm, char* data, int len) { if (_hComm == INVALID_HANDLE_VALUE) { QTextCodec *pCodec = QTextCodec::codecForName("GBK"); QString strPrint = pCodec->toUnicode("串口未打开"); _funcShowMsg(strPrint); return false; } //清空串口 PurgeComm(_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR); //写串口 DWORD dwWrite = 0; DWORD dwRet = WriteFile(_hComm, data, len, &dwWrite, NULL); int iErr = 0; if (! dwRet) { iErr = ::GetLastError(); } //清空串口 PurgeComm(_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR); if (! dwRet) { QTextCodec *pCodec = QTextCodec::codecForName("GBK"); QString strPrint = pCodec->toUnicode("发送数据失败"); strPrint += ", last error code is "+QString::number(iErr); _funcShowMsg(strPrint); return false; } return true; }
2.8、thread_z.h
#ifndef THREAD_Z_H #define THREAD_Z_H void Thread_RECV(void *_ArgList); #endif // THREAD_Z_H
2.9、thread_z.cpp
#include "thread_z.h" #include <QDebug> #include <Windows.h> #include <process.h> #include "PassInfo_z.h" #include "SeriesPort_z.h" int g_iIdx = 0; bool MsgHandler01(HWND _hWnd, DWORD _dwRecv, char* _pc); void Thread_RECV(void *_ArgList) { // ::MessageBoxA(0, "Thread_RECV(...) in ", "", 0); HWND hWnd = (HWND)_ArgList; if (hWnd == 0) { qDebug() << "Thread_RECV(...) - hWnd is NULL ."; _endthread(); return; } if (g_hCom == 0) { Info_Send_pc(hWnd, "Thread_RECV(...) - g_hCom is 0 ."); _endthread(); return; } Info_Send_pc(hWnd, "Thread_RECV(...) in"); qDebug() << "Thread_RECV(...) in"; //清空串口 PurgeComm(g_hCom, PURGE_RXCLEAR | PURGE_TXCLEAR); char buf[SERIES_PORT__OUT_QUEUE_SIZE] = {0}; char bufErr[256] = {0}; while (true) { // g_iIdx ++; // if (g_iIdx > 10) // break; BOOL bRtn = false; DWORD dwMask = EV_RXFLAG;//EV_RXCHAR; bRtn = WaitCommEvent(g_hCom, &dwMask, NULL); if (! bRtn) { int iErr = ::GetLastError(); sprintf_s(bufErr, sizeof(bufErr), "WaitCommEvent(...) return false, last error code is %d .", iErr); Info_Send_pc(hWnd, bufErr); break; } DWORD dwRead; bRtn = ReadFile(g_hCom, &buf[0], SERIES_PORT__OUT_QUEUE_SIZE, &dwRead, NULL); // { // char msg[128] = {0}; // sprintf_s(msg, sizeof(msg), "%d - %d", g_iIdx, dwRead); // Info_Send_pc(hWnd, msg); // } if (! bRtn) { int iErr = ::GetLastError(); sprintf_s(bufErr, sizeof(bufErr), "ReadFile(...) return false, last error code is %d .", iErr); Info_Send_pc(hWnd, bufErr); break; } if (dwRead > 0) if (! MsgHandler01(hWnd, dwRead, buf)) break; } Info_Send_pc(hWnd, "Thread_RECV(...) out"); qDebug() << "Thread_RECV(...) out"; /* _endthread given to terminate */ _endthread(); } // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** char g_infoRecv[1024 * 4] = {0}; DWORD g_dwInfoRecvCnt = 0; bool MsgHandler01(HWND _hWnd, DWORD _dwRecv, char* _pc) { //char buf1[64] = {0}; memcpy(&g_infoRecv[g_dwInfoRecvCnt], _pc, _dwRecv); g_dwInfoRecvCnt += _dwRecv; //Info_Send_pc(_hWnd, "11"); void* p = memchr(&g_infoRecv[0], '\n', g_dwInfoRecvCnt); if (p != NULL) { //Info_Send_pc(_hWnd, "12"); DWORD dwBegin = (DWORD)( &g_infoRecv[0] ); DWORD dwPos = (DWORD)p; int iLen = dwPos - dwBegin - 1;// \r\n 为占2个字节位置,需要将它们占的位置去掉 //Info_Send_pc(_hWnd, "13"); if (iLen < 0) { char bufErr[64] = {0}; sprintf_s(bufErr, sizeof(bufErr), "MsgHandler01(...) - (dwLen < 0) : %d", iLen); Info_Send_pc(_hWnd, bufErr); return false; } //sprintf_s(buf1, sizeof(buf1), "14 : %d, %d, %d, %d", iLen, g_dwInfoRecvCnt, dwBegin, dwPos); //Info_Send_pc(_hWnd, buf1); Info_Send(_hWnd, iLen, &g_infoRecv[0]); //Info_Send_pc(_hWnd, "15"); memcpy( &g_infoRecv[0], &g_infoRecv[iLen + 2], g_dwInfoRecvCnt - (iLen+2) ); //Info_Send_pc(_hWnd, "16"); g_dwInfoRecvCnt -= (iLen+2); //Info_Send_pc(_hWnd, "17"); } return true; }
3、
4、
5、