Components::RtcManager
The RTC Manager component supplies spacecraft time from the Real Time Clock (RTC). Spacecraft time is uptime plus a time offset. The RTC update interrupt corrects the time offset once per second. The timeGetPort does not access the RTC hardware. If the RTC was never read successfully, the component returns uptime.
[!IMPORTANT] The code executed by the RTC Manager’s
timeGetPortmust never call into the eventing system. The timeGetPort is invoked frequently across F Prime and is used by the eventing system itself, any handler code must not use event ports, commands, or telemetry — doing so can cause deadlocks. Log any errors from thetimeGetPorthandler to the console with throttling to prevent flooding and do not allow those errors to cause the handler to fail. If the time offset was never seeded, the component must fall back to returning a monotonic uptime.The RTC update callback runs on the system workqueue thread, not on the time port, and may emit events and telemetry; it must not emit while holding the spinlock.
Typical Usage
TIME_SET Command Usage
- The component is instantiated and initialized during system startup
- A ground station sends a
TIME_SETcommand with the desired time - On each command, the component:
- Cancels any running sequences
- Validates the time data (year >= 1900, month [1-12], day [1-31], hour [0-23], minute [0-59], second [0-59])
- Emits validation failure events if any field is invalid
- Sets the time on the RTC if validation passes
- Seeds the time offset from the new time. Reported time can step backward one time
- Emits a
TimeSetevent with the previous time if the time is set successfully - Emits a
TimeNotSetevent if the time is not set successfully - Emits a
DeviceNotReadyevent if the device is not ready
TIMEBASE Parameter Usage
- A ground station sends
TIMEBASE_PRM_SETwithTB_PROC_TIMEorTB_SC_TIME, andTIMEBASE_PRM_SAVEto persist the choice across boots - When the parameter is updated the component:
- Cancels any running sequences, since the change in reported time may affect them
- Emits a
TimeBaseChangedevent with the timebase now in use
- On each
timeGetPortcall, if the parameter isTB_PROC_TIMEthe component returns uptime without touching the RTC
The parameter uses Rtc.TimeBase, a component-local copy of the upstream F Prime TimeBase enum carrying only the bases this component can source (TB_PROC_TIME and TB_SC_TIME). It lives in its own Rtc module because a Drv.TimeBase would shadow the global TimeBase enum inside generated Drv code. The default is TB_SC_TIME.
Alarm Interrupt Path
The RV3028 signals an alarm on its ~INT pin. ~INT is open-drain and active low. An external 10 kΩ resistor (R5) pulls it up. The device tree declares the pin GPIO_ACTIVE_LOW. On the falling edge, the Zephyr driver reads the RTC status and calls the alarm callback.
The RV3028 keeps its alarm flag (AF) through a processor reset. If AF is stale, the next alarm triggers immediately when its callback is registered. The component clears AF with rtc_alarm_is_pending():
- In configure(), before any alarm callback is registered.
- In ALARM_SET, before the alarm time is written. This also covers a failed clear in configure().
- In ALARM_CANCEL, as part of disarming the alarm.
- After an alarm triggers, as part of disarming the alarm.
The driver disables the alarm interrupt at init. Only the callback registration in ALARM_SET enables it. Thus a stale AF cannot hold ~INT low before a callback exists.
The component disarms the alarm on ALARM_CANCEL and after an alarm triggers. Disarming happens in this order:
1. Unregister the callback with rtc_alarm_set_callback(dev, 0, NULL, NULL). This also disables the alarm interrupt.
2. Write the disabled alarm with rtc_alarm_set_time() and a mask of 0.
3. Clear AF with rtc_alarm_is_pending().
This order matters. Writing the disabled alarm in step 2 can set AF on the RV3028. If the alarm interrupt is still enabled at that point, this raises a false AlarmTriggered event. Disabling the interrupt first, in step 1, prevents this.
[!NOTE] The RV3028 alarm has minute resolution. An alarm set for a later second in the current minute triggers immediately (issue #521).
ALARM_SET Command Usage
- The component is instantiated and initialized during system startup
- A ground station sends a
ALARM_SETcommand with the desired time - On each command, the component:
- Validates that the alarm is at a future date
- Emits 'AlarmNotSet' if time is not valid or if an alarm is already present
- Clears a stale alarm flag with
rtc_alarm_is_pending(). If this fails, emitsAlarmHardwareErrorand respondsEXECUTION_ERROR - Sets the time to the RTC alarm if validation passes
- Emits a
AlarmSetevent with the previous time if the alarm is set successfully - Emits a
AlarmNotSetevent if the alarm is not set successfully
ALARM_CANCEL Command Usage
- A ground station sends a
ALARM_CANCELcommand with the desired alarm ID to cancel - On each command, the component:
- Validates that an alarm exists
- Emits 'AlarmNotCanceled' if an alarm is already present
- Cancels the present alarm if it is present on the system
- Disarms the alarm: unregisters the callback, writes the disabled alarm (mask 0), then clears a stale alarm flag with
rtc_alarm_is_pending(). If any step fails, emitsAlarmHardwareErrorand respondsEXECUTION_ERROR - Emits a
AlarmCanceledevent with the previous time if the alarm is canceled successfully - Emits a
AlarmNotCanceledevent if the alarm is not canceled successfully
ALARM_LIST Command Usage
- A ground station sends a
ALARM_LISTcommand - On each command, the component:
- Validates that an alarm exists
- Emits 'AlarmNotSet' if no alarm is present
- Emits 'AlarmSet' if an alarm is present and returns it's information to the ground station
timeGetPort Port Usage
- The component is instantiated and initialized during system startup
- In a deployment topology, a
time connectionrelation is made to sync FPrime's internal clock - On each call, the component:
- Returns uptime with the
TB_PROC_TIMEtime base if theTIMEBASEparameter isTB_PROC_TIME - Reads uptime from
k_uptime_ticks(), in microseconds - Locks the spinlock, calls
TimeDiscipline::read(), and unlocks - If the time offset is seeded: returns uptime plus time offset with the
TB_SC_TIMEtime base - If the time offset is not seeded: logs
RtcNotDisciplinedto the console (throttled) and returns uptime with theTB_PROC_TIMEtime base - Does not access the RTC hardware, and does not emit events or telemetry
- Returns uptime with the
Time Discipline
Terms
| Term | Meaning |
|---|---|
| Discipline | Keep a fine-grained clock (uptime) aligned to an accurate reference (the RTC) with small, regular corrections |
| Second edge | The moment the RTC seconds count increments. The RV3028 raises its update interrupt here |
| Uptime | Time since boot from k_uptime_ticks(), in microseconds. Uptime never decreases |
| Time offset | RTC time minus uptime, in microseconds |
| Seed | Set the time offset from one RTC read. Occurs at boot and on TIME_SET |
| Correction | New time offset minus old time offset, at one RTC update interrupt |
| Step | A correction with a magnitude of more than 100 ms |
| Stale sample | An RTC read whose seconds did not increase since the last seed or applied correction |
Rules
- Reported time = uptime + time offset. The microseconds field is reported time modulo 1 000 000.
- At each RTC second edge the update callback samples uptime, then reads the RTC. New time offset = RTC seconds × 1 000 000 − uptime. Uptime is sampled first because the edge has already happened; sampling it after the I²C read would add the read time to the offset. The RTC seconds do not change during the read.
- A stale sample is ignored. The driver gives one such sample when the callback is registered. A rejected sample does not change the last RTC seconds, so one bad far-future sample cannot cause good samples to be ignored.
- If no seed exists, the first correction seeds the time offset.
- A correction of 100 ms or less is applied. Reported time does not decrease: after a backward correction it holds at the last reported value until uptime + time offset passes it.
- A correction of more than 100 ms is rejected, except when the previous correction of more than 100 ms differs from it by 100 ms or less, has smaller RTC seconds, and no correction was applied and no seed occurred since. Then it is applied as a step and
TimeSteppedis emitted. A late callback gives a false backward correction and a bad RTC read (for example, year 2099) gives a false correction in either direction; neither normally repeats on the next edge. After a backward step, reported time can decrease one time. - If the RTC read fails, or the RTC seconds are outside the RV3028 range (years 2000 to 2099), the time offset does not change. The callback increments
DisciplineReadFaultsand emitsDisciplineReadFailedorDisciplineSampleImplausible. The boot seed andTIME_SETuse the same range check. - If the RTC lost power, it has no time until
TIME_SET, and the driver returns-ENODATA. This is not a read fault. The callback skips the sample without counting it and emitsRtcTimeNotSetonce. The next good read re-arms the event. TIME_SETseeds the time offset. The RV3028 resets its sub-second divider when the seconds are written, so this seed has no sub-second error.- The boot seed has an error in [0, 1) s because the sub-second position at boot is not known. An error of 100 ms or less is removed by the first correction; a larger error is stepped forward at the second edge.
Structure
TimeDisciplineholds the time offset, the last reported time, the last RTC seconds, and the pending step candidate. It is plain C++ with no Zephyr or F Prime includes, so the unit tests use it directly.RtcManagerprotectsTimeDisciplinewith ak_spinlock. ThetimeGetPortholds it only forread(), the update callback only forcorrect(). The callback emits telemetry and events after it releases the lock. A spinlock is used because thetimeGetPortmust never block and the locked section is a few integer operations.
Limits
- Reported time is late by the callback delay after the RTC second edge: approximately 3 ms, jitter ±0.35 ms (bench). The first correction after
TIME_SETis therefore approximately −3 ms. - Processor uptime runs approximately 500 ppm slow against the RV3028 on the V5e (issue #522), absorbed by a forward correction of approximately 0.5 ms each second. The driver calls the alarm callback before the update callback for the same edge, so an alarm event can be stamped up to approximately 1 ms before its second.
- The v5c, v5d, and v5e boards enable
CONFIG_RTC_UPDATE. On other boards, or ifrtc_update_set_callback()fails, the component logs this to the console and runs on the seed alone. - If the update callback is delayed by more than 100 ms on two edges in a row (for example, a busy system workqueue), the component applies a false backward step, and a forward step when the delay ends.
- On orbit, setting
TIMEBASEtoTB_PROC_TIMEbypasses the RTC and the time offset.
Requirements
| Name | Description | Validation |
|---|---|---|
| RtcManager-001 | The RTC Manager has a command that sets the time on the RTC | Integration test |
| RtcManager-002 | The timeGetPort returns spacecraft time (TB_SC_TIME) or uptime (TB_PROC_TIME) |
Integration test |
| RtcManager-003 | If the RTC was never read successfully, the timeGetPort returns uptime. A later RTC read failure does not change the time offset |
Unit tests (NotSeededReturnsFalse) and code review |
| RtcManager-004 | A time set event is emitted if the time is set successfully, including the previous time | Integration test |
| RtcManager-005 | A time not set event is emitted if the time is not set successfully | Integration test |
| RtcManager-006 | The RTC Manager validates time data and emits validation failure events for invalid fields | Integration test |
| RtcManager-007 | Time increments continuously regardless of RTC availability | Integration test |
| RtcManager-008 | The microseconds field is always in [0, 999999] | Unit tests |
| RtcManager-009 | Reported time never decreases, except after TIME_SET (026) or a step (025) |
Unit tests and integration test |
| RtcManager-010 | During a time set command, before the new time is set, RTC Manager informs a sequence cancellation port | Integration test |
| RtcManager-011 | An alarm set for a future minute triggers through the RTC interrupt line and emits an event | Integration test |
| RtcManager-012 | An alarm is set and then canceled, an event is emitted when the alarm is canceled | Integration test |
| RtcManager-013 | An alarm cancel command is sent when no alarm is present and an event is emitted | Integration test |
| RtcManager-014 | Alarm list is tested before and after an alarm is set to ensure proper behavior | Integration test |
| RtcManager-015 | Alarm is set with an impossible time and an event is emitted, the alarm is not set | Integration test |
| RtcManager-016 | Alarm is set and then another alarm is set. An event is emitted and the second alarm is not set | Integration test |
| RtcManager-017 | A timeGetPort call before the time offset is seeded is logged to the console with throttling to prevent flooding |
Manual testing and code review |
| RtcManager-018 | Spacecraft switches between RTC time and PROC time and listens for event emission | Integration test |
| RtcManager-019 | A stale alarm flag is cleared at init, on ALARM_SET, and on ALARM_CANCEL. A new alarm does not trigger early |
Manual testing (procedure in the PR #524 description) |
| RtcManager-020 | ALARM_CANCEL does not emit AlarmTriggered. A triggered alarm emits AlarmTriggered one time |
Integration test |
| RtcManager-021 | The timeGetPort does not access the RTC hardware |
Code review |
| RtcManager-022 | Spacecraft time = uptime + time offset. The RTC update interrupt corrects the time offset once per second. If no seed exists, the first correction seeds the time offset | Unit tests and integration test |
| RtcManager-023 | A correction whose RTC seconds did not increase since the last seed or applied correction is ignored. A rejected sample does not cause later samples to be ignored | Unit tests |
| RtcManager-024 | A backward correction of 100 ms or less does not decrease reported time | Unit tests |
| RtcManager-025 | A correction of more than 100 ms is applied as a step only after two sequential corrections of more than 100 ms that differ by 100 ms or less, in either direction. A throttled TimeStepped event is emitted |
Unit tests |
| RtcManager-026 | TIME_SET seeds the time offset. Reported time can step backward one time |
Unit tests and integration test |
| RtcManager-027 | The correction is written to telemetry each second | Integration test |
| RtcManager-028 | One late update callback does not step reported time | Unit tests |
| RtcManager-029 | One bad RTC read does not step reported time. RTC seconds outside years 2000 to 2099 are not used | Unit tests |
| RtcManager-030 | A failed or implausible update callback read increments DisciplineReadFaults and emits a throttled warning. A rejected sample increments DisciplineRejects |
Code review |
Port Descriptions
| Name | Description |
|---|---|
| timeGetPort | Time port for FPrime topology connection to get spacecraft time (uptime + time offset) |
| alarmTriggered | Output port to keep track of when an alarm triggers |
Commands
| Name | Description |
|---|---|
| TIME_SET | Sets the time on the RTC with validation of all time fields |
| ALARM_SET | Sets the RTC alarm with as much precision as hardware allows |
| ALARM_CANCEL | Cancels the current alarm |
| ALARM_LIST | Responds with info about the current set alarm |
Parameters
| Name | Description | Type | Default |
|---|---|---|---|
| TIMEBASE | Decides the timebase that timeGetPort reports | Rtc.TimeBase | TB_SC_TIME |
Telemetry
All three channels are sent in the Timing packet (id 23, group 5), whose downlink rate depends on telemetryDelay.
| Name | Type | Description |
|---|---|---|
| TimeCorrectionUs | I64 | Last applied or stepped correction of the time offset, in microseconds. Positive moves reported time forward. Not written for ignored or rejected samples or failed reads, so check DisciplineReadFaults and DisciplineRejects |
| DisciplineReadFaults | U32 | Count of update callback RTC reads that failed or gave seconds outside years 2000 to 2099. Reads of an RTC with no time (-ENODATA) are not counted |
| DisciplineRejects | U32 | Count of update callback samples rejected as an unconfirmed step. One at boot is normal (rule 10) |
Events
| Name | Description |
|---|---|
| DeviceNotReady | Emitted when the RTC device is not ready during TIME_SET command |
| TimeSet | Emitted on successful time set, includes previous time (seconds and microseconds) |
| TimeNotSet | Emitted on unsuccessful time set or if one exists when alarm list is run |
| TimeBaseChanged | Emitted when the TimeBase param is updated to signal the current TimeBase |
| TimeStepped | Emitted when a correction of more than 100 ms is applied as a step. Includes the correction in microseconds. Throttled to 5, throttle resets after 60 s |
| DisciplineReadFailed | Emitted when an update callback RTC read fails. Includes the driver return code. Throttled to 5, throttle resets after 60 s |
| DisciplineSampleImplausible | Emitted when an update callback RTC read gives seconds outside years 2000 to 2099. Includes the seconds, or −1 if the conversion failed. Throttled to 5, throttle resets after 60 s |
| RtcTimeNotSet | Emitted when an update callback RTC read finds no time because the RTC lost power (-ENODATA). Time discipline waits for TIME_SET. Throttled to 1, throttle resets on the next good read |
| AlarmSet | Emitted when alarm is successfully set |
| AlarmNotSet | Emitted when alarm cannot be set or if it is not set when alarm list is run |
| AlarmTriggered | Emitted when an alarm fires |
| AlarmCanceled | Emitted when an alarm is canceled |
| AlarmNotCanceled | Emitted when an alarm cannot be canceled |
| AlarmHardwareError | Emitted when the RTC driver returns an error for an alarm operation, including the stale alarm flag clear |
| YearValidationFailed | Emitted when provided year is invalid (should be >= 1900) |
| MonthValidationFailed | Emitted when provided month is invalid (should be [1-12]) |
| DayValidationFailed | Emitted when provided day is invalid (should be [1-31]) |
| HourValidationFailed | Emitted when provided hour is invalid (should be [0-23]) |
| MinuteValidationFailed | Emitted when provided minute is invalid (should be [0-59]) |
| SecondValidationFailed | Emitted when provided second is invalid (should be [0-59]) |
Class Diagram
RTC Manager Class Diagram
classDiagram
namespace Drv {
class RtcManagerComponentBase {
<<Auto-generated>>
}
class RtcManager {
- m_dev: const device*
- m_lock: k_spinlock
- m_discipline: TimeDiscipline
- m_RtcNotDisciplinedThrottle: atomic~bool~
- m_disciplineReadFaults: U32
- m_disciplineRejects: U32
- m_curr_mask: U16
- m_alarm_time: rtc_time
+ RtcManager(const char* const compName)
+ ~RtcManager()
+ configure(dev: const device*) void
- timeGetPort_handler(portNum: FwIndexType, time: Fw::Time&) void
- TIME_SET_cmdHandler(opCode: FwOpcodeType, cmdSeq: U32, t: Drv::TimeData) void
- ALARM_SET_cmdHandler(opCode: FwOpcodeType, cmdSeq: U32, t: Drv::TimeData) void
- ALARM_CANCEL_cmdHandler(opCode: FwOpcodeType, cmdSeq: U32, ID: U16) void
- ALARM_LIST_cmdHandler(opCode: FwOpcodeType, cmdSeq: U32) void
- parameterUpdated(id: FwPrmIdType) void
- static_alarm_callback_t(dev: const device*, id: uint16_t, user_data: void*) void$
- alarm_callback_t(dev: const device*, id: uint16_t) void
- static_update_callback_t(dev: const device*, user_data: void*) void$
- update_callback_t() void
- readRtcSeconds(rtc_s: int64_t&, rc: int&) RtcRead
- rtcTimeToSeconds(time_rtc: const rtc_time&, rtc_s: int64_t&) bool$
- seedDiscipline(rtc_s: int64_t) void
- uptimeUs() int64_t$
- log_CONSOLE_RtcNotDisciplined() void
- log_CONSOLE_RtcNotDisciplined_ThrottleClear() void
- timeDataIsValid(t: Drv::TimeData) bool
}
}
RtcManagerComponentBase <|-- RtcManager : inherits
RtcManager *-- TimeDiscipline : owns
Time Discipline Class Diagram
classDiagram
namespace Drv {
class TimeDiscipline {
+ STEP_THRESHOLD_US: int64_t = 100000$
+ STEP_CONFIRMATIONS: int = 2$
+ RTC_MIN_S: int64_t = 946684800$
+ RTC_MAX_S: int64_t = 4102444799$
- m_disciplined: bool = false
- m_offset_us: int64_t = 0
- m_last_reported_us: int64_t = 0
- m_last_rtc_s: int64_t = 0
- m_pending_count: int = 0
- m_pending_correction_us: int64_t = 0
- m_pending_rtc_s: int64_t = 0
+ isPlausibleRtcSeconds(rtc_s: int64_t) bool$
+ seed(rtc_s: int64_t, uptime_us: int64_t) void
+ correct(rtc_s: int64_t, uptime_us_at_edge: int64_t) CorrectionResult
+ read(uptime_us: int64_t, seconds: uint32_t&, useconds: uint32_t&) bool
}
class Correction {
<<enumeration>>
IGNORED
APPLIED
REJECTED
STEPPED
}
class CorrectionResult {
+ kind: Correction
+ correction_us: int64_t
}
}
TimeDiscipline ..> CorrectionResult : returns
CorrectionResult *-- Correction
Sequence Diagrams
timeGetPort port
The timeGetPort port is called from a time connection in a deployment topology. It uses only uptime and the time offset. When the TIMEBASE parameter is TB_PROC_TIME, it returns uptime.
Seeded
sequenceDiagram
participant Deployment Time Connection
participant RTC Manager
participant System Clock
participant Time Discipline
Deployment Time Connection->>RTC Manager: Call timeGetPort time port
RTC Manager->>System Clock: k_uptime_ticks()
System Clock-->>RTC Manager: Uptime in ticks, converted to microseconds
RTC Manager->>RTC Manager: Lock spinlock
RTC Manager->>Time Discipline: read(uptime_us)
Time Discipline-->>RTC Manager: true, seconds, useconds
RTC Manager->>RTC Manager: Unlock spinlock
RTC Manager-->>Deployment Time Connection: Return Fw::Time with time base `TB_SC_TIME`
Not Seeded
sequenceDiagram
participant Console Log
participant Deployment Time Connection
participant RTC Manager
participant System Clock
participant Time Discipline
Deployment Time Connection->>RTC Manager: Call timeGetPort time port
RTC Manager->>System Clock: k_uptime_ticks()
System Clock-->>RTC Manager: Uptime in ticks, converted to microseconds
RTC Manager->>RTC Manager: Lock spinlock
RTC Manager->>Time Discipline: read(uptime_us)
Time Discipline-->>RTC Manager: false
RTC Manager->>RTC Manager: Unlock spinlock
RTC Manager->>Console Log: Log RtcNotDisciplined (throttled)
RTC Manager-->>Deployment Time Connection: Return uptime with time base `TB_PROC_TIME`
RTC Update Callback
The RV3028 asserts ~INT at each RTC second edge. The Zephyr driver calls the update callback on the system workqueue thread.
sequenceDiagram
participant RTC Sensor
participant Zephyr RTC Driver
participant RTC Manager
participant Time Discipline
participant Telemetry
participant Event Log
RTC Sensor->>Zephyr RTC Driver: ~INT falling edge (UF set)
Zephyr RTC Driver->>RTC Sensor: Read status, clear UF
Zephyr RTC Driver->>RTC Manager: update_callback_t()
RTC Manager->>RTC Manager: uptime_us = uptimeUs()
RTC Manager->>RTC Sensor: readRtcSeconds() — rtc_get_time() + timeutil_timegm()
alt RTC has no time since power loss (-ENODATA)
RTC Manager->>Event Log: RtcTimeNotSet (throttle 1)
Note over RTC Manager: Return. Not counted as a fault
else Read fails, or RTC seconds outside years 2000 to 2099
RTC Manager->>Telemetry: tlmWrite_DisciplineReadFaults(count)
RTC Manager->>Event Log: DisciplineReadFailed(rc) or DisciplineSampleImplausible(rtc_s)
Note over RTC Manager: Return. Time offset does not change
else Read succeeds
RTC Manager->>Event Log: RtcTimeNotSet_ThrottleClear()
RTC Manager->>RTC Manager: Lock spinlock
RTC Manager->>Time Discipline: correct(rtc_s, uptime_us)
Time Discipline-->>RTC Manager: CorrectionResult
RTC Manager->>RTC Manager: Unlock spinlock
alt IGNORED
Note over RTC Manager: No telemetry, no event
else REJECTED
RTC Manager->>Telemetry: tlmWrite_DisciplineRejects(count)
else APPLIED
RTC Manager->>Telemetry: tlmWrite_TimeCorrectionUs(correction_us)
else STEPPED
RTC Manager->>Telemetry: tlmWrite_TimeCorrectionUs(correction_us)
RTC Manager->>Event Log: log_WARNING_LO_TimeStepped(correction_us)
end
end
parameterUpdated
sequenceDiagram
participant Ground Station
participant RTC Manager
Ground Station->>RTC Manager: Command TIMEBASE_PRM_SET sent
RTC Manager->>RTC Manager: Validate the command
RTC Manager->>RTC Manager: Cancel running sequences
RTC Manager->>Ground Station: Command response ok
Note over RTC Manager: Command for saving the parameter to memory is TIMEBASE_PRM_SAVE which flows the same except it persists across boots.
TIME_SET Command
The TIME_SET command is called to set the current time on the RTC. The component validates all time fields before attempting to set the time.
Success
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command TIME_SET with Drv::TimeData struct
RTC Manager->>RTC Manager: Check device_is_ready()
RTC Manager->>RTC Manager: Validate time data (timeDataIsValid)
Note over RTC Manager: Check year >= 1900<br/>month [1-12], day [1-31]<br/>hour [0-23], min [0-59], sec [0-59]
RTC Manager->>RTC Manager: Store previous time via getTime()
RTC Manager->>Zephyr RTC API: Set time via rtc_set_time()
Zephyr RTC API->>RTC Sensor: Set time
RTC Sensor-->>Zephyr RTC API: Return success
Zephyr RTC API-->>RTC Manager: Return success (status = 0)
RTC Manager->>RTC Manager: Lock, TimeDiscipline::seed(new time, uptime_us), unlock
RTC Manager->>Event Log: Emit TimeSet event (with previous time)
RTC Manager-->>Ground Station: Command response OK
Validation Failure
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
Ground Station->>RTC Manager: Command TIME_SET with invalid Drv::TimeData
RTC Manager->>RTC Manager: Check device_is_ready()
RTC Manager->>RTC Manager: Validate time data (timeDataIsValid)
Note over RTC Manager: Validation fails
RTC Manager->>Event Log: Emit validation failure events<br/>(YearValidationFailed, etc.)
RTC Manager->>Event Log: Emit TimeNotSet event
RTC Manager-->>Ground Station: Command response VALIDATION_ERROR
Device Not Ready
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
Ground Station->>RTC Manager: Command TIME_SET with Drv::TimeData struct
RTC Manager->>RTC Manager: Check device_is_ready()
Note over RTC Manager: Device not ready
RTC Manager->>Event Log: Emit DeviceNotReady event
RTC Manager-->>Ground Station: Command response EXECUTION_ERROR
Time Not Set (RTC Failure)
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command TIME_SET with Drv::TimeData struct
RTC Manager->>RTC Manager: Check device_is_ready()
RTC Manager->>RTC Manager: Validate time data (timeDataIsValid)
RTC Manager->>Zephyr RTC API: Set time via rtc_set_time()
Zephyr RTC API->>RTC Sensor: Set time
RTC Sensor-->>Zephyr RTC API: Return failure
Zephyr RTC API-->>RTC Manager: Return failure (status != 0)
RTC Manager->>Event Log: Emit TimeNotSet event
RTC Manager-->>Ground Station: Command response EXECUTION_ERROR
ALARM_SET Command
The ALARM_SET command is called to set the current time on the RTC alarm. The component validates that the time is in the future before setting the alarm
Success
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command ALARM_SET with Drv::TimeData struct
RTC Manager->>RTC Manager: Validate alarm not present on system
RTC Manager->>RTC Manager: Validate time data (timeDataIsValid)
RTC Manager->>Zephyr RTC API: Clear stale alarm flag via rtc_alarm_is_pending()
RTC Manager->>Zephyr RTC API: Set alarm time via rtc_alarm_set_time()
Zephyr RTC API->>RTC Sensor: Set rtc alarm time
RTC Sensor-->>Zephyr RTC API: Return success
Zephyr RTC API-->>RTC Manager: Return success (status = 0)
RTC Manager->>Event Log: Emit AlarmSet event (with previous time)
RTC Manager-->>Ground Station: Command response OK
Failure
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command ALARM_SET with Drv::TimeData struct
RTC Manager->>RTC Manager: Validate alarm not present on system
RTC Manager->>RTC Manager: Validate time data (timeDataIsValid)
RTC Manager->>Zephyr RTC API: Clear stale alarm flag via rtc_alarm_is_pending()
RTC Manager->>Zephyr RTC API: Set alarm time via rtc_alarm_set_time()
Zephyr RTC API->>RTC Sensor: Set rtc alarm time
RTC Sensor-->>Zephyr RTC API: Return failure
Zephyr RTC API-->>RTC Manager: Return failure (nonzero return code)
RTC Manager->>Event Log: Emit AlarmNotSet event (with previous time)
RTC Manager-->>Ground Station: Command response Execution Error
ALARM_CANCEL Command
The ALARM_CANCEL command is called to cancel the RTC alarm. component validates that an alarm is present and can be accessed
Success
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command ALARM_CANCEL with Uint16_t ID
RTC Manager->>RTC Manager: Validate alarm is present on system
RTC Manager->>Zephyr RTC API: Unregister callback via rtc_alarm_set_callback(dev, 0, NULL, NULL)
Zephyr RTC API->>RTC Sensor: Disable alarm interrupt (AIE)
Zephyr RTC API-->>RTC Manager: Return success (status = 0)
RTC Manager->>Zephyr RTC API: Set alarm mask to 0 via rtc_alarm_set_time()
Zephyr RTC API->>RTC Sensor: Set rtc alarm time
RTC Sensor-->>Zephyr RTC API: Return success
Zephyr RTC API-->>RTC Manager: Return success (status = 0)
RTC Manager->>Zephyr RTC API: Clear stale alarm flag via rtc_alarm_is_pending()
Zephyr RTC API->>RTC Sensor: Read status, clear AF
Zephyr RTC API-->>RTC Manager: Return 0 or 1 (both are success)
RTC Manager->>Event Log: Emit AlarmCanceled event (with ID)
RTC Manager-->>Ground Station: Command response OK
Failure
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command ALARM_CANCEL with Uint16_t ID
RTC Manager->>RTC Manager: Validate alarm is present on system
RTC Manager->>Zephyr RTC API: Unregister callback via rtc_alarm_set_callback(dev, 0, NULL, NULL)
Zephyr RTC API->>RTC Sensor: Disable alarm interrupt (AIE)
Zephyr RTC API-->>RTC Manager: Return success (status = 0)
RTC Manager->>Zephyr RTC API: Set alarm mask to 0 via rtc_alarm_set_time()
Zephyr RTC API->>RTC Sensor: Set rtc alarm time
RTC Sensor-->>Zephyr RTC API: Return Failure
Zephyr RTC API-->>RTC Manager: Return Failure (nonzero return code)
RTC Manager->>Event Log: Emit AlarmNotCanceled event (with ID)
RTC Manager-->>Ground Station: Command response Execution error
Alarm Trigger
The alarm triggers through the ~INT falling edge. No command is in the path.
sequenceDiagram
participant RTC Sensor
participant Zephyr RTC Driver
participant RTC Manager
participant Event Log
participant Downstream Components
RTC Sensor->>RTC Sensor: Alarm time matches, set AF
RTC Sensor->>Zephyr RTC Driver: ~INT falling edge (GPIO interrupt)
Zephyr RTC Driver->>Zephyr RTC Driver: Submit work to system workqueue
Zephyr RTC Driver->>RTC Sensor: Read status, clear AF
Zephyr RTC Driver->>RTC Manager: alarm_callback_t(dev, id)
RTC Manager->>Event Log: Emit AlarmTriggered event (with ID)
RTC Manager->>Downstream Components: alarmTriggered port
RTC Manager->>Zephyr RTC Driver: Unregister callback via rtc_alarm_set_callback(dev, 0, NULL, NULL)
Zephyr RTC Driver->>RTC Sensor: Disable alarm interrupt (AIE)
RTC Manager->>Zephyr RTC Driver: Set alarm mask to 0 via rtc_alarm_set_time()
RTC Manager->>Zephyr RTC Driver: Clear stale alarm flag via rtc_alarm_is_pending()
Zephyr RTC Driver->>RTC Sensor: Read status, clear AF
configure()
sequenceDiagram
participant Topology
participant RTC Manager
participant Zephyr RTC API
participant Event Log
Topology->>RTC Manager: configure(dev)
RTC Manager->>Zephyr RTC API: rtc_alarm_get_supported_fields()
RTC Manager->>Zephyr RTC API: Clear stale alarm flag via rtc_alarm_is_pending()
alt Return code < 0
RTC Manager->>Event Log: Emit AlarmHardwareError event
end
RTC Manager->>Zephyr RTC API: readRtcSeconds() — one polled read
alt Read succeeds
RTC Manager->>RTC Manager: Lock, TimeDiscipline::seed(rtc_s, uptime_us), unlock
end
RTC Manager->>Zephyr RTC API: rtc_update_set_callback(static_update_callback_t)
alt Return code != 0
Note over RTC Manager: Log to console. No corrections (seed only)
end
If the boot read fails, or the RTC seconds are outside years 2000 to 2099, the component is not seeded. The first update callback with a good read seeds it.
ALARM_LIST Command
The ALARM_LIST command is called to retrieve information about the rtc alarm's current status
Present
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command ALARM_LIST
RTC Manager->>RTC Manager: Determine whether an alarm is present or not with rtc_alarm_get_time()
RTC Manager->>Zephyr RTC API: retrieve alarm information (time, ID, mask)
Zephyr RTC API->>RTC Sensor: retrieve alarm information (time, ID, mask)
RTC Sensor-->>Zephyr RTC API: return mask
Zephyr RTC API-->>RTC Manager: return mask and determine whether it is zero
RTC Manager->>Event Log: Emit alarmSet event with the information of the present alarm
RTC Manager-->>Ground Station: Command response OK
Absent
sequenceDiagram
participant Ground Station
participant Event Log
participant RTC Manager
participant Zephyr RTC API
participant RTC Sensor
Ground Station->>RTC Manager: Command ALARM_LIST
RTC Manager->>RTC Manager: Determine whether an alarm is present or not with rtc_alarm_get_time()
RTC Manager->>Zephyr RTC API: retrieve alarm information (time, ID, mask)
Zephyr RTC API->>RTC Sensor: retrieve alarm information (time, ID, mask)
RTC Sensor-->>Zephyr RTC API: return mask
Zephyr RTC API-->>RTC Manager: return mask and determine whether it is zero
RTC Manager->>Event Log: Emit alarmNotSet event
RTC Manager-->>Ground Station: Command response OK
Change Log
| Date | Description |
|---|---|
| 2025-9-18 | Initial RTC Manager component |
| 2025-11-14 | Added monotonic uptime failover when RTC unavailable, input validation for TIME_SET command, TEST_UNCONFIGURE_DEVICE test command, and console logging for device not ready conditions |
| 2025-12-26 | Ensured sub-second time is monotonic; added unit tests for sub-second time calculation; removed TEST_UNCONFIGURE_DEVICE |
| 2026-04-02 | Added basic functionality for setting and canceling RTC alarms |
| 2026-04-09 | Hardening for more consistent behavior |
| 2026-09-22 | Fixed alarm interrupt polarity (~INT is active low). Clear stale alarm flag at init and on ALARM_CANCEL. Integration tests verify the interrupt path |
| 2026-09-22 | Disarm the alarm in a fixed order (unregister callback, write mask 0, clear AF) on ALARM_CANCEL and after an alarm triggers, so writing the disabled alarm cannot raise a false AlarmTriggered |
| 2026-09-23 | Spacecraft time = uptime + time offset, corrected once per second by the RTC update interrupt. The timeGetPort does not access the RTC. TimeDiscipline replaces RtcHelper. Added time discipline telemetry and events |