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

  • Set ADC
  • Clear control registers
  • Set 8-Bit mode (left aligned data)
  • Set VCC as a Reference
  • Set channel
  • Set ADC prescaler
  • Enable ADC
  • ADC Auto Trigger Enable
  • ADC interrupt enable
  • ADC Auto Trigger Source Selection
  • Set Timer1
  • Reset Timer/Counter1 control registers and Interrupt Mask Register
  • Set Clear Timer on Compare Match (CTC) Mode
  • Set Timer/Counter1 prescaler
  • Set upper count value
  • 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;
    	// Returned value
    	return (exp);

    Next post on same subject

    One Comment

    1. […] Understand PlainDAC functions which allow analog signals sampling and PlainFFT functions which allow Fast Fourier […]

    Leave a Reply

    You must be logged in to post a comment.