Interrupt scheduling issue of workqueue and threads when using Real-Time counter

Hello, I'm making a timekeeping library which uses the real time counter in the nRF9160. This works well. However, I want to update the time when a certain amount of time has passed. To do this I wanted to use the Real-Time Counters counter compare interrupt. My problem is that this interrupt does run, but trying to add work to a work queue or giving a semaphore to another thread does not result in the intended behavior. The thread or the work does not execute unless a k_sleep is introduced somewhere in the program, be that in the rtc handler or in an infinite loop in the main function.

So my question is the following: Why does the work or thread not get executed when there is no sleep command in either of the two places?

btw, I'm using Zephyr OS build v3.2.99-ncs1

I have placed a small program in the attachments which shows my problem. This code blinks an LED in the rtc handler, thus showing that the handler is called at the correct interval.

#include <nrf9160.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>

#include "date_time_SNTP.h"

LOG_MODULE_REGISTER(main);

// Setting this to 0 breaks the program
// 		- if ENABLE_K_SLEEP_IN_RTC_HANDLER is also 0, the work or thread is never called
// 		- if ENABLE_K_SLEEP_IN_RTC_HANDLER is 1, it works as expected (the work or thread is called once every 2 seconds)
#define USE_WHILE_LOOP_IN_MAIN 0

void main(void) {
	int err;
	err = date_time_sntp_init();
	if (err != 0){
		LOG_ERR("date_time_sntp_init failed, error code: %d", err);
		return;
	}
	#if USE_WHILE_LOOP_IN_MAIN
	while (1)
	{
		k_sleep(K_SECONDS(3));
	}
	#endif
}
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <nrfx_rtc.h>
#include "date_time_SNTP.h"

#include <nrfx_gpiote.h>
#define LED_0 2
#define LED_1 3

// Setting this to 0 breaks the program 
// 		- if USE_WHILE_LOOP_IN_MAIN is also 0 the work or thread is never called
// 		- if USE_WHILE_LOOP_IN_MAIN is 1 the work or thread is called but only at the frequency of the while loop 
//				The strange this is that the led is still blinking at the correct frequency, which means that the rtc handler is still being called
#define ENABLE_K_SLEEP_IN_RTC_HANDLER 1

// This setting doesn't make a difference in the outcome
#define USE_WORK_QUEUE 0 // else use thread

#define RTC_FREQ 512
#define COMPARE_COUNTERTIME_SEC 2
#define COMPARE_COUNTER_TICKS RTC_FREQ*COMPARE_COUNTERTIME_SEC

LOG_MODULE_REGISTER(rtc_test, LOG_LEVEL_INF);

const nrfx_rtc_t rtc = NRFX_RTC_INSTANCE(0);

static volatile uint32_t previous_counter_compare = COMPARE_COUNTER_TICKS;

int set_rtc_cc(){
	previous_counter_compare += COMPARE_COUNTER_TICKS;
	if(previous_counter_compare > NRF_RTC_COUNTER_MAX){ // overflow
		previous_counter_compare -= NRF_RTC_COUNTER_MAX;
	}
	int err = nrfx_rtc_cc_set(&rtc, 0, previous_counter_compare, true);
	if(err != NRFX_SUCCESS){
		LOG_ERR("error in cc set: %d", err);
		return err;
	}
	return 0;
}

#if USE_WORK_QUEUE
static void update_time_work_handler(struct k_work *work){
	LOG_INF("entering work handler");
	nrf_gpio_pin_toggle(LED_0);
}

K_WORK_DEFINE(update_time_work, update_time_work_handler);
#else
static K_SEM_DEFINE(time_update_pending, 0, 1);

static void update_time_thread_handler(void){
	while(1){
		LOG_INF("entering thread handler");
		k_sem_take(&time_update_pending, K_FOREVER);
		LOG_INF("sem time_update_pending taken");
		nrf_gpio_pin_toggle(LED_0);
	}
}

K_THREAD_DEFINE(date_time_thread, 1024, update_time_thread_handler, NULL, NULL, NULL, K_PRIO_COOP(7), 0, 0);
#endif // USE_WORK_QUEUE

static void rtc_handler(nrfx_rtc_int_type_t int_type){
 	if (int_type == NRFX_RTC_INT_COMPARE0) {
		set_rtc_cc();
		nrf_gpio_pin_toggle(LED_1);
		#if USE_WORK_QUEUE
		k_work_submit(&update_time_work);
		#else
		k_sem_give(&time_update_pending);
		#endif

		#if ENABLE_K_SLEEP_IN_RTC_HANDLER
		k_sleep(K_MSEC(1));
		#endif
	} 
}

static int rtc_config(void){
	nrfx_err_t err_code;
	LOG_INF("entering rtc_config");

	nrfx_rtc_config_t config = NRFX_RTC_DEFAULT_CONFIG;
	LOG_INF("prescaler: %u, interupt_priority: %u, tick_latency: %u, reliable: %s", config.prescaler, config.interrupt_priority, config.tick_latency, config.reliable ? "true" : "false");
	k_sleep(K_MSEC(1));
	config.prescaler = RTC_FREQ_TO_PRESCALER(RTC_FREQ);
	err_code = nrfx_rtc_init(&rtc, &config, rtc_handler);
	if (err_code != NRFX_SUCCESS) {
		if (err_code == NRFX_ERROR_INVALID_STATE){
			return -ENOTSUP;
		}
		return -ENOMSG;
	}
	
	nrfx_rtc_counter_clear(&rtc);
	err_code = nrfx_rtc_cc_set(&rtc, 0, COMPARE_COUNTER_TICKS, true);
	if (err_code != NRFX_SUCCESS) {
		if (err_code == NRFX_ERROR_TIMEOUT){
			return -ETIMEDOUT;
		}
		return -ENOMSG;
	}
	nrfx_rtc_enable(&rtc);
	return 0;
}

static void manual_isr_setup(){
	IRQ_DIRECT_CONNECT(RTC0_IRQn, 0, nrfx_rtc_0_irq_handler, 0);
	irq_enable(RTC0_IRQn);
}

int date_time_sntp_init(){
	nrf_gpio_cfg_output(LED_0);
	nrf_gpio_cfg_output(LED_1);

	int ret = rtc_config();
	if (ret != 0){
		LOG_ERR("rtc_config failed");
		return ret;
	}

	manual_isr_setup();
	return 0;
}
date_time_SNTP.h7343.prj.conf
#
# Copyright (c) 2020 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(real_time_counter_test)

# NORDIC SDK APP START
FILE(GLOB app_sources 
	main.c
	date_time_SNTP.c
	)
target_sources(app PRIVATE ${app_sources})
# NORDIC SDK APP END

Parents Reply Children
  • Yes, thanks
    I actually don't need the low latency of the direct irq, so I changed it to this:

    static void manual_isr_setup(){
     IRQ_CONNECT(RTC0_IRQn, 0, nrfx_rtc_0_irq_handler, NULL, 0);
     irq_enable(RTC0_IRQn);
    }

    It works perfectly now!

    If someone looks at this later and does need low latency, the following also worked:

    ISR_DIRECT_DECLARE(rtc_isr_handler){
     nrfx_rtc_0_irq_handler();
     ISR_DIRECT_PM();
     return 1;
    }
    
    static void manual_isr_setup(){
     IRQ_DIRECT_CONNECT(RTC0_IRQn, 0, rtc_isr_handler, 0);
     irq_enable(RTC0_IRQn);
    }
Related