ADC Oversampling
for a
Higher Resolution

Oversampling of an ADC's reading is a very common method of giving a pseudo higher resolution that the ADC itself can supply, and its principle is very straightforward, but hotly argued upon as well by some people. However, many tests over the years have shown that is does actually work, and many commercial units use the method.

Its principle method of operation is to take X amount of ADC readings for the final resolution required, and accumulate them. Then divide the accumulated value for the resolution required and the samples taken. Sounds simple doesn't it? And it is very simple to implement and understand when written in the Positron language. The Positron8 code listing below is a demonstration showing how to increase the 10-bit resolution of the on-board ADC, to 16-bits:

Click here to view the code listing for: ADC Oversampling to give a 16-bit resolution from a 10-bit ADC

'
' /\\\\\\\\\
' /\\\///////\\\
' \/\\\ \/\\\ /\\\ /\\\
' \/\\\\\\\\\\\/ /\\\\\ /\\\\\\\\\\ /\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\
' \/\\\//////\\\ /\\\///\\\ \/\\\////// /\\\/////\\\ \////\\\//// \////\\\//// \////////\\\
' \/\\\ \//\\\ /\\\ \//\\\ \/\\\\\\\\\\ /\\\\\\\\\\\ \/\\\ \/\\\ /\\\\\\\\\\
' \/\\\ \//\\\ \//\\\ /\\\ \////////\\\ \//\\/////// \/\\\ /\\ \/\\\ /\\ /\\\/////\\\
' \/\\\ \//\\\ \///\\\\\/ /\\\\\\\\\\ \//\\\\\\\\\\ \//\\\\\ \//\\\\\ \//\\\\\\\\/\\
' \/// \/// \///// \////////// \////////// \///// \///// \////////\//
' Let's find out together what makes a PIC Tick!
'
' A demonstration of simple 16-bit oversampling of the 10-bit ADC for higher resolution on a PIC18F26K22 device
'
' Written for the Positron8 compiler by Les Johnson.
'

Device = 18F26K22 ' Tell the compiler what device to compile for
Declare Xtal = 64 ' Tell the compiler what frequency the device will be operating at (in MHz)
Declare Float_Display_Type = Fast ' Tell the compiler to use its faster and more accurate Float to ASCII routines
'
' Setup USART1
'

Declare Hserial1_Baud = 9600 ' Set USART1 Baud rate to 9600
Declare HRSOut1_Pin = PORTC.6 ' Set USART1 TX pin

Include "ADC_OverSample.inc" ' Load the over-sampling ADC procedures into the program
'
' Create some constants for the demonstration programs
'

Symbol cVref_Voltage = 5.0 ' Holds the +Vref voltage that the ADC will use
Symbol cQuanta16 = ((cVref_Voltage) / 65536.0) ' Holds the quantasisation constant value for 16-bits at cVref_Voltage
'
' Create some variables for the demonstration program
'

Dim wADC_RawValue As Word ' Holds the oversampled ADC value
Dim fVolts As Float ' Holds the calculated voltage

'-------------------------------------------------------------------------------------
' The main program starts here
' Read the oversampled ADC value and display it, and the voltage supplied to the ADC on a serial terminal
'

Main:
Setup() ' Setup the program

Do ' Create a loop
wADC_RawValue =
ADC_Read16(0) ' Read the oversampled 16-bit ADC on channel AN0
fVolts = wADC_RawValue * cQuanta16
' \ Calculate the voltage from the 16-bit ADC reading
fVolts = fVolts + 0.01
' /
HRSOutLn "ADC=", Dec wADC_RawValue, " : ", ' Display the oversampled ADC result on a serial terminal
Dec2 fVolts, " Volts" ' Display the calculated voltage on a serial terminal
DelayMS 100 ' A small delay between samples, so they can be seen changing
Loop ' Do it forever

'-------------------------------------------------------------------------------------
' Setup the program
' Input : None
' Output : None
' Notes : None
'

Proc Setup()
IntOsc_64MHz() ' Set the device to operate at 64MHz with its internal oscillator
ADC_Init() ' Setup the ADC
EndProc

'-------------------------------------------------------------------------------------
' Read the 10-bit ADC directly on a PIC18F26K22 device
' Input : pChan holds the ADC channel to read (0 to X)
' Output : Returns the 10-bit ADC value
' Notes : The "ADC_Read10" procedure has been brought outside the library code because of the differences in most PIC devices
'

Proc ADC_Read10(pChan As Byte), Word
Dim wADRES As ADRESL.Word ' Create a 16-bit SFR from ADRESL and ADRESH

ADCON0 = 0b00000001 ' Clear the channel bits and enable the ADC peripheral
pChan = pChan << 2 ' Move the channel bits into the correct position for ADCON0
ADCON0 = ADCON0 | pChan ' Or the channel bits into ADCON0
ADCON0bits_GO_DONE = 1 ' \ Wait for the ADC to complete its reading
Repeat : Until ADCON0bits_GO_DONE = 0 ' /
Result = wADRES ' Load the result with the ADC value
EndProc

'-----------------------------------------------------------------------------------------
' Setup the ADC on a PIC18F26K22 device
' Input : None
' Output : None
' Notes : Set for 10-bit operation
' : Uses the Gnd and VDD as vrefs
' : Uses the FRC oscillator
' : Sets AN0 as an analogue pin
'

Proc ADC_Init()
ADCON1 = $00
ADCON2 = 0b10001111
ADRESL = $00
ADRESH = $00
ADCON0 = $00
ANSELAbits_ANSA0 = 1
' Setup AN0 as an analogue pin
EndProc

'-----------------------------------------------------------------------------------------
' Setup the device to use its internal oscillator at 64MHz
' Input : None
' Output : None
' Notes : For use with PIC18Fx6K22 devices
'

Proc IntOsc_64MHz()
OSCCON = $70
OSCCON2 = $04
OSCTUNE = $40
Repeat : Until OSCCON2bits_PLLRDY = 1 ' Wait for the PLL to stabilise
EndProc

'-----------------------------------------------------------------------------------------
' Configure the fuses to operate at 64MHz using the internal oscillator, on a PIC18F26K22 device
'

Config_Start
FOSC = INTIO67 ' Use the internal oscillator for the device's oscillator
PLLCFG = Off ' Disable PLL
PRICLKEN = On ' Primary clock enabled
FCMEN = Off ' Fail-Safe Clock Monitor disabled
IESO = Off ' Internal/External Oscillator Switchover mode disabled
PWRTEN = On ' Power up timer enabled
BOREN = SBORDIS ' Brown-out Reset enabled in hardware only (SBOREN is disabled)
BORV = 190 ' Brown Out Reset Voltage set to 1.90 V nominal
WDTEN = Off ' Watch dog timer is always disabled. SWDTEN has no effect.
WDTPS = 128 ' Watchdog Timer Postscale 1:128
CCP2MX = PORTC1 ' CCP2 input/output is multiplexed with RC1
PBADEN = Off ' PORTB<5:0> pins are configured as digital I/O on Reset
CCP3MX = PORTB5 ' P3A/CCP3 input/output is multiplexed with RB5
HFOFST = On ' HFINTOSC output and ready status are not delayed by the oscillator stable status
T3CMX = PORTC0 ' Timer3 Clock Input (T3CKI) is on RC0
P2BMX = PORTB5 ' ECCP2 B (P2B) is on RB5
MCLRE = EXTMCLR ' MCLR pin enabled, RE3 input pin disabled
STVREN = Off ' Stack full/underflow will not cause Reset
LVP = Off ' Single-Supply ICSP disabled
XINST = Off ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
Debug = Off ' Debug Disabled
CP0 = Off ' Block 0 (000800-001FFF) not code-protected
CP1 = Off ' Block 1 (002000-003FFF) not code-protected
CP2 = Off ' Block 2 (004000-005FFF) not code-protected
CP3 = Off ' Block 3 (006000-007FFF) not code-protected
CPB = Off ' Boot block (000000-0007FF) not code-protected
CPD = Off ' Data EEPROM not code-protected
WRT0 = Off ' Block 0 (000800-001FFF) not write-protected
WRT1 = Off ' Block 1 (002000-003FFF) not write-protected
WRT2 = Off ' Block 2 (004000-005FFF) not write-protected
WRT3 = Off ' Block 3 (006000-007FFF) not write-protected
WRTC = Off ' Configuration registers (300000-3000FF) not write-protected
WRTB = Off ' Boot Block (000000-0007FF) not write-protected
WRTD = Off ' Data EEPROM not write-protected
EBTR0 = Off ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
EBTR1 = Off ' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
EBTR2 = Off ' Block 2 (004000-005FFF) not protected from table reads executed in other blocks
EBTR3 = Off ' Block 3 (006000-007FFF) not protected from table reads executed in other blocks
EBTRB = Off ' Boot Block (000000-0007FF) not protected from table reads executed in other blocks
Config_End

