This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Setting longer acquisition times and slower sampling for SAADC

Hello,

To preface, this project was redirected to me and I have very little experience using SoCs. Happy to provide any additional information and apologies for issues in post quality.

We are currently using the onboard SAADC of the nrf52832 as a way to convert the voltage outputs of a TI OPT101 photodiode into digital values for physiological measurements. In this scenario, we tend to measure in very low light settings, caring less about high sampling rates and more for longer acquisition times (~500 us). Moreover, the SoC is controlled by a master module, which sends triggers on when to acquire data. Reading the ADC (as in the code snippet posted below) should only occur when such a trigger is received and only one readout is performed at a time.

When this project was handed to me, it was using an example configuration for a one-channel, single-ended input performing single task measurements. Here is our acquisition code when it was handed to me (minor edits have been made since, but general flow remains the same): 

int32_t readADC(int analog_pin, uint8_t gain) {
  volatile int16_t result = 0;  // the adc outputs signed 16-bit
  volatile float precise_result = 0;
  volatile int32_t sumSamples = 0;

  // Configure SAADC singled-ended channel, Internal reference (0.6V) and 1/6
  // gain.
  NRF_SAADC->CH[0].CONFIG =
      (gain << 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_RESP_Bypass << SAADC_CH_CONFIG_RESP_Pos) |
      (SAADC_CH_CONFIG_TACQ_3us << SAADC_CH_CONFIG_TACQ_Pos);

  // Configure the SAADC channel with Analog input as positive input, no
  // negative input(single ended).
  NRF_SAADC->CH[0].PSELP = (analog_pin + 1) << SAADC_CH_PSELP_PSELP_Pos;

  NRF_SAADC->CH[0].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;

  // Configure the SAADC resolution.
  NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_14bit
                          << SAADC_RESOLUTION_VAL_Pos;

  // Configure result to be put in RAM at the location of "result" variable.
  NRF_SAADC->RESULT.MAXCNT = 1;  // This would increase for multiple channels
  NRF_SAADC->RESULT.PTR = (uint32_t)&result;

  // No automatic sampling, will trigger with TASKS_SAMPLE.
  NRF_SAADC->SAMPLERATE = SAADC_SAMPLERATE_MODE_Task
                          << SAADC_SAMPLERATE_MODE_Pos;

  // Enable SAADC (would capture analog pins if they were used in CH[0].PSELP)
  NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;

  // Take multiple samples.
  for (int i = 0; i < SAMPLES_TO_READ; i++) {
    // Start the SAADC and wait for the started event.
    NRF_SAADC->TASKS_START = 1;
    while (NRF_SAADC->EVENTS_STARTED == 0)
      ;
    NRF_SAADC->EVENTS_STARTED = 0;

    // Do a SAADC sample, will put the result in the configured RAM buffer.
    NRF_SAADC->TASKS_SAMPLE = 1;
    while (NRF_SAADC->EVENTS_END == 0)
      ;
    NRF_SAADC->EVENTS_END = 0;

    sumSamples = sumSamples + result;
  }
  sumSamples = sumSamples / SAMPLES_TO_READ;
	//sumSamples = sumSamples;

  NRF_SAADC->TASKS_STOP = 1;
  while (NRF_SAADC->EVENTS_STOPPED == 0)
    ;
  NRF_SAADC->EVENTS_STOPPED = 0;
	
	return sumSamples;
  //return result;  // result only stored a single measurement. Instead, return sumSamples
}

In my reading, I saw that the SAADC supports up to 40 us acquisition times, and even that is well below our target range. Is there a way to define additional acquisition times that would suit our needs? 

Thank you very much for any feedback and assistance.

Parents
  • Hi Jared, 

    Thank you very much for your insight. My understanding is that oversampling is done through taking multiple samples and then averaging. Is that correct? I believe have done that "manually" in a sense in the for loop of acquisition script. 

    Best,

    Ed

  • edwardx said:
    Thank you very much for your insight. My understanding is that oversampling is done through taking multiple samples and then averaging. Is that correct? I believe have done that "manually" in a sense in the for loop of acquisition script. 

    That is correct, and indeed the loop that you have implements some form of oversampling. How is the sampled voltage versus the actual signal?

  • Hi Jared,

    That's a fair question. I'm working on placing a spare photodiode on a breakout board to measure voltages as the current ones are built into our modules. I can comment more on that once I have some data, but effectively we are only sampling noise at the moment. Our current setup samples with both the light on and off and those values overlap with each other.

    My uncertainty lies in whether a longer sampling time would help at the moment, particularly after finding (this post in the DevZone). If the ADC is not able to measure differences in the acquired signal, would sampling for longer be helpful (in constant light on the photodiode conditions). Any insight would be greatly appreciated.

    Thank you.

  • Photodiode current measurement? Don't need the SAADC although it can of course be used.

    Try reverse-biasing the photodiode with a port pin pulse; that charges the pin (and photodiode) to Vcc (say) 3 volts if you set up the pin in output mode and drive the pin high. Then turn the pin into an input and wait until the logic level changes from a '1' to a '0' as the charge slowly decays at a rate dependent on illumination level at the photodiode. This can of course be read with the SAADC, but that is not really necessary unless you want more accurate measurements. Time the decay with the RTC, and now you have a solution that takes only a few uA.

    Dynamic range? 200 very bright light to over 1,000,000 very dim light. Very low light is much easier to measure, but keep in mind opto-electronic devices may require temperature measurement and compensation.

    Oh, and series resistor? Don't bother; just use output mode S0D1 with pull-up (14k) enabled, then remember to disable the pull-up when switching to input mode as the pull-up applies to the pin always until disabled (datasheet errata). In fact don't even bother with output mode on the pin; just select input mode for the pin, enable the pull-up for a while long enough to reverse charge the photodiode, then disable the pull-up; repeat for each reading. Brilliant.

    Edit: Ok I see it's the OPT101 which is not a photodiode, it's a pricey photodiode with transimpedance amplifier. No worries, just don't connect pin 4 to pin 5 which will mess things up, simple photodiode is a better option though. That spare LED on the board would work just as well for about 1,000 times less cost; an LED reversed biased is a simple photodiode in disguise.

    /*
     * I use a charge-discharge measurement which provides a range of about 30uSecs to 250mSecs;  a
     * count of 100 is very bright light, with an active-high oscilloscope pulse of about 30uSecs
     * in length; a count of 1,000,000 is pretty dark, with a pulse (limited to) about 250mSecs in
     * length.
     */

Reply
  • Photodiode current measurement? Don't need the SAADC although it can of course be used.

    Try reverse-biasing the photodiode with a port pin pulse; that charges the pin (and photodiode) to Vcc (say) 3 volts if you set up the pin in output mode and drive the pin high. Then turn the pin into an input and wait until the logic level changes from a '1' to a '0' as the charge slowly decays at a rate dependent on illumination level at the photodiode. This can of course be read with the SAADC, but that is not really necessary unless you want more accurate measurements. Time the decay with the RTC, and now you have a solution that takes only a few uA.

    Dynamic range? 200 very bright light to over 1,000,000 very dim light. Very low light is much easier to measure, but keep in mind opto-electronic devices may require temperature measurement and compensation.

    Oh, and series resistor? Don't bother; just use output mode S0D1 with pull-up (14k) enabled, then remember to disable the pull-up when switching to input mode as the pull-up applies to the pin always until disabled (datasheet errata). In fact don't even bother with output mode on the pin; just select input mode for the pin, enable the pull-up for a while long enough to reverse charge the photodiode, then disable the pull-up; repeat for each reading. Brilliant.

    Edit: Ok I see it's the OPT101 which is not a photodiode, it's a pricey photodiode with transimpedance amplifier. No worries, just don't connect pin 4 to pin 5 which will mess things up, simple photodiode is a better option though. That spare LED on the board would work just as well for about 1,000 times less cost; an LED reversed biased is a simple photodiode in disguise.

    /*
     * I use a charge-discharge measurement which provides a range of about 30uSecs to 250mSecs;  a
     * count of 100 is very bright light, with an active-high oscilloscope pulse of about 30uSecs
     * in length; a count of 1,000,000 is pretty dark, with a pulse (limited to) about 250mSecs in
     * length.
     */

Children
  • Thanks to everyone for their helpful replies. I will have to try out what hmolesworth suggested, though that may take some time to implement.

    Somewhat on the same thread of that though, is there information regarding the (effective) time constant for the saadc? I know the figure provided in the documentation (Figure 6 here) is a simplified representation, but I imagine that the capacitor in the saadc does specific properties. From what I can tell, the lowest voltage from our photodiode is 7.5 mV and I was wondering if we are simply measuring too low of a voltage change.

    Also, is there way to identify the source resistance for a given part? For example if we're connecting the OPT 101 directly to the ADC?

    Thank you!

Related