AnalogRead in ISR crashes system

Ok,

Before someone tells me I should not do an AnalogRead in an ISR, I have good reasons. I do this on the UNO with no issues. I need to do it likewise on the Nano 33 BLE. My ISR period is MUCH larger than the ~100usec period of each AnalogRead.

Folks have suggested the underlying OS on the Nano 33 BLE is the cause. Regardless, has someone come up with a workaround...short of programming the Nano 33 BLE outside of the Arduino architecture?

Thanks,

Steve

Welcome to the forum.

Could you please describe what you are trying to achieve and what your good reasons are? Maybe this will help us find out whether your reasons are good and find a solution for your issue.

sbailey64:
I do this on the UNO with no issues.

The two processors are quite different. I think it is like saying you can drive around in your car with open windows and therefore you should be able to do this with an airplane.

sbailey64:
My ISR period is MUCH larger than the ~100usec period of each AnalogRead.

There are other potential reasons for the issue for instance processor and NVIC priority level.

sbailey64:
Folks have suggested the underlying OS on the Nano 33 BLE is the cause.

With an OS you get rules. If you break the rules you get into trouble. This is true even if you think the rules are stupid for your application. Maybe we can bend the rules or maybe you need a different RTOS with different rules.

I did a little search and looked at the source code. The issue could be caused by:

The analogRead() function calls

adc = new mbed::AnalogIn(name);

..\AppData\Local\Arduino15\packages\arduino\hardware\mbed\1.3.2\cores\arduino\wiring_analog.cpp

The mbedOS documentation

https://os.mbed.com/docs/mbed-os/v6.9/apis/interruptin.html

states

Warnings:

No blocking code in ISR: avoid any call to wait, infinite while loop or blocking calls in general.

No printf, malloc or new in ISR: avoid any call to bulky library functions. In particular, certain library functions (such as printf, malloc and new) are non re-entrant, and their behavior could be corrupted when called from an ISR.

For printfs from interrupt context, use Event instead.

The call is conditional. Maybe when you create an analogIn global outside the ISR the issue can be avoided. See example in the mbedOS documentation.

https://os.mbed.com/docs/mbed-os/v6.9/apis/i-o-apis.html

Thank you...I'll give that a try.

Given mbed.h is seen by the Arduino compiler when I include it, I don't know why the following gets an error when I declare it:

AnalogIn ain(A0);

The above is apparently an Mbed function.

Any ideas?

Steve

Yes, you need to use analogPinToPinName() to convert the Arduino pin name to the PinName type e.g.

mbed::AnalogIn analogPin( analogPinToPinName( PIN_A0 ) );

or

mbed::AnalogIn analogPin( analogPinToPinName( A0 ) );

Thank you Klaus_K. That worked as far as compiling. Using mbed Analogin.read() function in my ISR still crashes my system. Bummer. I thought this might work.

Try the mbed functions:

analogPin.read_u16() // this is normalized to a full 16-bit range (you do not get more resolution but the highest value is 0xFFFF and the lowest is 0x0000)

or

analogPin.read() // float value (range 0.0 to 1.0)

Yes...I tried both functions to no avail. Still crashes. Below is my code. Came from one of the Arduino examples for Nano 33 BLE. Maybe I am setting something up incorrectly?

Thanks,

Steve

#define TIMER_INTERRUPT_DEBUG 0
#define TIMERINTERRUPT_LOGLEVEL 0

#include "NRF52_MBED_TimerInterrupt.h"
#include "NRF52_MBED_ISR_Timer.h"
#include "mbed.h"

mbed::AnalogIn analogPin( analogPinToPinName( A0 ) );

#ifndef LED_BUILTIN
#define LED_BUILTIN D13
#endif

// Init NRF52 timer NRF_TIMER3
NRF52_MBED_Timer ITimer(NRF_TIMER_3);

// Init NRF52_MBED_ISRTimer
// Each NRF52_MBED_ISRTimer can service 16 different ISR-based timers
NRF52_MBED_ISRTimer ISR_Timer;

#define TIMER_FREQ_HZ 1.0

void TimerHandler()
{
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
analogPin.read_u16();
}

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

delay(100);

Serial.print(F("\nStarting TimerInterruptLEDDemo on ")); Serial.println(BOARD_NAME);
Serial.println(NRF52_MBED_TIMER_INTERRUPT_VERSION);

// configure pin in output mode
pinMode(LED_BUILTIN, OUTPUT);

// Interval in microsecs
if (ITimer.attachInterrupt(TIMER_FREQ_HZ, TimerHandler))
{
Serial.print(F("Starting ITimer OK, millis() = "));
Serial.println(millis());
}
else
Serial.println(F("Can't set ITimer. Select another freq. or timer"));

}

void loop()
{
/* Nothing to do all is done by hardware. Even no interrupt required. */
}

look at this topic : Cannot get timer on the Nano 33 BLE working - Programming Questions - Arduino Forum

Thanks...but the link you provided explains the same problem with no solution. I am thinking the Nano 33 BLE is not going to work for my applications. I was attracted to the potential 200k sample time at 12 bit resolution. Oh well

I have written an experimental example that shows you how to use a Timer and PPI to control the ADC. This will avoid the need to use analogRead in the timer interrupt. The timer is starting the conversion automatically and the ADC uses EasyDMA to transfer the data to RAM.
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 );
}
2 Likes

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.