Sound Pressure Level Meter

ESE 498 Senior Design

Matt Meshulam, Keith Swaback and Chris Reale





Our senior design project is a device that measures sound pressure level (SPL). SPL is a measure of air pressure change caused by a sound wave. SPL is measured in decibels and is calculated by the following equation, where px is the pressure level and pref is 20 µPa.

The motivation behind this project is that hearing loss becomes a concern in the presence of large SPLs for extended periods of time. The main goals for the design were to be accurate over a wide dynamic range (very quiet and very loud sounds), portable (battery powered), inexpensive, and easy to use.

For our design we chose to use the Silicon Labs C8051F320 microcontroller. This chip features a 10-bit ADC, so our goal in the design of the analog front-end was to maintain an SNR of about 60 dB. We also aim to be able to measure sound pressure levels of up to 120 dB SPL. We chose this level because a rule of thumb is that a live sound system is able to achieve a maximum SPL of about 120 dB. As shown in figure 1, our front-end consists of a microphone, a unity gain debalancing amplifier stage, an inverting gain stage, and a programmable gain amplifier (PGA) stage to increase dynamic range. The output of the PGA is connected to the microcontroller’s ADC.

Figure 1 - Block Diagram



I.                   SNR/Noise/Dynamic Range Considerations

Microphone and ADC

The first part of the signal chain is the microphone itself. We are using a DBX-brand RTA-M measurement microphone. SNR or self-noise is often not specified for microphones, but it can be assumed that a microphone designed to be used for measurements will have an SNR significantly higher than the 60 dB of our ADC. The microphone’s sensitivity is -63 dB where 0dB=1V/microbar. A microbar of pressure is equivalent to 74dB SPL. Therefore the maximum output voltage we expect from the microphone is:


The ADC on the PIC has a default input range equal to the supply rails, which in our case are 0-3.3 volts. The signal will therefore need to have a 1.65-volt DC bias. Our programmable gain amplifier is classified as a rail-to-rail output device, meaning that the output can reach its supply voltage, but it becomes less linear if the output gets within 300 mV of the positive or negative rails. For this reason, we decided to set the gain of the first amplifier stage to 6.5. This leads to a peak-to-peak output of 2.6 volts at 120 dB SPL, which uses almost the entire range of the ADC as well as assures minimal harmonic distortion.

First Preamp Stage (Debalancing Amplifier and Fixed-Gain Inverting Amplifier)

Due to the low output voltages from the microphone, we needed a very low-noise amplifier for the first amplification stage. To reduce the number of components, we preferred a dual op-amp solution as opposed to a discrete design. The Linear Technology LT1678 fit our requirements nicely. Unlike many low-noise op-amps, it can be powered by a single 3.3-volt supply. It has an input noise voltage density of 3.9nV/Hz throughout the audio spectrum. Assuming a bandwidth of 20 kHz, this results in an overall noise level of 551 nanovolts. The PGA has a maximum gain of 128, so if an audio signal is less than 1/128 the maximum level, the noise performance of all components is reduced equally. We can therefore think of the 1/128 max signal (0.141V/128=1.10 mV RMS) as a “critical point” for our analysis. The SNR in this case is

At this audio level, the ADC is still operating over its full range, so its SNR is 60 dB. Since the first-stage preamp’s SNR is greater than the ADC’s, the front end is not the limiting factor for the system SNR.

Second Preamp Stage (Programmable Gain Amplifier)

The final component in the front-end which could limit the system’s SNR is the programmable gain amplifier. We chose the TI PGA112 because it can be operated on a single 3.3-volt supply and because it offers gain steps which are powers of two. This significantly reduces the calculations required to rescale the input, since it can be accomplished with a simple shift operation. The PGA112 has a minimum gain of 1 (unity) and a max of 128, contributing 42 dB to the system’s dynamic range!

 With a 3.3-volt supply, the PGA112 has an input noise density of about 20 nV/Hz, yielding an audio-band noise level of 2.8µV. Assuming the same signal level as above (1.1mV), with a first-stage gain of 6.5, the PGA’s SNR is:

Since this is greater than the first-stage and ADC SNRs, the variable gain stage does not contribute appreciably to the overall system SNR.

Dynamic Range Discussion

