UDP网络上位机设计,接收STM32自定义报文数据的处理和显示
发布于 2021-01-13 01:47
文章目录
设计需求
UDP
TCP和UDP的区别
UI界面设计
界面设计
UDP代码设计与实现
打开UDP
关闭UDP
发送数据
关闭UDP
上位机板间通信协议
板子通信代码设计
UDP上位机数据处理与显示
HEX形式转十进制显示QString::number
设计需求
在原设计中,没有网络部分的设计,所以硬件部分是没有设计网络模块。
为了增加网络功能,通过对预留串口的芯片手册查找,发现了预留的PA9和PA10口,可以复用为uart1串口。
有了串口可以通信。那么就可以使用 uart转网口模块,实现网络功能。
正点原子以太网转串口模块 调试和使用方法(实战详解)
模块的使用方法这篇文章详细的进行了说明,本文章将从整体设计进行阐述。
UDP
本设计使用PC上位机和stm32的网络通信为UDP通信
TCP和UDP的区别
TCP是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性;而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。
也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。知道了TCP和UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的,因为程序员可以手动对UDP的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,即使是这样,UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率。
因为上面的特性,所以TCP需要区分客户端和服务器,UDP是不需要区分的
UI界面设计
对于板子的部分,硬件和代码都已经实现了,只是后面给数据加上报文协议就可以了。重点就放在了上位机的设计。
首先,设计出来上位机,实现UDP上位机能够正常接收网络数据的功能,再来考虑报文协议等问题。
之前写过一个TCP上位机的设计文章,设计UDP的过程大同小异,可以进行参考,链接在这里
Qt 设计实现TCP服务器和客户端上位机(实战详解)
界面设计
首先是界面的设计,板子采集的数据主要有功率 温度 还有风机状态这三个数据量,所以设计显示界面如图所示。
对于UDP服务,需要设置本地的端口号,以及目标的端口号还有目标的IP
UDP代码设计与实现
打开UDP
单机 “打开UDP” 的PushButton时候,应该实现初始化udp的socket协议,接收和读写数据的功能。主要实现的功能是 读取UI界面中输入的本地端口号,绑定本地端口到到socket,监听数据是否接收到数据,读写数据,数据显示到指定位置
void Widget::on_openpushButton_clicked()
{
/* 绑定本地端口号 */
if(( udpSocket->bind(ui->locaportlineEdit_2->text().toInt()) ) == true)
{
QMessageBox::information(this, "提示", "UDP端口号绑定成功!");
}else{
QMessageBox::warning(this, "警告", "UDP端口号绑定失败!");
}
connect(udpSocket, SIGNAL(readyRead()),
this, SLOT(readyRead_Slot()));
}
void Widget::readyRead_Slot()
{
/* 等待接收到数据 */
while (udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
/* 调整数组大小一致 */
datagram.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(datagram.data(), datagram.size()); // 读取接收到的数据
QString buf;
buf = datagram.data(); // 转化成字符串类型
ui->recvplainTextEdit->appendPlainText(buf); // 把内容显示在 接收窗口
}
}
关闭UDP
/* 关闭UDP服务 */
void Widget::on_closepushButton_2_clicked()
{
udpSocket->close();
}
发送数据
这里主要设计,读取UI界面中输入的目标端口号和目标IP地址,以及输入的数据存放到缓存中,最后就是发送指令
/* 发送数据 */
void Widget::on_pushButton_3_clicked()
{
quint16 port; // 目标端口号地址
QString sendbuff; // 发送数据的缓存
QHostAddress address; // 目标IP地址
address.setAddress(ui->aimiplineEdit_4->text());
sendbuff = ui->sendlineEdit->text();
port = ui->aimportlineEdit_3->text().toUInt();
udpSocket->writeDatagram(sendbuff.toLocal8Bit().data(), sendbuff.length(), address, port);
}
关闭UDP
/* 清空接收窗口的内容 */
void Widget::on_pushButton_clicked()
{
ui->recvplainTextEdit->clear();
}
上位机板间通信协议
因为要传输三个数据,直接发送过来接收,数据都挤在一起发送过来肯定是没有办法处理的,所以,要进行协议的设计,这里的报文协议设计采用比较简单使用的方法,规则如表所示
这里的校验方式采用比较简单实用的和校验
U8 TX_CheckSum(U8 *buf, U8 len) //buf为数组,len为数组长度
{
U8 i, ret = 0;
for(i=0; i<len; i++)
{
ret += *(buf++);
}
ret = ~ret;
return ret;
}
U8 RX_CheckSum(U8 *buf, U8 len) //buf为数组,len为数组长度
{
U8 i, ret = 0;
for(i=0; i<len; i++)
{
ret += *(buf++);
}
ret = ret;
return ret+1;
}
有了报文协议,这样就可以通过报文数据来区分不同的数据,数据的报文类型定义如下
板子通信代码设计
因为使用了串口转网口模块,所以实际操作中直接发送数据到串口就可以了。
串口的发送函数,根据报文协议,生成对应的报文结构,然后通过串口发送出去数组。
uint8_t usart1_send(uint8_t cmd, uint8_t *datas,uint16_t dataLen)
{
uint16_t len = 0;
uint8_t rxDatas[10] = {0};
uint8_t dataCheck;
head[1] = cmd;
head[2] = dataLen & 0xff;
head[3] = (dataLen >> 8) & 0xff;
head[4] = TX_CheckSum(head + 1,3);
dataCheck = TX_CheckSum(datas,dataLen);
usart1_send_data(head, 5);
usart1_send_data(datas,dataLen);
usart1_send_data(&dataCheck,1);
usart1_send_data(&tail,1);
return 0;
}
本来应该有一个主从的应答校验重发,就是这样,因为时间关系就没有做
UDP上位机数据处理与显示
void Widget::readyRead_Slot()
{
/* 等待接收到数据 */
while (udpSocket->hasPendingDatagrams()) {
QByteArray datagram, buf, tempAdcx, tempTemper;
/* 调整数组大小一致 */
datagram.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(datagram.data(), datagram.size()); // 读取接收到的数据
buf = datagram;
int len = buf.length();
int i = 0, tempFan;
while(len--)
{
if(buf[i] == 0xAA)
{
if(buf[i+1] == 0x01){
tempAdcx[0] = buf[i+5];
tempAdcx[1
] = buf[i+6];
}else if(buf[i+1] == 0x02){
tempTemper[0] = buf[i+5];
}else if(buf[i+1] = 0x03){
tempFan = buf[i+5];
}
}
i++;
}
QString strtempAdcx;
bool ok;
strtempAdcx = tempAdcx.toHex().data();
//qDebug() << strtempAdcx.toInt(&ok, 16) <<endl;
int adcx = strtempAdcx.toInt(&ok, 16);
ui->gonglvlabel_6->setText(QString::number(adcx, 10));
ui->wendulabel_8->setText(QString::number(tempTemper[0]));
if(tempFan == 1){
ui->fengjilabel_10->setText("故障");
}else{
ui->fengjilabel_10->setText("正常");
}
}
}
这里本来也应该有一个和校验,来检验数据有没有因为传输发生错误,在这个版本中没有加入,直接验证报文头然后提取数据进行一个读取了。
读取到的数据以QByteArray的类型保存,所以要进行格式的转换,才能最终显示为需要的十进制结果。
HEX形式转十进制显示QString::number
传送过来的报文数据,是以HEX字节的形式存在数组中,比如我们的数据,如果是1234,那么就是0x04D2,十六进制的格式,这个数据现在存放在上位机的buf中,buf的类型为QByteArray
QByteArray类有丰富的函数封装,可以进行一个格式的转换
QByteArray str("FF");
bool ok;
int hex = str.toInt(&ok, 16); // hex == 255, ok == true
int dec = str.toInt(&ok, 10); // dec == 0, ok == false
在QT的帮助中有例程可以查看,所以把数据转换成十进制数可以解决了
但是这里有一个问题,我需要把数据显示在label类型的显示界面中
这里可以看到
输入的内容需要时QString类型的数据,所以直接把转换好的十进制数据放进去是不可以编译通过的。
这个时候就需要使用QString的一个函数number
ui->gonglvlabel_6->setText(QString::number(adcx, 10));
ui->wendulabel_8->setText(QString::number(tempTemper[0]));
大功告成~
本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。
相关素材