QT使用串口与RS485设备通讯

QT使用串口与RS485设备通讯

背景

近期才开始接触QT编程,以前都是使用MFC编写上位机软件,经常需要通过串口与RS485设备通讯。之所以开始学习QT,是因为看到网上有个人说自从接触了QT之后,就觉得MFC可以放弃,于是我也有了学习QT的冲动。

买了一本QT入门的书籍《Qt Creater快速入门》,简单学习了按钮等基本控件的使用、信号与槽、以及多线程的知识后,开始编写一个简单的串口程序与RS485设备通讯。

这本书上没有关于串口知识,不过好在现在的网络这么发达,随便一搜就有一大堆。

开始

(1)创建一个Qt widgets application应用,包含以下头文件:

#include <QtSerialPort/QtSerialPort>
#include <QList>
#include <QtSerialPort/QSerialPortInfo>

(2)界面上放一个下拉列表comboBox,使用下面几句代码可以获取电脑上可用的串口,并显示在comboBox控件中,简简单单几句话,就列出了所有串口,感觉好方便啊,瞬间觉得好喜欢。

QList<QSerialPortInfo> list  = QSerialPortInfo::availablePorts();
foreach (const QSerialPortInfo & info, list) {
        ui->comboBox->addItem(info.portName());
}

(3)界面上放一个下拉列表控件comBaud,添加几种波特率

ui->comBaud->addItem(tr("9600"));
ui->comBaud->addItem(tr("4800"));
ui->comBaud->addItem(tr("19200"));

(4)创建一个串口对象:

QSerialPort port;

(5)界面上放置一个按钮openCom,用于打开串口,转到槽添加如下代码,

void Widget::on_openCom_clicked()
{
    QString name = ui->openCom->text();//获取按钮上的文字
    if(name == tr("打开串口"))
    {
        port.setPortName(ui->comboBox->currentText());//选择串口号      
        if( !port.open(QIODevice::ReadWrite))//打开串口
        {
            QMessageBox::information(this, tr("提示"), 
            	tr("打开串口失败,请查看串口是否被占用"), QMessageBox::Ok);
            return;
        }
        ui->openCom->setText(tr("关闭串口"));//改变按钮上的文字    
        port.setBaudRate(ui->comBaud->currentText().toInt());
        port.setDataBits(QSerialPort::Data8);//8位数据位
        port.setParity(QSerialPort::NoParity);//无检验
        port.setStopBits(QSerialPort::OneStop);//1位停止位
        port.setFlowControl(QSerialPort::NoFlowControl);//无硬件控制      
        ui->comboBox->setDisabled(true);//串口号下拉列表变灰
        ui->comBaud->setDisabled(true);//波特率下接列表变灰
    }else
    {
        port.close();//关闭串口
        ui->comboBox->setEnabled(true);//串口号下拉列表变亮
        ui->comBaud->setEnabled(true);//串口号下拉列表变亮
        ui->openCom->setText(tr("打开串口"));
    }
}

(6)添加一个按钮detect,用于点击之后开始与RS485设备通信,添加一个列表控件listWidget,用于显示通讯结果,转到槽添加如下代码

void Widget::on_detect_clicked()
{
    if(!port.isOpen())
    {
        QMessageBox::critical(this, tr("错误"), 
        	tr("请先打开串口"), QMessageBox::Ok);
        return;
    }
    ui->listWidget->clear();//清空列表内容   
    MyThread *th = new MyThread(this, &port);//启动线程,在线程内通讯    
    th->start();
}

(7)添加一个C++类,类名MyThread,继承自QThread,添加一个信号sendResult,用于将通讯结果反馈给主程(暂时这么干吧,毕竟我才刚入门)。

mythread.h文件:
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <widget.h>
#include <QtSerialPort/QtSerialPort>

