This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

app_timer on RTC1 interferes with timing on RTC2

Hi,

            I am running nrf52840 on a custom board, SDK 15.0.0, softdevice S140 6.0.0 and SES.

In my application I am reading an IMU ( ICM20602) and a laser ranger/Time of Flight chip (VL53l0x). The IMU is read every 10 ms in a handler triggered by an apptimer. The TOF chip is read in a loop every 20ms controlled by RTC2. As soon as the app_timer is started I get high jitter on the ticks from RTC2. I am measuring this by toggling a GPIO pin and measuring with a CRO.

My RTC code is:

#include "nrfx_rtc.h"
#include "nrfx_clock.h"
#include "nrf_drv_rtc.h"
#include "nrf_drv_clock.h"
#include "boards.h"
#include "pca10056.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "app_timer.h"

#include "VL53L0X.h"
#include "rtc.h"

static uint32_t last_msg = 0;
static bool is_uninit = true;


/** @file rtc.c
**  @brief real time counter code
**  @details real time counter (rtc) provides a tick for timouts and also an interrupt to wake
**           from sleep
**
**  @defgroup RealTimeCounter Main_Clock_API
**  @{
*/

const nrfx_rtc_t rtc = NRFX_RTC_INSTANCE(2); /**< Declaring an instance of nrfx_rtc for RTC2. */
bool overflow_toggle = false;

/** @brief interrupt handler for rtc
**  @details two types of interrupt - overflow and wake up
**  @param[in] int_type type of interrupt 
*/              
static void rtc_handler(nrfx_rtc_int_type_t int_type)
{
GPIO_debug_toggle();
    if (int_type == NRFX_RTC_INT_OVERFLOW)
    {
        if (overflow_toggle)
           overflow_toggle = false;
        else 
           overflow_toggle = true;
    }
  //  NRF_LOG_DEBUG("RTC INT: 0x%x", int_type);
}

/** @brief nothing handled but required by the sdk
*/
static void clk_handler(nrfx_clock_evt_type_t evt)
{
NRF_LOG_INFO("CLOCK HANDLER");
NRF_LOG_FLUSH();
}

/** @brief Function configuring gpio for pin toggling.
 */
static void leds_config(void)
{
    bsp_board_init(BSP_INIT_LEDS);
}

/** @brief Function starting the internal LFCLK XTAL oscillator.
 */
static void lfclk_config(void)
{
  // always started because we are running a softdevice which starts it
  //  ret_code_t err_code = nrfx_clock_init(clk_handler);
  //  APP_ERROR_CHECK(err_code);
 //   nrfx_clock_lfclk_start();
}

/** @brief Function initialization and configuration of RTC driver instance for ticks.
 */
void rtc_config_tick(void)
{
    uint32_t err_code;

    if (!is_uninit) {
       nrfx_rtc_disable(&rtc);
       nrfx_rtc_uninit(&rtc);
    } 

    // get the crystal buzzing
    /// The clock is also used by the softdevice. Depending on what else is started we may or may not have to start it
    if (!nrfx_clock_lfclk_is_running())
       lfclk_config();

    //Initialize RTC instance
    nrfx_rtc_config_t config = NRFX_RTC_DEFAULT_CONFIG;
    config.prescaler = RTC_FREQ_TO_PRESCALER(RTC_FREQ_TICK);
    err_code = nrfx_rtc_init(&rtc, &config, rtc_handler);
    APP_ERROR_CHECK(err_code);
    is_uninit = false;

    //Enable tick event & interrupt
    nrfx_rtc_tick_enable(&rtc,true);

    // enable over flow event
    nrfx_rtc_overflow_enable(&rtc, true); 
	
    //set the counter to 0
    nrfx_rtc_counter_clear(&rtc);

    //Power on RTC instance
    nrfx_rtc_enable(&rtc);
}

/** @brief configure the RTC for wakeup duties
*
*/
void rtc_config_wake(void)
{
    uint32_t err_code;

    if (!is_uninit) {
       nrfx_rtc_disable(&rtc);
       nrfx_rtc_uninit(&rtc);
    }

    // get the crystal buzzing
    /// The clock is also used by the softdevice. Depending on what else is started we may or may not have to start it
    if (!nrfx_clock_lfclk_is_running())
       lfclk_config();

    //Initialize RTC instance
    nrfx_rtc_config_t config = NRFX_RTC_DEFAULT_CONFIG;
    config.prescaler = RTC_FREQ_TO_PRESCALER(RTC_FREQ_WAKE);
    err_code = nrfx_rtc_init(&rtc, &config, rtc_handler);
    APP_ERROR_CHECK(err_code);
    is_uninit = false;

    //set the counter to 0
    nrfx_rtc_counter_clear(&rtc);

    //Power on RTC instance
    nrfx_rtc_enable(&rtc);
}

/**
** @brief setup counter to allow check for timeout
** @param[in] timeout in ms that we will measure against
** @param[out] mgt remember our target ticks and state of the overflow toggle
**
*/
void startTimeout(rtc_mgt_t *mgt, uint32_t timeout) {

  uint32_t ticks;

  if (is_uninit)
     rtc_config_tick();

  // set target ticks
  if (timeout != 0)
     ticks = timeout; // 1 ms pr tick
  else ticks = 0xfffff; // large number less than max ticks
  mgt->tgt_ticks = nrfx_rtc_counter_get(&rtc) + ticks;
  mgt->overflow_flag = overflow_toggle;
}

