Skip to main content
Arduino

Return to Answer

+ experiment on an Uno.
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

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:

  1. 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 arrays starts[], ends[] and vals[] are not really needed and optimized them away. When PRINT_WHILE_READING is not defined, I can see the "store in RAM" instructions in the generated assembly.

  2. 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:

  1. 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 arrays starts[], ends[] and vals[] are not really needed and optimized them away. When PRINT_WHILE_READING is not defined, I can see the "store in RAM" instructions in the generated assembly.

  2. 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.

Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

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.

AltStyle によって変換されたページ (->オリジナル) /