调整通讯协议udp+uart
This commit is contained in:
+39
-43
@@ -1,16 +1,18 @@
|
||||
#include "car.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QSerialPort>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkDatagram>
|
||||
#include <QSettings>
|
||||
#include <QUdpSocket>
|
||||
#include <QtGlobal>
|
||||
#include <cstring>
|
||||
|
||||
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<const char*>(&f), kFrameLen);
|
||||
m_sock->writeDatagram(reinterpret_cast<const char*>(&f), kFrameLen,
|
||||
QHostAddress(kCanTxHost), kCanTxPort);
|
||||
}
|
||||
|
||||
// 50ms 组:Chassis_ControlCmd / Arm_ControlCmd1 / Arm_ControlCmd2 / Emergency_Cmd
|
||||
|
||||
+5
-8
@@ -9,7 +9,7 @@
|
||||
#include <QVector>
|
||||
#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;
|
||||
|
||||
+1
-1
@@ -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 无合法帧判为链路断开
|
||||
|
||||
+2
-2
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user