#oscillator #signal #trig #waveform #iq

iq_osc

I/Q oscillator without trig function calls

1 stable release

Uses old Rust 2015

3.0.0 Jan 2, 2018

#17 in #oscillator

MIT license

22KB
318 lines

iq_osc.rs – I/Q oscillator without trig function calls

Documentation

Given f(t) = cos(θ0 + ωt) = cos Φ(t), an oscillator is defined here to evaluate f(0), f(1), f(2), ... in sequence to generate a sinusoidal signal. Further, a quadrature oscillator also evaluates g(t) = sin Φ(t) at each step for the quadrature signal.

Computing these trig functions at each evaluation of f(t) and g(t) can be costly with a high sample rate or within a tight loop. As an alternative, this crate implements a quadrature oscillator that replaces the 2 trig function calls at each evaluation with 6 arithmetic operations (4 multiplies, 1 addition, and 1 subtraction.)

Usage

This crate can be used through cargo by adding it as a dependency in Cargo.toml:

[dependencies]
iq_osc = "3.0.0"

and importing it in the crate root:

extern crate iq_osc;

lib.rs:

I/Q oscillator without trig function calls.

Given

f(t) = cos(θ0 + ωt) = cos Φ(t),

an oscillator is defined here to evaluate f(0), f(1), f(2), ... in sequence to generate a sinusoidal signal. Further, a quadrature oscillator also evaluates g(t) = sin Φ(t) at each step for the quadrature signal.

Calling out to these trig functions at each evaluation of f(t) and g(t) can be costly with a high sample rate or within a tight loop. As an alternative, this crate implements a quadrature oscillator that replaces the 2 trig function calls at each evaluation with 6 arithmetic operations (4 multiplies, 1 addition, and 1 subtraction.)

Theory

Using the definition of f(t) = cos Φ(t) from above, notice that

Φ(0) = θ0

Φ(1) = θ0 + ω = Φ(0) + ω

Φ(2) = θ0 + 2ω = Φ(1) + ω

And in general,

Φ(t) = Φ(t - 1) + ω, t > 0

With this and the trig identities

cos(u + v) = cos(u)cos(v) - sin(u)sin(v)

sin(u + v) = sin(u)cos(v) + cos(u)sin(v)

we can then write f(t) as

f(t) = cos(Φ(t - 1) + ω) = cos(Φ(t - 1))cos(ω) - sin(Φ(t - 1))sin(ω)

and similarly for the quadrature signal,

g(t) = sin(Φ(t - 1) + ω) = sin(Φ(t - 1))cos(ω) + cos(Φ(t - 1))sin(ω)

If we compute sin ω, cos ω, sin θ0, and cos θ0 at initialization, then we can compute f(t) and g(t) for t = 0, 1, 2, ... using just the arithmetic in the above equations.

Error Accumulation

Due to the accumulation of floating-point roundoff errors, the accuracy of returned sine/cosine evaluations will slowly degrade over phase steps. Using a very small phase step or running an IQOsc through many, many cycles will make this problem more pronounced. As a workaround, the double-precision IQOsc<f64> can be used, which provides a significant increase in accuracy across phase steps and has relatively little impact on speed – compare bench_osc32 and bench_osc64 in the output of cargo bench (example output is given below.)

test bench_osc32  ... bench:      55,043 ns/iter (+/- 4,479)
test bench_osc64  ... bench:      62,170 ns/iter (+/- 30,989)
test bench_trig32 ... bench:     490,407 ns/iter (+/- 90,148)
test bench_trig64 ... bench:   2,365,592 ns/iter (+/- 148,062)

Dependencies

~240KB