Last year I did a project, audio VU meter. Same arduino UNO, sampling rate 40 000 , calculates RMS and dB.
#include <glcd.h> // http://playground.arduino.cc/Code/GLCDks0108
#include <avr/pgmspace.h>
#include "fonts/allFonts.h" // system and arial14 fonts are used
#include "bitmaps/allBitmaps.h" // all images in the bitmap dir
#define SMP_RATE 40 // Sampling Rate, in kHz
#define SMP_TMR1 ((16000/SMP_RATE) -1) // Sampling Period of Timer1
/* VU Meter / The audio level meter most frequently encountered is the VU meter. Its characteristics are
defined as the ANSI specification C165. Some of the most important specifications for an AC meter
are its dynamic characteristics. These define how the meter responds to transients and how fast the reading
decays. The VU meter is a relatively slow full-wave averaging type, specified to reach 99% deflection in
300 ms and overshoot by 1 to 1.5%. In engineering terms this means a slightly underdamped second order
response with a resonant frequency of 2.1 Hz and a Q of 0.62.
While several European organizations have specifications for peak program meters, the German DIN specification
45406 is becoming a de facto standard. Rather than respond instantaneously to peak, however, PPM specifications re-
quire a finite “integration time” so that only peaks wide enough to be audible are displayed. DIN 45406
calls for a response of 1 dB down from steady-state for a 10 ms tone burst and 4 dB down for a 3 ms tone burst.
These requirements are consistent with the other frequently encountered spec of 2 dB down for a 5 ms burst and
are met by an attack time constant of 1.7 ms. The specified return time of 1.5s to ?20 dB requires a 650 ms
decay time constant.*/
Image_t icon;
gText countdownArea = gText(GLCD.CenterX, GLCD.CenterY,1,1,Arial_14);
int16_t adc_Offst = 512;
volatile int32_t ppm_Level = 0;
float rms_Level = 0.0;
// int16_t x10_coeff = 10;
ISR(TIMER1_COMPB_vect)
{
int32_t temp = ADC - adc_Offst;
temp = temp * temp;
if ( temp > ppm_Level ) ppm_Level = ((ppm_Level * 255) + temp) >> 8;
else ppm_Level = (ppm_Level * 16383) >> 14;
}
void Draw_Table()
{
GLCD.CursorToXY( 3, 3);
GLCD.Puts("-20 10 5 3 1 0 1 2 3");
GLCD.CursorToXY( 5, 52);
GLCD.Puts("VU meter");
GLCD.CursorToXY(75, 52);
GLCD.Puts("Magician");
GLCD.DrawRoundRect( 0, 0, 126, 63, 5);
GLCD.DrawLine( 64, 62, 5, 10, BLACK ) ;
GLCD.DrawLine( 63, 62, 4, 10, BLACK ) ;
}
void Draw_Arrow( int32_t scale )
{
static int st1 = 5;
static int st2 = 5;
st2 = map( scale, 20, 300, 5, 122); // 23.5 dB
if ( st2 > 122 ) st2 = 122;
if ( st2 < 5 ) st2 = 5;
if ( abs(st1 - st2) > 3 ) // 1/3 dB
{
GLCD.DrawLine( 64, 62, st1, 10, WHITE ) ;
GLCD.DrawLine( 63, 62, st1 -1, 10, WHITE ) ;
GLCD.DrawLine( 64, 62, st2, 10, BLACK ) ;
GLCD.DrawLine( 63, 62, st2 -1, 10, BLACK ) ;
st1 = st2;
}
}
void setup()
{
Serial.begin(115200);
GLCD.Init();
if(GLCD.Height >= 64)
icon = ArduinoIcon64x64; // the 64 pixel high icon
else
icon = ArduinoIcon64x32; // the 32 pixel high icon
introScreen();
GLCD.ClearScreen();
GLCD.SelectFont(System5x7, BLACK);
adc_Offst = analogRead(A5);
Draw_Table();
/* Setup ADC */
ADMUX = 0x45; // PIN 5 Analog.
ADCSRA = ((1<< ADEN)| // 1 = ADC Enable
(0<< ADSC)| // ADC Start Conversion
(1<<ADATE)| // 1 = ADC Auto Trigger Enable
(0<< ADIF)| // ADC Interrupt Flag
(0<< ADIE)| // ADC Interrupt Enable
(1<<ADPS2)|
(0<<ADPS1)| // ADC Prescaler : 1 MHz.
(0<<ADPS0));
ADCSRB = ((1<<ADTS2)| // Sets Auto Trigger source - Timer/Counter1 Compare Match B
(0<<ADTS1)|
(1<<ADTS0));
/* Set up TIMER 1 - ADC sampler */
TIMSK0 = 0x00;
TIMSK1 = 0x00;
TIMSK2 = 0x00;
TCCR1A = 0;
TCCR1B = 0;
TCCR1C = 0;
TCCR1A = ((1<<WGM11) | (1<<WGM10)); // Mode 15, Fast PWM
TCCR1B = ((1<<WGM13) | (1<<WGM12)); // Mode 15, Fast PWM
TCCR1B |= (1<<CS10); // clk/1 prescaling.
OCR1A = SMP_TMR1;
OCR1B = SMP_TMR1;
TCNT1 = 0;
TIFR1 |= (1<<OCF1B);
TIMSK1 |= (1<<OCIE1B);
}
void loop()
{
char incomingByte;
int32_t temp;
temp = ppm_Level; // Take a copy, so Real Value not affected by calculation.
temp = sqrt(temp);
rms_Level = 20.0 * log10(temp +1); // Calculated, available over Serial
Draw_Arrow( temp );
if (Serial.available() > 0) {
incomingByte = Serial.read();
// "x" command - DEBUG
if (incomingByte == 'x') {
Serial.println("\n\t");
Serial.println(adc_Offst, DEC);
Serial.println(ppm_Level, DEC);
Serial.println(rms_Level, 2);
}
if (incomingByte == 'c') {
GLCD.ClearScreen();
Draw_Table();
}
}
}
void countdown(int count){
while(count--){ // do countdown
countdownArea.ClearArea();
countdownArea.print(count);
delay(1000);
}
}
void introScreen(){
GLCD.DrawBitmap(icon, 32,0);
countdown(3);
GLCD.ClearScreen();
GLCD.SelectFont(Arial_14);
GLCD.CursorToXY(GLCD.Width/2 - 44, 3);
GLCD.print("*** VU Meter ***");
GLCD.DrawRoundRect(8,0,GLCD.Width-19,17, 5);
countdown(3);
GLCD.ClearScreen();
}
It draw results on SFE 12864 display. I didn't implement HPF, and simply measuring offset after restart
VU_LCD_2_stable_P.ino (6.57 KB)
