makersguide_ch5_2.png

 

SysTick Interrupt Handler

The easiest interrupt source to use in an ARM-based MCU is the System Timer, or SysTick.  The free-running System Timer built into the ARM core can be enabled and configured to generate a periodic SysTick Interrupt whenever the associated countdown register reaches zero.  What’s nice about this interrupt source is that you can configure it with a single call provided by the core libraries, so no #include files are needed at all, and you can implement the SysTick interrupt handler function anywhere in your code, and the compiler will find your function and execute it whenever the SysTick Interrupt occurs.

 

Although this interrupt source is easily configured, the down side to the SysTick is that it doesn’t have a lot of configurable options beyond the countdown value, and since it is tied directly to the core frequency of the MCU, it further limits its flexibility.

 

Here is how we configure the SysTick interrupt so that it is enabled and running at 1ms interrupts:

 

SysTick_Config(CMU_ClockFreqGet(cmuClock_CORE) / 1000));

 

The SysTick_Config function automatically starts the System Timer, sets the countdown value as its only input, and enables the SysTick interrupt.  By dividing the core frequency by 1,000, we are saying that we want each SysTick to occur every millisecond.  For example, if the clock is running at 32MHz, the countdown value is 320,000.  With that clock speed, that number of counts occur every 1ms.  If the clock is running at 14MHz, the countdown value is 140,000, which also happens every 1ms.   

 

Beware of setting this countdown value divider too low, i.e. values under 100 which would result in interrupts every 100 microseconds or less.  When the MCU executes the interrupt handler lookup and then enters the SysTick interrupt handler, it uses about 12 core clock cycles.  You need to make sure that you have enough time to enter the SysTick interrupt handler, execute whatever instructions are in the handler, and then exit the SysTick well before the next SysTick interrupt occurs.  Otherwise, the rest of your program won’t run, since the only thing the MCU will be doing is executing the SysTick interrupt handler over and over.

 

If we don’t create a function to handle the SysTick interrupt when it occurs, the MCU will generate a Hard Fault, which will halt execution of the program.  So we must create the interrupt handler function to be called to handle the timer-based event:

void SysTick_Handler(void)
{
  // Your code goes here
}

 

This interrupt handler code can be placed in any of your included files.   The core libraries already have a prototype of this function name, so the compiler will be looking for this function’s implementation when the SysTick interrupt occurs.

 

So now we have a function that gets called every millisecond, which could be a useful thing to perform some periodic instructions.  Beware that the timing will change if the system clock frequency changes, which you may want to do at some point to save power, since the SysTick count is calculated to correspond to a single system clock frequency.  Therefore you will need to rerun the SysTick_Config function after every core clock frequency change to keep a consistent interrupt rate.

 

SysTick as a Delay Function Generator

The examples in some of the Simplicity Studio application notes use the SysTick as a way to create a delay in a sequential program.  For example, the essence of the following code can be found in many of the included examples:

#define DEBUG_BREAK           __asm__("BKPT #0");
 
/* Counts 1ms timeTicks */
volatile uint32_t msTicks = 0;
 
void Delay(uint32_t dlyTicks)
{
      uint32_t curTicks;
 
      curTicks = msTicks;
      while ((msTicks - curTicks) < dlyTicks) ;
}
 
void SysTick_Handler(void)
{
      /* Increment counter necessary in Delay()*/
      msTicks++;
}
int main(void)
{
      // Chip errata
      CHIP_Init();
 
      if (SysTick_Config(CMU_ClockFreqGet(cmuClock_CORE) / 1000))
      {
            DEBUG_BREAK;
      }
 
      while (1)
      {
            Delay(1000);
            // Do something that will happen every second
      }
}

 

Notice that I have placed the SysTick_Config inside of an if statement and calls a DEBUG_BREAK in case the SysTick can’t be configured for the supplied divider value.  This differs from the examples included with the kit examples to make it easier to spot problems in my code.  

 

The compiler will replace DEBUG_BREAK in my code with the special assembly command BKPT #0, which tells the ARM core to drop into a debug mode and halt the processor. I will use these DEBUG_BREAK statements in my code wherever I need to spot something that is going wrong from the IDE.  As long as the debugger is attached to the system, you will see that the IDE breaks into debug mode and shows you where things have gone wrong.  Otherwise the system would lock up or act erratically and there would be no indication that things had gone wrong.  Once you are ready to deploy your firmware in the field for production, the DEBUG_BREAK code can be redefined as an empty string, and the compiler will remove all instances of the BKPT #0 command from your production code.

 

Once the System Timer is configured, the execution of the SysTick_Handler will interrupt any running program once every millisecond and update the global msTicks variable by adding one to the count.  The MCU will then return execution to the point in the sequential program where it was when the interrupt occurred.

 

