Higher resolution "CALIBADC" subroutines

AllyCat

Senior Member
#1
CALIBADC10 is a very useful PICAxe command which allows the supply rail to be calculated purely by softaware, without the need for any external hardware or PICaxe pins. Normally a voltage resolution to 1 or 2 decimal places (as described here) is sufficient for estimating the condition of a battery, for example. But for more advanced uses, I have devised a family of subroutines which offer higher resolutions, through to "CALIBADC16" (i.e. up to 64 times that of CALIBADC10), still without the need for any external hardware.

Two different techniques are used, which may be employed independently or in unison. The first method employs a higher voltage FVR, normally FVR2048, but FRV4096 might be used occasionally. The second method measures and accumulates multiple ADC values to create a longer (i.e. higher resolution) data word. However, this can be effective only if there is a degree of "noise" or randomness in the values, which is achieved here by taking measurements via the on-chip DAC (Digital to Analogue Converter), set to various different attenuation factors. In principle, any combination of DAC settings could be used with a suitable calibration factor, but these subroutines employ carefully selected levels such that the accumulated total is always an exact "power of two" (i.e. 2, 4, 8, 16, etc.) times greater than the normal CALIBADC10 value).

Occasionally, the "CALIBADC" result may be used directly, but normally it needs to be converted to an actual supply voltage by dividing the CALIBADC value into a "Magic Number". However, unfortunately even "CALIBADC11" needs a higher accuracy division calculation than is available in PICaxe Basic, so here I have used the "Double Word" division subroutine which I devised some time ago.

The "Magic Number" is determnined by the exact voltage of the on-chip FVR and the resolution (number of bits) in the CALIBADC measurement, so in principle it needs to be different for each subroutine. However, to avoid this, the value has been "normalised" for CALIBADC16, with the lower resolution CALIBADC measurements multiplied by the appropriate power of 2 to give the required result. For the code here, the Magic Number has 1024 in the High Byte (i.e. it's 1024 * 65536), so the voltage can be trimmed in 0.1% steps without needing to change the Low Byte away from zero.

Three sample subroutines are shown below: CALIBADC12 should give a modest increase in resolution but is mainly intended to show the principle of operation of the code; CALIBADC13* requires only FVR1024 so can be used with 14 and 20M2s, even at their lowest supply voltages and CALIBADC14 probably gives the best compromise between resolution and execution time. The subroutines use b1 and w1 to w3 explicitly, because I normally reserve these as "local" variables throughout my programs. In general, any word variables may be used, except that the code as shown does require the bit-addressing capability of W1.

Code:
; Higher Resolution CALIBADC subroutines -  AllyCat July 2015
#no_data
symbol CALIBCONST = 1024		; Adjust this slightly to correct for any minor errors in the FVR voltage
calibadc12:					; Measure the PICaxe supply rail using "12 bits resolution"	
	fvrsetup FVR2048			; Nominal 2 volt reference voltage
	dacsetup $88			; Reference chain to FVR
	adcconfig 0				; ADC ref to Vdd
	calibadc10 w3 			; Initialise accumulator to the CALIBADC10 value
	for b1 = 8 to 24 step 8 		; 1/4, 1/2 & 3/4 of FVR2048 = CALIBADC10 * 3 
		daclevel b1	 		; Set the DAC attenuation value (32 steps to full-scale)
		readdac10 w2		; Read the voltage on the "wiper"
		w3 = w3 + w2		; Accumulate the sum	
	next b1	
	w1 = 0				; Clear low byte of numerator (quotient)
	w2 = CALIBCONST		; High byte of numerator, normalised to CALIBADC16					

	w3 = w3 * 16			; Compensate for the 16-bit normalised calibration factor
	call div31				; Divide w2:w1 by w3
	return				; Result is in w1, CALIBADC12 value still in w3

calibadc13:					; Measure the supply rail using "13 bits resolution"	
	fvrsetup FVR1024 		; Nominal 1 volt reference voltage
	dacsetup $88			; Reference chain to FVR
	adcconfig 0			; ADC ref to Vdd
	calibadc10 w3			; Initialise the accumulator
	for b1 = 3 to 29 step 2	; 7 pairs of daclevels (eg 5 + 27) each totalling 32 
		daclevel b1			; Set the DAC attenuation value (32 steps to full-scale)
		readdac10 w2		; Read voltage on "wiper"
		w3 = w3 + w2		; Accumulate the sum	
	next b1	
	w1 = 0				; Clear low byte of numerator
	w2 = CALIBCONST		; High byte of numerator, normaised to CALIBADC16					
	w3 = w3 * 8			; Compensate for 16 bit normalised calibration factor
	goto div31			; Divide w2:w1 by w3 and use the subroutine's "return"					
calibadc14:					; Measure supply rail using "14 bits resolution"	
	fvrsetup FVR2048			; Nominal 2 volt reference voltage
	dacsetup $88			; Reference chain to FVR
	adcconfig 0				; ADC ref to Vdd
	calibadc10 w3			; Initialise accumulator
	for b1 = 2 to 30 step 2	; 7 pairs of daclevels each totalling 32 + middle value (=CALIBADC10) 
		daclevel b1			; Set the DAC attenuation value (32 steps to full-scale)
		readdac10 w2		; Read voltage on "wiper"
		w3 = w3 + w2		; Accumulate the sum	
	next b1	
	w1 = 0				; Clear low byte of numerator
	w2 = CALIBCONST		; High byte of numerator, normaised to CALIBADC16			
	w3 = w3 * 4				; Compensate for 16-bit calibration factor
;						; Fall into division routine    
div31:            				; Divide numerator (w2:w1) by divisor (w3) no error check
   for b1 =  0 to 15  			; Repeat for each bit positions
   w2 = w2 + w2 + bit31		; Shift High word of numerator left (top bit is lost), adding carry from w1
   w1 = w1 + w1      			; Shift Low word of numerator left
   if w2 >= w3 then    			; Skip if can't subtract
   	w2 = w2 - w3			; Subtract divisor, then.. 
   	w1 = w1 + 1    			; Add the flag into the result (in low word)
	endif      
   next b1
   return					; Result is in w1, remainder in w2, divisor in w3 is unchanged
I have intentionally kept the explanatory detail here quite brief but plan to start another thread in the Active forum to discuss further options and design details. In subsequent posts, I hope to add some additional subroutines using Conditional Compilation (#IFDEFs), and a "Universal resolution" subroutine which is configured by passing a single entry variable.

*EDIT: At low voltages the CALIBADC13 routine doesn't behave entirely as expected, because there appears to be a "bug" in the PIC silicon hardware (FVR1024-DAC) of all the chips I've tested. I plan to report this and update the thread in due course.

Cheers, Alan.
 
Last edited:

AllyCat

Senior Member
#2
Update:

Hi,

Here is the promised update to the subroutines described above.

Further measurements have shown that the FVR1024 voltage reference has such "unexpected behaviour" (aka bugs) that it is not suitable for use in these applications below about 3 volts (which is the only time it would be of benefit). Fortunately, CALIBADC13 is the only version that uses FVR1024; I did consider modifying the code to use FVR2048 (which would be easy), but I have now devised an alternative method for these "medium resolution" applications. That code is generally more satisfactory (faster, smaller and perhaps more understandable), so I am leaving CALIBADC13 unchanged, as a "demonstrator" of the issues with FVR1024. But of course it is not recommended for any critical applications.

For the "ultimate" resolution (~1mV and perhaps sub-mV), "CALIBADC15" gives slightly better performance than the alternative "ReadVdd" method, and also the associated "double word" division subroutine might be useful for other purposes. However, I didn't include the code in the post above (but it was shown later in this finished project), so here is a listing now:

Rich (BB code):
; CALIBADC15 - A "High Resolution" subroutine to measure the PICaxe supply rail, Vdd 
; AllyCat February 2016

#picaxe 08M2            ; Or any M2 picaxe 
#no_data

symbol CALIBCONST = 512 ; Adjust this constant slightly to correct for any minor variation in the FVR voltage

do
   call calibadc15      ; "High resolution" Vdd measurement subroutine (Vdd > 2.2v)
   sertxd(cr,lf,"Vdd= ",#w1," mV ")
   pause 2000
loop
            
calibadc15:             ; Measure supply rail using "15 bits resolution"   
   fvrsetup FVR2048     ; Nominal 2 volt reference voltage
   dacsetup $88         ; Reference chain to FVR
   adcconfig 0          ; ADC ref to Vdd
   calibadc10 w3        ; Initialise accumulator with CALIBADC10
   for b1 = 1 to 31     ; 15 pairs of DAC levels each totalling 32 + middle value (=CALIBADC10) 
      daclevel b1       ; Set the DAC attenuation value (32 steps to full-scale)
      readdac10 w2      ; Read voltage on the "wiper"
      w3 = w3 + w2      ; Accumulate the sum 
   next b1  
   w1 = 0               ; Clear low byte of numerator
   w2 = CALIBCONST      ; High byte of numerator, then fall into division routine    
div31:                  ; Divide numerator (w2:w1) by divisor (w3) no error check
   for b1 =  0 to 15    ; Repeat for each bit positions
   w2 = w2 + w2 + bit31 ; Shift High word of numerator left (top bit is lost), adding carry from w1
   w1 = w1 + w1         ; Shift Low word of numerator left
   if w2 >= w3 then     ; Skip if can't subtract
      w2 = w2 - w3      ; Subtract divisor, then.. 
      w1 = w1 + 1       ; Add the flag into the result (in low word)
   endif      
   next b1
   return               ; Result is in w1, remainder in w2, divisor in w3 is unchanged
Note that CALIBCONST has been halved compared with the listing in the first post above, because it was discovered that the result from the "double-word" division subroutine could overflow at the very lowest supply voltages (< 2 volts). For the same reason, I'm not including a "CALIBADC16" routine (using the FVR4096 voltage reference) which requires the Vdd to be above 4.2 volts under all operational conditions, so is really only relevant to 5 volt regulated supplies (where an independent voltage measurement is hardly necessary).

Cheers, Alan.
 
Last edited by a moderator:
Top