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}