From 124e92b95f09802eaedc807ee8f646affb29f908 Mon Sep 17 00:00:00 2001 From: xu <1012118331@qq.com> Date: Wed, 14 Oct 2026 10:33:58 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=80=9A=E8=AE=AF=E5=8D=8F?= =?UTF-8?q?=E8=AE=AEudp+uart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 +++++++------ app/appcontext.cpp | 4 +-- comm/car.cpp | 82 ++++++++++++++++++++++------------------------ comm/car.h | 13 +++----- comm/uart.cpp | 2 +- comm/uart.h | 4 +-- wuhan.pro | 2 +- 7 files changed, 61 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 4ba50b4..7780155 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 依据《QT界面详细设计说明书.md》实现的“玉兔工业无线遥控器(YTZT6K8EART2-aL25)”遥控端界面, 覆盖 ③开机密码(含密码警告/密码管理登录/密码管理/密码重置)④自检 ⑤系统首页面 ⑥作业选择 ⑦机器人底盘 ⑧机械臂 ⑨气体检测仪 ⑩消除作业 ⑪消毒作业 ⑫调试维护 ⑬故障查询 共 11 个界面, -并配套 **CAN 报文收发**的通信层(19 条报文经 `/dev/ttyS7` 串口收发,遥控器另占 `/dev/ttyS5`)。 +并配套 **CAN 报文收发**的通信层(19 条报文经 UDP 收发,遥控器另占串口 `/dev/ttyS7`)。 ## 环境与构建 @@ -83,16 +83,15 @@ mingw32-make -j4 0~24 L/min;调试维护页三泵转速上限各按协议(空压机 3000 / 柱塞泵 1450 / 叶片泵 2000 rpm)。 顶部「通信正常/异常」按收帧判定:连续 **2s** 未收到任何合法帧即判为异常(最慢的下行周期 -1000ms)。串口打开失败(如开发机 Windows 上没有 `/dev/ttyS7`)时不启动定时器、不崩溃, -界面显示「通信异常」。 +1000ms)。UDP 端口绑定失败(如 9000 已被占用)时不启动定时器、不崩溃,界面显示「通信异常」。 -可在 `config.ini`(程序目录)中配置 `car/device`(默认 `/dev/ttyS7`)、`car/baud`(默认 -`500000`)、`rc/device`(默认 `/dev/ttyS5`)、`rc/baud`(默认 `115200`)、`net/selfcheck_fault` -(自检故障演示)。 +车端 CAN 走 UDP,端口在 `comm/car.cpp` 中以常量固定:本机 **9000** 收、上行命令发往 **127.0.0.1:9001**。 +遥控器仍走串口:`comm/uart.cpp` 打开 `rc/device`(默认 `/dev/ttyS7`)、`rc/baud`(默认 `115200`)。 +`config.ini`(程序目录)中与通信相关的配置还有 `net/selfcheck_fault`(自检故障演示)。 参数(车速、柱塞泵流量、发泡倍数、泡沫比例、空压机流量)与密码经 `QSettings` 持久化,上电自动读取。 -`tools/can_sender.py` / `tools/rc_sender.py` 是旧的 UDP 联调工具,**已不适用于当前串口通道**, -留待需要时另写。 +`tools/can_sender.py` 是车端 CAN 的 UDP 联调工具(给 UI 发反馈时目标端口用 **9000**); +遥控器已回到串口,`tools/rc_sender.py` 发送的 UDP 报文不再被接收。 ### 已知缺口 @@ -108,11 +107,11 @@ mingw32-make -j4 (0,1)/(2,3)/(4,5)/(6,7),`value = data[起/8-1]<<8 | data[起/8]`,恰好占满 64 位、 无溢出。实现分类四时勿按 Intel 连续位序去读。 -## 遥控器(串口 /dev/ttyS5,24 字节二进制帧) +## 遥控器(串口 /dev/ttyS7,24 字节二进制帧) 遥控器不走车端 CAN 通道,独立占一路串口:`comm/uart.cpp` 打开 `rc/device` -(默认 **`/dev/ttyS5`**,115200 / 8 数据位 / 1 停止位 / 无校验),与车端 `/dev/ttyS7` -(见 `comm/car.cpp`)互不干扰。 +(默认 **`/dev/ttyS7`**,115200 / 8 数据位 / 1 停止位 / 无校验),与改走 UDP 的车端 CAN +(`comm/car.cpp`,收 9000 / 发 9001)互不干扰。 帧长 **24 字节**、1 字节对齐、多字节字段**小端**,发送周期 100ms。帧头 `BYTE0=0x11`、 帧尾 `BYTE22=0x22`、`BYTE23 = BYTE0~BYTE22 之和的低 8 位`;校验不过即丢弃。 @@ -157,7 +156,7 @@ mingw32-make -j4 ## 目录结构 - `app/` 全局常量、配色、应用上下文(AppContext)、QSS 样式、闪烁工具 -- `comm/` 车端 CAN 通信层(`car`,19 条报文经 `/dev/ttyS7`)+ 遥控器串口通道(`uart`,`/dev/ttyS5`) +- `comm/` 车端 CAN 通信层(`car`,19 条报文经 UDP 收 9000 / 发 9001)+ 遥控器串口通道(`uart`,`/dev/ttyS7`) - `widgets/` 公共组件:TitleBar / StatusStrip / LedIndicator / NumPadDialog / ConfirmDialog / AlarmBanner / ParamControl / DataCard / FaultTable - `pages/` 各界面(继承 BasePage),对应关系如下: diff --git a/app/appcontext.cpp b/app/appcontext.cpp index f9a0c83..f9172df 100644 --- a/app/appcontext.cpp +++ b/app/appcontext.cpp @@ -43,10 +43,10 @@ AppContext::AppContext(QObject* parent) if (!m_settings->contains(keyAirFlow())) m_settings->setValue(keyAirFlow(), 100); if (!m_settings->contains("param/pwd")) m_settings->setValue("param/pwd", "6789"); - // 通信层(19 条 CAN 报文经 /dev/ttyS7 收发,见 comm/car.h) + // 通信层(19 条 CAN 报文经 UDP 收发:收 9000 / 发 9001,见 comm/car.h) m_comm = new CarCommunication(&telemetry, this); - // 遥控器(串口 /dev/ttyS5,24 字节二进制帧,见 comm/uart.h) + // 遥控器(串口 /dev/ttyS7,24 字节二进制帧,见 comm/uart.h) m_rc = new RcCommunication(this); // 通信层故障信号 → 写入故障历史 diff --git a/comm/car.cpp b/comm/car.cpp index 5fb4ac9..e01dac1 100644 --- a/comm/car.cpp +++ b/comm/car.cpp @@ -1,16 +1,18 @@ #include "car.h" #include #include -#include +#include +#include #include +#include #include #include namespace { // ===================== 线格式 ===================== -// 一帧 = 32 位帧头 + 32 位 CAN ID + DLC + 8 字节数据 + 8 位校验和,共 18 字节。 -// 多字节字段按主机小端排列,与车载端网关一致;帧头用于在字节流中定位报文起始。 +// 一个 UDP 数据报承载 = 32 位帧头 + 32 位 CAN ID + DLC + 8 字节数据 + 8 位校验和,共 18 字节。 +// 多字节字段按主机小端排列,与车载端网关一致;帧头与校验和用于确认数据报内容合法。 #pragma pack(push, 1) struct CanFrame { quint32 header; // 固定帧头 0x5AA5FF00(线上字节序 00 FF A5 5A) @@ -25,6 +27,11 @@ const quint32 kFrameHeader = 0x5AA5FF00u; const int kFrameLen = int(sizeof(CanFrame)); const int kDataLen = 8; +// 车端 CAN 的 UDP 端口:本机 9000 收(车端反馈),上行命令发往 9001 +const quint16 kCanRxPort = 9000; +const quint16 kCanTxPort = 9001; +const char* kCanTxHost = "127.0.0.1"; + quint8 canChecksum(quint32 canId, quint8 dlc, const quint8* data) { quint8 sum = 0; @@ -224,13 +231,11 @@ const char* const kSysDetector = "气体检测仪"; CarCommunication::CarCommunication(Telemetry* tel, QObject* parent) : QObject(parent), m_tel(tel) { - m_serial = new QSerialPort(this); - connect(m_serial, &QSerialPort::readyRead, this, &CarCommunication::onReadyRead); + m_sock = new QUdpSocket(this); + connect(m_sock, &QUdpSocket::readyRead, this, &CarCommunication::onDatagram); - // 串口配置与 AppContext 共用 config.ini(默认 /dev/ttyS7,500000 8N1) + // 端口固定为 kCanRxPort/kCanTxPort 常量,config.ini 只剩自检演示开关要读 QSettings s(cfgPath(), QSettings::IniFormat); - m_device = s.value("car/device", "/dev/ttyS7").toString(); - m_baud = s.value("car/baud", 500000).toInt(); m_selfCheckFault = s.value("net/selfcheck_fault", true).toBool(); // 洗消四参数按存盘值初始化,否则开机帧发的是硬编码初值而非用户设定值。 @@ -273,23 +278,17 @@ CarCommunication::~CarCommunication() bool CarCommunication::start() { - if (m_serial->isOpen()) + if (m_sock->state() == QAbstractSocket::BoundState) return true; - m_serial->setPortName(m_device); - m_serial->setBaudRate(m_baud); - m_serial->setDataBits(QSerialPort::Data8); - m_serial->setStopBits(QSerialPort::OneStop); - m_serial->setParity(QSerialPort::NoParity); - m_serial->setFlowControl(QSerialPort::NoFlowControl); - - if (!m_serial->open(QIODevice::ReadWrite)) { - // 开发机(Windows)没有 /dev/ttyS7 时走这里:不启动定时器,界面显示通信异常 - qWarning() << "车端串口打开失败:" << m_device << m_serial->errorString(); + m_sock->abort(); + // AnyIPv4:回环与各本机网卡都能收到;若只允许本机回环,改成 QHostAddress::LocalHost + if (!m_sock->bind(QHostAddress::AnyIPv4, kCanRxPort)) { + // 端口被占用时走这里:不启动定时器,界面显示通信异常 + qWarning() << "车端 UDP 端口绑定失败:" << kCanRxPort << m_sock->errorString(); setCommOk(false); return false; } - m_buf.clear(); m_hbTimer->start(); m_fastTimer->start(); m_midTimer->start(); @@ -304,8 +303,7 @@ void CarCommunication::stop() m_midTimer->stop(); m_slowTimer->stop(); m_selfCheckTimer->stop(); - if (m_serial->isOpen()) m_serial->close(); - m_buf.clear(); + m_sock->close(); } void CarCommunication::setCommOk(bool ok) @@ -317,28 +315,25 @@ void CarCommunication::setCommOk(bool ok) } // ===================== 收包与分流 ===================== -void CarCommunication::onReadyRead() +// UDP 是数据报:单个数据报内含一条或多条完整帧,逐条校验即可, +// 不再需要串口时代那套跨读字节流的残帧缓冲与滑窗重同步。 +void CarCommunication::onDatagram() { - m_buf.append(m_serial->readAll()); - - // 串口是字节流:逐字节找帧头,校验不过则右移一字节继续找(容忍粘包、拆包与杂包)。 - // 尾部不足一帧的残段留在 m_buf 里等下次数据。 - int off = 0; - while (off + kFrameLen <= m_buf.size()) { - CanFrame f; - std::memcpy(&f, m_buf.constData() + off, kFrameLen); - if (f.header == kFrameHeader && f.dlc == kDataLen - && f.checksum == canChecksum(f.canId, f.dlc, f.data)) { - handleFrame(f.canId, f.data); - off += kFrameLen; - } else { - ++off; + while (m_sock->hasPendingDatagrams()) { + const QByteArray d = m_sock->receiveDatagram().data(); + int off = 0; + while (off + kFrameLen <= d.size()) { + CanFrame f; + std::memcpy(&f, d.constData() + off, kFrameLen); + if (f.header == kFrameHeader && f.dlc == kDataLen + && f.checksum == canChecksum(f.canId, f.dlc, f.data)) { + handleFrame(f.canId, f.data); + off += kFrameLen; + } else { + ++off; // 数据报内可能有杂字节,向后找下一个帧头 + } } } - if (off > 0) m_buf.remove(0, off); - - // 防御:长期收不到帧头时缓冲会一直涨,超过上限只保留尾部一帧长度 - if (m_buf.size() > 4096) m_buf.remove(0, m_buf.size() - kFrameLen); } void CarCommunication::handleFrame(quint32 canId, const quint8* d) @@ -633,7 +628,7 @@ void CarCommunication::cacheFaults() // ===================== 发包 ===================== void CarCommunication::sendFrame(quint32 canId, const quint8* data) { - if (!m_serial->isOpen()) return; + if (m_sock->state() != QAbstractSocket::BoundState) return; CanFrame f; f.header = kFrameHeader; @@ -641,7 +636,8 @@ void CarCommunication::sendFrame(quint32 canId, const quint8* data) f.dlc = kDataLen; std::memcpy(f.data, data, kDataLen); f.checksum = canChecksum(canId, f.dlc, f.data); - m_serial->write(reinterpret_cast(&f), kFrameLen); + m_sock->writeDatagram(reinterpret_cast(&f), kFrameLen, + QHostAddress(kCanTxHost), kCanTxPort); } // 50ms 组:Chassis_ControlCmd / Arm_ControlCmd1 / Arm_ControlCmd2 / Emergency_Cmd diff --git a/comm/car.h b/comm/car.h index 0db7506..aadde7b 100644 --- a/comm/car.h +++ b/comm/car.h @@ -9,7 +9,7 @@ #include #include "global.h" -class QSerialPort; +class QUdpSocket; // 摇杆量协议范围 0~4096,中位 2048(分类一各轴、Vehi_gear/Vehi_turning 通用) static const int kRcAxisMid = 2048; @@ -30,7 +30,8 @@ struct CanFault { // 下行 分类二/三 12 条(车端 → 遥控器):底盘 4 条、机械臂 4 条、洗消 2 条、气体检测仪 2 条, // 按 canId 分流转 handleFrame() 写入 Telemetry。 // 全部为 29 位扩展帧、信号一律 Intel(小端)位序。 -// 串口是字节流,故线上帧统一为 18 字节:帧头 0x5AA5FF00 + canId + DLC + 8 字节数据 + 校验和。 +// 传输走 UDP:bind 本机 9000 端口收下行反馈,上行命令发往 127.0.0.1:9001。 +// UDP 载荷统一为 18 字节:帧头 0x5AA5FF00 + canId + DLC + 8 字节数据 + 校验和。 // 通信超时判定:连续 2s 未收到任何合法帧则判为通信异常(最慢的下行周期 1000ms)。 class CarCommunication : public QObject { @@ -41,7 +42,6 @@ public: bool start(); void stop(); - QString deviceName() const { return m_device; } // 行车/机械臂模式(面板旋钮或遥控器报文)→ Arm_ControlCmd1 的 Arm_modeCmd / Arm_enable void setArmMode(bool arm) { m_armMode = arm; } @@ -73,7 +73,7 @@ signals: void faultAppended(const QString& system, const QString& code, const QString& desc); private slots: - void onReadyRead(); + void onDatagram(); void onSendFast(); // 50ms 组:底盘 / 机械臂 / 急停 void onSendMid(); // 100ms 组:洗消指令 / 洗消调试 void onSendSlow(); // 1000ms 组:气体检测仪 @@ -88,16 +88,13 @@ private: void setCommOk(bool ok); Telemetry* m_tel = nullptr; - QSerialPort* m_serial = nullptr; - QByteArray m_buf; // 串口字节流缓冲:帧头滑窗重同步用 + QUdpSocket* m_sock = nullptr; QTimer* m_hbTimer = nullptr; QTimer* m_fastTimer = nullptr; QTimer* m_midTimer = nullptr; QTimer* m_slowTimer = nullptr; QTimer* m_selfCheckTimer = nullptr; - QString m_device = "/dev/ttyS7"; - int m_baud = 500000; bool m_lastCommOk = true; bool m_armMode = false; diff --git a/comm/uart.cpp b/comm/uart.cpp index bd2e8f4..80a0a18 100644 --- a/comm/uart.cpp +++ b/comm/uart.cpp @@ -68,7 +68,7 @@ RcCommunication::RcCommunication(QObject* parent) // 与 AppContext 共用程序目录的 config.ini:默认构造读不到 rc/* 这些键 // (本工程没有 setOrganizationName/setApplicationName) QSettings s(QApplication::applicationDirPath() + "/config.ini", QSettings::IniFormat); - m_device = s.value("rc/device", "/dev/ttyS5").toString(); + m_device = s.value("rc/device", "/dev/ttyS7").toString(); m_baud = s.value("rc/baud", 115200).toInt(); // 协议发送周期 100ms,连续 500ms 无合法帧判为链路断开 diff --git a/comm/uart.h b/comm/uart.h index dc248d8..e8137dc 100644 --- a/comm/uart.h +++ b/comm/uart.h @@ -36,8 +36,8 @@ struct RcFrame { Q_DECLARE_METATYPE(RcFrame) // ===================== 遥控器串口接收层 ===================== -// 独占 rc/device(默认 /dev/ttyS5),115200 / 8 数据位 / 1 停止位 / 无校验, -// 与车端 /dev/ttyS7(见 car.h)互不干扰。 +// 独占 rc/device(默认 /dev/ttyS7),115200 / 8 数据位 / 1 停止位 / 无校验。 +// 车端 CAN 改走 UDP(见 car.h,收 9000 / 发 9001),不再占用串口。 // 只负责收包、校验、解析与链路超时判定,不决定遥控器数据的用途。 class RcCommunication : public QObject { diff --git a/wuhan.pro b/wuhan.pro index 0de0cb8..9c61cb9 100644 --- a/wuhan.pro +++ b/wuhan.pro @@ -1,4 +1,4 @@ -QT += core gui widgets serialport +QT += core gui widgets network serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets