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.

  • 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 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();
    }

  • I have used DSB() in the past, I tried a smattering after all the SAADC registers but to no avail.

    After debugging and waiting for a while I managed to capture the failure. The reads have shifted one in ram.

    I then checked all the PELP registers upon this occurrence and they're all correct.

    Given there is some shift errata around the calibration (although I've disabled calibration) I decided to try wiring END to STOP.

    NRF_PPI->CH[CFG_PPI_ADC_ENDAUTOSTOP].EEP = (uint32_t) &NRF_SAADC->EVENTS_END;
    NRF_PPI->CH[CFG_PPI_ADC_ENDAUTOSTOP].TEP = (uint32_t) &NRF_SAADC->TASKS_STOP;
    NRF_PPI->CH[CFG_PPI_ADC_CALIBRATEAUTOSTOP].EEP = (uint32_t) &NRF_SAADC->EVENTS_CALIBRATEDONE;
    NRF_PPI->CH[CFG_PPI_ADC_CALIBRATEAUTOSTOP].TEP = (uint32_t) &NRF_SAADC->TASKS_STOP;

    I then switched the interrupt to STOP.

    I'm still running and praying but so far no further shifting!

  • I am simply delighted to say this is resolved!

    I hope the Nordic team can create an errata and use the two PPI solution above not only for calibration but for scan operations. 

  • Nice! Is this for off-grid solar, by any chance? I'll keep an eye out for more exciting problems ..

  • Similar, a generator.


    So after much testing yesterday it occasionally would skip, which in my case is a fatal flaw that can damage hardware. I literally exhausted everything and then finally went the whole hog and used the errata code before any adc operation to do a full reset and reconfig. This has solved it.

      volatile uint32_t temp1;
      volatile uint32_t temp2;
      volatile uint32_t temp3;
      temp1 = *(volatile uint32_t *)0x40007640ul;
      temp2 = *(volatile uint32_t *)0x40007644ul;
      temp3 = *(volatile uint32_t *)0x40007648ul;
      *(volatile uint32_t *)0x40007FFCul = 0ul; 
      *(volatile uint32_t *)0x40007FFCul; 
      *(volatile uint32_t *)0x40007FFCul = 1ul;
      *(volatile uint32_t *)0x40007640ul = temp1;
      *(volatile uint32_t *)0x40007644ul = temp2;
      *(volatile uint32_t *)0x40007648ul = temp3;

    My understanding (be good if someone from Nordic can verify) is that this will also reset internal calibration values if ran.

Reply
  • Similar, a generator.


    So after much testing yesterday it occasionally would skip, which in my case is a fatal flaw that can damage hardware. I literally exhausted everything and then finally went the whole hog and used the errata code before any adc operation to do a full reset and reconfig. This has solved it.

      volatile uint32_t temp1;
      volatile uint32_t temp2;
      volatile uint32_t temp3;
      temp1 = *(volatile uint32_t *)0x40007640ul;
      temp2 = *(volatile uint32_t *)0x40007644ul;
      temp3 = *(volatile uint32_t *)0x40007648ul;
      *(volatile uint32_t *)0x40007FFCul = 0ul; 
      *(volatile uint32_t *)0x40007FFCul; 
      *(volatile uint32_t *)0x40007FFCul = 1ul;
      *(volatile uint32_t *)0x40007640ul = temp1;
      *(volatile uint32_t *)0x40007644ul = temp2;
      *(volatile uint32_t *)0x40007648ul = temp3;

    My understanding (be good if someone from Nordic can verify) is that this will also reset internal calibration values if ran.

Children
  • Hello,

    snoopy20 said:
    So after much testing yesterday it occasionally would skip, which in my case is a fatal flaw that can damage hardware. I literally exhausted everything and then finally went the whole hog and used the errata code before any adc operation to do a full reset and reconfig. This has solved it.

    I am sorry to read the struggles you've had to get this working as expected, which no doubt was a frustrating experience!

    My general suggestion for your setup would be to use the SAADC driver and to have the sampling triggered through PPI with the addition described in this answer by my colleague Jørgen referenced by hmolesworth earlier, but you would of course also need to pay heed to the erratas also mentioned by hmolesworth, which sounds close to what you are doing currently.

    snoopy20 said:
    My understanding (be good if someone from Nordic can verify) is that this will also reset internal calibration values if ran.

    Correct, adding this code will reset the entire SAADC peripheral, as mentioned in the workaround description. The offset calibration would indeed have to be run again, which should be done before you have started sampling (or after the END event, as described in the erratas), or anytime the ambient temperature has changed more than 10 degrees to counteract changes in the measurements caused by temperature. 
    Are there anything else than the offset calibration you are referring to here, when you say 'internal calibration values'?

    Best regards,
    Karl

  • Hi Karl,

    I'm in production with it, it's working well.

    I perform a number of steps in a adc-interrupt-loop with calibration being the first. Each step does a full reset of the adc using the errata code. It's not the fastest or the most optimal (i.e calibration every ~100ms) but it works and is fast enough!

    A.

  • Hello A,

    I am glad to hear that it now functions as expected! :)
    I understand that it is not ideal to have to go through the calibration so often, but unfortunately it is no way around the reset of the SAADC peripheral between the configurations you are using in this case.

    Alternatively, if the repeated calibration is effecting the performance of other components in your system you could also run some tests on the feasibility for your application not to perform the offset calibration at all - as mentioned in the Product Specification the SAADC has a temperature dependent offset, but if what you are after in your application is the relative change in voltage over time, and the device is operating in a small temperature range - less than 10 degrees change in ambient temperature - it could also be feasible for you to run without the calibration. At least I do not know enough about your applications requirements and constraints to rule this possibility out.

    Best regards,
    Karl

  • It's not an issue in this application and to be honest if it were I'd monitor the internal temperature of the die and skip the step if the delta isn't high enough. There's always a way! Slight smile

    I do think however that some work is needed on the errata. For the 810 at least there are two around the calibration, which are the same thing, and as I've shown in this thread the errata applies at any time configuration changes between scans and not just calibration.

  • snoopy20 said:
    It's not an issue in this application and to be honest if it were I'd monitor the internal temperature of the die and skip the step if the delta isn't high enough. There's always a way!

    Yeah, if it had been an issue for you we'd have to turn some rocks to see what the possible solutions could be, but I am glad to hear that this is not an issue! :) 

    snoopy20 said:
    I do think however that some work is needed on the errata. For the 810 at least there are two around the calibration, which are the same thing, and as I've shown in this thread the errata applies at any time configuration changes between scans and not just calibration.

    To me it looks like you are seeing the effects of Errata 212 paired with the buffer-shift issue explained by my colleague Jørgen in the other thread, could you elaborate what you mean when you say 'the errata applies at any time configuration changes between scans and not just calibration?

    I do agree that Errata 237 and Errata 252 perhaps should have been a single errata as I am not sure if their distinction is clear enough. I will check internally to see if what the background for this is.

    Best regards,
    Karl

Related