Enabling the SysTick

Once a stable CPU frequency has been set up, we can configure the main timer on the system, the SysTick. Since the implementation of a specific system timer is not mandatory on all Cortex-M, sometimes it is necessary to use an ordinary auxiliary timer to keep track of the system time. In most cases, though, the SysTick interrupt can be enabled by accessing its configuration, which is located in the system control block within the system configuration region. In all Cortex-M microcontrollers that include a system tick, the configuration can be found starting at address 0xE000E010, and exposes four registers:

  • The control/status register (SYSTICK_CSR) at offset 0
  • The reload value register (SYSTICK_RVR) at offset 4
  • The current value register (SYSTICK_CVR) at offset 8
  • The calibration register (SYSTICK_CALIB) at offset 12

The SysTick works as a countdown timer. It holds a 24-bit value, which is decreased at every CPU clock tick. The timer reloads the same value every time it reaches 0, and triggers the SysTick interrupt if it is configured to do so.

As a shortcut to access the SysTick registers, we define their locations:

#define SYSTICK_BASE  (0xE000E010)
#define SYSTICK_CSR (*(volatile uint32_t *)(SYSTICK_BASE + 0x00))
#define SYSTICK_RVR (*(volatile uint32_t *)(SYSTICK_BASE + 0x04))
#define SYSTICK_CVR (*(volatile uint32_t *)(SYSTICK_BASE + 0x08))
#define SYSTICK_CALIB (*(volatile uint32_t *)(SYSTICK_BASE + 0x0C))

Since we know the frequency of the CPU in Hz, we can define the system tick interval by setting the value in the Reload Value Register (RVR). For a 1 ms interval in between two consecutive ticks, we simply divide the frequency by 1,000. We can also set the current value for the timer to 0, so that the first interrupt is immediately triggered after we enable the countdown. The SysTick can finally be enabled by configuring the control/status register. The meaning of the least significant three bits of the CSR are as follows:

  • Bit 0: Enables countdown. After this bit is set, the counter in the SysTick timer is automatically decreased at every CPU clock interval.
  • Bit 1: Enables interrupt. If this bit is set when the counter reaches 0, a SysTick interrupt will be generated.
  • Bit 2: Source clock selection. If this bit is reset, an external reference clock is used as source. The CPU clock is used as source when this bit is set.

We are going to define a custom SysTick interrupt handler, so we want to set bit 1 as well. Because we configured the CPU clock correctly, and we are scaling the system tick interval reload value on that, we also want bit 2 to be set. The last line of our systick_enable routine will enable the three bits together in the CSR:

void systick_enable(void) {
SYSTICK_RVR = ((CPU_FREQ / 1000) - 1);
SYSTICK_CVR = 0;
SYSTICK_CSR = (1 << 0) | (1 << 1) | (1 << 2);
}

The system timer that we have configured is the same as that used by real-time operating systems to initiate process switches. In our case, it might be helpful to keep a monotonic system wallclock, measuring the time elapsed since the clock configuration. A minimalist implementation of the interrupt service routine for the system timer could be as follows:

volatile unsigned int jiffies = 0;
void isr_systick(void)
{
++jiffies;
}

This simple function, and the associated global volatile variable associated, are sufficient to keep track of the time transparently while the application is running. In fact, the system tick interrupt happens independently, at regular intervals, when the jiffies variable is incremented in the interrupt handler, without altering the flow of the main application. What actually happens is that every time the system tick counter reaches 0, the execution is suspended, and the interrupt routine quickly executes. When isr_systick returns, the flow of the main application is resumed by restoring exactly the same context of execution stored in memory a moment before the interrupt occurred.

The reason why the system timer variable must be defined and declared everywhere as volatile is that its value is supposed to change while executing the application in a way that is independent of the behavior possibly predicted by the compiler for the local context of execution. The keyword volatile in this case ensures that the compiler is forced to produce code that checks the value of the variable every time it is instantiated, by disallowing the use of optimizations based on the false assumption that the variable is not being modified by the local code.

Here is an example main program that uses the previous functions to boot the system, configure the master clock, and enable the SysTick:

void main(void) {
flash_set_waitstates();
clock_config();
systick_enable();
while(1) {
WFI();
}
}

The shortcut for the assembly instruction WFI, short for wait for interrupt, is defined. It is used in the main application to keep the CPU inactive until the next interrupt occurs:

#define WFI() asm volatile ("wfi")

To verify that the SysTick is actually running, the program can be executed with the debugger attached, and stopped after a while. If the system tick has been configured correctly, the variable jiffies should always be displaying the time in milliseconds elapsed since boot.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset