0

I'm trying to use micros() to measure the time it takes to execute an analogRead() cycle, but am having some issues. Originally, I just printed out the measured time for the analogRead() right after doing the subtraction, but I wanted to change that to printing out all of the time values for the 100 loops at once. When I print the start times, end times, and values both in the if statement (right after calculating them) and at the end, the values printed are the same. I'm not sure if they're accurate because they fluctuate a lot within those 100 tests. For example, some of these measured times consecutively are 216, 97, 153, 34, 89, 145, 27. I put the ADC in freerun mode, so I figured it could be normal for that. However, when I don't print the start times, end times, or values immediately after calculating them and instead try to print them in the else if statement, it gives a stream of values that are all either 407 or 408. If I print "Test" instead of the values right after calculating them, it still gives the (I'm assuming) correct values that vary a lot, not the 407s. If, instead of printing them in the if statement, I print them just in the general loop (currently commented out) and the else if statement, they are still the varying maybe-correct values. I assumed that even if the values aren't being printed, they're still being stored in the array correctly. I don't understand why printing something unrelated right after storing the values would change them. Any ideas?

Thanks

    int sensorPin = A1;    // select the input pin for the potentiometer
    int ledPin = 13;      // select the pin for the LED
    int sensorValue[1000];  // variable to store the value coming from the sensor
    int ind = 0;
    int starts[1000];
    int ends[1000];
    int vals[1000];
    int stopprint = 0;
    int i = 0;
    int countto = 100;

    void setup() {
      // declare the ledPin as an OUTPUT:
      Serial.begin(57600);
      analogReadResolution(12);
      pinMode(ledPin, OUTPUT);
      Serial.println("ready to begin");
        analogReadSetup(A1);
      for (i = 0; i < 1000; i++) {
        starts[i] = 0;
        ends[i] = 0;
        vals[i] = 0;
      }
    }

    void loop() {
      // read the value from the sensor:
      if (ind < countto) {
      starts[ind] = micros();
    //  delay(50);
      sensorValue[ind] = analogRead(sensorPin);
      ends[ind] = micros();
      vals[ind] = ends[ind] - starts[ind];
    //  Serial.println("Test");
        Serial.print(ind);
        Serial.print('\t');
        Serial.print(starts[ind]);
        Serial.print('\t');
        Serial.print(ends[ind]);
        Serial.print('\t');   
        Serial.print(vals[ind]);
        Serial.print('\t');
        Serial.print('|');
        Serial.print('\t');
      Serial.print('\n');
      Serial.print('\r');
      ind++;
      }
      else if (stopprint == 0) {
        Serial.println("~~~~~~~~~~~~~~~~~~~~~~~~~");
        for (int j = 0; j < countto; j++) {
    //        Serial.println(j);
            Serial.print(starts[j]);
            Serial.print('\t');
            Serial.print(ends[j]);
            Serial.print('\t');
            Serial.print(vals[j]);
            Serial.print('\n');
            Serial.print('\r');
            stopprint = 1;
        }
      }
      else if (stopprint == 1) {
        while(1);
      }
    //  Serial.print(ind-1);
    //  Serial.print('\t');
    //  Serial.print(starts[ind-1]);
    //  Serial.print('\t');
    //  Serial.print(ends[ind-1]);
    //  Serial.print('\t');
    //  Serial.print(vals[ind-1]);
    //  Serial.print('\n');
    //  Serial.print('\r');
   }

Also, I'm using the Arduino Zero if it makes a difference.

2

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.

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.

  • I tried changing it to your format and nothing changed. Thanks for the idea though. If you have any more ideas about what could be causing it I'd really appreciate it! – Alexandra Jul 11 '16 at 18:06
  • If you understand ARM assembly, you could disassemble the program to see the code generated by the compiler. That's what I would do on an AVR to try to understand that kind of issue, but I cannot read ARM assembly. – Edgar Bonet Jul 11 '16 at 18:29
  • That's more or less what I've been doing for about an hour now, but I haven't really found anything that would explain it – Alexandra Jul 11 '16 at 18:31
  • @Alexandra: I've updated the answer with some experiments done on my Uno. Maybe some of these results could be extrapolated to the Zero. – Edgar Bonet Jul 12 '16 at 9:40
  • Thanks for the information. I'm now just trying to get the time down from what the analogRead() timing is. The datasheet says it should take ~100 microseconds per read (on the Arduino Zero at least which I'm using), but by driving pins high to measure it, it's still taking about 400 microseconds. I've put the ADC in freerun mode and tried changing the prescaler but it doesn't even get close to 100 microseconds. I saw posts of people trying to get it below 100 us previously, but I'm not even reaching that baseline and I'm not sure why. Any ideas? (also posted on other comment, sorry) – Alexandra Jul 15 '16 at 15:33
0

If you have access to a digital scope, try bracketing your code with changes to the state of an output pin. print() has a lot of moving parts, some (like buffer size, interrupt implementation) will vary with various Arduinos as others have noted. I am mostly using ARM Cortex M4 on Teensy 3.2. If I want to time something really precisely I will drive an I/O pin (with digitalWrite()) at the beginning and end of what I want to time. This has super fast execution. You could use more than one pin to see two things at once. Then, look at them on an oscilloscope. Trigger on the starting edge of the most important signal. Adjust the time scale to encompass the active pin level time. With a digital scope you can set persistence to infinite and then you can see the variation on the trailing edge of the pulse. You can move the digitalWrite points around in your program until you see what is causing the variation. millis() and micros() are 32-bit values so on an 8-bit AVR that is multiple machine cycles to access. A single digitWrite is usually one machine cycle so it is quick even on a slowly-clocked 8-bit AVR. Once you find the place where most of the variation occurs you at least have a quantified smoking gun to tell you what details to excavate further.

  • A call to digitalWrite() on an AVR takes anywhere from 78 to 108 cycles, depending on the pin number and the value written. That's why, for time-critical tasks, people resort to direct port access: something like PORTB |= _BV(5) compiles into a single machine instruction and executes in two cycles. – Edgar Bonet Jul 13 '16 at 8:53
  • On the other hand, for precise timing, you can set the 16-bit timer to run at F_CPU and read it directly, which only takes 4 cycles. If you want to be accurate to the cycle, you will have to look at the generated assembly to see what exactly you have between the timer reads. – Edgar Bonet Jul 13 '16 at 8:53
  • Thanks for the information. I'm now just trying to get the time down from what the analogRead() timing is. The datasheet says it should take ~100 microseconds per read (on the Arduino Zero at least which I'm using), but by driving pins high to measure it, it's still taking about 400 microseconds. I've put the ADC in freerun mode and tried changing the prescaler but it doesn't even get close to 100 microseconds. I saw posts of people trying to get it below 100 us previously, but I'm not even reaching that baseline and I'm not sure why. Any ideas? – Alexandra Jul 15 '16 at 15:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.