Since the PGA’s gain doubles each step, the ADC’s SNR could be reduced by as much as 6dB if the input signal is just large enough to clip the ADC at the next gain step. Therefore the ADC’s SNR will be between 54 and 60 dB as long as the PGA can provide enough gain, that is, when the input is below 120 and above 78dB SPL. The system SNR can be calculated as the parallel sum of the individual components:

At the maximum audio level, 120dB SPL, the SNRs of stages 1 and 2 are very large, and the system SNR is approximately equal to SNRADC­, or 60 dB. At 78dB SPL, the system SNR can be calculated using the formula above:


At lower audio levels, all components are equally affected, so the system SNR degrades linearly with SPL. For example, a 68 dB SPL audio signal will have an SNR of 44 dB.

Suppose we choose 30dB as the minimum acceptable SNR, since this is about the limit of human perception and is more than enough to calculate an accurate sound pressure level. The system SNR is 30 when the initial SPL is 54 dB. This is about the level of a normal conversation at 1 meter away, much quieter than anything our meter is designed to measure. The resulting dynamic range of our system is 120-54=66dB. Note that if we determine that lower SNRs are acceptable, the resulting dynamic range increases.

II.               Design Specifics

Power/Preamp Stages

Figure 2 (below in Appendix A) shows the schematics for the device’s power regulation and conversion. The top schematic shows the phantom power generation circuit. Phantom power is an expression for the DC bias voltage applied to a condenser microphone, and is necessary for its operation. Most condenser microphones can operate over a range of phantom power voltages (typically in the range 15V – 50V), so we were able to generate these DC biases with a 9V battery and a “voltage doubling” circuit.

The voltage doubling circuit is built around the Maxim 1044 Switched Capacitor Voltage Coverter Chip and its associated circuitry. This chip uses “charge pump” technology to manipulate supply voltages. In the “voltage doubling” configuration, the circuit doubles any DC voltage applied to the V+ input at pin 5. Charge pump technology, explained simply, uses medium frequency (on the order of 10s of kilohertz) switching to alternatively charge a capacitor to the supply voltage and then connect the capacitor in series with this supply voltage to source twice the voltage to a load. Output capacitors (C2 and C3 above) are used to continue to source current to the load while the charge pump capacitor is being recharged. High enough switching frequencies ensure that small currents can be continuously sourced with little voltage sag during switching periods. Since condenser microphones rarely draw more than a few milliamps of current, this simple charge doubling circuit seemed the ideal choice for providing the necessary phantom power from a 9V battery.

The second schematic shown in Figure 2 shows the 1.6V generation stages used as “virtual grounds,” or DC bias levels, in the preamp stages discussed below. We used 2 of the 4 op-amps on a generic LM324 chip to provide isolated 1.6V supplies to both stages in the fixed gain preamp discussed below. The 3.3V regulated supply comes from a linear regulator on-board with the Silicon Industries microprocessor.

Figure 3 shows the fixed-gain preamp stages used to amplify the signal before it is sent to the programmable gain amplifier. This preamp is responsible for several functions: first, it must apply the necessary phantom power to the microphone terminals. Second, it must “debalance” the microphone signal—that is, convert the balanced microphone signal to a single-ended signal. Third, it must provide a 1.65V DC offset to the signal, since all of our amplifiers are single-supply and cannot amplify negative voltage swings. Fourth, it must provide some amount of fixed gain.

