The simplest project ever with ChibiOS

Introduction

In this example, we are going to see what is the simplest project you can create in ChibiOS. Following this demo, you will be able to continuously blink an LED with a simple single-thread application stripping down all the extra code. For this exercise, we are going to use the SDP-K1 and we are going to start from its default demo called RT-STM32F469I-EVAL-SDP-CK1Z which you can find under the demos folder of ChibiOS. Before we begin, if you are not familiar with ChibiOS and the SDP-K1 this article can help you quickly catch up with this demo.

Simplifying the project

As an initial step, we will duplicate the project RT-STM32F469I-EVAL-SDP-CK1Z and rename it. If this project is not visible in your ChibiStudio’s Project Explorer, you will need to import it from [ChibiOS root]\demos\STM32\RT-STM32F469I-EVAL-SDP-CK1Z.

Importing a project is quite straightforward. If you are unsure how to proceed, refer to the How to import a project in ChibiStudio guide. At this point, we should duplicate the project (refer to “How to duplicate a project in ChibiStudio), ensuring to modify the makefile and correct the relative path of ChibiOS.

Overview of the demo

Once we have a duplicated building project for out target board, we need to analyze the main.c to decide what is actually needed and what is superfluous

/*
    ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
    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"
#include "rt_test_root.h"
#include "oslib_test_root.h"
/*
 * This is a periodic thread that does absolutely nothing except flashing
 * a LED.
 */
static THD_WORKING_AREA(waThread1, 128);
static THD_FUNCTION(Thread1, arg) {
  (void)arg;
  chRegSetThreadName("blinker");
  while (true) {
    palSetLine(LINE_LED_GREEN);
    chThdSleepMilliseconds(50);
    palSetLine(LINE_LED_ORANGE);
    chThdSleepMilliseconds(50);
    palSetLine(LINE_LED_RED);
    chThdSleepMilliseconds(200);
    palClearLine(LINE_LED_GREEN);
    chThdSleepMilliseconds(50);
    palClearLine(LINE_LED_ORANGE);
    chThdSleepMilliseconds(50);
    palClearLine(LINE_LED_RED);
    chThdSleepMilliseconds(200);
  }
}
/*
 * Application entry point.
 */
int main(void) {
  /*
   * System initializations.
   * - HAL initialization, this also initializes the configured device drivers
   *   and performs the board-specific initializations.
   * - Kernel initialization, the main() function becomes ----a thread and the
   *   RTOS is active.
   */
  halInit();
  chSysInit();
  /*
   * Activates the serial driver 5 using the driver default configuration.
   */
  sdStart(&SD5, NULL);
  /*
   * Creates the example thread.
   */
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, Thread1, NULL);
  /*
   * Normal main() thread activity, in this demo it does nothing except
   * sleeping in a loop and check the button state.
   */
  while (true) {
    if (!palReadLine(LINE_ARD_A0)) {
      test_execute((BaseSequentialStream *)&SD5, &rt_test_suite);
      test_execute((BaseSequentialStream *)&SD5, &oslib_test_suite);
    }
    chThdSleepMilliseconds(500);
  }
}

This demo consists of two threads

  1. Main Thread (main), function, which performs several key initializations and then enters a loop where it checks the state of a line and if the line is shorted to ground runs the text suite.
  2. Blinker Thread (Thread1): which it sequentially sets and clears lines connected to different colored LEDs with specific timing delays between each action resulting in a repeating pattern of LED blinks that continues indefinitely.

To proceed with our task we would need to remove one of the threads and simplify the LEDs routing.

Removing the extra thread

Our purpose is to have a single threaded application (main only) which handles one of the LEDs. For this purpose we can cut out the entire Thread1 declaration:

/*
 * This is a periodic thread that does absolutely nothing except flashing
 * a LED.
 */
static THD_WORKING_AREA(waThread1, 128);
static THD_FUNCTION(Thread1, arg) {
  (void)arg;
  chRegSetThreadName("blinker");
  while (true) {
    palSetLine(LINE_LED_GREEN);
    chThdSleepMilliseconds(50);
    palSetLine(LINE_LED_ORANGE);
    chThdSleepMilliseconds(50);
    palSetLine(LINE_LED_RED);
    chThdSleepMilliseconds(200);
    palClearLine(LINE_LED_GREEN);
    chThdSleepMilliseconds(50);
    palClearLine(LINE_LED_ORANGE);
    chThdSleepMilliseconds(50);
    palClearLine(LINE_LED_RED);
    chThdSleepMilliseconds(200);
  }
}

Similarly, we also need to delete the function call chThdCreateStatic which is responsible for the creation of Thread1.

  /*
   * Creates the example thread.
   */
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, Thread1, NULL);

Amending the main function

