Mirko Pavleski
Published © GPL3+

Elecrow All-in-One Arduino Starter Kit Review - 20 Projects

This Elecrow Starter Kit saves time, eliminates wiring problems, and makes learning electronics both simple and enjoyable.

BeginnerFull instructions provided1 hour102
Elecrow All-in-One Arduino Starter Kit Review - 20 Projects

Things used in this project

Hardware components

All-in-one Starter Common Board Kit for Arduino with 15 Sensors and 21 Lessons
Elecrow All-in-one Starter Common Board Kit for Arduino with 15 Sensors and 21 Lessons
https://www.elecrow.com/all-in-one-starter-kit-for-arduino-nano-r4-with-20-lessons-and-16-modules.html?idd=5
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Mastech MS8217 Autorange Digital Multimeter
Digilent Mastech MS8217 Autorange Digital Multimeter

Story

Read more

Code

Code Spectrum Analyzer

C/C++
...
/*
  16x2 LCD Spectrum Analyzer
  Adapted for:
  Elecrow All-in-One Starter Kit for Arduino Nano R4

  Hardware connections on the kit:
  --------------------------------
  Microphone / Sound sensor : A1
  Three buttons             : A3
  LCD SDA                    : A4
  LCD SCL                    : A5
  LCD I2C address            : 0x27

  K1 changes the spectrum display style.

  by mircemk July 2026

*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <arduinoFFT.h>

// =====================================================
// ELECROW KIT PINS
// =====================================================

#define AUDIO_PIN   A1
#define BUTTON_PIN  A3

// =====================================================
// LCD CONFIGURATION
// Same configuration as the official Elecrow LCD code
// =====================================================

#define LCD_COLUMNS 16
#define LCD_ROWS     2

LiquidCrystal_I2C lcd(
  PCF8574_ADDR_A21_A11_A01,
  4, 5, 6, 16, 11, 12, 13, 14,
  POSITIVE
);

// =====================================================
// FFT CONFIGURATION
// =====================================================

const uint16_t SAMPLES = 256;

// 8000 Hz gives a maximum detectable frequency of 4000 Hz.
const double SAMPLING_FREQUENCY = 8000.0;

double vReal[SAMPLES];
double vImag[SAMPLES];

ArduinoFFT<double> FFT =
  ArduinoFFT<double>(
    vReal,
    vImag,
    SAMPLES,
    SAMPLING_FREQUENCY
  );

// FFT bins used for the 16 LCD columns.
// With 8000 Hz / 256 samples:
// one FFT bin is approximately 31.25 Hz.
const uint8_t spectrumBins[16] = {
  2,  3,  4,  6,
  8, 10, 12, 14,
  16, 18, 20, 22,
  24, 26, 28, 30
};

// =====================================================
// SPECTRUM SETTINGS
// =====================================================

// Increase this if the display reacts to background noise.
// Decrease it if the spectrum reacts too weakly.
const double NOISE_FLOOR_DB = 20.0;

// Minimum dynamic range above the noise floor.
const double MIN_DYNAMIC_RANGE_DB = 8.0;

// Additional space above the strongest peak.
const double PEAK_HEADROOM_DB = 4.0;

// Spectrum movement smoothing.
// Higher value = faster movement.
// Lower value = smoother movement.
const float BAR_ATTACK = 0.95;
const float BAR_RELEASE = 0.55;

// Automatic gain smoothing.
const float AUTO_GAIN_SPEED = 0.12;

// Displayed bar levels: 0 to 15.
float displayedLevel[16];

// Automatically adjusted upper spectrum limit.
double autoGainTopDB = 55.0;

// =====================================================
// DISPLAY MODES
// =====================================================

// Six pixel patterns from the original project.
const uint8_t modePattern[6] = {
  4,    // 00100
  14,   // 01110
  10,   // 01010
  27,   // 11011
  31,   // 11111
  21    // 10101
};

uint8_t spectrumMode = 0;

// Button debounce / release detection.
bool buttonWasPressed = false;
unsigned long lastButtonTime = 0;

// =====================================================
// FUNCTION PROTOTYPES
// =====================================================

void createSpectrumCharacters();
void readModeButton();
bool isK1Pressed();
void collectAudioSamples();
void calculateSpectrum();
void drawSpectrum();
double magnitudeToDb(double magnitude);

// =====================================================
// SETUP
// =====================================================

void setup() {
  Serial.begin(115200);

  pinMode(AUDIO_PIN, INPUT);
  pinMode(BUTTON_PIN, INPUT);

  // Default analogRead resolution used by the official
  // Elecrow button example is 10 bits: 0 to 1023.
  analogReadResolution(10);

  Wire.begin();

  lcd.begin(LCD_COLUMNS, LCD_ROWS, LCD_5x8DOTS);
  lcd.clear();
  lcd.backlight();

  createSpectrumCharacters();

  lcd.setCursor(0, 0);
  lcd.print("Spectrum");
  lcd.setCursor(0, 1);
  lcd.print("Analyzer Nano R4");

  delay(1200);
  lcd.clear();

  // Initial ADC read after selecting A1.
  // This helps the ADC input settle.
  analogRead(AUDIO_PIN);
}

// =====================================================
// MAIN LOOP
// =====================================================

void loop() {
  readModeButton();

  collectAudioSamples();
  calculateSpectrum();
  drawSpectrum();
}

// =====================================================
// READ K1 BUTTON ON A3
// =====================================================

void readModeButton() {
  bool pressed = isK1Pressed();

  if (pressed && !buttonWasPressed) {
    if (millis() - lastButtonTime > 250) {
      spectrumMode++;

      if (spectrumMode > 5) {
        spectrumMode = 0;
      }

      createSpectrumCharacters();

      lastButtonTime = millis();
    }

    buttonWasPressed = true;
  }

  if (!pressed) {
    buttonWasPressed = false;
  }
}

bool isK1Pressed() {
  /*
    Read twice because the ADC was previously reading A1.
    The first read allows the ADC multiplexer to settle.
  */

  analogRead(BUTTON_PIN);
  int buttonValue = analogRead(BUTTON_PIN);

  /*
    Official Elecrow values:

    K1 approximately 500-520
    K2 approximately 680-690
    K3 approximately 845-860

    A slightly wider range is used for tolerance.
  */

  if (buttonValue >= 470 && buttonValue <= 550) {
    return true;
  }

  return false;
}

