Timing problems with XIAO nrf52840

I want to switch from Arduino Nano BLE 33 to Seeed XIAO nrf52840 (because of size and price). So it is near at hand to use the toolchain with mbed-OS. But my BLE communication (based on Direct Register Programming) does not work with Seeed, though it works with Arduino. So I switched to the toolchain with TinyUSB and indeed, my BLE communication is working, but very very slow. I started with some tests:

First I wanted to compare the mbed-OS toolchains of Arduino and Seeed concerning the timing via micros(), because that is my basic function for time control. Following my test program:

// ----------------------------------------------------------------------------
//                T e s t T i m i n g . i n o
// ----------------------------------------------------------------------------
// Simple program to test the timing of Arduino boards (and compatibles)
// Information about the used functions comes from ARDUINODOCS
//
#include "Arduino.h"
#ifdef UseTinyUSB
#include <Adafruit_TinyUSB.h>
#endif

#define macroMics micros()
// Using a macro for the call to <micros()> to test own routines instead later

unsigned long loopStartMics = 0;  // Will be set beginning of loop
unsigned long loopEndMics   = 0;  // Will be set end of loop and in setup
unsigned long oldStartMics  = 0;  // To check the whole cycle

bool  togglePrint = false;
// Being prepared to measure loop time without serial communication
// for finer measurements of cycle times

// Some configurations
//
void setup()
{
  //Initialize serial and wait for port to open:
  Serial.begin(115200); // Bitrate is not relevant for USB CDC
  while (!Serial) { ; } // wait for serial port to connect. Needed for native USB
  loopEndMics = macroMics;  // Preset for end of loop
}

// The loop function is called in an endless loop
// So this is the function (entry) for the user and its timing will be relevant
//
void loop()
{
  oldStartMics = loopStartMics;

  // --------------------------------------------------------------------------
  // Testing the behaviour of <micros()>
  // --------------------------------------------------------------------------
  // To test the timing you need a function returning a time with a high
  // resolution. On Arduino this the function <micros()>
  //
  loopStartMics = macroMics;   // Time of entering the loop

  unsigned long loopOutMics = loopStartMics - loopEndMics;
  // Time outside the loop, it is the cycle time for empty loops

  // Measure the time of running micros itself
  //
  for(int i = 0; i < 1000; i++)
    macroMics;

  unsigned long microsCallTime = macroMics - loopStartMics;
  // this is the delay caused by using <micros()> muliplied with 1000
  // and neglecting the delay caused by the for-loop and the 2 extra <micros()>.

  loopEndMics = macroMics;

  // --------------------------------------------------------------------------
  // Printing the result via Serial
  // --------------------------------------------------------------------------
  // Printing every 2. loop-cycle to measure loopOutMics without influence by
  // delay of printing and <delay(1000)>
  if(togglePrint)
  {
    Serial.print("1000x micros = ");
    Serial.print(microsCallTime);
    Serial.print("   Out of loop time = ");
    Serial.println(loopOutMics);
    delay(1000);
    togglePrint = false;
  }
  else
    togglePrint = true;
}

With this test I found, that from the view of using micros(), Arduino and Seeed are identical. Calling micros() takes 8.6 microseconds (it will be less because the for-loop is included here) and the delay outside loop() is about 9 microseconds. So an empty loop() would be repeated with 100 kHz. No problems for my software, because my high speed state machines run with 10 kHz. My problem with the BLE communication on Seeed will have other reasons.

This test running with the TinyUSB (not mbed-OS, but some RTOS seems to be used) gives the surprising result, that no time is needed for the call of micros() and for the delay outside loop() (0 microseconds). Simply, micros() is always (in the slots of my testing) returning the same value. So time difference is zero. Estimating from the speed of my BLE software, micros() seems to change its return value only every millisecond. Hopefully the value is then increased by 1000.

Because my BLE is at least running, my next step is to write my own micros() routine based on using a timer of the nRF52840 (of course with direct register access). Currently I have no idea, why my BLE is running on the Arduino but not on the Seeed (both using mbed-OS and having the same timing with micros()). So far I will continue with TinyUSB.

Hi there,

So You got APPLES and ORANGES :face_with_open_eyes_and_hand_over_mouth: there.

