News:

;) This forum is the property of Proton software developers

Main Menu

Accuracy on 12 bit ADC 16f1788

Started by geoff_c, Jul 01, 2026, 07:29 PM

Previous topic - Next topic

geoff_c

So im taking a differential signal from iso224 and feeding it to AN0 and AN1 on the pic I am using a external reference and have it enabled.The external ref is 4.096 v measured on a reliable meter (4.095) I measure the differential input going to the pic and compare it to the reading displayed on a lcd and its always about 4 % Low even at different levels and even different boards. Isnt that to high of error, i was expecting less than 1 %. Ive tried different clock rates and really no different. I can get the board to function by appling a cal factor but wanted to make sure eventhing was good first. TIA.
Geoff

trastikata

What is the source and reference impedance? For higher accuracy you may need to buffer the Vref and analog inputs.

Also the did you use dedicated analog ground trace or just the digital ground plane/traces? Another thing to consider is capacitor charging time, did you set it properly?

geoff_c

The differential signal comes from the ISO224 through 100 ohms in each leg and a 10 nf cap across for noise. the A side of the ISO224 is a isolated suppy and the b side is the same 5 v that goes to the pic, so i thought i could join both pin 8 Vss & pin 19 Vss together.


    DelayUS 200

    ADCON0.1 = 1

    While ADCON0.1 = 1
        KickWatchdog()
    Wend

    AdcU = 0
    AdcU = AdcU + (ADRESH << 8)
    AdcU = AdcU + ADRESL

I have 200 us set for precharge , itried higher but it didnt change. Im not buffering the v ref theres a 1 uf cap on the output of v ref and then right into the pic. I use a ref 3040eaidbzr reference and clock is set to Fosc/16

Thanks

trastikata

Hi,

I was talking about the Tad and Fosc relationship. Check in the datasheet ADC CLOCK PERIOD (TAD) VS. DEVICE OPERATING FREQUENCIES and ADC Acquisition Requirements paragraphs.

geoff_c

Thanks. Fosc is 16 Mhz and im using Fosc /16 so its in the recommended range. What accuracy would you expect.

top204

#5
I have found the ADC peripheral on the 8-bit devices to be rather inaccurate, in the years I have used them, and that is mainly because of the ADC type used in them.

However, they are not too inaccurate for most applications, and the PIC16F1877 device is now quite an old type (2015/2016), without some of the extras in the ADC peripherals now available in the more recent devices, especially the 18F types.

If better accuracy is required, you will need to use an ADC peripheral chip. Or try one of the newer devices with the ADCC peripheral, because they are more accurate.

Try different clock settings for the ADC, and also alter the time to wait between ADC readings, to give the capacitor time to stabilise, and also remember, too high a resistance on the ADC's channel input will not allow the internal capacitor to charge/discharge correctly, this giving an inaccurate reading.

I created the code template below so that the ADC clock can be changed easily when it is initialised, and the time between samples can be altered in the ADC_Read12 procedure.

It is 'untested', because I do not have a PIC16F1788 device, but it looks OK based upon the datasheet specs:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' A template code listing to operate a PIC16F1788 device with its internal oscillator at 16MHz.
' Reads the ADC in 12-bit mode and transmits the results to a serial terminal.
'
' **** Untested because a PIC16F1788 device is not available, so created from the datasheet specs. ****
'
' Written for the Positron8 compiler by Les Johnson.
' https://sites.google.com/view/rosetta-tech/positron-compilers-experimenters-notebook.
'
    Device = 16F1788                                                ' Tell the compiler what device to compile for
    Declare Xtal = 16                                               ' Tell the compiler what frequency the device is operating at (in MHz)
    Declare Auto_Heap_Arrays = On                                   ' Tell the compiler to create arrays above standard variables, so assembler code is more efficient
    Declare Auto_Heap_Strings = On                                  ' Tell the compiler to create Strings above standard variables, so assembler code is more efficient
    Declare Auto_Variable_Bank_Cross = On                           ' Tell the compiler to create any multi-byte variables in the same RAM bank. For more efficiency
'
' Setup USART1
'
    Declare Hserial_Baud = 9600                                     ' Set the Baud rate for USART1
    Declare HSerout1_Pin = PORTC.6                                  ' Tell the compiler the pin for USART1 Tx