class MyThread : public QThread
{
    Q_OBJECT
public:    
    MyThread(Widget * parent, QSerialPort *port);
protected:
    void run();
protected:   
    QSerialPort * port;
    Widget * parent;    
signals:
    void sendResult(int code, QString str);
};
#endif // MYTHREAD_H
mythread.cpp文件:
#include "mythread.h"

MyThread::MyThread(Widget * parent, QSerialPort *port)
{
    this->parent = parent;//传入父对象,以便给它发信号
    this->port = port;//传入串口对象,以便在线程中使用
}
//CRC校验码计算
unsigned short CRC16(unsigned char *pBuf,unsigned short len)
{
    unsigned short uCRC=0xFFFF;
    int i,j;
    for(i=0;i<len;i++)
    {
        uCRC^=(*(pBuf+i));
        for(j=0;j<8;j++)
        {
            if((uCRC&0x0001)==0x0001)
            {
                uCRC=(uCRC>>1);
                uCRC^=0xA001;
            }
            else
                uCRC=(uCRC>>1);
        }
    }
    return uCRC;
}

void MyThread::run()
{
    bool bRet = false;
    unsigned char buf[6];
    //关联信号与槽,用于给父对象信号,告知通讯结果
    QObject::connect(this, SIGNAL(sendResult(int, QString)), 
    					parent, SLOT(result(int, QString)));
    	
    //与43个RS485设备通讯,检查设备是否有回应				
    for(unsigned char addr = 1; addr <= 43 ; ++addr)
    {
        buf[0] = addr;
        buf[1] = 0x03;
        buf[2] = 0xA0;
        buf[3] = 0x06;
        unsigned short crc = CRC16(buf, 4);
        buf[4] = static_cast<unsigned char>(crc);
        buf[5] = static_cast<unsigned char>(crc >> 8);

        port->write(reinterpret_cast<const char *>(buf), 6);//发送数据
        port->flush();//让数据立刻从串口发送出去,不要在缓冲区里墨迹        
        msleep(200);//等待200ms
        //检查串口接收到了多少数据
        int num = static_cast<int>(port->bytesAvailable());
        if(num != 6)
        {
            QString str = QString().sprintf("addr=%02d,设备没有响应, 返回数据量为%d",
                                            addr, static_cast<int>(num));
            emit sendResult(0, str);
        }else
        {
			// 接收到了6个字节,表时RS485设备有响应
            QByteArray array = port->readAll();
            QString str = QString().sprintf("addr=%02d, 发现设备,返回数据量为%d",
                                            addr, static_cast<int>(num));
            emit sendResult(0, str);
        }
        msleep(800);      
    }   
}

(8)测试结果如下:43个设备除41号设备外(该设备RS485通讯线没有连接)都有回应,完成了预期目标。
在这里插入图片描述
在这里插入图片描述

小结

(1)往串口写数据的时候,write()后面要加一句flush()才能把数据立刻通过串口发送出去。
(2)刚开始用的是异步读取数据方式,感觉怪怪的,因为发送数据和接收数据不在同一个函数里,而且readyRead信号槽中接收到的数据经常不完整,往往要多次拼接数据,这对于RS485通讯来说很不好操作,后来改用同步操作,使用waitForReadyRead()来等待响应数据,结果在waitForReadyRead()函数这里折腾了小半天,根据手册,waitForReadyRead()返回值为true时表明已经接收到数据了,返回false表示接收超时,结果明明有接收到数据,却还是会超时返回false,造成先是readyRead信号槽提示接收到数据,然后是waitForReadyRead()返回false提示接收超时。后来进到QIODevice::waitForReadyRead()中查看,里面说到使用connect关联了readyRead信号,那么waitForReadyRead()将不会接收到readyReady信号返回true,这下明白了,把connect信号的那句注释掉,waitForReadyRead()果然返回了true,于是就有了上面的程序,先发送数据,等等待一个固定时间再读数据,不要用waitForReadyRead()来等待响应数据,因为它接收到的数据不完整。

  • 27
    点赞
  • 196
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值