Phantom power was applied with a circuit based on a simple “Phantom Powering Circuit” shown on the Elliot Sound Products website ( Well-matched 6.8k resistors apply the phantom bias to the microphone terminals; we used 1% percent resistors and matched them with a voltmeter to ensure that any common mode noise present in the power supply was identically applied to the balanced signal. The balanced signal lines are then AC-coupled to the preamp input. 10V Zener diodes to ground in series with 10 ohm current-limiting resistors are used to prevent current spikes from reaching the op-amp input terminals; in particular, currents spikes are worst when the condenser mic is plugged in while phantom power is applied, because the coupling caps will discharge quickly and draw significant current until the plate voltages of the microphone are fully biased. The Zener diodes breakdown appropriately and will handle any of these current surges along with the 10 ohm current limiting resistors.

Shown next are the two LT1678 op-amps, which handle the remaining three requirements: they debalance the signal, apply voltage gain, and shift the signal by half-supply. The first stage is a standard difference amplifier utilizing a “virtual ground” of 1.65V described in the power regulation section above. This stage both “debalances” the signal and provides a DC shift according to the following basic op-amp analysis. If we define V1 as the signal from XLR pin 2, V2 as the signal from XLR pin 3, Vout as the output voltage, and Va as the voltage present at the input terminals, then

The output of this first stage is thus the difference between the inputs with a DC voltage shift of 1.6V. The second stage is a simple inverting op-amp configuration, giving a voltage of gain of 6.6 V/V, as required by the calculations in Section I above.           


The software running on the Silicon Labs F320 microcontroller was written in C using the Silicon Labs integrated development environment (IDE). Conceptually, the software is quite simple: the chip’s ADC acquires samples at a fixed sample rate of 44.1 kHz. These values are squared and summed over a length of time. After the specified number of samples are accumulated, the Analyze_Data() function is called. In this function, the max sample value and accumulator values are used to determine whether the PGA’s gain should be decreased if the signal was oversaturating the ADC, increased if it was significantly smaller than the ADC’s range, or remain the same. If no change is needed, the accumulation, which is proportional to the input signal power, is used to calculate the sound level in dB SPL by factoring in the gain of the PGA and converting to a logarithmic scale.

The F320 is based on the 8-bit 8051 architecture, which is not very conducive to digital signal processing. We were therefore unable to implement any digital filtering or other processing. Much of the time developing the code was spent optimizing for speed. For example, the 16-by-16-bit multiply provided by the compiler was not fast enough to be performed every ADC sample (44,100 times per second), so we had to use a multiply function written in assembly.

In order to avoid the complications of computing square roots and logarithms on-chip, we opted to use a lookup table instead. The table is a list of 16-bit unsigned integers in steps of 0.2 dB. Typically, humans can perceive a sound level change of about 1 decibel, so we decided that a 0.2 dB step size would give us a high level of precision while still keeping the lookup table small. The values were calculated in Matlab by the equation:

We normalize the 32-bit accumulator value to a 16-bit “mantissa” by right-shifting until it is less than 216. Then we find the closest value in the lookup table, and its index corresponds to the relative decibel level. Finally, this value is denormalized by adding 3 dB for every bit that the accumulator was shifted, subtracting 6 dB for every power of two of voltage gain, and adding a scale factor. This calculation is sped up significantly since we account for the scale factors after converting to the logarithmic scale.


To display the SPL, the microcontroller writes to a small Liquid Crystal Display (LCD) via a Universal Asynchronous Receiver/Transmitter (UART). This display method was chosen to minimize development time. The microcontroller manufacturer implemented the C standard input/output to interface with the UART, and the LCD has a serial backpack chip that translates serial commands from the micro into parallel commands for the LCD. All that was required to display data on the LCD was to connect three wires (power, ground, and signal) from the micro to the LCD and to add a few print statements in the micro code.  The LCD has two lines which can each display 16 characters. The first line displays the SPL and the second line displays the gain of the PGA.


Power/Preamp Stages

While our design to this point works well with relatively loud signal levels applied to the microphone, some design improvements to the preamp section would certainly improve the device’s performance over a wider dynamic range. Three things would be done next to optimize the device’s analog performance. First, an improved biasing scheme that builds gain into the difference amplifier to take advantage of the high common-mode rejection of an op-amp should be developed. Secondly, low-level noise sources should be troubleshot. Third, the sensitivity of the microphone should be tested (using some kind of SPL reference) so that the exact amount of fixed-gain needed in the preamp can be determined.     

The biasing scheme described above does well in providing an exact amount of DC bias to the signal, but limits what can be done with the difference amplifier. The use of a “virtual ground” in the first stage of the op-amp to provide offset becomes more complicated if gain is incorporated into the difference amplifier. While common mode noise is well-rejected if the gain is applied in the difference amplifier, the topology used in Figure 3 was simple and intuitive, and still achieved what we needed.

Another biasing scheme could use well-matched 1% resistors as voltage dividers to provide a mid-supply bias to both inputs to the difference amplifier. However, even 1% resistors will cause slightly different bias voltages to be applied to the balanced inputs, causing undesired (and unpredictable) DC offset in the amplifier output. In any case, relying on perfectly well-matched resistors is an impractical design decision for a product that might eventually be manufactured in large quantities.         

Noise levels on the order of 10 millivolts should be expected when using a breadboard and surface-mount adapters to prototype a design. However, these noise sources will drown out low-level signals from the microphone (think 50dB SPL), which are only several millivolts themselves. Certainly, accurate noise performance using the components we’ve selected should be evaluated after the circuits have been constructed on well-designed PCB’s. We continue to have an issue with oscillations present in the output when a small signal is presented to the amplifier inputs; this is one of the first problems we would need to isolate and eliminate with further design and testing.

Another important step that would need to be taken to finalize the preamp design would be to test the microphone sensitivity rating, so that the exact amount of fixed-gain could be determined. With the current 6.6V/V of fixed gained, we noticed some clipping with very high-level signals (when PGA gain is 1), which leads us to believe that there is actually too much fixed gain in the preamp section. Establishing an SPL reference—most likely using another SPL meter picking up a sine tone, for example—would allow us to determine the exact sensitivity of the microphone, allowing us to select an exact amount of preamp gain.


In hindsight, the microcontroller we used, the Silicon Labs C8051F320F, was not the best choice for this application. Since it is an 8-bit processor, the calculations required more clock cycles than they would have on a 16-bit chip. We were unable to implement any digital filtering, and in fact we needed to use an assembly function just to perform a single 16-by-16 bit multiply for every ADC sample (44,100 times per second). Any chip designed for DSP would have been a better choice, provided it had an onboard ADC that was at least 10 bits. Another possible solution would be to use an FPGA, which would allow for advanced processing such as weighting frequencies to emulate the frequency response of the human ear ("A-weighting").


Although our solution to display the SPL minimized development time, it’s the most expensive part of the design and could be cheaper. The LCD and its serial backpack cost about ten dollars each whereas the microcontroller costs about three dollars. To reduce this cost, a UART to HD44780 (LCD communication spec) interface could be developed and implemented on a Complex Programmable Logic Device (CPLD) or a small Field Programmable Gate Array (FPGA). This would eliminate the need for the serial backpack.

The LCD could be replaced with a line of light emitting diodes (LED) controlled by a CPLD or a small FPGA. Each lit up LED could signify 10 more dB in the SPL. This may or may not be acceptable depending on the precision required by the application, but it would cost much less.

Appendix A: Schematics





Figure 2 - Power Regulation/Conversion




Figure 3 - Fixed-gain Preamp




Figure 4 - PGA, Microcontroller and Display


Appendix B: Source Code



// SPLmeter_main.c


// ESE 498 Senior Design Project

// Keith Swaback, Matt Meshulam, Chris Reale





// Includes and definitions



#include "c8051F320.h"                 // SFR declarations

#include "math.h"

#include "stdio.h"


// 16-bit SFR Definitions for 'F32x

sfr16 TMR2RL   = 0xca;                 // Timer2 reload value

sfr16 TMR2     = 0xcc;                 // Timer2 counter

sfr16 ADC0     = 0xbd;                 // ADC0 result



#define SYSCLK       12000000          // SYSCLK frequency in Hz

#define SPI_CLOCK    250000

#define BAUDRATE     9600              // Baud rate of UART in bps


#define ADCOFFSET    -11          // Sample value of zero signal level

#define SAMPLERATE   44100        // ADC sample rate in Hz.


#define NUMSAMPS     2048              // Number of samples per window

#define CLIPTHRESH   499               // Amplitude to consider "clipping"


#define DBSCALE      400               // Calibration offset, in tenths of a dB


sbit LED = P2^2;                       // LED='1' means ON

sbit SW =  P2^0;                       // SW='0' means pressed



unsigned char gainbits = 0;

bit clipflag = 0;

unsigned long accumulator = 0;

unsigned int max = 0;

int dbdisplay = 0;



// Function PROTOTYPES



void SYSCLK_Init (void);

void PORT_Init (void);

void Timer2_Init(void);

void ADC0_Init(void);

void SPI0_Init (void);

void UART0_Init (void);

void Init_Device (void);


void Set_Gain(void);

void Delay(void);

void Analyze_Data(void);

unsigned long uint_mult ( unsigned int x, unsigned int y );

void display(int value);



// MAIN Routine



void main (void)





   putchar(0x7C);           // Set display to 9600baud


   AD0EN = 1;                          // enable ADC0

   while (1) {                         // spin forever





void Analyze_Data(void)


   unsigned char accshift = 0;   // # of bits to shift accumulator

   unsigned char ind = 0;        // Index into table


   // linear-to-dB lookup table, 0.2-dB increments.

   unsigned int dbTable[] = {65535,62585,59768,57078,54509,52056,49713,47475,



                       18049,17237,16461 };


   AD0EN = 0;                    // Stop conversions.


   if (max >= CLIPTHRESH)         // Decrease the gain


      if (gainbits > 0)







            clipflag = 1;  // If input clips when gain is all the way down



   else if (accumulator < 16000000 && gainbits < 7)

   {                                              // Increase the gain


         clipflag = 0;





      for (accshift=0; accumulator > 65535; accshift++)   // Make accumulator smaller.


            accumulator >>= 1;


      for (ind=0; dbTable[ind] > accumulator; ind++)      // Find closest db value.

  dbdisplay = DBSCALE + 30 * (accshift - 2*gainbits) - (2*ind);



         LED =~ LED;



   accumulator = 0;

   max = 0;




void Set_Gain (void)


   while (!NSSMD0);                    // Wait until the SPI is free, in case

                                       // it's already busy

   ESPI0 = 0;                          // Disable SPI interrupts

   NSSMD0 = 0;


   SPI0DAT = 0x2a;

   while (TXBMT != 1)                  // Wait until the command is moved into

   {                                   // the XMIT buffer


   SPI0DAT = gainbits << 4;

   while (TXBMT != 1)                  // Wait until the command is moved into

   {                                   // the XMIT buffer



   SPIF = 0;

   while (SPIF != 1)                   // Wait until the last byte of the

   {                                   // data reaches the Slave


   SPIF = 0;


   NSSMD0 = 1;                         // Disable the Slave





void Delay (void)


   unsigned int count;

   for (count = 30000; count > 0; count--);



void display(int value){

   int whole;

   int fraction;

   int gainnum;


   putchar(0xFE); //clear screen



   putchar(0xFE); //go to beginning of first line



   whole = value / 10;

   fraction = value % 10;


   printf("%i.%i dB SPL      ", whole, fraction);


   putchar(0xFE); //go to beginning of second line



   gainnum = 1 << gainbits;

   if(clipflag == 1) {

      printf("Gain: %i      X",gainnum);

   } else {

      printf("Gain: %i ",gainnum);








// This ISR averages 2048 samples then prints the result to the terminal.  The

// ISR is called after each ADC conversion which is triggered by Timer2.



void ADC0_ISR (void) interrupt 10


   static unsigned measurements = NUMSAMPS;      // Set sample counter

   signed int tempcalc1;

   unsigned int tempcalc2;

   unsigned long tempcalc3;


   AD0INT = 0;                           // clear ADC0 conv. complete flag


   tempcalc1 = (signed int)ADC0 - ADCOFFSET;

   tempcalc2 = abs(tempcalc1);


   if (tempcalc2 > max) {                // keep track of largest sample each window

      max = tempcalc2;



   tempcalc3 = uint_mult(tempcalc2, tempcalc2); // call assembly code for 16x16 mult.

   accumulator += tempcalc3;



   if(measurements == 0)


         measurements = NUMSAMPS;               // reset sample counter

         Analyze_Data();                        // run the processing/display code.





// Initialization Subroutines



void Init_Device (void)


   SYSCLK_Init ();

   PORT_Init ();

   Timer2_Init ();

   SPI0_Init ();

   ADC0_Init ();

   UART0_Init ();


   EA = 1;                               // Enable global interrupts




// SYSCLK_Init



void SYSCLK_Init (void)


   PCA0MD &= ~0x40;                    // WDTE = 0 (clear watchdog timer

                                       // enable)

   OSCICN = 0x83;                      // configure internal oscillator for

                                       // 12MHz

   RSTSRC = 0x04;                      // enable missing clock detector




// PORT_Init


// Configure the Crossbar and GPIO ports.


// P0.0  -  SCK  (SPI0), Push-Pull,  Digital

// P0.1  -  MISO (SPI0), Open-Drain, Digital

// P0.2  -  MOSI (SPI0), Push-Pull,  Digital

// P0.3  -  NSS  (SPI0), Push-Pull,  Digital


// P0.7 - ADC0 negative input (VREF)

// P0.6 - ADC0 positive input

// P2.2 - LED (push-pull)




void PORT_Init (void)


   XBR0    = 0x03;                    // Enable SPI and UART on the crossbar

   XBR1    = 0x40;                    // Enable crossbar and weak pull-ups

   P0MDOUT = 0x1D;                    // Set UTX, SCK, MOSI, and NSS to push-pull

   P2MDOUT = 0x04;                    // enable LED as a push-pull output


   P1SKIP  = 0x40;

   P1MDIN  = ~0x40;


   P0SKIP  = 0x80;

   P0MDIN  = ~0x80;                      // set P0.7 (VREF) as analog in




// Timer2_Init


// Configure Timer2 to 16-bit auto-reload and generate an interrupt at 100uS

// intervals.  Timer 2 overflow automatically triggers ADC0 conversion.




void Timer2_Init (void)


   TMR2CN  = 0x00;                     // Stop Timer2; Clear TF2;

                                       // use SYSCLK as timebase, 16-bit

                                       // auto-reload

   CKCON  |= 0x10;                     // select SYSCLK for timer 2 source

   TMR2RL  = 65535 - (SYSCLK / SAMPLERATE); // init reload value for 10uS

   TMR2    = 0xffff;                   // set to reload immediately

   TR2     = 1;                        // start Timer2




// SPI0_Init


// Configures SPI0 to use 4-wire Single Master mode. The SPI timing is

// configured for Mode 1,1 (data centered on first edge of clock phase and

// SCK line low in idle state).


void SPI0_Init()


   SPI0CFG   = 0x70;//0x40;            // Enable the SPI as a Master

                                       // CKPHA = '1', CKPOL = '1'

   SPI0CN    = 0x0D;                   // 4-wire Single Master, SPI enabled


   // SPI clock frequency equation from the datasheet

   SPI0CKR   = (SYSCLK/(2*SPI_CLOCK))-1;




// ADC0_Init

// Configures ADC0 to make single-ended analog measurements on pin P1.6



void ADC0_Init (void)


   ADC0CN = 0x02;                      // ADC0 disabled, normal tracking,

                                       // conversion triggered on TMR2 overflow


   REF0CN = 0x06;                      // Enable buffer and external VREF


   AMX0P = 0x06;                       // ADC0 positive input = P1.6

   AMX0N = 0x1E;                       // ADC0 neg input = VREF


   ADC0CF = ((SYSCLK/3000000)-1)<<3;   // set SAR clock to 3MHz


   ADC0CF |= 0x00;                     // right-justify results


   EIE1 |= 0x08;                       // enable ADC0 conversion complete int.






// UART0_Init

// Configure the UART0 using Timer1, for <BAUDRATE> and 8-N-1.



void UART0_Init (void)


   SCON0 = 0x00;                       // SCON0: 8-bit variable bit rate

                                       //        level of STOP bit is ignored

                                       //        RX enabled

                                       //        ninth bits are zeros

                                       //        clear RI0 and TI0 bits

   if (SYSCLK/BAUDRATE/2/256 < 1) {

      TH1 = -(SYSCLK/BAUDRATE/2);

      CKCON &= ~0x0B;                  // T1M = 1; SCA1:0 = xx

      CKCON |=  0x08;

   } else if (SYSCLK/BAUDRATE/2/256 < 4) {

      TH1 = -(SYSCLK/BAUDRATE/2/4);

      CKCON &= ~0x0B;                  // T1M = 0; SCA1:0 = 01                 

      CKCON |=  0x01;

   } else if (SYSCLK/BAUDRATE/2/256 < 12) {

      TH1 = -(SYSCLK/BAUDRATE/2/12);

      CKCON &= ~0x0B;                  // T1M = 0; SCA1:0 = 00

   } else {

      TH1 = -(SYSCLK/BAUDRATE/2/48);

      CKCON &= ~0x0B;                  // T1M = 0; SCA1:0 = 10

      CKCON |=  0x02;



   TL1 = TH1;                          // init Timer1

   TMOD &= ~0xf0;                      // TMOD: timer 1 in 8-bit autoreload

   TMOD |=  0x20;                      

   TR1 = 1;                            // START Timer1

   TI0 = 1;                            // Indicate TX0 ready




// End Of File