Skip to main content

scirs2_core/embedded/
mod.rs

1//! Embedded Systems Support for SciRS2 (v0.2.0)
2//!
3//! This module provides no_std-compatible utilities and abstractions for embedded systems,
4//! including ARM Cortex-M, RISC-V, AVR, and embedded Linux targets.
5//!
6//! # Features
7//!
8//! - **no_std Compatible**: Works without standard library
9//! - **Fixed-Point Arithmetic**: For systems without FPU
10//! - **Stack-Based Operations**: Minimal heap usage
11//! - **Deterministic Execution**: Predictable timing for real-time systems
12//! - **Memory-Constrained**: Optimized for limited RAM/ROM
13//!
14//! # Example: Basic Embedded Usage
15//!
16//! ```rust,ignore
17//! #![no_std]
18//! #![no_main]
19//!
20//! use scirs2_core::embedded::*;
21//!
22//! #[entry]
23//! fn main() -> ! {
24//!     // Fixed-size array operations (no heap allocation)
25//!     let data = StackArray::<f32, 16>::new();
26//!
27//!     // Deterministic computation
28//!     let result = stack_based_filter(&data);
29//!
30//!     loop {}
31//! }
32//! ```
33//!
34//! # Memory Requirements
35//!
36//! - **Minimum RAM**: 8KB (basic operations)
37//! - **Recommended RAM**: 32KB (full functionality)
38//! - **Flash/ROM**: 64KB (core functionality)
39//!
40//! # Supported Platforms
41//!
42//! - ARM Cortex-M (M0/M0+/M3/M4/M7/M33)
43//! - RISC-V (RV32I/RV32IMC/RV32IMAC)
44//! - AVR (ATmega series)
45//! - Embedded Linux (no libc)
46
47#![cfg_attr(not(feature = "std"), no_std)]
48
49#[cfg(all(feature = "alloc", not(feature = "std")))]
50extern crate alloc;
51
52#[cfg(all(feature = "alloc", not(feature = "std")))]
53use alloc::vec::Vec;
54
55use crate::error::{CoreError, CoreResult};
56use core::marker::PhantomData;
57use num_traits::{Float, NumAssignOps, Zero};
58
59// Re-exports for convenience
60pub use error_handling::{EmbeddedError, EmbeddedResult};
61pub use memory_estimation::{estimate_stack_usage, MemoryRequirement};
62pub use stack_array::{BufferFullError, FixedSizeBuffer, StackArray};
63
64/// Stack-based computation for element-wise operations
65///
66/// Performs operations without heap allocation, suitable for embedded systems.
67///
68/// # Example
69///
70/// ```rust
71/// use scirs2_core::embedded::StackArray;
72///
73/// let a = StackArray::<f32, 8>::new();
74/// let b = StackArray::<f32, 8>::new();
75/// // Perform element-wise operations
76/// ```
77#[inline]
78pub fn stack_add<T, const N: usize>(a: &StackArray<T, N>, b: &StackArray<T, N>) -> StackArray<T, N>
79where
80    T: Float + NumAssignOps + Copy + Default,
81{
82    let mut result = StackArray::new();
83    for i in 0..N {
84        result.data[i] = a.data[i] + b.data[i];
85    }
86    result
87}
88
89/// Stack-based multiplication
90#[inline]
91pub fn stack_mul<T, const N: usize>(a: &StackArray<T, N>, b: &StackArray<T, N>) -> StackArray<T, N>
92where
93    T: Float + NumAssignOps + Copy + Default,
94{
95    let mut result = StackArray::new();
96    for i in 0..N {
97        result.data[i] = a.data[i] * b.data[i];
98    }
99    result
100}
101
102/// Calculate sum without heap allocation
103#[inline]
104pub fn stack_sum<T, const N: usize>(arr: &StackArray<T, N>) -> T
105where
106    T: Float + NumAssignOps + Copy + Default + Zero,
107{
108    let mut sum = T::zero();
109    for i in 0..N {
110        sum += arr.data[i];
111    }
112    sum
113}
114
115/// Calculate mean without heap allocation
116#[inline]
117pub fn stack_mean<T, const N: usize>(arr: &StackArray<T, N>) -> T
118where
119    T: Float + NumAssignOps + Copy + Default + Zero,
120{
121    if N == 0 {
122        return T::zero();
123    }
124    stack_sum(arr) / T::from(N).expect("Failed to convert N to T")
125}
126
127/// Memory estimation utilities
128pub mod memory_estimation {
129    use core::mem;
130
131    /// Estimated memory requirement for an operation
132    #[derive(Debug, Copy, Clone)]
133    pub struct MemoryRequirement {
134        /// Stack memory in bytes
135        pub stack_bytes: usize,
136        /// Heap memory in bytes (if alloc feature enabled)
137        pub heap_bytes: usize,
138        /// ROM/Flash memory in bytes
139        pub flash_bytes: usize,
140    }
141
142    impl MemoryRequirement {
143        /// Create a new memory requirement estimate
144        pub const fn new(stack_bytes: usize, heap_bytes: usize, flash_bytes: usize) -> Self {
145            Self {
146                stack_bytes,
147                heap_bytes,
148                flash_bytes,
149            }
150        }
151
152        /// Estimate for basic operations
153        pub const fn basic() -> Self {
154            Self::new(1024, 0, 4096)
155        }
156
157        /// Estimate for signal processing
158        pub const fn signal_processing() -> Self {
159            Self::new(4096, 0, 16384)
160        }
161
162        /// Estimate for linear algebra
163        pub const fn linalg() -> Self {
164            Self::new(8192, 0, 32768)
165        }
166    }
167
168    /// Estimate stack usage for a given array size
169    pub const fn estimate_stack_usage<T>(count: usize) -> usize {
170        mem::size_of::<T>() * count
171    }
172}
173
174/// Error handling for embedded systems
175pub mod error_handling {
176    use core::fmt;
177
178    /// Embedded-specific error types (no heap allocation)
179    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
180    pub enum EmbeddedError {
181        /// Buffer overflow
182        BufferOverflow,
183        /// Buffer underflow
184        BufferUnderflow,
185        /// Invalid size
186        InvalidSize,
187        /// Out of memory
188        OutOfMemory,
189        /// Numerical error
190        NumericalError,
191        /// Not supported in no_std mode
192        NotSupported,
193    }
194
195    impl fmt::Display for EmbeddedError {
196        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197            match self {
198                EmbeddedError::BufferOverflow => write!(f, "Buffer overflow"),
199                EmbeddedError::BufferUnderflow => write!(f, "Buffer underflow"),
200                EmbeddedError::InvalidSize => write!(f, "Invalid size"),
201                EmbeddedError::OutOfMemory => write!(f, "Out of memory"),
202                EmbeddedError::NumericalError => write!(f, "Numerical error"),
203                EmbeddedError::NotSupported => write!(f, "Not supported in no_std mode"),
204            }
205        }
206    }
207
208    /// Result type for embedded operations
209    pub type EmbeddedResult<T> = Result<T, EmbeddedError>;
210}
211
212/// Stack array module
213pub mod stack_array {
214    use core::marker::PhantomData;
215
216    /// Error returned when attempting to push to a full buffer
217    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
218    pub struct BufferFullError;
219
220    impl core::fmt::Display for BufferFullError {
221        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
222            write!(f, "buffer is full")
223        }
224    }
225
226    /// Stack-based array for no-heap operations
227    ///
228    /// This provides a fixed-size array allocated on the stack,
229    /// suitable for embedded systems without heap allocation.
230    ///
231    /// # Example
232    ///
233    /// ```rust
234    /// use scirs2_core::embedded::StackArray;
235    ///
236    /// let mut data = StackArray::<f32, 16>::new();
237    /// data[0] = 1.0;
238    /// data[1] = 2.0;
239    /// assert_eq!(data.len(), 16);
240    /// ```
241    #[derive(Debug, Clone)]
242    pub struct StackArray<T, const N: usize> {
243        pub(super) data: [T; N],
244        _marker: PhantomData<T>,
245    }
246
247    impl<T: Copy + Default, const N: usize> StackArray<T, N> {
248        /// Create a new stack-allocated array initialized with default values
249        #[inline]
250        pub fn new() -> Self {
251            Self {
252                data: [T::default(); N],
253                _marker: PhantomData,
254            }
255        }
256
257        /// Create from a slice (copies data if it fits)
258        #[inline]
259        pub fn from_slice(slice: &[T]) -> Option<Self> {
260            if slice.len() != N {
261                return None;
262            }
263            let mut result = Self::new();
264            result.data.copy_from_slice(slice);
265            Some(result)
266        }
267
268        /// Get the length of the array
269        #[inline]
270        pub const fn len(&self) -> usize {
271            N
272        }
273
274        /// Check if the array is empty (always false for const generic arrays)
275        #[inline]
276        pub const fn is_empty(&self) -> bool {
277            N == 0
278        }
279
280        /// Get a reference to the underlying array
281        #[inline]
282        pub fn as_slice(&self) -> &[T] {
283            &self.data
284        }
285
286        /// Get a mutable reference to the underlying array
287        #[inline]
288        pub fn as_mut_slice(&mut self) -> &mut [T] {
289            &mut self.data
290        }
291    }
292
293    impl<T: Copy + Default, const N: usize> Default for StackArray<T, N> {
294        fn default() -> Self {
295            Self::new()
296        }
297    }
298
299    impl<T, const N: usize> core::ops::Index<usize> for StackArray<T, N> {
300        type Output = T;
301
302        #[inline]
303        fn index(&self, index: usize) -> &Self::Output {
304            &self.data[index]
305        }
306    }
307
308    impl<T, const N: usize> core::ops::IndexMut<usize> for StackArray<T, N> {
309        #[inline]
310        fn index_mut(&mut self, index: usize) -> &mut Self::Output {
311            &mut self.data[index]
312        }
313    }
314
315    /// Fixed-size buffer for streaming operations
316    ///
317    /// Useful for real-time signal processing where data arrives continuously.
318    ///
319    /// # Example
320    ///
321    /// ```rust
322    /// use scirs2_core::embedded::FixedSizeBuffer;
323    ///
324    /// let mut buffer = FixedSizeBuffer::<f32, 8>::new();
325    /// buffer.push(1.0).expect("Buffer should not be full");
326    /// buffer.push(2.0).expect("Buffer should not be full");
327    /// assert_eq!(buffer.len(), 2);
328    /// ```
329    #[derive(Debug, Clone)]
330    pub struct FixedSizeBuffer<T, const N: usize> {
331        pub(super) data: [T; N],
332        len: usize,
333        _marker: PhantomData<T>,
334    }
335
336    impl<T: Copy + Default, const N: usize> FixedSizeBuffer<T, N> {
337        /// Create a new empty buffer
338        #[inline]
339        pub fn new() -> Self {
340            Self {
341                data: [T::default(); N],
342                len: 0,
343                _marker: PhantomData,
344            }
345        }
346
347        /// Push an element to the buffer (returns error if full)
348        #[inline]
349        pub fn push(&mut self, value: T) -> Result<(), BufferFullError> {
350            if self.len >= N {
351                return Err(BufferFullError);
352            }
353            self.data[self.len] = value;
354            self.len += 1;
355            Ok(())
356        }
357
358        /// Pop an element from the buffer (returns None if empty)
359        #[inline]
360        pub fn pop(&mut self) -> Option<T> {
361            if self.len == 0 {
362                return None;
363            }
364            self.len -= 1;
365            Some(self.data[self.len])
366        }
367
368        /// Get the current length
369        #[inline]
370        pub const fn len(&self) -> usize {
371            self.len
372        }
373
374        /// Check if the buffer is empty
375        #[inline]
376        pub const fn is_empty(&self) -> bool {
377            self.len == 0
378        }
379
380        /// Check if the buffer is full
381        #[inline]
382        pub const fn is_full(&self) -> bool {
383            self.len == N
384        }
385
386        /// Get the capacity
387        #[inline]
388        pub const fn capacity(&self) -> usize {
389            N
390        }
391
392        /// Clear the buffer
393        #[inline]
394        pub fn clear(&mut self) {
395            self.len = 0;
396        }
397
398        /// Get a slice of the valid data
399        #[inline]
400        pub fn as_slice(&self) -> &[T] {
401            &self.data[..self.len]
402        }
403    }
404
405    impl<T: Copy + Default, const N: usize> Default for FixedSizeBuffer<T, N> {
406        fn default() -> Self {
407            Self::new()
408        }
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415
416    #[test]
417    fn test_stack_array_basic() {
418        let mut arr = StackArray::<f32, 8>::new();
419        arr[0] = 1.0;
420        arr[1] = 2.0;
421        assert_eq!(arr.len(), 8);
422        assert_eq!(arr[0], 1.0);
423        assert_eq!(arr[1], 2.0);
424    }
425
426    #[test]
427    fn test_fixed_size_buffer() {
428        let mut buffer = FixedSizeBuffer::<f32, 4>::new();
429        assert!(buffer.is_empty());
430
431        buffer.push(1.0).expect("Failed to push");
432        buffer.push(2.0).expect("Failed to push");
433        assert_eq!(buffer.len(), 2);
434
435        assert_eq!(buffer.pop(), Some(2.0));
436        assert_eq!(buffer.len(), 1);
437    }
438
439    #[test]
440    fn test_stack_operations() {
441        let mut a = StackArray::<f32, 4>::new();
442        let mut b = StackArray::<f32, 4>::new();
443
444        for i in 0..4 {
445            a[i] = i as f32;
446            b[i] = (i + 1) as f32;
447        }
448
449        let sum = stack_add(&a, &b);
450        assert_eq!(sum[0], 1.0);
451        assert_eq!(sum[1], 3.0);
452
453        let product = stack_mul(&a, &b);
454        assert_eq!(product[0], 0.0);
455        assert_eq!(product[1], 2.0);
456    }
457
458    #[test]
459    fn test_stack_statistics() {
460        let mut arr = StackArray::<f32, 4>::new();
461        arr[0] = 1.0;
462        arr[1] = 2.0;
463        arr[2] = 3.0;
464        arr[3] = 4.0;
465
466        let total = stack_sum(&arr);
467        assert_eq!(total, 10.0);
468
469        let avg = stack_mean(&arr);
470        assert_eq!(avg, 2.5);
471    }
472}