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;
}






Parents
  • And into the evening it continues.

    I've managed to determine the loop is good, but when softdevice is enabled the reads tail off and stop.

    <info> app: Step2 11
    <info> app: Step2 10
    <info> app: Step2 15
    <info> app: Step2 16
    <info> app: Step2 17
    <info> app: Step2 -4
    <ends>

    I've now replaced the timer and PPI's with raw

      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;


    Eventually I've found that the ADC dies if it's interrupt priority 4 or above. 3 works, above the softdevice non-critical timing ops. I can't tell why this is because I also have a few interrupts at higher levels and they are being serviced.

Reply
  • And into the evening it continues.

    I've managed to determine the loop is good, but when softdevice is enabled the reads tail off and stop.

    <info> app: Step2 11
    <info> app: Step2 10
    <info> app: Step2 15
    <info> app: Step2 16
    <info> app: Step2 17
    <info> app: Step2 -4
    <ends>

    I've now replaced the timer and PPI's with raw

      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;


    Eventually I've found that the ADC dies if it's interrupt priority 4 or above. 3 works, above the softdevice non-critical timing ops. I can't tell why this is because I also have a few interrupts at higher levels and they are being serviced.

Children
  • Right, so I've determined that it will mostly work with an RTC prescale of 9, but mostly the ADC will eventually fail and start giving crap out. At a prescale of 8 it will mostly not work and at 7 it's crap out all the time.

    A prescale of 9 equals 3631Hz, which is 274us. 

    Something is interfering with the operation and it worsens as load increases (i.e run softdevice).


    #define ADC_RESOLUTION SAADC_RESOLUTION_VAL_12bit
    #define ADC_SAMPLETIME_CFG_SLOW SAADC_CH_CONFIG_TACQ_20us
    #define ADC_SAMPLETIME_CFG_MPPT_VOLTAGE SAADC_CH_CONFIG_TACQ_20us
    #define ADC_SAMPLETIME_CFG_MPPT_CURRENT SAADC_CH_CONFIG_TACQ_10us
    
    // ADC takes 2us per channel, 4us + 40us + 40us = 84us
    
    // 3641Hz = 274us
    // 667 (fit into ram)*274 = 182ms
    #define MPPT_SAMPLES 350
    
    // 200ms total / 81ms
    #define MPPT_LOOP 3
    
    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;
    };
    
    volatile static struct {
      int16_t usb;
      int16_t front;
      int16_t rear;
      int16_t storage;
      int16_t lightsense;
    } voltages;
    volatile static struct {
      int16_t usb;
      int16_t front;
      int16_t rear;
    } currents;
    static struct mpptReadProto banks[2][MPPT_SAMPLES];
    
    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 uint8_t bank;
    
    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->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;
    
      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->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos;
      NRF_PPI->CHENSET = 1 << CFG_PPI_ADC_SAMPLEBYTIMER;
    }
    
    static void trackAssess (uint8_t bankId) {
    
      for(struct mpptReadProto *pCur = &banks[bankId][0]; pCur < &banks[bankId][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) {
    
      if (loop == MPPT_LOOP-1) {
        loop = 0;
        bank = 0;
    
        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;
        
        trackAssess(bank);
        MPPT_Track(&track);
        track.voltage = 0;
        track.current = 0;
        step1();
        return;
      }
    
      NRF_SAADC->RESULT.PTR = bank? (uint32_t) &banks[0] : (uint32_t) &banks[1];
      NRF_SAADC->TASKS_START = SAADC_TASKS_START_TASKS_START_Trigger << SAADC_TASKS_START_TASKS_START_Pos;
      trackAssess(bank);
      bank = bank? 0:1;
      loop++;
    }
    
    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;
    
      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;
    }



    Here's an example of when it switches to crap out. High reads are valid.

    <info> app: Store 625
    <info> app: Store 633
    <info> app: Store 626
    <info> app: Store 628
    <info> app: Store 627
    <info> app: Store 632
    <info> app: Store 632
    <info> app: Store 5
    <info> app: Store 7
    <info> app: Store 11
    <info> app: Store 12


  • I appear to have stability. Max RTC prescaler is 8, or 4096Hz.

    To resolve the instability disable calibration completely. It appears to be linked to the channel so reconfiguring them as I do will result in bad data eventually. 

    So far the ADC hasn't crashed so fingers crossed its sorted.


  • Nope it's crashed again, just took a lot longer. F**** sake!

  • Such fun .. some years ago I had a similar issue and made these notes; if you post the sequence of calls to these functions (and more of the #defines as it's tedious creating them) I could do some independant testing, as I don't see where you are checking things like cal finished before starting a scan, and there is an SAADC errata where cal breaks things unless issued prior to a START or following an END. nRF52810 errata 237 and errata 252. Also note errata 212.

    // Problem when the SAADC is configured in continuous mode using PPI to trigger the SAMPLE task at a regular interval,
    // while the END event and START task is handled by CPU interrupt. When the SAMPLE task is triggered, each channel is
    // sampled and written to RAM with DMA as fast as possible. When the buffer have been filled, the DMA transfer will be
    // delayed until the START task have been triggered. You are triggering the START task in the interrupt handler after
    // receiving the END event. If you receive the END event when IRQ is disabled or an interrupt with higher priority is
    // executing, the triggering of the START task can get delayed until after the SAMPLE task have been triggered using
    // PPI. Triggering of the SAMPLE task will generate a DMA transfer request, but this request will not be acknowledged
    // until the START task have been triggered. The scan cycle of the SAADC will however expect the DMA transfer to finish,
    // and will sample next channel. When the START task is triggered, the pending DMA transfer will be executed, but the
    // transferred sample will correcpond to the latest sampled channel. Samples from previous channels will have been lost.
    //
    // There are two possible solutions to this problem:
    //
    // Use PPI to trigger START task on an END event. This will avoid the delayed triggering og the START task due to a
    // queued interrupt generated by the END event, but in the case of high sample frequency and long delays, it can cause
    // your buffer to be overwritten before you are able to process the buffer. In case of using this solution, it is
    // neccessary to use double buffering and large enough buffers to avoid data loss.
    // Trigger sampling from CPU interrupt. If the SAMPLE task is triggered from an interrupt with lower priority than the
    // SAADC IRQ handler, the START task will always be triggered between an END event and a new SAMPLE task. This solution
    // will make the sampling vary a bit in time, as higher priority tasks can cause the triggering of SAMPLE task to be delayed.

    I think the following post might have some useful info: offset-in-saadc-samples-with-easy-dma-and-ble

  • 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.

Related