Increase the ADC sample rate

Hi,
it is said in the datasheet of the nRF52840 that the ADCs can digitized 200,000 samples every second. I have a project where I need to achieve this sampling rate to respect the Nyquist–Shannon criteria.
I have checked the time of execution of the analogRead() function with a Nano 33 BLE and I have about 50µs at every occurence, which will give around 20ksps.
I am wondering if there is a way to achieve a faster sampling rate with the Nano 33 boards. I have seen in another thread that a solution has been given for the atmel microcontroller. I was not able to find anything related to the Nordic one and I am not able to figure out the solution by myself.
Thank you for your answers!

Welcome to the forum.

I suspect there are no libraries for this kind of requirements. Arduinos are educational devices and provide simple to use functions with little complexity.

If you want to make use of the full capabilities of the peripherals you will need to learn how to read data sheets and write the libraries yourself. You can still use the original libraries as inspiration. They will tell you how the Arduino team configured the peripherals, you can make use of register definitions and macros and you might get some hints on special stuff you need to do to get things working e.g. some registers need to be unlocked or synchronized, clocks need to be enabled ...

If you have a specific issue and need some help please share your code (Use code tags.). Make sure the example is as short a possible and can run with little external hardware. This will allow people in the forum to test this on their boards and provide you a solution or give you a hint into the right direction.

How did you manage to even get the 20ksps. I cannot even get that.

I have written an experimental example that shows you how to use a Timer and PPI to control the ADC.
This is not a beginners example. Please read the notes and let me know if you have any questions.

/*
  This experimental code shows how to control the nRF52840 SAADC using a Timer and PPI.
  The SAADC uses EasyDMA to copy each sample into a variable in memory.
  The ADC samples pin A0.
  Note:
  - the maximum sampling rate is 200kSamples/s
  - only one sample per second it printed
  - this code has not been tested with a debugger, some samples might be lost due to mbedOS (needs further testing)
  - this code will likely not work when using analogRead() on other pins
  
  The circuit:
  - Arduino Nano 33 BLE/ BLE Sense board.

  This example code is in the public domain.
*/

#include "mbed.h"

#define SAMPLES_PER_SECOND  180000
#define PPI_CHANNEL         (7)

#define ADC_BUFFER_SIZE     1
volatile nrf_saadc_value_t adcBuffer[ADC_BUFFER_SIZE];
volatile bool adcFlag = false;

volatile uint32_t sampleCounter = 0;


void setup()
{
  Serial.begin( 9600 );
  while ( !Serial );
  Serial.println( "Arduino Nano 33 BLE (mbedOS) example: Timer -> PPI -> SAADC" );

  initADC();
  initTimer4();
  initPPI();
}

void loop()
{
  static uint32_t previousMillis = 0;
  if ( adcFlag )
  {
    adcFlag = false;
    if ( sampleCounter >= SAMPLES_PER_SECOND )
    {
      uint32_t sampleCount = sampleCounter;
      uint32_t currentMillis = millis();
      uint32_t runTime = currentMillis - previousMillis;
      Serial.print( "Samples: " );
      Serial.print( sampleCount );
      Serial.print( " run time: " );
      Serial.print( runTime );
      Serial.print( " ms A0: " );
      Serial.println( adcBuffer[0] );

      previousMillis = currentMillis;
      sampleCounter = 0;
    }
  }
}


extern "C" void SAADC_IRQHandler_v( void )
{
  if ( NRF_SAADC->EVENTS_END != 0 )
  {
    NRF_SAADC->EVENTS_END = 0;
    adcFlag = true;
    sampleCounter++;
  }
}


void initADC()
{
  nrf_saadc_disable();

  NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT;

  NRF_SAADC->CH[2].CONFIG = ( SAADC_CH_CONFIG_GAIN_Gain1_4    << SAADC_CH_CONFIG_GAIN_Pos ) |
                            ( SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos ) |
                            ( SAADC_CH_CONFIG_REFSEL_VDD1_4   << SAADC_CH_CONFIG_REFSEL_Pos ) |
                            ( SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos ) |
                            ( SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos ) |
                            ( SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos );

  NRF_SAADC->CH[2].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput2 << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[2].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;

  NRF_SAADC->RESULT.MAXCNT = ADC_BUFFER_SIZE;
  NRF_SAADC->RESULT.PTR = ( uint32_t )&adcBuffer;

  NRF_SAADC->EVENTS_END = 0;
  nrf_saadc_int_enable( NRF_SAADC_INT_END );
  NVIC_SetPriority( SAADC_IRQn, 1UL );
  NVIC_EnableIRQ( SAADC_IRQn );

  nrf_saadc_enable();

  NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
  while ( NRF_SAADC->EVENTS_CALIBRATEDONE == 0 );
  NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
  while ( NRF_SAADC->STATUS == ( SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos ) );
}


void initTimer4()
{
  NRF_TIMER4->MODE = TIMER_MODE_MODE_Timer;
  NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
  NRF_TIMER4->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
  NRF_TIMER4->PRESCALER = 0;
  NRF_TIMER4->CC[0] = 16000000 / SAMPLES_PER_SECOND; // Needs prescaler set to 0 (1:1) 16MHz clock
  NRF_TIMER4->TASKS_START = 1;
}


void initPPI()
{
  NRF_PPI->CH[PPI_CHANNEL].EEP = ( uint32_t )&NRF_TIMER4->EVENTS_COMPARE[0];
  NRF_PPI->CH[PPI_CHANNEL].TEP = ( uint32_t )&NRF_SAADC->TASKS_START;
  NRF_PPI->FORK[PPI_CHANNEL].TEP = ( uint32_t )&NRF_SAADC->TASKS_SAMPLE;
  NRF_PPI->CHENSET = ( 1UL << PPI_CHANNEL );
}
3 Likes

This is a great solution! Thank you! What would be the best way to extract all the sampled values? Like not only print them every second but to have access to all the values (e.g save them in a txt file).

Sorry for the trivial question.

This is not a trivial question. You have enough processor cycles available to do some computation and do something useful with the signal. Getting the data off the Arduino Nano 33 BLE is not that easy.

At 200ksps and 12 bit ADC resolution you create 2.4Mbit/s. This is raw data rate. There might be some overhead from whatever protocol you choose.

  • I2C, UART and BLE are too slow to get this data rate
  • SPI would be fast enough but there is no SPI on a PC
  • the USB Serial might be fast enough, but you would need to test whether the USB stack and mbedOS work nicely with the ADC interrupt
  • SPI can be used to write to SD cards but the library and mbedOS might interfere as well

Why do you want to store the data in a txt file?
How many samples do you want to store?

1 Like

Hello thank you for your help. I want to store about 20 000 sps.

The ADC is a little too fast for my need. About 20 ksps is enough, I do not need to store all of the 200k samples. A text file works best for our system’s experimental setup. 20k would be great

That sounds reasonable. Thanks to the large amount of RAM, we can store enough samples and then take our time to send the data to your laptop.

I have modified the example to store the data via software to a buffer and then show them in the Serial Plotter window. Connected to my laptop my Arduino Nano 33 BLE gets enough noise from the power line that I can see a 50Hz sine wave signal from the ADC when no signal is connected. I have slowed the display of the signal to 5ms per sample so you can watch the signal being plotted and the size to 2000 so it does not take too long to display.
I have tested 100k samples (200kB) which is likely the limit. You already get a warning that mbedOS might become unstable from the compiler/linker. So, your 20k are fine.
I have attached a button to pin 10.

/*
  This experimental code shows how to control the nRF52840 SAADC using a Timer and PPI.
  The SAADC uses EasyDMA to copy each sample into a variable in memory.
  The ADC samples pin A0.
  The Timer is started by pulling a digital pin LOW.
  At the end of the measurement the data is send via Serial to be displayed in the Serial Plotter.
  Note:
  - the maximum sampling rate is 200kSamples/s
  - this code has not been tested with a debugger, some samples might be lost due to mbedOS (needs further testing)
  - this code will likely not work when using analogRead() on other pins
  
  The circuit:
  - Arduino Nano 33 BLE/ BLE Sense board.

  This example code is in the public domain.
*/

#include "mbed.h"

#define SAMPLES_PER_SECOND  20000
#define PPI_CHANNEL         (7)

#define ADC_BUFFER_SIZE     1
volatile nrf_saadc_value_t adcBuffer[ADC_BUFFER_SIZE];
volatile bool adcFlag = false;


#define DATA_BUFFER_SIZE    2000
volatile nrf_saadc_value_t dataBuffer[DATA_BUFFER_SIZE];
uint32_t dataBufferIndex = 0;

#define BUTTON_START_PIN    10

#define DATA_SEND_INTERVAL  5


void setup()
{
  Serial.begin( 9600 );
  while ( !Serial );

  initADC();
  initTimer4();
  initPPI();
  pinMode( BUTTON_START_PIN, INPUT );
}

void loop()
{
  enum APP_STATE_TYPE { APP_WAIT_FOR_START,
                        APP_START_MEASUREMENT,
                        APP_COLLECT_SAMPLES,
                        APP_STOP_MEASUREMENT,
                        APP_SEND_DATA,                         
                        APP_STATE_RESTART = 255
                      };  

  static uint32_t state = 0;
  
  switch ( state )
  {
    case APP_WAIT_FOR_START:
      if ( digitalRead( BUTTON_START_PIN ) == LOW )
      {
        state++;
      }
      break;
    case APP_START_MEASUREMENT:
      startTimer4();
      state++;
      break;
    case APP_COLLECT_SAMPLES:
      if ( adcFlag )
      {
        if ( copyData() == 0 )
        {
          state++;
        }
        adcFlag = false;      
      }
      break;
    case APP_STOP_MEASUREMENT:
      stopTimer4();
      dataBufferIndex = 0;
      state++;
      break;
    case APP_SEND_DATA:
      if ( sendData( DATA_SEND_INTERVAL ) == 0 )
      {
        state++;
      }
      break;
    default:
      dataBufferIndex = 0;
      state = 0;
      break;
  }
}


extern "C" void SAADC_IRQHandler_v( void )
{
  if ( NRF_SAADC->EVENTS_END != 0 )
  {
    NRF_SAADC->EVENTS_END = 0;
    adcFlag = true;
  }
}


void initADC()
{
  nrf_saadc_disable();

  NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT;

  NRF_SAADC->CH[2].CONFIG = ( SAADC_CH_CONFIG_GAIN_Gain1_4    << SAADC_CH_CONFIG_GAIN_Pos ) |
                            ( SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos ) |
                            ( SAADC_CH_CONFIG_REFSEL_VDD1_4   << SAADC_CH_CONFIG_REFSEL_Pos ) |
                            ( SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos ) |
                            ( SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos ) |
                            ( SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos );

  NRF_SAADC->CH[2].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput2 << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[2].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;

  NRF_SAADC->RESULT.MAXCNT = ADC_BUFFER_SIZE;
  NRF_SAADC->RESULT.PTR = ( uint32_t )&adcBuffer;

  NRF_SAADC->EVENTS_END = 0;
  nrf_saadc_int_enable( NRF_SAADC_INT_END );
  NVIC_SetPriority( SAADC_IRQn, 1UL );
  NVIC_EnableIRQ( SAADC_IRQn );

  nrf_saadc_enable();

  NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
  while ( NRF_SAADC->EVENTS_CALIBRATEDONE == 0 );
  NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
  while ( NRF_SAADC->STATUS == ( SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos ) );
}


void initTimer4()
{
  NRF_TIMER4->MODE = TIMER_MODE_MODE_Timer;
  NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
  NRF_TIMER4->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
  NRF_TIMER4->PRESCALER = 0;
  NRF_TIMER4->CC[0] = 16000000 / SAMPLES_PER_SECOND; // Needs prescaler set to 0 (1:1) 16MHz clock
//  NRF_TIMER4->TASKS_START = 1;
}

void startTimer4()
{
  NRF_TIMER4->TASKS_START = 1;
}

void stopTimer4()
{
  NRF_TIMER4->TASKS_STOP = 1;
}


