readVcc function using new UNO R4 RA4M1 ADC

Hi everyone,

Fairly new to Arduino (month or two), very new to the forum as an active user (first post). I have been using the code below (picked it up somewhere (unfortunately, cannot recall original source, have seen it come back in several places) along the many forum posts and/or YouTube tutorials, for reading analog sensors on Arduino UNO R3 (8-bit ATmega328P) and added my own comments to better understand what is going on. This code is a function, used to read the actual supply voltage Vcc of the Arduino (e.g. ~5 VDC) by setting the corresponding bits in the ADMUX register, to read the internal (bandgap) reference voltage of 1.1 V (perform a single ADC conversion with AVcc as the reference) and then reverse-calculating what the actual Vcc should be (provided an exact and reproducible 1.1 V int. ref., can be determined/verified with an external DMM). The actual Vcc is returned by this function and can then be used to obtain more accurate analog readings between 0 - 5 V (corresponding to 0 - 1023).

I am working on a program which will exceed the 32 kB / 2 kB limitations of the UNO R3 but hoorah, Arduino just released the R4, with a lot more storage and memory. Unfortunately, this means that the code below, which is written for different AVR boards, will not work on the new R4 boards, using the Renesas RA4M1 32-bit ARM Cortex-M4 instead of the 8-bit ATmega328 on the R3. The ADC also got an upgrade and can now be used with (default) 10-bit, 12-bit, or 14-bit accuracy.

My question is: does anyone have enough knowledge of the new RA4M1 chip (ADC, registers, etc.) or maybe has already done this exercise, to update or expand the code below so it will also work on the new UNO R4 boards? Because this is well beyond my capabilities at the moment. Thanks in advance!
< I have found an entire UNO R4 forum section under hardware, but thought it was best placed here. If not, please advise or move accordingly. >

Kind regards, fdw

float intVREF = 1.100; // for more accuracy: verify actual value with DMM
unsigned long intVREFbin = intVREF * 1000 * 1024.0;  // Arduino internal 1.1V ref. (+/- 0.1V)  * 1000 mV/V * 1024 values

long readVcc()
{ // function to read actual supply voltage Vcc (in mV) of Arduino
  long result;
  // if/else statements for setting correct bits in ADMUX (ADC multiplexer) register, based on which Arduino (which ATmega chip) is being used
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  // setting ADMUX bits to read internal 1.1 V ref against AVcc (REFS1 = 0, REFS0 = 1, MUX3:0 = 1110)

  delay(2);             // Wait 2 ms for Vref to settle after change
  ADCSRA |= _BV(ADSC);  // Write ADSC bit to 1 in "ADC Control and Status Register A" for starting single ADC conversion

  while (bit_is_set(ADCSRA, ADSC)); // when ADC conversion is over, ADSC bit will go back to 0
  // ADC generates 10-bit result (containing measurement of 1.1V int. ref.) in ADC data registers: ADCH (high) and ADCL (low)
  result = ADCL;                    // if result is left adjusted & requires <=8-bit precision, ADCH is sufficient, otherwise: ADCL read first (important!), then ADCH
  result |= ADCH << 8;              // ADCL, then ADCH (to ensure registers hold data of same conversion), 8-place bit shift to left: obtain full-precision 10 bit result
  result = intVREFbin / result;     // Calculate Vcc (in mV); intVREFbin = ~1100 mV * 1024
  return result;                    // long result (Vcc in mV) is returned as a result of readVcc function as soon as conversion is over (ADSC = 0 again)
}

Welcome to the forum.

Using the internal reference to calculate VCC is not the same as more accurate analog readings.

When a voltage of a battery is measured, then a voltage divider and the internal reference can be used. By using the average of a number of samples, the electronic noise is removed and the result is surprisingly accurate.

When reading a LDR or NTC or an other analog sensor, and the circuit is powered by the VCC, then use the VCC as a reference. The value of VCC can go up and down, but the ADC always reads the same value. That is called "ratiometric". By using the average, the result is also here very good.

Calculating VCC has no place in this. Using the average of samples has little effect and too much accuracy gets lost along the way.

Can you take a few steps back ?
What is your project ? Maybe we can find the right board for your project.

It looks like (there is code. I haven't tried it, cause my v4 hasn't arrived.)

   float vcc = analogReference();

will return the current ADC Reference voltage (as a floating point number.) Unless you've changed it, the default reference is Vcc...

1 Like

Hi, thank you for your quick reply.

I am exploring Arduino as a multiparameter controller, where you can connect multiple (water) sensors (e.g. pH, conductivity, temperature), turn on/off small dosing pumps accordingly, display measurements (OLED display) and store them internally (microSD card), together with a timestamp (RTC), or have it display its measurements live over serial (e.g. via PuTTY). Now with the new R4 WiFi, I also thought about playing around with remote access to the device and its measurement data. All these peripherals and associated libraries, together with specific code for user-interface calibration menus, per (type of) sensor, of course increase the size of a sketch significantly, and I am approaching/crossing the limits of UNO R3. I know e.g. MEGA has a lot more capacity, but I like the small form factor (and lower cost) of the UNO and the way it fits into specific casing, and now with the R4 WiFi and extra storage/memory, seems ideal.

I have also used it as a voltage meter (cheap multimeter), where the absolute value of the voltage and Vcc is important and where the readVcc function has been very useful to get closer to the "real" voltage of e.g. a battery, irrespective of the Arduino power supply (9V adapter, USB, battery). I would not like to lose this functionality when upgrading to an UNO R4. I believe you are suggesting to use a voltage divider on the analog input signal to lower the voltage to max. e.g. 1.1V, set the analogReference for ADC to e.g. 1V1 internal (to set 0-1023 to 1.1 V instead of 5.0V) and use this as a measurement instead. This however requires additional hardware & steps, you need to know/measure the values of the voltage divider resistors accurately, and you need to know the exact value of the 1.1 bandgap reference voltage (which is stable, but can be anything between 1.0 and 1.2 V) - and the way to measure this 1.1V, is exactly making use of the readVcc function I am asking about (I do not know another way).

I guess you could calibrate the measurement value of an analog sensor relative to the output of analogRead instead of to a specific voltage, if Arduino and sensor supply voltage is indeed the same, and the sensor output is also relative to the supply voltage, meaning you get the same analogRead output every time. I do use Vcc as a reference, but want to include the exact Vcc in the calculation of converting an analogRead result to a voltage (float voltage = ((rawADC + 0.5 ) / 1024.0 * 5.xxx;). A lot of the cheap sensors (e.g. from DFRobot) come with calibration curves (linear, quadratic, cubic,...) supplied by the manufacturer, where the parameter of interest (y) is a function of measured voltage (x) on the analog input. It would therefore be convenient to just work with the "correct" measured voltage to immediately obtain the "correct" sensor value with just the one calibration conversion equation. If I store e.g. 2.500 V as the calibration value corresponding to a pH of 7.0 in EEPROM, it would be nice to know that this 2.500 V is always correct and not depending on Vcc. Or does "ratiometric" mean just that, that as long as the pH circuit is powered by the same Vcc as the ADC, it will always return the same measured V no matter what and I can just use 5.0V in all conversions?

Hi, thank you for your quick reply.

Thank you for the code, I will have a look but it seems quite extensive with not that many comments so it will take some time to work my way through it, if I can make sense out of it at all.

The way I understand it, that is not how analogReference works. I believe it is used to set/choose the analogReference that is used by the ADC as the full scale reference. You use this function like:

analogReference(DEFAULT);

//or

analogReference(INTERNAL1V1);

but I do not think that you can use it to assign a (exactly measured?) value to a float.
<EDIT:> Sorry for not looking at the code in more detail before my first answer, it seems indeed that there is an "analogReference" function written there that does return values and can be assigned to a float. I was just confused with the standard analogReference function that is already there in Arduino. Wondering if this would result in any conflicts, using the same name?

Can you give an example of a sensor that outputs an analog voltage that would require to know what the VCC is ?

The UNO R4 would be a good board for your project, but there are other boards.

Thank you for your input and making me think. It could be that I am just overcomplicating things with the analog sensors and that I am focusing too much on the absolute value of Vcc, while it is in fact always a ratio of the full range and hence, Vcc cancels itself out in the equations in that case. I was just not sure if these boards, such as a pH sensor, will always result in the same (relative) ADC result, as long as Vcc of the pH module and Arduino are the same, or if there may be some offset or "absolute voltage" factors at play (I do not really have a background in electronics, so some of the basic things to the general users might seem like voodoo to me).

Apart from the analog sensors, I do believe the readVcc function has its use/importance when trying to read absolute voltages in the range of 0 - 5V, using Vcc as the reference voltage. It is also the only way I have found thus far to obtain an accurate reading of the internal 1.1V reference without extra equipment (I do not have super-accurate DMM, oscilloscope, external reference voltage, etc.)

I'm not sure anymore if you use the 5V to measure the 1.1V or use the 1.1V to measure the 5V :dizzy_face: In both cases, you lose accuracy. So you need a good multimeter :dollar:

I can't find a schematic of the pH sensor :slightly_frowning_face: Does it output an absolute voltage ? then it is an execption.

About your original question: No, there is no way to measure the VCC via an internal route (yet) and we don't know how the Renesas processor on the UNO R4 behaves in a Arduino environment.

The ATmega328P is well known. The ATmega328P can measure a battery with a voltage divider with very high resistor values, so the current through the resistors is much less than the leakage current of the battery.

Did you know that the Renesas processor can output only 4mA instead of the 8mA that Arduino writes about ?

The Raspberry Pi Pico is a 3.3V board without the weird pin things of the ESP32. At least the pH sensor runs also at 3.3V. If you know Mbed, then this might be a good option. The Arduino software layer is on top of Mbed for the Raspberry Pi Pico.

1 Like

Thank you for the Pi/Mbed suggestions. For now, I would like to stick with the things I got myself familiar with (Arduino/IDE), took already enough time and sweat to get to this point :slight_smile: .

I understand the 1.1 / 5V 5 / 1.1V is confusing, it got me mixed up at first too. The way I have understood this approach to work, and have been using it up to now, is as follows:

  • get a new Arduino board with stable (more stable than Vcc), but unknown 1.1 +- 0.1V internal ref.
  • use readVcc function, which performs a single ADC conversion/measurement of the internal 1.1V with Vcc as reference, then compares this 1.1V measured value to the theoretical 1.100V (variable changed by the user) to calculate the original Vcc, which is finally returned by the function (in mV).
  • I keep running this readVcc function while adjusting the 1.100 parameter between 1.000 and 1.200 until the Vcc returned by the readVcc function exactly matches the Vcc that I measure with a (good-but-not-professional) DMM on the 5V Arduino pin. The "exact" modified 1.100V value is then stored in EEPROM so the Arduino can use this each time the readVcc function is used in the future.
  • before performing a sensor analogRead (with Vcc as reference), readVcc is run to get the actual Vcc at that time (using the board's exact 1.1 internal V value from EEPROM.
  • when converting a sensor analogRead to a voltage, instead of using 5.0V as found in the standard equations, I use Vcc returned by the readVcc function.
  • I then use calibration values stored in EEPROM (e.g. pH 7 and corresponding cal. voltage) to convert the measured voltage to the sensor value, e.g. pH.

More information about DFRobot pH probe or other, similar type (PH4502C) I have used. Please note that the pH probe itself is very high impedance, and has a small mV (neg. and pos.) output. It is the IC supplied with the probe which boosts the voltage and has an offset screw to put the midpoint of pH (7) to around 2.5 V, meaning that on the Arduino you will measure e.g. 2.0V for pH 10, 2.5V for pH 7, 3.0 V for pH 4. But my electronics knowledge is too limited to know if the voltage output would be absolute in any way, or just ratiometric in relation to Vcc, as you claim.

I did not know about the limited 4 mA of the new processor (thank you), and guess this will have implications at some point for everyone? Even in very simple setups such as blinking an LED; people will need a higher value resistor to limit current to 4 mA instead of 20 mA of previous Arduino generations.

Thanks, the output of the pH module is a voltage directly amplified from the pH probe. That is indeed a voltage. So you need to know the VCC :grimacing:

In these situations, Adafruit sometimes connects the AREF pin to the 3.3V output. Thus using the accuracy of the 3.3V voltage regulator as an external reference.

Tip: There are many problems with a pH sensor. If a aquarium is grounded or if there is a other sensor in the water, then the pH sensor does no longer work well. In that case, a galvanic isolated pH module might help: https://www.tindie.com/products/rezahussain/dormant-labs-ph-module-v2/

I could not find a review of the multimeter (I keep calling it a "multimeter" :stuck_out_tongue_winking_eye:) at EEVblog, only a discussion about the inner circuit: https://www.eevblog.com/forum/reviews/amprobe-am-530-a-peek-under-the-hood/msg163567/

Conclusion: Just go for the UNO R4 board, measure the VCC with a voltage divider, and see how far you get ?

1 Like

I thought that too, but analog.cpp has two versions (overloaded, a feature of C++) of analogReference(), so I infer that if you pass it an argument, it sets the reference, and if you don't, it returns the set reference voltage. (I mean, what's the point of having a clearly defined API if you don't violate it by adding additional overloads?)

cores/arduino/Arduino.h:103:float analogReference();
cores/arduino/api/Common.h:100:void analogReference(uint8_t mode);
cores/arduino/analog.cpp:529:void analogReference(uint8_t mode) {
cores/arduino/analog.cpp:558:float analogReference() {
cores/arduino/analog.cpp:572:          analogReference(AR_INTERNAL);
cores/arduino/analog.cpp:578:          analogReference(AR_DEFAULT);
cores/arduino/main.cpp:108:   analogReference();
1 Like

I have read parts of the datasheet (p. 95), documentation and the code of the analogReference function you provided (thank you) and tried to understand it. I took the original code and added comments myself to try and understand). It seems the internal reference of the RA4M1 is not 1.1V like with the ATmega328P, but 1.43V (min. 1.36V - max. 1.50V) (Table 2.50 in Renesas RA4M1 Group Datasheet). I do not know where the 1.5, 2.0, 2.5V are coming from in the switch cases. A bit stuck in the aref calculation without knowing what AVCC_MULTIPLY_FACTOR is. Also: if Vref is set to internal 1.43V before the analogRead, won't analogRead on Vcc pin just max out the ADC regardless of the Vcc (e.g. 16383 for 14-bit ADC)? And how would aref then return a correct (averaged) measurement of Vcc?

static float aref = 0;
float analogReference() {
// function that returns a float containing the V value of ref. used for ADC
    switch (adc.cfg_extend.adc_vref_control) {
      case ADC_VREF_CONTROL_1_5V_OUTPUT:
        return 1.5; // function returns 1.5 if selected VREF = 1.5V (how, where?)
      case ADC_VREF_CONTROL_2_0V_OUTPUT:
        return 2.0; // function returns 2.0 if selected VREF = 2.0V (how, where?)
      case ADC_VREF_CONTROL_2_5V_OUTPUT:
        return 2.5; // function returns 2.5 if selected VREF = 2.5V (how, where?)
      case ADC_VREF_CONTROL_VREFH0_AVSS0:
        // the user must know the voltage he applies from outside (THIS COMMENT WAS ALREADY THERE, NOT SURE WHAT IT IS ABOUT)
        return NAN;
      default: // default scenario of the switch/case
        #if defined(AVCC_MEASURE_PIN)
        if (aref == 0) { // is still equal to zero when you start the sum loop
          analogReference(AR_INTERNAL); // classic analogReference function: set ADC ref. to internal V ref. = 1.43 V (Datasheet Table 2.50)
          delayMicroseconds(5); // wait a short time to let new VREF settle
          for (int i = 0; i < 10; i++) { // measure aref for 10 times and add result to the previous measurements
            aref += analogRead(AVCC_MEASURE_PIN) * 1.43f  * AVCC_MULTIPLY_FACTOR / (1 << _analogRequestedReadResolution);
            // aref (V) = result of analogRead on Vcc pin * 1.43Vref * (factor?) and divide by resolution, e.g. 16383 for 14-bit ADC
          }
          aref = aref / 10; // divide by 10 to obtain average of 10 readings that were added before
          analogReference(AR_DEFAULT); // set ADC ref. V back to default
        }
        return aref; // return averaged measured ref. V
        #else
        return NAN;
        #endif
    }
}

Still to be tested when I receive my R4.

Good to have it confirmed that my quest for the correct Vcc is justified. Not sure if the 3.3V is as stable as the 1.1V (I do not remember what I read about it) and how it compares to the stability of 5V - which is probably the worst, being the reason people are looking for accurate ways to measure it. But it is worth the try.

I have indeed learned that multiple sensors in the same liquid affect each other, so galvanic isolation is a must if they are connected to the same Arduino.

I will have to wait for my UNO R4 to arrive and get to know it better, but I found the discussion thus far already very interesting.

1 Like

The R4 Minima (at least) has two voltage dividers connected between Vcc and Gnd (it's got lots of extra pins; why not.)


This nominally makes it "less tricky" to measure VCC than on an ATmega chip, and probably implies that "measure the Vreference" isn't one of the features of the RA4m1 chip (the AVR method of reading Vcc consists of reading the internal bandgap while using Vcc as the ADC reference, and working backward. The RA4 can just set one of the (more) internal references to higher than the expected divided Vcc, and read the voltage directly.)

2 Likes

Does anyone know the correct macro definition for the Renesas RA4M1 chip (something like
__ AVR_ATmega328P__), so I can expand the existing code with an additional #elif to make it work with UNO R4 Minima / WiFi?

 #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__)   etc. etc.
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 
etc. etc.

--->
 #elif defined(__RA4M1__) // WHAT IS THE CORRECT MACRO DEFINITION HERE?
    result = analogReference() * 1000;
    return result;  // return result in mV

Today, I got my hands on both an R4 Minima and a WiFi for the first time. Did the analogReference() test immediately and got the following results:

VccMinima = analogReference(); --> VccMinima = 4.890
                              check with DMM = 4.72

VccWiFi   = analogReference(); --> VccWiFi   = 4.719
                              check with DMM = 4.67

Seems like a large discrepancy between the built-in function and the actual value. Actual value also quite a bit lower than what I measure on several of my Uno R3 boards (more like 4.995 - 5.02 V there). Seems like my problem has not been solved yet. Maybe if the problem is related to inaccurate internal reference, used for analogReference() calculation, it can be reverse-calculated like for the Uno R3 (provided the used internal reference is, stable - although inacurrate).

Looks like you could use #elif defined(ARDUINO_ARCH_RENESAS)
Or _RENESAS_RA_ if you want to be more general (but the function is dependent on the Arduino board hardware, so the arduino-specific symbol is probably a better choice.)

1 Like

Indeed. I didn't check the math in the function; perhaps it is simply incorrect.

1 Like

Modified readVcc function I am currently using between Uno R3 and R4 boards below. Accuracy / reproducibility of readVcc still seems a bit better for R3 (spot on, within accuracy of ADC) than R4 (10 - 20 mV variation). I only used 9V or USB for the power supply and tested as-is, I did not do extensive testing with variable power supplies etc. to see how everything would perform in a draining-battery scenario for example.

For R3, intVREF, stored in EEPROM location 0, represents the exact value of the 1.1 V bandgap reference (after calibration with DMM).
For R4, intVREF, stored in EEPROM location 0, represents a correction factor of around 1 which is used for the (more or less stable/fixed?) offset of the (unknown) internal reference of the RA4M1 (also after calibration with DMM).

A big thanks to everyone for their valuable input!

long readVcc()
{ // function to read actual supply voltage Vcc (in mV) of Arduino - adapted to also work with new UNO R4
  long result;
  // setting correct bits in ADMUX register to read internal 1.1 V ref against AVcc (based on which AVR chip, e.g. Uno R3: REFS1 = 0, REFS0 = 1, MUX3:0 = 1110),
  // or directly read Vcc if newer Uno R4 (RA4M1 chip)
  #if defined(ARDUINO_ARCH_AVR)  // for AVR boards (such as Arduino Uno R3)
  
    #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__)  // if e.g. (very) old Arduino, or Nano, or Uno R3 is used
      ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // if e.g. Arduino Leonardo, or Mega is used
      ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) // if using different kinds of ATtiny 
      ADMUX = _BV(MUX5) | _BV(MUX0);
    #elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) // if using different kinds of ATtiny
      ADMUX = _BV(MUX3) | _BV(MUX2);
    #else
      result = 0;                         // return result Vcc of 0 if chip is not supported by this function
      return result;
    #endif  

      delay(2);                           // Wait 2 ms for Vref to settle after change
      ADCSRA |= _BV(ADSC);                // Write ADSC bit to 1 in "ADC Control and Status Register A" for starting single ADC conversion

      while (bit_is_set(ADCSRA, ADSC));   // when ADC conversion is over, ADSC bit will go back to 0
      // ADC generates 10-bit result (containing measurement of 1.1V int. ref.) in ADC data registers: ADCH (high) and ADCL (low)
      result = ADCL;                      // if result is left adjusted & requires <=8-bit precision, ADCH is sufficient, otherwise: ADCL read first (important!), then ADCH
      result |= ADCH << 8;                // ADCL, then ADCH (to ensure registers hold data of same conversion), 8-place bit shift to left: obtain full-precision 10 bit result
      result = intVREFbin / result;       // Calculate Vcc (in mV); intVREFbin = ~1100 mV * 1024
      return result;                      // long result (Vcc in mV) is returned as a result of readVcc function as soon as conversion is over (ADSC = 0 again)
  
  #elif defined(ARDUINO_ARCH_RENESAS)     // for RA4M1 boards (such as Arduino Uno R4)
    float Vcc = analogReference();        // analogReference() reads Vcc, based on internal reference, and returns value as a float
    result = Vcc * intVREF * 1000;        //  intVREF * 1000;   // result is corrected for incorrect internal reference (fixed factor) and converted to mV
    return result;                        // long result (Vcc in mV) is returned as a result of readVccUnoR4 function
  
  #else
      result = 0;                         // return result Vcc of 0 if chip is not supported by this function
      return result;

  #endif
}

To clarify...
The processor allows setting the GPIO Output Current to different options. For the GPIO pins that are used on the Uno R4, mode [Middle drive*2 (VCC = 3.0 to 5.5 V)] is limited to Source 4mA IOH, and Sink 8mA IOL. One would have to assume the Arduino team selected this "Middle drive" I/O option in the firmware.

This still has to comply with the "chip combined total" of 60mA IOH and IOL.

1 Like