I don't know how long an abs() in C takes
On ARMv6-m (Cortex-M0/M0+) and ARMv7e-m (Cortex-M3/M4/M7) it's two or three instructions, no jumps.
If you have
<stdlib.h> or equivalent, just use its
abs().
If you use GCC or Clang, use
__builtin_abs(). This should yield the fastest integer absolute value the compiler can generate.
(Unless there is an even better way, it usually ends up becoming
t =
x arithmetic right-shifted all the way so that sign bit is copied to all bits, then abs(
x) = (
x+
t)^
t = (
x^
t)-
t.)
Here's the code I would probably use on a Cortex-M:
const int32_t average_scale; // 1 .. 32767, constexpr for C++
int32_t average_magnitude_scaled; // 0 .. 1073741823
uint32_t average_magnitude; // 0 .. 32768
uint32_t update_average_magnitude(const int16_t sample) {
const int32_t sample_magnitude = abs(sample);
average_magnitude_scaled += (int32_t)average_scale * (sample_magnitude - (int32_t)(average_magnitude_scaled >> 15));
average_magnitude = (uint32_t)(average_magnitude_scaled + 16384) >> 15;
return average_magnitude;
}
It compiles to less than two dozen instructions, no conditionals; and uses a single 32×32=32-bit (signed) multiplication. The rest are binary operations, additions, and subtractions. You could definitely use multiple in parallel, too, even on a Cortex-M0.
For the mathematical analysis,
average_scale = 32768 - 32768 *
alpha = 32768 - 32768 /
n, or equivalently,
alpha = 1 -
scale/32768 = 1/
n, and
n = 32768 / (32768 -
scale). Essentially,
alpha is the weight of the new sample, and (1-
alpha) the weight of the average thus far.
Note that in my implementation,
average_magnitude is
rounded to
average_magnitude_scaled. This is mostly because the original sample range is signed, and I want zero samples to have the same unit range as all other magnitudes, i.e. ]-0.5,+0.5[ instead of double the others ]-1.0,+1.0[ that it would be if truncating.
(To get the scaled average magnitude to converge to the middle of the integer-valued region, we do need to truncate the scaled average magnitude when dividing it down in the average calculation. Fortunately, on Cortex-M, this generates even better code than using pre-computed value in a separate variable, because adding or subtracting a value shifted by some bits is only one instruction. If a separate variable were used, even
average_magnitude, it would first have to be loaded into a register, thus only adding more machine code.)
The smallest
alpha this can represent is 1/32768 = 0.000030517578125, corresponding to
n = 32768, when
average_scale = 1. Then, convergence from zero to sample magnitude 32768 takes 359605 consecutive samples of value -32768.
average_magnitude does reach 32768 after 343220 consecutive samples, but it takes an additional 16385 samples for the
average_magnitude_scaled to converge and no longer change.
Note that you cannot choose an arbitrary
n or
alpha, because you're limited to integer values of
average_scale (and therefore integer values of 32768/
n). This, the reduced choice in larger
n or very small
alpha, is the price we pay by converting the integer division to an integer multiplication.