mirror of
https://github.com/espressif/openthread.git
synced 2026-10-12 10:47:43 +00:00
[mac] simplify CSL receiver window calculation (#13591)
This commit simplifies the CSL sample window calculation in `SubMac::CslReceiver`: - Replaces `GetWindowEdges()` with `DetermineWindow()` taking an explicit sample time and returning a `Window` struct. - Uses `Radio::SyncedTime` in `Window` to keep the radio start time and local timer fire time synchronized. - Eliminates `GetNextCycleDrift()` by determining the next CSL window directly after advancing the sample time. - Cleans up `ProcessRxFrame()` and `ProcessTxDone()`. - Introduces `SubMac::HasAddress()` to check whether a MAC address matches any of the device's addresses (short, alternate short, or extended).
This commit is contained in:
@@ -160,6 +160,35 @@ void SubMac::SetExtAddress(const ExtAddress &aExtAddress)
|
||||
LogDebg("RadioExtAddress: %s", mExtAddress.ToString().AsCString());
|
||||
}
|
||||
|
||||
bool SubMac::HasAddress(const Address &aAddress) const
|
||||
{
|
||||
bool matches = false;
|
||||
|
||||
switch (aAddress.GetType())
|
||||
{
|
||||
case Address::kTypeNone:
|
||||
break;
|
||||
|
||||
case Address::kTypeShort:
|
||||
if (aAddress.GetShort() == mShortAddress)
|
||||
{
|
||||
matches = true;
|
||||
}
|
||||
else if (mAlternateShortAddress != kShortAddrInvalid)
|
||||
{
|
||||
matches = (aAddress.GetShort() == mAlternateShortAddress);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Address::kTypeExtended:
|
||||
matches = (aAddress.GetExtended() == mExtAddress);
|
||||
break;
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
void SubMac::SetRxOnWhenIdle(bool aRxOnWhenIdle)
|
||||
{
|
||||
mRxOnWhenIdle = aRxOnWhenIdle;
|
||||
@@ -297,7 +326,10 @@ void SubMac::HandleReceiveDone(RxFrame *aFrame, Error aError)
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
mCslReceiver.UpdateLastSyncTimestamp(aFrame, aError);
|
||||
if ((aFrame != nullptr) && (aError == kErrorNone))
|
||||
{
|
||||
mCslReceiver.ProcessRxFrame(*aFrame);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
|
||||
@@ -571,7 +603,7 @@ void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aErro
|
||||
mCallbacks.RecordCcaStatus(ccaSuccess, aFrame.GetChannel());
|
||||
}
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
|
||||
mCslReceiver.UpdateLastSyncTimestamp(frameInfo, aAckFrame);
|
||||
mCslReceiver.ProcessTxDone(frameInfo, aAckFrame);
|
||||
#endif
|
||||
break;
|
||||
|
||||
|
||||
@@ -287,6 +287,16 @@ public:
|
||||
*/
|
||||
void SetExtAddress(const ExtAddress &aExtAddress);
|
||||
|
||||
/**
|
||||
* Indicates whether a given MAC address matches this device's short, alternate short, or extended address.
|
||||
*
|
||||
* @param[in] aAddress The MAC address to check.
|
||||
*
|
||||
* @retval TRUE If @p aAddress matches any of this device's addresses.
|
||||
* @retval FALSE If @p aAddress does not match any of this device's addresses.
|
||||
*/
|
||||
bool HasAddress(const Address &aAddress) const;
|
||||
|
||||
/**
|
||||
* Registers a callback to provide received packet capture for IEEE 802.15.4 frames.
|
||||
*
|
||||
@@ -642,8 +652,8 @@ private:
|
||||
void Init(void);
|
||||
void Stop(void) { mTimer.Stop(); }
|
||||
void SetParams(uint16_t aPeriod, uint8_t aChannel, ShortAddress aShortAddr, const ExtAddress &aExtAddr);
|
||||
void UpdateLastSyncTimestamp(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame);
|
||||
void UpdateLastSyncTimestamp(RxFrame *aFrame, Error aError);
|
||||
void ProcessTxDone(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame);
|
||||
void ProcessRxFrame(const RxFrame &aFrame);
|
||||
void HandleTimer(void);
|
||||
|
||||
const CslAccuracy &GetParentAccuracy(void) const { return mParentAccuracy; }
|
||||
@@ -654,14 +664,18 @@ private:
|
||||
static constexpr uint32_t kMinReceiveOnAfter = OPENTHREAD_CONFIG_MIN_RECEIVE_ON_AFTER;
|
||||
static constexpr uint32_t kReceiveTimeAhead = OPENTHREAD_CONFIG_CSL_RECEIVE_TIME_AHEAD;
|
||||
|
||||
struct Window
|
||||
{
|
||||
Radio::SyncedTime mStartTime;
|
||||
uint32_t mDuration;
|
||||
};
|
||||
|
||||
void RestartTimerAfterSyncUpdate(void);
|
||||
void SetLastSyncToNow(void);
|
||||
void GetWindowEdges(uint32_t &aAhead, uint32_t &aAfter);
|
||||
void DetermineWindow(const Radio::SyncedTime &aSampleTime, Window &aWindow) const;
|
||||
uint32_t DetermineClockDrift(uint32_t aIntervalUs) const;
|
||||
uint32_t GetNextCycleDrift(void) const;
|
||||
bool IsEnabled(void) const { return mPeriod > 0; }
|
||||
void LogWindow(Radio::Time64 aStart, uint32_t aDuration);
|
||||
void LogReceived(RxFrame *aFrame);
|
||||
void LogReceived(const RxFrame &aFrame);
|
||||
|
||||
using CslTimer = TimerMicroIn<SubMac, &SubMac::HandleCslReceiverTimer>;
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ SubMac::CslReceiver::CslReceiver(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mTimer(aInstance)
|
||||
{
|
||||
mParentAccuracy.Init();
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -55,6 +54,7 @@ void SubMac::CslReceiver::Init(void)
|
||||
mPeriod = 0;
|
||||
mChannel = 0;
|
||||
mPeerShort = 0;
|
||||
mParentAccuracy.Init();
|
||||
mSampleTime.Clear();
|
||||
mTimer.Stop();
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
|
||||
@@ -81,11 +81,12 @@ void SubMac::CslReceiver::RestartTimerAfterSyncUpdate(void)
|
||||
}
|
||||
}
|
||||
|
||||
void SubMac::CslReceiver::UpdateLastSyncTimestamp(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame)
|
||||
void SubMac::CslReceiver::ProcessTxDone(const TxFrame::ParseInfo &aFrameInfo, RxFrame *aAckFrame)
|
||||
{
|
||||
// Actual synchronization timestamp should be from the sent frame instead of the current time.
|
||||
// Assuming the error here since it is bounded and has very small effect on the final window duration.
|
||||
|
||||
VerifyOrExit(IsEnabled());
|
||||
VerifyOrExit(aAckFrame != nullptr);
|
||||
VerifyOrExit(aFrameInfo.mParsedFully);
|
||||
VerifyOrExit(aFrameInfo.Has<CslIe>());
|
||||
@@ -97,21 +98,21 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void SubMac::CslReceiver::UpdateLastSyncTimestamp(RxFrame *aFrame, Error aError)
|
||||
void SubMac::CslReceiver::ProcessRxFrame(const RxFrame &aFrame)
|
||||
{
|
||||
VerifyOrExit(aFrame != nullptr && aError == kErrorNone);
|
||||
VerifyOrExit(IsEnabled());
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
|
||||
LogReceived(aFrame);
|
||||
#endif
|
||||
|
||||
// Assuming the risk of the parent missing the Enh-ACK in favor of smaller CSL receive window
|
||||
if ((mPeriod > 0) && aFrame->IsAckedWithSecEnhAck())
|
||||
if (aFrame.IsAckedWithSecEnhAck())
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
|
||||
SetLastSyncToNow();
|
||||
#else
|
||||
mLastSync = aFrame->GetTimestamp();
|
||||
mLastSync = aFrame.GetTimestamp();
|
||||
#endif
|
||||
RestartTimerAfterSyncUpdate();
|
||||
}
|
||||
@@ -158,70 +159,66 @@ exit:
|
||||
|
||||
void SubMac::CslReceiver::HandleTimer(void)
|
||||
{
|
||||
/*
|
||||
* The handler will be called once per CSL period. When the handler is called, it will set the timer to
|
||||
* fire at the next CSL sample time and call `ReceiveAt` to start sampling for the current CSL period.
|
||||
* The timer fires some time before the next sample time.
|
||||
*
|
||||
* Timer fires Timer fires
|
||||
* ^ ^
|
||||
* x-|------------|-------------------------------------x-|------------|---------------------------------------|
|
||||
* sample sleep sample sleep
|
||||
*/
|
||||
uint32_t periodUs = CslPeriodToUsec(mPeriod);
|
||||
uint32_t timeAhead, timeAfter;
|
||||
Radio::Time64 winStart;
|
||||
uint32_t winDuration;
|
||||
Window window;
|
||||
|
||||
GetWindowEdges(timeAhead, timeAfter);
|
||||
DetermineWindow(mSampleTime, window);
|
||||
Get<SubMac>().ReceiveAt(window.mStartTime.GetAsTime64(), window.mDuration, mChannel);
|
||||
|
||||
mTimer.FireAt(mSampleTime.GetAsLocalTimeMicro() + periodUs - timeAhead - kReceiveTimeAhead - GetNextCycleDrift());
|
||||
LogDebg("CSL window start %lu, duration %lu", ToUlong(window.mStartTime.GetAsTime32()), ToUlong(window.mDuration));
|
||||
|
||||
winStart = mSampleTime.GetAsTime64() - timeAhead;
|
||||
winDuration = timeAhead + timeAfter;
|
||||
|
||||
mSampleTime += periodUs;
|
||||
// Advance sample time to the next CSL period, update the radio
|
||||
// sample time on `Radio` and schedule the timer ahead of the
|
||||
// next window's start time.
|
||||
|
||||
mSampleTime += CslPeriodToUsec(mPeriod);
|
||||
Get<Radio::Radio>().UpdateCslSampleTime(mSampleTime.GetAsTime32());
|
||||
|
||||
Get<SubMac>().ReceiveAt(winStart, winDuration, mChannel);
|
||||
|
||||
LogWindow(winStart, winDuration);
|
||||
DetermineWindow(mSampleTime, window);
|
||||
mTimer.FireAt(window.mStartTime.GetAsLocalTimeMicro() - kReceiveTimeAhead);
|
||||
}
|
||||
|
||||
void SubMac::CslReceiver::GetWindowEdges(uint32_t &aAhead, uint32_t &aAfter)
|
||||
void SubMac::CslReceiver::DetermineWindow(const Radio::SyncedTime &aSampleTime, Window &aWindow) const
|
||||
{
|
||||
/*
|
||||
* CSL sample timing diagram
|
||||
* |<---------------------------------Sample--------------------------------->|<--------Sleep--------->|
|
||||
* | | |
|
||||
* |<--Ahead-->|<--UnCert-->|<--Drift-->|<--Drift-->|<--UnCert-->|<--MinWin-->| |
|
||||
* | | | | | | | |
|
||||
* ---|-----------|------------|-----------|-----------|------------|------------|----------//------------|---
|
||||
* -timeAhead CslPhase +timeAfter -timeAhead
|
||||
* CSL sample timing diagram:
|
||||
*
|
||||
* |<------------------------------------ Sample Window ----------------------------------->|
|
||||
* | |
|
||||
* |<--MinAhead-->|<-- Uncert -->|<-- Drift -->|<-- Drift -->|<-- Uncert -->|<--MinAfter-->|
|
||||
* | |<---------- Guard --------->|<---------- Guard --------->| |
|
||||
* --|--------------|--------------|-------------|-------------|--------------|---------------|---
|
||||
* ^ ^ ^
|
||||
* mStartTime SampleTime WindowEnd
|
||||
* (SampleTime - ahead) (SampleTime + after)
|
||||
*/
|
||||
uint32_t semiPeriod = CslPeriodToUsec(mPeriod) / 2;
|
||||
uint32_t elapsed = 0;
|
||||
uint32_t semiWindow;
|
||||
|
||||
uint32_t halfPeriod = CslPeriodToUsec(mPeriod) / 2;
|
||||
uint32_t guardInterval = 0;
|
||||
uint16_t uncertainty;
|
||||
uint32_t ahead;
|
||||
uint32_t after;
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
|
||||
if (mSampleTime.GetAsLocalTimeMicro() > mLastSync)
|
||||
if (aSampleTime.GetAsLocalTimeMicro() > mLastSync)
|
||||
{
|
||||
elapsed = mSampleTime.GetAsLocalTimeMicro() - mLastSync;
|
||||
guardInterval = DetermineClockDrift(aSampleTime.GetAsLocalTimeMicro() - mLastSync);
|
||||
}
|
||||
#else
|
||||
if (mSampleTime.GetAsTime64() > mLastSync)
|
||||
if (aSampleTime.GetAsTime64() > mLastSync)
|
||||
{
|
||||
elapsed = ClampToUint32(mSampleTime.GetAsTime64() - mLastSync);
|
||||
guardInterval = DetermineClockDrift(ClampToUint32(aSampleTime.GetAsTime64() - mLastSync));
|
||||
}
|
||||
#endif
|
||||
|
||||
semiWindow = DetermineClockDrift(elapsed);
|
||||
semiWindow +=
|
||||
Radio::ConvertUncertaintyToUsec(mParentAccuracy.GetUncertainty() + Get<Radio::Radio>().GetCslUncertainty());
|
||||
uncertainty = mParentAccuracy.GetUncertainty() + Get<Radio::Radio>().GetCslUncertainty();
|
||||
guardInterval += Radio::ConvertUncertaintyToUsec(uncertainty);
|
||||
|
||||
aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead);
|
||||
aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter);
|
||||
ahead = Min(guardInterval + kMinReceiveOnAhead, halfPeriod);
|
||||
after = Min(guardInterval + kMinReceiveOnAfter, halfPeriod);
|
||||
|
||||
aWindow.mStartTime = aSampleTime;
|
||||
aWindow.mStartTime -= ahead;
|
||||
|
||||
aWindow.mDuration = ahead + after;
|
||||
}
|
||||
|
||||
uint32_t SubMac::CslReceiver::DetermineClockDrift(uint32_t aIntervalUs) const
|
||||
@@ -231,8 +228,6 @@ uint32_t SubMac::CslReceiver::DetermineClockDrift(uint32_t aIntervalUs) const
|
||||
return Radio::DetermineClockDrift(clockAccuracy, aIntervalUs);
|
||||
}
|
||||
|
||||
uint32_t SubMac::CslReceiver::GetNextCycleDrift(void) const { return DetermineClockDrift(CslPeriodToUsec(mPeriod)); }
|
||||
|
||||
void SubMac::CslReceiver::SetLastSyncToNow(void)
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
|
||||
@@ -242,66 +237,68 @@ void SubMac::CslReceiver::SetLastSyncToNow(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_DEBG)
|
||||
void SubMac::CslReceiver::LogWindow(Radio::Time64 aWinStart, uint32_t aWinDuration)
|
||||
{
|
||||
LogDebg("CSL window start %lu, duration %lu", ToUlong(Radio::ConvertTime64To32(aWinStart)), ToUlong(aWinDuration));
|
||||
}
|
||||
#else
|
||||
void SubMac::CslReceiver::LogWindow(Radio::Time64, uint32_t) {}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
|
||||
void SubMac::CslReceiver::LogReceived(RxFrame *aFrame)
|
||||
void SubMac::CslReceiver::LogReceived(const RxFrame &aFrame)
|
||||
{
|
||||
static constexpr uint8_t kLogStringSize = 72;
|
||||
RxFrame::ParseInfo frameInfo;
|
||||
Radio::SyncedTime sampleTime;
|
||||
Window window;
|
||||
uint32_t margin;
|
||||
Radio::Time64 timestamp;
|
||||
uint32_t deviation;
|
||||
char signChar;
|
||||
LogLevel logLevel;
|
||||
|
||||
String<kLogStringSize> logString;
|
||||
RxFrame::ParseInfo frameInfo;
|
||||
int32_t deviation;
|
||||
uint32_t sampleTime, ahead, after;
|
||||
SuccessOrExit(frameInfo.ParseFrom(aFrame, Frame::kParseAddrFields));
|
||||
|
||||
IgnoreError(frameInfo.ParseFrom(*aFrame, Frame::kParseAddrFields));
|
||||
|
||||
VerifyOrExit((frameInfo.mAddrs.mDestination.IsShort() &&
|
||||
frameInfo.mAddrs.mDestination.GetShort() == Get<SubMac>().GetShortAddress()) ||
|
||||
(frameInfo.mAddrs.mDestination.IsExtended() &&
|
||||
frameInfo.mAddrs.mDestination.GetExtended() == Get<SubMac>().GetExtAddress()));
|
||||
VerifyOrExit(Get<SubMac>().HasAddress(frameInfo.mAddrs.mDestination));
|
||||
|
||||
LogDebg("Received frame in state %s, timestamp %lu", StateToString(Get<SubMac>().mState),
|
||||
ToUlong(Radio::ConvertTime64To32(aFrame->GetTimestamp())));
|
||||
ToUlong(Radio::ConvertTime64To32(aFrame.GetTimestamp())));
|
||||
|
||||
VerifyOrExit((Get<SubMac>().mState == kStateTimedReceive) || (Get<SubMac>().mState == kStateSleep));
|
||||
|
||||
GetWindowEdges(ahead, after);
|
||||
ahead -= kMinReceiveOnAhead;
|
||||
sampleTime = mSampleTime;
|
||||
sampleTime -= CslPeriodToUsec(mPeriod);
|
||||
|
||||
sampleTime = mSampleTime.GetAsTime32() - CslPeriodToUsec(mPeriod);
|
||||
deviation = Radio::ConvertTime64To32(aFrame->GetTimestamp()) + Radio::kHeaderPhrDuration - sampleTime;
|
||||
DetermineWindow(sampleTime, window);
|
||||
|
||||
// This logs three values (all in microseconds):
|
||||
// - Absolute sample time in which the CSL receiver expected the MHR of the received frame.
|
||||
// - Allowed margin around that time accounting for accuracy and uncertainty from both devices.
|
||||
// - Real deviation on the reception of the MHR with regards to expected sample time. This can
|
||||
// be due to clocks drift and/or CSL Phase rounding error.
|
||||
// This means that a deviation absolute value greater than the margin would result in the frame
|
||||
// not being received out of the debug mode.
|
||||
logString.Append("Expected sample time %lu, margin ±%lu, deviation %ld", ToUlong(sampleTime), ToUlong(ahead),
|
||||
static_cast<long>(deviation));
|
||||
// The `kMinReceiveOnAhead` is not considered for the margin since
|
||||
// it has no impact on understanding possible deviation errors
|
||||
// between transmitter and receiver.
|
||||
|
||||
// Treat as a warning when the deviation is not within the margins. Neither kReceiveTimeAhead
|
||||
// or kMinReceiveOnAhead/kMinReceiveOnAfter are considered for the margin since they have no
|
||||
// impact on understanding possible deviation errors between transmitter and receiver. So in this
|
||||
// case only `ahead` is used, as an allowable max deviation in both +/- directions.
|
||||
if ((deviation + ahead > 0) && (deviation < static_cast<int32_t>(ahead)))
|
||||
margin = sampleTime.GetAsTime32() - window.mStartTime.GetAsTime32();
|
||||
margin -= Min(margin, kMinReceiveOnAhead);
|
||||
|
||||
timestamp = aFrame.GetTimestamp() + Radio::kHeaderPhrDuration;
|
||||
|
||||
if (timestamp >= sampleTime.GetAsTime64())
|
||||
{
|
||||
LogDebg("%s", logString.AsCString());
|
||||
deviation = ClampToUint32(timestamp - sampleTime.GetAsTime64());
|
||||
signChar = '+';
|
||||
}
|
||||
else
|
||||
{
|
||||
LogWarn("%s", logString.AsCString());
|
||||
deviation = ClampToUint32(sampleTime.GetAsTime64() - timestamp);
|
||||
signChar = '-';
|
||||
}
|
||||
|
||||
// Treat as a warning when the deviation is not within the allowable margin.
|
||||
|
||||
logLevel = (deviation <= margin) ? kLogLevelDebg : kLogLevelWarn;
|
||||
|
||||
// The log includes three values (all in microseconds):
|
||||
// - Absolute sample time at which the CSL receiver expected the MHR
|
||||
// of the received frame.
|
||||
// - Allowed margin around that time accounting for clock drift and
|
||||
// uncertainty from both devices.
|
||||
// - Real deviation on the reception of the MHR with regards to
|
||||
// expected sample time. This can be due to clock drift and/or
|
||||
// CSL Phase rounding error.
|
||||
|
||||
LogAt(logLevel, "Expected sample time %lu, margin ±%lu, deviation %c%lu", ToUlong(sampleTime.GetAsTime32()),
|
||||
ToUlong(margin), signChar, ToUlong(deviation));
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user