Go Down

Topic: Measuring amplifier output power (watts) (Read 9562 times) previous topic - next topic

Jiggy-Ninja

I have an audio amplifier (TPA3122, Class D amplifier: http://www.mouser.com/ds/2/405/slos527a-88277.pdf) and I wish to use an Arduino to measure its output power to make sure I'm not feeding too much into my speaker. It doesnt have to be exact, a ballpark estimate will suffice (10-20% error may be acceptable). I'm new to signal processing and I don't know where to start. This value is intended to be put on a display, so it doesn't need to update very fast. Once or twice per second is good.

I've hooked up a circuit according to the Simplified Application Circuit in the datasheet using only the left output for sigle-ended mono and had it succefully play music from my phone.

I don't know quite where to start with this. If I just hook a low-pass filter to the output of a large time constant, it'll just smooth it out to the DC level. Make it less, and I might be able to follow the audiop waveform, but I don't know how fast the ADC can work, if it'll catch stuff without aliasing problems.

I have plenty of op-amps, resistors, caps, and diodes I can use. I just need some guidance.

My end game for this project is to create a digitally controlled amplifier+speaker in a box that I can use to play music from my phone's 3.5mm jack, with possibly some equalizer filters like bass/treble boost and such. For that, I need to know what size speaker I need for a given loudness, as well as possibly have software control to turn down the volume if the power is too high. It's still in the planning stages right now.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Zapro

Measuring "power" output will not help you get to your goal.

You can have a 1000 Watt amp and 100 Watts speakers and never blow anything. Having a 100 Watt amp, and 1000 watts speakers usually ends in destroyed speakers.

When you overdrive your system, you will get high frequency oscillation which destroys the speakers. You could do some FFT analysis to see, if something is being overdriven.

// Per.

retrolefty

Quote

For that, I need to know what size speaker I need for a given loudness,


Size does not (directly) determine what the 'loudness' of a specific speaker is when driven with a specific reference power value. Even a speakers 'maximum power rating' does not tell you how 'loud' that speaker will be at it's maximum power output.  Better speaker manufactures will publish a specification called SPL (specific power Vs loudness) and it's a direct indication of the efficiency of that specific speaker, that is how much signal voltage will generate how much absolute sound level.

Speakers are not always or even usually selected by either their maximum power rating or their SPL rating alone, as speaker frequency response 'flatness' is an even more important specification as it's often more important that a speaker be able to respond accurately for best sound reproduction.

Full range 20-20,000 Hz 'hi-fi' single speakers are rather hard to obtain and why most quality hi-fi speakers have multiple speakers inside their box often called woofer, tweeter, mid-range etc. This then requires a passive cross-over filter so the audio power will be automatically directed preferentially to each driver so that the over all audio output sound is 'flat' across the audio frequency range desired.  More expensive/quality hi-fi speakers often use fuses to protect the driver voice coils from over-driving them.

And of course the box one mounts the speaker(s) has as much to do with the accurate frequency response and efficiency as well as it's size and constructed (ported Vs sealed) must be 'tuned' or 'matched' to the speaker specifications, esp the woofer.

Audio reproduction is a mature technology with tons of information available on the web. While the state of the art in audio electronics has continued to progress (class A, AB, D, etc) speaker tech is pretty mature and known. As in most things quality results are often directly related to costs.

Lefty

Jiggy-Ninja


You can have a 1000 Watt amp and 100 Watts speakers and never blow anything. Having a 100 Watt amp, and 1000 watts speakers usually ends in destroyed speakers.

You'll have to explain that some more, since it doesn't make any sense at all to me.

@ retrolefty:
By "size" I was not referring to physical size, but rather the power rating. Right now the only speaker I have is a generic 0.5W 8 ohm speaker like this one from Sparkfun: https://www.sparkfun.com/products/9151. I'd like something a bit louder, maybe in the order of a few watts or so.

I'm not really looking for a hi-fi, just something that's an improvement (in loudness and possibly quality) over my phone's speaker. I'm not an audiophile, so little imperfections are fine.

The primary reason I'm doing this though is because it seems interesting and I want to learn things.

The fact that there's tons of information can be a negative as well as a positive, since a lot of information online is garbage. Do you know some good sites you'd recommend?

In terms of the actual measurement, I've been doing some research of my own. I've put the Arduino's ADC is free running mode with a prescaler of /64. With that setup and my ISR, I'm getting about 18,500 conversions per second. I've hooked a low pass filter up the the output according to the attached schematic that also attenuates the signal by 11. This lets me use the internal reference instead of AVcc, since I'm powering the TPA amplifier with 12 volts for now.

My ISR fills a large ring buffer with the ADC reading.When it wraps around back to the beginning, it calculates the RMS voltage of the values in the ring buffer (referenced to the DC center) and stores that in another ring buffer that is used to average out short scale bursts of power.

Sketch:
Code: [Select]
unsigned long timer = 0;
unsigned long ADC_count=0;

#define RING_BUFFER_SIZE 500
volatile int ring_buffer[RING_BUFFER_SIZE];
volatile unsigned int ring_buffer_index = 0;

#define RMS_BUFFER_SIZE 50
volatile int rms_ring_buffer[RMS_BUFFER_SIZE];
volatile unsigned int rms_buffer_index = 0;

#define ADC_REF_MVOLTS 1100UL


void setup()
{
  Serial.begin( 19200 );
 
  ADCSRA = _BV(ADEN) + // ADC Enable
            _BV(ADATE) + // ADC Auto Trigger Enable
            _BV(ADIE) + // ADC Interrupt Enable
            0x06; // /64 prescaler, ADC does approximately 18,500 conversions per second.
  ADCSRB = 0; // Set ADC to freerunning mode
  ADMUX =   (0x3<<6) +  // 1.1 V Reference
            0;          // Channel 0
 
  ADCSRA |= _BV(ADSC); // Start first conversion.
  delay( 50 ); // Delay a little bit to fill the buffer.
}

void loop()
{
  unsigned long t_now_ms = millis();
 
  if( t_now_ms - timer > 1000 )
  {
    timer = t_now_ms;
    noInterrupts();
    int rms_average = average_array( rms_ring_buffer, RMS_BUFFER_SIZE );
    unsigned long temp = ADC_count;
    ADC_count = 0;
    interrupts();
   
    Serial.print( "RMS: " );
    Serial.println( rms_average );
    Serial.print( "RMS (mV): " );
    int speaker_rms = unscale_voltage_divider(adc_raw_to_mv(rms_average));
    Serial.println( speaker_rms );
    Serial.print( "Output (mW): " );
    Serial.println( (unsigned long)speaker_rms * speaker_rms / 8000 );
    Serial.print( "ADC passes: " );
    Serial.println( temp );
    Serial.println();
  }
}

ISR(ADC_vect)
{
  ADC_count++;
  ring_buffer[ring_buffer_index] = ADC;
  ring_buffer_index++;
  if( ring_buffer_index==RING_BUFFER_SIZE )
  {
    ring_buffer_index = 0;
    int average = average_array( ring_buffer, RING_BUFFER_SIZE );
    int rms = rms_array( ring_buffer, RING_BUFFER_SIZE, average );
    rms_ring_buffer[rms_buffer_index] = rms;
    rms_buffer_index++;
    if( rms_buffer_index == RMS_BUFFER_SIZE )
      rms_buffer_index = 0;
  }
}

int average_array(volatile int array[], int size)
{
  long sum = 0;
  for( int i=0; i<size; i++ )
    sum += array[i];
   
  return sum/size;
}

int rms_array(volatile int array[], int size, int zero_level)
{
  long sum = 0;
  int val;
  for( int i=0; i<size; i++ )
  {
    val = abs( array[i] - zero_level );
    sum += (unsigned long)val * val;
  }
 
  long square_mean = sum / size;
 
  return sqrt( square_mean );
}

int adc_raw_to_mv(int raw)
{
  int result = raw * ADC_REF_MVOLTS / 1024;
  return result;
}

int unscale_voltage_divider( int output )
{
  return output*11;
}

Serial output:
Code: [Select]
RMS: 131
RMS (mV): 1540
Output (mW): 296
ADC passes: 18544

RMS: 137
RMS (mV): 1617
Output (mW): 326
ADC passes: 18539

RMS: 148
RMS (mV): 1738
Output (mW): 377
ADC passes: 18564

RMS: 133
RMS (mV): 1562
Output (mW): 304
ADC passes: 18584


Is this algorithm the right approach? I know I could probably make this more efficient, but it's a start.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Grumpy_Mike

Quote
it calculates the RMS voltage of the values in the ring buffer

The RMS power is not a measure of loudness.
I did a consultancy years ago for some one who owned a night club. He had bought a sound system of a certain wattage and he complained it did not sound loud enough. He got me and another guy in to measure what his system could produce.
We had to explain the various ways of measuring power and how none of them were directly related to perceived loudness. But perhaps peak music power is the closest but note it is not the same as RMS. Normally it produces a higher number and it has been miss-used in advertising.

Jiggy-Ninja

I'm not trying to electronically measure "loudness", I'm trying to measure the power going into a speaker to ensure it stays within the speaker's limit. I'll be judging the approximate loudness I want with my ear.

And you mentioned peak power as being a better measure of loudness than RMS power, but this post (https://auphonic.com/blog/2012/08/02/loudness-measurement-and-normalization-ebu-r128-calm-act/) says:
Quote
Instead, audio productions were (and still are) normalized to peak levels, which do in no way determine how loud a signal is.

(Emphasis mine)
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Magician

I spotted at least 2 flows in design. First one in hardware, what makes you think 10k resistor and 22nF capacitor has a cut-off 8 kHz?
Second one in software, your math is absolutely wrong, calculation of averages before you get RMS doesn't make any sense.
DC offset needs to be subtracted at first stage, than you do RMS , as RMS =  sqrt( V1^2 + V2^2 + ....+ Vn^2), and you may average after that

Jiggy-Ninja


I spotted at least 2 flows in design. First one in hardware, what makes you think 10k resistor and 22nF capacitor has a cut-off 8 kHz?
Second one in software, your math is absolutely wrong, calculation of averages before you get RMS doesn't make any sense.
DC offset needs to be subtracted at first stage, than you do RMS , as RMS =  sqrt( V1^2 + V2^2 + ....+ Vn^2), and you may average after that

There's a voltage divider formed by the 10k and 1k resistor. Rearrange that into the Thevanin equivalent and it's a 900 ohm resistor in series with a Vin/11 voltage source. 900 ohms and 22nF has a cutoff of around 8kHz by my math.

Software wise, the calculation of the average of the ring buffer is the DC offset. I use that value in the RMS function to subtract from the measured value.

One of the speed improvements I could have is to calculate the DC offset at the beginning with the amplifier muted (50% duty cycle). This was just a proof-of-concept sketch to make sure I got the algorithm right.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Magician

#8
Mar 10, 2014, 03:58 am Last Edit: Mar 10, 2014, 04:01 am by Magician Reason: 1
Right, this small resistors always bug me.. 8)
Sampling 18k, you have about 50 usec  between samples, it's mean doing average 500 would take ~ 1 millisec or so, AFAIK summ long about 2 usec each. Than you taking RMS, where at least 20 samples skipped. More during other math, so it's not going to be accurate, there is no real-time?.
"abs" is not necessary.
What is the board?

Jiggy-Ninja

The RMS calculation happens inside the ISR. Even if the ADC is still free running, it won't update the array until everything is done.

Just tested the RMS calculation time with a for loop on another sketch, with a buffer that size it takes 2.3 ms to do the calculation. So I'd miss out on 2-3 ticks of millis() during that time. Is that what you're concerned about?

This is just on my breadboard right now. I don't have this built up on a PCB or perfboard yet.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Zapro



You can have a 1000 Watt amp and 100 Watts speakers and never blow anything. Having a 100 Watt amp, and 1000 watts speakers usually ends in destroyed speakers.

You'll have to explain that some more, since it doesn't make any sense at all to me.


If you have a heavy load on a wimpy amplifier, you will overlad the amplifier at some point when someone cranks the volume too high. Then the power supply in the amplifier will not be up to the job, and you will get distortion - which destroys your speaker and your ears.

// Per.

Magician

Quote
This is just on my breadboard right now. I don't have this built up on a PCB or perfboard yet.
Than what is the chip?
Regarding timing, my concern is the accuracy, you can get any precision skipping samples, 2-3 msec out of ? 500/18000 = 27.77 msec. There is no logic to calculate DC offset in each frame, if results of by 10%.  Better approach is HPF inside ISR to have deal with varying DC.

Jiggy-Ninja

@Zapro: Wouldn't the load the speaker resents depend on it's impedance, not wattage?

@Magician: Chip is TPA3122, which is linked in my first post.

The DC offset doesn't vary that much. I could calculate it once with the speaker on mute and be done with it.

What is HPF? High Pass Filter? I assume a software implementation. Wouldn't that take even more time?
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Magician

Quote
@Magician: Chip is TPA3122, which is linked in my first post.
I mean arduino a-like chip, what you have to run a code? AtMega328?
Quote
What is HPF? High Pass Filter? I assume a software implementation. Wouldn't that take even more time?

Could be, the question is what factors you estimate/ considered more important for accuracy, and what less.
Obviously, arduino (not DUE) is not capable to run 20-20000 power measurements precise, so you 'd need to sacrifice something but keep accuracy 1-3% , which is as good as most multimeters have.
Factors are: sampling rate; tracking DC offset; rounding errors - do math in 16 or 32 or even 64 bits, flatness over full audio band .
From mine point of view, leaving a 10 % gaps in sampling is a big error, especially accounting that samples goes squared (^2) and  error goes up to 30%. So I would run code real-time, but sacrificing  DC tracking, for example doing DC calibration ones in while (month/year or by request, keeping data in EEPROM).

dlloyd

I took a quick look at some speaker protection info here.  Since you only need a ballpark estimate of power and the thermal time constant of a voice coil is relatively slow, I think power estimation, thermal protection and AGC could be done with a sample rate in the range of 10-20 sps and using an RC filter of around 50-100ms. This could simplify your code and processing demand in your project.

Go Up