Parametric Threads with ChibiOS

Introduction

In this article, we will demonstrate how to create multiple threads using parametric functions. This means that while the function executed by each thread is the same, the specific behavior is influenced by passing different parameters each time.

To illustrate how parametric threads work, we will blink multiple LEDs in different ways defining a single function. We will begin with the simplest project ever and gradually modify it to create a project that blinks two LEDs in counter phase.

Before starting, make sure you have the latest version of ChibiStudio installed and have the Analog Devices EVAL-SDP-CK1Z board to fully engage in this hands-on experience. 

How to parametrize a thread

To create a static thread without a parameter, we pass NULL during creation and we cast the argument to void in the thread function to avoid compiler warnings

static THD_WORKING_AREA(waThread1, 128);
static THD_FUNCTION(Thread1, arg) {
  
  /* Unused argument. */
  (void) arg;
  while (true) {
    chThdSleepMilliseconds(50);
  }
}
int main(void) {
  
  ...
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, Thread1, NULL);
  ...
}

However, ChibiOS offer the chance to parameterize threads by passing a pointer to void at creation time as shown in the following example

static THD_WORKING_AREA(waThread1, 128);
static THD_FUNCTION(Thread1, arg) {
  
  /* Using a temporary variable and recasting the type of arg. */
  uint32_t* myargp = (uint32_t*)arg;
  while (true) {
    /* Using the parameter. */
    myargp++;
    chprintf(chp, "%d", *myargp);
    chThdSleepMilliseconds(50);
  }
}
int main(void) {
  
  ... 
  uint32_t myarg;
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, Thread1, &myarg);
  ...
}

In many cases, the application requires passing multiple arguments and this is when you turn to structures as we are going to show in this demo. In general, this involves defining a new structure and passing that to the thread.

LEDs in counter phase

Working with a structure

Since this demo is about two LEDs blinking in counter-phase both with a 50% duty cycle, multiple parameters are necessary. Hence, we need to group together variables of different types and this can be done with a structure.

/**
 * @brief   LED configuration structure.
 */
typedef struct {
  /**
   * @brief Line associated to LED
   */
  ioline_t                  led;
  /**
   * @brief Time associated to LED.
   */
  systime_t                 time;
  /**
   * @brief Line status associated to LED.
   */
  uint32_t                  status;
} LEDconfig_t;

The first thing we need is a ioline_t to identify the line where the LED of interest is connected. The second member is related to the time the LED needs to blink on and off, and its type is systime_t. Since we are assuming a 50% duty cycle, no additional time-related parameters are necessary. To achieve the counter-phase effect between two LEDs we need to set the initial logical state of the line. This information can be represented using a Boolean value thus we add an addition uint32_t member.

Once the structure is defined, we can create two new LEDConfig_t variables, redledconfig and greenledconfig. These will be used to specify the configuration for the red and the green LEDs, respectively.

/*
 * Creating configuration for the thread for the red LED 
 */
static LEDconfig_t redledconfig = {
  .led = LINE_LED_RED,
  .time = 500,
  .status = TRUE
};
/*
 * Creating configuration for the thread for the green LED 
 */
static LEDconfig_t greenledconfig = {
  .led = LINE_LED_GREEN,
  .time = 500,
  .status = FALSE
};

To achieve the counter-phase behavior, we set the initial state of the red LED to TRUE and the green LED to FALSE. However, this is a personal choice and you can switch the initial states if desired.

As the function palToggleLine inverts the logical state of a line, if a LED is initially on, calling the function on its line will turn it off and vice versa.

Two threads one function

At this point, we are ready to define the parametric thread function that both threads will execute.

/*===========================================================================*/
/* Blinking Thread Definition                                                */
/*===========================================================================*/
static THD_FUNCTION(ThreadBlinker, arg) {
  
  /* Using a temporary variable to make our code more readable. */
  LEDconfig_t* mycfg = (LEDconfig_t*)arg;
  
  /* Configuring the line to the initial value. */
  palWriteLine(mycfg->led, mycfg->status);
  while (true) {
    palToggleLine(mycfg->led);
    chThdSleepMilliseconds(mycfg->time);
  }
}