// =====================================================
// CREATE THE 8 CUSTOM LCD BAR CHARACTERS
// =====================================================

void createSpectrumCharacters() {
  uint8_t pixelPattern = modePattern[spectrumMode];

  for (uint8_t characterNumber = 0;
       characterNumber < 8;
       characterNumber++) {

    uint8_t customCharacter[8];

    /*
      Character 0 has one active row.
      Character 7 has all eight rows active.
    */

    for (uint8_t row = 0; row < 8; row++) {
      if (row >= (7 - characterNumber)) {
        customCharacter[row] = pixelPattern;
      } else {
        customCharacter[row] = 0;
      }
    }

    lcd.createChar(characterNumber, customCharacter);
  }
}

// =====================================================
// AUDIO SAMPLING
// =====================================================

void collectAudioSamples() {
  const uint32_t samplingPeriodMicroseconds =
    1000000UL / SAMPLING_FREQUENCY;

  // Allow ADC multiplexer to settle after reading A3.
  analogRead(AUDIO_PIN);

  for (uint16_t i = 0; i < SAMPLES; i++) {
    uint32_t sampleStart = micros();

    vReal[i] = analogRead(AUDIO_PIN);
    vImag[i] = 0.0;

    while ((micros() - sampleStart) <
           samplingPeriodMicroseconds) {
      // Wait for the next sample time.
    }
  }
}

// =====================================================
// FFT CALCULATION
// =====================================================

void calculateSpectrum() {
  // Remove microphone DC offset.
  FFT.dcRemoval();

  // Apply Hamming window.
  FFT.windowing(
    FFTWindow::Hamming,
    FFTDirection::Forward
  );

  // Calculate FFT.
  FFT.compute(FFTDirection::Forward);

  // Convert complex values into magnitudes.
  FFT.complexToMagnitude();

  double frameMaximumDB = NOISE_FLOOR_DB;

  // Find strongest displayed frequency bin.
  for (uint8_t column = 0; column < 16; column++) {
    uint8_t bin = spectrumBins[column];

    double levelDB = magnitudeToDb(vReal[bin]);

    if (levelDB > frameMaximumDB) {
      frameMaximumDB = levelDB;
    }
  }

  // Calculate target upper gain limit.
  double targetTopDB = frameMaximumDB + PEAK_HEADROOM_DB;

  if (targetTopDB <
      NOISE_FLOOR_DB + MIN_DYNAMIC_RANGE_DB) {

    targetTopDB =
      NOISE_FLOOR_DB + MIN_DYNAMIC_RANGE_DB;
  }

  // Smooth automatic gain.
  autoGainTopDB =
    autoGainTopDB * (1.0 - AUTO_GAIN_SPEED) +
    targetTopDB * AUTO_GAIN_SPEED;

  // Calculate 16 display levels.
  for (uint8_t column = 0; column < 16; column++) {
    uint8_t bin = spectrumBins[column];

    double levelDB = magnitudeToDb(vReal[bin]);

    double normalized =
      (levelDB - NOISE_FLOOR_DB) /
      (autoGainTopDB - NOISE_FLOOR_DB);

    if (normalized < 0.0) {
      normalized = 0.0;
    }

    if (normalized > 1.0) {
      normalized = 1.0;
    }

    float targetLevel = normalized * 15.0;

    // Faster rise, slower fall.
    if (targetLevel > displayedLevel[column]) {
      displayedLevel[column] =
        displayedLevel[column] * (1.0 - BAR_ATTACK) +
        targetLevel * BAR_ATTACK;
    } else {
      displayedLevel[column] =
        displayedLevel[column] * (1.0 - BAR_RELEASE) +
        targetLevel * BAR_RELEASE;
    }
  }
}

// =====================================================
// CONVERT FFT MAGNITUDE TO DECIBEL-LIKE VALUE
// =====================================================

double magnitudeToDb(double magnitude) {
  return 20.0 * log10(magnitude + 1.0);
}

// =====================================================
// DRAW 16 SPECTRUM BARS
// =====================================================

void drawSpectrum() {
  for (uint8_t column = 0; column < 16; column++) {
    int level = round(displayedLevel[column]);

    level = constrain(level, 0, 15);

    if (level == 0) {
      lcd.setCursor(column, 0);
      lcd.print(' ');

      lcd.setCursor(column, 1);
      lcd.print(' ');
    }
    else if (level <= 8) {
      lcd.setCursor(column, 0);
      lcd.print(' ');

      lcd.setCursor(column, 1);
      lcd.write((uint8_t)(level - 1));
    }
    else {
      lcd.setCursor(column, 0);
      lcd.write((uint8_t)(level - 9));

      lcd.setCursor(column, 1);
      lcd.write((uint8_t)7);
    }
  }
}

Credits

Mirko Pavleski
248 projects • 1684 followers
Service specialist

Comments