Project 1: NeuroGrip โœ‹ โ†’ Controlling Servo Motor Using EMG

Build a simple EMG-based system using the Muscle BioAmp Shield and Arduino Uno to control a servo claw โ€” just like prosthetic hands!

๐Ÿ”ฌ What is Electromyography (EMG)?

EMG measures electrical signals produced by muscle contractions. These signals help detect muscle activation and control assistive tech.

๐Ÿงฉ About Muscle BioAmp Shield

An Arduino-compatible EMG shield with plug-and-play sensors, accelerometers, OLEDs, and servo support.

๐Ÿงฐ Supplies Needed


๐Ÿงช Step 2: Stacking on Arduino Uno

Align shield pins properly before pressing it in.

๐Ÿงด Step 3: Skin Preparation

Apply NuPrep gel โ†’ rub gently โ†’ wipe clean.

๐Ÿ”Œ Step 4: Connecting Electrode Cable

Connect the BioAmp cable exactly as shown โ€” do NOT put electrodes yet.

๐Ÿ“ Step 5: Electrode Placement

Place electrodes on forearm as shown below:

Using EMG BioAmp Band

๐Ÿฆพ Step 6: Connect Servo Claw

Plug the servo claw into the BioAmp Shield ports. Servo moves automatically based on muscle activation.

๐Ÿง  Step 7 โ€” Upload the EMG โ†’ Servo Control Code

Paste the following code into Arduino IDE and upload:


#if defined(ESP32) 
  // For ESP32 Servo library
  #include <ESP32Servo.h>
#else
  // Arduino Servo library
  #include <Servo.h>
#endif

// Samples per second
#define SAMPLE_RATE 500

// Make sure to set the same baud rate on your Serial Monitor/Plotter
#define BAUD_RATE 115200

// Change if not using A0 analog pin
#define INPUT_PIN A0

// envelopeee buffer size
// High value -> smooth but less responsive
// Low value -> not smooth but responsive
#define BUFFER_SIZE 64

// Servo pin (Change as per your connection)
#define SERVO_PIN 2 // Pin2 for Muscle BioAmp Shield v0.3

// EMG Threshold value, different for each user
// Check by plotting EMG envelopee data on Serial plotter
#define EMG_THRESHOLD 47

// Servo open & close angles
#define SERVO_OPEN 10
#define SERVO_CLOSE 90

// EMG Envelope baseline value
// Minimum value without flexing hand
#define EMG_ENVELOPE_BASELINE 4

// EMG Envelope divider
// Minimum value increase required to turn on a single LED
#define EMG_ENVELOPE_DIVIDER 4


Servo servo;
// Servo toggle flag
bool flag = 0;
// Last gesture timestamp
unsigned long lastGestureTime = 0;
// Delay between two actions
unsigned long gestureDelay = 240;

int circular_buffer[BUFFER_SIZE];
int data_index, sum;

// Muscle BioAmp Shield v0.3 LED pin numbers in-order
int led_bar[] = {8, 9, 10, 11, 12, 13};
int total_leds = sizeof(led_bar) / sizeof(led_bar[0]);

void setup() {
  // Serial connection begin
  Serial.begin(BAUD_RATE);
  // Initialize all the led_bar
    for (int i = 0; i < total_leds; i++) {
      pinMode(led_bar[i], OUTPUT);
    }
   servo.attach(SERVO_PIN);
   servo.write(0);
}

void loop() {
  // Calculate elapsed time
  static unsigned long past = 0;
  unsigned long present = micros();
  unsigned long interval = present - past;
  past = present;

  // Run timer
  static long timer = 0;
  timer -= interval;

  // Sample and get envelope
  if(timer < 0) {
    timer += 1000000 / SAMPLE_RATE;
   
    // RAW EMG Values
    int sensor_value = analogRead(INPUT_PIN);
    
    // Filtered EMG
    int signal = EMGFilter(sensor_value);
    
    // EMG envelopee
    int envelope = getEnvelope(abs(signal));

    // Update LED bar graph
    for(int i = 0; i<=total_leds; i++){
      if(i>(envelope/EMG_ENVELOPE_DIVIDER - EMG_ENVELOPE_BASELINE)){
          digitalWrite(led_bar[i], LOW);
      } else {
          digitalWrite(led_bar[i], HIGH);
      }
    }
     
    if(envelope > EMG_THRESHOLD) {
      if((millis() - lastGestureTime) > gestureDelay){
      if(flag == 1){
        servo.write(SERVO_OPEN);
        flag = 0;
        lastGestureTime = millis();
      }
      else {
        servo.write(SERVO_CLOSE);
        flag = 1;
        lastGestureTime = millis();
      }
      }
    } 
    
    // EMG Raw signal
    Serial.print(signal);
    // Data seprator
    Serial.print(",");
    // EMG envelopeee
    Serial.println(envelope);

  }
}

// envelope detection algorithm
int getEnvelope(int abs_emg){
  sum -= circular_buffer[data_index];
  sum += abs_emg;
  circular_buffer[data_index] = abs_emg;
  data_index = (data_index + 1) % BUFFER_SIZE;
  return (sum/BUFFER_SIZE) * 2;
}
float EMGFilter(float input)
{
  float output = input;
  {
    static float z1, z2; // filter section state
    float x = output - 0.05159732*z1 - 0.36347401*z2;
    output = 0.01856301*x + 0.03712602*z1 + 0.01856301*z2;
    z2 = z1;
    z1 = x;
  }
  {
    static float z1, z2; // filter section state
    float x = output - -0.53945795*z1 - 0.39764934*z2;
    output = 1.00000000*x + -2.00000000*z1 + 1.00000000*z2;
    z2 = z1;
    z1 = x;
  }
  {
    static float z1, z2; // filter section state
    float x = output - 0.47319594*z1 - 0.70744137*z2;
    output = 1.00000000*x + 2.00000000*z1 + 1.00000000*z2;
    z2 = z1;
    z1 = x;
  }
  {
    static float z1, z2; // filter section state
    float x = output - -1.00211112*z1 - 0.74520226*z2;
    output = 1.00000000*x + -2.00000000*z1 + 1.00000000*z2;
    z2 = z1;
    z1 = x;
  }
  return output;
}

โš ๏ธ Important Notice

Keep laptop unplugged and remove the charger .

๐Ÿฆพ Step 9 โ€” Control the Servo Claw

Flex your muscles โ†’ envelope rises โ†’ servo toggles. Stronger flex = higher EMG = faster servo movement.

Project 2: MindScope ๐Ÿ”ฎ โ†’ Detecting Emotions Using EEG

In this project, you will record EEG signals using the BioAmp EXG Pill and an arduino Dev Board, extract brainwave bandpowers using FFT, and classify emotional states like Focused, Calm, Drowsy, Daydreaming, Hyper-aware. The output appears live in the Serial Monitor.

๐Ÿงฐ Hardware Required

๐Ÿ’ป Software Required

๐Ÿ”Œ Circuit Diagram

Circuit diagram showing EEG setup Circuit diagram showing EEG setup

๐Ÿงช Step 1: Upload the EEG Emotion Detection Program

๐Ÿ“ Electrode Placement

  • Place the two EEG electrodes on the Brain BioAmp Band.
  • IN+ on the left side, INโˆ’ on the right side.
  • Ensure metal points touch the skin firmly.
  • Place the band properly on the forehead.
  • Keep the band tight enough for stable contact.
  • Avoid hair covering the electrodes.
  • Attach the REF electrode behind the earlobe.
  • Place it on the hard bony area for stability.
  • This improves noise reduction and signal clarity.

๐Ÿง  Step 1 Code: EEG Emotion Detection

Create a new sketch in Arduino IDE and paste the following code:


#include <arduinoFFT.h>

// ================= EEG + FFT Config =====================
#define SAMPLE_RATE 512
#define FFT_SIZE 256
#define BAUD_RATE 115200
#define INPUT_PIN A2
#define LED_PIN 2

// ================= Frequency Bands ======================
#define DELTA_LOW 0.5
#define DELTA_HIGH 4.0
#define THETA_LOW 4.0
#define THETA_HIGH 8.0
#define ALPHA_LOW 8.0
#define ALPHA_HIGH 13.0
#define BETA_LOW 13.0
#define BETA_HIGH 30.0
#define GAMMA_LOW 30.0
#define GAMMA_HIGH 45.0

// Less smoothing โ†’ responds faster
#define SMOOTHING_FACTOR 0.35
const float EPSILON = 1e-6f;

// ================= Structs ==============================
typedef struct {
  float delta, theta, alpha, beta, gamma, total;
} BandpowerResults;

typedef struct {
  float delta = 0, theta = 0, alpha = 0, beta = 0, gamma = 0, total = 0;
} SmoothedBandpower;

