Fast Signal Sampling (Part 1)
Part 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 11, 12
In an early post, I described an electronic interface which adjusts the level electret microphones to the level of Arduino’s ADC. We need now to accurately sample the signal, using enough samples to get a nice representation of the captured sounds. The human ear is capable of detecting sounds up to 16 kHz. But this upper limit degrades over the time, and there are few situations where such frequencies are audible. So that we may arbitrary decide to measure frequencies up to 10 kHz. Applying the Nyquist sampling theorem, we finally need to sample the wave signal at 20 kHz, thus the interval between to consecutive measurements which is t=1/F = 50 µs! That is quite fast, but feasible!
Instead of using the quite trivial delayMicroseconds() function, I decided to use a refined method which is fully described (but requires some concentration) in the ATMega Datasheet
“…a conversion can be triggered automatically by various sources. Auto Triggering is enabled by setting the ADC Auto Trigger Enable bit, ADATE in ADCSRA. The trigger source is selected by setting the ADC Trigger Select bits, ADTS in ADCSRB (See description of the ADTS bits for a list of the trigger sources). When a positive edge occurs on the selected trigger signal, the ADC prescaler is reset and a conversion is started. This provides a method of starting conversions at fixed intervals.”
So that, initializing this acquisition engine will consist in setting both the ADC and the Timer1 parameters, according to the logical order
Apart from the absolute logic of this sequence of settings, two parameters may require some optimiszation: ADC prescaler and Timer/Counter1 prescaler. Depending upon the time interval, we may want to optimize the amount of time spent on conversion (Signal-to-noise ratio increases as the square root of the time interval), and the way the Timer/Counter1 operates.
Here is the code for the automatic calculation of the ADC prescaler
// Compute adc prescaler value automatically uint8_t adcPrescaler = 128; // Allowed values are ranging from 2^1 to 2^7 // Decrease prescaler down to 16; for some reasons, decreasing below 16 drives to unaccurate timing while ((adcPrescaler > uint16_t((timInterval << 4) / 13)) && (adcPrescaler > 16)) { adcPrescaler = (adcPrescaler >> 1); } // Set prescaler uint8_t adcPrescalerExponent = exponent(2, adcPrescaler); ADCSRA |= (adcPrescalerExponent >> 1);
And here is the (easier) code for the automatic calculation of the Timer/Coounter1 prescaler
// Compute prescaler if (timInterval < 4096) timPrescaler = 1; else if (timInterval < 32768) timPrescaler = 8; else timPrescaler = 64; uint8_t timPrescalerExponent = exponent(8, timPrescaler); // set prescaler TCCR1B |= (timPrescalerExponent + 0x01);
I am hesitant to show the exponent() function which is not the most elegant function I wrote so far :-[ . Anyway, it's helpful and it works fine 😉
uint8_t exponent(uint8_t mantissa, uint16_t x) { // Compute the exponent of a powered value // Same as exponent = Ln(x)/Ln(mantissa) but quick and dirty uint8_t exp = 0; uint16_t result = 1; while (x != result) { result *= mantissa; exp++; } // Returned value return (exp); }
[…] Understand PlainDAC functions which allow analog signals sampling and PlainFFT functions which allow Fast Fourier […]