From beff21ba42a79f3a1172d6d9da44963c2c7ba97f Mon Sep 17 00:00:00 2001 From: BitterAndReal Date: Fri, 15 May 2026 13:33:29 +0200 Subject: [PATCH 1/2] fix: correct PCAN timestamps for uptime-based backends PCAN provides hardware timestamps relative to device power-on (uptime), not Unix epoch. Previous approaches tried to anchor this uptime clock to the wall clock, but an unavoidable ~3ms software lag created a fixed bias that made Rx timestamps appear after Tx in override-mode delta calculations, breaking correct Tx/Rx alignment. Fix: - For uptime-based backends (hwTimestamp < timeBasis), use CANConManager::getElapsedUs() directly for Rx timestamps instead of the PCAN hw clock. This is the identical monotonic source used for Tx timestamps, so Tx and Rx are always on the same clock with no drift or bias. - Add CANConManager::getElapsedUs() helper exposing mElapsedTimer. - Remove the first-frame anchor members (mFirstHwTimestamp, mFirstElapsedUs) that are no longer needed. - Revert the timestamp sort in refreshConnection() that was a workaround for the bias; natural emission order is now always correct. --- connections/canconmanager.cpp | 5 +++++ connections/canconmanager.h | 1 + connections/serialbusconnection.cpp | 33 ++++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/connections/canconmanager.cpp b/connections/canconmanager.cpp index e2ca5fdb..6d559072 100644 --- a/connections/canconmanager.cpp +++ b/connections/canconmanager.cpp @@ -134,6 +134,11 @@ uint64_t CANConManager::getTimeBasis() return mTimestampBasis; } +uint64_t CANConManager::getElapsedUs() +{ + return static_cast(mElapsedTimer.nsecsElapsed()) / 1000ULL; +} + QList& CANConManager::getConnections() { return mConns; diff --git a/connections/canconmanager.h b/connections/canconmanager.h index 65b90142..0c7e4f88 100644 --- a/connections/canconmanager.h +++ b/connections/canconmanager.h @@ -25,6 +25,7 @@ class CANConManager : public QObject CANConnection* getByName(const QString& pName) const; uint64_t getTimeBasis(); + uint64_t getElapsedUs(); // µs elapsed since last resetTimeBasis() — same reference as Tx timestamps void resetTimeBasis(); int getNumBuses(); diff --git a/connections/serialbusconnection.cpp b/connections/serialbusconnection.cpp index 22cc4030..6b02f210 100644 --- a/connections/serialbusconnection.cpp +++ b/connections/serialbusconnection.cpp @@ -151,6 +151,22 @@ void SerialBusConnection::disconnectDevice() { } } +/* Immediately update status when the device state changes */ +void SerialBusConnection::deviceStateChanged(QCanBusDevice::CanBusDeviceState state) +{ + CANConStatus stats; + if (state == QCanBusDevice::ConnectedState) { + setStatus(CANCon::CONNECTED); + stats.conStatus = getStatus(); + stats.numHardwareBuses = mNumBuses; + emit status(stats); + } else if (state == QCanBusDevice::UnconnectedState && getStatus() == CANCon::CONNECTED) { + setStatus(CANCon::NOT_CONNECTED); + stats.conStatus = getStatus(); + stats.numHardwareBuses = mNumBuses; + emit status(stats); + } +} void SerialBusConnection::errorReceived(QCanBusDevice::CanBusError error) const { @@ -229,7 +245,22 @@ void SerialBusConnection::framesReceived() if (useSystemTime) { frame_p->setTimeStamp(CommFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } - else frame_p->setTimeStamp(CommFrame::TimeStamp(0, (recFrame.timeStamp().seconds() * 1000000ul + recFrame.timeStamp().microSeconds()) - timeBasis)); + else { + uint64_t hwTimestamp = static_cast(recFrame.timeStamp().seconds()) * 1000000ULL + + static_cast(recFrame.timeStamp().microSeconds()); + uint64_t relTimestamp; + if (hwTimestamp >= timeBasis) { + // Epoch-based hw timestamp (e.g. SocketCAN): subtract session basis. + relTimestamp = hwTimestamp - timeBasis; + } else { + // Uptime-based hw timestamp (e.g. PCAN): the hw clock cannot be + // synchronised to the elapsed timer without a fixed anchor bias, + // so use the elapsed timer directly — the same source as Tx timestamps. + // This guarantees Tx and Rx share one consistent clock with no drift. + relTimestamp = CANConManager::getInstance()->getElapsedUs(); + } + frame_p->setTimeStamp(CommFrame::TimeStamp(0, relTimestamp)); + } checkTargettedFrame(*frame_p); From 303e336a98bb66953bfedbe2a26bd740161b831e Mon Sep 17 00:00:00 2001 From: BitterAndReal Date: Fri, 15 May 2026 14:15:04 +0200 Subject: [PATCH 2/2] fix: prevent incorrect timestamps after Clear Frames When 'Clear Frames' is clicked while frames are arriving, resetTimeBasis() restarts mElapsedTimer but frames already queued by the working thread carry timestamps from the old (pre-reset) elapsed timer. These get drained on the next 20ms tick and appear with large incorrect timestamps, breaking timestamp alignment with subsequent frames. Fix: flush every connection's queue inside resetTimeBasis() so all pre-reset frames are discarded before the timer fires. --- connections/canconmanager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/connections/canconmanager.cpp b/connections/canconmanager.cpp index 6d559072..aff8eded 100644 --- a/connections/canconmanager.cpp +++ b/connections/canconmanager.cpp @@ -39,6 +39,11 @@ void CANConManager::resetTimeBasis() { mTimestampBasis = QDateTime::currentMSecsSinceEpoch() * 1000; mElapsedTimer.restart(); + // Flush any frames already queued by connections — they carry timestamps + // computed against the old mElapsedTimer and would appear with incorrect + // (large) values after the reset if not discarded now. + foreach (CANConnection* conn_p, mConns) + conn_p->getQueue().flush(); } CANConManager::~CANConManager()