Embedded HandbookApps
All sections › XIV. Machine learning on a microcontroller
STM32F407 · level intermediate · 9 min read
Open in the interactive handbook

117. Data from the sensor: windows, features, CMSIS-DSP

The model from section 116 received ready-made numbers — the RMS vibration and the peak frequency. On a device you have to compute them yourself from the stream of accelerometer samples: cut the stream into windows, remove the offset, take the spectrum, find the peak. How this is done matters more than the choice of model: a poor feature spoils any classifier. This section computes the features with the CMSIS-DSP library — the same one that runs on the F407 — only on a PC, where it can be checked against a calculation by the formulas, and shows how one well-chosen feature raises bearing recognition from 80 to 100 cases out of 100.

Models are section 116, the ADC is section 14, DMA is section 9, the accelerometer is section 99.

Terms used in the documentation

Term Meaning
sampling rate how many samples are taken per second
Nyquist frequency half the sampling rate
aliasing high frequencies showing up as false low ones
anti-aliasing filter a filter in front of the ADC that removes aliasing
frame, window a piece of the signal of N samples
fast Fourier transform (FFT) a fast way to compute the spectrum
frequency bin the frequency band of one spectrum point
spectral leakage the energy of a tone spreading into neighbouring bins
window function (Hann) a weighting that tapers the edges of the window
parabolic interpolation refining the peak with a parabola
DC offset the constant component, the zero offset
crest factor the largest value / RMS
band energy the energy within a frequency band

117.1 From the sensor to the features

The chain on the device: accelerometer → ADC triggered by a timer, DMA stores the samples → a window of 256 samples → features → the model's decision. At a sampling rate of 1024 Hz a window lasts 250 ms, and a decision is ready four times a second; windows with half overlap give a decision every 125 ms. Everything below is about how to get from these 256 numbers two or three features you can trust.


117.2 Sampling rate and aliasing

Sampling tells frequencies apart only up to half its own rate: at 1024 Hz, up to 512 Hz. Everything above does not disappear but pretends to be a low frequency. Experiment: a 160 Hz tone sampled at 256 Hz landed in the spectrum at 96 Hz — 256 − 160. Such a false peak cannot be told from a real one by the spectrum, so the sampling rate is chosen with a margin above the highest frequency of interest, and a filter is placed in front of the ADC that suppresses everything above half the sampling rate.


117.3 The window: time against resolution

Spectrum of a 25 Hz tone: without a window the energy spreads, the Hann window keeps it at the peak

The spectrum of a window of N samples is divided into bins of width FS / N: at 1024 Hz and 256 samples, 4 Hz. A longer window gives narrower bins, but you wait longer for a decision and fast changes are harder to see. A 24 Hz tone falls exactly on bin 6, and the peak comes out at 24.0000 Hz, and the RMS at 0.3536 for an amplitude of 0.5: exactly 0.5 / √2, because a whole number of periods fit into the window.


117.4 Leakage and the Hann window

A 25 Hz tone does not fall on a bin, and its energy spreads into the neighbouring bins — leakage. Without a window, five bins away from the peak there is still −24.89 dB: this is enough to hide a weak bearing tone next to a strong one. The Hann window smoothly tapers the edges of the segment, and at the same place there is −52.81 dB. The window is applied before the transform, with one multiplication:

    if (hann)
        arm_mult_f32(buf, win, buf, N_WIN);

117.5 A peak between bins

The bin number gives the frequency in steps of 4 Hz. More precisely — the vertex of a parabola through the magnitudes of three neighbouring bins:

static float refine(uint32_t k)
{
    if (k == 0 || k >= N_WIN / 2 - 1)
        return (float)k;
    float a = mag[k - 1], b = mag[k], c = mag[k + 1];
    float den = a - 2.0f * b + c;
    return den != 0.0f ? (float)k + 0.5f * (a - c) / den : (float)k;
}

For a 25 Hz tone without a window the parabola gives 24.19 Hz, with the Hann window 24.80 Hz. The window is needed here too: its peak is closer to a parabola in shape.