The Delay function can be called anywhere in your sequential program where you need to insert a time delay (except inside the SysTick_Handler itself.   More on that in a moment.) The Delay function will wait the number of dlyTicks (in this case, the number of milliseconds) before exiting the Delay function.  Since the execution of the Delay function’s while loop is being interrupted by the SysTick interrupt handler, the msTicks variable is being updated in the background outside of the Delay function’s knowledge or control.  That is why msTicks is defined as volatile, which tells the compiler that this variable can change by a background process.  This keeps the compiler from optimizing ineffective statements out of your program when it appears to the compiler that msTicks would never change.  As long as the SysTick interrupt is firing, the Delay function will eventually find that msTicks is larger than curTicks by dlyTicks and allow the function to exit, returning program control to the main() while loop.  You can easily test this code by placing a breakpoint on the line of the Delay(1000) statement.  Upon running the code, you should notice that the code breaks on the line.  Then, when you press the resume button, it should break again after a second.  Set the value to Delay(10000) and recompile.  After the first break-and-resume operation, you should now see the breakpoint being triggered after 10 seconds every time you resume the program.

 

The reason why the Delay function cannot be used inside of the SysTick handler is because interrupts in the ARM core are not reentrant.  This means that once the handler is executing, even if a millisecond has passed and another SysTick interrupt occurs, the handler will exit and then re-enter the interrupt  immediately to service the blocked interrupt source.  In this case, msTicks will never increment, so the Delay function will never exit, and the whole thing locks up.

 

This is a quick and dirty way to create a delay function, and it is a bit better than our instruction-based delay function that we created in the past lessons to introduce a pause by keeping the MCU mindlessly busy in order to blink the LED at a certain rate.  The SysTick method can be configured to keep accurate time at any clock frequency, but they both keep the processor in a high-power state at all times.  And, both can be impacted by any code that implements interrupt handlers or disables interrupts for timing-critical procedures.  This will add more delay to your delay function that what you expect, and your timing will change in unexpected ways.  As you start building more complex programs, these intricate interactions could give you a hard time.

 

The big problem with the above code to generate a delay is that the variable msTicks is used to count off the number of milliseconds that have elapsed, but the SysTick interrupt is not guaranteed to be triggered at regular intervals if the code inside the SysTick handler is not quick enough to exit before the next SysTick interrupt occurs, so msTicks will not get updated as routinely as expected.  And, the entry into the SysTick interrupt handler can be delayed by any code that temporarily disables interrupts.  This means that the count that is kept by this procedure may be inconsistent depending on what is going on with the rest of the system. 

 

The bottom line is that the SysTick interrupt can be used anytime that you need something to happen on a roughly periodic basis, but beware that it can be “starved” for time on the MCU, so don’t rely upon it for accurate time keeping purposes.  It has plenty of good uses, but it is not your only tool for timing-based functions.

 

In the next lesson, I will introduce the peripheral timers on the EFM32 series of MCUs to create more accurate timing functions.

 

PREVIOUS NEXT

  • Blog Posts
  • Makers
  • HI,

     

    I would like to know if it is possible to use Systick on EFM8 or an equivalent function to call periodically handler.

    I have tried to call periodically a function since 1 week but it doesn't work, have you got some idea?

     

    Thank you for your answer.

     

    Liolio

     

    0
  • Hi Liolio,

     

    The 8051 architecture does not have a built-in system timer like SysTick.  However, you can definitely do something similar with Timer 0 or Timer 1.  You can find an example of this in the EFM8BB1 Comparators Reset Source example included in Simplicity Studio when you click the Software Examples tile.

     

    //-----------------------------------------------------------------------------
    // Timer0_Delay
    //-----------------------------------------------------------------------------
    //
    // Return Value : None
    // Parameters   :
    //   1) uint16_t us - number of microseconds of delay
    //                        range is full range of integer: 0 to 65335
    //
    // Configure Timer0 to delay <us> microseconds before returning.
    //-----------------------------------------------------------------------------
    
    void Timer0_Delay (uint16_t us)
    {
       unsigned i;                         // Millisecond counter
    
       for (i = 0; i < us; i++)            // Count microseconds
       {
          TCON &= ~0x30;                   // STOP Timer0 and clear overflow
          TH0 = (-SYSCLK/1000000) >> 8;    // Set Timer0 to overflow in 1us
          TL0 = -SYSCLK/1000000;
          TCON |= 0x10;                    // START Timer0
          while (TCON_TF0 == 0);           // Wait for overflow
       }
    }

    ~Tabi

    0
  • Hi Tabi,

     

    Thanks for your answer, but I don't understand where I can call the handler... Robot Sad

    Have a good day,

     

    Lio

    0