Skip to content

Programming IO Devices

1. Capacitive Touch Sensor

During the Input week, Prof. Neil taught us about the step response and I really like the idea of capacitive touch sensor which can be designed into actual sensors like buttons, sliders, wheels, and touch screens. So I decided to explore how I can create and program capacitive touch sensors for my Nap Pod.

What is Capacitive Touch sensor

To understand the basics of capacitive touch sensor, I went to through some papers shared by Prof. Neil on fabacademy page.

Capacitive touch sensors detect human interaction by measuring changes in capacitance caused by a user’s finger proximity, offering a reliable, low-cost alternative to mechanical switches. It basically detect touch or proximity of user’s hand using minimal energy. With no moving parts, it ensures durability, resistance to environmental changes, and longevity.

Working Principle Capacitive touch sensors operate by detecting changes in capacitance caused by the proximity or touch of a conductive object, such as a human finger or hand. The sensor consists of a touch pad, typically a conductive trace on a printed circuit board (PCB) or a coated surface (e.g., glass or plastic), which forms a capacitor with respect to ground or another electrode. When a finger approaches, it alters the capacitance due to its electrical properties, acting as a “virtual ground” with finite impedance to the sensor’s ground.

Types of Capacitive Touch Configurations

Capacitive touch sensors can be categorized into two types based on the capacitance measurement:

  • Self-Capacitance: Measures the capacitance between the touch pad and ground.
  • Mutual-Capacitance: Measures the capacitance between the touch pad and another electrode (not ground).

In the paper I refer, the EFM32 is comes a lot as the paper is written how we can design a capacitive touch sensor around the microcontroller. The EFM32 is a family of 32-bit microcontrollers (MCUs) developed by Silicon Labs, designed for ultra-low-power applications. These MCUs are based on the ARM Cortex-M architecture and are optimized for energy efficiency, making them ideal for battery-powered devices such as wearables, IoT devices, and human-machine interfaces. Hence in the pa

Key Fundamentals - Capacitance Change Detection: The quality of a touch measurement depends on the magnitude of the capacitance change when a finger touches the pad. A larger change in capacitance results in a more reliable detection. The goal of hardware design is to maximize the capacitance change upon touch and minimize noise sources to ensure accurate detection.

  • Signal-to-Noise Ratio (SNR): SNR quantifies the performance of a capacitive touch sensor by comparing the “signal” (difference in count values between touched and untouched states) to the “noise” (peak-to-peak variation in count values under static conditions). A high SNR (>5) is critical for reliable touch detection, as the capacitance change is typically small (0.3%–50%). Hardware and software design must work together to optimize SNR, with hardware minimizing noise sources and software handling environmental variations.

  • Hardware Design Considerations: The touch pad is typically implemented as a PCB trace, often covered with a non-conductive overlay (plastic or glass) to protect the sensor and enhance aesthetics. The design aims to maximize the capacitance change caused by a finger while minimizing noise from external sources, such as radiated or conducted noise from other components, traces, or power supplies. Proper PCB layout, grounding, and shielding are critical to reducing noise and improving SNR.

  • External Disturbances and Mitigation: Capacitive touch sensors are sensitive to environmental and electrical disturbances that can affect capacitance measurements. These include: Radiated/Conducted Noise (Hardware): Mitigated by improving PCB layout, shielding, and filtering. Supply Voltage Noise (Software/Hardware): Stabilized through power supply design and software compensation. Temperature Changes (Software): Handled by software algorithms that adjust for baseline drift. Humidity Changes (Software): Compensated similarly to temperature changes. Oscillator Jitter (Hardware): Reduced by ensuring a stable low-frequency oscillator.

  • Low-Power Operation with EFM32: The EFM32’s LESENSE (Low Energy Sensor Interface) enables ultra-low-power operation by continuously scanning touch pads without waking the CPU unless a touch is detected. This makes the EFM32 ideal for battery-powered devices like wearables, IoT devices, and other human-machine interfaces requiring minimal energy consumption.

  • Evaluation and Testing: The SNR must be validated on the final PCB with the intended overlay (material and thickness) to ensure reliable performance. The capacitive touch software application note (AN0028) provides guidance on evaluating SNR and optimizing software for touch detection.

Testing Capacitive Touch Sensor Following is the program written for Mutual capacitance touch sensor written by Prof. Neil.

//
// hello.txtx.RP2040.op-amp.ino
//    RP2040 XIAO transmit-receive step-response op-amp hello-world
//    overclock at 250 MHz
//
// Neil Gershenfeld 7/28/24
//
// This work may be reproduced, modified, distributed,
// performed, and displayed for any purpose, but must
// acknowledge this project. Copyright is retained and
// must be preserved. The work is provided as is; no
// warranty is provided, and users accept all liability.
//

#define digitalWriteFast(pin,val) (val ? sio_hw->gpio_set = (1 << pin) : sio_hw->gpio_clr = (1 << pin))
#define digitalReadFast(pin) ((1 << pin) & sio_hw->gpio_in)

#define Rx 26 // receive pin (A0)
#define Tx 1 // transmit pin (D7)
#define settle 20 // settle time
#define samples 5000 // number of samples to accumulate

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

void loop() {
   }

void setup1() {
   pinMode(Tx,OUTPUT);
   analogReadResolution(12);
   }

void loop1() {
   int32_t up,down;
   up = down = 0;
   for (int i = 0; i < samples; ++i) {
      digitalWriteFast(Tx,HIGH); // charge up
      up += analogRead(Rx); // read
      delayMicroseconds(settle); //settle
      digitalWriteFast(Tx,LOW); // charge down
      down += analogRead(Rx); // read
      delayMicroseconds(settle); // settle
      }
   Serial.println((down-up)/samples); // send difference (op-amp inverts)
   }

I then added an led which will will respond to the capacitive touch and I changed threshold that works best for me.

  • Then I try to edit the code for three capacitive touch sensors.

Here is Program for three capacitive touch sensors with some changes made on Prof. Neil Program.

#define digitalWriteFast(pin,val) (val ? sio_hw->gpio_set = (1 << pin) : sio_hw->gpio_clr = (1 << pin))
#define digitalReadFast(pin) ((1 << pin) & sio_hw->gpio_in)

#define Tx 29 // transmit
#define Rx1 26 // receive 1 
#define Rx2 27 // receive 2 
#define Rx3 28 // receive 2 
#define settle 20 // settle time
#define samples 5000 // number of samples to accumulate


# define led1 6
# define led2 7
# define led3 0

int data1;
int data2;
int data3;

void setup() {
  Serial.begin(115200);
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);
}

void loop() {
  if(data1 > -50 && data1 < -5){
    digitalWrite(led1, HIGH);
  }
  if(data2 > -50 && data2 < -5){
    digitalWrite(led2, HIGH);
  }
  if(data3 > -120 && data3 < -50){
    digitalWrite(led3, HIGH);
  }else {
  digitalWriteFast(led1, LOW);
  digitalWriteFast(led2, LOW);
  digitalWriteFast(led3, LOW);
  }

}

void setup1() {
  analogReadResolution(12);
  pinMode(Tx,OUTPUT);

}

void loop1() {
  int32_t up1,down1,up2,down2, up3,down3;
  up1 = down1 = up2 = down2 = up3 = down3;
  for (int i = 0; i < samples; ++i) {
    digitalWriteFast(Tx,HIGH); // charge up
      up1 += analogRead(Rx1); // read
      delayMicroseconds(settle); //settle
      digitalWriteFast(Tx,LOW); // charge down
      down1 += analogRead(Rx1); // read
      delayMicroseconds(settle); // settle
      digitalWriteFast(Tx,HIGH); // charge up
      up2 += analogRead(Rx2); // read
      delayMicroseconds(settle); //settle
      digitalWriteFast(Tx,LOW); // charge down
      down2 += analogRead(Rx2); // read
      delayMicroseconds(settle); // settle
      digitalWriteFast(Tx,HIGH); // charge up
      up3 += analogRead(Rx3); // read
      delayMicroseconds(settle); //settle
      digitalWriteFast(Tx,LOW); // charge down
      down3 += analogRead(Rx3); // read
      delayMicroseconds(settle); // settle

  }
  data1 = (down1-up1)/samples;
  data2 = (down2-up2)/samples;
  data3 = (down3-up3)/samples;

  Serial.print("S1 = ");
  Serial.println(data1); // send difference (op-amp inverts)

  Serial.print("S2 = ");
  Serial.println(data2); // send difference (op-amp inverts)

  Serial.print("S3 = ");
  Serial.println(data3); // send difference (op-amp inverts)
}
  • Here is video of me trying to test three capacitive touch sensor.
  1. Blynk and Programming DFPlayer Mini
  • Here is the circuit connection.
/* Fill-in information from Blynk Device Info here */
#define BLYNK_TEMPLATE_ID "TMPL3RChFD034"
#define BLYNK_TEMPLATE_NAME "NAP POD"
#define BLYNK_AUTH_TOKEN "wBKDIwuXVMXQLEJSb_QJpJKnXZ_sDJcK"
#define BLYNK_PRINT Serial
#include <DFRobotDFPlayerMini.h>
#include <SoftwareSerial.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>

// Initialize software serial on pins D0 (RX) and D1 (TX)
SoftwareSerial mySerial(D4, D5); 

// Create DFPlayer object
DFRobotDFPlayerMini myDFPlayer;

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "esp32";
char pass[] = "esp32369369";

BlynkTimer timer;
bool NapPodActivated = false;

// Volume control from Blynk slider on V1
BLYNK_WRITE(V1) {
  int vol = constrain(param.asInt(), 0, 30);
  myDFPlayer.volume(vol);
  Serial.print(F("Volume set to: "));
  Serial.println(vol);
}
bool status = 1;
// This function is called every time the Virtual Pin 0 state changes

BLYNK_WRITE(V0) {  
  if(NapPodActivated){
    // Called when the datastream virtual pin V0 is updated by Blynk.Console, Blynk.App, or HTTP API. 
    String value = param.asStr();
    if (value == "play") {
      if (status){
        Serial.println("'play' button pressed");
        myDFPlayer.play(3);  //Play the first mp3
        delay(1000);
        status = !status; // Toggle pause state
      }else if(!status) {
        myDFPlayer.start();  //start the mp3 from the pause
        delay(1000);
      }

    } else if (value == "stop") {
      Serial.println("'stop' button pressed");
      myDFPlayer.pause();  //pause the mp3

    } else if (value == "prev") {
      Serial.println("'prev' button pressed");
      myDFPlayer.previous();  //Play previous mp3
      delay(1000);

    } else if (value == "next") {
      Serial.println("'next' button pressed");
      myDFPlayer.next();  //Play next mp3
      delay(1000);
    } else {
    Serial.print("V0 = '");
    Serial.print(value);
    Serial.println("'");
    }
  }
} 

void setup(){
  // Debug console
  Serial.begin(115200);
  mySerial.begin(9600);
  Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
  // Initialize DFPlayer
  if (!myDFPlayer.begin(mySerial)) {
    Serial.println(F("DFPlayer initialization failed!"));
    Serial.println(F("1. Check RX/TX connections (must be crossed)"));
    Serial.println(F("2. Insert SD card with MP3 files"));
    while(true);  // Halt if initialization fails
  }
}

BLYNK_WRITE(V2){
  int pinValueV2 = param.asInt(); // Get the value from the app
  if (pinValueV2 && !NapPodActivated) {
    myDFPlayer.play(1);  //Play the first mp3
    Serial.println(F("Activating nappod"));
    delay(1000);
    NapPodActivated = true;

  }else if(!pinValueV2 && NapPodActivated ){
    myDFPlayer.play(2);  //Play the first mp3
    Serial.println(F("Deactivating nappod"));
    delay(1000);
    NapPodActivated = false;

  }
}

//-----------------------------------------------------------------------
// Music Therapy(nature and meditation music) (0003 - 0005)
BLYNK_WRITE(V3){
  int pinValueV3 = param.asInt(); // Get the value from the app
  if (pinValueV3 && NapPodActivated) {
    Serial.println(F("Button V3 pressed"));
    myDFPlayer.play(3);  //Play the first mp3
    delay(1000);
  }
}

// Soothing Bhutanese Music (000 - 0008)
BLYNK_WRITE(V4){
  int pinValueV4 = param.asInt(); // Get the value from the app
  if (pinValueV4 && NapPodActivated) {
    Serial.println(F("Button V4 pressed"));
    myDFPlayer.play(6);  //Play the first mp3
    delay(1000);
  }
}

// Soothing Western Music (0009 - 00011)
BLYNK_WRITE(V5){
  int pinValueV5 = param.asInt(); // Get the value from the app
  if (pinValueV5 && NapPodActivated) {
    Serial.println(F("Button V5 pressed"));
    myDFPlayer.play(9);  //Play the first mp3
    delay(1000);
  }
}

// Soothing Indian Music (00012 - 00014)
BLYNK_WRITE(V6){
  int pinValueV6 = param.asInt(); // Get the value from the app
  if (pinValueV6 && NapPodActivated) {
    Serial.println(F("Button V6 pressed"));
    myDFPlayer.play(12);  //Play the first mp3
    delay(1000);
  }
}

//-----------------------------------------------------------------------------

void loop(){
  Blynk.run();
  timer.run();
}
  1. Creating Interface using Processing I will explore some integration option so that I can use the Interface created using Processing in my Final Project.

  • This is the circuit connection I used above.

Processing Program for Interface

// Nap Pod Controller GUI

import processing.serial.*;
Serial port;

class MusicPlayer {
  float x, y;
  float width, height;
  boolean isPlaying;
  int currentTrack = 1;
  int totalTracks = 14;
  float volume = 1;
  String currentPlaylist = "None";

  MusicPlayer(float x, float y, float w, float h) {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    this.isPlaying = false;
  }

  void display() {
    // Player background
    fill(30);
    rect(x, y, width, height, 10);

    // Display area
    fill(50);
    rect(x + 20, y + 20, width - 40, height - 100, 5);

    // Track info
    fill(200);
    textSize(16);
    textAlign(LEFT, CENTER);
    text("Track: " + currentTrack + "/" + totalTracks, x + 30, y + 40);
    text("Playlist: " + currentPlaylist, x + 30, y + 70);
    text("Status: " + (isPlaying ? "Playing" : "Stopped"), x + 30, y + 100);

    // Volume display
    fill(100, 150, 255);
    rect(x + 30, y + 130, (width - 60) * volume, 20, 5);
    fill(200);
    text("Volume: " + int(volume * 100) + "%", x + 30, y + 160);

    // Control buttons
    drawButton(x + width/2 - 80, y + height - 50, 40, 40, "|<", color(100)); // Previous
    drawButton(x + width/2 - 30, y + height - 50, 40, 40, isPlaying ? ">" : "||", color(100, 200, 100)); // Play/Pause
    drawButton(x + width/2 + 20, y + height - 50, 40, 40, ">|", color(100)); // Next
  }

  void drawButton(float bx, float by, float bw, float bh, String label, color btnColor) {
    fill(btnColor);
    rect(bx, by, bw, bh, 5);
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(16);
    text(label, bx + bw/2, by + bh/2);
  }

  boolean isPlayButtonClicked(float mx, float my) {
    return (mx > x + width/2 - 30 && mx < x + width/2 + 10 && 
            my > y + height - 50 && my < y + height - 10);
  }

  boolean isPrevButtonClicked(float mx, float my) {
    return (mx > x + width/2 - 80 && mx < x + width/2 - 40 && 
            my > y + height - 50 && my < y + height - 10);
  }

  boolean isNextButtonClicked(float mx, float my) {
    return (mx > x + width/2 + 20 && mx < x + width/2 + 60 && 
            my > y + height - 50 && my < y + height - 10);
  }

  boolean isVolumeClicked(float mx, float my) {
    return (mx > x + 30 && mx < x + width - 30 && 
            my > y + 130 && my < y + 150);
  }

  void setVolume(float mx) {
    volume = constrain((mx - (x + 30)) / (width - 60), 0, 1);
  }
}

class PlaylistButton {
  float x, y;
  float width, height;
  String label;
  color btnColor;
  boolean active;

  PlaylistButton(float x, float y, float w, float h, String label, color btnColor) {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    this.label = label;
    this.btnColor = btnColor;
    this.active = false;
  }

  void display() {
    fill(active ? color(red(btnColor), green(btnColor), blue(btnColor), 200) : btnColor);
    rect(x, y, width, height, 5);
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(14);
    text(label, x + width/2, y + height/2);
  }

  boolean isClicked(float mx, float my) {
    return (mx > x && mx < x + width && my > y && my < y + height);
  }
}

MusicPlayer player;
PlaylistButton[] playlistButtons;
boolean NapPodActivated = false;
color activeColor = color(23, 241, 115);
color inactiveColor = color(100);

void setup() {
  size(800, 700);
  smooth();

  port = new Serial(this, "COM23", 9600);

  // Create music player
  player = new MusicPlayer(50, 50, width - 100, 300);

  // Create playlist buttons
  playlistButtons = new PlaylistButton[4];
  playlistButtons[0] = new PlaylistButton(50, 400, width - 100, 50, "Music Therapy (Nature Sounds)", color(123, 25, 116));
  playlistButtons[1] = new PlaylistButton(50, 470, width - 100, 50, "Bhutanese Music", color(206, 69, 100));
  playlistButtons[2] = new PlaylistButton(50, 540, width - 100, 50, "Western Music", color(143, 14, 190));
  playlistButtons[3] = new PlaylistButton(50, 610, width - 100, 50, "Indian Music", color(226, 147, 80));

  // Set text properties
  rectMode(CORNER);
  textAlign(CENTER, CENTER);
  textSize(12);
}

void draw() {
  background(20);

  // Draw title
  fill(255);
  textSize(24);
  text("Nap Pod Controller", width/2.8, 40);

  // Draw activation button
  fill(NapPodActivated ? activeColor : inactiveColor);
  rect(width - 150, 10, 120, 30, 5);
  fill(255);
  textSize(16);
  text(NapPodActivated ? "ACTIVATED" : "ACTIVATE", width - 95, 30);

  // Draw music player
  player.display();

  // Draw playlist buttons
  for (PlaylistButton btn : playlistButtons) {
    btn.display();
  }

  // Draw instructions
  fill(255);
  textSize(15);
  textAlign(CENTER);
  text("Click on volume bar to adjust volume", 400, 250);
}

void mousePressed() {
  // Check Nap Pod activation
  if (mouseX > width - 150 && mouseX < width - 30 && 
      mouseY > 10 && mouseY < 40) {
    NapPodActivated = !NapPodActivated;
    if (NapPodActivated) {
      println("Nap Pod Activated");
      port.write('A');
      player.isPlaying = true;
      player.currentTrack = 1;
      player.currentPlaylist = "Startup Sound";
    } else {
      println("Nap Pod Deactivated");
      port.write('D');
      player.isPlaying = false;
      player.currentPlaylist = "None";
      // Deactivate all playlist buttons
      for (PlaylistButton btn : playlistButtons) {
        btn.active = false;
      }
    }
    return;
  }

  // Only respond to music controls if Nap Pod is active
  if (!NapPodActivated) return;

  // Check music player controls
  if (player.isPlayButtonClicked(mouseX, mouseY)) {
    println(player.isPlaying ? "Play" : "Pause");
    if (player.isPlaying) {
      port.write('x');
      player.isPlaying = !player.isPlaying;
    } else {
      player.isPlaying = !player.isPlaying;
      port.write('y');
    }
  } else if (player.isPrevButtonClicked(mouseX, mouseY)) {
    player.currentTrack = max(1, player.currentTrack - 1);
    println("Previous Track");
    if (player.isPlaying){
      player.isPlaying = !player.isPlaying;
    }else{
      player.isPlaying = player.isPlaying;
    }
    port.write('z');

  } else if (player.isNextButtonClicked(mouseX, mouseY)) {
    player.currentTrack = min(player.totalTracks, player.currentTrack + 1);
    println("Next Track");
    if (player.isPlaying){
      player.isPlaying = !player.isPlaying;
    }else{
      player.isPlaying = player.isPlaying;
    }
    port.write('l');

  } else if (player.isVolumeClicked(mouseX, mouseY)) {
    player.setVolume(mouseX);
    int volumePercent = int(player.volume * 100);
    int scaledVolume = (int)map(volumePercent, 0, 100, 0, 9);
    port.write(scaledVolume);
    println("Volume set to " + (scaledVolume));


  }

  // Check playlist buttons
  for (int i = 0; i < playlistButtons.length; i++) {
    if (playlistButtons[i].isClicked(mouseX, mouseY)) {
      // Deactivate all other buttons
      for (PlaylistButton btn : playlistButtons) {
        btn.active = false;
      }
      // Activate this button
      playlistButtons[i].active = true;
      player.isPlaying = true;

      // Set playlist info
      switch(i) {
        case 0:

          player.currentPlaylist = "Music Therapy";
          player.currentTrack = 3;
          println("Playing Music Therapy playlist");
          if (player.isPlaying){
            player.isPlaying = !player.isPlaying;
          }else{
            player.isPlaying = player.isPlaying;
          }
          port.write('M');
          break;
        case 1: 
          player.currentPlaylist = "Bhutanese Music";
          player.currentTrack = 6;
          println("Playing Bhutanese Music playlist");
          if (player.isPlaying){
            player.isPlaying = !player.isPlaying;
          }else{
            player.isPlaying = player.isPlaying;
          }
          port.write('B');
          break;
        case 2: 
          player.currentPlaylist = "Western Music";
          player.currentTrack = 9;
          println("Playing Western Music playlist");
          if (player.isPlaying){
            player.isPlaying = !player.isPlaying;
          }else{
            player.isPlaying = player.isPlaying;
          }
          port.write('W');
          break;
        case 3: 
          player.currentPlaylist = "Indian Music";
          player.currentTrack = 12;
          println("Playing Indian Music playlist");
          if(player.isPlaying){
            player.isPlaying = !player.isPlaying;
          }else{
            player.isPlaying = player.isPlaying;
          }
          port.write('I');
          break;
      }
    }
  }
}
  • Embedded Program that communicates with Processing Program
#include <DFRobotDFPlayerMini.h>
#include <SoftwareSerial.h>

// Constants ------------------------
//#define LED_PIN             D0 // This led is used for deebugging purposes
#define SOFTWARE_SERIAL_RX  D4
#define SOFTWARE_SERIAL_TX  D5
#define INITIAL_VOLUME      30
#define RESPONSE_DELAY_MS   1000

// Track numbers-----------------------------------------
enum {
  TRACK_ACTIVATE = 1,
  TRACK_DEACTIVATE = 2,
  TRACK_MEDITATION_START = 3,  // Default starting track
  TRACK_BHUTANESE_START = 6,
  TRACK_WESTERN_START = 9,
  TRACK_INDIAN_START = 12
};

// Control commands--------------------------------------
enum Command {
  CMD_ACTIVATE = 'A',     // Activate the NapPod
  CMD_DEACTIVATE = 'D',   // Deactivate the NapPod
  CMD_MEDITATION = 'M',   // Musics for Meditation
  CMD_BHUTANESE = 'B',    // Bhutanese Music
  CMD_WESTERN = 'W',      // Western Music
  CMD_INDIAN = 'I',       // Indian Music
  CMD_PLAY = 'P',         // Play track
  CMD_PAUSE_TOGGLE = 'x', // Toggle
  CMD_PAUSE = 'y',        // Pause current track
  CMD_PREVIOUS = 'z',     // Play Previous track
  CMD_NEXT = 'l'          // Play Next track
};

// Global Variables -----------------------------------------------------------
SoftwareSerial softwareSerial(SOFTWARE_SERIAL_RX, SOFTWARE_SERIAL_TX);
DFRobotDFPlayerMini dfPlayer;

bool napPodActive = false;
bool playbackPaused = true;
bool firstPlayAfterActivation = true;  // flag to track first play

// Forward Declarations -------------------------------------------------------
void activateNapPod();
void playDefaultTrack();
void playMeditation();
void playBhutanese();
void playWestern();
void playIndian();
void handleVolumeControl(char value);
void handleTransportControls(char command);

// Core Functions -------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  softwareSerial.begin(9600);
  //pinMode(LED_PIN, OUTPUT); used this led for debugging. It was really helpful!!!

  if (!dfPlayer.begin(softwareSerial)) {
    while (true); // Hardware freeze on failure
  }
  dfPlayer.volume(INITIAL_VOLUME);
}

void loop() {
  if (!Serial.available()) return;
  char command = Serial.read();

  switch (command) {
    case CMD_ACTIVATE:
    case CMD_DEACTIVATE:
      activateNapPod();
      break;

    case CMD_PLAY:  // New play command
      playDefaultTrack();
      break;

    case CMD_MEDITATION:
      playMeditation();
      break;

    case CMD_BHUTANESE:
      playBhutanese();
      break;

    case CMD_WESTERN:
      playWestern();
      break;

    case CMD_INDIAN:
      playIndian();
      break;

    case CMD_PAUSE_TOGGLE:
    case CMD_PAUSE:
    case CMD_PREVIOUS:
    case CMD_NEXT:
      handleTransportControls(command);
      break;

    default:
    if (command >= 0 && command <= 9) {
      handleVolumeControl(command);
    }
    break;
  }
}

//Control Implementations ---------------------------------------------
void activateNapPod() {
  dfPlayer.volume(30);
  dfPlayer.play(napPodActive ? TRACK_DEACTIVATE : TRACK_ACTIVATE);
  napPodActive = !napPodActive;
  firstPlayAfterActivation = true;  // Reset flag on activation
  delay(RESPONSE_DELAY_MS);
}

void playMeditation() {
  if (napPodActive) dfPlayer.play(TRACK_MEDITATION_START);
  delay(RESPONSE_DELAY_MS);
}

void playBhutanese() {
  if (napPodActive) dfPlayer.play(TRACK_BHUTANESE_START);
  delay(RESPONSE_DELAY_MS);
}

void playWestern() {
  if (napPodActive) dfPlayer.play(TRACK_WESTERN_START);
  delay(RESPONSE_DELAY_MS);
}

void playIndian() {
  if (napPodActive) dfPlayer.play(TRACK_INDIAN_START);
  delay(RESPONSE_DELAY_MS);
}

void handleVolumeControl(char value) {
  int mappedValue = map(value, 0, 9, 0, 30);
  dfPlayer.volume(mappedValue);  
}

void playDefaultTrack() {
  if (napPodActive) {
    if (firstPlayAfterActivation) {
      dfPlayer.play(3);
      firstPlayAfterActivation = false;
    } else {
      dfPlayer.start();  // Resume playback from current track
    }
  }
}

void handleTransportControls(char command) {
  switch (command) {
    case CMD_PAUSE_TOGGLE:
      playDefaultTrack();
      playbackPaused ? dfPlayer.start() : dfPlayer.pause();
      playbackPaused = !playbackPaused;
      break;

    case CMD_PAUSE:
      dfPlayer.pause();
      playbackPaused = true;
      break;

    case CMD_PREVIOUS:
      dfPlayer.previous();
      break;

    case CMD_NEXT:
      dfPlayer.next();
      break;
  }
  delay(RESPONSE_DELAY_MS);
}
  1. *Programming Addressable RGB Leds lighting option

  2. Here is my Program of lighting Addressable RGB Leds.

#include <Adafruit_NeoPixel.h>

// Define the number of NeoPixels and the pins they are connected to
#define NUM_PIXELS 24
#define PIN_STRIP1 D0
#define PIN_STRIP2 D1

// Initialize the NeoPixel objects for both strips
Adafruit_NeoPixel strip1 = Adafruit_NeoPixel(NUM_PIXELS, PIN_STRIP1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2 = Adafruit_NeoPixel(NUM_PIXELS, PIN_STRIP2, NEO_GRB + NEO_KHZ800);

// Define colors
uint32_t RED = strip1.Color(255, 0, 0);    // Red
uint32_t BLUE = strip1.Color(0, 0, 255);   // Blue
uint32_t GREEN = strip1.Color(0, 255, 0);  // Green

// Function to clear all pixels on both strips
void clearPixels() {
  for (int i = 0; i < NUM_PIXELS; i++) {
    strip1.setPixelColor(i, 0); // Turn off all pixels on strip1
    strip2.setPixelColor(i, 0); // Turn off all pixels on strip2
  }
  strip1.show();
  strip2.show();
}

// Function to smoothly blink all pixels on both strips with a specific color
void blinkColor(uint32_t color, int blinkDuration) {
  // Fade in
  for (int brightness = 0; brightness <= 255; brightness += 50) {
    for (int i = 0; i < NUM_PIXELS; i++) {
      strip1.setPixelColor(i, color);
      strip2.setPixelColor(i, color);
      strip1.setBrightness(brightness);
      strip2.setBrightness(brightness);
    }
    strip1.show();
    strip2.show();
    delay(blinkDuration); // Smooth transition
  }
}

// Function to turn on all pixels one by one on both strips
void snakeEffect(uint32_t color, int rounds) {
  for (int r = 0; r < rounds; r++) {
    for (int i = 0; i < NUM_PIXELS; i++) {
      // Turn on the current pixel on both strips
      strip1.setPixelColor(i, color);
      strip2.setPixelColor(i, color);

      // Update both strips
      strip1.show();
      strip2.show();

      // Wait a bit before moving to the next pixel
      delay(100);
    }
  }
}

// Function to turn off all pixels one by one on both strips
void snakeEffectOff(uint32_t color, int rounds) {
  for (int r = 0; r < rounds; r++) {
    for (int i = 0; i < NUM_PIXELS; i++) {
      // Turn off the current pixel on both strips
      strip1.setPixelColor(i, 0);
      strip2.setPixelColor(i, 0);

      // Update both strips
      strip1.show();
      strip2.show();

      // Wait a bit before moving to the next pixel
      delay(100);
    }
  }
}

void setup() {
  // Initialize both NeoPixel strips
  strip1.begin();
  strip2.begin();
  clearPixels(); // Ensure all pixels are off at the start
}

bool sequenceCompleted = false; // Flag to track if the sequence is done

void loop() {
  if (!sequenceCompleted) {
    // Blink red, blue, and green in sequence on both strips
    snakeEffect(RED, 1);
    clearPixels(); // Clear pixels after the red snake
    blinkColor(RED, 300);   // Blink red for 0.3 seconds

    blinkColor(BLUE, 300);  // Blink blue for 0.3 seconds

    blinkColor(GREEN, 300); // Blink green for 0.3 seconds
    snakeEffectOff(GREEN, 1);
    clearPixels(); // Clear pixels after the green snake

    sequenceCompleted = true; // Set the flag to true after the sequence
    clearPixels(); // Ensure all pixels are off at the start
  }
}

Putting Everything Together

  • Here is my final Program.