NRF5340 - PRE KERNEL1 timer

Hi,

I'm trying to implement a PRE_KERNEL1 timer (with top priority 0) to toggle an LED.

I’m able to control the LED using registers, and now I want to add a timer layer.

I've tried using both registers and NRFX for this task. Both solutions compiled and flashed successfully, but they don’t seem to do anything. :(

In the NRFX solution, it looks like the program gets stuck during timer initialization (code below).

What do you think might be the issue?

// Base addresses - NRF5340
#define TIMER1_BASE 0x50010000UL
#define CLOCK_BASE 0x50005000UL
#define OSC_BASE 0x50004000UL
#define NRF_P0_BASE 0x50842500UL

// LED definitions
#define NRF_GPIO_DIRSET (*(volatile uint32_t *)(NRF_P0_BASE + 0x018))
#define NRF_GPIO_DIRCLR (*(volatile uint32_t *)(NRF_P0_BASE + 0x01C))
#define NRF_GPIO_OUTSET (*(volatile uint32_t *)(NRF_P0_BASE + 0x008))
#define NRF_GPIO_OUTCLR (*(volatile uint32_t *)(NRF_P0_BASE + 0x00C))
#define NRF_GPIO_IN (*(volatile uint32_t *)(NRF_P0_BASE + 0x010))
#define NRF_GPIO_PIN_CNF(PIN) (*(volatile uint32_t *)(NRF_P0_BASE + 0x700 + PIN * 4))
#define PIN_LED 3 // P0.03

// Timer registers (offsets from TIMER1_BASE)
#define TIMER_TASKS_START (*(volatile uint32_t *)(TIMER1_BASE + 0x000))
#define TIMER_TASKS_STOP (*(volatile uint32_t *)(TIMER1_BASE + 0x004))
#define TIMER_TASKS_CLEAR (*(volatile uint32_t *)(TIMER1_BASE + 0x008))
#define TIMER_TASKS_CAPTURE0 (*(volatile uint32_t *)(TIMER1_BASE + 0x018))
#define TIMER_TASKS_CAPTURE1 (*(volatile uint32_t *)(TIMER1_BASE + 0x01C))

#define TIMER_EVENTS_COMPARE0 (*(volatile uint32_t *)(TIMER1_BASE + 0x140))
#define TIMER_EVENTS_COMPARE1 (*(volatile uint32_t *)(TIMER1_BASE + 0x144))

#define TIMER_SHORTS (*(volatile uint32_t *)(TIMER1_BASE + 0x200))
#define TIMER_INTENSET (*(volatile uint32_t *)(TIMER1_BASE + 0x304))
#define TIMER_INTENCLR (*(volatile uint32_t *)(TIMER1_BASE + 0x308))
#define TIMER_MODE (*(volatile uint32_t *)(TIMER1_BASE + 0x504))
#define TIMER_BITMODE (*(volatile uint32_t *)(TIMER1_BASE + 0x508))
#define TIMER_PRESCALER (*(volatile uint32_t *)(TIMER1_BASE + 0x510))
#define TIMER_CC0 (*(volatile uint32_t *)(TIMER1_BASE + 0x514))
#define TIMER_CC1 (*(volatile uint32_t *)(TIMER1_BASE + 0x518))

// Clock registers (offsets from CLOCK_BASE)
#define CLOCK_TASKS_HFCLKSTART (*(volatile uint32_t *)(CLOCK_BASE + 0x000))
#define CLOCK_HFCLKSTAT (*(volatile uint32_t *)(CLOCK_BASE + 0x40C))

// NVIC_BASE comes from <core_cm33.h>
#define NVIC_ISER0 (*(volatile uint32_t *)(NVIC_BASE + 0x000)) // Interrupt Set Enable Register 0
#define NVIC_ICER0 (*(volatile uint32_t *)(NVIC_BASE + 0x080)) // Interrupt Clear Enable Register 0
#define NVIC_IPR0 (*(volatile uint8_t *)(NVIC_BASE + 0x400))   // Interrupt Priority Register 0 (byte access)
#define SCB_SCR (*(volatile uint32_t *)(SCB_BASE + 0x010)) // System Control Register


//////// Register test /////// - please run just one at the time

// Interrupt handler for TIMER1
void TIMER1_IRQHandler(void)
{
    static bool led_status = false;
    NRF_GPIO_OUTSET = (1 << PIN_LED);
    // Check if COMPARE0 event caused the interrupt
    if (TIMER_EVENTS_COMPARE0)
    {
        // Clear the event
        TIMER_EVENTS_COMPARE0 = 0;

        // Toggle the LED
        led_status = !led_status;
        if (led_status)
        {
            NRF_GPIO_OUTSET = (1 << PIN_LED);
        }
        else
        {
            NRF_GPIO_OUTCLR = (1 << PIN_LED);
        }
    }
}

void timer_init()
{
    // Start high-frequency clock (if not already running)
    CLOCK_TASKS_HFCLKSTART = 1;
    while (!(CLOCK_HFCLKSTAT & 1))
        ; // Wait for HFCLK to start

    // NRF_GPIO_OUTSET = (1 << PIN_LED); // for testing, can be removed
    // Configure Timer
    TIMER_TASKS_STOP = 1;  // Ensure timer is stopped before configuring
    TIMER_TASKS_CLEAR = 1; // Clear timer

    TIMER_MODE = 0x00;      // Timer mode
    TIMER_BITMODE = 0x02;   // 32-bit timer
    TIMER_PRESCALER = 0x04; // Prescaler.  32MHz / (2^4) = 2MHz timer clock.  Each tick is 0.5 us.
    TIMER_CC0 = 2000000;    // Compare value for 1 second (2MHz * 1 second = 2000000)

    // Enable shortcut to clear timer on COMPARE0 event
    TIMER_SHORTS = (1 << 0);

    // Enable interrupt on COMPARE0 event
    TIMER_INTENSET = (1 << 16);

    NVIC_IPR0 |= (0x40 << (17 % 8) * 8); // Set priority to 0x40 (adjust as needed)

    // Enable TIMER1 interrupt in NVIC
    NVIC_ISER0 = (1 << 17); // Enable interrupt number 17 (TIMER1)
    TIMER_TASKS_START = 1;  // Start timer
}



/////// NRFX Test //////// - please run just one at the time
#define TIMER_INSTANCE_ID 1
static const nrfx_timer_t TIMER_INSTANCE = NRFX_TIMER_INSTANCE(TIMER_INSTANCE_ID);

#define TIMER_IRQ_NUM TIMER1_IRQn     // IRQ number for TIMER1
#define TIMER_CC_VALUE (16000000 * 5) // 5 seconds (16 MHz clock)



void timer_event_handler(nrf_timer_event_t event_type, void *context)
{
    static bool led_status = false;

    led_status = !led_status;
    if (led_status)
    {
        NRF_GPIO_OUTSET = (1 << PIN_LED);
    }
    else
    {
        NRF_GPIO_OUTCLR = (1 << PIN_LED);
    }

    // Reset the timer
    nrfx_timer_clear(&TIMER_INSTANCE);
}



void timer_init()
{
    // Timer configuration structure
    nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG(NRF_TIMER_FREQ_16MHz);
    timer_cfg.frequency = NRF_TIMER_FREQ_16MHz;
    timer_cfg.mode = NRF_TIMER_MODE_TIMER;
    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
    timer_cfg.interrupt_priority = 1;

    // Initialize Timer
    nrfx_timer_init(&TIMER_INSTANCE, &timer_cfg, timer_event_handler);
    
	// The next 2 lines are led testing - if we reach this point.
	// It looks like we stuck here, because the led is not turned on,
	// but it doest turn on if i place it before 'nrfx_timer_init' line 
	
	NRF_GPIO_OUTSET = (1UL << PIN_LED); // for testing, should be removed
    return; // for testing, should be removed



    // Set CC0 for 5-second interval
    nrfx_timer_extended_compare(&TIMER_INSTANCE,
                                NRF_TIMER_CC_CHANNEL0,
                                TIMER_CC_VALUE,
                                NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                true);

    // Enable Timer Interrupt
    NVIC_ClearPendingIRQ(TIMER_IRQ_NUM);
    NVIC_EnableIRQ(TIMER_IRQ_NUM);

    // Start Timer
    nrfx_timer_enable(&TIMER_INSTANCE);
}





///// Register function to run before kernel 1 - MCUBOOT ///// - This should run all the time
static int gpio_early_read_write(void)
{

    // Set LED gpio as output
    NRF_GPIO_DIRSET = (1UL << PIN_LED);
    NRF_GPIO_OUTCLR = (1UL << PIN_LED);
    
	// LED Test to see if the system powered
	NRF_GPIO_OUTSET = (1UL << PIN_LED);
    for (volatile int i = 0; i < 1000000; i++){};
    NRF_GPIO_OUTCLR = (1UL << PIN_LED);

    timer_init();



	// Wait for timer
    for (;;){};

    return 0;
}


SYS_INIT(gpio_early_read_write, PRE_KERNEL_1, 0);

Parents Reply Children
  • Hi and thanks!

    i got you about magic numbers, absolutely should be taken from device tree.

    anyways, the led gpio actually works, i can make it turn on and off.

    I want to toggle it from timer isr, but i couldnt make it work, i configure the timer but nothing happens.

    the thing is that i want it to work pure hardware, before the OS starts.

    maybe i missed some basic stuff, thanks for your reply!

  • You might also have wrong magic numbers in your own register definitions. Checking would trigger my hourly rates - I am NOT a nordic employee.

    Use the compiler macros from the NRF connect SDK. NRF_P0 should be available once you

    #include <zephyr/kernel.h>

    EDIT: I am a bit blind. Your interrupt handler function TIMER1_IRQHandler is not connected to the interrupt stuff yet. You would need the IRQ_CONNECT macro in order to do that.

    Try putting a debug break point into that handler function in order to check.

    You should have gotten an unhandled interrupt exception unless the timer1 is enabled in the dts - which means it has an interrupt handler function already.

  • Hi,

    The registers took from nrf5340 datasheet, and the gpio works, so it should be fine.

    I saw there is a macro to register "TIMER1_IRQHandler" as a week function, so it looks like it just need to be implemented, anyways, with nrfx- i register "timer_event_handler" to this timer.

    and i tried to use "IRQ_CONNECT" and enabled macro but nothing.

    at the end i managed to run something with nrfx but just after pre_kernel1, which is too late so i need to use the bare metal registers to run it before any kernel or OS.

    for some reason, the mcuboot takes ~1sec to boot Disappointed

  • It would have been a good idea to mention mcuboot in your first post...

    PRE_KERNEL means running after MCUBOOT, which is its own little project that runs before everything.

    If you wanted to do something at mcuboot time, you needed to modify this project. Not recommended unless you know what you are doing - it took me a while to get my custom MCUBOOT project running properly with an application (I wanted SD card support for the bootloader).

Related