What I see technically

  1. You’re treating XIAO like a pin-compatible Nano 33 BLE. It isn’t.
    Your “Direct Register BLE” code is almost certainly built around:
  • Arduino mbed core’s clock/timer setup
  • Nano 33’s BLE stack (mbed/Cordio)
  • Specific assumptions about interrupts & timing Move that over to the XIAO with a different core (Seeed’s BSP, TinyUSB, different startup code) and those assumptions are just wrong. The fact that it “kinda works but slow” under TinyUSB screams timing/stack mismatch, not “micros() is broken.”
  1. Your micros() test on TinyUSB is misleading.
    On the TinyUSB core You’re using, micros() is clearly only updated at 1 kHz (i.e., it’s basically millis() * 1000), so within a 1 ms slot You always get the same value → Your loop sees 0 µs deltas. That doesn’t mean micros() is “free”, it means:
  • The core isn’t providing true microsecond resolution.
  • The 10 kHz state machines that depended on true µs timing are now living on top of a 1 kHz tick. Of course BLE “feels slow”.So you’re correctly measuring that the resolution changed, but you’re drawing the wrong conclusion from it.
  1. You are trying to re-implement the runtime instead of using it.
    Now you want to: :grin:

“write my own micros() routine based on using a timer of the nRF52840 (of course with direct register access).”
That’s basically recreating what the BSP already does, and it’s still going to be fighting:

  • Low-power modes
  • Clock sources (HFCLK/LFCLK)
  • Interrupt priorities vs BLE radio All for something the existing ArduinoBLE / Seeed core already solves cleanly.

You need to UNDERSTAND they are not the same Hardware, same nrf52 cores that’s where it ends Bro. :grin: :v:

You’ve basically discovered that the different cores set up clocks and timers differently – the Nano 33 BLE’s mbed core gives you a real microsecond timer, the XIAO core you’re using with TinyUSB is only updating micros() every 1 ms. So your test is really showing a change in timer resolution, not that micros() is “free” or that the chip suddenly got slow.

On top of that, your “direct register BLE” code is tailored to the Nano 33’s mbed/BLE stack. Moving that 1:1 onto the XIAO with a different BSP (and different startup, interrupts, and timing) is why it works on the Arduino board and misbehaves on the Seeed board.

If the goal is a small, low-power BLE beacon, the easiest and most robust route on the XIAO nRF52840 is:

  • Use the correct XIAO nRF52840 BSP
  • Use the built-in BLE stack (ArduinoBLE / Bluefruit / Nordic SDK)
  • Let the core handle timers and low-power, and build your state machines on top of that.

You can go full “direct register + custom micros() + custom BLE”, but at that point you’re basically writing your own mini-SDK – and you’ll keep running into exactly these clock/timer/stack differences every time you switch boards.

FIX_ IMO
(use the proper BSP + BLE library), stop fighting the core and stop treating Nano 33 code as drop-in for XIAO.

HTH
GL :santa_claus: PJ :v:

Perhaps one should indicate the AI source when pasting so much AI generated content?

Hi there,

Perhaps we can concentrate on the answer! or you could provide one, or how and with what experience I derived and delivered this one is NOT important the results are. You’re just trying to be funny :grin: I get it..
btw I did..

You just didn’t see it.

:santa_claus:

:index_pointing_up: GL :slight_smile: PJ :v:
Seasons Greetings :evergreen_tree:

Your going to Love the next One…

Hi there,

So , I was running the code when the Peanut Gallery distracted me, anyway here is how it looks, implemented lightly, I connected the PPK2 and The BLE to show the info more clearly why you want to NOT re-invent the wheel . But like some on here , GO for it!
YOLO :santa_claus: :v:
On the XIAO nRF52840 you don’t need to fight the core. Use the XIAO nRF52 BSP with ArduinoBLE, let it own the radio, clocks, and timers, and just update your beacon payload at whatever interval you want.

The sketch below does exactly what you’re trying to do:

  • small battery beacon
  • advertises a 2-byte “pressure” value
  • updates every 5 seconds
  • no direct register BLE and no custom micros() required

If you like register-level work (I get it, 6502/Z80 alumni here too), save it for peripherals that actually need it. The BLE stack is already tuned for timing and low power; bolting a home-made time base and custom radio control on top of it is exactly why it behaves differently between Nano 33 and XIAO.
GL :slight_smile: PJ :+1:

// ================================================================
// PJ-style Simple BLE Beacon for Seeed XIAO nRF52840
// - Uses ArduinoBLE (mbed core)
// - Advertises a 2-byte "pressure" value in manufacturer data
// - Updates every 5 seconds using millis(), not heroics with micros()
// ================================================================

#include <Arduino.h>
#include <ArduinoBLE.h>

// Just a dummy value to show something changing
uint16_t pressureValue = 1000;

// Update interval (ms)
const uint32_t ADV_UPDATE_INTERVAL_MS = 5000;
uint32_t lastUpdate = 0;

// Manuf. ID: pick something non-zero and non-Apple
// (0xFFFF is "experimental / test" per Bluetooth spec)
const uint16_t MANUFACTURER_ID = 0xFFFF;

void setup() {
  Serial.begin(115200);
  //while (!Serial) {
    // Give USB a moment on dev bench; in the field this returns immediately
 // }
delay(3000);
  Serial.println("XIAO nRF52840 BLE Beacon - PJ sanity edition");
  Serial.println("Bringing up ArduinoBLE...");

  if (!BLE.begin()) {
    Serial.println("Failed to start BLE. Check core/BSP selection.");
    while (1) {
      delay(1000);
    }
  }

  // Local name (what scanners will see)
  BLE.setLocalName("PressurePoint");
  BLE.setDeviceName("PressurePoint");

  // Initial manufacturer data: 2-byte pressure
  uint8_t manufData[4];
  manufData[0] = lowByte(MANUFACTURER_ID);
  manufData[1] = highByte(MANUFACTURER_ID);
  manufData[2] = lowByte(pressureValue);
  manufData[3] = highByte(pressureValue);
  BLE.setManufacturerData(manufData, sizeof(manufData));

  // We’re just a broadcaster, no services/characteristics needed
  BLE.setAdvertisedServiceUuid(""); // keep it pure ADV if you like

  // Start advertising
  BLE.advertise();

  Serial.println("BLE advertising started as 'PressurePoint'.");
  Serial.println("Updating pressure in advertising payload every 5 seconds...");
}

void updateAdvertisingData() {
  // Bump the dummy “pressure” a bit, wrap it
  pressureValue++;
  if (pressureValue > 1099) {
    pressureValue = 1000;
  }

  uint8_t manufData[4];
  manufData[0] = lowByte(MANUFACTURER_ID);
  manufData[1] = highByte(MANUFACTURER_ID);
  manufData[2] = lowByte(pressureValue);
  manufData[3] = highByte(pressureValue);

  BLE.setManufacturerData(manufData, sizeof(manufData));

  // Make sure we’re still advertising (should be, but cheap insurance)
  if (!BLE.advertise()) {
    BLE.advertise();
  }

  Serial.print("Updated adv pressure = ");
  Serial.println(pressureValue);
}

void loop() {
  // Let the stack do its housekeeping
  BLE.poll();

  uint32_t now = millis();

  if (now - lastUpdate >= ADV_UPDATE_INTERVAL_MS) {
    lastUpdate = now;
    updateAdvertisingData();
  }

  // On mbed/ArduinoBLE, this is already low-power friendly.
  // When you’re done debugging, kill most Serial prints and
  // the core will park the CPU between radio events.
}

Serial Output

XIAO nRF52840 BLE Beacon - PJ sanity edition
Bringing up ArduinoBLE...
BLE advertising started as 'PressurePoint'.
Updating pressure in advertising payload every 5 seconds...
Updated adv pressure = 1001
Updated adv pressure = 1002
Updated adv pressure = 1003
Updated adv pressure = 1004
Updated adv pressure = 1005
Updated adv pressure = 1006
Updated adv pressure = 1007
Updated adv pressure = 1008
Updated adv pressure = 1009
Updated adv pressure = 1010
Updated adv pressure = 1011
Updated adv pressure = 1012
Updated adv pressure = 1013
Updated adv pressure = 1014
Updated adv pressure = 1015

HTH

Seasons Greetings :christmas_tree:
feel free to add the A SYSTEMOFF version (deepest sleep, wakes every 5 minutes)

Not trying to be funny. Was wanting a reference to the source so I could try find out how to get a Round Display working with NRF52840.

Since you have the Seeed Round Display working with XIAO NRF52840, but didn’t outline the steps (clearly enough for us “noobs”), I thought perhaps I could try work out the issues I’m facing with the same AI tools that you use?

Hi there,

FWIW,
So many know I’m from Pittsburgh , Greatest City in the country FACT! :grin:
and from the excellent connects at PCS while I worked at CMU_SEI some fellow technologists Guys and Girl who are much better versed at AI, put this together for me, I’m not using ChatGPT or any off-the-shelf toy. I have a private, highly customized AI model that I trained specifically on Nordic’s nRF Connect SDK, Zephyr, SoftDevice, ESP-IDF, NimBLE, and most of the ESP32 families. It’s also tuned on my own codebases, overlays, pin maps, and toolchain setups. It runs locally, uses a private vector engine for retrieval, and it’s not something I can share publicly.
But I do use it to help folks here when they get stuck, because it can solve low-level MCU issues faster than digging through scattered docs. Just clearing that up.
I only care that the solutions I give people compile, flash, and work on hardware. Results speak for themselves.”
As You were. :+1:

:santa_claus: Seasons Greetings. :christmas_tree:

Hey @PJ_Glasso
thank you very much for your answers.

The important point is, that I do not use BLE in the way it was invented for. I created a high speed broadcast network based on my own BLE-Beacons for measurement and control. I use it for many sensors publishing their values with a rate of 10 Hz (though my BLE communication itself has very high rates and a concept for avoiding collisions). I do not use any code from libraries and my classes directly manipulate the registers of the nRF52840.

The reason is, that I like this MC very much. When I detected it (years ago), I was amazed about the possibilities of wiring (like an FPGA) with the SHORTS- and PPI-Registers and the many peripherals, not to forget the remarkable size of RAM and Flash. It is really a pleasure to write programs for it. And after I (hopefully) accomplish to have that XIAO nRF52… running, I will focus on the nRF54… and adapt my classes.

1 Like

@grobasoz

I do not use any AI tools. Till now, I do not need them. May be, that changes, when I am getting to old to remember my own knowledge and capabilities.

2 Likes

I think that certain AI tools have been a useful addition to my development process.
I still depend on good old fashioned coding practices as a large number of my projects have been non standard embedded devices.

What fascinated me about your project was the novel use of the device,

I was asking for the AI source to be divulged so that others could benefit from the information and techniques in obtaining the best results.

Good luck with your transition to the new device!

Hi there,

Yes, probably the only use case to do such an Effort , Amazing you pushed through and now the tools are just tools to you. Using things in unconventional ways is what man Man not a monkey. :grin:
It’s obvious to most when AI is being deployed and used (not fooling anyone). You can clearly see it’s effect on posts I create. You are so Right! :+1:
It’s reminded me of some thing s I did forget, so in that sense very helpful.
Other are afraid to use it or don’t know how, and their is a Right Way and Wrong way IMO. People often criticize what they don’t fully comprehend or understand, trying to communicate with a piece of silicon adds another layer to the struggle.

It’s why we are Here and AI is NOT. So ask your questions and thank you for the contribution and thoughtful responses. it’s a niche use of BLE so I’m all ears.

Seasons Greetings :christmas_tree:
GL :santa_claus: PJ :v:

For this evening I am happy. It was rather easy to program the timer (3) for a 1MHz frequency and to fetch the timer value.

So the test program I have shown above now takes only 0.6 microseconds for “micros()” (8.6 with mbed). Tomorrow I will start my application with it. For this evening I will be happy with the test only. Because if my application would not run, I could not sleep this night.

Have a nice evening …

1 Like

XIAO nrf52840 is running as wished

With my micsec-timer (timer3 of nrf52840, 32 bit, 1 MHz) instead of Arduino micros() my XIAO with TinyUSB is running with the high speed BLE communication as it should …

But with the first tests I had the same no go as with the mbed OS. It had taken some time to detect the reason for that. It is not the BLE which did not run, it was the USB communication. Via Serial I control the BLE network (start, stop, set addresses, etc.) and fetch data. I have 2 communication paths on that line, one is a monitor (with prompt and interpreting commands for looking into the registers, measure speed, etc.). This serial communication works from the start without any problems. But when I switch from monitor to application (the other path), it did not work any more, though the same Serial is used.
The main difference is, that the communication with the application does not contain any LineFeed (or CarriageReturn) character, because it is made for being served by a graphical program on my PC.
Switching between these 2 paths is done with LineFeed (or CarriageReturn). This switch back to monitor always worked, but not the feeding of other characters (by the way, for testing I use a terminal on Linux for serial communication (GTKTerm, GitHub - wvdakker/gtkterm: GTKTerm: A GTK+ Serial Port Terminal ) without any interpretation; any pressed key goes directly to the MC-board and any received character is directly displayed on the screen).

I wanted to discuss this problem with a colleague. To show him the difference, I again programmed the board with the defect micros() function (1 ms steps). The USB communication works without any problem from the start, but the BLE network is lazy.
Then I programmed the board again with my timer replacement for micros(), to show him the USB communication problem. Hey :star_struck: , and now this also worked from the start without any problems and doing the high speed BLE as wished.
Tomorrow I will try a simple reset (plug USB on/off, this board is already inside a box and I cannot do any other manipulation) if the USB communication does not work properly.

I remember example programs from @PJ_Glasso , where there was some extra initialization (LineFeed) with Serial in his setup(). I did not store the links, so please give me some hints. I will make some tests with it. If that works, I will have the same behavior as with my Arduino Nano BLE 33.
No, not the same, even better, because the time measurement with my monitor shows, that the TinyUSB on XIAO has less load than the mbedOS USB on NANO.

So at least I am happy also this evening.

EDIT: Probably the mbed version of XIAO will also work after solving the USB communication problem.

1 Like

Hi there,

So , Wow… Good on you for getting it to GO :grin: :+1:
The While serial, can catch you, I comment them OUT, and just use a small delay after calling it to begin. That avoids any issues.

Great to know the Old ways still work ! :v:

Seasons Greetings :christmas_tree:

GL :santa_claus: PJ

1 Like