企業(yè)寬帶可以做網(wǎng)站嗎安卓優(yōu)化大師手機(jī)版下載
【QT】基于UDP/TCP/串口的Ymodom通訊協(xié)議客戶端
- 前言
- Ymodom實(shí)現(xiàn)
- QT實(shí)現(xiàn)
- 開源庫的二次開發(fā)-1
- 開源庫的二次開發(fā)-2
- 串口方式實(shí)現(xiàn)
- TCP方式實(shí)現(xiàn)
- UDP方式實(shí)現(xiàn)
- 補(bǔ)充:文件讀取
- 補(bǔ)充:QT 封裝成EXE
前言
Qt 運(yùn)行環(huán)境 Desktop_Qt_5_11_2_MSVC2015_64bit ,基于Ymodom通訊協(xié)議,開發(fā)客戶端實(shí)現(xiàn)與設(shè)備的UDP /TCP /串口通訊。在前期測試過程中,主要用了網(wǎng)絡(luò)調(diào)試助手、串口調(diào)試助手、Virtual Serial Port Driver虛擬串口。
對(duì)Ymodom的了解過程中,主要學(xué)習(xí)了博文Ymodem協(xié)議詳解 、【嵌入式——QT】QT集成Ymodem協(xié)議使用UDP進(jìn)行傳輸、qt隨手記——ymodem協(xié)議使用,里面對(duì)協(xié)議的規(guī)則進(jìn)行了詳細(xì)的講述,方便理解Ymodom 是什么。
在沒有設(shè)備的情況下,用虛擬設(shè)備進(jìn)行測試,發(fā)一個(gè)文件對(duì)應(yīng)的指令如下:
43 ( C)
--
06 ( Ack)
43 ( C)
......
06 ( Ack)
----
04 (收 Eot)
15 ( NAK)
04 (收 Eot)
06 ( Ack)
43 ( C)
--
06 ( Ack)
Ymodom實(shí)現(xiàn)
該協(xié)議包括起始幀、數(shù)據(jù)幀、結(jié)束幀,狀態(tài)變量流轉(zhuǎn)的方向如下:
YmodemFileTransmit.h
status : StatusEstablish->StatusTransmit ->StatusFinishymodem.h
stage: StageNone-> StageEstablishing -> StageEstablished -> StageTransmitting->StageFinishing->StageFinished ->StageNone
code: CodeNone
里面數(shù)據(jù)幀要注意,傳1024或128規(guī)則如下:
- 數(shù)據(jù)大于128,則按1024傳;
- 數(shù)據(jù)小于128,則按128傳。
關(guān)于數(shù)據(jù)填充,看網(wǎng)上說是,以0x1A填充,但實(shí)際測試發(fā)現(xiàn),是按照00填充的。
整個(gè)指令流程如下:
- 接收方先發(fā) 43(C)
- 發(fā)送方發(fā) 文件名+文件大小
- 接收方發(fā) 06(Ack)
- 接收方發(fā) 43(C)
- 發(fā)送方開始一包包數(shù)據(jù)的發(fā)送,每發(fā)一包得等接收方回復(fù) 06(Ack)后再開始下一包
- 當(dāng)發(fā)完最后一包數(shù)據(jù)后,發(fā)送方發(fā) 04 (Eot)
- 接收方發(fā) 15( NAK)
- 發(fā)送方再發(fā) 04 (Eot)
- 接收方發(fā) 06 ( Ack)
- 接收方發(fā) 43(C),開始下一個(gè)文件傳輸
- 如果不在發(fā)文件,則發(fā)送方發(fā)一包00數(shù)據(jù)
- 接收方發(fā) 06(Ack),結(jié)束傳輸。
在傳輸中,使用的指令主要如下:
CodeNone = 0x00,CodeSoh = 0x01, //128字節(jié)數(shù)據(jù)包;CodeStx = 0x02, //1024字節(jié)數(shù)據(jù)包;CodeEot = 0x04, //文件傳輸結(jié)束指令;CodeAck = 0x06, //接收正確指令;CodeNak = 0x15, //重傳當(dāng)前數(shù)據(jù)包請(qǐng)求指令;CodeCan = 0x18, //取消傳輸指令,連續(xù)發(fā)送5個(gè)該命令,終止傳輸;CodeC = 0x43, //請(qǐng)求數(shù)據(jù)包CodeA1 = 0x41,CodeA2 = 0x61
QT實(shí)現(xiàn)
主要用得是Ymodem的開源庫函數(shù),然后對(duì)其進(jìn)行二次開發(fā)。
開源庫的二次開發(fā)-1
里面最主要的一個(gè)變更是,在傳輸完成進(jìn)行二次回復(fù)確定時(shí),接收方會(huì)發(fā)一個(gè)Ack過來,此時(shí)會(huì)調(diào)用transmitStageFinishing()
,不難發(fā)現(xiàn)里面并沒有關(guān)于Ack 的處理,此時(shí)會(huì)調(diào)用default:
處理:
如果一直沒有發(fā)C指令,會(huì)不斷累加定時(shí)器調(diào)用次數(shù),由于設(shè)置一次定時(shí)器10ms,間隔5s后會(huì)重發(fā)04 (Eot)指令;如果超過設(shè)置的最大響應(yīng)時(shí)間25s,會(huì)寫取消傳輸指令。當(dāng)然正常是不會(huì)有問題的,但是在測試時(shí)候,由于輸入需要時(shí)間,時(shí)而會(huì)出現(xiàn)重發(fā)的情況,而且要注意這里的5s,是從第二次發(fā)完 EOT 后開始計(jì)算的。因此,對(duì)transmitStageFinishing()
增加Ack 處理 :
case CodeAck://sht-240813 add :避免發(fā)完ACK后C回復(fù)不及時(shí),導(dǎo)致多發(fā)EOT指令{timeCount = 0;errorCount = 0;dataCount = 0;break;}
開源庫的二次開發(fā)-2
第二個(gè)最大變更是,關(guān)于讀取回復(fù)指令的長度設(shè)置,在部分的設(shè)備中,會(huì)存在回復(fù)指令加 0D 0A
的情況,用于分隔指令,因?yàn)榻邮辗交貜?fù)指令中存在連續(xù)發(fā)2個(gè)指令的情況,如果有了0D 0A
的加入,可以直接 以 06 0D 0A 43 0D 0A
方式發(fā)指令,當(dāng)然也可以不用 0D 0A
,單純只是用 06 43
或者間隔一下時(shí)間分別發(fā),都可以。
既然出現(xiàn)了加 0D 0A
情況,那就要對(duì)讀取進(jìn)行二次處理,在receivePacket()
中將read(&(rxBuffer[0]), 1)
修改為read(&(rxBuffer[0]), 3)
。
串口方式實(shí)現(xiàn)
最主要的就是串口收發(fā)一定要寫好,Ymodom 部分主要就是進(jìn)行虛函數(shù)復(fù)寫就行。
QT += serialport
#include <QSerialPort>
QSerialPort * serialPort;
if (serialPort->open(QSerialPort::ReadWrite) == true){//成功打開串口return true;}else{//串口打開失敗return false;}
//讀寫指定長度len,存入buff
uint32_t YmodemFileTransmitSerial::read(uint8_t* buff, uint32_t len)
{return serialPort->read((char*)buff, len);
}uint32_t YmodemFileTransmitSerial::write(uint8_t* buff, uint32_t len)
{return serialPort->write((char*)buff, len);
}
TCP方式實(shí)現(xiàn)
QT +=network
#include <QTcpSocket>
QTcpSocket * tcpClient;
這里一定要注意,平常會(huì)有信號(hào)觸發(fā)的方式進(jìn)行連接成功的判斷,但為了減少跳轉(zhuǎn),以及代碼邏輯的統(tǒng)一,這邊采用了waitForConnected
去進(jìn)行連接成功與否的判斷,設(shè)置的30000為等待連接時(shí)間,超過了則返回false。
tcpClient->connectToHost(targetAddr,serverPort);if(tcpClient->waitForConnected(30000)){//連接成功return true;}else{return false;}
這里也一定要注意,平常進(jìn)行數(shù)據(jù)接收我們一般也是采用信號(hào)觸發(fā)的方式,但這邊不是,用得read
和write
,傳參分別是存儲(chǔ)信息的地址和讀取長度,返回實(shí)際讀取長度。當(dāng)發(fā)來一共10個(gè)字節(jié),然后讀了3個(gè),后面7個(gè)字節(jié)會(huì)緩存,可以下次讀,因此針對(duì)開源庫的二次開發(fā)-2主要就是影響這里,加了0D 0A
,在讀1字節(jié),就會(huì)有問題。
//-----------虛函數(shù)實(shí)現(xiàn),讀取內(nèi)容----
uint32_t YmodemFileTransmitTcp::read(uint8_t *buff, uint32_t len)
{QByteArray array = tcpClient->read(len);uint32_t lenArray = array.size();uint32_t lenBuff = len;uint32_t length = qMin(lenArray, lenBuff);memcpy(buff, array, length);return length;
}
//-----------虛函數(shù)實(shí)現(xiàn),寫內(nèi)容----
uint32_t YmodemFileTransmitTcp::write(uint8_t *buff, uint32_t len)
{int ret = tcpClient->write((char*)buff, len);return ret;
}
UDP方式實(shí)現(xiàn)
QT +=network
#include <QUdpSocket>
QUdpSocket* udpClient;
UDP也是一樣的情況,由于不連接通訊,倒是不用增加連接步驟,但是接收信息不用常用的信號(hào)觸發(fā)實(shí)現(xiàn),也是直接用read
和write
,其目的其實(shí)都是為了方便代碼編寫,更好使用Ymodom 庫,確保三種方式邏輯編寫規(guī)則統(tǒng)一。
//-----------虛函數(shù)實(shí)現(xiàn),讀取內(nèi)容----
uint32_t YmodemFileTransmit::read(uint8_t* buff, uint32_t len)
{QNetworkDatagram datagram =udpClient->receiveDatagram(len);QByteArray array = datagram.data();uint32_t lenArray = array.size();uint32_t lenBuff = len;uint32_t length = qMin(lenArray, lenBuff);memcpy(buff, array, length);return length;
}
//-----------虛函數(shù)實(shí)現(xiàn),寫內(nèi)容----
uint32_t YmodemFileTransmit::write(uint8_t* buff, uint32_t len)
{QHostAddress targetAddr(serverIp);int ret = udpClient->writeDatagram((char*)buff, len, targetAddr, serverPort);return ret;
}
補(bǔ)充:文件讀取
在開發(fā)中,需要涉及到文件的讀取,為了方便后續(xù)的復(fù)用,這邊也做一個(gè)整理
- 找文件,存文件路徑
#include <QFileDialog>
#include <QMessageBox>void BootLoader::on_pushButtonBrowse_clicked()
{QString curPath = QDir::currentPath();ui->lineEditFilePath->setText(QFileDialog::getOpenFileName(this, u8"打開文件", curPath, u8"任意文件 (*.*)"));
}
- 讀文件
#include <QFile>
QFile* file;
YmodemFileTransmit::YmodemFileTransmit(QObject* parent) :QObject(parent),file(new QFile)
{
}
//-------------設(shè)置讀取文件名--------
void YmodemFileTransmit::setFileName(const QString& name)
{file->setFileName(name);
}
//獲取文件名 +文件大小
if(file->open(QFile::ReadOnly) == true) {QFileInfo fileInfo(*file);fileSize = fileInfo.size();fileCount = 0;//將文件名fileInfo.fileName().toLocal8Bit().data()存buffstrcpy((char*)buff, fileInfo.fileName().toLocal8Bit().data());//將文件大小QByteArray::number(fileInfo.size()).data())存buffstrcpy((char*)buff + fileInfo.fileName().toLocal8Bit().size() + 1, QByteArray::number(fileInfo.size()).data());}
//讀YMODEM_PACKET_1K_SIZE最大長度的內(nèi)容存buff,返回讀取的實(shí)際長度。
//而且只要沒有file->close();,file->read 會(huì)移動(dòng)文件游標(biāo),讀完一次后面接著讀fileCount += file->read((char*)buff, YMODEM_PACKET_1K_SIZE);
補(bǔ)充:QT 封裝成EXE
可以查看大神的博文【QT中如何生成導(dǎo)出.exe可執(zhí)行文件并打包給其他人使用】,里面講得很清晰。