ADC Gain with bias voltage

I have a single-ended signal that spans [0V,3.3V] and is centered around V_bias=1.65V (=3.3V/2).

I want to use the NRF54L internal gain to amplify only the AC parts of the signal and then add back V_bias so that my signal stays in the range [0V,3.3V]. This way the signal is still centered around V_bias.

I don't know what type of amplifier is internal to the gain stage of the NRF54L SAADC so it is difficult for me to configure external circuitry (e.g. bias resistors, ac-coupling capacitors) appropriately.

Some guidance on how to do this from a hardware and software perspective this would be appreciated.

Parents
  • I do something similar, but note this is an unofficial response. The input amplifier after the Mux and the following SAADC both have true-differential inputs. This allows a simple resistor divider from 3.3V to provide the 1.65V (3.3/2) negative input, with the signal applied to the positive input such that the SAADC only sees the AC voltage being the difference between signal and bias. The output value is then the AC voltage with no DC (bias) offset scaled up with as large a gain setting allowed that keeps the AC peak-peak within the VDD range; small AC signal variations allow a very large gain, but if the AC component moves the signal towards VDD or Gnd then a gain of x1 or x2 would be required.

    The resistor chain should model the sensor excitation; ideally the sensor excitation has no filter capacitors and uses nRF54 VDD directly, in which case the resistor divider should also have no filter capacitors as a ratiometric result is required by ensuring VDD noise couples equally to both sensor and resistor negative reference. If the sensor uses other than VDD, the resistor divider should be attached to the sensor supply and not VDD. The impedance/capacitance of the sensor (if any) should be matched by the resistor divider.

    Applying an SAADC calibration periodically will introduce a discontinuity in the sensor AC reading with the same period. Preferably minimise calibration steps and instead remove residual SAADC offsets by reversing the SAADC pins (in software) used for the differential measurement - so take two measurements of opposite polarity - and average the results. I show an example below.

    The nRF52 series of SAADC have internal analogue resistors which allows internal VDD/2 as the negative input without requiring the external resistors, but that is not documented on the nRF54L15.

    If the amplified SAADC output value requires restoring to 1.65V bias level, simple do a dual-differential measurement on the resistor reference input and Gnd input preferably with the latter on external Gnd at the resistor but if no spare pins then internal Gnd (dual means reversing the inputs as described above for the AC signal). Restored value is then obtained simply by addition of the measured reference.

    No coupling capacitors are required; resistor and sensor impedance only affects sampling time required.

    This is an example of removing residual SAADC offset by reversing the inputs (via PSEL registers) in SAADC differential mode. Measured 14-bit value of 905.0 Ohm 2-Wire test resistor using 128 samples is 904.9 Ohms with -0.01% error

    // Internal 0.6v Ref 14-bit S0S1 2-Wire RTD actual value 905.0 Ohm (reference 2001.4 Ohm) - Pos-Neg ADC
    // VDD mV Iref uA  RdsOn  V1 mV V2 mV V3 mV    Vrtd    Vref   Rrtd  Error
    // ====== ======= ======  ===== ===== =====  ======  ====== ====== ======
    //   2974    1008     41   2932   911    -3  3108.5  6883.3  903.8 -0.13%
    //   2975    1007     41   2933   912    -2  3108.3  6882.6  903.8 -0.13%
    //   2974    1007     37   2936   913    -2  3107.9  6882.4  903.8 -0.14%
    //   2976    1007     42   2933   914    -2  3108.5  6882.0  904.0 -0.11%
    //   2973    1007     38   2934   911    -2  3108.3  6881.3  904.0 -0.11%
    //   2977    1007     42   2934   911    -1  3110.3  6882.9  904.4 -0.07%
    //   2978    1008     40   2937   913    -1  3110.1  6884.8  904.1 -0.10%
    //   2977    1008     40   2936   913    -1  3109.8  6885.3  903.9 -0.12%
    //
    // Internal 0.6v Ref 14-bit S0S1 2-Wire RTD actual value 905.0 Ohm (reference 2001.4 Ohm) - Neg-Pos ADC
    // VDD mV Iref uA  RdsOn  V1 mV V2 mV V3 mV    Vrtd    Vref   Rrtd  Error
    // ====== ======= ======  ===== ===== =====  ======  ====== ====== ======
    //   2977    1010     41   2935   912    -3  3120.1  6896.4  905.5  0.05%
    //   2976    1009     40   2935   912    -2  3119.5  6893.8  905.7  0.07%
    //   2973    1009     39   2933   912    -2  3119.4  6892.3  905.8  0.09%
    //   2976    1010     40   2935   911     0  3121.9  6896.3  906.0  0.11%
    //   2975    1010     39   2935   913    -2  3120.9  6896.3  905.7  0.08%
    //   2975    1009     40   2934   911    -1  3120.0  6891.4  906.1  0.12%
    //   2976    1009     41   2934   912     0  3118.6  6892.3  905.6  0.07%
    //   2975    1009     38   2936   913    -1  3118.3  6891.4  905.6  0.07%
    //
    // Measured 14-bit value of 905.0 Ohm 2-Wire test resistor using 128 samples is 904.9 Ohms with -0.01% error
    

  • Thanks for the the detailed response. I really appreciate the effort to help.

    I tried switching my ADC sampling to differential mode with V_bias as the negative input. That seems to be a working fine.

    I have follow-up questions:

    1. Here is the Digital output formula below:
    RESULT = [V(P) – V(N) ] * GAIN/REFERENCE * 2^(RESOLUTION - m)
    If the difference V(P) - V(N) is negative, will the RESULT be be clipped to 0?

    2. The gain stage happens before the SAR core block and after the mux selection.
    Is the gain applied to the difference or independently to each P and N input? If the gain is applied independently to each input P and N, then wouldn't there be a chance of clipping to the IC supply rail after amplification?

    3. I prepare the bias voltage V_bias through a resistor divider circuit. I would like the resistors to be very high value to prevent excess current. What is the current requirement for the ADC input pins?