'
' ADC Clock values for the ADC_Init procedure
'
$define cADC_FRC       %00000011                                    ' Use the FRC clock, which is supplied from a dedicated FRC oscillator
$define cADC_FOscDiv2  %00000000                                    ' Use the FOSC/2
$define cADC_FOscDiv4  %00000100                                    ' Use the FOSC/4
$define cADC_FOscDiv8  %00000001                                    ' Use the FOSC/8
$define cADC_FOscDiv16 %00000101                                    ' Use the FOSC/16
$define cADC_FOscDiv32 %00000010                                    ' Use the FOSC/32
$define cADC_FOscDiv64 %00000110                                    ' Use the FOSC/64
'
' Create any global variables, constants and aliases here
'
    Dim wADC_Value As Word                                          ' Holds the value read from the ADC
   
'------------------------------------------------------------------------------------------
' The main program starts here
' Read the ADC peripheral and transmit the results to a serial terminal
'
Main:
    Setup()                                                         ' Setup the program and any peripherals

    Do                                                              ' Create a loop
        wADC_Value = ADC_Read12(0)                                  ' Read the 12-bit ADC peripheral
        HRSOut1Ln "wADC_Value = ", Dec wADC_Value                   ' Transmit the result to a serial terminal
        DelayMS 500                                                 ' Delay, so that the values do not swamp the terminal
    Loop                                                            ' Do it forever

'------------------------------------------------------------------------------------------
' Read the 12-bit ADC
' Input     : pChan holds the ADC channel to read
' Output    : Returns the 12-bit ADC reading value
' Notes     : None
'
Proc ADC_Read12(pChan As Byte), Word
    Dim wADRES_SFR As ADRESL.Word
 
    pChan = pChan << 2                                              ' Shift the channel bits into the correct position
    ADCON0 = %00000001                                              ' Set for 12-bit mode, and clear the channel bits of ADCON0, and turn on the ADC
    ADCON0 = ADCON0 | pChan                                         ' Or in the channel bits of ADCON0
    DelayUS 20                                                      ' Acquisition time delay (change as required)
    ADCON0bits_GO_DONE = 1                                          ' Start the conversion
    Repeat: Until ADCON0bits_GO_DONE = 0                            ' Wait for the conversion to finish 
    Result = wADRES_SFR                                             ' Return the result
EndProc

'------------------------------------------------------------------------------------------
' Initialise the ADC peripheral
' Input     : pClk sets the ADC clock to use:
'              6 = FOSC/64 (or use cADC_FOscDiv64)
'              5 = FOSC/16 (or use cADC_FOscDiv16
'              4 = FOSC/4  (or use cADC_FOscDiv4
'              3 = FRC     (or use cADC_FRC)
'              2 = FOSC/32 (or use cADC_FOscDiv32
'              1 = FOSC/8  (or use cADC_FOscDiv8
'              0 = FOSC/2  (or use cADC_FOscDiv2
'           : pFVR holds 1 if the FVR is used for +Vref. 0 for VDD used for +Vref
' Output    : None
' Notes     : For a PIC16F1788
'
Proc ADC_Init(pClk As Byte, pFVR As Bit)
    ADCON0 = %00000001                                              ' ADRMD is set for 12-bit mode. ADC enabled. CHS is defaulted to AN0
    If pFVR = 1 Then
        ADCON1 = %10000001                                          ' ADFM set for 2s complement. -VREF is VSS. +VREF is FVR
    Else
        ADCON1 = %10000000                                          ' ADFM set for 2s complement. -VREF is VSS. +VREF is VDD
    EndIf
    pClk = pClk << 4                                                ' Move the clock bits into the correct position
    ADCON1 = ADCON1 | pClk                                          ' Or in the Clock bits (bits 4 to 6)
    ADCON2 = $00
    ADRESL = 0
    ADRESH = 0
EndProc

'------------------------------------------------------------------------------------------
' Set the ADC FVR for 4.096 Volts on a PIC16F1788
'
Proc FVR_4096()
    FVRCONbits_ADFVR1 = 1                                           ' \ ADC Fixed Voltage Reference Peripheral Output is 4x (4.096V)
    FVRCONbits_ADFVR0 = 1                                           ' /
EndProc

'------------------------------------------------------------------------------------------
' Set the ADC FVR for 2.048 Volts on a PIC16F1788
'
Proc FVR_2048()     
    FVRCONbits_ADFVR1 = 1                                           ' \ ADC Fixed Voltage Reference Peripheral Output is 2x (2.048V)
    FVRCONbits_ADFVR0 = 0                                           ' /
EndProc

'------------------------------------------------------------------------------------------
' Set the ADC FVR for 1.024 Volts on a PIC16F1788
'
Proc FVR_1024()    
    FVRCONbits_ADFVR1 = 0                                           ' \ ADC Fixed Voltage Reference Peripheral Output is 1x (1.024V)
    FVRCONbits_ADFVR0 = 1                                           ' /
EndProc

'------------------------------------------------------------------------------------------
' Disable the ADC FVR on a PIC16F1788
'
Proc FVR_Off()    
    FVRCONbits_ADFVR1 = 0                                           ' \ ADC Fixed Voltage Reference Peripheral Output is off
    FVRCONbits_ADFVR0 = 0                                           ' /
EndProc   
 
'------------------------------------------------------------------------------------------
' Enable the FVR on a PIC16F1788
' Input     : None
' Output    : None
' Notes     : Sets the ADC FVR for 4.096 Volts
'  
Proc FVR_Init()  
    FVRCON = %10100011                                              ' CDAFVR off. FVREN enabled. TSRNG Lo_range. ADFVR 4x. TSEN enabled
    Repeat: Until FVRCONbits_FVRRDY = 1                             ' Wait for the FVR to become stable
EndProc

'------------------------------------------------------------------------------------------
' Initialise the oscillator
' Input     : None
' Output    : None
' Notes     : For a PIC16F1788
'
Proc Osc_Init()   
    OSCCON  = $78                                                   ' SCS is FOSC. SPLLEN is disabled. IRCF is 16MHz
    OSCSTAT = $00
    OSCTUNE = $00
    BORCON  = $00
    DelayMS 100                                                     ' A delay to wait for the oscillator to stabilise
EndProc

'------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup() 
    Osc_Init()                                                      ' Initialise the oscillator
    FVR_Init()                                                      ' Setup the FVR for the default 4.096 volts
    PinInput PORTA.0                                                ' Set the ADC channel to input
    ANSELA.0 = 1                                                    ' Set pin AN0 as analogue input   
    ADC_Init(cADC_FOscDiv64, 0)                                     ' Setup the ADC to use the FOsc/64 clock, and VDD as +Vref
EndProc

'------------------------------------------------------------------------------------------
' Setup the config fuses for an internal oscillator on a PIC16F1788 device.
' OSC pins are general purpose I/O lines.
'
    Config1 FOSC_INTOSC,_                                           ' INTOSC oscillator. I/O function on CLKIN pin
            WDTE_OFF,_                                              ' Watchdog Timer disabled
            PWRTE_OFF,_                                             ' Power-up Timer disabled
            MCLRE_ON,_                                              ' MCLR/VPP pin function is MCLR
            CP_OFF,_                                                ' Program memory code protection is disabled
            CPD_OFF,_                                               ' EEPROM code protection is disabled
            BOREN_ON,_                                              ' Brown-out Reset enabled
            CLKOUTEN_OFF,_                                          ' Clock Out function is disabled. I/O or oscillator function on the CLKOUT pin
            IESO_ON,_                                               ' Internal/External Switchover mode is enabled
            FCMEN_ON                                                ' Fail-Safe Clock Monitor is enabled

    Config2 WRT_OFF,_                                               ' Flash Memory Self-Write protection off
            VCAPEN_OFF,_                                            ' Voltage Regulator Capacitor functionality is disabled on RA6
            PLLEN_OFF,_                                             ' PLL disabled
            STVREN_ON,_                                             ' Stack Overflow or Underflow will cause a reset
            BORV_LO,_                                               ' Brown-out Reset Voltage (Vbor). Low trip point selected
            LPBOR_OFF,_                                             ' Low Power Brown-Out Reset is disabled
            LVP_ON                                                  ' Low-voltage programming enabled

