Update: I tried the following experiment on my Uno:
#define PRINT_WHILE_READING
#define READ_COUNT 100
void setup() {
unsigned int starts[READ_COUNT];
unsigned int ends[READ_COUNT];
unsigned int vals[READ_COUNT];
Serial.begin(57600);
Serial.println("Ready to begin");
// Take the readings.
for (int i = 0; i < READ_COUNT; i++) {
starts[i] = micros();
analogRead(A1);
ends[i] = micros();
vals[i] = ends[i] - starts[i];
#if defined(PRINT_WHILE_READING)
Serial.print(i);
Serial.print('\t');
Serial.print(starts[i]);
Serial.print('\t');
Serial.print(ends[i]);
Serial.print('\t');
Serial.println(vals[i]);
#endif
}
#if !defined(PRINT_WHILE_READING)
// Print them now.
Serial.println("All data taken");
for (int i = 0; i < READ_COUNT; i++) {
Serial.print(i);
Serial.print('\t');
Serial.print(starts[i]);
Serial.print('\t');
Serial.print(ends[i]);
Serial.print('\t');
Serial.println(vals[i]);
}
#endif
}
void loop() {}
If PRINT_WHILE_READING
is defined as above, I get times that fluctuate
between 108 and 116 μs. If I comment out the line
#define PRINT_WHILE_READING
, I get mostly 116 μs and,
occasionally, 124 μs. In both cases, the first reading takes longer
(208 and 212 μs respectively).
The fact that the first reading takes longer is explained in the
datasheet: the ADC does some initializations in that first reading. All
measured times are multiples of 4 μs because that is the resolution
of micros()
.
Now, trying to understand the differences between the two cases:
With
PRINT_WHILE_READING
the times are shorter on average. This is because the numbers are not stored in RAM, as the compiler figured out the arraysstarts[]
,ends[]
andvals[]
are not really needed and optimized them away. WhenPRINT_WHILE_READING
is not defined, I can see the "store in RAM" instructions in the generated assembly.With
PRINT_WHILE_READING
the numbers fluctuate more. I guess this is due to the printing through the serial port taking a variable amount of time depending on the numbers being printed, and thus creating timing inconsistencies.
The last point deserves some explanation. The ADC is clocked by a prescaler that divides the frequency of the main clock by 128. This gives a clock period of 8 μs. When an ADC conversion is started, the ADC waits for the next rising edge of its clock, which can take anywhere between 0 and 8 μs, then it does the conversion in 13 cycles of its clock. This means the whole process can take anywhere between 104 and 112 μs depending on the exact time the conversion is started relative to the phase of the prescaler. If the conversions are started at irregular intervals, you expect to see roughly 8 μs of fluctuation in the measured times.
All these observations have been done on an Uno, which is based on an AVR clocked at 16 MHz. I know the Zero is quite different. You will have to look at the datasheet of the ATSAMD21G18 to see whether anything of the above can be extrapolated to the Zero.
Update: I tried the following experiment on my Uno:
#define PRINT_WHILE_READING
#define READ_COUNT 100
void setup() {
unsigned int starts[READ_COUNT];
unsigned int ends[READ_COUNT];
unsigned int vals[READ_COUNT];
Serial.begin(57600);
Serial.println("Ready to begin");
// Take the readings.
for (int i = 0; i < READ_COUNT; i++) {
starts[i] = micros();
analogRead(A1);
ends[i] = micros();
vals[i] = ends[i] - starts[i];
#if defined(PRINT_WHILE_READING)
Serial.print(i);
Serial.print('\t');
Serial.print(starts[i]);
Serial.print('\t');
Serial.print(ends[i]);
Serial.print('\t');
Serial.println(vals[i]);
#endif
}
#if !defined(PRINT_WHILE_READING)
// Print them now.
Serial.println("All data taken");
for (int i = 0; i < READ_COUNT; i++) {
Serial.print(i);
Serial.print('\t');
Serial.print(starts[i]);
Serial.print('\t');
Serial.print(ends[i]);
Serial.print('\t');
Serial.println(vals[i]);
}
#endif
}
void loop() {}
If PRINT_WHILE_READING
is defined as above, I get times that fluctuate
between 108 and 116 μs. If I comment out the line
#define PRINT_WHILE_READING
, I get mostly 116 μs and,
occasionally, 124 μs. In both cases, the first reading takes longer
(208 and 212 μs respectively).
The fact that the first reading takes longer is explained in the
datasheet: the ADC does some initializations in that first reading. All
measured times are multiples of 4 μs because that is the resolution
of micros()
.
Now, trying to understand the differences between the two cases:
With
PRINT_WHILE_READING
the times are shorter on average. This is because the numbers are not stored in RAM, as the compiler figured out the arraysstarts[]
,ends[]
andvals[]
are not really needed and optimized them away. WhenPRINT_WHILE_READING
is not defined, I can see the "store in RAM" instructions in the generated assembly.With
PRINT_WHILE_READING
the numbers fluctuate more. I guess this is due to the printing through the serial port taking a variable amount of time depending on the numbers being printed, and thus creating timing inconsistencies.
The last point deserves some explanation. The ADC is clocked by a prescaler that divides the frequency of the main clock by 128. This gives a clock period of 8 μs. When an ADC conversion is started, the ADC waits for the next rising edge of its clock, which can take anywhere between 0 and 8 μs, then it does the conversion in 13 cycles of its clock. This means the whole process can take anywhere between 104 and 112 μs depending on the exact time the conversion is started relative to the phase of the prescaler. If the conversions are started at irregular intervals, you expect to see roughly 8 μs of fluctuation in the measured times.
All these observations have been done on an Uno, which is based on an AVR clocked at 16 MHz. I know the Zero is quite different. You will have to look at the datasheet of the ATSAMD21G18 to see whether anything of the above can be extrapolated to the Zero.
Not sure if it is related to your problem but... if I were to try such a measurement, I would use local variables, like
unsigned int start = micros();
int value = analogRead(sensorPin);
unsigned int end = micros();
sensorValue[ind] = value;
starts[ind] = start;
ends[ind] = end;
vals[ind] = end - start;
This way you would be measuring the time it takes to call both
analogRead()
and micros()
, and very little more. If you do something
like
sensorValue[ind] = analogRead(sensorPin);
then you are also measuring the time taken by the pointer arithmetic and the memory access.
I generally program AVR-based Arduinos. These have a load-store architecture, where saving to a local variable costs essentially nothing, as the variable is typically assigned a CPU register by the compiler. Saving to a global takes more time because of the RAM access.
I have no experience with ARM-based Arduinos, like the Zero, but I believe it's also a load-store architecture, thus the same considerations should probably apply.