Friday, February 6, 2015

Messing around an Electret microphone

Hello there .. again. It has been a while, so today we will be messing around an electret microphone. The mission for now is : Make a simple voice recorder. Of course we won't be getting studio quality (obviously), and that is .. for several reasons..
  1. ADC on Atmega16(the one that I got around) gives tops 15 kilosamples per second, and it's just 10 bits. While just CD quality requires 16 bit, 44100 kilosamples.
  2. Except for getting ADC values, we also need to send it somehow to the computer
  3. ... well you got the idea, whatever, moving on
BUT, we can still mess around, see the best we can do with minimal effort possible.

The action plan ..

So, the name electret comes from two words - electrostatic and magnet (wiki). It basically has two conducting plates, one of which is serving as a membrane, and when mechanical waves (i.e. sound) hits the membrane plate, it causes change of capacity, which will cause a change in voltage. Mostly all this type of microphones have a FET inside for gain, so all you would need to do is bias it with a resistor. For my case I used a 1k resistor.

So, we biased it, then what ? Then will just hook it onto one of Atmega16's differential input pins (Port A 3 - in my case) through a capacitor (~50 nF), and the negative pin of the differential input will be connected to the ground. Also, we could really use some gain.
Well, 10x sounds tempting, let's see. Ok, we have the idea of inputs, now sampling rate and resolution.. AVR's ADC offers us up to 10 bit resolution. Sending 10 bits to the computer .. what a drag.. if it would have been 8 bits, we could send it over to the computer using UART, seems like the most effortless way. Sure, we will loose those two bits .. but whatever, too much effort, not worth it. As for sampling rate, the important information when speaking is contained around 1-4 khz, so I'd guess 10,000 samples per second sounds pretty good. Assemble the schematic on your breadboard, and lets move on to the code.

Code

Once again, what we decided:
  • 10 kilosmaples per second
  • 8 bit resolution (ADLAR :)
  • UART feedback to the computer
  • Differential input channels
I guess the Free running mode for ADC was made just for this purposes. The clock for my MCU is 16 mhz. Divide it on 10,000, you'll get 1600. So if we divide our clock (clock_io) on that magical number - 1600, we'll get our sampling clock. 1600 = 8 * 200. Thus, if we choose the clock divider to be 1 : 8, we can fit in the 8 bit timer counter 0. Configure it on CTC mode to count from 0 to 199 (200 ticks), on 16 mhz / 8 frequency, the sampling clock is done. Now how to trigger the ADC on this event ? Well, several ways for this. The one that comes to mind - just make it start the conversion from the interrupt of Timer 0 Compare match - but that's a bad idea, we have free running mode made for this. So by configuring the ADC, we'll just say it to look up for Timer0 compare match flag, and when it gets set, start converting. When done, it'll call the interrupt handler for ADC finished, and we'll just send the byte of information out from the interrupt. CRITICAL: in order for ADC to start conversion, the selected interrupt flag should go from 0 to 1. So if you don't clear it, it will get stuck. If you enable hardware interrupt for current interrupt flag, it will clear the flag itself, but however, if you don't, just write logical one to the bit spot of that interrupt flag.
So, the function to start the Timer counter 0, pretty simple actually.
void init_timer0 (void) {
  /* 
   * 8 bit timer counter 0
   * Mode of operation: CTC
   * Divider: 8 / OCR - 199 (200 divider)
   * Interrupt frequency: 10000 hertz
   */
  TCCR0 = _BV(WGM01) | _BV(CS01);
  // TIMSK |= _BV(OCIE0);
  OCR0 = 199;
}
As for ADC initialization code, we set ADLAR (left adjust, for 8 bit resolution), highest possible clock speed, ADATE (automatic update trigger .. or whatever it was), and MUXes, for selected pins, at selected 10x gain. Also in SFIOR, we select the trigger for ADC to be Timer0 compare match.
void adc_init (uint8_t muxx) {
  SFIOR |= _BV(ADTS1) | _BV(ADTS0); // TIMER0 compare match
  ADMUX =  _BV(REFS0) | _BV(ADLAR)| muxx;
  ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADIE) | _BV(ADATE)
    /* | _BV(ADPS1) */;
}
The interrupt handler in our design had two jobs - send data over UART, and clear the Timer0 compare match flag.
ISR (ADC_vect) {
  // send converted ADC data over uart
  UDR = ADCH;
  // clear TIMER0 compare match flag
  TIFR |= _BV(OCF0);
}
And the main routine - just call all the init functions, and sit back, and relax.

Processing the audio

Well, good, we opened the serial terminal with logging, and got bunch of crap that seems to be changing when we talk. What next ? What we get is not just useless code, we get code, representing our analog signal, or otherwise called raw data. I found that Audacity has a feature of importing raw data.

The resulting wave looked like this.
In case of any questions, comments are welcome. And all the code can be found here. Goodbye!

No comments:

Post a Comment