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 
HTH
GL
PJ 
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.
