Xiao BLE nrf52840

I want to use the Hardware Timer and the ppi of the Xiao BLE nrf52840. Actually i am Not able to start a Timer.

Is there any working example for the Timer with direct Register Access. For example like this:

NRF_TIMER1->CC[0] = 3; // 3 ticks = 200 ns
I am using the Arduino IDE and the Seeed Lib.

Any ideas would be helpful

Hi there,

Well it’s easier in Nrf_SDK of course, The GPIOTE isn’t well implemented in Arduino LIB, so You can do it this way, it configures TIMER1, PPI, and GPIOTE on the Xiao BLE nRF52840, and uses a capture task to read the current timer count (via channel 1) in the loop:

#include <Arduino.h>

#define LED_PIN LED_BUILTIN  // Onboard LED (active low on the Xiao)

// This example sets up TIMER1 to run in 32-bit mode at 16MHz (with no prescaler),
// using a compare value on channel 0 to automatically clear the timer.
// PPI channel 0 is configured to trigger GPIOTE to toggle LED_PIN whenever TIMER1 reaches the compare value.
// In the loop(), we capture the current timer value into CC[1] and print it.
// Tests the RGB LED on boot.
void setup() {
  Serial.begin(9600);
  delay (2000);
  Serial.println(); // only for mbed 2.9.x
  Serial.println("Power ON \n ");  // Let's BEGIN!!
  initdisplay();    // INIT pins for LED 
  delay (2000);
  Serial.println("Xiao RGB LED Test compiled on " __DATE__ " at " __TIME__);
  Serial.println("Processor came out of reset.");
  Serial.println();
  setupblink();  // RG
  Serial.println("Starting TIMER1, PPI, and GPIOTE example with capture...");

  
  // --- TIMER1 Configuration ---
  NRF_TIMER1->TASKS_STOP = 1;    // Stop timer
  NRF_TIMER1->TASKS_CLEAR = 1;   // Clear timer
  NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;           // Set timer to Timer mode
  NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_32Bit;    // 32-bit mode
  NRF_TIMER1->PRESCALER = 0;   // No prescaling (16 MHz clock; ~62.5 ns per tick)
  NRF_TIMER1->CC[0] = 3;       // Set compare value: 3 ticks (~187.5 ns)
  NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Msk; // Automatically clear timer on compare match
  NRF_TIMER1->EVENTS_COMPARE[0] = 0;  // Clear any pending compare event

  // --- GPIOTE Configuration ---
  // Configure GPIOTE channel 0 to toggle LED_PIN when its task is triggered.
  NRF_GPIOTE->CONFIG[0] =
      (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |       // Task mode
      (LED_PIN << GPIOTE_CONFIG_PSEL_Pos) |                       // Select LED_PIN
      (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) | // Toggle on task trigger
      (1 << GPIOTE_CONFIG_OUTINIT_Pos);                           // Initial state HIGH (LED off)

  // --- PPI Configuration ---
  // Configure PPI channel 0 to link TIMER1 COMPARE[0] event to GPIOTE TASKS_OUT[0].
  NRF_PPI->CH[0].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];
  NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];
  NRF_PPI->CHENSET = PPI_CHEN_CH0_Msk;  // Enable PPI channel 0

  // Start TIMER1.
  NRF_TIMER1->TASKS_START = 1;
}

void loop() {
  // Capture the current TIMER1 count into CC[1].
  NRF_TIMER1->TASKS_CAPTURE[1] = 1;
  uint32_t counter = NRF_TIMER1->CC[1];

  Serial.print("TIMER1 COUNTER: ");
  Serial.println(counter);
  delay(1000);  // Print the counter value every second.
}

void setupblink(){
  Serial.print("Testing R G B LED - ");
   
   delay(2000);
   setLedRGB(true, false, false);  // Red
   Serial.print("RED, ");
   delay(2000);
   setLedRGB(false, true, false);  // Gren
   Serial.print("GREEN, ");
   delay(2000);
   setLedRGB(false, false, true);  // Blue 
   Serial.println("BLUE... "); 
   delay(2000);
   setLedRGB(false, false, false);  // OFF
}

void initdisplay() {
  pinMode(LEDR, OUTPUT);  // initialize the LED pin as an output:
  pinMode(LEDG, OUTPUT);  // initialize the LED pin as an output:
  pinMode(LEDB, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);  // initialize the LED pin as an output:
  digitalWrite(LEDR, HIGH);
  digitalWrite(LEDG, HIGH);
  digitalWrite(LEDB, HIGH);
 }

void setLedRGB(bool red, bool green, bool blue) {
   if (!red) { digitalWrite(LEDR, HIGH); } else { digitalWrite(LEDR, LOW); }
   if (!green) { digitalWrite(LEDG, HIGH); } else { digitalWrite(LEDG, LOW); }
    if (!blue) { digitalWrite(LEDB, HIGH); } else { digitalWrite(LEDB, LOW); }
}

Explanation

  • TIMER1 Setup:
    The timer is configured in 32‑bit mode with no prescaling so that it runs at 16 MHz.
    A compare value is set on CC[0] (3 ticks, ~187.5 ns) and a shortcut is enabled so the timer automatically clears on compare match.
  • GPIOTE Setup:
    GPIOTE channel 0 is set in Task mode with toggle polarity. When its task (TASKS_OUT[0]) is triggered, it toggles the state of LED_PIN.
  • PPI Setup:
    PPI channel 0 is configured to link the TIMER1 compare event (EVENTS_COMPARE[0]) to the GPIOTE task (TASKS_OUT[0]). This makes the LED toggle automatically on every compare event.
  • Reading the Timer:
    In the loop, the code triggers a capture task on TIMER1 channel 1, storing the current timer count in NRF_TIMER1->CC[1], which is then printed to Serial.

This example demonstrates direct register access for configuring a hardware timer, linking events via PPI, and using GPIOTE to perform an action (toggle an LED) without CPU intervention.
It compiles and runs on the Xiao Expansion brd, I used BSP2.9.2
You get this for serial output:

Power ON 
 
Xiao RGB LED Test compiled on Feb 20 2025 at 14:21:58

Processor came out of reset.

Testing R G B LED - RED, GREEN, BLUE... 
Starting TIMER1, PPI, and GPIOTE example with capture...
TIMER1 COUNTER: 152
TIMER1 COUNTER: 16157243
TIMER1 COUNTER: 32216876
TIMER1 COUNTER: 48337215
TIMER1 COUNTER: 64460401
TIMER1 COUNTER: 80588950
TIMER1 COUNTER: 96718288
TIMER1 COUNTER: 112850240
TIMER1 COUNTER: 128978373
TIMER1 COUNTER: 145105434
TIMER1 COUNTER: 161231900
TIMER1 COUNTER: 177361980
TIMER1 COUNTER: 193490580

The PPI channel 0 is set up to connect the TIMER1 compare event to the task that toggles a GPIO pin (here, the onboard LED). Each time TIMER1 reaches the compare value, the LED toggles. I print the timer’s counter value once per second. Although the timer is running very fast, this provides a way to confirm that it’s working.

The timers and counters for that matter are not well implemented IMO for Arduino. In the Nrf_sdk you get WAY more possibilities. The New chip Nrf54L15 has a DPPI and more channels for RTC as well :+1:

HTH
GL :slight_smile: PJ :v:

the nRF54L15 (as part of the nRF54 family) brings several enhancements that make timing and scheduling more flexible and efficient:

  • Enhanced Peripheral Interconnect (DPPI):
    Unlike the older PPI found in the nRF52840, the nRF54 series uses the Distributed Programmable Peripheral Interconnect (DPPI). This gives you many more channels and far more flexible routing between peripherals. You can link timer or RTC events to tasks (or even chain events between multiple peripherals) in ways that weren’t possible before.
  • More Timer/RTC Resources:
    The nRF54L15 typically provides additional timer instances or more compare channels than the nRF52840. This extra “real estate” means you can run more independent timing routines simultaneously, which can be particularly helpful if you need to schedule multiple low-power events or complex timing operations.
  • Dual-Core Architecture Benefits:
    The nRF54 series is built around a dual‑core design (with separate network and application cores). This allows you to dedicate timing resources to one core while the other handles higher‑level tasks. In practice, this can lead to lower latencies and better real‑time performance for things like wireless communication and scheduling.
  • Improved Power Efficiency and Accuracy:
    The RTC peripherals in the nRF54L15 are designed to be even more power‑efficient and to offer improved accuracy (less drift) compared to the nRF52840. This is especially important for battery‑powered devices that rely on precise timekeeping while spending most of their time in a low‑power sleep state.

These enhancements make it easier to build complex, low‑power, real‑time applications compared to the nRF52840.
:selfie:

Thanks for the example and the explantion. I tried it in the Arduino IDE and there are some errors, because LEDR and some other things are not defined.
So i changed it to Pin 7 of the Xiao BLE and monitored the pin with an logic analyzer. But there is no pin toggle on that pin.
Adding a println for the status of the timer tasks is showing that the timer seems not to start.
‘NRF_TIMER1->TASKS_START = 0;’

The Serial output is permant 0, saying the timer task does not start.

Besides setting up the nrf connect SDK with vscode is also “pain in the a…”.
I tried an example for the timer and even this prepared example does not compile.

At the moment my conclusion is that the Xiao BLE is an excellent hardware part but it is struggeling with proper software support.

I going to try another Xiao BLE board, just to get sure that my board is not the reason.

Hi there,

Well that is a surprise, It compiles fine and flashes perfectly, Runs as you see it.I’m using a Sense version, fyi. What BSP did you use?
Something else is wrong. (keyboard and the Floor) :sweat_smile:
Can you post the first 3 lines of the compiler output?

HTH
GL :slight_smile: PJ :v:

I found the Nrf_SDK to be the best Dev enviro if you want all the Bells and whistles. Takes a small learning curve and there are tons of free course on video, one for timers and CTC’s also :+1: The debugging is excellent as well.

The first lines of the compiler are:

In function 'void initdisplay()':
test3:79:11: error: 'LEDR' was not declared in this scope
   79 |   pinMode(LEDR, OUTPUT);  // initialize the LED pin as an output:
      |           ^~~~
test3:80:11: error: 'LEDG' was not declared in this scope
   80 |   pinMode(LEDG, OUTPUT);  // initialize the LED pin as an output:
      |           ^~~~
test3:81:11: error: 'LEDB' was not declared in this scope
   81 |   pinMode(LEDB, OUTPUT);
      |           ^~~~

The used lib in Arduino is the Seeed nRF52 Board in version 1.1.5.

Maybe it is the moon phase at my location… :wink:

I got it working, my platformio.ini was wrong. Simply was working on the wrong BSP.
Thanks for your assistance, now i can solve my application problems.