DT_Analog
From Darrel's website . . . all his words, not mine.
You've probably noticed that when using the Analog to Digital converter, the numbers are NEVER stable. At best, the result will always be bouncing back and forth between two numbers.
While it's extremely annoying, there is a reason for it, and the number of times it bounces between those two numbers is actually indicating the values of more bits of resolution that are not included in the original 10-bit result.
This effect can be exploited to increase the effective number of bits in the result.
By taking a number of consecutive samples and averaging the results with a lower divisor, you can get up to 16-bit accuracy from that poor little 10-bit converter.
An explanation of the Oversampling technique can be found on Wikipedia.
http://en.wikipedia.org/wiki/Oversampling
A/D Bits |
Samples Required |
MAX result |
Conv. Time (1) |
10 |
1 |
1023 |
24.2us |
11 |
4 |
2046 |
96.8us |
12 |
16 |
4092 |
387us |
13 |
64 |
8184 |
1.54ms |
14 |
256 |
16368 |
6.2ms |
15 |
1024 |
32736 |
24.7ms |
16 |
4096 |
65472 |
99.1ms |
(1) Conversion Time based on 5 us acquisition with 1.6 us Tad |
Samples required:
For each additional bit of resolution, the number of samples required is multiplied times 4 (22(n-10)). The samples are accumulated in a 32-bit variable, then divided by a number that is doubled for each additional bit (2(n-10)). [This is all handled by the module.]
Maximum results:
Since the highest value provided by the converter is 1023, it can't bounce between 1023-1024.
This limits the maximum values to slightly lower than expected, which is (1023 << (bits-10))
For 16-bits, the maximum is (1023 << 6) or 65472. This value is returned in the ADmax variable after the conversion for easy voltage calculations.
Practicality:
Taking 1/10th of a second to convert a 16-bit value seems like a colossal waste of time. But it's nice to know you can do it if you really need to.
To me, the real winner is 12-bit, at under 400us conversion time, with a 4 fold increase in resolution, on almost any PIC, ... I doubt I'll be using 10-bit much anymore. But even 14-bit's not too bad @6ms, although the extra time for 14-bit probably isn't worth it for most analog projects.
Conversion time:
The time it takes to complete a conversion depends on a couple of factors.
1. The Acquisition time specified in the DEFINE ADC_SAMPLEUS statement. If there is no DEFINE, PBP will default to 50us, which is for the absolute worst case scenario with high impedance. It's very likely that you will be able to use a much smaller value. With an impedance of 1K or less, you can usually get away with 3-5us. Also, the minimum ADC_SAMPLEUS is limited to the minimum PAUSEUS delay, which at 4Mhz OSC is 24us, @20Mhz it's 3us. Use as low a value as you can possibly get away with because this time happens for every sample. With 256 or more samples being taken, it adds up pretty quick.
2. The ADC clock source should be set as close as possible to 1.6us Tad. And it takes 12 Tad to complete a 10-bit conversion. This one's hard to explain, so I'll just list the recommended values. Use the highlighted values in your DEFINE ADC_CLOCK statement, based on your oscillator frequency.
OSC |
DEFINE ADC_CLOCK |
Clk source |
Tad(us) |
12 Tad(us) |
4 |
1 |
Fosc/8 |
2 |
24 |
8 |
5 |
Fosc/16 |
2 |
24 |
10 |
5 |
Fosc/16 |
1.6 |
19.2 |
12 |
2 |
Fosc/32 |
2.6 |
31.2 |
16 |
2 |
Fosc/32 |
2 |
24 |
20 |
2 |
Fosc/32 |
1.6 |
19.2 |
24 |
6 |
Fosc/64 |
2.6 |
31.2 |
32 |
6 |
Fosc/64 |
2 |
24 |
40 |
6 |
Fosc/64 |
1.6 |
19..2 |
48 |
3 |
FRC |
4 |
48us |
Noise in the system:
As previously mentioned, it's the number of times the value bounces back and forth between two numbers that allows it to gain more bits of resolution.
If (due to poor design) your A/D values are swinging wildly even at normal 10-bit, this system will also help calm them down (some) since it will find an average of the samples taken. Usually it's the mid-point of those swings that you want to find anyway. Naturally, you should minimize ALL noise as much as possible to begin with.
Using the Module:
You must first configure your PIC to have the desired Analog inputs and use the correct Oscillator source. This module will not do it for you.
You must also set the ADC DEFINE's for the fastest available 10-bit results, depending on your oscillator frequency.
Then INCLUDE the file somewhere near the top of your program.
Set the ADchan variable to the AN channel you wish to read. (ADchan = 1)
Set the ADbits variable to the desired resolution. (ADbits = 12)
Then GOSUB GetADC.
The resulting value will be returned in the ADvalue word sized variable.
And the maximum A/D value for the selected resolution is returned in the ADmax variable.
Code:
'http://www.darreltaylor.com/DT_Analog/
'****************************************************************
'* Name : DT_Analog.pbp *
'* Author : Darrel Taylor *
'* Notice : Copyright (c) 2009 *
'* Date : 5/23/2009 *
'* Version : 1.0 *
'* Notes : Up to 16 bit A/D with a 10-bit A/D converter *
'* : http://en.wikipedia.org/wiki/Oversampling *
'****************************************************************
GOTO OverDTAnalog
ADchan VAR BYTE ; global - A/D channel to use
ADbits VAR BYTE ; global - # of bits in result
ADvalue VAR WORD ; global - the final A/D value
ADmax VAR WORD ; global - Max A/D value at this resolution
;--------------------
DTadCount VAR WORD ; local - sample count
DTadDivisor VAR WORD ; local - averaging divisor
DTadShiftR VAR BYTE ; local - bits to shift for UnderSampling
DTadAccum VAR WORD[2] ; local - 32-bit sample accumulator
;---------------------------------------------------------------------------
GetADC:
IF (ADbits >= 10) THEN
DTadShiftR = 0 ; 10 11 12 13 14 15 16
LOOKUP2 ADbits-10,[ 1, 4,16,64,256,1024,4096],DTadCount
LOOKUP ADbits-10,[ 1, 2, 4, 8, 16, 32, 64],DTadDivisor
ELSE
DTadCount = 1
DTadDivisor = 1
DTadShiftR = 10 - ADbits
ENDIF
LOOKUP2 ADbits,[0,1,3,7,15,31,63,127,255,511,1023, _
2046,4092,8184,16368,32736,65472],ADmax
DTadAccum = 0 : DTadAccum[1] = 0 ; clear the accumulator
DTadLoop:
ADCIN ADchan, ADvalue ; get 10-bit sample
DTadAccum = DTadAccum + ADvalue ; add it to the accumulator
IF DTadAccum < ADvalue THEN ; if low word overflowed
DTadAccum[1] = DTadAccum[1] + 1 ; increment the high word
ENDIF
DTadCount = DTadCount - 1 ; done with this sample
IF DTadCount > 0 THEN DTadLoop ; loop if not done with ALL samples
R2 = DTadAccum ; put 32-bit accumulated value in PBP
R0 = DTadAccum[1] ; registers, prepare for DIV32
ADvalue = DIV32 DTadDivisor ; get the average value
ADvalue = ADvalue >> DTadShiftR ; Shift right if < 10-bit
RETURN
OverDTAnalog:
************************************************** ***************************
************************************************** ***************************
Code:
Here's a simple example that displays 12-bit A/D values on an LCD.
Of course, by changing one number it will display 16-bit values too.
Download 12-bit example Download example code.
(Save it to a new folder. Not the PBP folder)
View 12-bit DEMO in browser View
;--- Set your __configs here as needed ---
DEFINE OSC 20
INCLUDE "DT_Analog.pbp" ; DT's 16-bit Analog Module
DEFINE ADC_BITS 10 ; Set-up ADC for fast 10-bit results
DEFINE ADC_CLOCK 2
DEFINE ADC_SAMPLEUS 5
;----[Change these to match your hardware]----------------------------------
DEFINE LCD_DREG PORTB ; LCD Data Port
DEFINE LCD_DBIT 0 ; Starting Data Bit
DEFINE LCD_RSREG PORTB ; Register Select Port
DEFINE LCD_RSBIT 4 ; Register Select Bit
DEFINE LCD_EREG PORTB ; Enable Port
DEFINE LCD_EBIT 5 ; Enable Bit
DEFINE LCD_BITS 4 ; Data Bus Size
DEFINE LCD_LINES 2 ; Number of Lines on LCD
DEFINE LCD_COMMANDUS 2000 ; Command Delay time in uS
DEFINE LCD_DATAUS 50 ; Data Delay time in uS
ADCON1 = %10001101 ; right justify, AN0 & AN1 analog
PAUSE 250 : LCDOUT $FE,1 ; Initialize LCD
PAUSE 100 : LCDOUT $FE,1 ; not all LCDs need both lines
ADbits = 12 ; set to 12-bit resolution
;---------------------------------------------------------------------------
Main:
FOR ADchan = 0 to 1 ; Do both AN0 and AN1
GOSUB GetADC ; Get A/D value
IF ADchan = 0 THEN
LCDOUT $FE, $80 ; LCD line 1
ELSE
LCDOUT $FE, $C0 ; LCD line 2
ENDIF
LCDOUT "CH-",DEC1 ADchan," = ",DEC ADvalue," "
NEXT ADchan
GOTO Main:
************************************************** ********************************
************************************************** ********************************
Code:
Here's another example that shows all the resolutions from 1-16 bit from a single A/D channel via the USART with HyperTerminal or other ANSI terminal program.
Download ALL-bit example Download example code.
(Save it to a new folder. Not the PBP folder)
View ALL-bit DEMO in browser View
;--- Set your __configs here as needed ---
DEFINE OSC 20
INCLUDE "DT_Analog.pbp" ; DT's 16-bit Analog Module
DEFINE ADC_BITS 10
DEFINE ADC_CLOCK 2
DEFINE ADC_SAMPLEUS 5
DEFINE HSER_SPBRG 10 ; 115200 @ 20 Mhz
DEFINE HSER_RCSTA 90h ; Hser receive status init
DEFINE HSER_TXSTA 24h ; Hser transmit status init
DEFINE HSER_CLROERR 1 ; Hser clear overflow automatically
Volts VAR WORD ; Volts calculated from the A/D reading
ADCON1 = %10001101 ; right justify, AN0 & AN1 analog
HSEROUT [27,"[2J"] ; Clear screen
;---------------------------------------------------------------------------
Main:
HSEROUT [27,"[H"] ; home cursor
HSEROUT [27,"[34m",27,"[1m"] ; bold blue text
HSEROUT ["ADbits DEC HEX BIN16 ADmax Volts"]
HSEROUT [27,"[37m",13,10] ; black text
ADchan = 1 ; Select channel AN1
FOR ADbits = 1 to 16 ; cycle thru all bit resolutions
GOSUB GetADC ; get the A/D value
GOSUB ShowAD ; show the A/D results
NEXT ADbits ; do next resolution
GOTO Main
;---------------------------------------------------------------------------
ShowAD:
IF ADbits < 10 THEN HSEROUT [" "]
HSEROUT [DEC ADbits,"-bit = ",DEC ADvalue," "]
HSEROUT [27,"[",DEC ADbits+1,";16H",HEX4 ADvalue," ",BIN16 ADvalue, _
" ",DEC ADmax]
IF ADbits < 16 THEN ; calculate the Voltage with 4 decimals
Volts = 50000 * ADvalue
Volts = DIV32 ADmax
ELSE ; with 16-bit, ADmax is too big for DIV32
ADmax = ADmax >> 1 ; cut it in half
Volts = 25000 * ADvalue ; and scale the multiplier accordingly
Volts = DIV32 ADmax ; PBPL & Longs, doesn't have that problem
ENDIF
HSEROUT [27,"[",DEC ADbits+1,";49H", _
DEC Volts/10000,".",DEC4 Volts//10000 ,13,10]
RETURN
This is a screen shot of the ALLBitDEMO in action.
And before you ask Charles ...
This will not work (as is) on a PIC with a 12-bit A/D converter.
************************************************** ********************
************************************************** ********************
Code:
'http://www.darreltaylor.com/DT_Analog/
'****************************************************************
'* Name : AllBitDEMO.pbp *
'* Author : Darrel Taylor *
'* Notice : Copyright (c) 2009 *
'* Date : 5/24/2009 *
'* Version : 1.0 *
'* Notes : Example program for DT_Analog.pbp *
'* Displays all possible A/D resolutions for a single *
'* channel using the USART and HyperTerminal or other *
'* ANSI terminal program *
'****************************************************************
;--- Set your __configs here as needed ---
DEFINE OSC 20
INCLUDE "DT_Analog.pbp" ; DT's 16-bit Analog Module
DEFINE ADC_BITS 10
DEFINE ADC_CLOCK 2
DEFINE ADC_SAMPLEUS 5
DEFINE HSER_SPBRG 10 ; 115200 @ 20 Mhz
DEFINE HSER_RCSTA 90h ; Hser receive status init
DEFINE HSER_TXSTA 24h ; Hser transmit status init
DEFINE HSER_CLROERR 1 ; Hser clear overflow automatically
Volts VAR WORD ; Volts calculated from the A/D reading
ADCON1 = %10001101 ; right justify, AN0 & AN1 analog
HSEROUT [27,"[2J"] ; Clear screen
;---------------------------------------------------------------------------
Main:
HSEROUT [27,"[H"] ; home cursor
HSEROUT [27,"[34m",27,"[1m"] ; bold blue text
HSEROUT ["ADbits DEC HEX BIN16 ADmax Volts"]
HSEROUT [27,"[37m",13,10] ; black text
ADchan = 1 ; Select channel AN1
FOR ADbits = 1 to 16 ; cycle thru all bit resolutions
GOSUB GetADC ; get the A/D value
GOSUB ShowAD ; show the A/D results
NEXT ADbits ; do next resolution
GOTO Main
;---------------------------------------------------------------------------
ShowAD:
IF ADbits < 10 THEN HSEROUT [" "]
HSEROUT [DEC ADbits,"-bit = ",DEC ADvalue," "]
HSEROUT [27,"[",DEC ADbits+1,";16H",HEX4 ADvalue," ",BIN16 ADvalue, _
" ",DEC ADmax]
IF ADbits < 16 THEN ; calculate the Voltage with 4 decimals
Volts = 50000 * ADvalue
Volts = DIV32 ADmax
ELSE ; with 16-bit, ADmax is too big for DIV32
ADmax = ADmax >> 1 ; cut it in half
Volts = 25000 * ADvalue ; and scale the multiplier accordingly
Volts = DIV32 ADmax ; PBPL & Longs, doesn't have that problem
ENDIF
HSEROUT [27,"[",DEC ADbits+1,";49H", _
DEC Volts/10000,".",DEC4 Volts//10000 ,13,10]
RETURN
Bookmarks