Skip to content

Getting Started

Install

Arduino Library Manager

In the Arduino IDE, open Tools → Manage Libraries…, search for SignalCore, and click Install.

PlatformIO

Add the dependency to platformio.ini:

lib_deps =
    SignalCore

Manual

Clone or download the repository into the Arduino libraries directory:

cd ~/Documents/Arduino/libraries
git clone https://github.com/voidloopers/SignalCore.git

The libraries folder is found within the Arduino sketchbook folder, whose location can be found within the Settings dialog. Click Arduino IDE → Settings and look for the Sketchbook location text box. The libraries folder is not necessarily auto created — if it does not yet exist, simply create the folder and copy SignalCore into it.

Restart the Arduino IDE so it picks up the new library.

Confirm the install by opening the SignalGeneratorBasic example (File → Examples → SignalCore → SignalGeneratorBasic) and uploading it to a board.

Hello SignalCore

The smallest useful sketch generates a tone in software and prints it one sample per line, so the Arduino Serial Plotter draws it directly. No analog hardware is needed, which makes this a good first check that the library compiles and runs on your board.

#include <SignalCore.h>
using namespace signalcore;

#define SAMPLE_RATE 10000          // virtual samples per second

SignalGenerator sig(SAMPLE_RATE);

void setup() {
    Serial.begin(115200);
    sig.setCarrier(SignalShape::Sine, 200.0);   // 200 Hz tone
}

void loop() {
    Serial.println((float)sig.nextSample(0), 4);   // 0 = full-resolution float
    delayMicroseconds(1000000UL / SAMPLE_RATE);    // pace for the plotter
}

Upload it, then open Tools → Serial Plotter at 115200 baud. A clean 200 Hz sine wave should scroll across the window. This is the bundled SignalGeneratorBasic example in its simplest form.

A first FFT

The next step generates a tone, runs an FFT over it, and reports the peak bin. The reported frequency should track the carrier to within one bin, where a bin is SAMPLE_RATE / FFT_LENGTH wide.

The transform length is set once, as a single number. That same number sizes the sample buffer too, so there is only one value to change when you want a different length:

#include <SignalCore.h>
using namespace signalcore;

#define SAMPLE_RATE 10000
#define FFT_LENGTH  1024           // power of two, 32 up to 4096

FFT<FFT_LENGTH> fft;               // the transform, sized at compile time
SignalGenerator sig(SAMPLE_RATE);
float           samples[FFT_LENGTH];

void setup() {
    Serial.begin(115200);
    sig.setCarrier(SignalShape::Sine, 1000.0);          // 1 kHz tone
    fft.setWindowType(WindowType::BlackmanHarris);
}

void loop() {
    for (int i = 0; i < FFT_LENGTH; i++) {
        samples[i] = (float)sig.nextSample(0);
    }

    removeDC(samples, FFT_LENGTH);                       // strip DC before transforming
    fft.calculate(samples);                             // run the transform

    // peakBin() searches result[0 .. N/2] and skips the DC bin by default, so a
    // constant offset cannot win the search. peakFrequency() converts it to Hz.
    int   peak = fft.peakBin();
    float hz   = fft.peakFrequency(SAMPLE_RATE);
    Serial.print("Peak bin: "); Serial.print(peak);
    Serial.print("  Hz: ");     Serial.println(hz);

    delay(250);
}

A few things worth knowing:

  • The size is one number. FFT<FFT_LENGTH> takes a plain integer, and the same FFT_LENGTH sizes the samples buffer, so the two cannot drift apart. You can also write the length directly, as FFT<1024>. A named form, FFT<FFTSize::N1024>, still works if you prefer it.
  • The size is fixed at compile time and must be a power of two between 32 and
  • A value that is not a power of two, such as FFT<500>, fails to compile with a message saying why, rather than misbehaving at run time.
  • Reading the spectrum. peakBin() and peakFrequency() hand back the dominant tone directly. To read the spectrum itself, treat the FFT object as a float* array, where only the first N/2 + 1 entries hold real magnitude bins.
  • Board limits. The AVR build cap is 256 points, sized for larger AVR parts such as the Mega 2560. On an ATmega328 board (UNO R3, Nano — 2 KB of SRAM) the practical ceiling is FFT<128>: FFT<256> compiles but its ~2.5 KB of buffers do not fit. See Configuration & Platforms.

Tip

On a Teensy 4.x the FFT runs on CMSIS-DSP automatically, with no change to this sketch. On every other board the portable C++ implementation produces the same output, bin for bin, so the same code ports unchanged.


Created by VoidLoop · Founded by Gregory Kovacs · Written by Zachariah Magee