SmoothedBandpower smoothedPowers;

// ================= FFT Buffers ==========================
double vReal[FFT_SIZE];
double vImag[FFT_SIZE];
ArduinoFFT<double> FFT(vReal, vImag, FFT_SIZE, SAMPLE_RATE);

// ================= Timing ================================
unsigned long lastEmotionTime = 0;
unsigned long sampling_period_us;

// ================= Functions =============================
void smoothBandpower(BandpowerResults *raw, SmoothedBandpower *s) {
  s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
  s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
  s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
  s->beta  = SMOOTHING_FACTOR * raw->beta  + (1 - SMOOTHING_FACTOR) * s->beta;
  s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
  s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
}

BandpowerResults calculateBandpower(double *powerSpectrum, double binWidth) {
  BandpowerResults results = {0};

  for (int i = 1; i < FFT_SIZE / 2; i++) {
    float freq = i * binWidth;
    float power = powerSpectrum[i];
    results.total += power;

    if (freq >= DELTA_LOW && freq < DELTA_HIGH) results.delta += power;
    else if (freq >= THETA_LOW && freq < THETA_HIGH) results.theta += power;
    else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH) results.alpha += power;
    else if (freq >= BETA_LOW  && freq < BETA_HIGH)  results.beta  += power;
    else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH) results.gamma += power;
  }
  return results;
}

// ================= Emotion Classification ==================
void printEmotion() {
  float d = (smoothedPowers.delta / (smoothedPowers.total + EPSILON)) * 100;
  float t = (smoothedPowers.theta / (smoothedPowers.total + EPSILON)) * 100;
  float a = (smoothedPowers.alpha / (smoothedPowers.total + EPSILON)) * 100;
  float b = (smoothedPowers.beta  / (smoothedPowers.total + EPSILON)) * 100;
  float g = (smoothedPowers.gamma / (smoothedPowers.total + EPSILON)) * 100;

  String emotion = "Neutral";

  if (smoothedPowers.total < 0.001) {
    emotion = "No Signal";
  }
  else if (d > 60 && b < 10 && g < 10) {
    emotion = "Drowsy";
  }
  else if (t > a && t > b && t > 15) {
    emotion = "Daydreaming";
  }
  else if (b > 25 && b > a) {
    emotion = "Focused";
  }
  else if (a > 25 && a > b) {
    emotion = "Calm";
  }
  else if (g > 15) {
    emotion = "Hyper-aware";
  }

  Serial.print("Emotion: "); Serial.println(emotion);
  Serial.print("Delta: "); Serial.print(d, 1);
  Serial.print(" | Theta: "); Serial.print(t, 1);
  Serial.print(" | Alpha: "); Serial.print(a, 1);
  Serial.print(" | Beta: ");  Serial.print(b, 1);
  Serial.print(" | Gamma: "); Serial.println(g, 1);
}

// ================= Setup ==================
void setup() {
  Serial.begin(115200);
  pinMode(INPUT_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);

  sampling_period_us = round(1000000 * (1.0 / SAMPLE_RATE));

  Serial.println("EEG Emotion Detection (Improved) Started...");
}

// ================= Loop ==================
void loop() {
  unsigned long microseconds;

  // Sampling
  for (int i = 0; i < FFT_SIZE; i++) {
    microseconds = micros();
    vReal[i] = analogRead(INPUT_PIN);
    vImag[i] = 0;
    while (micros() - microseconds < sampling_period_us);
  }

  // FFT Processing
  FFT.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.compute(FFT_FORWARD);
  FFT.complexToMagnitude();

  double binWidth = (1.0 * SAMPLE_RATE) / FFT_SIZE;

  BandpowerResults raw = calculateBandpower(vReal, binWidth);
  smoothBandpower(&raw, &smoothedPowers);

  double b_pct = (smoothedPowers.beta / (smoothedPowers.total + EPSILON)) * 100;
  digitalWrite(LED_PIN, (b_pct > 20.0 ? HIGH : LOW));

  if (millis() - lastEmotionTime > 5000) {
    printEmotion();
    lastEmotionTime = millis();
  }
}

๐Ÿ“Š Step 2: View Your Emotions Live

Open Tools โ†’ Serial Monitor in Arduino IDE and set the baud rate to 115200.

You will see outputs like:

Emotion: Calm
Delta: 12.3 | Theta: 18.4 | Alpha: 32.1 | Beta: 10.5 | Gamma: 2.1

Emotion: Focused
Delta: 10.2 | Theta: 8.4 | Alpha: 14.1 | Beta: 29.5 | Gamma: 3.0

Project 3: Blink-a-saur ๐Ÿฆ– โ†’ Control Dino Game Using EOG Signals

In this project, you will create a fun game controller that lets you play the Chrome Dino Game using your eye blinks. Using EOG (Electrooculography) signals from your eyes, the system detects blinks and triggers a keyboard SPACE press โ€” making the Dino jump!

๐Ÿ‘๏ธ What is Electrooculography (EOG)?

Electrooculography measures the electrical potential produced by eye movement. The cornea is positively charged, the retina is negatively charged โ€” creating the electrooculogram. When you blink, this voltage changes, and we detect it using the BioAmp EXG Pill.

๐Ÿงฐ Supplies Needed

HARDWARE:

SOFTWARE:

๐Ÿ”ฌ About DIY Neuroscience Kit Pro

A complete portable neuroscience lab for EEG, EMG, ECG and EOG recordings โ€” designed for HCI and BCI projects.

๐Ÿงช Step 1: Stack Muscle BioAmp Shield on Arduino Uno

Carefully align the pins and stack the Muscle BioAmp Shield on top of Arduino Uno.

๐Ÿงช Step 2: Connecting BioAmp EXG Pill

Circuit diagram showing EEG setup Circuit diagram showing EEG setup

Connect the BioAmp EXG Pill to the A2 port using the STEMMA cable.

๐Ÿ”Œ Step 3: Connecting Electrode Cable

Plug the BioAmp Cable into the EXG Pill exactly as shown.

๐Ÿงด Step 4: Skin Preparation

Apply NuPrep Gel โ†’ rub gently โ†’ clean with wet wipe. This reduces skin impedance and improves EOG signal quality.

About NuPrep: Removes dry skin and improves conductivity.

๐Ÿ“ Step 5: Electrode Placements

Electrode placement
  • IN+ โ†’ Forehead
  • INโˆ’ โ†’ Below the eye
  • REF โ†’ Behind the earlobe

๐Ÿง  Step 6 Code: EOG Dino Game Controller




#include <Arduino.h>
#include <Keyboard.h>  // HID keyboard library for Arduino R4
#include <math.h>

// #define DEBUG  // Uncomment this line to enable debugging

// ----------------- USER CONFIGURATION -----------------
#define SAMPLE_RATE       512          // samples per second
#define BAUD_RATE         115200
#define INPUT_PIN         A2
#define LED_PIN           LED_BUILTIN

// Envelope Configuration for EOG detection
#define ENVELOPE_WINDOW_MS 100  // Smoothing window in milliseconds
#define ENVELOPE_WINDOW_SIZE ((ENVELOPE_WINDOW_MS * SAMPLE_RATE) / 1000)

// Blink Detection Thresholds - adjust these based on your setup
const int BlinkLowerThreshold = 30; 
const int BlinkUpperThreshold = 50;

// Circular buffer for timing-based sampling
#define BUFFER_SIZE 64
float eogCircBuffer[BUFFER_SIZE];
int writeIndex = 0;
int readIndex = 0;
int samplesAvailable = 0;

// Single Blink Detection Configuration
const unsigned long BLINK_DEBOUNCE_MS = 300;   // minimum time between blinks to prevent double-triggering
unsigned long lastBlinkTime = 0;               // time of most recent blink
float currentEOGEnvelope = 0;

// HID Command Cooldown to prevent rapid-fire commands
const unsigned long HID_COOLDOWN_MS = 250;  // 250ms between space commands (allows ~4 jumps per second)
unsigned long lastHIDCommandTime = 0;

// EOG Envelope Processing Variables
float eogEnvelopeBuffer[ENVELOPE_WINDOW_SIZE] = {0};
int eogEnvelopeIndex = 0;
float eogEnvelopeSum = 0;

// Game Statistics
unsigned long totalBlinks = 0;
unsigned long gameStartTime = 0;

// EOG Statistics for debug display
#define SEGMENT_SEC 1
#define SAMPLES_PER_SEGMENT (SAMPLE_RATE * SEGMENT_SEC)
float eogBuffer[SAMPLES_PER_SEGMENT] = {0};
uint16_t segmentIndex = 0;
unsigned long lastSegmentTimeMs = 0;
float eogAvg = 0, eogMin = 0, eogMax = 0;
bool segmentStatsReady = false;

// --- Filter Functions ---

// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
// Sampling rate: 512.0 Hz, frequency: [48.0, 52.0] Hz.
// Filter is order 2, implemented as second-order sections (biquads).
// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
float Notch(float input)
{
  float output = input;
  {
    static float z1, z2; // filter section state
    float x = output - -1.58696045*z1 - 0.96505858*z2;
    output = 0.96588529*x + -1.57986211*z1 + 0.96588529*z2;
    z2 = z1;
    z1 = x;
  }
  {
    static float z1, z2; // filter section state
    float x = output - -1.62761184*z1 - 0.96671306*z2;
    output = 1.00000000*x + -1.63566226*z1 + 1.00000000*z2;
    z2 = z1;
    z1 = x;
  }
  return output;
}

// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
// Sampling rate: 512.0 Hz, frequency: 5.0 Hz.
// Filter is order 2, implemented as second-order sections (biquads).
// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
float EOGFilter(float input)
{
  float output = input;
  {
    static float z1, z2; // filter section state
    float x = output - -1.91327599*z1 - 0.91688335*z2;
    output = 0.95753983*x + -1.91507967*z1 + 0.95753983*z2;
    z2 = z1;
    z1 = x;
  }
  return output;
}

float updateEOGEnvelope(float sample) 
{
  float absSample = fabs(sample); 

  // Update circular buffer and running sum
  eogEnvelopeSum -= eogEnvelopeBuffer[eogEnvelopeIndex];
  eogEnvelopeSum += absSample;
  eogEnvelopeBuffer[eogEnvelopeIndex] = absSample;
  eogEnvelopeIndex = (eogEnvelopeIndex + 1) % ENVELOPE_WINDOW_SIZE;

  return eogEnvelopeSum / ENVELOPE_WINDOW_SIZE;  // Return moving average
}

// HID Keyboard Functions
void sendSpaceBar() {
  unsigned long nowMs = millis();
  if ((nowMs - lastHIDCommandTime) >= HID_COOLDOWN_MS) {
    Keyboard.press(' ');  // Press space bar
    delay(30);            // Hold key for 30ms (shorter for gaming responsiveness)
    Keyboard.release(' ');
    lastHIDCommandTime = nowMs;
    totalBlinks++;
    
    Serial.print("JUMP! Blink #");
    Serial.println(totalBlinks);
    
    // LED feedback - quick single flash
    digitalWrite(LED_PIN, HIGH);
    delay(50);
    digitalWrite(LED_PIN, LOW);
  }
}

void setup() {
  Serial.begin(BAUD_RATE);
  delay(100);
  
  pinMode(INPUT_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  
  // Initialize HID Keyboard
  Keyboard.begin();
  
  // LED startup sequence - game ready indicator
  for(int i = 0; i < 3; i++) {
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(200);
  }
  
  gameStartTime = millis();
  lastSegmentTimeMs = millis();  // Initialize the segment timer
  
  Serial.println("=================================");
  Serial.println("Arduino R4 EOG Dino Game Controller");
  Serial.println("=================================");
  Serial.println("Single Blink = Space Bar (Jump)");
  Serial.println("Perfect for Chrome Dino Game!");
  Serial.println("");
  Serial.println("Instructions:");
  Serial.println("1. Open Chrome and go to chrome://dino/");
  Serial.println("2. Or disconnect internet and try to browse");
  Serial.println("3. Press spacebar once to start game");
  Serial.println("4. Then use blinks to jump!");
  Serial.println("");
  Serial.println("Starting EOG monitoring at 512 Hz...");
  Serial.println("Ready to play! ๐Ÿฆ•");
}

void loop() {
    static unsigned long lastMicros = 0;
    static long timer = 0;
    
    digitalWrite(LED_PIN, LOW);  // Default LED state
    
    // Timing-based sampling for 512 Hz
    unsigned long currentMicros = micros();
    long interval = (long)(currentMicros - lastMicros);
    lastMicros = currentMicros;
    
    timer -= interval;
    const long period = 1000000L / SAMPLE_RATE;
    while (timer < 0) {
        timer += period;
        int raw = analogRead(INPUT_PIN);
        float filtered = Notch(raw);
        float eog = EOGFilter(filtered);
        eogCircBuffer[writeIndex] = eog;
        writeIndex = (writeIndex + 1) % BUFFER_SIZE;
        if (samplesAvailable < BUFFER_SIZE) {
            samplesAvailable++;
        }
    }
    
    // Process all available samples from circular buffer
    while (samplesAvailable > 0) {
        float eog = eogCircBuffer[readIndex];
        readIndex = (readIndex + 1) % BUFFER_SIZE;
        samplesAvailable--;
        
        // Process the sample (envelope calculation)
        currentEOGEnvelope = updateEOGEnvelope(eog);
        
        // Add to segment buffer for statistics
        if(segmentIndex < SAMPLES_PER_SEGMENT) {
            eogBuffer[segmentIndex] = currentEOGEnvelope;
            segmentIndex++;
        }
    }
    
    // Get current time for blink detection logic
    unsigned long nowMs = millis();
    
    // ===== SEGMENT STATISTICS PROCESSING =====
    if ((nowMs - lastSegmentTimeMs) >= (1000UL * SEGMENT_SEC)) {
        if(segmentIndex > 0) {
            // Compute min/max/avg for the completed segment
            eogMin = eogBuffer[0]; 
            eogMax = eogBuffer[0];  
            float eogSum = 0;
            
            for (uint16_t i = 0; i < segmentIndex; i++) {
                float eogVal = eogBuffer[i];
                
                // EOG statistics
                if (eogVal < eogMin) eogMin = eogVal;
                if (eogVal > eogMax) eogMax = eogVal;
                eogSum += eogVal;
            }
            
            eogAvg = eogSum / segmentIndex;
            segmentStatsReady = true;
        }
        
        lastSegmentTimeMs = nowMs;
        segmentIndex = 0;
    }
    
    // ===== SINGLE BLINK DETECTION AND SPACE BAR CONTROL =====
    if (currentEOGEnvelope > BlinkLowerThreshold && 
        currentEOGEnvelope < BlinkUpperThreshold && 
        (nowMs - lastBlinkTime) >= BLINK_DEBOUNCE_MS) {
        
        lastBlinkTime = nowMs;
        
        #ifdef DEBUG
        Serial.println("Blink detected!");
        #endif
        
        // Send space bar immediately for single blink
        sendSpaceBar();
    }
    
    // ===== PERIODIC STATUS UPDATES =====
    static unsigned long lastStatusUpdate = 0;
    if ((nowMs - lastStatusUpdate) >= 30000) {  // Every 30 seconds
        unsigned long gameTimeSeconds = (nowMs - gameStartTime) / 1000;
        float blinksPerMinute = (totalBlinks * 60.0) / (gameTimeSeconds + 1);
        
        Serial.println("");
        Serial.println("=== Game Stats ===");
        Serial.print("Game Time: ");
        Serial.print(gameTimeSeconds);
        Serial.println(" seconds");
        Serial.print("Total Jumps: ");
        Serial.println(totalBlinks);
        Serial.print("Jump Rate: ");
        Serial.print(blinksPerMinute, 1);
        Serial.println(" per minute");
        Serial.print("Current EOG Level: ");
        Serial.println(currentEOGEnvelope);
        Serial.println("==================");
        Serial.println("");
        
        lastStatusUpdate = nowMs;
    }
    
    // ===== REAL-TIME EOG MONITORING (DEBUG) =====
    #ifdef DEBUG
    static unsigned long lastDebugPrint = 0;
    if ((nowMs - lastDebugPrint) >= 1000) {  // Every 1 second
        if (segmentStatsReady) {
            Serial.print("EOG: (Avg: "); Serial.print(eogAvg);
            Serial.print(", Min: "); Serial.print(eogMin);
            Serial.print(", Max: "); Serial.print(eogMax); Serial.println(")");
        } else {
            Serial.println("EOG: "); Serial.print(currentEOGEnvelope);
        }
        lastDebugPrint = nowMs;
    }
    #endif
}
โš ๏ธ Important Notice

Keep laptop unplugged and remove the charger.

๐Ÿ“Š Step 8: Testing the Connections

Open Serial Plotter โ†’ Press SW1 โ†’ Blink โ†’ Observe clear spike signals.

๐Ÿฆ– Step 10: Play the Game!

Control the Chrome Dino Game using only your eye blinks ๐ŸŽ‰

  • Open Google Chrome
  • visit
  • chrome://dino/
  • Press Space once to start
  • Blink โ†’ Dino Jumps!

Stronger blinks = higher accuracy ๐Ÿ˜Ž๐Ÿ‘๏ธ๐Ÿฆ–