/**
** @brief check whether a timeout has expired
** @param[in] mgt containing our target and the start overflow state
** returns true if timed out, false if not
**
*/
bool checkTimeoutExpired(rtc_mgt_t *mgt) {
  if (overflow_toggle != mgt->overflow_flag) {
     // have overflowed so say we have timed out
     return true;
  } else if (nrfx_rtc_counter_get(&rtc) >= mgt->tgt_ticks ) {
     // timed out
     return true;
  } else return false;
}

/** @brief set an interrupt to wake us up
**  @param[in] sleep_ms the time to sleep in millisecs
*/
bool set_wakeup(uint32_t sleep_ms) {
uint32_t err_code;

    rtc_config_wake();

    err_code = nrfx_rtc_cc_set(&rtc,  0, NRFX_RTC_US_TO_TICKS(sleep_ms * 1000, RTC_FREQ_WAKE),true);
    if (err_code == NRFX_ERROR_TIMEOUT) {
       return false;
    } else APP_ERROR_CHECK(err_code);
    return true;
}

/** @brief returns the current tick
*** @return current ticks
*/
uint32_t rtc_get_tick(void) {
    return RTC_TICKS_TO_MS(nrfx_rtc_counter_get(&rtc), RTC_FREQ_TICK);
}

/** @brief set the last msg counter to current tick
*/
void rtc_msg_rcd(void) {
   last_msg = nrfx_rtc_counter_get(&rtc);
}

/**
** @brief check whether a timeout has expired due to no message
** @param[in] mgt containing our target and the start overflow state
** @param[in] max_secs is the timeout period
** returns true if timed out, false if not
**
*/
bool checkLastMsg(rtc_mgt_t *mgt, uint32_t max_secs) {

  if (overflow_toggle != mgt->overflow_flag) {
     // have overflowed so say we have timed out
    return true;
  } else if (nrfx_rtc_counter_get(&rtc)  > 
              (last_msg + max_secs * 1000)) {
     // timed out
     return true;
  } else return false;
}

/** @} */

matching header - apart from prototypes

#define RTC_FREQ_TICK 1000     /// frequency in hz for ticks ie 1000 ticks per sec or one per millisec
#define RTC_FREQ_WAKE 6400     /// frequency in hz gives us a resolution to handle millisecs 20ms is 128 ticks

/** @struct rtc_mgt_t
**  @brief management structure for timeouts.
**  @details gathers the information in one place. Ticks we are going for and state of the overflow flag
*/
typedef struct {
    uint32_t tgt_ticks;
    bool overflow_flag;
} rtc_mgt_t;

/**@brief Macro to convert ticks to millisects. */
#define RTC_TICKS_TO_MS(ticks,freq) (((ticks) * 1000) / (freq))

the code that starts the app_timer:

/** @brief init the mpu and the madgwick library, init internal variables
*/
void Mems_Init(void) {
ret_code_t err_code;
NRF_LOG_INFO("mems init");
	mpu9250_init(&mpu9250, &mpu9250_inst, &mpu9250_conf, NULL);
        TM_AHRSIMU_Init(&AHRSIMU, AHRS_BETA, AHRS_SAMPLE_RATE, 0);

	for (int i = 0; i < 3; i++)
	{
		acc[i] = 0.0f;
		gyr[i] = 0.0f;
	}
	temp = 0.0f;
        Mems_getCal();
 
        // start timer for mems stuff
        if (mems_timer_state == DEFINED) {
            err_code = app_timer_create(&m_mems_timer_id,
                                APP_TIMER_MODE_REPEATED,
                                Mems_Iteration);
            APP_ERROR_CHECK(err_code);
            mems_timer_state = STOPPED;
        }
      
        if (mems_timer_state == STOPPED) {
           err_code = app_timer_start(m_mems_timer_id, APP_TIMER_TICKS(10), NULL);
           APP_ERROR_CHECK(err_code);
           mems_timer_state = STARTED;
        }
}

Any help would be gratefully received

Cheers Paul

Parents
  • If you're toggling a gpio from a CPU interrupt's callback function then you are at the mercy of the CPU load. 

    I suggest you connect the RTC2 to a pin via the PPI — Programmable peripheral interconnect and GPIOTE — GPIO tasks and events peripherals, then you can measure the jitter of RTC2 when the CPU is taken out of the equation. 

    How do you interface with your range finder/TOF devices, what protocol?
    There might be some tricks you can do with our serial peripherals, PPI, and EasyDMA, in order to read data from your devices without involving the CPU. 



  • Hi Haakonsh,

                          the TOF chips - reading  one at a time ie just the one for many cycles - are on i2c and the IMU is on SPI. The jitter is introduced even without the TOF chips being read. State machine looks like:

    disconnected -> standby -> sample

    The tof chips are initialised but not ranging coming into standby. They start ranging in the sample state. The app_timer starts in disconnected state and starts calling the IMU to return values. So I see the jitter start in disconnected state well before the TOF chips are even started.

    I think you are right though, reading the IMU it taking enough cpu time to skew everything else. I will have a look at ppi and easydma.

    Cheers paul.

Reply
  • Hi Haakonsh,

                          the TOF chips - reading  one at a time ie just the one for many cycles - are on i2c and the IMU is on SPI. The jitter is introduced even without the TOF chips being read. State machine looks like:

    disconnected -> standby -> sample

    The tof chips are initialised but not ranging coming into standby. They start ranging in the sample state. The app_timer starts in disconnected state and starts calling the IMU to return values. So I see the jitter start in disconnected state well before the TOF chips are even started.

    I think you are right though, reading the IMU it taking enough cpu time to skew everything else. I will have a look at ppi and easydma.

    Cheers paul.

Children
Related