Skip to main content

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
251
252
253impl Timer {
254    /// Creates a new software timer.
255    ///
256    /// # Parameters
257    ///
258    /// * `name` - Timer name for debugging
259    /// * `timer_period_in_ticks` - Timer period in ticks
260    /// * `auto_reload` - `true` for periodic, `false` for one-shot
261    /// * `param` - Optional parameter passed to callback
262    /// * `callback` - Function called when timer expires
263    ///
264    /// # Returns
265    ///
266    /// * `Ok(Self)` - Successfully created timer
267    /// * `Err(Error)` - Creation failed
268    ///
269    /// # Examples
270    ///
271    /// ```ignore
272    /// use osal_rs::os::{Timer, TimerFn};
273    /// 
274    /// let timer = Timer::new(
275    ///     "my_timer",
276    ///     1000,
277    ///     false,
278    ///     None,
279    ///     |_timer, _param| Ok(None)
280    /// ).unwrap();
281    /// ``
282    
283    pub fn new<F>(name: &str, timer_period_in_ticks: TickType, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
284    where
285        F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
286
287            let mut boxed_timer = Box::new(Self {
288                handle: core::ptr::null_mut(),
289                name: name.to_string(),
290                callback: Some(Arc::new(callback.clone())),
291                param: param.clone(),
292            });
293
294            let handle = unsafe {
295                xTimerCreate( to_c_str!(name), 
296                    timer_period_in_ticks, 
297                    if auto_reload { 1 } else { 0 }, 
298                    Box::into_raw(boxed_timer.clone()) as *mut _, 
299                    Some(super::timer::callback_c_wrapper)
300                )
301            };
302
303            if handle.is_null() {
304                Err(Error::NullPtr)
305            } else {
306                boxed_timer.as_mut().handle = handle;
307                Ok(*boxed_timer)
308            }
309
310    }
311    
312}
313
314impl TimerFn for Timer {
315
316    fn start(&self, ticks_to_wait: TickType) -> OsalRsBool {
317        if unsafe {
318            osal_rs_timer_start(self.handle, ticks_to_wait)
319        } != pdPASS {
320            OsalRsBool::False
321        } else {
322            OsalRsBool::True
323        }
324    }
325
326    fn stop(&self, ticks_to_wait: TickType)  -> OsalRsBool {
327        if unsafe {
328            osal_rs_timer_stop(self.handle, ticks_to_wait)
329        } != pdPASS {
330            OsalRsBool::False
331        } else {
332            OsalRsBool::True
333        }
334    }
335
336    fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool {
337        if unsafe {
338            osal_rs_timer_reset(self.handle, ticks_to_wait)
339        } != pdPASS {
340            OsalRsBool::False
341        } else {
342            OsalRsBool::True
343        }
344    }
345
346    fn change_period(&self, new_period_in_ticks: TickType, new_period_ticks: TickType) -> OsalRsBool {
347        if unsafe {
348            osal_rs_timer_change_period(self.handle, new_period_in_ticks, new_period_ticks)
349        } != pdPASS {
350            OsalRsBool::False
351        } else {
352            OsalRsBool::True
353        }
354    }
355
356    fn delete(&mut self, ticks_to_wait: TickType) -> OsalRsBool {
357        if unsafe {
358            osal_rs_timer_delete(self.handle, ticks_to_wait)
359        } != pdPASS {
360            self.handle = null_mut();
361            OsalRsBool::False
362        } else {
363            self.handle = null_mut();
364            OsalRsBool::True
365        }
366    }
367}
368
369impl Drop for Timer {
370    fn drop(&mut self) {
371        self.delete(0);
372    }
373}
374
375impl Deref for Timer {
376    type Target = TimerHandle;
377
378    fn deref(&self) -> &Self::Target {
379        &self.handle
380    }
381}
382
383impl Debug for Timer {
384    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
385        f.debug_struct("Timer")
386            .field("handle", &self.handle)
387            .field("name", &self.name)
388            .finish()
389    }
390}
391
392impl Display for Timer {
393    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
394        write!(f, "Timer {{ name: {}, handle: {:?} }}", self.name, self.handle)
395    }
396}