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}