An (M2) alternative to CALIBADC for measuring the PICaxe Supply Voltage (Vdd).

AllyCat

Senior Member
Hi,

CALIBADC10 is a useful facility which allows the PICaxe to calculate its own supply rail (Vdd) to moderate accuracy, without using any additional pins. Positive features are that it requires only a very small amount of program codespace, it works over the entire range of supply voltages (down to 1.5 volts for a 14/20M2 with its "brownout" disabled), and is available on all PICAXEs (some earlier versions may need a different calculation).

However, it works "backwards" by using the (unknown) supply voltage as a "reference" to the ADC and then "measures" the (known) FVR1024 voltage. This may be rather confusing for a novice, but more significantly, the "reversing" process requires numerical division, which is one of PICaxe Basic's weak points. Also, the typical ADC value returned is only around 200 - 300 (even when using CALIBADC10) so the "best" that can be expected is a resolution of 0.4% (at around Vdd = 4.1 volts), which becomes worse at the extremes of voltage. Thus, it cannot directly calculate Vdd accurately to two decimal places, and maybe only to within +/- 30 mV above 5 volts.

Therefore, why not use the FVR as the ADC reference voltage and measure the Vdd against it, thus avoiding the division process (and also using the full range of the ADC)? Of course Vdd is higher than the reference voltage, but most PICaxe chips contain a voltage divider, aka the "DAC". As a Digital-Analogue Converter Output Module it's rather "rubbish" (IMHO), because it has only 32 levels (5 bits), it has a high (and varying) output impedance and with most M2s it must share a pin with the serial (programming) output.

However, as a Programmable Potential divider (POT), the DAC can be very useful when operating entirely inside the chip. Typically its top would be connected to the supply rail and it delivers an appropriate fraction directly to various internal modules, such as the ADC. The PICaxe READADC{10} command is configured to measure the analogue voltage only on Port pins, but PICaxe Basic has also a READDAC{10} command, which does exactly what we want.

Another issue is that "officially" the lowest FVR voltage which may be used for the ADC reference is 2 volts, so cannot be used for the lowest possible Vdds. But such voltages are rather unusual in PICaxe projects, and in practice, for most purposes, the ADC appears to work quite well from the 1 volt FVR (but the DAC does not). I've discussed these potential issues in more detail in this recent thread.

So here is a sample test program to measure and display Vdd in volts, to a genuine resolution of two decimal places. It uses entirely "normal" PICaxe commands (i.e. no POKESFRs), but of course it cannot be used/tested in the PE simulator:

Code:
#picaxe 20m2		; Or any other M2
#no_data
#terminal 4800

symbol CALVDD10 = 41942		; "Fractional" multiplier to calibrate Vdd in 10s of mV (nominally 41942)
main:
do
	sertxd(cr,lf,"Vdd= ")
	call ReadVdd10		; Measure the PICaxe supply rail to a resolution of 10mV into w1
	call show2dp		; Report the value. (Or use BINTOASCII w1,units,tenths,hundredths )
	sertxd(" Volts")
	pause 3000
loop

ReadVdd10:			; Read the supply rail in hundredths of a volt into w1
	fvrsetup fvr1024		; Select the 1 volt Fixed Voltage Reference 
	dacsetup $80		; Enable the DAC referenced to Vdd (NOT connected to output pin)
	daclevel 5		; = Vdd * 5 / 32 , must be less than the FVR2048 reference (Vdd < 6v) 
	adcconfig 3		; Use the FVR as reference voltage for READADC (and READDAC)
	readdac10 w1		; Read the DAC voltage (nominally Vdd * 5 / 32)
	w1 = w1 ** CALVDD10	; Calibrate to 10 mV units
	return
