Skip to content

Commit

Permalink
docs
Browse files Browse the repository at this point in the history
* include readme as landing page in docs
* warn on missing docs, add other pragmas
* add missing docs
* cleanup docs, modules
* remove unused tools/utils
  • Loading branch information
jordens committed Jan 12, 2024
1 parent d2bd6be commit 50576e3
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 82 deletions.
25 changes: 8 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
# Embedded DSP algorithms

[![GitHub release](https://img.shields.io/github/v/release/quartiq/idsp?include_prereleases)](https://github.com/quartiq/idsp/releases)
[![crates.io](https://img.shields.io/crates/v/idsp.svg)](https://crates.io/crates/idsp)
[![Documentation](https://img.shields.io/badge/docs-online-success)](https://docs.rs/idsp)
[![QUARTIQ Matrix Chat](https://img.shields.io/matrix/quartiq:matrix.org)](https://matrix.to/#/#quartiq:matrix.org)
[![Continuous Integration](https://github.com/quartiq/idsp/actions/workflows/ci.yml/badge.svg)](https://github.com/quartiq/idsp/actions/workflows/ci.yml)

This crate contains some tuned DSP algorithms for general and especially embedded use.
Many of the algorithms are implemented on integer datatypes for reasons that become important in certain cases:

* Speed: even with a hard floating point unit integer operations are faster.
* Accuracy: single precision FP has a 24 bit mantissa, `i32` has full 32 bit.
* No rounding errors.
* Natural wrap around (modulo) at the integer overflow: critical for phase/frequency applications.
* Natural definition of "full scale".
Many of the algorithms are implemented on integer (fixed point) datatypes.

One comprehensive user for these algorithms is [Stabilizer](https://github.com/quartiq/stabilizer).

Expand All @@ -24,10 +19,6 @@ This uses a small (128 element or 512 byte) LUT, smart octant (un)mapping, linea

This returns a phase given a complex signal (a pair of in-phase/`x`/cosine and quadrature/`y`/sine). The RMS phase error is less than 5e-6 rad, max error is less than 1.2e-5 rad, i.e. 20.5 bit RMS, 19.1 bit max accuracy. The bias is minimal.

## ComplexExt

An extension trait for the `num::Complex` type featuring especially a `std`-like API to the two functions above.

## PLL, RPLL

High accuracy, zero-assumption, fully robust, forward and reciprocal PLLs with dynamically adjustable time constant and arbitrary (in the Nyquist sampling sense) capture range, and noise shaping.
Expand All @@ -36,22 +27,22 @@ High accuracy, zero-assumption, fully robust, forward and reciprocal PLLs with d

Tools to handle, track, and unwrap phase signals or generate them.

## iir
## IIR/Biquad

Fixed point (`i8`, `i16`, `i32`, `i64`) and floating point (`f32`, `f64`) biquad IIR filters.
Robust and clean clipping and offset (anti-windup, no derivative kick, dynamically adjustable gains) suitable for PID controller applications.
Direct Form 1 and Direct Form 2 Transposed supported.
Coefficient sharing for multiple channels.

## State variable filter

Simple IIR state variable filter simultaneously providing highpass, lowpass,
bandpass, and notch filtering of a signal.

## Lowpass, Lockin

Fast, infinitely cascadable, first- and second-order lowpass and the corresponding integration into a lockin amplifier algorithm.

## FIR filters

Fast `f32` symmetric FIR filters, optimized half-band filters, half-band filter decimators and integators and cascades.

## SVF filter

Simple IIR state variable filter simultaneously providing highpass, lowpass,
bandpass, and notch filtering of a signal.
2 changes: 2 additions & 0 deletions src/accu.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use num_traits::ops::wrapping::WrappingAdd;

/// Wrapping Accumulator
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
pub struct Accu<T> {
state: T,
step: T,
}

impl<T> Accu<T> {
/// Create a new accumulator with given initial state and step.
pub fn new(state: T, step: T) -> Self {
Self { state, step }
}
Expand Down
7 changes: 7 additions & 0 deletions src/complex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ use super::{atan2, cossin};

/// Complex extension trait offering DSP (fast, good accuracy) functionality.
pub trait ComplexExt<T, U> {
/// Unit magnitude from angle
fn from_angle(angle: T) -> Self;
/// Square of magnitude
fn abs_sqr(&self) -> U;
/// Log2 approximation
fn log2(&self) -> T;
/// Angle
fn arg(&self) -> T;
/// Staturating addition
fn saturating_add(&self, other: Self) -> Self;
/// Saturating subtraction
fn saturating_sub(&self, other: Self) -> Self;
}

Expand Down Expand Up @@ -97,6 +103,7 @@ impl ComplexExt<i32, u32> for Complex<i32> {

/// Full scale fixed point multiplication.
pub trait MulScaled<T> {
/// Scaled multiplication for fixed point
fn mul_scaled(self, other: T) -> Self;
}

Expand Down
10 changes: 10 additions & 0 deletions src/filter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/// Single inpout single output i32 filter
pub trait Filter {
/// Filter configuration type.
///
/// While the filter struct owns the state,
/// the configuration is decoupled to allow sharing.
type Config;
/// Update the filter with a new sample.
///
Expand All @@ -16,6 +21,9 @@ pub trait Filter {
fn set(&mut self, x: i32);
}

/// Nyquist zero
///
/// Filter with a flat transfer function and a transfer function zero at Nyquist.
#[derive(Copy, Clone, Default)]
pub struct Nyquist(i32);
impl Filter for Nyquist {
Expand All @@ -34,6 +42,7 @@ impl Filter for Nyquist {
}
}

/// Repeat another filter
#[derive(Copy, Clone)]
pub struct Repeat<const N: usize, T>([T; N]);
impl<const N: usize, T: Filter> Filter for Repeat<N, T> {
Expand All @@ -54,6 +63,7 @@ impl<const N: usize, T: Default + Copy> Default for Repeat<N, T> {
}
}

/// Combine two different filters in cascade
#[derive(Copy, Clone, Default)]
pub struct Cascade<T, U>(T, U);
impl<T: Filter, U: Filter> Filter for Cascade<T, U> {
Expand Down
18 changes: 17 additions & 1 deletion src/hbf.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! Half-band filters and cascades
//!
//! Used to perform very efficient high-dynamic range rate changes by powers of two.
use core::{
iter::Sum,
ops::{Add, Mul},
Expand Down Expand Up @@ -457,12 +461,18 @@ impl Default for HbfDecCascade {
}

impl HbfDecCascade {
/// Set cascade depth
///
/// Sets the number of HBF filter stages to apply.
#[inline]
pub fn set_depth(&mut self, n: usize) {
assert!(n <= 4);
self.depth = n;
}

/// Cascade depth
///
/// The number of HBF filter stages to apply.
#[inline]
pub fn depth(&self) -> usize {
self.depth
Expand Down Expand Up @@ -543,7 +553,7 @@ impl Filter for HbfDecCascade {
#[derive(Copy, Clone, Debug)]
pub struct HbfIntCascade {
depth: usize,
pub stages: (
stages: (
HbfInt<
'static,
f32,
Expand Down Expand Up @@ -586,11 +596,17 @@ impl Default for HbfIntCascade {
}

impl HbfIntCascade {
/// Set cascade depth
///
/// Sets the number of HBF filter stages to apply.
pub fn set_depth(&mut self, n: usize) {
assert!(n <= 4);
self.depth = n;
}

/// Cascade depth
///
/// The number of HBF filter stages to apply.
pub fn depth(&self) -> usize {
self.depth
}
Expand Down
2 changes: 2 additions & 0 deletions src/iir/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! IIR filters, coefficients and applications
mod biquad;
pub use biquad::*;
mod coefficients;
Expand Down
9 changes: 6 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)]
#![doc = include_str!("../README.md")]
#![deny(rust_2018_compatibility)]
#![deny(rust_2018_idioms)]
#![warn(missing_docs)]
#![forbid(unsafe_code)]

mod tools;
pub use tools::*;
mod atan2;
pub use atan2::*;
mod accu;
Expand Down
3 changes: 3 additions & 0 deletions src/lockin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::{Complex, ComplexExt, Filter, MulScaled};

/// Lockin filter
///
/// Combines two [`Filter`] and an NCO to perform demodulation
#[derive(Copy, Clone, Default)]
pub struct Lockin<T> {
state: [T; 2],
Expand Down
15 changes: 9 additions & 6 deletions src/lowpass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::Filter;
///
/// The filter will cleanly saturate towards the `i32` range.
///
///
/// Both filters have been optimized for accuracy, dynamic range, and
/// speed on Cortex-M7.
#[derive(Copy, Clone)]
Expand All @@ -33,9 +32,13 @@ impl<const N: usize> Filter for Lowpass<N> {
/// `1 << 16 <= k <= q*(1 << 31)`.
type Config = [i32; N];
fn update(&mut self, x: i32, k: &Self::Config) -> i32 {
let mut d = x.saturating_sub((self.0[0] >> 32) as i32) as i64 * k[0] as i64;
let mut d = x.saturating_sub(self.get()) as i64 * k[0] as i64;
let y;
if N >= 2 {
if N == 1 {
self.0[0] += d;
y = self.get();
self.0[0] += d;
} else if N == 2 {
d += (self.0[1] >> 32) * k[1] as i64;
self.0[1] += d;
self.0[0] += self.0[1];
Expand All @@ -47,15 +50,15 @@ impl<const N: usize> Filter for Lowpass<N> {
self.0[0] += self.0[1];
self.0[1] += d;
} else {
self.0[0] += d;
y = self.get();
self.0[0] += d;
unimplemented!()
}
y
}

fn get(&self) -> i32 {
(self.0[0] >> 32) as i32
}

fn set(&mut self, x: i32) {
self.0[0] = (x as i64) << 32;
}
Expand Down
4 changes: 4 additions & 0 deletions src/num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub trait FilterNum: 'static + Copy + Num + AsPrimitive<Self::ACCU> {
/// Undefined result if `max < min`.
fn macc(self, s: Self::ACCU, min: Self, max: Self, e1: Self) -> (Self, Self);

/// Clamp to between min and max
///
/// Undefined if `min > max`.
fn clip(self, min: Self, max: Self) -> Self;

/// Multiplication (scaled)
Expand All @@ -33,6 +36,7 @@ pub trait FilterNum: 'static + Copy + Num + AsPrimitive<Self::ACCU> {
where
Self: AsPrimitive<C>,
C: Float + AsPrimitive<Self>;
// TODO: range check and Result
}

macro_rules! impl_float {
Expand Down
21 changes: 20 additions & 1 deletion src/svf.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
//! State variable filter
use num_traits::{Float, FloatConst};
use serde::{Deserialize, Serialize};

/// Second order state variable filter state
pub struct State<T> {
/// Lowpass output
pub lp: T,
/// Highpass output
pub hp: T,
/// Bandpass output
pub bp: T,
}

impl<T: Float> State<T> {
/// Bandreject (notch) output
pub fn br(&self) -> T {
self.hp + self.lp
}
}

/// State variable filter
///
/// <https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/>
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd)]
pub struct Svf<T> {
f: T,
q: T,
}

impl<T: Float + FloatConst> Svf<T> {
/// Set the critical frequency
///
/// In units of the sample frequency.
pub fn set_frequency(&mut self, f0: T) {
self.f = (T::one() + T::one()) * (T::PI() * f0).sin();
}

/// Set the Q parameter
pub fn set_q(&mut self, q: T) {
self.q = T::one()/q;
self.q = T::one() / q;
}

/// Update the filter
///
/// Ingest an input sample and update state correspondingly.
/// Selected output(s) are available from [`State`].
pub fn update(&self, s: &mut State<T>, x0: T) {
s.lp = s.bp * self.f + s.lp;
s.hp = x0 - s.lp - s.bp * self.q;
Expand Down
54 changes: 0 additions & 54 deletions src/tools.rs

This file was deleted.

0 comments on commit 50576e3

Please sign in to comment.