WMXZ-EU / microSoundRecorder

Environmental Sound Recorder for Teensy 3.6
36 stars 5 forks source link

Missing sample conversion from unsigned to signed prevents use of high DC bias microphones #12

Closed marklewis3000 closed 4 years ago

marklewis3000 commented 4 years ago

The sample conversion problem occurs in myApp.cpp at line 435, in the assignment. Original code: // multiplex data int16_t *tmp = tempBuffer; for(int ii=0;ii<AUDIO_BLOCK_SAMPLES;ii++) for(int jj=0; jj<NCH; jj++) *tmp++ = *data[jj]++; // release queues Above, data[] is declared as a Signed int16, but it actually contains an unsigned 12 bits audio sample from the ADC. 12 bits unsigned means the values can range from 0 - 4095, and this is fine for microphones where the DC bias is low so a data value is never above 2047. For example a mic with bias of 0.67 v the peak to peak data will be under 2 times the bias, so the range is 0 to 1.34 volts. Even this would be extreme because the peak would not in practice get that high. This is equivalent to half the value of the ADC reference voltage (2.53v INTERNAL) setup in the audio library. In this case you won't be affected by the error. But if you use microphone with better sensitivity and higher DC bias there is a problem. For example the Adafruit product number 1713, MAX9814 has a DC offset of 1.25V and 1v peak-to-peak. For this microphone, a quiet level produces 1.25 V, which is close to the max level to a 0.67 bias microphone, which is about midpoint on the 0 - 2.53 v range of the ADC setting. Therefore, even a moderate sound will produce a sample above 2048, and now we have a negative number! Examining the resulting sample .wav file in Audacity shows a very clipped waveform that looks like icicles and sounds awful! The fix is to do a proper conversion from the 12 bit sample to the signed 16-bit number that goes into the .wav file. And now my .wav files sound and look good. Here is my solution, showing each step, but of course it could be done in 1 or 2 lines of code. for(int ii=0;ii<AUDIO_BLOCK_SAMPLES;ii++) for(int jj=0; jj<NCH; jj++){ //*tmp++ = *data[jj]++; int16_t rawValue = *data[jj]++; // Convert the 12-bit unsigned count read from the ADC to // a signed 16 bit value for the .wav file. // This depends on the DC bias for the mic (1.25 for AGC, 0.67 for MEMs, see datasheets) // and the ADC reference voltage (2.56 or 3.3; INTERNAL or DEFAULT respectively) // which is set by calling analogReference() in file audio_mods.h uint32_t val_u32 = rawValue & 0xfff; // get the useful 12 bits 0x0fff (0 - 4095) val_u32 = val_u32 - 2000; // subtract DC bias of AGC mic (4096*1.25/2.56=2000) //val_u32 = val_u32 - 1072; // subtract DC bias of MEMs mic (4096*0.67/2.56=1072) //val_u32 *= 75; // The MEMs mic also needs a big boost rawValue = (uint16_t)( val_u32 & 0xffff); // take 16 bits from result, use as a uint16_t *tmp++ = rawValue; //Serial.printf("%10d \r\n", rawValue); }

DD4WH commented 4 years ago

If I have understood correctly, the ADC of the Teensy 3.6 as implemented in the microSoundRecorder uses the internal reference, which is 1.2 Volts. So your input voltage range is 0 to 1.2 Volts. With a bias of 0.6 Volts applied, you can use signals from -0.6 to +0.6 Volts. From my understanding you would have to adjust the gain of the MAX9814 so that signals stay within this range and the bias is at 0.6 Volts (see also the Teensy Audio GUI: https://www.pjrc.com/teensy/gui/?info=AudioInputAnalog).

marklewis3000 commented 4 years ago

The Teensy Audio library initializes the reference voltage to analogReference(INTERNAL); in audio/input_adc.cpp Likewise, microAudioRecorder intializes it the same in audio_mods.h. That location around line 80 I found works if I want to change to a different reference (like DEFAULT 3.3v) but I didn't have to. You have a good point, for other Teensy's maybe its 1.2 volts reference on the ADC, however for newer ones, Teensy 3.6, INTERNAL is 2.53 volts, see https://www.pjrc.com/teensy/adc.html

I successfully get good .wav files from Adafruit MAX9814 microphone without any external circuit components, just connecting directly to Vcc, Gnd, and Out to A0 (pin 14), after the slight software change I showed above. Now even with an input voltage range from this mic of 1.25v +/- 1.0v, so that's 0.25 to 2.25v, a much higher dynamic range. Again the software changes I made are

1) correctly convert the 12 bit sample to a signed value 2) subtract off the ADC counts equivalent to the DC bias of whichever mic you use 3) store the new value

DD4WH commented 4 years ago

the page you are referencing is for Teensy 1.0 & 2.0, T3.6 uses 1.2 Volts internally, as far as I know.

marklewis3000 commented 4 years ago

Sorry. You are correct, it does appear Teensy 3.6 analogReference(INTERNAL) is 1.2 Volts. Thank you. I was not running the code I thought I was. However, if I change line 80 in audio_mods.h to analogReference(DEFAULT); // ADC range 0 to 3.3 volts instead of INTERNAL, then I am able to record the MAX9814 mic directly into pin 14 without external voltage shifting or attenuation. I guess this could be a configuration parameter in some future version for high DC offset microphones if you like.

marklewis3000 commented 4 years ago

I did not realize that removing the DC bias for the mic input is actually being done in the audio library at the bottom of input_adc.cpp. I was attempting to do it myself, and that completely trash the sound input. However, what may be of value of what I learned, is that analogReference can be changed in audio_mods.h to accommodate the use of microphones like the MAX9814 that have a higher DC bias. So an option in the config.h for the ADC range options could be useful.