show2dp:			; Display the value in w1 with two decimal places
	b1 = w1 / 100	 	; Integer part
	b2 = w1 // 100		; Fractional part ( 0 - 99)
	b3 = b2 // 10		; Hundredths
	b2 = b2 / 10		; Tenths
	sertxd(#b1,".",#b2,#b3)	; Show as a decimal value
	return
This particular version uses FVR1024 as the reference voltage for the ADC, but FVR2048 can be used if the operational supply rail is never expected to fall below 2.2 volts. With an absolute maximum of 6 volts (Vdd) applied to the top of the DAC resistor chain, the voltage supplied to the ADC input should not exceed 1 volt, so "DAC Level 5" (of 32) is selected. The nominal ADC step is 1 mV (1.024 v / 1024 steps) which is equivalent to 32 / 5 = 6.4 mV per step at the top of the divider chain. Thus to convert the measured ADC10 units to hundredths of a volt, we need to multiply the value by 6.4 / 10 , or 0.64 .

For calibration, I normally use the ** operator, which first multiplies by the specified variable/constant and then divides by 65526. This effectively allows any "fractional" multiplier (i.e. less than 1) to be used for high accuracy calibration. However, it may not be possible to calculate the required constant directly within the Program Editor, in which case a pocket calculator can be used. For this example we want 1 volt to be read as 100 (i.e. displayed as 1.00) so "CALVDD10" has a nominal value = 65536 * 0.64 = 41942 (which may need to be adjusted by +/- 10 units for each mV error in the measured Vdd value around 4 volts).

This Vdd measurement method uses a few more program bytes than CALIBADC plus the required Basic division, but it is still significantly smaller than the code needed to "print" the result to 2 decimal places. In the version above, I have used a custom subroutine to display the decimal value, which uses fewer program bytes and two less byte-variables than the normal BINTOASCII macro (that does not permit the output bytes to "overlay" the input variable word).

But generally, I prefer to calculate and display the voltage directly in millivolts to the best convenient resolution. The following example cannot give a true 1 mV resolution (it's about 7 mV), and this time the 2 volt FVR is used, so the DAC Level is set to 10. The nominal calibration factor is 6.4 / 7 * 65536 = 59919 (which again may need to be adjusted by about +/- 15 for each mV error at a Vdd of 4 volts).

Code:
#picaxe 20m2		; Or any other M2
#no_data
#terminal 4800
symbol CALVDD7 = 59919	; "Fractional" multiplier to calibrate Vdd/FVR value (nominally 59919)

do
ReadVdd7:
	fvrsetup fvr2048		; Select the 2 volt reference (or can use FVR1024) 
	dacsetup $80			; Enable the DAC referenced to Vdd
	daclevel 10			; = Vdd * 10 / 32 so always should be less than the FVR2048 reference (Vdd < 6v)
	adcconfig 3			; Use the FVR as reference voltage for ADC and READDAC
	readdac10 w1			; Read the voltage on the "wiper" of the DAC
	w1 = w1 * 7 ** CALVDD7		; Calibrate the reported value to mV units
	sertxd(cr,lf,"Vdd= ",#w1," mV")
	pause 3000
loop
In practice, consecutive ADC measurements may have some "dither", due to electrical noise inside the chip (which occurs also with CALIBADC10). The noise might be slightly higher when using the DAC, but this is not necessarily a disadvantage: If a number of consecutive measurements are made and averaged (taking care not to lose any useful resolution) in the presence of a small amount of noise, then the effective resolution of the ADC can be increased beyond 10 bits. Therefore, in a subsequent post, I'll show some sample code to average the Vdd over several measurements, to give a smoothed result with "true" 1 mV resolution.

Cheers, Alan.
 

hippy

Technical Support
Staff member
It's a very interesting idea and would seem to be easier that the CALIBADC route, primarily because the divisor will be constant.

It took me a little while to grasp what you were doing so I'll just add my thoughts because they may help someone else ...

The basic READADC functionality is

Nadc = (Vin/Vref) * 1023

Where Nadc is the reading (0-1023) obtained with Vin applied to the ADC which is read with respect to the ADC reference voltage Vref.


Using CALIBADC where a Vref is measured with respect to Vpsu

Nadc = (Vref/Vpsu) * 1023

Vpsu = (Vref/Nadc) * 1023


Using READDAC to read Vdac ( which is proportional to Vpsu ) with respect to a Vref

Nadc = (Vdac/Vref) * 1023

Vdac = (Nadc*Vref) / 1023

Vpsu*K = (Nadc*Vref) / 1023

Vpsu = ( (Nadc*Vref) / 1023 ) / K

Vpsu = ( (Nadc*Vref) / 1023 ) / (DAClevel/32)

Vpsu = ( Nadc*Vref * 32 ) / ( 1023 * DAClevel )
 
Last edited:

AllyCat

Senior Member
Hi,

Thanks hippy, sorry I'd forgotten that I hadn't replied in this thread, nor added the promised "1mV" version of the code. However, it needs only a minor addition to repeat the measurement 7 times and accumulate a result in sub-mV units.

Rich (BB code):
; "ReadVdd" routine to measure the PICaxe suply rail to (almost) "1mV" resolution
; AllyCat February 2016

#picaxe 20m2                     ; Or any other M2
#no_data
#terminal 4800
symbol CALVDD7 = 59919           ; "Fractional" multiplier to calibrate Vdd/FVR value,nominally 59919

do
ReadVdd:
   fvrsetup fvr2048              ; Select the 2 volt reference (or can use FVR1024)
   dacsetup $80                  ; Enable the DAC referenced to Vdd
   daclevel 10                   ; = Vdd * 10 / 32 so always should be less than FVR2048 if Vdd < 6v
   adcconfig 3                   ; Use the FVR as reference voltage for ADC and READDAC
   w2 = 0                        ; Initialise an accumulator
   for b1 = 1 to 7               ; Read DAC 7 times
      readdac10 w1               ; NB: DAC (not ADC),but uses the ADC reference (FVR) 
      w2 = w2 + w1               ; Accumulate 7 values
   next b1
   w1 = w2 ** CALVDD7            ; Calibrate the value to mV units
   sertxd(cr,lf,"Vdd= ",#w1," mV")
   pause 3000
loop

Probably this code doesn't quite resolve the Vdd to a genuine 1 mV accuracy (perhaps a couple of mV), but it should be good enough for most purposes.

I'm reasonably satisfied that my alternative CALIBADC15 routine gets closer to a "real" 1 mV resolution, but I haven't decided how best to apply the method to this code, to give better speed or code size:

In view of the amount of internal noise inside the PICaxe, additional simple averaging of a repeated DAC measurement may be sufficient. However, the changing DAC-Level "randomising" noise technique may give some benefits at lower voltages. But unlike the CALIBADCnn routines, DAC levels only up to 10 can be used (with FVR 2048).

Cheers, Alan.
 
Last edited:

johndk

Senior Member
Although I don't understand what is actually happening in this code, I need a way to monitor voltage level of my supply battery. So I tried this code, and got results of zero. That would be expected since it was written for a 5v rail and I'm running on a 3v battery. So I tried to tinker with it and managed to get readings. But the readings didn't vary between 3.3v and 2.8v. I was just looking at the straight readings without even trying to convert to mv. So is this because I'm working with too low a voltage range? Or is the sensitivity too low to read differences of 0.1v?

What I need is for my remote device to be able to send out a warning when the battery voltage is getting too low and I was delighted that AllyCat had read my mind preemptively. Then disappointment, but mostly frustration because I don't understand it enough to tinker intelligently. I'm working with a 28x2 chip.
 

hippy

Technical Support
Staff member
This method is reading the Vdac to around 1/1000th of Vref so determining Vpsu to 0.1V should be achievable.
 

AllyCat

Senior Member
Hi,

The code will only work (as written) for an M2 device, which are you using?

Certainly the code does work, but for resolutions down to about 20mV threre are also several other code snippetts which use CALIBADC10 more directly.

Cheers, Alan. (currently only on a freee WiFi hotspot).

EDIT: Ah I see you're using a 28X2, that's why it doesn't work. Theoretically, it might be possible to adapt to an X2, but BESCUIT also reported problems with an X2. So it's probably best to look for one of the conventional CALIBADC10 code snippetts.
 
Last edited:

johndk

Senior Member
I found one of your other code snips which seems to be working well. I had to make a significant adjustment to the CALVDD because I'm using it at the lower 3.3 voltage. But it seems to be just what I need ... and simple. Thanks for sharing.

John
 

geoff07

Senior Member
I used AllyCat's CALVDD10 version on a 14M2 that I'm working on, and it gave me results about 2% above my trusted DVM, which was close enough not to need to calibrate further. So thanks for this great idea.

I'm working on a micropower gadget using a 50F supercap and hoping ultimately to keep it charged it from a 10x2 cm solar cell array taken from a transistor radio. So far I'm getting just short of 20 hours from a fully charged cap (2.3v) to when errors creep in at 1.53v. Having it display the voltage via sertxd saves a lot of measuring. The next step is to see what voltage the panel can maintain it at.
 

AllyCat

Senior Member
Hi,

The PIC data sheet only guarantees a reference voltage accuracy of around +/- 7%, but in practice it normally seems much better. However, I did find some "undocumented" behaviour of the DAC at low voltages.

A quick "back of envelope" calculation (start a thread in the Active forum if you want more details) suggests that that solar cell should be sufficient even in mid-winter if the sun shone brightly every day. But, at least in the UK we can get many "dull" days that may give very little solar energy. Personally, I would use a single rechargeable AA LiFePO4 cell (3.2 volts) or 2 or 3 x AAA NiMH cells which will store perhaps 50 times more energy than a 50F Supercap.

Of course there will be far too much energy in summer, but the PICaxe (software) can be easily arranged to shunt away surplus current when the supply rail gets higher than desired (I have used thresholds of 3.4 volts for LiFePO4 or 1.40 volts/cell for NiMH).

Cheers, Alan.
 

geoff07

Senior Member
Yes, I agree that there should be enough juice, and if there were not on a particular day it is programmed to suspend when the voltage drops until it recovers. It is really just an experiment with a cap and cells I had but you are right about the higher power of an Li. I have oodles of 18650s from old laptop packs and often use them and other recovered cells. I turn on an led when the voltage is getting too high, to dump some power. I like the idea of a cap if it works as it is much simpler, there is a certain elegance in using solar pv energy stored directly as charge. They have the advantage of very fast charge and discharge, but a limited max voltage.
 
Top