ADC Sampling Synchronization using Timer and PPI

I have code set up to configure Timer3 to generate a Compare event at 100 KHz, using the PPI so that triggers an ADC Sample event for the two enabled ADC channels. EasyDMA is configured to capture a run of 4K samples each alternating between 2 ADC channels. Some of the time it works perfectly, but AFAICT at random, it appears to swap the two channels in memory. I can't tell if it's caused by the ADC triggering prematurely so it's one sample out of phase (which would effectively swap the channels in memory), or if there's something else going on.

Please don't tell me to call XYZ function. I don't need yet another layer between me and the hardware, both for more learning curve and more places for errors to creep in. The hardware should work exactly as documented, whether set up by my function or yours.

Here's the raw code that sets up the ADC, Timer, and PPI, and starts the whole ball rolling. The ping-pong buffering is a recent addition to attempt to resolve this issue, but hasn't made any detectable difference. The variable PingPong is toggled by the code that calls this function.

#define Channels 2
typedef int16_t AdcValT;
typedef struct
   {
   AdcValT Ch [Channels];
   }
SnapT;

typedef enum
   {
   ppPing,
   ppPong,
   PingPongs
   }
PingPongT;
PingPongT PingPong;

SnapT Wave [PingPongs] [4096];


// Make sure we don't get any premature triggers
#define AdcPpiChan 7
#define AdcTimer NRF_TIMER3
NRF_PPI -> CHENCLR = 1 << AdcPpiChan;
AdcTimer -> TASKS_STOP = 1;
// Build the parts of WaveInfo that only we know about
WaveInfo.uSecPerPoint = 10;
WaveInfo.uVPerCount [0] = 1000;
WaveInfo.uVPerCount [1] = 1000;
// Set ADC inputs back to reset defaults
NRF_SAADC -> TASKS_STOP = 1;
for (int i = 1000000; i; --i) if (NRF_SAADC -> EVENTS_STOPPED) break;
NRF_SAADC -> ENABLE = 0;
NRF_GPIO -> DIRCLR = 0b10100000000000000000000000010100; // All ADC pins to inputs
for (int i = 0; i < 8; ++i)
   {
   NRF_SAADC -> CH [i].PSELP = adcNoConnect;
   NRF_SAADC -> CH [i].PSELN = adcNoConnect;
   }
// Set up 2 channels to capture samples at 100 KSPS
NRF_SAADC -> CH [0].PSELP = adcCh1P;
NRF_SAADC -> CH [0].PSELN = adcCh1M;
NRF_SAADC -> CH [0].CONFIG = (Range1 << 8) | (1 << 20); // nonburst mode, diffierential, 3 uSec, internal 600 mV ref, specified gain, no pullups
NRF_SAADC -> CH [1].PSELP = adcCh2P;
NRF_SAADC -> CH [1].PSELN = adcCh2M;
NRF_SAADC -> CH [1].CONFIG = (Range2 << 8) | (1 << 20); // nonburst mode, diffierential, 3 uSec, internal 600 mV ref, specified gain, no pullups
NRF_SAADC -> OVERSAMPLE = 0;
NRF_SAADC -> RESOLUTION = 2; // 12 bits
NRF_SAADC -> SAMPLERATE = 0; // Cannot use internal timer for multiple channels 0x1000 + 40; // 100 KSPS per channel
NRF_SAADC -> EVENTS_DONE = 0;
NRF_SAADC -> EVENTS_STARTED = 0;
NRF_SAADC -> EVENTS_END = 0;
NRF_SAADC -> EVENTS_STOPPED = 0;
NRF_SAADC -> EVENTS_RESULTDONE = 0;
// Connect timer output to ADC sampler via PPI
//AdcTimer -> EVENTS_COMPARE [0] = 0;
AdcTimer -> TASKS_CLEAR;
NRF_PPI -> CH [AdcPpiChan].EEP = (uint32_t) &(AdcTimer -> EVENTS_COMPARE [0]);
NRF_PPI -> CH [AdcPpiChan].TEP = (uint32_t) &(NRF_SAADC -> TASKS_SAMPLE);
NRF_PPI -> CHENSET = 1 << AdcPpiChan;
// Set up TIMERn to trigger sampling (multiple channel sampling cannot use internal timer)
AdcTimer -> SHORTS = 1; // auto restart on CC[0]
AdcTimer -> MODE = 0; // Timer
AdcTimer -> BITMODE = 3; // 32 bit timer
AdcTimer -> PRESCALER = 0; // no prescale
AdcTimer -> CC [0] = 16000000 / 100000; //FIXME

NRF_SAADC -> RESULT.PTR = (uint32_t) &(Wave [Which]);
NRF_SAADC -> RESULT.MAXCNT =    sizeof Wave [Which] / sizeof (uint16_t);
NRF_SAADC -> ENABLE = 1;
NRF_SAADC -> TASKS_START = 1;
for (int i = 1000000; i; --i) if (NRF_SAADC -> EVENTS_STARTED) break;
AdcTimer -> TASKS_CLEAR;
AdcTimer -> TASKS_START = 1;

Parents
  • Hello, are you able (for test at least) to reduce the sampling speed (e.g. 10kHz), this to try to narrow down the issue. I am thinking you are updating the PTR at given times and call START task at given time, I would assume the exact time when you do this (asynchronous to the running timer and potential other high priority interrupts) can influence which channel is next to be sampled to the updated PTR. So I guess I would check the RESULT.AMOUNT register if it's possible it sampled an odd amount of values prior to this.

    Kenneth

  • I have already tried reducing the sampling speed, as well as moving around some of the TASKS_START events, along exactly those lines of thought. I will go try saving the RESULT.AMOUNT register at several times throughout the setup. Good idea, that might at least narrow down when the (alleged) extra sample is being taken.

  • Interesting, the workaround is this errata:
    https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_241.html 

    But that one should (at least according to the description) affect the power consumption only, seems it also cleanup something more in this case.

    Kenneth

  • Yes, it was the only way I could get easydma not to occasionally have this issue. The full reset also needs to be done after ADC calibration as that could also trigger it (I'm not 100% whether the reset then wipes the calibration value though!).

  • No idea, I assume it's controlling mosfets supplying power to the various aspects of the ADC hardware but who knows. Glad it worked, knew it would though, I had a personal war with the nrf52 ADC a few year back. Slight smile

  • For some reason my reply didn't go in-line before, I'll try again.

    I do a lot with the ADC, it runs like tasks where the interupt is an end-of-task. I use 810 and RAM is short in supply so I loop the loop sort of speak, that is the easydma runs 256 times instead of 4,000. I did do that in the past but the values I get are multiplied and then required 64 bit maths which hogged the CPU. By doing 256 times and introducing a little jitter I lower CPU usage. It depends what else you need the CPU for and whether at the end you're flip-flopping buffers to keep sampling.

    It's ok if the timer tick starts before START is called, however if you have a PPI free you can wire START to call both SAMPLE and TIMER_START for perfect sync (see PPI Fork). The only reason I don't is I have no PPI's free (I've took nrf52810 to the max!).

    Yes RESULTSDONE to SAMPLE will cause the issue eventually even with the reset code. Sometimes it would take 30 minutes before it became an issue. As my nrf52810 controls hardware based on the reads they had to be 100% or hardware would be destroyed.


  • snoopy20 said:
    It's ok if the timer tick starts before START is called, however if you have a PPI free you can wire START to call both SAMPLE and TIMER_START for perfect sync (see PPI Fork). The only reason I don't is I have no PPI's free (I've took nrf52810 to the max!).

    A very good idea, I at least did not think of that.

    Thanks for chipping in!

    Kenneth

Reply
  • snoopy20 said:
    It's ok if the timer tick starts before START is called, however if you have a PPI free you can wire START to call both SAMPLE and TIMER_START for perfect sync (see PPI Fork). The only reason I don't is I have no PPI's free (I've took nrf52810 to the max!).

    A very good idea, I at least did not think of that.

    Thanks for chipping in!

    Kenneth

Children
No Data
Related