osal_rs/freertos/timer.rs
1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21//! Software timer support for FreeRTOS.
22//!
23//! This module provides software timers that run callbacks at specified intervals.
24//! Timers can be one-shot or auto-reloading (periodic) and execute their callbacks
25//! in the timer daemon task context.
26
27use core::any::Any;
28use core::fmt::{Debug, Display};
29use core::ops::Deref;
30use core::ptr::null_mut;
31
32use alloc::boxed::Box;
33use alloc::string::{String, ToString};
34use alloc::sync::Arc;
35
36use crate::freertos::ffi::pdPASS;
37use crate::to_c_str;
38use crate::traits::{ToTick, TimerParam, TimerFn, TimerFnPtr};
39use crate::utils::{OsalRsBool, Result, Error};
40use super::ffi::{TimerHandle, pvTimerGetTimerID, xTimerCreate, osal_rs_timer_start, osal_rs_timer_change_period, osal_rs_timer_delete, osal_rs_timer_reset, osal_rs_timer_stop};
41use super::types::{TickType};
42
43/// A software timer that executes a callback at regular intervals.
44///
45/// Timers can be configured as:
46/// - **One-shot**: Executes once after the specified period
47/// - **Auto-reload**: Executes repeatedly at the specified interval
48///
49/// Timer callbacks execute in the context of the timer daemon task, not in
50/// interrupt context. This means they can call most RTOS functions safely.
51///
52/// # Important Notes
53///
54/// - Timer callbacks should complete quickly to avoid delaying other timers
55/// - Callbacks must not block indefinitely
56/// - Requires `configUSE_TIMERS = 1` in FreeRTOSConfig.h
57///
58/// # Examples
59///
60/// ## One-shot timer
61///
62/// ```ignore
63/// use osal_rs::os::{Timer, TimerFn};
64/// use core::time::Duration;
65///
66/// let timer = Timer::new_with_to_tick(
67/// "oneshot",
68/// Duration::from_secs(1),
69/// false, // Not auto-reload (one-shot)
70/// None,
71/// |timer, param| {
72/// println!("Timer fired once!");
73/// Ok(param)
74/// }
75/// ).unwrap();
76///
77/// timer.start_with_to_tick(Duration::from_millis(10)).unwrap();
78/// ```
79///
80/// ## Periodic timer
81///
82/// ```ignore
83/// use osal_rs::os::{Timer, TimerFn};
84/// use core::time::Duration;
85///
86/// let timer = Timer::new_with_to_tick(
87/// "periodic",
88/// Duration::from_millis(500),
89/// true, // Auto-reload (periodic)
90/// None,
91/// |timer, param| {
92/// println!("Tick every 500ms");
93/// Ok(param)
94/// }
95/// ).unwrap();
96///
97/// timer.start_with_to_tick(Duration::from_millis(10)).unwrap();
98///
99/// // Stop after some time
100/// Duration::from_secs(5).sleep();
101/// timer.stop_with_to_tick(Duration::from_millis(10));
102/// ```
103///
104/// ## Timer with custom parameters
105///
106/// ```ignore
107/// use osal_rs::os::{Timer, TimerFn, TimerParam};
108/// use alloc::sync::Arc;
109/// use core::time::Duration;
110///
111/// struct CounterData {
112/// count: u32,
113/// }
114///
115/// let data = Arc::new(CounterData { count: 0 });
116/// let param: TimerParam = data.clone();
117///
118/// let timer = Timer::new_with_to_tick(
119/// "counter",
120/// Duration::from_secs(1),
121/// true,
122/// Some(param),
123/// |timer, param| {
124/// if let Some(param_arc) = param {
125/// if let Some(data) = param_arc.downcast_ref::<CounterData>() {
126/// println!("Counter: {}", data.count);
127/// }
128/// }
129/// Ok(None)
130/// }
131/// ).unwrap();
132///
133/// timer.start_with_to_tick(Duration::from_millis(10));
134/// ```
135///
136/// ## Changing timer period
137///
138/// ```ignore
139/// use osal_rs::os::{Timer, TimerFn};
140/// use core::time::Duration;
141///
142/// let timer = Timer::new_with_to_tick(
143/// "adjustable",
144/// Duration::from_millis(100),
145/// true,
146/// None,
147/// |_, _| { println!("Tick"); Ok(None) }
148/// ).unwrap();
149///
150/// timer.start_with_to_tick(Duration::from_millis(10));
151///
152/// // Change period to 500ms
153/// Duration::from_secs(2).sleep();
154/// timer.change_period_with_to_tick(
155/// Duration::from_millis(500),
156/// Duration::from_millis(10)
157/// );
158/// ```
159///
160/// ## Resetting a timer
161///
162/// ```ignore
163/// use osal_rs::os::{Timer, TimerFn};
164/// use core::time::Duration;
165///
166/// let timer = Timer::new_with_to_tick(
167/// "watchdog",
168/// Duration::from_secs(5),
169/// false,
170/// None,
171/// |_, _| { println!("Timeout!"); Ok(None) }
172/// ).unwrap();
173///
174/// timer.start_with_to_tick(Duration::from_millis(10));
175///
176/// // Reset timer before it expires (like a watchdog)
177/// Duration::from_secs(2).sleep();
178/// timer.reset_with_to_tick(Duration::from_millis(10)); // Restart the 5s countdown
179/// ```
180#[derive(Clone)]
181pub struct Timer {
182 /// FreeRTOS timer handle
183 pub handle: TimerHandle,
184 /// Timer name for debugging
185 name: String,
186 /// Callback function to execute when timer expires
187 callback: Option<Arc<TimerFnPtr>>,
188 /// Optional parameter passed to callback
189 param: Option<TimerParam>,
190}
191
192unsafe impl Send for Timer {}
193unsafe impl Sync for Timer {}
194
195impl Timer {
196 /// Creates a new software timer with tick conversion.
197 ///
198 /// This is a convenience method that accepts any type implementing `ToTick`
199 /// (like `Duration`) for the timer period.
200 ///
201 /// # Parameters
202 ///
203 /// * `name` - Timer name for debugging
204 /// * `timer_period_in_ticks` - Timer period (e.g., `Duration::from_secs(1)`)
205 /// * `auto_reload` - `true` for periodic, `false` for one-shot
206 /// * `param` - Optional parameter passed to callback
207 /// * `callback` - Function called when timer expires
208 ///
209 /// # Returns
210 ///
211 /// * `Ok(Self)` - Successfully created timer
212 /// * `Err(Error)` - Creation failed
213 ///
214 /// # Examples
215 ///
216 /// ```ignore
217 /// use osal_rs::os::{Timer, TimerFn};
218 /// use core::time::Duration;
219 ///
220 /// let timer = Timer::new_with_to_tick(
221 /// "periodic",
222 /// Duration::from_secs(1),
223 /// true,
224 /// None,
225 /// |_timer, _param| { println!("Tick"); Ok(None) }
226 /// ).unwrap();
227 /// ```
228 #[inline]
229 pub fn new_with_to_tick<F>(name: &str, timer_period_in_ticks: impl ToTick, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
230 where
231 F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
232 Self::new(name, timer_period_in_ticks.to_ticks(), auto_reload, param, callback)
233 }
234
235 /// Starts the timer with tick conversion.
236 ///
237 /// Convenience method that accepts any type implementing `ToTick`.
238 ///
239 /// # Parameters
240 ///
241 /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
242 ///
243 /// # Returns
244 ///
245 /// * `OsalRsBool::True` - Timer started successfully
246 /// * `OsalRsBool::False` - Failed to start timer
247 ///
248 /// # Examples
249 ///
250 /// ```ignore
251 /// use osal_rs::os::{Timer, TimerFn};
252 /// use core::time::Duration;
253 ///
254 /// timer.start_with_to_tick(Duration::from_millis(10));
255 /// ```
256 #[inline]
257 pub fn start_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
258 self.start(ticks_to_wait.to_ticks())
259 }
260
261 /// Stops the timer with tick conversion.
262 ///
263 /// Convenience method that accepts any type implementing `ToTick`.
264 ///
265 /// # Parameters
266 ///
267 /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
268 ///
269 /// # Returns
270 ///
271 /// * `OsalRsBool::True` - Timer stopped successfully
272 /// * `OsalRsBool::False` - Failed to stop timer
273 ///
274 /// # Examples
275 ///
276 /// ```ignore
277 /// use osal_rs::os::{Timer, TimerFn};
278 /// use core::time::Duration;
279 ///
280 /// timer.stop_with_to_tick(Duration::from_millis(10));
281 /// ```
282 #[inline]
283 pub fn stop_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
284 self.stop(ticks_to_wait.to_ticks())
285 }
286
287 /// Resets the timer with tick conversion.
288 ///
289 /// Resets the timer to restart its period. For one-shot timers, this
290 /// restarts them. For periodic timers, this resets the period.
291 ///
292 /// # Parameters
293 ///
294 /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
295 ///
296 /// # Returns
297 ///
298 /// * `OsalRsBool::True` - Timer reset successfully
299 /// * `OsalRsBool::False` - Failed to reset timer
300 ///
301 /// # Examples
302 ///
303 /// ```ignore
304 /// use osal_rs::os::{Timer, TimerFn};
305 /// use core::time::Duration;
306 ///
307 /// // Reset watchdog timer before it expires
308 /// timer.reset_with_to_tick(Duration::from_millis(10));
309 /// ```
310 #[inline]
311 pub fn reset_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
312 self.reset(ticks_to_wait.to_ticks())
313 }
314
315 /// Changes the timer period with tick conversion.
316 ///
317 /// Convenience method that accepts any type implementing `ToTick`.
318 ///
319 /// # Parameters
320 ///
321 /// * `new_period_in_ticks` - New timer period
322 /// * `new_period_ticks` - Maximum time to wait for the command to be sent to timer daemon
323 ///
324 /// # Returns
325 ///
326 /// * `OsalRsBool::True` - Period changed successfully
327 /// * `OsalRsBool::False` - Failed to change period
328 ///
329 /// # Examples
330 ///
331 /// ```ignore
332 /// use osal_rs::os::{Timer, TimerFn};
333 /// use core::time::Duration;
334 ///
335 /// // Change from 1 second to 500ms
336 /// timer.change_period_with_to_tick(
337 /// Duration::from_millis(500),
338 /// Duration::from_millis(10)
339 /// );
340 /// ```
341 #[inline]
342 pub fn change_period_with_to_tick(&self, new_period_in_ticks: impl ToTick, new_period_ticks: impl ToTick) -> OsalRsBool {
343 self.change_period(new_period_in_ticks.to_ticks(), new_period_ticks.to_ticks())
344 }
345
346 /// Deletes the timer with tick conversion.
347 ///
348 /// Convenience method that accepts any type implementing `ToTick`.
349 ///
350 /// # Parameters
351 ///
352 /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
353 ///
354 /// # Returns
355 ///
356 /// * `OsalRsBool::True` - Timer deleted successfully
357 /// * `OsalRsBool::False` - Failed to delete timer
358 ///
359 /// # Examples
360 ///
361 /// ```ignore
362 /// use osal_rs::os::{Timer, TimerFn};
363 /// use core::time::Duration;
364 ///
365 /// timer.delete_with_to_tick(Duration::from_millis(10));
366 /// ```
367 #[inline]
368 pub fn delete_with_to_tick(&mut self, ticks_to_wait: impl ToTick) -> OsalRsBool {
369 self.delete(ticks_to_wait.to_ticks())
370 }
371}
372
373/// Internal C-compatible wrapper for timer callbacks.
374///
375/// This function bridges between FreeRTOS C API and Rust closures.
376/// It retrieves the timer instance from the timer ID, extracts the callback
377/// and parameters, and executes the user-provided callback.
378///
379/// # Safety
380///
381/// This function is marked extern "C" because it:
382/// - Is called from FreeRTOS C code (timer daemon task)
383/// - Performs raw pointer conversions
384/// - Expects a valid timer handle with associated timer instance
385extern "C" fn callback_c_wrapper(handle: TimerHandle) {
386
387 if handle.is_null() {
388 return;
389 }
390
391 let param_ptr = unsafe {
392 pvTimerGetTimerID(handle)
393 };
394
395 let mut timer_instance: Box<Timer> = unsafe { Box::from_raw(param_ptr as *mut _) };
396
397 timer_instance.as_mut().handle = handle;
398
399 let param_arc: Option<Arc<dyn Any + Send + Sync>> = timer_instance
400 .param
401 .clone();
402
403 if let Some(callback) = &timer_instance.callback.clone() {
404 let _ = callback(timer_instance, param_arc);
405 }
406}
407
408
409
410impl Timer {
411 /// Creates a new software timer.
412 ///
413 /// # Parameters
414 ///
415 /// * `name` - Timer name for debugging
416 /// * `timer_period_in_ticks` - Timer period in ticks
417 /// * `auto_reload` - `true` for periodic, `false` for one-shot
418 /// * `param` - Optional parameter passed to callback
419 /// * `callback` - Function called when timer expires
420 ///
421 /// # Returns
422 ///
423 /// * `Ok(Self)` - Successfully created timer
424 /// * `Err(Error)` - Creation failed
425 ///
426 /// # Examples
427 ///
428 /// ```ignore
429 /// use osal_rs::os::{Timer, TimerFn};
430 ///
431 /// let timer = Timer::new(
432 /// "my_timer",
433 /// 1000,
434 /// false,
435 /// None,
436 /// |_timer, _param| Ok(None)
437 /// ).unwrap();
438 /// ``
439
440 pub fn new<F>(name: &str, timer_period_in_ticks: TickType, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
441 where
442 F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
443
444 let mut boxed_timer = Box::new(Self {
445 handle: core::ptr::null_mut(),
446 name: name.to_string(),
447 callback: Some(Arc::new(callback.clone())),
448 param: param.clone(),
449 });
450
451 let handle = unsafe {
452 xTimerCreate( to_c_str!(name),
453 timer_period_in_ticks,
454 if auto_reload { 1 } else { 0 },
455 Box::into_raw(boxed_timer.clone()) as *mut _,
456 Some(super::timer::callback_c_wrapper)
457 )
458 };
459
460 if handle.is_null() {
461 Err(Error::NullPtr)
462 } else {
463 boxed_timer.as_mut().handle = handle;
464 Ok(*boxed_timer)
465 }
466
467 }
468
469}
470
471impl TimerFn for Timer {
472
473 /// Starts the timer.
474 ///
475 /// Sends a command to the timer daemon to start the timer. If the timer
476 /// was already running, this has no effect.
477 ///
478 /// # Parameters
479 ///
480 /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
481 ///
482 /// # Returns
483 ///
484 /// * `OsalRsBool::True` - Timer started successfully
485 /// * `OsalRsBool::False` - Failed to start (command queue full)
486 ///
487 /// # Examples
488 ///
489 /// ```ignore
490 /// use osal_rs::os::{Timer, TimerFn};
491 ///
492 /// let timer = Timer::new("my_timer", 1000, true, None, |_, _| Ok(None)).unwrap();
493 /// timer.start(10); // Wait up to 10 ticks
494 /// ```
495 fn start(&self, ticks_to_wait: TickType) -> OsalRsBool {
496 if unsafe {
497 osal_rs_timer_start(self.handle, ticks_to_wait)
498 } != pdPASS {
499 OsalRsBool::False
500 } else {
501 OsalRsBool::True
502 }
503 }
504
505 /// Stops the timer.
506 ///
507 /// Sends a command to the timer daemon to stop the timer. The timer will not
508 /// fire again until it is restarted.
509 ///
510 /// # Parameters
511 ///
512 /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
513 ///
514 /// # Returns
515 ///
516 /// * `OsalRsBool::True` - Timer stopped successfully
517 /// * `OsalRsBool::False` - Failed to stop (command queue full)
518 ///
519 /// # Examples
520 ///
521 /// ```ignore
522 /// use osal_rs::os::{Timer, TimerFn};
523 ///
524 /// timer.stop(10); // Wait up to 10 ticks to stop
525 /// ```
526 fn stop(&self, ticks_to_wait: TickType) -> OsalRsBool {
527 if unsafe {
528 osal_rs_timer_stop(self.handle, ticks_to_wait)
529 } != pdPASS {
530 OsalRsBool::False
531 } else {
532 OsalRsBool::True
533 }
534 }
535
536 /// Resets the timer.
537 ///
538 /// Resets the timer's period. For a one-shot timer that has already expired,
539 /// this will restart it. For a periodic timer, this resets the period.
540 ///
541 /// # Parameters
542 ///
543 /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
544 ///
545 /// # Returns
546 ///
547 /// * `OsalRsBool::True` - Timer reset successfully
548 /// * `OsalRsBool::False` - Failed to reset (command queue full)
549 ///
550 /// # Examples
551 ///
552 /// ```ignore
553 /// use osal_rs::os::{Timer, TimerFn};
554 ///
555 /// // Reset a watchdog timer before it expires
556 /// timer.reset(10);
557 /// ```
558 fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool {
559 if unsafe {
560 osal_rs_timer_reset(self.handle, ticks_to_wait)
561 } != pdPASS {
562 OsalRsBool::False
563 } else {
564 OsalRsBool::True
565 }
566 }
567
568 /// Changes the timer period.
569 ///
570 /// Changes the period of a timer that was previously created. The timer
571 /// must be stopped, or the period will be changed when it next expires.
572 ///
573 /// # Parameters
574 ///
575 /// * `new_period_in_ticks` - New period for the timer in ticks
576 /// * `new_period_ticks` - Maximum time to wait for command to be sent to timer daemon
577 ///
578 /// # Returns
579 ///
580 /// * `OsalRsBool::True` - Period changed successfully
581 /// * `OsalRsBool::False` - Failed to change period (command queue full)
582 ///
583 /// # Examples
584 ///
585 /// ```ignore
586 /// use osal_rs::os::{Timer, TimerFn};
587 ///
588 /// // Change period from 1000 ticks to 500 ticks
589 /// timer.change_period(500, 10);
590 /// ```
591 fn change_period(&self, new_period_in_ticks: TickType, new_period_ticks: TickType) -> OsalRsBool {
592 if unsafe {
593 osal_rs_timer_change_period(self.handle, new_period_in_ticks, new_period_ticks)
594 } != pdPASS {
595 OsalRsBool::False
596 } else {
597 OsalRsBool::True
598 }
599 }
600
601 /// Deletes the timer.
602 ///
603 /// Sends a command to the timer daemon to delete the timer.
604 /// The timer handle becomes invalid after this call.
605 ///
606 /// # Parameters
607 ///
608 /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
609 ///
610 /// # Returns
611 ///
612 /// * `OsalRsBool::True` - Timer deleted successfully
613 /// * `OsalRsBool::False` - Failed to delete (command queue full)
614 ///
615 /// # Safety
616 ///
617 /// After calling this function, the timer handle is set to null and should not be used.
618 ///
619 /// # Examples
620 ///
621 /// ```ignore
622 /// use osal_rs::os::{Timer, TimerFn};
623 ///
624 /// let mut timer = Timer::new("temp", 1000, false, None, |_, _| Ok(None)).unwrap();
625 /// timer.delete(10);
626 /// ```
627 fn delete(&mut self, ticks_to_wait: TickType) -> OsalRsBool {
628 if unsafe {
629 osal_rs_timer_delete(self.handle, ticks_to_wait)
630 } != pdPASS {
631 self.handle = null_mut();
632 OsalRsBool::False
633 } else {
634 self.handle = null_mut();
635 OsalRsBool::True
636 }
637 }
638}
639
640/// Automatically deletes the timer when it goes out of scope.
641///
642/// This ensures proper cleanup of FreeRTOS resources by calling
643/// `delete(0)` when the timer is dropped.
644impl Drop for Timer {
645 fn drop(&mut self) {
646 self.delete(0);
647 }
648}
649
650/// Allows dereferencing to the underlying FreeRTOS timer handle.
651impl Deref for Timer {
652 type Target = TimerHandle;
653
654 fn deref(&self) -> &Self::Target {
655 &self.handle
656 }
657}
658
659/// Formats the timer for debugging purposes.
660///
661/// Shows the timer handle and name.
662impl Debug for Timer {
663 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
664 f.debug_struct("Timer")
665 .field("handle", &self.handle)
666 .field("name", &self.name)
667 .finish()
668 }
669}
670
671/// Formats the timer for display purposes.
672///
673/// Shows a concise representation with name and handle.
674impl Display for Timer {
675 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
676 write!(f, "Timer {{ name: {}, handle: {:?} }}", self.name, self.handle)
677 }
678}