Time is hard

Apr 30th 2018

Dealing with time and timing on a microcontroller seems trivial from first glance but it is very hard. 

We will start with the delay() function in Arduino library for the Arudino Zero (SAMD21)

void delay( uint32_t ms )
{
	if ( ms == 0 )
	{
		return ;
	}
  	uint32_t start = _ulTickCount ;
	do
	{
		yield() ;
	} while ( _ulTickCount - start < ms ) ;
}

Where _ulTickCount is updated in the systick interrupt handler.    This seems reasonable but I ran into a problem when I tired to call the delay() function with interrupts disabled.  With the interrupts disabled or calling delay() from inside an ISR (something you should avoid) it will lock up the code and spin forever. 

So out of curiosity I loaded up the Atmel ASF FreeRTOS example project to see how it handles delays. 

/**
 * \brief Delay loop to delay n number of cycles
 * Delay program execution for at least the specified number of CPU cycles.
 *
 * \param n  Number of cycles to delay
 */
static inline void delay_cycles(
		const uint32_t n)
{
	if (n > 0) {
		SysTick->LOAD = n;
		SysTick->VAL = 0;


		while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)) {
		};
	}
}
<br>

It is not obvious at first but there is a major bug waiting to bite someone in a most unpleasant way.  Specifically the FreeRTOS  tick function uses the SysTick timer.  Hence the first time you call an ASF delay function your RTOS will go off the rails and start having unpredictable results as that the SysTick timing will be changed. 

So some people will say of course you should should use a nop spin loop for the delay function and that will solve all your problems. For example from Arduino

/**
 * \brief Pauses the program for the amount of time (in microseconds) specified as parameter.
 *
 * \param dwUs the number of microseconds to pause (uint32_t)
 */
static __inline__ void delayMicroseconds( uint32_t ) __attribute__((always_inline, unused)) ;
static __inline__ void delayMicroseconds( uint32_t usec )
{
  if ( usec == 0 )
  {
    return ;
  }


  /*
   *  The following loop:
   *
   *    for (; ul; ul--) {
   *      __asm__ volatile("");
   *    }
   *
   *  produce the following assembly code:
   *
   *    loop:
   *      subs r3, #1        // 1 Core cycle
   *      bne.n loop         // 1 Core cycle + 1 if branch is taken
   */


  // VARIANT_MCK / 1000000 == cycles needed to delay 1uS
  //                     3 == cycles used in a loop
  uint32_t n = usec * (VARIANT_MCK / 1000000) / 3;
  __asm__ __volatile__(
    "1:              \n"
    "   sub %0, #1   \n" // substract 1 from %0 (n)
    "   bne 1b       \n" // if result is not 0 jump to 1
    : "+r" (n)           // '%0' is n variable with RW constraints
    :                    // no input
    :                    // no clobber
  );
  // <a href="https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html">https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.h...</a>
  // <a href="https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Volatile">https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.h...</a>
}


<br>

So the spin loops are great, and using them ensures that your code will not stall forever. However if you get an interrupt during the spin loop the time in the interrupt handler is not counted. Or if you are using an RTOS a task could get the processor and then the time in a different tasks is not counted. 

Note you should never use a spin loop with RTOS as there are better OS based delays where other tasks can use processor for that time. 

So how can you handle delays? Well if you are in a task with RTOS use the RTOS delay of course. How about for low level drivers which may not have RTOS up and running? 

Well what I did to work around Arudino's problem was make my own delay. 

//the Arduino delay function requires interrupts to work.
// if interrupts are disabled use the delayMicroseconds which is a spin loop
static inline void DelayMs(uint32_t ms)
{
	uint32_t prim;
	/* Read PRIMASK register, check interrupt status before you disable them */
    /* Returns 0 if they are enabled, or non-zero if disabled */
    prim = __get_PRIMASK();


	if (prim==0)
	{
		delay(ms);
	}else
	{
		while(ms)
		{
			delayMicroseconds(1000);
			ms--;
		}
	}
}
 

This code determines if global interrupts are enabled or not, if so it uses a cycle spin loop, else it uses the Arduino delay based on the Systick.  Of course there are situation like systick interrupt turned off or delay in higher priority interrupt service routine where this delay will not work.  Again time is hard. 

Possibly the best way to handle delays is to dedicate a timer to handle delays, assuming you have enough timers on your chip. 

In the next blog post, http://misfittech.net/blog/time-is-hard-part-2/

 I will continue discussing why time is hard...