串口QSerialPort類同步與異步接收和發送數據


1.功能需求

  通過QT,編寫一個庫。庫的作用是上層直接調用庫的函數,並且傳參。庫函數根據下位機的通信協議,將數據進行封裝。通過串口將數據發送給下位機。下位機獲得數據后,會對數據進行解析,再通過串口應答一幀數據。庫函數再對數據進行解析,提取上層需要的數據,以返回值的形式傳遞給上層。

 

2.實現步驟

  1.初始化並打開串口

  2.根據下位機的通信協議,編寫相對應的函數對數據進行封裝。

  3.庫函數接收到一幀數據后,提取有效數據並返回給上層。

 

3.代碼實現

3.1打開串口

/* 全局變量 */
QSerialPort *serial;

bool OpenCOM(const QString &name) { serial = new QSerialPort(); //port name serial->setPortName(name); //open serial->open(QIODevice::ReadWrite); if(serial->isOpen()) { serial->setBaudRate(115200); serial->setDataBits(QSerialPort::Data8); serial->setStopBits(QSerialPort::OneStop); serial->setFlowControl(QSerialPort::NoFlowControl); } else { return false; } return true; }

  以上的程序就是實例化一個QSerialPort類的對象。上位機根據實際串口是COM幾,以傳參的形式傳遞進來。要先打開串口再對串口進行配置。

  其中isOpen()用來檢測設備是否打開。

  這里需要說明一下流控制。通訊的雙方A和B,假如A給B發送數據時,B反應過慢,A不管不顧的不停發送數據,結果會導致數據丟失。為了防止這種情況發生,可使用流控制(也叫握手)。

  軟件流控制(XON/XOFF):通訊的一方(B)如果不能及時處理串口數據,會給對方(A)發送XOFF字符,對方接收到這個字符后,會停止發送數據;B不再忙的時候,會給A發送XON字符,A接收到這個字符后,會接着發送數據。軟件流控制最大的問題就是不能傳輸XON和XOFF。

  硬件流控制(RTS/CTS):硬件流控制需要按下圖連接兩個串口設備的RTS和CTS。

 

 

   通訊的一方(B)如果不能及時處理串口數據,會設置自己的RTS為低電平,B的RTS連着對方(A)的CTS,A發現自己的CTS為低電平,將停止發送數據;B不再忙的時候,會設置自己的RTS為高電平,A發現自己的CTS為高電平,將接着發送數據。

  上面的代碼中,設置流控制為無,其含義為:不管對方是否能夠反應過來,這邊只管發送數據。

if(serial->open(QIODevice::ReadWrite))
{
    //成功打開串口
    serial->setRequestToSend(true);             //設置 RTS 為高電平
    serial->setDataTerminalReady(true);         //設置 DTR 為高電平

} 

  當流控制為硬件時,系統會自動管理RTS和DTR的狀態。否則,應該設置RTS和DTR為高電平,通知對方可以發送串口數據了。

 

3.2 關閉串口

void CloseCom(void)
{
    serial->clear();
    serial->close();
    serial->deleteLater();  //因為之前new了serial這個對象,所以在關閉串口的時候要銷毀這個對象。不然會造成內存泄露
}

  clear()用來清除輸入輸出緩沖區里面的數據。調用這個函數之前,串口必須已經被打開。

  close()用來關閉串口設備。跟open相對應。

  由於我們之前使用new創建了一個對象,會調用構造函數。就必須調用delete來銷毀這個對象。這個是C++的規則。QT作為C++的庫,也是一樣的道理。但是QT可以不用delete,還可以使用deleteLater。從字面上的意思就是后面再刪除。 (delete 和 new 必須 配對使用(一 一對應):delete少了,則內存泄露,多了麻煩更大。) 

  deleteLater()並沒有將對象立即銷毀,而是向主消息循環發送了一個event,下一次主消息循環收到這個event之后才會銷毀對象。 這樣做的好處是可以在這些延遲刪除的時間內完成一些操作,壞處就是內存釋放會不及時。

 

3.3數據封裝

void Open_Door(int addr, int which_door)
{
    QByteArray tx_buf;

    tx_buf.append(0xAA);
    tx_buf.append(static_cast<char>(addr));
    tx_buf.append(0x01);
    tx_buf.append(static_cast<char>(which_door));
    tx_buf.append(zero);
    tx_buf.append(zero);
    tx_buf.append(zero);
    tx_buf.append(0xFF);

    SendCmd(tx_buf);

}

  

3.4 通過串口下發數據

QByteArray SendCmd(QByteArray cmd)
{
    serial->write(cmd);
    serial->waitForBytesWritten(50000);

    QByteArray data;

    while(serial->waitForReadyRead(5000))
   {

        data = serial->readAll(); //讀取串口數據
        if(!data.isEmpty())
        {
            //讀到數據了,退出循環
            return data;

        }

    }

}

  可以看到這邊使用了waitForBytesWritten和waitForReadyRead函數。下面來解釋一下這兩個函數。

 

4. 串口發送接收的同步與異步

4.1 異步讀取串口數據

  m_port->readAll(函數QIODevice::readAll)用來讀取串口數據。不過,它是異步執行的。什么是異步呢?那就是即使對方還沒有發送串口數據,m_port->readAll也會立即返回,而不是傻傻的等着對方發送數據過來后再返回。

  既然是異步的,那么何時讀取串口數據就成為了關鍵。Qt提供的方案就是使用信號、槽。

connect(m_port,SIGNAL(readyRead()),this,SLOT(slotReadData()));

  當對方發送串口數據后,將觸發m_port的信號QIODevice::readyRead。上面的代碼將信號readyRead與槽函數slotReadData連接了起來,因此槽函數slotReadData將被調用,其代碼如下:

void Widget::slotReadData()
{
  QByteArray data;
  const int nMax = 64 * 1024;

  for(;;)
  {
    data = m_port->readAll(); //讀取串口數據
    if(data.isEmpty())
    {//沒有讀取到串口數據就退出循環
      break;
    }
    //讀取到的串口數據,加入到QByteArray m_dataCom
    m_dataCom.append(data);
    if(m_dataCom.size() > nMax)
    {  
      //防止 m_dataCom 過長       m_dataCom = m_dataCom.right(nMax);     }   }   ui->txtRecv->setText(m_dataCom); //將 m_dataCom 顯示到文本框   ui->txtRecv->moveCursor(QTextCursor::End); //移動文本框內的插入符 }

 

4.2 發送串口數據

  m_port->write(函數QIODevice::write)用來發送串口數據,不過它也是異步的。也就是說:代碼m_port->write("123");會立即返回,至於數據"123"何時會發送給對方,那是操作系統的事情。操作系統不忙的時候,才會做此項工作。

參考如下代碼:

char szData[1024];
memset(szData,'1',sizeof(szData));
szData[sizeof(szData)-1]='\0';
m_port->write(szData);
m_port->close();

  m_port->write(szData);會把1023字節的'1'發送出去。假如波特率為1200,則這些數據需要9秒才能發送完畢。因為m_port->write是異步執行的,所以m_port->write(szData)只是把數據提交給了操作系統就立即返回了。操作系統克隆了一份串口數據szData,在空閑的時候發送,還沒發送完畢m_port->close()就被執行了。結果就是大部分的串口數據丟失。

  為了保證上述代碼不丟失串口數據,需要將異步通訊更改為同步通訊:

char szData[1024];
memset(szData,'1',sizeof(szData));
szData[sizeof(szData)-1]='\0';
m_port->write(szData);
m_port->waitForBytesWritten(10000);
m_port->close();

  就增加了一行代碼m_port->waitForBytesWritten(10000);其含義為:操作系統把串口數據發送出去后,m_port->waitForBytesWritten才會返回。不過,總不能無限制等下去吧?10000就是等待時間的最大值,其單位為毫秒,10000毫秒就是10秒。

 

4.3 同步讀取串口數據

  異步通訊的效率比較高,但是代碼結構比較復雜。有時,需要同步讀取。如:給對方發送字符串 Volt,對方回應電壓值 5。

代碼如下:

m_port->write("Volt");
m_port->waitForBytesWritten(5000);
QByteArray data;

for(;;)
{
    data = m_port->readAll(); //讀取串口數據
    if(!data.isEmpty())
    {
        //讀到數據了,退出循環
        break;
    }
}

  通過一個無限循環,將異步讀取變成了同步讀取。不過,上述代碼運行時,CPU占用率將會達到100%(單核CPU)。為此,需要改進代碼:

m_port->write("Volt");
m_port->waitForBytesWritten(5000);
QByteArray data;

while(m_port->waitForReadyRead(3000))
{
    data = m_port->readAll(); //讀取串口數據
    if(!data.isEmpty())
    {
        //讀到數據了,退出循環
        break;
    }
}     

  修改了一行代碼m_port->waitForReadyRead(3000),其含義為等待對方發送串口數據過來。如果對方發送串口數據過來了,它返回true,然后使用m_port->readAll讀取串口數據;如果對方在3秒內都沒有發送串口數據過來,它返回false,退出循環。

注意:

  如果使用waitForReadyRead這種同步的方式來讀取串口數據,那么就不需要用connect來連接readyRead信號和槽函數。

  這種方式使用場景是串口發送數據后,數據最好是能立馬返回或者是固定多少時間返回。如果串口返回數據的時間不確定,不要用這種方式。還是用connect的異步方式。

 

5. 知識延伸“波特率”

  在4.2中有談到“把1023字節的'1'發送出去。假如波特率為1200,則這些數據需要9秒才能發送完畢”。

  為什么是9秒呢?

  首先需要明確幾個概念:

波特率:

  在消息傳輸通道中,攜帶數據信息的信號單元叫碼元,每秒通過信道傳輸的碼元數稱為碼元傳輸速率,簡稱波特率。

  所以波特率傳輸的單位是碼元,而碼元不是bit。是可以通過不同調制方法在一個符號上負載多個bit信息。

  用人話來講就是碼元就理解為一幀數據。

數據幀:

  電腦串口以及一般使用的開發板串口都是默認8個數據bit,一個停止bit,(起始1bit是必須的)默認無奇偶校驗位,無流控。

  那么實際上一幀數據其實是10bit,而不是8個bit。那么1200的波特率一秒就是能發送120幀數據,因為一幀里面只有1個字符。就是中間的8個有效數據(ASCII中可以轉為為字符,8位就是char這種的數據類型)。

比特率:

  比特率是每秒傳輸多少bit。以9600bps為例,就是每秒傳輸9600bit。

  那么每個bit的時間就是1/9600秒=104.16666666666666666666666666666us,大約0.1ms。因此每個bit緊接着下個bit,不存在額外的間隔,不管是起始bit,數據bit,奇偶bit,停止bit。

  所以波特率和比特率的傳輸單位是不同的。前者是碼元后者是bit。

  如果還有人認為那最后都是按照bit傳輸的啊,還是都一樣啊。那我個人理解就是:你跟豬是一樣的,畢竟都是細胞組成的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM