Monday, March 30, 2015

Auto on-off for amplifier

I ordered a bunch of Digispark boards - for a few dollars each and included bootloader you cannot go wrong. They are basically prototype&forget boards and go under various other names (Picoduino, Pro Nano, Olimexino-85). has them for ~3$.

The first project is an automatic power-on, power-off for an old Sony speaker set, formerly CD/cassette deck.

My goal is for the amplifier to turn on when audio signal is detected and turn off after 30 minutes of silence. This is not so much for power saving as it is to reduce the white noise and GSM pickup during the night.

The speakers can be turned on by connecting a 100 kOhm resistor between two of the four pins on a plug

I've made various measurements to determine the correct input level that's reliably above the silence noise floor.

In my case it was somewehere around 20mV.

The ADC of ATTiny85 can be set to a 1.1V reference, improving the detection precision. Otherwise, by default, the 3.3V reference is used.

I've tested various algorithms and found that peak detection works best. Averaging seemed better on paper but was not able to detect low levels of music.

I haven't drawn a schematic, but here's a wiring picture:

If that makes sense to you as much as it does to me, here's the description.
The Digispark is fed directly from the speakers' 16.5V supply, into the VIN pin. Power dissipation is decent.
A 4N35 optocoupler connects through pins 4 and 5 a 100k resistor across the power-on pins, like in the first picture.
The 4N35 pin 1 is driven by the Digispark pin 4, through a 10k resistor. Pin #2 of the optocoupler goes to GND.

Audio signal is taken directly from the center lead of the RCA jack, through a capacitor (a random one from the box, probably in the nanoFarad range) and goes to PIN2 on the Digispark.
A 10-20kOhm capacitor from the same pin #2 to ground reduces ambient noise. *

TODO: draw a schematic and post it here. Remind me in the comments if I forget.

* To make the circuit more sensitive, increase the resistor value from pin 2 (analog in) to ground.


The input is sampled every 100ms and the peak value is retained. If that peak value is above a threshold ('1' in this case) then the code detects the audio as playing and lights up an LED and turns on the optocoupler.
If after a predefined duration (30 minutes) no audio was detected then it shuts off the optocoupler.

There are various caveats to the Digispark - limited flash space (if Serial-to-USB debugging is used), limited pins (cannot use serial debugging afterwards), some pins cannot be really used (pin 5, I think).

Code has some snippets from this, however the original code does not have reliable detection.

#include <DigiCDC.h>
#include <avr/sleep.h>

// A1->P2, A2->P4, A3->P3, A0->P5
#define AMP_OUTPUT_PIN 4

/* Timing constants
 * maxIdleMinutes - Longer than this, amplifier will be turned off
 * sampleSleeTime - Every 2,000 mSec we sample and process
const int maxIdleMinutes = 30;
const int sampleSleepTime = 100;

// the setup routine runs once when you press reset:
void setup() {
  pinMode(STATE_OUTPUT_PIN, OUTPUT); //LED on Model A  or Pro
  pinMode(AMP_OUTPUT_PIN, OUTPUT); //LED on Model A  or Pro


// the loop routine runs over and over again forever:
unsigned long average = 0;
unsigned long peak = 0;
unsigned long idleTimeToTurnOff = (unsigned long)(maxIdleMinutes * 60L * 1000L);
unsigned long lastIdle; // Preserves the millis() read when idle was detected

boolean isSoundPlaying() {
  peak = 0;
  long temp_peak, reading;
  for (int i = 0; i < 100; i++) {
    reading = analogRead(ANALOG_INPUT_PIN);
    average = (9 * average + reading) / 10;
    if (reading > peak) peak = reading;
  return peak > SIGNAL_THRESHOLD;

void loop() {
  unsigned long now = millis(); // Take current time
  unsigned long diff;
  if (!isSoundPlaying()) {
    digitalWrite(STATE_OUTPUT_PIN, 0);
    if (lastIdle != 0)
      diff = 0;
      if (now < lastIdle) // Overflow of millis()
        diff = now + (0xFFFFFFFFL - lastIdle);
        diff = now - lastIdle;
      SerialUSB.print("Idle for ");
      SerialUSB.println(diff, DEC);
      if (diff > idleTimeToTurnOff)
        SerialUSB.print("Turning off");
        // Turn off the Amplifier
        digitalWrite(AMP_OUTPUT_PIN, 0);
      lastIdle = now; // Now is first time we see idle, save the time.
    // sound is playing
    digitalWrite(STATE_OUTPUT_PIN, 1);
    SerialUSB.print("Turning on");
    digitalWrite(AMP_OUTPUT_PIN, 1);
    lastIdle = 0;



  1. Thank you for your post!
    It was interesting to read. I'm going to repeat it for personal use.
    I would like to make small changes to the design - connect a second audio channel to the ADC input #3 (PB3) with appropriate changes in the MCU code.
    The only confuses for me - presence of DC bias 3.6V on this input from the USB con. circuit.
    What do you think, is it possible to compensateit in the code?

    1. There are two things to watch out for:

      DC bias on signal is fine, you just need a coupling capacitor in series, a resistor to limit the current and the ADC should likely use only the positive signal (rectification through protection diodes). Or you can add a diode to feel safer.

      DC offset on ground is a more complicated topic. On most car audio gear the audio ground is sitting at half the supply voltage. You WILL blow up the MCU if you try to connect it without protection.
      The best solution is to use an audio transformer, feed the signal from the transformer into MCU ground and ADC.
      The other solution is a capacitor in series with the signal ground, but you will be creating a ground loop that feeds noise back into the audio system. I also don't know what the frequency response of a system like this would be.
      The third solution is a differential amplifier or comparator. This you can feed directly into an I/O pin instead of using ADC. IN+ goes to signal, IN- goes to signal ground, output to MCU. You will need some low-pass filtering, gain adjustments and hysteresis. Search for LM324 schematics.

      Hope that helps, it's a topic in itself and hard to cover. Plus, I'm not knowledgeable enough to give better advice.