void initPPI()
{
  NRF_PPI->CH[PPI_CHANNEL].EEP = ( uint32_t )&NRF_TIMER4->EVENTS_COMPARE[0];
  NRF_PPI->CH[PPI_CHANNEL].TEP = ( uint32_t )&NRF_SAADC->TASKS_START;
  NRF_PPI->FORK[PPI_CHANNEL].TEP = ( uint32_t )&NRF_SAADC->TASKS_SAMPLE;
  NRF_PPI->CHENSET = ( 1UL << PPI_CHANNEL );
}

int32_t copyData()
{
  dataBuffer[dataBufferIndex] = adcBuffer[0];
  dataBufferIndex = ( dataBufferIndex + 1 ) % DATA_BUFFER_SIZE;
  return dataBufferIndex;
}

int32_t sendData( uint32_t interval )
{
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis >= interval )
  {
    previousMillis = currentMillis;
    
    Serial.println( dataBuffer[dataBufferIndex] );
    dataBufferIndex = ( dataBufferIndex + 1 ) % DATA_BUFFER_SIZE;
    return dataBufferIndex;
  }
  return -1;
}

Great Code!

I am using Pyserial to read the data from the serial port. It seems like I cannot sample a sine wave with more than 5Hz. Any improvement suggestions? Thank you in advance

import serial

ser = serial.Serial('COM4',9600)

f = open('dataFile.txt','w')

while 1 :
    cont=ser.readline()
    cont = cont.decode('utf-8')
    f.write(cont)
    f.close()
    f = open('dataFile.txt','a')

Can you please be a bit more specific what you mean by sampling with more than 5Hz? The sketch separates sampling the data from printing the data.

I did a few tests with pyserial on my laptop and was able to reliably receive valid data with fairly high speed. I have some tips for your python script.

  • open and closing files are expensive operations, open the files once and then just write to it
  • the python documentation suggests to use the following construct to provide predefined clean-up, closing the file
with open("myfile.txt") as f:
    ...

See 8.8 Predefined Clean-up Actions at https://docs.python.org/3/tutorial/errors.html

My problem is that when I generate any signal over 5kHz, I do not get accurate results. Even when I use context manager, the problem still persists:

I used this python - How can I improve PySerial read speed - Stack Overflow which is supposed to give around 790 kB/sec

and although slightly better, my 10KhZ wave is not sampled properly. Is there any other way to further improve the pyserial reading?

Thank you in advance!

Why do you think pyserial is your issue? If your computer cannot keep up with the serial data, you can simply increase the DATA_SEND_INTERVAL. I doubt that is necessary. What kind of PC do you have?

How do you determine that the 10kHz signal is not sampled properly? Are you missing samples? You will only get a few samples per wave. What do you do with these samples? e.g., do you display the data or use some algorithm ...

Also be aware, the signal from the ADC will have noise it. The power converter of the Arduino Nano 33 BLE and IoT are not a good solution for accurate ADC. You can sample a constant voltage and see what the range of the noise is.

Thank you! I managed to resolve that. I love the way your code is written. I am struggling to add more than one ADC convert channel (I have 3 inputs). Is there any solution to it?

You only need to add the configuration for the additional channels and increase the ADC_BUFFER_SIZE to 3. I copied the data to three data buffers and print them using TABs in between. This will show all three signals in the Serial Plotter window. I chose a DATA_BUFFER_SIZE of 500 because that is one full window. You can increase that to what you need as long as the total is less than total RAM.

/*
  This experimental code shows how to control the nRF52840 SAADC using a Timer and PPI.
  The SAADC uses EasyDMA to copy each sample into a variable in memory.
  The ADC samples pin A0, A1 and A2.
  The Timer is started by pulling a digital pin LOW.
  At the end of the measurement the data is send via Serial to be displayed in the Serial Plotter.
  Note:
  - the maximum sampling rate is 200kSamples/s
  - this code has not been tested with a debugger, some samples might be lost due to mbedOS (needs further testing)
  - this code will likely not work when using analogRead() on other pins
  
  The circuit:
  - Arduino Nano 33 BLE/ BLE Sense board.

  This example code is in the public domain.
*/

#include "mbed.h"

#define SAMPLES_PER_SECOND  20000
#define PPI_CHANNEL         (7)

#define ADC_BUFFER_SIZE     3
volatile nrf_saadc_value_t adcBuffer[ADC_BUFFER_SIZE];
volatile bool adcFlag = false;


#define DATA_BUFFER_SIZE    500
volatile nrf_saadc_value_t dataBuffer[DATA_BUFFER_SIZE];
uint32_t dataBufferIndex = 0;
volatile nrf_saadc_value_t dataBuffer2[DATA_BUFFER_SIZE];
volatile nrf_saadc_value_t dataBuffer3[DATA_BUFFER_SIZE];


#define BUTTON_START_PIN    10

#define DATA_SEND_INTERVAL  5


void setup()
{
  Serial.begin( 9600 );
  while ( !Serial );

  initADC();
  initTimer4();
  initPPI();
  pinMode( BUTTON_START_PIN, INPUT );
  Serial.println( "A0\tA1\tA2" );
}

void loop()
{
  enum APP_STATE_TYPE { APP_WAIT_FOR_START,
                        APP_START_MEASUREMENT,
                        APP_COLLECT_SAMPLES,
                        APP_STOP_MEASUREMENT,
                        APP_SEND_DATA,                         
                        APP_STATE_RESTART = 255
                      };  

  static uint32_t state = 0;
  
  switch ( state )
  {
    case APP_WAIT_FOR_START:
      if ( digitalRead( BUTTON_START_PIN ) == LOW )
      {
        state++;
      }
      break;
    case APP_START_MEASUREMENT:
      startTimer4();
      state++;
      break;
    case APP_COLLECT_SAMPLES:
      if ( adcFlag )
      {
        if ( copyData() == 0 )
        {
          state++;
        }
        adcFlag = false;      
      }
      break;
    case APP_STOP_MEASUREMENT:
      stopTimer4();
      dataBufferIndex = 0;
      state++;
      break;
    case APP_SEND_DATA:
      if ( sendData( DATA_SEND_INTERVAL ) == 0 )
      {
        state++;
      }
      break;
    default:
      dataBufferIndex = 0;
      state = 0;
      break;
  }
}


extern "C" void SAADC_IRQHandler_v( void )
{
  if ( NRF_SAADC->EVENTS_END != 0 )
  {
    NRF_SAADC->EVENTS_END = 0;
    adcFlag = true;
  }
}


void initADC()
{
  nrf_saadc_disable();

  NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT;

  // see Datasheet page 578 Pin assignments and Arduino Nano 33 BLE Pin Diagram
  // P0.04 - AIN2 -> Pin A0
  NRF_SAADC->CH[2].CONFIG = ( SAADC_CH_CONFIG_GAIN_Gain1_4    << SAADC_CH_CONFIG_GAIN_Pos ) |
                            ( SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos ) |
                            ( SAADC_CH_CONFIG_REFSEL_VDD1_4   << SAADC_CH_CONFIG_REFSEL_Pos ) |
                            ( SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos ) |
                            ( SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos ) |
                            ( SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos );

  NRF_SAADC->CH[2].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput2 << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[2].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;

  // P0.05 - AIN3 -> Pin A1
  NRF_SAADC->CH[3].CONFIG = ( SAADC_CH_CONFIG_GAIN_Gain1_4    << SAADC_CH_CONFIG_GAIN_Pos ) |
                            ( SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos ) |
                            ( SAADC_CH_CONFIG_REFSEL_VDD1_4   << SAADC_CH_CONFIG_REFSEL_Pos ) |
                            ( SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos ) |
                            ( SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos ) |
                            ( SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos );

  NRF_SAADC->CH[3].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput3 << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[3].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;

  // P0.30 - AIN6 -> Pin A2
  NRF_SAADC->CH[6].CONFIG = ( SAADC_CH_CONFIG_GAIN_Gain1_4    << SAADC_CH_CONFIG_GAIN_Pos ) |
                            ( SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos ) |
                            ( SAADC_CH_CONFIG_REFSEL_VDD1_4   << SAADC_CH_CONFIG_REFSEL_Pos ) |
                            ( SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos ) |
                            ( SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos ) |
                            ( SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos );

  NRF_SAADC->CH[6].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput6 << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[6].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;


  NRF_SAADC->RESULT.MAXCNT = ADC_BUFFER_SIZE;
  NRF_SAADC->RESULT.PTR = ( uint32_t )&adcBuffer;

  NRF_SAADC->EVENTS_END = 0;
  nrf_saadc_int_enable( NRF_SAADC_INT_END );
  NVIC_SetPriority( SAADC_IRQn, 1UL );
  NVIC_EnableIRQ( SAADC_IRQn );

  nrf_saadc_enable();

  NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
  while ( NRF_SAADC->EVENTS_CALIBRATEDONE == 0 );
  NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
  while ( NRF_SAADC->STATUS == ( SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos ) );
}


void initTimer4()
{
  NRF_TIMER4->MODE = TIMER_MODE_MODE_Timer;
  NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
  NRF_TIMER4->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
  NRF_TIMER4->PRESCALER = 0;
  NRF_TIMER4->CC[0] = 16000000 / SAMPLES_PER_SECOND; // Needs prescaler set to 0 (1:1) 16MHz clock
//  NRF_TIMER4->TASKS_START = 1;
}

void startTimer4()
{
  NRF_TIMER4->TASKS_START = 1;
}

void stopTimer4()
{
  NRF_TIMER4->TASKS_STOP = 1;
}


void initPPI()
{
  NRF_PPI->CH[PPI_CHANNEL].EEP = ( uint32_t )&NRF_TIMER4->EVENTS_COMPARE[0];
  NRF_PPI->CH[PPI_CHANNEL].TEP = ( uint32_t )&NRF_SAADC->TASKS_START;
  NRF_PPI->FORK[PPI_CHANNEL].TEP = ( uint32_t )&NRF_SAADC->TASKS_SAMPLE;
  NRF_PPI->CHENSET = ( 1UL << PPI_CHANNEL );
}

int32_t copyData()
{
  dataBuffer[dataBufferIndex] = adcBuffer[0];
  dataBuffer2[dataBufferIndex] = adcBuffer[1];
  dataBuffer3[dataBufferIndex] = adcBuffer[2];
  dataBufferIndex = ( dataBufferIndex + 1 ) % DATA_BUFFER_SIZE;
  return dataBufferIndex;
}

int32_t sendData( uint32_t interval )
{
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis >= interval )
  {
    previousMillis = currentMillis;
    Serial.print( dataBuffer[dataBufferIndex] );
    Serial.print( '\t' );
    Serial.print( dataBuffer2[dataBufferIndex] );
    Serial.print( '\t' );
    Serial.println( dataBuffer3[dataBufferIndex] );
    
    dataBufferIndex = ( dataBufferIndex + 1 ) % DATA_BUFFER_SIZE;
    return dataBufferIndex;
  }
  return -1;
}

This what the Serial Plotter looks like.
3 Channel ADC

1 Like

Thank you so much. I am sorry I did not see it before!

Klaus_K

2- questions

  1. I have code where I need a non-blocking analogRead. I am using the "NRF52_MBED_TimerInterrupt.h" interrupts to fire this at a precise time - but I found the AnalogRead function is a no no in an interrupt routine. I can follow your setup of the ADC and its interrupt which flags its completion. I am a little lost on the timer stuff. I see the setup of the timer but I do not understand how it is linked to the initPPI routine where the sample is started. If I have my own timer interrupt routine or if I just want to fire off a non-blocking sample from my code- do I need to just add the bottom 3 lines of the initPPI routine to start the sample?
    2)I had posted in a different thread about interrupts and using the PWM chip on the BLE33 or Sense. I noticed here you used the _v for the routine handler - is this the reason why my interrupts never seemed to fire?

Thanks and Regards

My example does not use timer interrupts. The timer is connected to to the ADC via the PPI system. The PPI system has a hardware channel from the timer Event End Point (EEP) to the ADC with two Task End Points (TEP). Once everything is setup and the timer is started, everything is controlled by hardware until the ADC interrupt fires.

Unfortunately, most of the mbed stuff is compiled into a library and included as an .a file. I had a look into it using readelf and found a symbol table with all the interrupt vectors. Some have a _v at the end of the name.