The demonstration code listing above uses the ADC_Oversampling.inc library file that has five procedures within it to oversample a device's 10-bit ADC peripheral to 12-bit, 13-bit, 14-bit, 15-bit, or 16-bit. However, nothing is free, and oversampling does have some costs to pay. The main one is the time it takes to read an ADC value because of the multiple ADC readings required for accumulation. For example, the list below shows the time it takes for each oversampling procedure to read and ADC value when the 18F microcontroller is working at 64MHz:

OverSampled 12-bit takes 867 us to complete an ADC reading.
OverSampled 13-bit takes 3.46 ms to complete an ADC reading.
OverSampled 14-bit takes 13.95 ms to complete an ADC reading.
OverSampled 15-bit takes 55.79 ms to complete an ADC reading.
OverSampled 16-bit takes 223.0 ms to complete an ADC reading.

So, as can be seen, the higher the resolution required, the longer it takes for accumulation to occur, and that is with the Positron8 compiler producing its highly optimised and compact assembler code.

For measuring projects, a fast ADC reading is not always required, but for audio or control projects, the ADC's speed must be taken into account, so it may be beneficial to move to a PIC24 or dsPIC33 microcontroller for more speed and faster division when oversampling. Another thing to keep in mind is that the oversampled resolution value is not exact, and will not give as exact a reading as a true ADC at the required resolution would, but for most purposes, the oversampled ADC operations work well. The code below is the
ADC_Read12 procedure from the ADC_Oversampling.inc library file, to show what is required for simple oversampling 12-bits:

'-------------------------------------------------------------------------------------
' Oversample a 10-bit ADC to return a 12-bit result
' Input : pChan holds the ADC channel to read the samples from: (0 to X)
' Output : Returns the oversampled 12-bit ADC value
' Notes : Calls the "ADC_Read10" procedure that must be created in the main program.
' Because of the many differences in PIC microcontroller devices,
' a single procedure within the library code will not work on all of them with efficiency.
'

Proc ADC_Read12(pChan As Byte), OverSamp_wResult
Dim bSamples As Result.Byte0 ' Create an alias byte variable for the accumulation loop counter

OverSamp_wAccum = 0
' Clear the accumulator before entering the accumulation loop
For bSamples = 15 DownTo 0 ' Create an accumulation loop for the final resolution required
OverSamp_wAccum = OverSamp_wAccum +
ADC_Read10(pChan) ' Accumulate the 10-bit ADC readings
Next ' Close the accumulation loop
Result = OverSamp_wAccum >> 2 ' Divide the accumulator, and return the value
EndProc

Below is the above demonstration program listing running in the proteus simulator. As can be seen, the voltage into the ADC matches the ADC oversampled value when it is convrted back into a voltage by quantasisation calculations:

And below is the demonstration program: "ADC_OverSample12_18F26K22.bas" working within the proteus simulator, giving a 12-bit oversampling of the device's 10-bit ADC:

The ADC_Oversampling.inc library file can be downloaded from here: Positron - ADC_OverSampling.zip, and it also contains the demonstration programs and the proteus simulation project files.