From f2322046514130c1460115e3c4a500d4cf61c6f4 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 30 Jul 2026 20:01:46 -0700 Subject: [PATCH] [radio] add `Radio::DetermineClockDrift()` for clock drift calculation (#13453) This commit introduces `Radio::DetermineClockDrift()` to calculate clock drift in microseconds for a given interval and clock accuracy (in ppm), using `DivideAndRoundUp` to ensure a conservative (rounded-up) drift value. It updates `SubMac` to use the new helper for CSL window and cycle drift calculations. --- src/core/common/num_utils.hpp | 14 ++++++++++++++ src/core/mac/sub_mac.hpp | 3 ++- src/core/mac/sub_mac_csl_receiver.cpp | 17 +++++++++-------- src/core/radio/radio_types.cpp | 9 +++++++++ src/core/radio/radio_types.hpp | 11 +++++++++++ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/core/common/num_utils.hpp b/src/core/common/num_utils.hpp index b40b0d332..5f6c42223 100644 --- a/src/core/common/num_utils.hpp +++ b/src/core/common/num_utils.hpp @@ -126,6 +126,20 @@ template uint16_t ClampToUint16(UintType aValue) return static_cast(Min(aValue, static_cast(NumericLimits::kMax))); } +/** + * Returns a clamped version of given integer to a `uint32_t`. + * + * If @p aValue is greater than max value of a `uint32_t`, the max value is returned. + * + * @param[in] aValue The value to clamp. + * + * @returns The clamped version of @p aValue to `uint32_t`. + */ +inline uint32_t ClampToUint32(uint64_t aValue) +{ + return static_cast(Min(aValue, static_cast(NumericLimits::kMax))); +} + /** * Returns a clamped version of given integer to a `int8_t`. * diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp index b418b4b7a..83699a5cd 100644 --- a/src/core/mac/sub_mac.hpp +++ b/src/core/mac/sub_mac.hpp @@ -614,7 +614,8 @@ private: void UpdateCslLastSyncTimestamp(RxFrame *aFrame, Error aError); void HandleCslTimer(void); void GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter); - uint32_t GetNextCycleDrift(void); + uint32_t DetermineClockDrift(uint32_t aIntervalUs) const; + uint32_t GetNextCycleDrift(void) const; uint32_t GetLocalTime(void); bool IsCslEnabled(void) const { return mCslPeriod > 0; } #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE diff --git a/src/core/mac/sub_mac_csl_receiver.cpp b/src/core/mac/sub_mac_csl_receiver.cpp index 67b3d7830..71dc2382a 100644 --- a/src/core/mac/sub_mac_csl_receiver.cpp +++ b/src/core/mac/sub_mac_csl_receiver.cpp @@ -260,22 +260,23 @@ void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter) curTime = GetLocalTime(); elapsed = curTime - mCslLastSync.GetValue(); - semiWindow = static_cast(static_cast(elapsed) * - (Get().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy()) / - Time::kOneSecondInUsec); + semiWindow = DetermineClockDrift(elapsed); semiWindow += mCslParentAccuracy.GetUncertaintyInMicrosec() + Get().GetCslUncertainty() * 10; aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead + kCslReceiveTimeAhead); aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter); } -uint32_t SubMac::GetNextCycleDrift(void) +uint32_t SubMac::DetermineClockDrift(uint32_t aIntervalUs) const { - uint64_t periodUs = mCslPeriod * Radio::kUsPerTenSymbols; + uint16_t clockAccuracy = Get().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy(); - return static_cast(periodUs * - (Get().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy()) / - Time::kOneSecondInUsec); + return Radio::DetermineClockDrift(clockAccuracy, aIntervalUs); +} + +uint32_t SubMac::GetNextCycleDrift(void) const +{ + return DetermineClockDrift(static_cast(mCslPeriod) * Radio::kUsPerTenSymbols); } uint32_t SubMac::GetLocalTime(void) diff --git a/src/core/radio/radio_types.cpp b/src/core/radio/radio_types.cpp index eec5e1b5d..0396e85b7 100644 --- a/src/core/radio/radio_types.cpp +++ b/src/core/radio/radio_types.cpp @@ -46,6 +46,15 @@ bool IsTimeStrictlyBefore(Time32 aFirstTime, Time32 aSecondTime) return (firstTime < secondTime); } +uint32_t DetermineClockDrift(uint16_t aClockAccuracy, uint32_t aIntervalUs) +{ + static constexpr uint64_t kPpmDivisor = 1000000u; + + uint64_t drift = static_cast(aIntervalUs) * static_cast(aClockAccuracy); + + return ClampToUint32(DivideAndRoundUp(drift, kPpmDivisor)); +} + //--------------------------------------------------------------------------------------------------------------------- // SyncedTime diff --git a/src/core/radio/radio_types.hpp b/src/core/radio/radio_types.hpp index 046d760a9..59ea5c1f1 100644 --- a/src/core/radio/radio_types.hpp +++ b/src/core/radio/radio_types.hpp @@ -88,6 +88,17 @@ inline Time32 ConvertTime64To32(Time64 aTime64) { return static_cast(aTi */ bool IsTimeStrictlyBefore(Time32 aFirstTime, Time32 aSecondTime); +/** + * Calculates clock drift in microseconds for a given interval and clock accuracy. + * + * @param[in] aClockAccuracy The clock accuracy in ppm (can be for a single device or combined accuracy of transmitter + * and receiver). + * @param[in] aIntervalUs The interval duration in microseconds. + * + * @returns The calculated clock drift in microseconds (rounded up). + */ +uint32_t DetermineClockDrift(uint16_t aClockAccuracy, uint32_t aIntervalUs); + #if OT_CONFIG_RADIO_TIME_ENABLE && OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE /**