element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Spring Clean!
  • Challenges & Projects
  • Project14
  • Spring Clean!
  • More
  • Cancel
Spring Clean!
Spring Clean Projects 2026 Resurrection of the speakers
  • News and Projects
  • Forum
  • Members
  • More
  • Cancel
  • New
Join Spring Clean! to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: me_Cris
  • Date Created: 20 May 2026 2:57 PM Date Created
  • Views 36 views
  • Likes 3 likes
  • Comments 0 comments
  • PAM8403
  • pam8406
  • Spring Clean 2026
Related
Recommended

Resurrection of the speakers

me_Cris
me_Cris
20 May 2026
Resurrection of the speakers

Hi makers! Wave

A little context, I have a pair of speakers that I have never heard music coming out of, I know thiDrooling facesounds a bit strange, I bought them for a very low price from a thrift store (I couldn't hold myself Drooling face), and they seemed unused. After several attempts and checks I still haven't been able to fix them.
Movin on, let's implement the "Do It Yourself" method to bring something back to life, in this case it's speakers and LEDs.

Wouldn't it be a shame to just throw them away? Thinking

image

Main parts:
• speakers;
• PAM8403 audio amplifier, later PAM8406;
• microcontroller and addressable LEDs.

I have a pair of PC speakers, powered by 5V, they also have addressable LEDs built in, thus offering a more special look. But these speakers also have problems, the first being a broken LED that I had to replace, easy job, it's like done. The other problem I couldn't solve, something is damaged on the electronic circuit. It mostly contains: audio amplifier, digital volume control and an unencrypted IC but which I think has something to do with LEDs control.

{gallery}Original circuit board

image

IMAGE TITLE: Circuit board 

image

IMAGE TITLE: Circuit board



Solution in 2 steps

1. Audio amplifier replacement
I applied the DIY version using a PAM8403 (initially) as an audio amplifier (the well-known module that you can buy almost everywhere), and for LEDs control I have a microcontroller.
I designed a circuit in Kicad, then I made a more homemade PCB let's say (given that I only need one piece, it didn't make sense to buy, more) on which I mounted the PAM8403 module and some pin headers to connect with jump wires. I know that this module doesn't have a volume control but I don't mind this, it's basically just made to "sing".
I have a very basic schematic and PCB:

{gallery}Schematic + PCB

image

IMAGE TITLE: Basic schematoc

image

IMAGE TITLE: PCB

image



After doing some tests I was quite satisfied, until I added LEDs control. That's how another problem appeared, interference and an unbearable noise that bothers anyone's ear. When the LEDs are on and I apply a light show, a very sharp sound is heard, and without playing music. It's less noticeable during music playback, but otherwise it makes its presence felt. I don't think you want to hear it, I'm very serious. 

Noise record.m4a


I can't accept that, so I tried an upgrade to the PAM8406 which, as mentioned in the datasheet, performs better in terms of noise and interference.
Indeed, it is a considerable upgrade, it is not perfect but it is certainly better now. That sharp sound is still audible but weak, only if I put my ears in the speakers.
Since I made the original PCB for the PAM8403, I also tested it on this one, basically I improvised by soldering some wires (I mean painstaking work Sweat smile).
I will leave it as it is until I can make a suitable PCB for the PAM8406.

{gallery}PAM8403 / PAM8406

image

IMAGE TITLE: PAM8403

image

IMAGE TITLE: PAM8403

image

IMAGE TITLE: PAM8406

image

IMAGE TITLE: PAM8406

image

IMAGE TITLE: PAM8406

2. WS2812B addressable LED control
As a controller for the LEDs I have an ATMega8 mounted on a simple board, a minimal one. Another option for LED control would have been some simple controllers but since the speakers initially had brightness adjustment I finally opted for a microcontroller because it allows me to use my imagination as well. So I integrated a TTP223 touch button.
We also have a program written in Arduino IDE using the FastLed library. There are 6 operating LED effects: Gaussian pulse, Gaussian ping-pong, meteor effect, random single LED fade and sparkle effect. Using TTP223 I can go through these effects as I want.
In one version the program was too large to be uploaded, so I turned to A.I. for optimizations and finally I managed to upload the program to the microcontroller. Pray

Fullscreen All in one V3.3.1.txt Download
/*
  ALl In One v3.3.1
  LED effects for speakers
*/

#include <FastLED.h>
#include <avr/pgmspace.h>

// --- Hardware Configuration ---
// These define the pins, LED count and type.
#define LED_PIN_1 11
#define LED_PIN_2 10  // Mirrored output, pins show the same LED pattern
#define NUM_LEDS 7
#define LED_TYPE WS2812
#define COLOR_ORDER GRB
#define BRIGHTNESS 255  // Adjust 0-255 to set global intensity (0 = off, 255 = max)

// --- STEP 1: Optimization - Lookup Table (LUT) ---
// The gaussianLUT stores precomputed brightness weights for a 7-point Gaussian-like pulse.
// Using PROGMEM keeps these values in Flash (program memory) instead of SRAM.
// Values range 0-255 where 255 is full brightness for the color before global brightness scaling.
const uint8_t gaussianLUT[7] PROGMEM = { 13, 71, 190, 255, 190, 71, 13 };

// --- STEP 2: Optimization - PROGMEM Colors ---
// colorArray stores RGB colors as 24-bit hex values in Flash to save SRAM.
// NUM_COLORS must match the number of entries in colorArray.
const uint32_t colorArray[] PROGMEM = {
  0xFF0000,  // #1 Red
  0x00FF00,  // #2 Green
  0x0000FF,  // #3 Blue
  0xFFFF00,  // #4 Yellow
  0xFF00FF,  // #5 Magenta
  0x00FFFF,  // #6 Cyan
  0xFF69B4,  // #7 Pink
  0xFFA500,  // #8 Orange
  0x800080   // #9 Purple
};
const uint8_t NUM_COLORS = 9;

// leds[] is the main buffer used by FastLED. It holds the color for each LED.
//CRGB leds[NUM_LEDS];
CRGB ledsA[NUM_LEDS];
CRGB ledsB[NUM_LEDS];

// --- State Management ---
// currentEffect selects which effect runs in loop()
// EFFECT_COUNT is the number of selectable effects (0..EFFECT_COUNT-1)
uint8_t currentEffect = 0;
const uint8_t EFFECT_COUNT = 6;
volatile bool buttonPressedFlag = false;  // set by checkButton() when a valid press is detected

// Function to fetch color from Flash memory
// idx is wrapped with modulo NUM_COLORS to avoid out-of-range access.
CRGB getColor(uint8_t idx) {
  // pgm_read_dword reads a 32-bit value from PROGMEM; cast to CRGB for FastLED usage.
  return (uint32_t)pgm_read_dword(&(colorArray[idx % NUM_COLORS]));
}

// Copies ledsA reversed into ledsB and then calls FastLED.show()
inline void mirrorAtoB_and_show() {
  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    ledsB[i] = ledsA[NUM_LEDS - 1 - i];
  }
  FastLED.show();
}

// ---------- Button Logic (Debounced) ----------
// BUTTON_PIN uses INPUT_PULLUP so the button should connect the pin to GND when pressed.
const uint8_t BUTTON_PIN = 12;
void checkButton() {
  // lastState remembers the previous digitalRead state to detect changes.
  static uint8_t lastState = HIGH;
  // lastTime stores the last time we accepted a change; used for debouncing.
  static uint32_t lastTime = 0;
  uint8_t reading = digitalRead(BUTTON_PIN);

  // Debounce check: ignores signal noise shorter than 40ms.
  // If the reading changed and more than 40ms passed since last accepted change,
  // we treat it as a real button press/release.
  if (reading != lastState && (millis() - lastTime) > 100) {
    // Because INPUT_PULLUP is used, LOW means the button is pressed.
    if (reading == LOW) buttonPressedFlag = true;
    lastState = reading;
    lastTime = millis();
  }
}

// ---------- Gaussian Rendering Logic ----------
// renderGaussian draws a centered pulse using the gaussianLUT weights.
// head: center position of the pulse (can be outside 0..NUM_LEDS-1 to allow smooth entry/exit).
// colorIdx: index into colorArray to pick the pulse color.
void renderGaussian(int8_t head, uint8_t colorIdx) {
  CRGB color = getColor(colorIdx);

  // For each LED compute its distance to the pulse center and apply the LUT weight.
  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    // dist shifts the head so that LUT index 0 corresponds to the left-most sample.
    // The +3 offset aligns the 7-point LUT around the head position.
    int8_t dist = i - head + 3;

    // If distance is within the LUT range (0..6) read the weight from PROGMEM.
    // Otherwise weight is 0 (LED off).
    uint8_t weight = (dist >= 0 && dist < 7) ? pgm_read_byte(&(gaussianLUT[dist])) : 0;

    // Set the LED color, then scale its brightness by the LUT weight.
    // nscale8_video scales the color in place using an 8-bit scale (0-255).
    ledsA[i] = color;
    ledsA[i].nscale8_video(weight);
  }

  // Mirror ledsA into ledsB (reverse order)
  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    ledsB[i] = ledsA[NUM_LEDS - 1 - i];
  }
  // Push the buffer to the LEDs.
  FastLED.show();
}

// ---------- Effect 0: Moving Gaussian Pulse ----------
// Default effect, this effect moves a Gaussian-shaped pulse from right to left.
// g1_headPos starts off-screen to the right so the pulse enters smoothly.
const int8_t LUT_HALF = 3;                                        // existing alignment offset (7-point LUT -> half = 3)
const int8_t OFFSCREEN_BEFORE = LUT_HALF;                         // -3 before visible LEDs (keep as 3)
const int8_t OFFSCREEN_AFTER = 3;                                 // +3 after visible LEDs (increase this to lengthen pause)
const int8_t HEAD_START = NUM_LEDS + OFFSCREEN_AFTER + LUT_HALF;  // e.g., 7 + 3 + 3 = 13
const int8_t RESET_THRESHOLD = -OFFSCREEN_BEFORE;                 // e.g., -3
int8_t g1_headPos = HEAD_START;
uint8_t g1_colorIdx = 0;
void runGaussianRightToLeft() {
  static uint16_t offscreenPause = 0;
  // PAUSE_FRAMES controls how long the strip stays blank after the pulse fully leaves.
  // Try values 8–20 to find the pause you like.
  const uint16_t PAUSE_FRAMES = 12;  // tune: larger = longer pause
  static uint32_t lastStep = 0;
  // SPEED ADJUST: Change 90 to higher (slower) or lower (faster).
  // This value is the minimum milliseconds between steps.
  if (millis() - lastStep < 90) return;
  lastStep = millis();

  // Draw the pulse at the current head position and then move it left.
  renderGaussian(g1_headPos, g1_colorIdx);
  g1_headPos--;

  // BOUNDARY ADJUST: When the head has moved far enough left that the pulse tail
  // is fully off the strip (head < -3), reset to the starting position.
  if (g1_headPos < RESET_THRESHOLD) {
    // Hold off for a few frames before resetting and changing color
    if (offscreenPause < PAUSE_FRAMES) {
      offscreenPause++;
      return;  // keep returning so the strip stays black (pulse off-screen)
    } else {
      offscreenPause = 0;
      g1_headPos = HEAD_START;
      g1_colorIdx = (g1_colorIdx + 1) % NUM_COLORS;
    }
  }
}

// ---------- Effect 1: Gaussian Ping-Pong ----------
// This effect bounces the Gaussian pulse back and forth across the strip.
int8_t g2_headPos = -3;
int8_t g2_dir = 1;  // direction: +1 = right, -1 = left
uint8_t g2_colorIdx = 0;
void runGaussianPingPong() {
  static uint32_t lastStep = 0;
  // SPEED ADJUST: Change 70 to modify travel speed.
  if (millis() - lastStep < 70) return;
  lastStep = millis();

  renderGaussian(g2_headPos, g2_colorIdx);
  g2_headPos += g2_dir;

  // DIRECTION LOGIC: Reverse when hitting virtual boundaries.
  // Boundaries are chosen so the pulse can fully enter and exit the 7-LED window.
  if (g2_headPos >= 10 || g2_headPos <= -3) {
    g2_dir *= -1;
    // Change color only when it returns to the start position (optional stylistic choice).
    if (g2_headPos <= -3) g2_colorIdx = (g2_colorIdx + 1) % NUM_COLORS;
  }
}

// ---------- Effect 2: Meteor ----------
// A moving head with a fading trail. fadeToBlackBy reduces brightness of all LEDs each step.
int8_t m4_dir = -1;  // +1 = left-to-right as before, -1 = right-to-left
int8_t m4_head = -6;
uint8_t m4_colorIdx = 0;
// Virtual boundaries (symmetric)
const int8_t METEOR_ENTRY = -6;   // head start off-screen
const int8_t METEOR_EXIT = NUM_LEDS + 6; // head fully past right side
void runMeteor() {
  static uint32_t lastStep = 0;
  // SPEED ADJUST: Change 60 for movement speed.
  if (millis() - lastStep < 60) return;
  lastStep = millis();

  // TRAIL ADJUST: fadeToBlackBy reduces each LED's brightness by the given amount.
  // Larger values make the trail decay faster (shorter tail).
  fadeToBlackBy(ledsA, NUM_LEDS, 60);

  // Only set the head LED if it's within the visible range.
  if (m4_head >= 0 && m4_head < NUM_LEDS) {
    ledsA[m4_head] = getColor(m4_colorIdx);
  }
  // Mirror ledsA into ledsB (reverse order)
  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    ledsB[i] = ledsA[NUM_LEDS - 1 - i];
  }
  // Push the buffer to the LEDs.
  FastLED.show();

  // Advance head according to direction
  m4_head += m4_dir;

  // Reset or wrap depending on direction
  if (m4_dir > 0) {
    // moving left-to-right (increasing index)
    if (m4_head >= METEOR_EXIT) {
      m4_head = METEOR_ENTRY;
      m4_colorIdx = (m4_colorIdx + 1) % NUM_COLORS;
    }
  } else {
    // moving right-to-left (decreasing index)
    if (m4_head <= METEOR_ENTRY - 1) {
      m4_head = METEOR_EXIT - 1;
      m4_colorIdx = (m4_colorIdx + 1) % NUM_COLORS;
    }
  }
}

// ---------- Effect 3: Random Single Fade ----------
// A single LED fades in then out, then a new random LED/color is chosen.
uint8_t r3_bri = 0, r3_led = 0, r3_colorIdx = 0;
bool r3_fadingIn = true;
void runRandomSingleFade() {
  static uint32_t lastStep = 0;
  // SPEED ADJUST: Lower than 20 makes the fade smoother/faster (smaller delay between updates).
  if (millis() - lastStep < 20) return;
  lastStep = millis();

  // Clear all LEDs, set the chosen LED to the selected color and scale by brightness.
  fill_solid(ledsA, NUM_LEDS, CRGB::Black);
  ledsA[r3_led] = getColor(r3_colorIdx);
  ledsA[r3_led].nscale8_video(r3_bri);
  mirrorAtoB_and_show();

  // FADE RATE: r3_bri is incremented/decremented by 8 each step.
  // Increase this increment to make the fade faster; decrease to make it slower/smoother.
  if (r3_fadingIn) {
    r3_bri += 8;
    // Use 248 as a near-maximum threshold to avoid overflow when adding 8.
    if (r3_bri >= 248) r3_fadingIn = false;
  } else {
    r3_bri -= 8;
    // When brightness reaches 0, pick a new LED and color for the next cycle.
    if (r3_bri <= 0) {
      r3_led = random(NUM_LEDS);  // Pick new LED for next cycle
      r3_colorIdx = (r3_colorIdx + 1) % NUM_COLORS;
      r3_fadingIn = true;
    }
  }
}

// ---------- Effect 4: Sparkle ----------
// Small independent sparks that fade in then out. Limited to 3 sparks to save RAM.
struct Spark {
  bool active;
  uint8_t led;
  uint8_t colorIdx;
  uint16_t start;  // start time in ms (truncated to 16-bit)
  uint16_t life;   // life duration in ms
};
Spark s5_sparks[3];  // Only 3 sparks active to save memory on ATmega8

void runSparkle() {
  static uint32_t lastStep = 0;
  if (millis() - lastStep < 30) return;
  lastStep = millis();

  // Clear the strip each frame; active sparks will set their LED.
  fill_solid(ledsA, NUM_LEDS, CRGB::Black);

  for (uint8_t i = 0; i < 3; i++) {
    if (!s5_sparks[i].active) {
      // PROBABILITY ADJUST: random8() < 25 gives ~10% chance per frame to spawn a spark.
      // Increase the threshold to make the effect busier.
      if (random8() < 25) {
        // Initialize a new spark with random LED, color and lifetime.
        s5_sparks[i] = { true, (uint8_t)random8(NUM_LEDS), (uint8_t)random8(NUM_COLORS), (uint16_t)millis(), (uint16_t)random(300, 800) };
      }
    } else {
      // Compute how long the spark has been alive.
      uint16_t age = millis() - s5_sparks[i].start;
      if (age >= s5_sparks[i].life) s5_sparks[i].active = false;
      else {
        // TRIANGLE CALCULATION: create a fade-in then fade-out brightness curve.
        // If age is in the first half of life, map 0..half -> 0..255 (fade in).
        // Otherwise map half..life -> 255..0 (fade out).
        uint8_t bri = (age < s5_sparks[i].life / 2) ? map(age, 0, s5_sparks[i].life / 2, 0, 255) : map(age, s5_sparks[i].life / 2, s5_sparks[i].life, 255, 0);
        ledsA[s5_sparks[i].led] = getColor(s5_sparks[i].colorIdx);
        ledsA[s5_sparks[i].led].nscale8_video(bri);
      }
    }
  }
  mirrorAtoB_and_show();
}

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  // Mirroring Setup: Same data buffer sent to both pins.
  // This duplicates the LED output on two physical pins (useful for mirrored strips).
  //FastLED.addLeds<LED_TYPE, LED_PIN_1, COLOR_ORDER>(leds, NUM_LEDS);
  //FastLED.addLeds<LED_TYPE, LED_PIN_2, COLOR_ORDER>(leds, NUM_LEDS);
  // Replace the single addLeds calls with two separate buffers:
  FastLED.addLeds<LED_TYPE, LED_PIN_1, COLOR_ORDER>(ledsA, NUM_LEDS);
  FastLED.addLeds<LED_TYPE, LED_PIN_2, COLOR_ORDER>(ledsB, NUM_LEDS);

  FastLED.setBrightness(BRIGHTNESS);
}

void loop() {
  // Check the button each loop; if pressed, advance to the next effect.
  checkButton();
  if (buttonPressedFlag) {
    buttonPressedFlag = false;
    currentEffect = (currentEffect + 1) % EFFECT_COUNT;
    // Clear old effect data so the new effect starts from a blank state.
    fill_solid(ledsA, NUM_LEDS, CRGB::Black);
    mirrorAtoB_and_show();
  }

  // Effect Selector: call the function for the currently selected effect.
  switch (currentEffect) {
    case 0: runGaussianRightToLeft(); break;
    case 1: runGaussianPingPong(); break;
    case 2: runMeteor(); break;
    case 3: runRandomSingleFade(); break;
    case 4: runSparkle(); break;
    case 5:  // All Off state
      fill_solid(ledsA, NUM_LEDS, CRGB::Black);
      mirrorAtoB_and_show();
      break;
    default: runGaussianRightToLeft(); break;
  }
}



As for assembly, some manual work, nothing special.

{gallery}Assembly

image

IMAGE TITLE: Assembly

image

IMAGE TITLE: Assembly

image

IMAGE TITLE: Assembly

image

IMAGE TITLE: Assembly



I also leave you a video (not maximum volume, I don't want to disturb the neighbors).

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

What do you think of my DIY project? Grin


Thank you and have a good day!




  • Sign in to reply
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube