ADC Infuriating Behaviour With Multi-channel Scan Using PPI to RTC

Been on all day at this and ready to smash something! :)

I need to do three 'steps' or 'tasks' which are:

1. read 5 channels
2. read 3 channels, which are the same as above minus two (there's a pin aux)
3. read 2 channels repeatedly filling up one of two banks with additional looping.

For brevity I've removed (3) to just do a single read and then call (1).

Each step works from adc interrupt. In the case of (1) and (2) the SAMPLE is wired to STARTED by PPI. Primarily this is because the RTC tick at 32KHz) is too fast.

Then this PPI is disabled and the RTC TICK EVENT is wired to STARTED instead. This gives 30.5us, and the 2 channels plus overhead are well within it.

Behaviour

The routine(s) loop twice and then end with no further interrupts. I believe the ADC is stuck on (2) because the TICK EVENT isn't being pumped through when the PPI channel is re-enabled.

I think I've tried everything to get this working but maybe another pair of eyes could help! 


extern struct statusProto status;

#define ADC_RESOLUTION SAADC_RESOLUTION_VAL_12bit
#define ADC_SAMPLETIME_CFG_SLOW SAADC_CH_CONFIG_TACQ_40us
#define ADC_SAMPLETIME_CFG_MPPT_VOLTAGE SAADC_CH_CONFIG_TACQ_10us
#define ADC_SAMPLETIME_CFG_MPPT_CURRENT SAADC_CH_CONFIG_TACQ_10us

// ADC takes 2us per channel, 4us + 15us + 5us = 24us

// 32,768Hz = 30.52us
// 667 (fit into ram)*30 = 20ms
#define MPPT_SAMPLES 1

// 200ms total / 20ms
#define MPPT_LOOP 2

enum {
  ADC_CH_USB,
  ADC_CH_FRONT,
  ADC_CH_REAR,
  ADC_CH_STORE,
  ADC_CH_LIGHTSENSE,
  ADC_CH_MPPT_VOLTAGE,
  ADC_CH_MPPT_CURRENT
};

struct mpptReadProto {
  int16_t voltage;
  int16_t current;
};

struct {
  volatile struct {
    int16_t usb;
    int16_t front;
    int16_t rear;
    int16_t storage;
    int16_t lightsense;
  } voltages;
  volatile struct {
    int16_t usb;
    int16_t front;
    int16_t rear;
  } currents;
  struct mpptReadProto banks[2][MPPT_SAMPLES];
} reads;

static void step3n (void);
static void step3 (void);
static void step2 (void);
static void step1 (void);

static void (*step)(void);
static uint8_t loop;
static struct mpptTrackProto track;
static struct mpptReadProto *currentBank;

static void step1 (void) {

  step = &step2;

  NRF_PPI->CHENSET = 1 << CFG_PPI_ADC_SAMPLEONSTART;

  NRF_GPIO->OUTCLR = 1 << CFG_PIN_ADC_MUX;

  NRF_SAADC->CH[ADC_CH_USB].PSELP = CFG_ADC_USB_PSEL;
  NRF_SAADC->CH[ADC_CH_FRONT].PSELP = CFG_ADC_FRONT_PSEL;
  NRF_SAADC->CH[ADC_CH_REAR].PSELP = CFG_ADC_REAR_PSEL;
  NRF_SAADC->CH[ADC_CH_STORE].PSELP = CFG_ADC_STORE_PSEL;
  NRF_SAADC->CH[ADC_CH_LIGHTSENSE].PSELP = CFG_ADC_LIGHTSENSE_PSEL;
  NRF_SAADC->RESULT.PTR = (uint32_t) &reads.voltages;
  NRF_SAADC->RESULT.MAXCNT = sizeof(reads.voltages) / 2;
  NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos; 
}

static void step2 (void) {

  step = &step3;

  NRF_GPIO->OUTSET = 1 << CFG_PIN_ADC_MUX;
  // rather than wait for adc task compute we do here and send as event
  // this is only benefical if the storage read is greater than 500ms (adc task)
  if (reads.voltages.storage < STORAGE_VOLTAGE_CRITICAL_ON) {
    POWERMANAGER_EventStorageCritical();
  }

  NRF_SAADC->CH[ADC_CH_STORE].PSELP = SAADC_CH_PSELP_PSELP_NC;
  NRF_SAADC->CH[ADC_CH_LIGHTSENSE].PSELP = SAADC_CH_PSELP_PSELP_NC;
  NRF_SAADC->RESULT.PTR = (uint32_t) &reads.currents;
  NRF_SAADC->RESULT.MAXCNT = sizeof(reads.currents) / 2;
  NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos; 
}

static void step3 (void) {

  currentBank = &reads.banks[0][0];
  step = &step3n;

  NRF_PPI->CHENCLR = 1 << CFG_PPI_ADC_SAMPLEONSTART;

  NRF_SAADC->CH[ADC_CH_USB].PSELP = SAADC_CH_PSELP_PSELP_NC;
  NRF_SAADC->CH[ADC_CH_FRONT].PSELP = SAADC_CH_PSELP_PSELP_NC;
  NRF_SAADC->CH[ADC_CH_REAR].PSELP = SAADC_CH_PSELP_PSELP_NC;
  NRF_SAADC->CH[ADC_CH_MPPT_VOLTAGE].PSELP = CFG_ADC_MPPT_VOLTAGE_PSEL;
  NRF_SAADC->CH[ADC_CH_MPPT_CURRENT].PSELP = CFG_ADC_MPPT_CURRENT_PSEL;
  NRF_SAADC->RESULT.PTR = (uint32_t) &reads.banks[0];
  NRF_SAADC->RESULT.MAXCNT = sizeof(reads.banks[0]) / sizeof(reads.banks[0][0]);
  NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos;
  NRF_PPI->CHENSET = 1 << CFG_PPI_ADC_SAMPLEBYTIMER;
}


static void step3n (void) {

  // code cut down to remove looping
  NRF_SAADC->CH[ADC_CH_MPPT_VOLTAGE].PSELP = SAADC_CH_PSELP_PSELP_NC;
  NRF_SAADC->CH[ADC_CH_MPPT_CURRENT].PSELP = SAADC_CH_PSELP_PSELP_NC;
  NRF_PPI->CHENCLR = 1 << CFG_PPI_ADC_SAMPLEBYTIMER;
  step1();
}

void SAADC_IRQHandler (void) {

  NRF_SAADC->EVENTS_END = SAADC_EVENTS_END_EVENTS_END_NotGenerated << SAADC_EVENTS_END_EVENTS_END_Pos;
  step();
}

uint32_t ADC_TaskCalibrate (void) {

  NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;
  NRF_SAADC->TASKS_CALIBRATEOFFSET = SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Trigger << SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Pos;
  return CFG_SCHEDULER_TIMER_MINIMUM;
}

uint32_t ADC_TaskStart (void) {

  NRF_SAADC->EVENTS_END = SAADC_EVENTS_END_EVENTS_END_NotGenerated << SAADC_EVENTS_END_EVENTS_END_Pos;
  NRF_SAADC->INTENSET = SAADC_INTENSET_END_Enabled << SAADC_INTENSET_END_Pos;
  
  // Note RTC Event tick is enabled elsewhere!

  step1();
  return CFG_SCHEDULER_TIMER_MINIMUM;
}


inline void ADC_Init (void) {

  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_STORE] = pinDisconnectInputBuffer;
  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_USB] = pinDisconnectInputBuffer;
  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_FRONT] = pinDisconnectInputBuffer;
  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_REAR] = pinDisconnectInputBuffer;
  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_MPPT_VOLTAGE] = pinDisconnectInputBuffer;
  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_MPPT_CURRENT] = pinDisconnectInputBuffer;
  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_LIGHTSENSE] = pinDisconnectInputBuffer;
  NRF_GPIO->PIN_CNF[CFG_PIN_ADC_MUX] = pinDisconnectInputBuffer | (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
  NRF_GPIO->OUTSET = 1 << CFG_PIN_ADC_MUX;

  NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit << SAADC_RESOLUTION_VAL_Pos;
  NRF_SAADC->CH[ADC_CH_USB].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
  NRF_SAADC->CH[ADC_CH_FRONT].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
  NRF_SAADC->CH[ADC_CH_REAR].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
  NRF_SAADC->CH[ADC_CH_STORE].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1_2 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
  NRF_SAADC->CH[ADC_CH_LIGHTSENSE].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain2 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
  NRF_SAADC->CH[ADC_CH_MPPT_VOLTAGE].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_MPPT_VOLTAGE << SAADC_CH_CONFIG_TACQ_Pos;
  NRF_SAADC->CH[ADC_CH_MPPT_CURRENT].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_MPPT_CURRENT << SAADC_CH_CONFIG_TACQ_Pos;
  
  NRF_PPI->CH[CFG_PPI_ADC_SAMPLEONSTART].EEP = (uint32_t) &NRF_SAADC->EVENTS_STARTED;
  NRF_PPI->CH[CFG_PPI_ADC_SAMPLEONSTART].TEP = (uint32_t) &NRF_SAADC->TASKS_SAMPLE;
  NRF_PPI->CH[CFG_PPI_ADC_SAMPLEBYTIMER].EEP = (uint32_t) &NRF_RTC1->EVENTS_TICK;
  NRF_PPI->CH[CFG_PPI_ADC_SAMPLEBYTIMER].TEP = (uint32_t) &NRF_SAADC->TASKS_SAMPLE;
  
  NVIC_SetPriority(SAADC_IRQn, CFG_INTERRUPT_PRIORITY_ADC);
  NVIC_EnableIRQ(SAADC_IRQn);
}

void ADC_Shutdown (void) {

  NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos;
}






  • Hello!

    In my current working case I've disabled calibration. I think many of the 832 errata also apply to the 810 even if not specified. For one, I have used 10us sampling or more due to the PPI issues.

    Some of the extra defines are

      NVIC_SetPriority(CFG_SCHEDULER_TIMER_IRQn, CFG_INTERRUPT_PRIORITY_SCHEDULER);
      NVIC_EnableIRQ(CFG_SCHEDULER_TIMER_IRQn);
      CFG_SCHEDULER_TIMER->EVTENSET = RTC_EVTENSET_TICK_Set << RTC_EVTENSET_TICK_Pos;
      CFG_SCHEDULER_TIMER->PRESCALER = CFG_SCHEDULER_TIMER_PRESCALER-1;
      CFG_SCHEDULER_TIMER->TASKS_START = RTC_TASKS_START_TASKS_START_Trigger << RTC_TASKS_START_TASKS_START_Pos;
    
    #define CFG_ADC_FRONT_PSEL SAADC_CH_PSELP_PSELP_AnalogInput0
    #define CFG_ADC_REAR_PSEL SAADC_CH_PSELP_PSELP_AnalogInput1
    #define CFG_ADC_USB_PSEL SAADC_CH_PSELP_PSELP_AnalogInput2
    #define CFG_ADC_LIGHTSENSE_PSEL SAADC_CH_PSELP_PSELP_AnalogInput4
    #define CFG_ADC_STORE_PSEL SAADC_CH_PSELP_PSELP_AnalogInput5
    #define CFG_ADC_MPPT_VOLTAGE_PSEL SAADC_CH_PSELP_PSELP_AnalogInput6
    #define CFG_ADC_MPPT_CURRENT_PSEL SAADC_CH_PSELP_PSELP_AnalogInput7
    
    #define pinDisconnectInputBuffer (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos)
    
    #define CFG_SCHEDULER_TIMER_PRESCALER 8
    


    The current workings are

    #include <stdint.h>
    
    
    #define ADC_RESOLUTION SAADC_RESOLUTION_VAL_12bit
    #define ADC_SAMPLETIME_CFG_SLOW SAADC_CH_CONFIG_TACQ_40us
    #define ADC_SAMPLETIME_CFG_MPPT_VOLTAGE SAADC_CH_CONFIG_TACQ_10us
    #define ADC_SAMPLETIME_CFG_MPPT_CURRENT SAADC_CH_CONFIG_TACQ_10us
    
    // ADC takes 2us per channel, 4us + 10us + 10us = 24us
    
    // 4096Hz = 244us
    // 350 (fit into ram)*244 = 85.4ms
    #define MPPT_SAMPLES 700
    
    // 200ms total / 81ms
    #define MPPT_LOOP 1
    
    enum {
      ADC_CH_USB,
      ADC_CH_FRONT,
      ADC_CH_REAR,
      ADC_CH_STORE,
      ADC_CH_LIGHTSENSE,
      ADC_CH_MPPT_VOLTAGE,
      ADC_CH_MPPT_CURRENT
    };
    
    struct mpptReadProto {
      int16_t voltage;
      int16_t current;
    };
    
    static struct {
      int16_t usb;
      int16_t front;
      int16_t rear;
      int16_t storage;
      int16_t lightsense;
    } voltages;
    
    static struct {
      int16_t usb;
      int16_t front;
      int16_t rear;
    } currents;
    
    static void step4 (void);
    static void step3 (void);
    static void step2 (void);
    static void step1 (void);
    
    static void (*step)(void);
    static uint8_t loop;
    static struct mpptTrackProto track;
    static struct mpptReadProto banks[MPPT_LOOP > 1? 2 : 1][MPPT_SAMPLES];
    static uint8_t bank;
    
    extern struct statusProto status;
    
    static void step1 (void) {
    
      step = &step2;
    
      NRF_GPIO->OUTCLR = 1 << CFG_PIN_ADC_MUX;
      NRF_PPI->CHENSET = 1 << CFG_PPI_ADC_SAMPLEONSTART;
    
      NRF_SAADC->CH[ADC_CH_USB].PSELP = CFG_ADC_USB_PSEL;
      NRF_SAADC->CH[ADC_CH_FRONT].PSELP = CFG_ADC_FRONT_PSEL;
      NRF_SAADC->CH[ADC_CH_REAR].PSELP = CFG_ADC_REAR_PSEL;
      NRF_SAADC->CH[ADC_CH_STORE].PSELP = CFG_ADC_STORE_PSEL;
      NRF_SAADC->CH[ADC_CH_LIGHTSENSE].PSELP = CFG_ADC_LIGHTSENSE_PSEL;
    
      NRF_SAADC->RESULT.PTR = (uint32_t) &voltages;
      NRF_SAADC->RESULT.MAXCNT = sizeof(voltages) / 2;
    
      //NRF_SAADC->EVENTS_STARTED = 0;
      NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos;
      //while (!NRF_SAADC->EVENTS_STARTED) {}
      //NRF_SAADC->TASKS_SAMPLE = 1;
    }
    
    static void step2 (void) {
    
      step = &step3;
    
      NRF_GPIO->OUTSET = 1 << CFG_PIN_ADC_MUX;
    
      NRF_SAADC->RESULT.PTR = (uint32_t) &currents;
      NRF_SAADC->RESULT.MAXCNT = sizeof(currents) / 2;
      NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos;
    }
    
    static void step3 (void) {
    
      step = &step4;
      NRF_PPI->CHENCLR = 1 << CFG_PPI_ADC_SAMPLEONSTART;
    
      NRF_SAADC->CH[ADC_CH_USB].PSELP = SAADC_CH_PSELP_PSELP_NC;
      NRF_SAADC->CH[ADC_CH_FRONT].PSELP = SAADC_CH_PSELP_PSELP_NC;
      NRF_SAADC->CH[ADC_CH_REAR].PSELP = SAADC_CH_PSELP_PSELP_NC;
      NRF_SAADC->CH[ADC_CH_STORE].PSELP = SAADC_CH_PSELP_PSELP_NC;
      NRF_SAADC->CH[ADC_CH_LIGHTSENSE].PSELP = SAADC_CH_PSELP_PSELP_NC;
      NRF_SAADC->CH[ADC_CH_MPPT_VOLTAGE].PSELP = CFG_ADC_MPPT_VOLTAGE_PSEL;
      NRF_SAADC->CH[ADC_CH_MPPT_CURRENT].PSELP = CFG_ADC_MPPT_CURRENT_PSEL;
    
      NRF_SAADC->RESULT.PTR = (uint32_t) &banks;
      NRF_SAADC->RESULT.MAXCNT = MPPT_SAMPLES * 2;
    
      NRF_SAADC->EVENTS_STARTED = 0;
      NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos;
      while (!NRF_SAADC->EVENTS_STARTED) {}
      NRF_PPI->CHENSET = 1 << CFG_PPI_ADC_SAMPLEBYTIMER;
    }
    
    static void trackAssess () {
    
      for(struct mpptReadProto *pCur = &banks[bank][0]; pCur < &banks[bank][0] + sizeof(banks[0]); pCur++) {
        if (pCur->voltage > track.voltage && pCur->current > track.current) {
          track.voltage = pCur->voltage;
          track.current = pCur->current;
        }
      }
    }
    
    static void step4 (void) {
    
      NRF_PPI->CHENCLR = 1 << CFG_PPI_ADC_SAMPLEBYTIMER;
    
      #if MPPT_LOOP > 1
    
      if (loop == MPPT_LOOP-1) {
    
      #endif
    
        NRF_SAADC->CH[ADC_CH_MPPT_VOLTAGE].PSELP = SAADC_CH_PSELP_PSELP_NC;
        NRF_SAADC->CH[ADC_CH_MPPT_CURRENT].PSELP = SAADC_CH_PSELP_PSELP_NC;
        trackAssess();
        MPPT_Track(&track);
        track.voltage = 0;
        track.current = 0;
        loop = 0;
        bank = 0;
        step1();
        return;
    
      #if MPPT_LOOP > 1
    
      }
    
      NRF_SAADC->RESULT.PTR = bank? (uint32_t) &banks[0] : (uint32_t) &banks[1];
      NRF_SAADC->EVENTS_STARTED = 0;
      NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos;
      while (!NRF_SAADC->EVENTS_STARTED) {}
      NRF_PPI->CHENSET = 1 << CFG_PPI_ADC_SAMPLEBYTIMER;
      
      trackAssess();
      bank = bank? 0:1;
      loop++;
    
      #endif
    }
    
    void SAADC_IRQHandler (void) {
    
      NRF_SAADC->EVENTS_END = SAADC_EVENTS_END_EVENTS_END_NotGenerated << SAADC_EVENTS_END_EVENTS_END_Pos;
      step();
    }
    
    uint32_t ADC_TaskCalibrate (void) {
    
      NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;
      //NRF_SAADC->TASKS_CALIBRATEOFFSET = SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Trigger << SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Pos;
      return 0; //CFG_SCHEDULER_TIMER_MINIMUM;
    }
    
    uint32_t ADC_TaskStart (void) {
    
      NRF_SAADC->EVENTS_END = SAADC_EVENTS_END_EVENTS_END_NotGenerated << SAADC_EVENTS_END_EVENTS_END_Pos;
      NRF_SAADC->INTENSET = SAADC_INTENSET_END_Enabled << SAADC_INTENSET_END_Pos;
      step1();
      return CFG_SCHEDULER_TIMER_MINIMUM;
    }
    
    uint32_t ADC_TaskCalc (void) {
    
      return 0;
    }
    
    inline void ADC_Init (void) {
    
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_STORE] = pinDisconnectInputBuffer;
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_USB] = pinDisconnectInputBuffer;
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_FRONT] = pinDisconnectInputBuffer;
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_REAR] = pinDisconnectInputBuffer;
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_MPPT_VOLTAGE] = pinDisconnectInputBuffer;
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_MPPT_CURRENT] = pinDisconnectInputBuffer;
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_LIGHTSENSE] = pinDisconnectInputBuffer;
      NRF_GPIO->PIN_CNF[CFG_PIN_ADC_MUX] = pinDisconnectInputBuffer | (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
      NRF_GPIO->OUTSET = 1 << CFG_PIN_ADC_MUX;
    
      NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit << SAADC_RESOLUTION_VAL_Pos;
      NRF_SAADC->CH[ADC_CH_USB].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
      NRF_SAADC->CH[ADC_CH_FRONT].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
      NRF_SAADC->CH[ADC_CH_REAR].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
      NRF_SAADC->CH[ADC_CH_STORE].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1_2 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
      NRF_SAADC->CH[ADC_CH_LIGHTSENSE].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain2 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_SLOW << SAADC_CH_CONFIG_TACQ_Pos;
      NRF_SAADC->CH[ADC_CH_MPPT_VOLTAGE].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_MPPT_VOLTAGE << SAADC_CH_CONFIG_TACQ_Pos;
      NRF_SAADC->CH[ADC_CH_MPPT_CURRENT].CONFIG = SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos | SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos | ADC_SAMPLETIME_CFG_MPPT_CURRENT << SAADC_CH_CONFIG_TACQ_Pos;
      
      NRF_PPI->CH[CFG_PPI_ADC_SAMPLEONSTART].EEP = (uint32_t) &NRF_SAADC->EVENTS_STARTED;
      NRF_PPI->CH[CFG_PPI_ADC_SAMPLEONSTART].TEP = (uint32_t) &NRF_SAADC->TASKS_SAMPLE;
      NRF_PPI->CH[CFG_PPI_ADC_SAMPLEBYTIMER].EEP = (uint32_t) &NRF_RTC1->EVENTS_TICK;
      NRF_PPI->CH[CFG_PPI_ADC_SAMPLEBYTIMER].TEP = (uint32_t) &NRF_SAADC->TASKS_SAMPLE;
      
      NVIC_SetPriority(SAADC_IRQn, CFG_INTERRUPT_PRIORITY_ADC);
      NVIC_EnableIRQ(SAADC_IRQn);
    }
    
    void ADC_Shutdown (void) {
    
      NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos;
    }


    Effectively I'm using two methods both PPI, one wires STARTED to SAMPLE, where I want to use scan for n amount of channels once, and then an alternative second PPI wires the RTC tick to SAMPLE, where I scan 2 channels multiple times.

    Due to RAM limitations I was using multiple banks, so easydma would fill one and processing would occur while it began filling the other one. Currently given the prescaler is so high I don't need the loop and am running just the one bank, 750 x 2 channels.

    I'm using various interrupts, no code occurs within the main loop it's just an idle there. For some reason the ADC must run with priority 3 or greater when the softdevice is running. This suggested the softdevice was overwhelming the cpu to service higher interrupts, but then the scheduler and other higher interrupts I have are being serviced ok. It's odd!

    enum {
      CFG_INTERRUPT_PRIORITY_ADC = 3,
      CFG_INTERRUPT_PRIORITY_GPIOTE = 3, // must come before scheduler
      CFG_INTERRUPT_PRIORITY_EGU = 5,
      CFG_INTERRUPT_PRIORITY_SCHEDULER = 4
    };
    


    It shouldn't make any difference I believe with the CPU jumping out of the ADC interrupt and back-in since none of the code that I can see needs to be atomic. 

    At this point I have **** all idea why it randomly crashes. Also when I say crash I mean the ADC peripheral; the ADC values randomly transform to low garbage and only a power reset clears it.

  • Interesting find that link. So regarding the START operations which I am indeed performing in the interrupt...

    1. for one scenario the SAMPLE is fired by PPI on the STARTED event, so I don't believe the SAMPLE can fire before STARTED.

    2. for the second scenario I am calling START and then enabling the PPI channel to allow the timer tick to come through. I actually already added code (step3) to check if STARTED event has fired before enabling the PPI channel, just to make sure no SAMPLE event comes through prior.

    There's potentially the case where the ADC will receive a SAMPLE from the timer while easydma is operating but the END event hasn't yet been fired. In the case of a unique timer it would be possible to use PPI to stop the timer on END, but in the case of my RTC I have to use an interrupt to do the dirty, which potentially is much slower. I'm unsure however if it makes any difference. There's nothing to indicate that a timer can't in fact be left running.

  • Edit: I see the sequence is repeat step1-2-3-4-1-2 ..If a PSEL is slow getting disabled, then the steps immediately go out of sequence so instead of input0-1-2-3-4-5-0 ... input6-7-6-7 it ends up maybe being input0-1-2-3-4-5-0 ... input0-1-0-1... Do the measured voltages look like that?

    Also there is no TASKS_STOP and EVENTS_STOPPED check prior to a TASKS_START; I wonder if that would help ..

  • Tasks Stop are supposed to be like an 'Abort', they shouldn't be required once an End Event has fired, so I am led to believe.

    You're right, it's step1,2,3,4 repeat and (4) is actually running (1) atm, but was there for looping purposes.

    I'll try and get a breakpoint on step 2 to inspect the voltages tomorrow once it's gone pair shaped and see if they've shifted. It's possible as most the voltage if not all the others are very low, which is what the main voltage I'm primarily interested switches to (voltage.storage).

    I haven't read anything around PSEL taking a while to set, is that something you've seen before?

    Regards, Andrew

  • I tried to run the code but my test setup is using a different SDK version not recognising some of the SAADC defines, maybe I'll get another look tomorrow.

    Regarding the PSEL taking a while, all peripheral operations take a while and code access to registers may be delayed wrt hardware event access. Two issues, one the peripherals use a 16MHz clock instead of the CPU 64MHz clock and two the AHB bus priority instigates unquantifiable delays. Here's a simple example from personal experience. A single SPIM DMA transaction event should generate a single interrupt, and mSpiInterruptCounter should therefore be equal to 1 after the event. Here are 3 example interrupts, all correct; the first generates a count of 2, the second 2 examples generate a count of 1. Multiple interrupts from a single event is ridiculous, but it happens because the interrupt tail-chains and repeats before the AHB/register clear transaction completes as the CPU has both higher priority and a 4x faster clock than the peripheral. This is a known Cortex-M4 bug. Is this an issue with the SAADC here? Dunno .. the hardware event - which does not have to traverse the stalled AHB bus - can in some cases happen before a delayed cpu transaction. __DSB(); fixes this issue in some cases. Nordic engineers might step in here :-)

    volatile uint32_t mSpiInterruptCounter = 0UL;
    
    void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
    {
      mSpiInterruptCounter++;
      if (NRF_SPIM0->EVENTS_END) NRF_SPIM0->EVENTS_END = 0;  // Subtle Bug here
    }
    
    void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
    {
      if (NRF_SPIM0->EVENTS_END) NRF_SPIM0->EVENTS_END = 0;  // Ok
      mSpiInterruptCounter++;
    }
    
    void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
    {
      mSpiInterruptCounter++;
      if (NRF_SPIM0->EVENTS_END) NRF_SPIM0->EVENTS_END = 0;  // Ok
      // Clear pending hardware register bus operations - Cortex-M4 issue
      __DSB();
    }

Related