117.6 Offset, RMS and crest factor

An accelerometer powered from a single supply outputs vibration around the middle of the scale — for example, 1.65 V. This constant component is subtracted before anything else, otherwise it gets into the RMS and occupies the zero bin. Experiment: a 160 Hz tone with an amplitude of 0.5 on top of an offset of 1.65 — RMS 0.3536, the same as without the offset, and the peak exactly at 160.00 Hz. The crest factor is the largest value divided by the RMS: for a pure sine it is 1.4142, that is √2, while the impacts of a spalled bearing raise it noticeably higher — one more feature, almost for free.


117.7 A feature that fits the physics of the defect

Three motor states — 100 windows each, with amplitude spread of ±30 %, frequency spread and noise — and the threshold from section 116 on features computed from the signal:

Normal Imbalance Bearing
By peak frequency: correct 100 82 80
By energy in the 100–248 Hz band: correct 100 82 100

By the frequency of the highest peak the bearing is recognized in only 80 windows out of 100: when the 25 Hz rotation tone turns out stronger than the defect tone, the peak moves to 25 Hz, and in 17 windows the bearing is taken for normal. The mean peak frequency for the bearing is 131.7 Hz instead of 160. The feature "share of energy in the 100–248 Hz band is greater than 0.1" asks not "where is the main peak" but "is there energy where the bearing sounds" — and recognizes it in all 100 windows. Imbalance is still confused with normal in 18 windows: they have the same frequency and a similar RMS, and that is already a question of data and model — sections 118–120.

A feature that fits the physics of the defect: band energy recognizes the bearing in all 100 windows, the peak in 80

    arm_power_f32(mag + 1, N_WIN / 2 - 1, &e_all);
    arm_power_f32(mag + BAND_LO, BAND_HI - BAND_LO + 1, &e_band);
    f->band = e_all > 0.0f ? e_band / e_all : 0.0f;

117.8 CMSIS-DSP on the F407

The whole feature calculation is CMSIS-DSP calls:

    arm_mean_f32(x, N_WIN, &mean);                  /* смещение нуля датчика и АЦП */
    arm_offset_f32(x, -mean, buf, N_WIN);
    arm_rms_f32(buf, N_WIN, &f->rms);
    arm_absmax_f32(buf, N_WIN, &peak, &k);

The image for the F407 with the Keil startup file took 8964 bytes of code and 2920 bytes of FFT tables in flash, and 6272 bytes of RAM — the window, spectrum and sample buffers plus the stack and heap of the startup file. One line decides how much the tables take: the generic arm_rfft_fast_init_f32 accepts any size and therefore pulls tables for all sizes into the image — 79504 bytes, 27 times more. Initialization for one size keeps only the ones needed:

    return arm_rfft_fast_init_256_f32(&fft) == ARM_MATH_SUCCESS;

On a PC the same library is built by gcc in __GNUC_PYTHON__ mode, and all the experiments of the section are computed with it; the check compared them with a calculation by the formulas.


117.9 Pitfalls

There is a peak in the spectrum that the mechanism does not have. Aliasing: the sampling rate is too low or there is no filter in front of the ADC. 160 Hz at 256 Hz looks like 96 Hz.

The peak "wanders" between neighbouring frequencies. The tone does not fall on a bin. The Hann window and parabolic refinement.

The RMS does not drop to zero on a stopped motor. The constant component of the sensor is not subtracted.

The firmware grew by 75 KB from a single FFT. The generic initialization pulls in tables for all sizes; use arm_rfft_fast_init_256_f32.

The bearing is visible one moment and not the next. The feature is "frequency of the main peak": with a strong rotation tone the peak moves to it. Use the energy in the defect band.


117.10 Summary

Full sources

feat117.c — vibration features from a window of samples with CMSIS-DSP.

