[SAMD21J18A][FreeRTOS][TICKLESS mode] TC (Timer/Counter) sync issue

Go To Last Post
2 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

I am trying to use freeRTOS with the Tickless mode, where basically before the sleep I reconfigure the TC counter to have a longer tick time and wakeup every few seconds or so, and then immediately activate if a UART interrupt happens. My problem is that when reading the TC counter register, blocks the whole MCU for like 180us. I elaborate below.

TL;DR
With a CPU running at 48MHz from the FLL and a low power TC running at 32kHz I use to read its value after a wakeup, I get a delay of unwanted 170us of CPU stall (with also other ISR not goung in the background). It does not change using a sync busy wait before access the TC (reading the flag tc_module->STATUS.reg). Is it due to synchronization between 2 synchronous clock domains? the 48MHz is generated from the same 32kHz.

 

 

Long story:

This is the code at the wakeup, which happens after the "DSB" and "WFI" instructions:
 

cpu_irq_enable();
system_sleep();

// ****************************** THE FIRST OR SECOND ISR BYTEs FROM UART ARE PROBABLY EXECUTED AT THIS POINT

/* We just woke up.
How long did we sleep for?
*/

	uint32_t sleep_count = tc_get_count_value(&tc);
	uint32_t notify_ui = sleep_count;
	/* Pause the system tick timer while we reconfigure it */
        tc_stop_counter(&tc); 

        /* A counter overflow means that we slept for at least the expected time, so we can go ahead
           and adjust the RTOS tick count by that amount right now. */
        if (tc.hw->COUNT32.INTFLAG.bit.OVF){
                vTaskStepTick(xExpectedIdleTime);
	        notify_ui += xExpectedIdleTime*TIMER_COUNTS_ONE_TICK;
	}

	vPortExitingSleepMode(notify_ui, system_gclk_gen_get_hz(LOW_POWER_TICKLESS_CLOCK)); 

            /* We must also adjust for time left over (if any) */
            uint32_t cur_count = (last_count + sleep_count);
            vTaskStepTick(cur_count / TIMER_COUNTS_ONE_TICK);

            /* Resume the system tick, starting at approximately the remainder of time until the next tick (that is, what
               we could not account for above) */
            tc_set_count_value(&tc, cur_count % TIMER_COUNTS_ONE_TICK);
            resume_system_tick();

Where in resume_system_tick(), which is not from ASF, I have:

static inline void resume_system_tick(void)
{
    /* Disconnect callback */
    tc_disable_callback(&tc, TC_CALLBACK_CC_CHANNEL0);
    tc_unregister_callback(&tc, TC_CALLBACK_CC_CHANNEL0);

    /* Connect the FreeRTOS system tick callback */
    tc_register_callback(&tc, (tc_callback_t)xPortSysTickHandler,TC_CALLBACK_CC_CHANNEL0);
    tc_enable_callback(&tc, TC_CALLBACK_CC_CHANNEL0);

    /* Resume system tick, the starting count is taken from whatever
       is in the counter right now.  This lets us literally resume
       or otherwise pre-load the counter. */
    tc_set_top_value(&tc, TIMER_COUNTS_ONE_TICK);// ? TIMER_RELOAD_VALUE_ONE_TICK);
    tc_start_counter(&tc);
}

Which is like this example: https://community.atmel.com/forum/samd21-tc-sync-fixes-short-sleep where basically at the wakeup reads the TC value, reconfigure it with a different delay and reassign the callbacks, and continues. The OP of that post has an issue of a delay due to the synchronization, which is different, but uses the same approach as me.

In the wakeup, if I mask up the read instruction inside the tc_get_count_value(&tc), all is ok, the critical sections that happens afterwards in the RTOS are all a fraction of a UART byte reception (we are at 115.2kbps) and nothing gets skipped. But if I let the counter being read (and does not change with, without synchronization or stopping the counter, this happens: (where the duration is higlighted by setting a pin before and reset to 0 after such instruction)

 

The second signal is a pin toggled around  the uint32_t sleep_count = tc_get_count_value(&tc); and clearly skips the ISRs, but inside the function there are only these instructions, where the 32 bit gets executed:

uint32_t tc_get_count_value(
		const struct tc_module *const module_inst)
{
	/* Sanity check arguments */
	Assert(module_inst);
	Assert(module_inst->hw);

	/* Get a pointer to the module's hardware instance */
	Tc *const tc_module = module_inst->hw;

	while (tc_is_syncing(module_inst)) {
		/* Wait for sync */
	}

	/* Read from based on the TC counter size */
	switch (module_inst->counter_size) {
		case TC_COUNTER_SIZE_8BIT:
			return (uint32_t)tc_module->COUNT8.COUNT.reg;

		case TC_COUNTER_SIZE_16BIT:
			return (uint32_t)tc_module->COUNT16.COUNT.reg;

		case TC_COUNTER_SIZE_32BIT:
			return tc_module->COUNT32.COUNT.reg;  // ************************************************************* THIS ONE EXECUTES and STUCK EVERYTHING fo
	}

	Assert(false);
	return 0;
}

The sync part does not change anything, is really the tc_module->COUNT32.COUNT.reg access to generate a blocked situation of 180us..

The clock is organized to use the DFLL from the extern low power 32,768 kHz, then multiply up to 48 MHz. The TC is clocked from the same crystal source directly, but not multiplied. What you see is when the DFLL is configured to run also in sleep (I use the mode 2 sleep). I would have said the DFLL clock needed time to startup before getting the access to the slower TC, plus the sync time itself, but if is supposed to run, it should not wait that much. I think there is something fundamentally wrong on how I use the TC.

Feel free to ask more if something is not clear. Also, is even more mysterious how the ISR runs 2 times with no delay due to a previous IRQ pending (see image), but STILL gets the right data... it should skip the bytes from my understanding.

Thanks,

Last Edited: Sat. Aug 22, 2020 - 10:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

After some trials, in the end was the slow 32kHz clock which made the TC module to synchronize with the faster CPU. With faster clock this does not happen. Although, I am not sure why the rest of the system was not working, any clue on what happens during the sync mechanism in the APB bus? Does the bus go to a kind of wait mode?