If the accuracy keeps being too low, you could try averaging multiple ADC samples, in a moving average filter, or a median filter using a bubble-sort mechanism.

Regards
Les


trastikata

Hi,

what is the output drive current for the external reference, is it buffered, this is important too.

david

Is it worth swapping the ADC's Vref to Vdd and seeing if you still get 4% low?  You'll have to do a bit of maths.
4% is pretty disappointing - could do that with a 5 bit ADC.

geoff_c

Sorry been away for a few days. I tried all the settings that Les had listed in the program and still same 4 % low. I changed from the external ref to using VCC and doing the math still 4 % low. Thought maybe source impedance from the iso224 . COuld not find a real spec so did some measurements and it is significant. Had a noise filter between The ISO224 and the PIC took that out make just a tiny bit of difference. Ive put 2 boards together and they both do the exact same. Wondering if this has something to do with how the differential inputs are working. Im going to try using a single input and seeing if i get the 4% low yet.

top204

4% error is very high, and I have never seen a device's ADC be so inaccurate.

Convert the ADC reading to a voltage in the device, and measure the voltage in, and the voltage converted.

Also... Never take the first ADC reading, always make a few dummy reading, then start taking the real reading. This is why I always use a median (bubble sort) filter for ADC readings in applications, so that anomalous readings will be moved out of the centre of the filter array. Then after a few readings, the centre of the array holds the most common ADC reading. i.e. median (middle), so average.

Regards
Les

GDeSantis

This is what Google AI has to say about PIC MCU ADC Accuracy:

Typical ADC accuracy in PIC microcontrollers is defined by the Total Unadjusted Error (TUE). It generally ranges from ± 1.5 to ± 5 LSB (Least Significant Bits). This means that while a 12-bit ADC provides 4,096 distinct steps, the absolute measurement accuracy usually corresponds to an effective 8 to 10 bits.

Several specific factors dictate this accuracy:

Total Unadjusted Error (TUE): This represents the worst-case combined error of offset, gain, and non-linearity, typically falling between 0.05% and 0.2% of the full-scale range.

Voltage Reference Stability: Relying on the microcontroller's supply voltage or internal references (e.g., FVR) introduces errors of ± 1% to ± 5%, as they drift with load and temperature. Using an external voltage reference like the TL431 or a precision reference IC drastically improves absolute accuracy.
 
Source Impedance: Microchip typically requires analog sources to have an impedance of < 10 kΩ. Higher impedances prevent the internal sampling capacitor from charging fully within the acquisition window, leading to erroneous drops in reading.
Noise & Substrate Ringing: Digital switching and high clock speeds create electrical noise on the MCU substrate, which directly degrades measurement repeatability.

To maximize PIC ADC accuracy in your circuit, it is highly recommended to:

Software Calibration: Compensate for offset and gain errors in your code using a multi-point calibration curve.
Oversampling & Decimating: Average multiple samples to reduce Gaussian noise and increase the Effective Number of Bits (ENOB).
Use an External Reference: Bypass the Vdd supply rails by utilizing a dedicated, low-drift, precision voltage reference connected to the MCU's Vref pin.

If you want to dive deeper, tell me:

What specific PIC microcontroller model are you using?
What analog signal or sensor are you trying to measure?
I can give you specific component recommendations or configuration tips tailored to your circuit.

david

Instead of your differential noise filter capacitor you could try capacitors to ground on each analog output of the ISO224. (after the resistors)
As a last resort you also have the option of taking two single ended readings and subtracting them but be aware this will only be accurate for slow moving signals due to the delay caused by mux settling time and the analog reading.
The micro only has one sample and hold so the differential action is happening prior to this but Microchip don't indicate if it's a real differential amp or if it's a switched capacitor network or whatever.  4% is 1/25 - not even 5 bit accuracy.
Good luck.

Cheers,
David

John Lawton

Have you tried removing the differential capacitor and putting bypass capacitors to 0V for both ADC inputs? The caps hold a charge giving a low impedance drive to the ADC. See if this affects your readings.

John
-----------------------------------------------------------------------------------------
Amicus 8 and 16A/16B dev boards
Especially created for Positron development
https://www.easy-driver.co.uk/Amicus