Using CMSIS and SysTick to blink an LED on an #STM32F4

The SysTick (System Timer) is a timer inside ARM based microcontrollers, in contradistinction to timer peripherals provided by vendors like ST. It can be used to generate interrupts at a specified time interval. The convention is that the interrupt is fired every 1ms. You can used SysTick for scheduling tasks in an RTOS, or generating tick values for use in delay functions. If you want to generate interrupts at a greater frequency, then you are probably better off using a vendor timer (TIM1, TIM2, etc.).

In this tutorial, we are going to be blinking two LEDs: PC10 and PC12. For PC10 we use a delay function to turn the LED on for 50ms, and off for 950ms. For PC12, we’re just going to toggle the LED every time SysTick is updated.

I’m going to be CMSIS for this to show how everything works at a low level. With all this in mind, here’s the code:

     1	#define STM32F411xE
     2	#include "stm32f4xx.h"
     3	
     4	uint32_t SystemCoreClock = 16000000;
     5	
     6	volatile uint32_t  ticks = 0; // must be volatile to prevent compiler optimisations
     7	
     8	void  SysTick_Handler(void)
     9	{
    10		ticks++;
    11		if(GPIOC->ODR &  GPIO_ODR_OD12) // is PC12 on?
    12			GPIOC->BSRR = GPIO_BSRR_BR12; // turn it off via a reset
    13		else
    14			GPIOC->BSRR = GPIO_BSRR_BS12; // turn it on via set
    15	}
    16	
    17	void delay_ms(int ms)
    18	{
    19		uint32_t started = ticks;
    20		while((ticks-started)<=ms); // rollover-safe (within limits)
    21	}
    22	
    23	int main (void) 
    24	{   
    25		RCC->AHB1ENR	|= RCC_AHB1ENR_GPIOCEN; //RCC on for GPIO C
    26		GPIOC->MODER	|= GPIO_MODER_MODER10_0; // PC10 mode out
    27		GPIOC->MODER	|= GPIO_MODER_MODER12_0; // PC12 mode out
    28	
    29		SysTick_Config(SystemCoreClock/1000); // set tick to every 1ms
    30	
    31		while(1) {
    32			GPIOC->BSRR = GPIO_BSRR_BS10; // PC10 on
    33			delay_ms(50);
    34			GPIOC->BSRR = GPIO_BSRR_BR10; // PC10 off
    35			delay_ms(950);
    36		}
    37	}

As you can see, the program is only 37 lines long, so it is nice and simple. The generated binary is only 692 bytes long. The F411 has 512Kb of flash memory. Given our code is so compact, it’s difficult to imagine how we would ever get close to using all of flash memory! But enough boasting …

Lines 1-2 just set up the correct include file for our microprocessor.

Line 4 gives us the speed of the system core. In our case, it’s 16MHz. If you’re not sure of your core speed, then go to you CMSIS folder and look for a template file like Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c

Line 6 contains a count of the ticks in ms. Note that it is declared volatile. The MCU (microcontroller) will likely misbehave if you don’t do this.

Line 8 defines the interrupt controller that we want to call every 1ms. You must call it SysTick_Handler(). Why that name? It’s because the file startup_stm32f411retx.s enumerates the interrupt vectors, and specifies a name it expects for that interrupt. You could change the names if you wanted to, but that is likely to cause confusion. Just stick to convention is your best advice.

Line 10 just increments the tick counter. Notice that the counter is declared as an unsigned integer. This will help with integer overflow. The counter will overflow approx. once every 49.7 days, assuming it is increments every 1ms (2^32/1000/60/60/24). This should be enough for most applications. For truly long-running apps, you might decide to resort to a uint64_t. Then it won’t overflow for 584.9bn years. Is that enough for you? You may want to investigate the processor’s RTC (Real Time Clock) for long-running operations instead, though.

Line 11 checks to see if the output pin PC12 is high or not. Registers ODR state which pins on the port are output high, and which are output low. It is possible to toggle this register to drive a pin high or low, but this is likely to be bad practise. Why? Well, it has to do with the “atomicity” of operations. To toggle a pin state this way you would need to read the register, change the bit that needs changing, and rewrite the register. This takes several clock cycles. If there is an interrupt during this operation, the ISR and background task might be working with inconsistent values for the registers, thereby causing pins to be set wrong.

To work around this, you should prefer to use the BSRR (Bit Set and Reset Register), which guarantees atomic operations. The high 16 bits are used for resetting pins, and the low 16-bits are used for setting them. Create a bit pattern for the pins you want to drive high or low, and set the BSRR to that value.

Line 12, for example, turns off pin 12 by using GPIO_BSRR_BR12 (the reset), whilst line 14 uses GPIO_BSRR_BS12 (set).

Lines 17-21 define a delay function for us. We note a start tick and a current tick, and do a blocking loop for the required number of ms.

Line 23: we start our main function.

Line 25 starts the peripheral clock for GPIO port C. We need to activate it in order to do anything useful with the port.

Lines 26-27 set pins 10 and 12 of port C to output.

Line 29 starts the SysTick. To get the system timer to update every 1ms, simply pass in the value SystemCoreClock/1000. SysTick_Config() is a function provided by CMSIS. We don’t need to worry too much about the internals. It is defined in a file like Driveres/CMSIS/Core/include/core_cm4.h. Its code will be something like:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
  {
    return (1UL);                                                   /* Reload value impossible */
  }

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */
  return (0UL);                                                     /* Function successful */
}

Note that one of the things it does is enable the SysTick interrupt.

Lines 31-36 should be fairly self-explanatory given the foregoing. We loop indefinitely, setting the bit to turn pin number 10 of GPIOC in the BSRR register. We wait for 50ms using the function we created earlier. Then we turn the pin off using the reset bit, which we send to the BSRR. Then we wait for 950ms, and repeat the whole process again.

And that’s it. I hope you found this information useful. You now have a pretty good idea as to how to use the SysTick timer. You can set all of this up very easily using the CubeIDE, but the code it generates can be a bit convoluted. The code written here should be slightly faster, because it isn’t going through additional layers of abstraction.

Code for this project is available here.

About mcturra2000

Computer programmer living in Scotland.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment