Cannot get timer on the Nano 33 BLE working

Hi!

I want to use the timer on the Nano 33 BLE to generate periodic interrupts. I could not find any oficial arduino documentation on this, so I am using the nRF52840 datasheet.

My code compiles and uploads to the arduino succesfully, but nothing happens after that. I think that the interrupt "function" is never called.

volatile int flag = 0;

extern "C"
{
   void TIMER2_IRQHandler()
  {
    if (NRF_TIMER2->EVENTS_COMPARE[0] == 1)
    {   
        flag++;
        NRF_TIMER2->EVENTS_COMPARE[0] = 0;
    }
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Configuring timer");
 
    NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
    NRF_TIMER2->PRESCALER = 4 << TIMER_PRESCALER_PRESCALER_Pos;
    NRF_TIMER2->CC[0] = 10000;
    NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
    NRF_TIMER2->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
 
    NVIC_EnableIRQ(TIMER2_IRQn);
    NRF_TIMER2->TASKS_START = 1;
}

void loop() {
  Serial.println(flag);
}

I would really apreciate if someone could help me with my code, or tell me how to accomplish what i want some other way.

There appear to be two issues:

  • You need to give your ISR a "_v" suffix; TIMER1_IRQHandler_v
  • It seems TIMER2 is already reserved by the mbed OS*; try a different timer

* See: mbed-os/pal_timer.c at bfbed2de7b52aa93806127c06a6113d04620cacc · ARMmbed/mbed-os · GitHub
If you declare TIMER2_IRQHandler_v it conflicts at link time with that file.

I can confirm that using brisk_CZ's code with arduarn's advice (I used timer 4) works.

Hi,

Thanks for the example which is very helpful - it worked perfectly with the suggested changes on my Nano33 BLE.

I have been attempting to extend the functionality to include an analogue read and an SPI transaction within the ISR. My eventual aim is to create a datalogger with as fast and consistent a sample rate as I can achieve, hence the plan to do it under interrupt control.

As a first-step on this project I have programmed my device to read analogue values from the ADC and write them to a serial flash memory device. Unfortunately it seems that attempting either an 'analogueRead()' or an 'SPI.transfer()' from within the ISR causes problems. The program below is a simple modification of the original example but with a couple more variables and some additional lines inside the ISR which demonstrate the problem(s).

/*
 * Investigation of interrupt problems with AI and SPI transactions within ISR
 * based on (https://forum.arduino.cc/index.php?topic=664425.0)
 */

// define global variables
int sensorPin = A0;                         // select the input pin for the sensor
const int slaveSelectPin = 10;              // set pin 10 as the slave select
byte dummy_reg;                             // variable for SPI transaction
volatile int flag = 0;
volatile unsigned int rawInput;

// include the SPI library
#include <SPI.h>

extern "C"
{
   void TIMER1_IRQHandler_v()
  {
    if (NRF_TIMER1->EVENTS_COMPARE[0] == 1)
    {   
      // Read value from the input
      // rawInput = analogRead(sensorPin);    

      // Attempt SPI transaction (write disable, Microchip SST25VF080B serial flash)
      digitalWrite(slaveSelectPin, LOW);    // take the SS pin low to select the chip
      // dummy_reg = SPI.transfer(0x04);
      digitalWrite(slaveSelectPin, HIGH);   // take the SS pin high to de-select the chip
      
      flag++;
      NRF_TIMER1->EVENTS_COMPARE[0] = 0;
    }
  }
}

void setup() {

  // setup digital output pins
  pinMode(slaveSelectPin, OUTPUT);            // set & initialise SPI slaveSelectPin as an output:
  digitalWrite(slaveSelectPin, LOW);
  
  // Set the ADC resolution to 12-bit (0..4095)
  analogReadResolution(12); // Can be 8, 10, 12 or 14

  // initialize SPI:
    Serial.println("Initialising SPI ...");
  SPI.begin();
  SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
  
  Serial.begin(115200);
  Serial.println("Configuring timer");
 
    NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
    NRF_TIMER1->PRESCALER = 4 << TIMER_PRESCALER_PRESCALER_Pos;
    NRF_TIMER1->CC[0] = 10000;
    NRF_TIMER1->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
    NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
 
    NVIC_EnableIRQ(TIMER1_IRQn);
    NRF_TIMER1->TASKS_START = 1;
}

void loop() {
  Serial.print(flag);
  Serial.print("     ");
  Serial.println(rawInput);
}

Line 23 is an analogue read, and line 27 is an SPI output command to the flash device If lines 23 & 27 are both commented-out then the program compiles and runs fine - the value of 'flag' is output to the serial monitor (together with a zero value for raw input) and the digital output (pin 10) can be observed on a scope.

If line 23 only is un-commented then I would expect the raw ADC value to be output to the serial monitor along with the value of 'flag'. What actually happens is the program compiles and uploads correctly but no activity observed on pin 10. Port selection in 'Tools' menu is greyed-out and serial monitor cannot be opened (error message 'Board at COM8 is not available' is displayed). To restore correct operation I need to double-press the reset button and upload the sketch with the offending line commented-out again.

Line 27 is an output to the serial flash which doesn't return anything. So if I un-comment this line I don't expect the program to do anything observably different, but it does serve to demonstrate the problem. If line 27 only is un-commented then the behaviour is exactly the same as for un-commenting line 23.

I suppose strictly speaking this is two problems, one with analogue read and another with SPI, but the symptom is the same and I am assuming (rightly or wrongly) that they might have the same cause or at least be related.

In my setup I have a 10k potentiometer driving the analogue input, and a serial Flash device connected (Microchip SST25VF080B 8Mbit SPI Serial Flash). I don't think this matters for the example program but I have included a sketch of the circuit for completeness.

I've had some success with Uno and Nano projects but the Nano33 BLE is completely new to me. An explanation as to why the system is behaving in this way, or any other assistance you could give me would be very much appreciated.

Had to rewrite this post after another forum glitch making me appear logged out. Grrr.

An Uno or Nano with Arduino core is a fairly simple direct-to-hardware affair.
A Nano33 BLE uses a much more advanced (read complicated) processor and, to simplify development of the core, the Arduino team developed the Arduino core for the 33 on top of the Mbed OS, which is itself based on CMSIS-RTOS. So you have to be mindful of what the OS is doing behind the scenes and be prepared to do a little extra research when you go beyond basic Arduino sketches.

ISRs are best kept as short and quick as possible; ideally just setting a flag which you then poll for in your main execution context. Using analogRead is not ideal as it takes a period of time to perform the conversion during which interrupts are disabled. However, the real issue with using analogRead is as follows (quick and dirty search through code follows; no warranty provided):

your sketch: enters ISR context, interrupts are disabled

your sketch: analogRead(sensorPin)

wiring_analog.cpp:

int analogRead(pin_size_t pin)
{
  ...
  return (mbed::AnalogIn(analogPinToPinName(pin)).read_u16() >> (16 - read_resolution)) * multiply_factor;
}

mbed-os/drivers/AnalogIn.h:

class AnalogIn {
...
    virtual void lock()
    {
        _mutex->lock();
    }
...
static SingletonPtr<PlatformMutex> _mutex;
...
}

mbed-os/drivers/source/AnalogIn.cpp:

unsigned short AnalogIn::read_u16()
{
    lock();
    unsigned short ret = analogin_read_u16(&_adc);
    unlock();
    return ret;
}

mbed-os/platform/include/platform/PlatformMutex.h:

#include "rtos/Mutex.h"
typedef rtos::Mutex PlatformMutex;

mbed-os/rtos/source/Mutex.cpp:

void Mutex::lock(void)
{
    osStatus status = osMutexAcquire(_id, osWaitForever);
...
}

cmsis_os2.h:

osStatus_t osMutexAcquire (osMutexId_t mutex_id, uint32_t timeout);

Looking at CMSIS documentation, the following is stated:

Note
Mutex management functions cannot be called from Interrupt Service Routines (ISR), unlike a binary semaphore that can be released from an ISR.

BOOM!

Hi,

Thanks very much for your reply. There's quite a lot for me to unpick, but I can see that I can't do what I was trying to do (at least not in the way that I was trying to do it) because of the mutex call restriction. I'll have a re-think. I can also see that I need to learn about Mbed OS if I'm going to make the most of the Nano33 BLE!