The first parameter (ThreadBlinker) refers to the name and the pointer to the thread function. The second parameter (arg) represents the argument that will be passed to this function. In this case, arg will be one of our two LEDConfig_t configurations.

Status LED

In this demo, having two LEDs blinking in counter-phase was specifically chosen to showcase the functionality of parametric threads. However, when the results of the parametric threads are not immediately apparent, the status LED can be used as a quick way to check if the system is running or the application has crashed. As a good measure, we will add to our main loop a LED blinking at a different speed.

int main(void) {
  ...
  while (true) {
    palToggleLine(LINE_PROCESSOR_STATUS);
    chThdSleepMilliseconds(50);
  }
}

Composing the puzzle

At this point, we can see how the entire main.c looks like

/*
    PLAY Embedded demos - Copyright (C) 2014...2023 Elisa Bellacosa
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
        http://www.apache.org/licenses/LICENSE-2.0
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/
#include "ch.h"
#include "hal.h"
/**
 * @brief   LED configuration structure.
 */
typedef struct {
  /**
   * @brief Line associated to LED
   */
  ioline_t                  led;
  /**
   * @brief Time associated to LED.
   */
  systime_t                 time;
  /**
   * @brief Line status associated to LED.
   */
  uint32_t                  status;
} LEDconfig_t;
/**
 * @brief   Parametric thread function.
 */
static THD_FUNCTION(ThreadBlinker, arg) {
  
  /* Using a temporary variable to make our code more readable. */
  LEDconfig_t* mycfg = (LEDconfig_t*)arg;
  
  /* Configuring the line to the initial value. */
  palWriteLine(mycfg->led, mycfg->status);
  while (true) {
    palToggleLine(mycfg->led);
    chThdSleepMilliseconds(mycfg->time);
  }
}
/*===========================================================================*/
/* Thread 1 related                                                          */
/*===========================================================================*/
/* Thread 1 working area. */
static THD_WORKING_AREA(waThread1, 128);
/* Thread 1 parameters. */
static LEDconfig_t redledconfig = {
  .led = LINE_LED_RED,
  .time = 500,
  .status = TRUE
};
/*===========================================================================*/
/* Thread 2 related                                                          */
/*===========================================================================*/
/* Thread 2 working area. */
static THD_WORKING_AREA(waThread2, 128);
/* Thread 2 parameters. */
static LEDconfig_t greenledconfig = {
  .led = LINE_LED_GREEN,
  .time = 500,
  .status = FALSE
};
/*===========================================================================*/
/* Application entry point.                                                  */
/*===========================================================================*/
int main(void) {
  /*
   * System initializations HAL and RT.
   */
  halInit();
  chSysInit();
  /*
   * Creates the two threads.
   */
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, ThreadBlinker, &redledconfig);
  chThdCreateStatic(waThread2, sizeof(waThread2), NORMALPRIO + 1, ThreadBlinker, &greenledconfig);
  /*
   * Status LED blinking.
   */
  while (true) {
    palToggleLine(LINE_PROCESSOR_STATUS);
    chThdSleepMilliseconds(50);
  }
}

At the beginning of the file, we define the format of the thread configuration as well as the parametric function. For each thread, we then have to define a dedicated working area and the appropriate configuration

The main() initializes the system as usual and then creates the two threads: note that the two creations use the same function but the specific working area and configuration.

If we connect the SDP-K1 to our laptop and proceed with the flash and run of our program, we can observe our two LEDs blink in counter-phase while the Processor status LED blinks on its own.

Status LED blinking together with the red and the green LEDs

The synchronism between the LEDs (and therefore between threads) is kept even after long runs. This result demonstrates that we are working with a hard real-time operating system (RTOS). An RTOS is a type of operating system that is specifically designed to guarantee a deterministic response time to a given set of tasks or inputs, regardless of the other activities running on the system. The RTOS must meet its timing constraints with absolute certainty, regardless of any external factors that may be affecting the system.

Be the first to reply at Parametric Threads with ChibiOS

Leave a Reply