What is the use case for using dual slow interleaved mode to implement analogRead()?
One of the first things I tried when I got my Spark Core was to connect a TMP36 analog temperature sensor like the one in the Spark Maker Kit. I didn’t use a capacitor.
There are 0.78 volts between the A0 and GND pins according to my digital multimeter. That’s a reading of 28 degrees Celsius (0.78 × 100 - 50) which is warm but conceivable.
But from analogRead() I get the following values:
692 micros 65
332 micros 34
331 micros 34
342 micros 32
331 micros 33
329 micros 32
331 micros 32
331 micros 33
330 micros 36
331 micros 33
… which is only about 0.27 volts (value × 3.3 / 4095). To be precise I guess I should scale by the actual reference voltage (between the 3.3* and GND pins) but regardless these values are way off.
Here’s the code I used to get them:
#include "application.h"
uint16_t ADC_ConvertedValue;
void setup()
{
Serial.begin(9600);
while (!Serial.available());
}
void loop()
{
long start = micros();
ADC_ConvertedValue = analogRead(A0);
long end = micros();
Serial.print(ADC_ConvertedValue);
Serial.print(" micros ");
Serial.println(end - start);
delay(1000);
}
I tried calling the Standard Peripherals Library directly to see what’s going on. analogRead() uses dual slow interleaved mode to take 20 samples (one every 14 ADC clock cycles or 1.17 microseconds) and returns the average.
See ADC modes and their applications section 2.3 and Reference Manual section 11.9.4
Here’s an example of 20 samples:
923
932
910
963
862
889
793
829
716
756
644
680
580
612
519
546
465
490
431
449
micros 26
Here’s the code I used to get them:
#include "application.h"
#define ADC1_DR_Address ((uint32_t) 0x4001244c)
uint16_t ADC_ConvertedValue[20];
void setup()
{
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
Serial.begin(9600);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) &ADC_ConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 10;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_SlowInterl;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_7Cycles5);
ADC_InitStructure.ADC_Mode = ADC_Mode_SlowInterl;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC2, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC2, ADC_Channel_0, 1, ADC_SampleTime_7Cycles5);
ADC_ExternalTrigConvCmd(ADC2, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
ADC_Cmd(ADC2, ENABLE);
ADC_ResetCalibration(ADC2);
while (ADC_GetResetCalibrationStatus(ADC2));
ADC_StartCalibration(ADC2);
while (ADC_GetCalibrationStatus(ADC2));
while (!Serial.available());
long start = micros();
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (!DMA_GetFlagStatus(DMA1_FLAG_TC1));
DMA_ClearFlag(DMA1_FLAG_TC1);
long end = micros();
for (int i = 0; i < 20; i++) Serial.println(ADC_ConvertedValue[i]);
Serial.print("micros ");
Serial.println(end - start);
}
I tried increasing the sampling time. See How to get the best ADC accuracy sections 2.2.6 and 3.4.3 and these topics:
I figure you might as well use the 13Cycles5 sampling time since dual slow interleaved mode takes samples every 14 ADC clock cycles either way so it doesn’t much change the analogRead() time? analogRead() waits for the last sample, the difference between 7Cycles5 and 13Cycles5 is 0.5 microseconds (6 cycles / 12 MHz) each sample starts at the same time regardless so the last sample doesn’t start for 22.17 microseconds (19 samples × 14 cycles / 12 MHz)
But that didn’t improve the values much. I guess sampling every 1.17 microseconds (14 cycles) is just too fast for this source? (TMP36 without capacitor)
Here’s an example of 20 samples at 13Cycles5:
948
953
899
928
840
870
781
809
727
753
683
704
648
663
619
634
599
609
583
590
micros 26
But if I drop the dual slow interleaved mode I get the following values:
938 micros 4
932 micros 3
931 micros 4
931 micros 3
933 micros 4
931 micros 4
933 micros 3
931 micros 4
932 micros 3
932 micros 3
… which is about 0.75 volts much closer to my multimeter (0.78 volts)
Here’s the code I used to get them:
#include "application.h"
#define ADC1_DR_Address ((uint32_t) 0x4001244c)
uint16_t ADC_ConvertedValue;
void setup()
{
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
Serial.begin(9600);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) &ADC_ConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_7Cycles5);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
while (!Serial.available());
}
void loop()
{
long start = micros();
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (!DMA_GetFlagStatus(DMA1_FLAG_TC1));
DMA_ClearFlag(DMA1_FLAG_TC1);
long end = micros();
Serial.print(ADC_ConvertedValue);
Serial.print(" micros ");
Serial.println(end - start);
delay(1000);
}
So I’m wondering what’s the use case for using dual slow interleaved mode? Because it doesn’t work with the TMP36. (I guess it does work if I add a capacitor but what’s the point?)
Also if I increase the sampling time to 239Cycles5 (the maximum) I get the following values:
961 micros 23
960 micros 23
960 micros 23
961 micros 23
960 micros 23
960 micros 23
960 micros 23
960 micros 23
960 micros 23
959 micros 23
… which is about 0.77 volts even closer to my multimeter (0.78 volts) and still faster than the current analogRead() implementation (23 microseconds for 239Cycles5 vs. 26 microseconds for 20 dual slow interleaved mode samples and > 30 microseconds for analogRead())