Close

The Beating Heart: FreeRTOS

A project log for NTP Clock Based on STM32H735 Discovery Kit

This is an SNTP clock based on the STM32H735 Discovery Kit.

dmoisandmoisan 03/30/2021 at 15:580 Comments

My clock is built around FreeRTOS.  FreeRTOS is an open-source real-time operating system for embedded devices.  It provides timers and synchronization functions, amongst many other capabilities, and it is a fully preemptive multitasking operating system, like its big brother, Linux.Clocks are timers (and timers are clocks!)  That is perhaps stupidly obvious, but that's what our clock, and many other projects, needs.  The TCP/IP stack (Microchip's) in my last clock (the Twatch derivative) didn't use an OS;  it used cooperative multitasking, where the stack would do some work, and then pause for the main function (my clock code) to do something, and my code would pause to let the stack work.  LwIP will work this way, but it works better with an RTOS, and that's what we're using. 

Here's some code from my main.c.  I'll explain it as I go along.

int main(void)
{

  /* Configure the MPU attributes as Device memory for ETH DMA descriptors */
  MPU_Config();

  /* Enable the CPU Cache */
  CPU_CACHE_Enable();

  /* STM32H7xx HAL library initialization:
       - Configure the TIM6 to generate an interrupt each 1 msec
       - Set NVIC Group Priority to 4
       - Low Level Initialization
     */
  HAL_Init();

 //
 // Code to redirect trace to ST-LINK SWM Port
 //

/* Configure the system clock to 520 MHz */
  SystemClock_Config();

  /* Configure the LCD ...*/
  BSP_Config();

  // Initialize LED's.
  BSP_LED_Init(LED1);
  BSP_LED_Init(LED2);

  // Turn on red ("warning/error") LED
  // It will be turned off when the timers are started
  //

  BSP_LED_On(LED2);

  /* Init thread */
  osKernelInitialize();
  
  attr.name = "Start";
  attr.stack_size = 4 * configMINIMAL_STACK_SIZE;
  attr.priority = osPriorityNormal;
  StartHandle = osThreadNew(StartThread, NULL, &attr);

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */
  for( ;; );
}

The first part of this code through BSP_Config just sets up the hardware.  We then initialize the LCD (covered later on) and then the LED's.  LED1 is green, and LED2 is red.  I use the red LED to indicate initialization.  When the clock is working normally, LED2 will be turned on during initialization, and then turned off just before the clock starts running with its main code.  Normally, LED2 will briefly blink.  If, however, LED2 remains lit, it means the device locked up or crashed and I need to do something.  LED1, the green one, is used to blink once a second and I will talk about that later.

The following code is boilerplate.  FreeRTOS is based on tasks--there is no code running outside of FreeRTOS in the device.  The code initializes the FreeRTOS kernel (osKernelInitialize) and sets up an initial task, the function StartThread.  Then the kernel is started with osKernelStart, and the code continues in an infinite while(1) loop.  This is a very common idiom in FreeRTOS coding, and embedded coding in general.  (After all, you're responsible for everything and there is no exit() function to take you back!)

Now, let's see StartThread.  This is the first task that FreeRTOS executes, and it kicks off the rest of the code, including my clock code.

void StartThread(void* argument)
{
  /* Reset seconds counter */
  ResetCounters();

  /* Create tcp_ip stack thread */

  tcpip_init(NULL, NULL);

  /* Initialize the LwIP stack */
  Netif_Config();

...
[Some code omitted for clarity...]
...

  //
  // Create TickUpdate timer
  //

  // Display update interval is 1 second

  xTimerHandle DisplayUpdateHandle = xTimerCreate(
        "DispUpd", /* name */
        pdMS_TO_TICKS(1000), /* period/time 1 second */
        pdTRUE, /* auto reload */
        (void*)0, /* timer ID */
        (TimerCallbackFunction_t )DisplayUpdate); /* callback */

  if (DisplayUpdateHandle==NULL) {
    for(;;); /* failure! */
  }

  //
  // Start DisplayUpdate Timer
  //

  if (xTimerStart(DisplayUpdateHandle, 0)!=pdPASS) {
    for(;;); /* failure!?! */
  }

    //
    // Turn off red (warning/error) LED
    //
    BSP_LED_Off(LED2);

    //
    // Clear LCD
    //

    UTIL_LCD_Clear(UTIL_LCD_COLOR_BLACK);

    for( ;; )
    {
    /* Delete the Init Thread */
        osThreadTerminate(StartHandle);
    }
} 

First off, we have a static variable that holds our seconds counter.  We initialize it with SecondsCounter().  (More on this later.)  Then, initialize the TCP thread and stack (tcpip_init, Netif_Config).  Then we create a timer.  FreeRTOS can create one-time tasks, ongoing tasks, and periodic tasks running on a timer.  It will manage the timer and use it to run tasks on a schedule.

A task running once a second?  That's a clock!  Perfect.  The boilerplate code in the middle of the snippet tells FreeRTOS to create a timer to fire once a second and run the function DisplayUpdate every time it fires.  That function has all of our clock code.

Once the timer is created, LED2 is turned off and the LCD is cleared.  To save memory, the current task is terminated, leaving only our timer thread running.

We can now leave main.c and begin to discuss the display code.  In the next log entries, I'll be jumping back and forth between the display function, and the network function, specifically the SNTP client, which I have not yet talked about. Thanks for following so far, and keep reading!

Discussions