您现在的位置是:首页 > 智能机电

Qt实践录:串口调试助手

智慧创新站 2025-04-15【智能机电】215人已围观

简介由于项目需要使用到串口调试及测试,为了练手,使用Qt编写一个串口调试助手。本文按开发的过程进行简单介绍,同时也涉及部分用到的模块代码。详细代码参考源码仓库。开发过程工程相关Qt使用的串口类为`QSerialPort`,需要在工程文件中添加对应的库,如下:```QT+=coreguiserialpor...

由于项目需要使用到串口调试及测试,为了练手,使用Qt编写一个串口调试助手。本文按开发的过程进行简单介绍,同时也涉及部分用到的模块代码。详细代码参考源码仓库。

开发过程

工程相关

Qt使用的串口类为`QSerialPort`,需要在工程文件中添加对应的库,如下:

```

QT+=coreguiserialport

```

logo图标,注意是ico格式:

```

RC_ICONS=images/

```

```

RESOURCES+=\

```

USB设备检测依赖的库:

```

win32:LIBS+=-lSetupAPI-luser32

```

信号与槽

此机制及操作方式,可类比于MFC的界面设计和消息响应。实际上,笔者喜欢将槽函数称为响应函数。

串口相关

串口类声明:

```

includeQSerialPortInfo

QSerialPortserial;

```

枚举本机串口设备:

```

QSerialPortInfo::availablePorts()

```

串口参数设置:

```

("com4");//串口名称

(115200);//串口波特率

(QSerialPort::Data8);//数据位

(QSerialPort::OneStop);//停止位

(QSerialPort::NoParity);//校验位

(QSerialPort::NoFlowControl);//流控

```

注:Qt似乎只有无流控、软件流控、硬件流控这三种,无法区分RTS、DTR。

串口打开、关闭:

```

(QIODevice::ReadWrite);

();

```

串口数据发送:

```

(sData);

```

串口数据接收:

```

//串口数据到来时,会触发QSerialPort::readyRead事件,添加相应的响应函数

QObject::connect(serial,QSerialPort::readyRead,this,MainWindow::readyRead);

voidMainWindow::readyRead()

{

}

```

注意,串口数据类型为`QByteArray`。

自动检测USB

鉴于目前大部分场合使用的是USB串口线,所以添加对USB设备的检测。这里检测到USB设备时,再使用`QSerialPortInfo::availablePorts()`检测串口设备。

```

staticconstGUIDGUID_DEVINTERFACE_LIST[]=

{

//GUID_DEVINTERFACE_USB_DEVICE

{0xA5DCBF10,0x6530,0x11D2,{0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED}},

//GUID_DEVINTERFACE_DISK

{0x53f56307,0xb6bf,0x11d0,{0x94,0xf2,0x00,0xa0,0xc9,0x1e,0xfb,0x8b}},

//GUID_DEVINTERFACE_HID,

{0x4D1E55B2,0xF16F,0x11CF,{0x88,0xCB,0x00,0x11,0x11,0x00,0x00,0x30}},

//GUID_NDIS_LAN_CLASS

{0xad498944,0x762f,0x11d0,{0x8d,0xcb,0x00,0xc0,0x4f,0xc3,0x35,0x8c}}

////GUID_DEVINTERFACE_COMPORT

//{0x86e0d1e0,0x8089,0x11d0,{0x9c,0xe4,0x08,0x00,0x3e,0x30,0x1f,0x73}},

////GUID_DEVINTERFACE_SERENUM_BUS_ENUMERATOR

//{0x4D36E978,0xE325,0x11CE,{0xBF,0xC1,0x08,0x00,0x2B,0xE1,0x03,0x18}},

////GUID_DEVINTERFACE_PARALLEL

//{0x97F76EF0,0xF883,0x11D0,{0xAF,0x1F,0x00,0x00,0xF8,0x00,0x84,0x5C}},

////GUID_DEVINTERFACE_PARCLASS

//{0x811FC6A5,0xF728,0x11D0,{0xA5,0x37,0x00,0x00,0xF8,0x75,0x3E,0xD1}}

};

//注册插拔事件

HDEVNOTIFYhDevNotify;

DEV_BROADCAST_DEVICEINTERFACENotifacationFiler;

ZeroMemory(NotifacationFiler,sizeof(DEV_BROADCAST_DEVICEINTERFACE));

_size=sizeof(DEV_BROADCAST_DEVICEINTERFACE);

_devicetype=DBT_DEVTYP_DEVICEINTERFACE;

for(inti=0;isizeof(GUID_DEVINTERFACE_LIST)/sizeof(GUID);i++)

{

_classguid=GUID_DEVINTERFACE_LIST[i];//GetCurrentUSBGUID();//m_usb-GetDriverGUID();

hDevNotify=RegisterDeviceNotification((HANDLE)this-winId(),NotifacationFiler,DEVICE_NOTIFY_WINDOW_HANDLE);

if(!hDevNotify)

{

DWORDErr=GetLastError();

}

}

```

响应`nativeEvent`事件:

```

boolMainWindow::nativeEvent(constQByteArrayeventType,void*message,long*result)

{

MSG*msg=reinterpret_castMSG*(message);

intmsgType=msg-message;

if(msgType==WM_DEVICECHANGE)//设备插入事件

{

PDEV_BROADCAST_HDRlpdb=(PDEV_BROADCAST_HDR)msg-lParam;

switch(msg-wParam){

caseDBT_DEVICEARRIVAL:

if(lpdb-dbch_devicetype==DBT_DEVTYP_DEVICEINTERFACE)

{

//PDEV_BROADCAST_DEVICEINTERFACEpDevInf=(PDEV_BROADCAST_DEVICEINTERFACE)lpdb;

//QStringstrname=QString::fromWCharArray(pDevInf-dbcc_name,pDevInf-dbcc_size);

//qDebug()"arrive"+strname;

printDebugInfo("USBdevicearrive");

emitsig_deviceChanged(1);

}

break;

caseDBT_DEVICEREMOVECOMPLETE://设备移除事件

if(lpdb-dbch_devicetype==DBT_DEVTYP_DEVICEINTERFACE)

{

//PDEV_BROADCAST_DEVICEINTERFACEpDevInf=(PDEV_BROADCAST_DEVICEINTERFACE)lpdb;

//QStringstrname=QString::fromWCharArray(pDevInf-dbcc_name,pDevInf-dbcc_size);

printDebugInfo("USBdeviceremoved");

emitsig_deviceChanged(0);

}

break;

}

}

returnfalse;

}

```

这里使用自定义的信号`sig_deviceChanged`,连接到函数`on_deviceChanged`:

```

QObject::connect(this,MainWindow::sig_deviceChanged,this,MainWindow::on_deviceChanged);

voidMainWindow::on_deviceChanged(intflag)

{

if(flag==1)

{

foreach(constQSerialPortInfoinfo,QSerialPortInfo::availablePorts())

{

if(-1==ui-cbPortName-findText(()))

ui-cbPortName-addItem(());

}

}

else

{

();

ui-btnOpen-setText(tr("打开串口"));

ui-btnOpen-setIcon(QIcon(":images/"));

}

}

```

界面逻辑

界面基本设置

在`initMainWindow`函数中对窗口进行基本设置,如标题、窗口大小,最小化最大化按钮,等等。

```

//对主窗口的初始化

voidMainWindow::initMainWindow()

{

setWindowTitle(tr("QtSerialPort"));

setMinimumSize(480,320);

Qt::WindowFlagswinFlags=Qt::Dialog;

winFlags=winFlags|Qt::WindowMinMaxButtonsHint|Qt::WindowCloseButtonHint;

setWindowFlags(winFlags);

}

```

控件贴图

新建资源文件,内容如下,再在QtCreator中添加该文件:

```

RCC

qresourceprefix="/"

fileimages//file

fileimages//file

fileimages//file

fileimages//file

fileimages//file

fileimages//file

/qresource

/RCC

```

也可以在QtCreator新建资源文件,右键添加图片。效果一样。

以打开串口按钮为例,设置文字和图标代码如下:

```

ui-btnOpen-setText(tr("打开串口"));

ui-btnOpen-setIconSize(ui-btnOpen-rect().size());

ui-btnOpen-setIcon(QIcon(":images/"));

```

注:资源文件qrc的前缀为`/`,images为工程下的目录,使用QIcon的参数形式为`:images/xxx`。

串口参数自动更新

为串口打开时,可以实时更改参数,但串口设备除外。响应QComboBox的`currentTextChanged`或`currentIndexChanged`事件。如下:

```

//串口设备直接用文本文字形式即可

voidMainWindow::on_cbPortName_currentTextChanged(constQStringarg1)

{

(arg1);

}

//如停止位等,需要用索引转换

voidMainWindow::on_cbStopbit_currentIndexChanged(intindex)

{

//qDebug()index;

//设置停止位

switch(index)

{

case0:(QSerialPort::OneStop);break;

case1:(QSerialPort::OneAndHalfStop);break;

case2:(QSerialPort::TwoStop);break;

default:break;

}

}

```

定时器

重载`timerEvent`函数:

```

十六进制

为方便调试,工具支持字符、十六进制数据的发送和显示。“十六进制字符串”转字符串等函数集如下:

```

inthexStringToString(QStringhexStr,QStringstr)

{

intret=0;

boolok;

hexStr=();

hexStr=();

QStringListsl=("");

foreach(QStrings,sl)

{

if(!())

{

charc=((ok,16))0xFF;

if(ok)

{

(c);

}

else

{

ret=-1;

}

}

}

str=retByte;

returnret;

}

inthexStringToHexArray(QStringhexStr,QByteArrayarr)

{

intret=0;

boolok;

hexStr=();

hexStr=();

QStringListsl=("");

foreach(QStrings,sl)

{

if(!())

{

charc=((ok,16))0xFF;

if(ok)

{

(c);

}

else

{

ret=-1;

}

}

}

returnret;

}

inthexArrayToString(QByteArrayhexArr,QStringstr)

{

intret=0;

str=('').toLower();

returnret;

}

```

其它

实际上,程序难度不大,特别是串口类的操作,因为`QSerialPort`提供了十分友好、方便的接口进行串口的设置、收发。如果要说有难度,可能在于界面逻辑的设计。如定时发送与单次发送,控件提示文字和图标显示,十六进制与字符串之间的转换,等等。

笔者在此工具基础上实现了对ESP8266的操作,包括指示LED灯、继电器、出厂恢复以及运行态的功能测试验证等操作。由于与本文关联不大,不再展开。

#代码仓库

代码以仓库为准,本文不一定全部囊括。本工程所有源码均可自由自主使用,包括但不限于添加、删除、修改,商用、自用。由此带来的成果/后果概与作者无关。限于水平能力,本程序无任何质量保证,本程序作者无提供服务之义务。

很赞哦!(142)