Faster analogRead() for Arduino Uno R4 Wifi

Hello

I am using an Arduino Uno R4 Wifi, that reads on pin A3 the voltage of a photodiode. I would like the measurement frequency to be as high as possible.
The code looks like this :

while(1) { delayMicroseconds(50); int x=analogRead(A3); if (x<threshold) { break; } }

I had to put a delay to avoid getting nonsense input, that is much higher than the rise and fall times of the photodiode (less than 1uS each), which makes me think that I should try to write analogRead() directly in the registers.
How can I do that? Can it be done while the rest of the sketch stays in Arduino language?

Thanks

Start by reading the processor's data sheet to understand how its ADC works and what limitations there may be. Then read the source code for analogRead() and determine which steps are causing the slow read rate you're complaining about. Determine which (if any) of those steps may be eliminated. Then write your code.

1 Like

Tell us more about this "nonsense input", and post a wiring diagram and links to the photodiode and associated components.

Very likely, the actual problem is faulty input circuit design, such as source impedance too high.

1 Like

Thanks for your answers.

I have a LED connected to analog output 9 and gnd, and a photodiode connected to A3 and gnd (in reverse) with a 1MOhm resistor in between A3 and gnd.
I agree, the design is probably the cause of the problem. I know that I am supposed to consider impedance mismatch, but so far this setup has been working with "relatively slow" measurement frequencies, so that was fine. But indeed I may have to rethink it (it has actually more diodes and photodiodes, and that's annoying to change, but I get it).
Still, I wantly to rewrite analogRead() into "register language" if that makes sense, to give it a try, in case it could help.

Here you can see how changing the delay does change the results, and why I was okay with this quick and dirty setup :

void setup() { 
  Serial.begin(115200);
  pinMode(9,OUTPUT); pinMode(A3,INPUT); digitalWrite(9, HIGH); delay(1000);
}

int t[10]={};

void loop() {
  for (int i=0;i<10;i++) {                          t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(1);    t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(5);    t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(10);   t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(50);   t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(100);  t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(500);  t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(1000); t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  for (int i=0;i<10;i++) { delayMicroseconds(5000); t[i]=analogRead(A3); } for (int i=0;i<10;i++) { Serial.print(t[i]); Serial.print("  "); } Serial.println(); delay(10);
  digitalWrite(9, LOW);
  delay(1000000);
}

63 67 70 72 73 73 73 74 74 74
53 62 67 69 70 71 71 72 72 72
53 61 65 67 68 68 68 68 68 68
52 60 63 64 65 65 65 65 65 65
53 55 56 56 56 56 56 56 56 56
53 53 53 53 53 53 53 53 53 53
53 52 52 52 52 52 52 52 52 52
53 52 52 53 52 52 52 53 52 52
53 53 53 53 52 52 52 53 53 52

The results tend to show that the correct value is reached only with a delay in between measurements of 50uS minimum, if not more.
Again, not sure if registers could help, but I would be interested to try it, if anyone has a magic formula :slightly_smiling_face:

For fast response, a photodiode must be reverse biased. Also, remember it behaves like a current source. So consider using a transimpedance amplifier as a buffer stage.

Sorry, I meant "A3 and +3.3V". The resistor still goes between A3 and gnd.

The voltage looks pretty close to proportional to the level of received light (with this annoying delay of course), so I don't know if I would need it. And then I would have to add such a circuit to every photodiode, hmm... I will have a look at it.
Thanks for the advice

The ADC (analog-to-digital converter) which is required to read an analog value doesnt work like a digital pin. It takes a bit of time for the ADC to measure the input. As per this reference it takes about 100 microseconds on ATmega based boards.

Funnily enough me trying to figure out how fast the ADC on an R4 minima is took me to this post.

So, to fully answer your question, you are not getting any faster than whatever the speed of the ADC is on the chip. Now there is also a caveat to this, that being that as per the official datasheet for the RA4M1 chip, the ADC only supports 12-bit and 14-bit operation (Table 1.8), however, by default, the analogRead() method is 10-bit by default for compatibility reasons with ATmega chips, so this might cause extra delay depending on implementation. It can be changed however, so that would be the first avenue to look into.

The documentation for the ADC starts at page 87 of the datasheet. As per Table 2.42, under the best circumstances in high-speed conversion mode, the ADC can take a measurament every 0.7 microseconds, however that is assuming the input impedance is less than 0.3 kΩ.
Reading further, the low power times are from around 2 microseconds to 21 microseconds, again depending on input impedance, reference voltage, etc.

Note that all those values are for the case where the ADC operates on its maximum clock (64 MHz), so if the ADC is running slower on the arduino, then you should expect slower measuring speed. The ADC takes samples of the input on that clock frequency, so keep in mind that the worse the conditions, the more samples it needs to give you an accurate reading, so the more time it takes.

My guess is that the ADC is in low power by default on the arduino, so writing your own analogRead() and setting the registers for high speed is a viable solution. Your best bet is first calculating your input impedance, looking up the apropriate table in the datasheet going from there.

My personal take on this, its easier to write code around this than dealing with it. First and foremost, if you write your own analogRead(), you are bound to that specific chip, so it wont work on other arduinos not based on the RA4M1. IMHO not worth the hassle.

I use one of the following three workarounds:

  • If I dont care about doing nothing while waiting for the ADC, I just slap a delay(1) before it and am done. 1 millisecond is enough for any ADC you might come across.
  • If I do care about doing nothing for one milisecond and dont care about optimizing code for speed on all chips. I just use millis() to make sure the ADC has enough time to do its thing.
  • Lastly if everything needs to be perfect, I count program cycles and after estimating the lenght of loop() I try to read only in certain cycles based on the program lenght. I just abuse the overflow of uint8.
  • It would be only here that I would consider needing faster read speed on the ADC.

Anyway hope you enjoyed my essay and hope this answers your question

what are you trying to measure?
why do you need measure a rapidly changing light intensity?

1 Like

And of course if you generate lots of data through a really fast read , what are you going to do with it - that should slow it up a bit …

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.