Reply
  • Thanks for the the detailed response. I really appreciate the effort to help.

    I tried switching my ADC sampling to differential mode with V_bias as the negative input. That seems to be a working fine.

    I have follow-up questions:

    1. Here is the Digital output formula below:
    RESULT = [V(P) – V(N) ] * GAIN/REFERENCE * 2^(RESOLUTION - m)
    If the difference V(P) - V(N) is negative, will the RESULT be be clipped to 0?

    2. The gain stage happens before the SAR core block and after the mux selection.
    Is the gain applied to the difference or independently to each P and N input? If the gain is applied independently to each input P and N, then wouldn't there be a chance of clipping to the IC supply rail after amplification?

    3. I prepare the bias voltage V_bias through a resistor divider circuit. I would like the resistors to be very high value to prevent excess current. What is the current requirement for the ADC input pins?

Children
  • !. The RESULT is a signed integer and will be a negative value where V(P)-V(N) is negative. Reversing the inputs to the differential amplifier via PSEL will give a similar signed positive value where the absolute difference would be due to residual offset

    2. The gain is applied to the difference between V(P) and V(N) (so no rail clipping)

    3. The input impedance when the SAADC is connected via the Mux is quoted as >1M. High value resistors may be used to generate the bias if this is noted but ideally the impedance should not be hugely different to the sensor. The sampling time can be adjusted to match the impedance, and worst-case a capacitor can be used to hold up the bias while the SAADC samples and measures but this will impact CMR (Common Mode Rejection) of noise on the supply affecting both sensor and bias which ideally should have the same noise. Often it is better to use lower bias resistor values (say 220K) and turn off the bias supply when not actively measuring to reduce current drain, perhaps by driving the bias voltage from an output port set to L0H1.

    This is an example of the SAADC taken from the nRF52833 which is (mostly) similar to the nRF54L15 except the 160k optional bias resistors are not documented on the latter.

    // RTD Measurement - 2- and 3-wire Unidirectional current
    //                          + ----------------------------------------------------------------------+
    //                          |  nRF52832/nRF52833/nRF52840                                           |
    //                          |                                                                       |
    //                          |    VDD               VDD                 VDD                          |
    //                          |   --#--             --#--               --#--                         |
    //                          |     |                 |                   |                           |
    //                          |   __|__               |   Excitation      |   Option                  |
    //                          |    / \                +-|   On            +-|   13k                   |
    //                          |   /-+-\                 |<- H0   H1         |<- Pullup                |
    //                          |     |                 +-|   160R 35R      +-|   (not used)            |
    //         Excitation  P0.04|     |                 |                   |                           |
    //         +----------------O-----#------#----------#-------------------#------------ In            |
    //         |                |     |      |          |   Excitation      |   Option                  |
    //         |                |   __|__  -----        +-|   Off           +-|   13k                   |
    //         |                |    / \   -----          |<- L0   L1         |<- Pulldown              |
    //         |                |   /-+-\    |3pF       +-|   160R 35R      +-|   (not used)            |
    //         |                |     |      |          |                   |                           |
    //         |                |   =====  =====      =====               =====                         |
    //         |                |    ===    ===        ===                 ===                          |
    //         |                |     =      =          =                   =                           |
    //         |                |                                                                       |
    //         |                |    VDD                                         VDD                    |
    //         |                |   --#--                                       --#--                   |
    //         |                |   __|__                    Auto-adjust:         |   160k              |
    //         |                |    / \                     Bias inputs at VDD/2 +-| Bias Hi/Lo/Both   |
    //         |                |   /-+-\                     VDD1_2                |<-                 |
    //         |                |     |   PIN_VOLTAGE_1                           +-|                   |
    //         |          P0.28 |     |   AIN4          |----'T'-Mux-P---|        |            P SAADC  |
    //         #--------#-------O-----#------#-------#--+ +----#-------+ +--#-----#------#------#-->    |
    //         |        |       |     |      |       |  | |    |       | |  |     |      |      |       |
    //         |        |       |   __|__  -----     |  ----   +-|    ----- |     +-|   +++     |       |
    //         |        |       |    / \   -----     |   |       |<--+  |   |       |<- | |   -----     |
    //  Rref  +++ 100nF |       |   /-+-\    | 3pF   |   |     +-|   |  |   |     +-|   | |   -----     |
    //  2k0   | |     -----     |     |      |       |   +---- |--------#   |     |     +++     | 2.5pF |
    //  1%    | |     -----     |   =====  =====     |         |     |  |   |     |      |1M0   |       |
    // (0.1%) +++       |       |    ===    ===      |       =====   |  |   |   =====  =====  =====     |
    //         |        |       |     =      =       |        ===    |  |   |    ===    ===    ===      |
    //         |        |       |                    |         =    Select  |     =      =      =       |
    //         |        |       |                    |                      |                           |
    //         |        |       |                    |                      |    VDD                    |
    //         |        |       |                    |  |----'T'-Mux-N---|  |   --#--                   |
    //         |        |       |                    +--+ +----#-------+ +----+   |   160k              |
    //         |        |       |                                           | |   +-| Bias Hi/Lo/Both   |
    //         |        |       |         PIN_VOLTAGE_2                     | |     |<-                 |
    //         |        | P0.29 |         AIN5          |----'T'-Mux-----|  | |   +-|                   |
    //         #--------#-------O-----#------#-------#--+ +----#-------+ +--# |   |            N SAADC  |
    //         |        |       |                    +--+ +----#-------+ +----#---#------#------#-->    |
    //    RTD +++ 100nF |       |                                           | |   |      |      |       |
    //        | |     -----     |                                           | |   +-|   +++     |       |
    //        | |     -----     |          Note V3 can be negative          | |     |<- | |   -----     |
    //        +++       |       |          PIN_VOLTAGE_3                    | |   +-|   | |   -----     |
    //         |        | P0.31 |          AIN7         |----'T'-Mux-----|  | |   |     +++     | 2.5pF |
    //         #--------#-------O-----#------#-------#--+ +----#-------+ +--# |   |      |1M0   |       |
    //         |                |                    +--+ +----#-------+ +----# =====  =====  =====     |
    //         |                |                                           | |  ===    ===    ===      |
    //         |                |                                           | |   =      =      =       |
    //         |        VDD     |    VDD                                    | |                         |
    //         |       --#--    |   --#--                                   | |   P: Single-ended       |
    //         |         |      |     |                                     | |   P&N: Differential     |
    //         |         +------O-----#   PIN_VOLTAGE_0                     | |                         |
    //         |                |     |   AIN_VDD       |----'T'-Mux-----|  | |                         |
    //         |                |     +-----#--------#--+ +----#-------+ +--+ |                         |
    //         |                |                    +--+ +----#-------+ +----#                         |
    //         |                |                                             |                         |
    //         |                |    GND                |----'T'-Mux-----|    |                         |
    //         #----------------O-----#-----------------+ +----#-------+ +----+                         |
    //         |                |     |                                                                 |
    //       =====              |   =====                                                               |
    //        ===               |    ===                                                                |
    //         =                |     =                                                                 |
    //                          +-----------------------------------------------------------------------+
    //
    // Capacitor min value = (3pF+2.5pF)*2^14=80nF
    // Vref = differential voltage V1 and V2 in SAADC counts
    // Vrtd = differential voltage V2 and V3 in SAADC counts
    // Rrtd = (Vrtd * Rref)/Vref Ohms
    

    I don't know if this will help or just confuse, but here is some sample code to alternate between measurements using PSEL, For slow AC signals this is fine, but higher frequencies will require faster sampling between alternate sample to minimise phase delay which will introduce errors. High AC frequencies may preclude the use of the dual-measurement offset compensation technique.

    // SAADC on nRF54L15
    // =================
    // CH[n].CONFIG.BURST can be enabled to avoid setting SAMPLE task 2^OVERSAMPLE times. With BURST = 1 the
    // ADC will sample the input 2^OVERSAMPLE times as fast as it can (actual timing: <(tACQ+tCONV)*2^OVERSAMPLE).
    // ie similar to one-shot mode
    // Noise shaping is implemented by using the SAR-ADC in a first-order delta-sigma loop and the filtering
    // is done with the use of FIR filters. The sampling rate in these modes is 1MS/s, and only 12 and 14 bit
    // resolution are supported. To enable noise shaping use NOISESHAPE
    // The ADC can use different reference voltages VREF, controlled in the REFSEL field of the CH[n].CONFIG.
    // These are:
    //   Internal reference, VREF = 0.9 V
    //   External reference, VREF provided by the EXTREF pin
    //    Note: The external reference voltage should be close the internal reference voltage, typ 1.2V
    // nRF54L15 has a TCONV and a different TACQ as well as different gain settings, plus a NOISESHAPE filter
    
    static uint32_t mSAADC_InterruptCount = 0UL;
    void SAADC_IRQHandler(void)
    {
       NRF_SAADC->EVENTS_END = 0;
       __DSB();
       mSAADC_InterruptCount++;
    }
    static int32_t GetRTD_Voltage(uint32_t AnalogueChannelP, uint32_t AnalogueChannelN)
    {
       int32_t result = 9999L;
       uint32_t timeout = 512UL;
       volatile int16_t buffer[8] = {0};
       uint32_t i=0;  // SAADC Channel
       // nRF54L15 has a TCONV and a different TACQ as well as different gain settings, plus a NOISESHAPE filter
       //NRF_SAADC->
       // Can use internal ref SAADC_CH_CONFIG_REFSEL_Internal with 100nF caps or VDD ratiometric SAADC_CH_CONFIG_REFSEL_VDD1_4 with no caps
       // Check if single-ended or differential SAADC mode
       if (AnalogueChannelN == SAADC_CH_PSELN_PSELN_NC)
       {
          // Configure SAADC singled-ended channel, Internal reference (0.6V) and 1/6 gain (range 3.6V), no pull-up or pull-down
          // nRF54L15 has different settings ToDo
          NRF_SAADC->CH[i].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_6    << SAADC_CH_CONFIG_GAIN_Pos)   |
                                    (SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos)   |
                                    (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                                    (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos)   |
                                    (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESP_Pos)   |
                                    (SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos);
       }
       else
       {
          // Configure SAADC differential channel, Internal reference (0.6V) and 1/4 gain (range 2.4V), no pull-up or pull-down
          NRF_SAADC->CH[i].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_4    << SAADC_CH_CONFIG_GAIN_Pos)   |
                                    (SAADC_CH_CONFIG_MODE_Diff       << SAADC_CH_CONFIG_MODE_Pos)   |
                                    (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                                    (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos)   |
                                    (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESP_Pos)   |
                                    (SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos);
          NRF_SAADC->CH[i].LIMIT = 0x0000 | 0x0000;  // High- and Low-limits, signed 16-bit thresholds
       }
       NRF_SAADC->RESOLUTION = SAADC_RESOLUTION << SAADC_RESOLUTION_VAL_Pos;
       NRF_SAADC->CH[i].PSELP = AnalogueChannelP << SAADC_CH_PSELP_PSELP_Pos;
       NRF_SAADC->CH[i].PSELN = AnalogueChannelN << SAADC_CH_PSELN_PSELN_Pos;
       // Note the SAMPLE task must be triggered 2^OVERSAMPLE times for each output value
       NRF_SAADC->OVERSAMPLE = SAADC_OVERSAMPLE_OVERSAMPLE_Over16x;
       // Setup interrupts if required; for limits use CH0LIMITH CH0LIMITL
       NRF_SAADC->INTENSET = SAADC_INTENSET_END_Set << SAADC_INTENSET_END_Pos;
       NVIC_SetPriority(SAADC_IRQn, 6);
       NVIC_ClearPendingIRQ(SAADC_IRQn);
       NVIC_EnableIRQ(SAADC_IRQn);
       // Enable SAADC
       NRF_SAADC->ENABLE = 1;
       NRF_SAADC->RESULT.PTR = (uint32_t)buffer;
       NRF_SAADC->RESULT.MAXCNT = 1;
       //NRF_SAADC->RESULT.AMOUNT
       // Do an initial calibration as the offset shows up in comparing +ve and -ve current measurements
       // Any gain changes require recalibration. This reduces but does not eliminate SAADC offset
       if(!mAmCalibrated)
       {
          NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
          NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
          while (!NRF_SAADC->EVENTS_CALIBRATEDONE) ;
          mAmCalibrated = true;
          // Clear event, though maybe not actually necessary
          NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
       }
       NRF_SAADC->EVENTS_END = 0;
       NRF_SAADC->EVENTS_DONE = 0;
       NRF_SAADC->EVENTS_RESULTDONE = 0;
       NRF_SAADC->TASKS_START = 1;
       // Assume could be up to 256 samples, but don't bother to check actual number
       mSaadcSampleCount = 0;
       while(true)
       {
          NRF_SAADC->TASKS_SAMPLE = 1;
          __DSB();
          while(!NRF_SAADC->EVENTS_DONE) ;
          // Update sample count for reports
          mSaadcSampleCount++;
          if (NRF_SAADC->EVENTS_RESULTDONE || timeout == 0) break;
          NRF_SAADC->EVENTS_DONE = 0;
          timeout--;
       }
       // Check if last result is equal or above CH[i].LIMIT.HIGH or equal or below CH[i].LIMIT.LOW
       if (NRF_SAADC->EVENTS_CH[i].LIMITH)
       {
       }
       // Don't need NRF_SAADC->EVENTS_END for now
       NRF_SAADC->TASKS_STOP = 1;
       NRF_SAADC->EVENTS_STARTED = 0;
       NRF_SAADC->EVENTS_END = 0;
       NRF_SAADC->EVENTS_DONE = 0;
       NRF_SAADC->EVENTS_RESULTDONE = 0;
       // Disable command
       NRF_SAADC->ENABLE = 0;
       // Process result
       if (AnalogueChannelN == SAADC_CH_PSELN_PSELN_NC)
       {
          // Display in mVolts, single-ended values are only for info
          if (timeout != 0)
          {
             // Calculate mVolt value with rounding, later assume convert to 16-bit signed for prints
             result = (((int32_t)buffer[0] * 1000L)+(SAADC_COUNTS_PER_VOLT_SE)/2L) / (SAADC_COUNTS_PER_VOLT_SE);
          }
          return result;
       }
       else
       {
          // Display and calculate differential values in signed SAADC counts for best resolution
          return (int32_t)buffer[0];
       }
    }
    
    usage:
          for (uint32_t i=0; i<AVERAGING_NUMBER; i++)
          {
             // Differential measurements Pos-Neg or Neg-Pos
             if (SampleCount < REPORTS_NUMBER/2)
             {
                Vrtd += GetRTD_Voltage(SAADC_CH_PSELP_PSELP_AnalogInput5, SAADC_CH_PSELN_PSELN_AnalogInput7);
                Vref += GetRTD_Voltage(SAADC_CH_PSELP_PSELP_AnalogInput4, SAADC_CH_PSELN_PSELN_AnalogInput5);
             }
             else
             {
                Vrtd += GetRTD_Voltage(SAADC_CH_PSELP_PSELP_AnalogInput7, SAADC_CH_PSELN_PSELN_AnalogInput5);
                Vref += GetRTD_Voltage(SAADC_CH_PSELP_PSELP_AnalogInput5, SAADC_CH_PSELN_PSELN_AnalogInput4);
             }
          }
          Vrtd_f = Vrtd /(float)AVERAGING_NUMBER;
          Vref_f = Vref /(float)AVERAGING_NUMBER;
    .

Related