Let’s look closely to our main function

/*
 * Application entry point.
 */
int main(void) {
  /*
   * System initializations.
   * - HAL initialization, this also initializes the configured device drivers
   *   and performs the board-specific initializations.
   * - Kernel initialization, the main() function becomes ----a thread and the
   *   RTOS is active.
   */
  halInit();
  chSysInit();
  /*
   * Activates the serial driver 5 using the driver default configuration.
   */
  sdStart(&SD5, NULL);
  /*
   * Creates the example thread.
   */
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, Thread1, NULL);
  /*
   * Normal main() thread activity, in this demo it does nothing except
   * sleeping in a loop and check the button state.
   */
  while (true) {
    if (!palReadLine(LINE_ARD_A0)) {
      test_execute((BaseSequentialStream *)&SD5, &rt_test_suite);
      test_execute((BaseSequentialStream *)&SD5, &oslib_test_suite);
    }
    chThdSleepMilliseconds(500);
  }
}

The main, the application entry point, is actually the starting point of our code execution. In here we have the functions halInit() and chSysInit() which are crucial for the system initialization and that is why we do not delete them Note that these functions should be kept at the beginning of the main before calls to any other ChibiOS’ API.

For what concern the Test Suite of ChibiOS, we don’t intend to use it. The Suite performs some tests over the various part of ChibiOS printing the results over a Serial Driver (in this case SD5). As we don’t intend to use it now the serial driver we can remove the call to sdStart(&SD5, NULL) which initializes the serial driver.

Looking at the main loop we have a check on the A0 which will eventually execute the test suite. The loop as well can be clean up removing the entire if branch and replacing it with a call to palToggleLine() to change the status of an LED Line.

Performing all these changes we can accomplish the following simplified main

int main(void) {
  /* System initializations HAL and RT. */
  halInit();
  chSysInit();
  while (true) {
    palToggleLine(LINE_LED_GREEN);
    chThdSleepMilliseconds(500);
  }
}

Removing extra inclusion

Finally we can remove the inclusion of the test suite header at the beginning of the file.

#include "rt_test_root.h"
#include "oslib_test_root.h"

Our simplified project

With all the changes to our main.c, the project will be very nimble with only few lines to initialize the system and blink the LED.

#include "hal.h"
#include "ch.h"
int main(void) {
  /* System initializations HAL and RT. */
  halInit();
  chSysInit();
  while (true) {
    palToggleLine(LINE_LED_GREEN);
    chThdSleepMilliseconds(500);
  }
}

What we have achieved so far seems to be the stated goal: our application is now single-threaded and reduced to the bare minimum. Now we are ready to connect the SDP-K1 controller board to our laptop and to proceed to test it.

If we have done all the steps above and the flash and run correctly we can actually see the green LED toggling!!

The green LED of the SDP-K1 blinking

Further memory footprint optimizations

The Binary file

Reducing the code footprint and resource usage is always a crucial consideration when working on embedded system projects. The changes made in the main.c file alone may not be sufficient to do that. It’s important to keep in mind that when building the project, the opcode instructions are encoded as Binary and are stored in a file with the .bin extension, named ch.bin.

This file often referred to as binary image, is copied one to one in the flash memory of the microcontroller and its size represents the space that will be occupied in flash after the programming.

It takes just a few seconds to check the size of the ch.bin file. You can navigate to the build folder of the project you are working on, right-click the ch.bin file, and select properties. For example, the size of the demo RT-STM32F469I-EVAL-SDP-CK1Z built with GCC 10.3.1 is 56.5KB.

Default demo ch.bin size

In our project, the ch.bin size has undergone a reduction of memory footprint from 56.5kB to 6.64kB.

Single thread main

Even if the size has been reduced when building our project we can see that the system still does compilation regarding the Test Suite and the Serial Driver.

The building process completed with Test Suite and Serial Driver still compiled

How to optimize memory usage

To further optimize the code, we can edit the Makefile by deleting or commenting out all lines of code labeled as “optional” related to the Test Suite and can reduce the flash memory occupancy even more by disabling the Serial Driver in the halconf.h file.

# Other files (optional).
# include $(CHIBIOS)/os/test/test.mk
# include $(CHIBIOS)/test/rt/rt_test.mk
# include $(CHIBIOS)/test/oslib/oslib_test.mk
/**
 * @brief   Enables the SERIAL subsystem.
 */
#if !defined(HAL_USE_SERIAL) || defined(__DOXYGEN__)
#define HAL_USE_SERIAL                      FALSE
#endif

Now by performing one more time check on the ch.bin file size, we can see the file has been further reduced going from 6.64KB to 4.57KB.

Binary file size is reduced even further

Replies to The simplest project ever with ChibiOS

Leave a Reply