Bypassing mbedOS on Arduino Nano BLE 33

For my home automation projects I want to use radio communication. I found a very good solution with MySensors, based on Arduino.
But my concept of communication is founded on broadcast, like CAN used in cars. The goal is to realize a kind of swarm intelligence based on the idea, that any device knows the states of all other devices and includes the real time background of the whole system for local decisions.

Because the mainstream of communication is based on P2P (Master/Slave, Client/Server) and even sensor networks (e.g. BT-Piconet) are structured accordingly, I have to go my own way and develop the communication from the root.
One model near to my idea is the usage of Beacons with BLE. So I studied BLE (specification) and found a very good forum at NORDIC. I bought a development kit for the nRF52840 and tested a few examples.

But for BLE, the usage of the nRF52840 is somehow hidden inside so called Soft Devices and it has taken a lot of time to program my own Beacon with direct access to the registers of the M4 and its periphery.
Now I want to use the Arduino BLE 33 for a "Sensor Data Beacon", but there is a similar problem: The mbedOS hides the usage of the micro controller and I did not find information about the resources used by mbedOS (e.g. which timers are used).
Fortunately there is no significant background activity if You avoid to use the ArduinoBLE library (which I do not need).

So I started this thread for programmers who want to use the nRF52840 with the Arduino BLE 33 like I do without the mbedOS. That includes

  • using own callbacks for peripheral interrupts
  • using own hardware timers
  • using own libraries for I2C/TWI and other peripheral elements
  • etc.

A few things I found out meanwhile, but it is a start with a small lantern in the fog. My hope is, that there are many other programmers with similar ideas and together we crack all nuts.

Success with hardware timers and peripheral interrupts!

My first goal was, to use my own interrupt handlers for the periphery of the nRF52840. That is easily tested with hardware timers. So I developed my own Timer-class with integrated interrupt handler (one static function for each hardware timer branching to one non static function being the interrupt handler of the class).

It is perfectly running. So for interested programmers here the main hurdles:

To set the interrupt vectors to the static interrupt handlers (isr), use
NVIC_SetVector(irq, isr);
with irq = (IRQn_Type) Periphery-ID.
You can find the Periphery-ID in the nRF52840 product description, it is 8,9,10,26,27 for timers Timer 0 to Timer 4.
Enabling the physical interrupts is done with

The most important instruction of the interrupt handler is clearing the event. I used compare-event with CC[0], so the instruction is
timerPtr->EVENTS_COMPARE[0] = 0;
timerPtr is initialized in my timer class (based on the nRF52840 product description).
Without clearing the event, you get a never ending interrupt series with maximum speed, disturbing anything on the Nano BLE 33. And there is also a feedback to your PC because the USB-connection does not work any more and blocks USB at the PC.

I tested Timer 1 to Timer 4 and so far (with a simple test program using Serial.println) there was no disturbance on the Nano BLE 33. I did not dare to test Timer 0, because I suppose, that this timer is used by mbedOS and I depend on the Arduino function micros() in my own libraries, which is calling the mbedOS-timer on the Nano BLE 33. It is a pity, that you cannot dig into the mbedOS, because that is only an object library in the Arduino core.

Now I am happy with this experience and the next step will be to optimize my Timer-class and then to develop my own I2C/TWI-class with a comfortable interrupt handling.

Success with I2C/TWI communication on interrupts

I started with the so called legacy-twi-mode of the nRF52840 described in section 6.29 of the NORDIC product specification. There are also new modes, TWIM in section 6.31 and TWIS in section 6.32, using the so called EasyDMA. But I only want to have a basis for the LSM9DS1 on the Arduino Nano BLE 33 and the communication is rather simple/small. I think the DMA-modes may be better used with bigger data streams (EEPROM, etc.).

There is no interrupt line to the sensor, but using the interrupts for the TWI-events of the nRF52840 is enough for creating a background communication based on IRQ 3 (TWI0) and 4 (TWI1). I prepared handlers for the events ERROR, TXDSENT, RXDREADY and STOPPED. I did not use the shortcuts BB->SUSPEND or BB->STOP, because I did not really understand their behavior, especially with the requirement, that the STOP-task has to be triggered before reading from RXD at the last received byte.

I tested a few simple readings and writings and it works as I hoped. The CPU-load seems to be very small, I think, there will be drop-outs less than a microsecond, not the millisecond I lost with the Arduino libraries based on mbedOS.

Next step will be to create a new class for the LSM9DS1 to handle all features of the sensor, especially to set different scanning frequencies and observe the long time behavior.

LSM9DS1 not working without Event-Task-Shortcut

My pleasure with the interrupt driven TWI/I2C communication was to early. After reading wrong values from the LSM9DS1, I detected, that I always have read an extra byte. Though I triggered the STOP-Task before reading RXD with the RXDREADY-Event, it was too late to create a NAK on the line.
Now I understood the BB->STOP shortcut.
If it is enabled (SHORTS = 2) before the last byte is read, the STOP-event will be triggered by the incoming byte and there is time enough to prepare for NAK.
So in the TXDSENT-event of the written sub-address I had to trigger the STARTRX-task and to enable the shortcut BB->STOP. A CPU-triggered STOP-task is not necessary.
I disable the shortcut with the STOPPED-event.
Finally I understood Figure 167: The TWI master reading data from a slave.
But the usage of SHORTS for TWI should be better explained in the product specification.
Maybe I will also have to use the BB->SUSPEND shortcut when I go to high TWI speed. But first I will try to make the sensor working without that shortcut.

If you are going this route, you should start with a more simpler Cortex M0+ MCU...

Thanks for your comment.
But it is not my goal to replace I2C-libraries from Arduino which are working sufficiently based on direct MC-access (as there are for the Arduino ZERO with M0 and which I could adapt to my needs).
I want to use the BLE NANO 33 (with the radio feature of nRF52840 and the sensor LSM9DS1) for my own software environment, based on state machines needing short loop() cycles. All typical Arduino libraries I can use, but not those based on mbedOS, which are creating delays of milliseconds in loop().

One of the other things that can benefit you a lot is to learn how to use the DMA features for most of the higher end Cortex MCUs. This would make your state machine code more responsive, even when using "slow" peripheral such as UART.

Yes, thank You for the hint.
This is EasyDMA with the nRF52840 and it will be a reserve to go below microseconds delays.
At the time I am happy to be significant below the millisecond.
UART (Serial) is another problem for me with the NANO BLE, because it runs on the native USB and I have no knowledge to handle this.
Using the "real" UART is no problem, I had developed my own class based on interrupt handling for the Arduino DUE years ago and that is easy to adapt to the NANO BLE.

Sensor access working perfectly

I am happy with my latest routines to read measurement values from LSM9DS1.
But there was a high hurdle, which I did not expect.

With my Monitor (a class for doing direct single character based communication from a terminal) everything worked fine. I could configure the sensor and read values (triggered from the keyboard).
But when I used the tested functions in my application, the sensor was "dead", nothing happened.

Finally I found, that my interrupt driven TWI/I2C communication is only working, if I add a delay of more than 4 milliseconds after nRF52840 register initialization. So I added "delay(5)" at the end of my TWI/I2C begin() function and now the application runs.

My test application displays the acceleration values on a serial terminal with 115200 bits/s, which is about 300 formatted messages possible per second.
I have chosen for acceleration and gyro a scanning frequency of 119 Hz. I am scanning the status register for new values, creating an average from 6 samples and so producing about 20 measurements per second.
Converting to float values and sending to the terminal, I can watch the behavior of the sensor. The values are plausible and follow immediately the movements of the NANO BLE 33 board.

And the goal is reached: There is no delay caused by the TWI communication running on interrupts in the background.
Now I will create my Measurement-Beacon, which should publish about 20 measurements per second of all sensor data via BLE.

The final goal is to have about 32 NANO BLE 33 sending their measurement values (20 per second) to the PC for a graphical visualization.