osal_rs/freertos/
timer.rs

1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2023/2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***************************************************************************/
19
20//! Software timer support for FreeRTOS.
21//!
22//! This module provides software timers that run callbacks at specified intervals.
23//! Timers can be one-shot or auto-reloading (periodic) and execute their callbacks
24//! in the timer daemon task context.
25
26use core::any::Any;
27use core::fmt::{Debug, Display};
28use core::ops::Deref;
29use core::ptr::null_mut;
30
31use alloc::boxed::Box;
32use alloc::string::{String, ToString};
33use alloc::sync::Arc;
34
35use crate::freertos::ffi::pdPASS;
36use crate::to_c_str;
37use crate::traits::{ToTick, TimerParam, TimerFn, TimerFnPtr};
38use crate::utils::{OsalRsBool, Result, Error};
39use 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};
40use super::types::{TickType};
41
42/// A software timer that executes a callback at regular intervals.
43///
44/// Timers can be configured as:
45/// - **One-shot**: Executes once after the specified period
46/// - **Auto-reload**: Executes repeatedly at the specified interval
47///
48/// Timer callbacks execute in the context of the timer daemon task, not in
49/// interrupt context. This means they can call most RTOS functions safely.
50///
51/// # Important Notes
52///
53/// - Timer callbacks should complete quickly to avoid delaying other timers
54/// - Callbacks must not block indefinitely
55/// - Requires `configUSE_TIMERS = 1` in FreeRTOSConfig.h
56///
57/// # Examples
58///
59/// ## One-shot timer
60///
61/// ```ignore
62/// use osal_rs::os::{Timer, TimerFn};
63/// use core::time::Duration;
64/// 
65/// let timer = Timer::new_with_to_tick(
66///     "oneshot",
67///     Duration::from_secs(1),
68///     false,  // Not auto-reload (one-shot)
69///     None,
70///     |timer, param| {
71///         println!("Timer fired once!");
72///         Ok(param)
73///     }
74/// ).unwrap();
75/// 
76/// timer.start_with_to_tick(Duration::from_millis(10)).unwrap();
77/// ```
78///
79/// ## Periodic timer
80///
81/// ```ignore
82/// use osal_rs::os::{Timer, TimerFn};
83/// use core::time::Duration;
84/// 
85/// let timer = Timer::new_with_to_tick(
86///     "periodic",
87///     Duration::from_millis(500),
88///     true,  // Auto-reload (periodic)
89///     None,
90///     |timer, param| {
91///         println!("Tick every 500ms");
92///         Ok(param)
93///     }
94/// ).unwrap();
95/// 
96/// timer.start_with_to_tick(Duration::from_millis(10)).unwrap();
97/// 
98/// // Stop after some time
99/// Duration::from_secs(5).sleep();
100/// timer.stop_with_to_tick(Duration::from_millis(10));
101/// ```
102///
103/// ## Timer with custom parameters
104///
105/// ```ignore
106/// use osal_rs::os::{Timer, TimerFn, TimerParam};
107/// use alloc::sync::Arc;
108/// use core::time::Duration;
109/// 
110/// struct CounterData {
111///     count: u32,
112/// }
113/// 
114/// let data = Arc::new(CounterData { count: 0 });
115/// let param: TimerParam = data.clone();
116/// 
117/// let timer = Timer::new_with_to_tick(
118///     "counter",
119///     Duration::from_secs(1),
120///     true,
121///     Some(param),
122///     |timer, param| {
123///         if let Some(param_arc) = param {
124///             if let Some(data) = param_arc.downcast_ref::<CounterData>() {
125///                 println!("Counter: {}", data.count);
126///             }
127///         }
128///         Ok(None)
129///     }
130/// ).unwrap();
131/// 
132/// timer.start_with_to_tick(Duration::from_millis(10));
133/// ```
134///
135/// ## Changing timer period
136///
137/// ```ignore
138/// use osal_rs::os::{Timer, TimerFn};
139/// use core::time::Duration;
140/// 
141/// let timer = Timer::new_with_to_tick(
142///     "adjustable",
143///     Duration::from_millis(100),
144///     true,
145///     None,
146///     |_, _| { println!("Tick"); Ok(None) }
147/// ).unwrap();
148/// 
149/// timer.start_with_to_tick(Duration::from_millis(10));
150/// 
151/// // Change period to 500ms
152/// Duration::from_secs(2).sleep();
153/// timer.change_period_with_to_tick(
154///     Duration::from_millis(500),
155///     Duration::from_millis(10)
156/// );
157/// ```
158///
159/// ## Resetting a timer
160///
161/// ```ignore
162/// use osal_rs::os::{Timer, TimerFn};
163/// use core::time::Duration;
164/// 
165/// let timer = Timer::new_with_to_tick(
166///     "watchdog",
167///     Duration::from_secs(5),
168///     false,
169///     None,
170///     |_, _| { println!("Timeout!"); Ok(None) }
171/// ).unwrap();
172/// 
173/// timer.start_with_to_tick(Duration::from_millis(10));
174/// 
175/// // Reset timer before it expires (like a watchdog)
176/// Duration::from_secs(2).sleep();
177/// timer.reset_with_to_tick(Duration::from_millis(10));  // Restart the 5s countdown
178/// ```
179#[derive(Clone)]
180pub struct Timer {
181    /// FreeRTOS timer handle
182    pub handle: TimerHandle,
183    /// Timer name for debugging
184    name: String, 
185    /// Callback function to execute when timer expires
186    callback: Option<Arc<TimerFnPtr>>,
187    /// Optional parameter passed to callback
188    param: Option<TimerParam>, 
189}
190
191unsafe impl Send for Timer {}
192unsafe impl Sync for Timer {}
193
194impl Timer {
195    #[inline]
196    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>
197    where
198        F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
199            Self::new(name, timer_period_in_ticks.to_ticks(), auto_reload, param, callback)
200        }
201
202    #[inline]
203    pub fn start_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
204        self.start(ticks_to_wait.to_ticks())
205    }
206
207    #[inline]
208    pub fn stop_with_to_tick(&self, ticks_to_wait: impl ToTick)  -> OsalRsBool {
209        self.stop(ticks_to_wait.to_ticks())
210    }
211
212    #[inline]
213    pub fn reset_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
214        self.reset(ticks_to_wait.to_ticks())
215    }
216
217    #[inline]
218    pub fn change_period_with_to_tick(&self, new_period_in_ticks: impl ToTick, new_period_ticks: impl ToTick) -> OsalRsBool {
219        self.change_period(new_period_in_ticks.to_ticks(), new_period_ticks.to_ticks())
220    }
221
222    #[inline]
223    pub fn delete_with_to_tick(&mut self, ticks_to_wait: impl ToTick) -> OsalRsBool {
224        self.delete(ticks_to_wait.to_ticks())
225    }
226}
227
228extern "C" fn callback_c_wrapper(handle: TimerHandle) {
229
230    if handle.is_null() {
231        return;
232    }
233
234    let param_ptr = unsafe {
235        pvTimerGetTimerID(handle) 
236    };
237    
238    let mut timer_instance: Box<Timer> = unsafe { Box::from_raw(param_ptr as *mut _) };
239
240    timer_instance.as_mut().handle = handle;
241
242    let param_arc: Option<Arc<dyn Any + Send + Sync>> = timer_instance
243        .param
244        .clone();
245
246    if let Some(callback) = &timer_instance.callback.clone() {
247        let _ = callback(timer_instance, param_arc);
248    }
249}
250
251impl TimerFn for Timer {
252    fn new<F>(name: &str, timer_period_in_ticks: TickType, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
253    where
254        F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
255
256            let mut boxed_timer = Box::new(Self {
257                handle: core::ptr::null_mut(),
258                name: name.to_string(),
259                callback: Some(Arc::new(callback.clone())),
260                param: param.clone(),
261            });
262
263            let handle = unsafe {
264                xTimerCreate( to_c_str!(name), 
265                    timer_period_in_ticks, 
266                    if auto_reload { 1 } else { 0 }, 
267                    Box::into_raw(boxed_timer.clone()) as *mut _, 
268                    Some(super::timer::callback_c_wrapper)
269                )
270            };
271
272            if handle.is_null() {
273                Err(Error::NullPtr)
274            } else {
275                boxed_timer.as_mut().handle = handle;
276                Ok(*boxed_timer)
277            }
278
279    }
280
281    fn start(&self, ticks_to_wait: TickType) -> OsalRsBool {
282        if unsafe {
283            osal_rs_timer_start(self.handle, ticks_to_wait)
284        } != pdPASS {
285            OsalRsBool::False
286        } else {
287            OsalRsBool::True
288        }
289    }
290
291    fn stop(&self, ticks_to_wait: TickType)  -> OsalRsBool {
292        if unsafe {
293            osal_rs_timer_stop(self.handle, ticks_to_wait)
294        } != pdPASS {
295            OsalRsBool::False
296        } else {
297            OsalRsBool::True
298        }
299    }
300
301    fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool {
302        if unsafe {
303            osal_rs_timer_reset(self.handle, ticks_to_wait)
304        } != pdPASS {
305            OsalRsBool::False
306        } else {
307            OsalRsBool::True
308        }
309    }
310
311    fn change_period(&self, new_period_in_ticks: TickType, new_period_ticks: TickType) -> OsalRsBool {
312        if unsafe {
313            osal_rs_timer_change_period(self.handle, new_period_in_ticks, new_period_ticks)
314        } != pdPASS {
315            OsalRsBool::False
316        } else {
317            OsalRsBool::True
318        }
319    }
320
321    fn delete(&mut self, ticks_to_wait: TickType) -> OsalRsBool {
322        if unsafe {
323            osal_rs_timer_delete(self.handle, ticks_to_wait)
324        } != pdPASS {
325            self.handle = null_mut();
326            OsalRsBool::False
327        } else {
328            self.handle = null_mut();
329            OsalRsBool::True
330        }
331    }
332}
333
334impl Drop for Timer {
335    fn drop(&mut self) {
336        self.delete(0);
337    }
338}
339
340impl Deref for Timer {
341    type Target = TimerHandle;
342
343    fn deref(&self) -> &Self::Target {
344        &self.handle
345    }
346}
347
348impl Debug for Timer {
349    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
350        f.debug_struct("Timer")
351            .field("handle", &self.handle)
352            .field("name", &self.name)
353            .finish()
354    }
355}
356
357impl Display for Timer {
358    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
359        write!(f, "Timer {{ name: {}, handle: {:?} }}", self.name, self.handle)
360    }
361}