/* Раздел 117: признаки вибрации из окна отсчётов — на CMSIS-DSP.
 * Окно N_WIN отсчётов при FS: постоянная составляющая долой, СКЗ и пик-
 * фактор, окно Ханна, быстрое преобразование Фурье, модуль спектра, пик
 * и его уточнение параболой между бинами. Тот же файл собирается armclang
 * для F407 и gcc на компьютере (CMSIS-DSP в режиме __GNUC_PYTHON__) —
 * там feat117_host.c ставит опыты. Проверка — _verify/ml117_check.py. */
#include "arm_math.h"

#define FS     1024.0f                  /* Гц: частота выборки */
#define N_WIN  256u                     /* отсчётов в окне: 250 мс, 4 Гц на бин */
#define BAND_LO  25u                    /* бин 25 — 100 Гц: полоса дефекта подшипника */
#define BAND_HI  62u                    /* бин 62 — 248 Гц */

typedef struct {
    float rms;                          /* СКЗ без постоянной составляющей */
    float peak_hz;                      /* частота пика, уточнённая параболой */
    float crest;                        /* пик-фактор: наибольший модуль / СКЗ */
    float band;                         /* доля энергии спектра в полосе 100–248 Гц */
    uint32_t bin;                       /* номер бина пика до уточнения */
} features_t;

static float win[N_WIN], buf[N_WIN], spec[N_WIN], mag[N_WIN / 2];
static arm_rfft_fast_instance_f32 fft;

/* инициализация под один размер: общая arm_rfft_fast_init_f32 тянет в образ таблицы всех размеров */
_Static_assert(N_WIN == 256u, "размер окна совпадает с arm_rfft_fast_init_256_f32");

int features_init(void)
{
    arm_hanning_f32(win, N_WIN);
#ifdef FFT_ALL_SIZES                                /* для сравнения размеров образа */
    return arm_rfft_fast_init_f32(&fft, N_WIN) == ARM_MATH_SUCCESS;
#else
    return arm_rfft_fast_init_256_f32(&fft) == ARM_MATH_SUCCESS;
#endif
}

/* пик между бинами: вершина параболы через три соседних модуля */
static float refine(uint32_t k)
{
    if (k == 0 || k >= N_WIN / 2 - 1)
        return (float)k;
    float a = mag[k - 1], b = mag[k], c = mag[k + 1];
    float den = a - 2.0f * b + c;
    return den != 0.0f ? (float)k + 0.5f * (a - c) / den : (float)k;
}

void features(const float *x, int hann, features_t *f)
{
    float mean, peak, m;
    uint32_t k;
    arm_mean_f32(x, N_WIN, &mean);                  /* смещение нуля датчика и АЦП */
    arm_offset_f32(x, -mean, buf, N_WIN);
    arm_rms_f32(buf, N_WIN, &f->rms);
    arm_absmax_f32(buf, N_WIN, &peak, &k);
    f->crest = f->rms > 0.0f ? peak / f->rms : 0.0f;
    if (hann)
        arm_mult_f32(buf, win, buf, N_WIN);
    arm_rfft_fast_f32(&fft, buf, spec, 0);          /* buf служит рабочим — он больше не нужен */
    arm_cmplx_mag_f32(spec, mag, N_WIN / 2);
    mag[0] = 0.0f;                                  /* в первой паре — ноль и Найквист вместе: отбросить */
    arm_max_f32(mag, N_WIN / 2, &m, &f->bin);
    f->peak_hz = refine(f->bin) * FS / N_WIN;
    float e_all, e_band;                            /* энергия — сумма квадратов модулей */
    arm_power_f32(mag + 1, N_WIN / 2 - 1, &e_all);
    arm_power_f32(mag + BAND_LO, BAND_HI - BAND_LO + 1, &e_band);
    f->band = e_all > 0.0f ? e_band / e_all : 0.0f;
}

/* модуль спектра последнего окна — для опытов и рисунка */
const float *features_spectrum(void)
{
    return mag;
}

Next: section 118 "Training a small model in a container" — from windows with features to weights in the firmware.

Open in the interactive handbook