Logging FreeRTOS via Virtual Com Port

Introduction

Embedded systems based on STM32 microcontrollers are widely used in various fields – from consumer electronics to industrial equipment and IoT devices. One of the key tasks when developing such systems is debugging and monitoring the operation of applications. Effective logging greatly facilitates these processes, allowing developers to quickly identify and resolve errors and analyze system behavior in real time.

The purpose of this article is to introduce you to the STM32 logging library developed using FreeRTOS RTOS. This library simplifies the process of debugging RTOS applications by providing convenient tools for outputting and managing logs. Although the solution is not designed for the enterprise level, it has already proven its effectiveness in various projects, speeding up the development process and improving the quality of the final product. Thanks to the use of FreeRTOS functions, the library can be easily adapted to other microcontrollers by changing the methods of interaction with peripherals.

We will look at the main features of the library, its architecture and integration with FreeRTOS, as well as the advantages of using this solution in your projects. Code examples and detailed explanations will help you easily integrate the library into STM32 projects and use its functionality for effective debugging and monitoring of applications.

Example of output logs

To better understand the operation of the logging library, we will give an example of log output and the corresponding function that generates these logs.

[DEBUG_ALL][0s.922]: Logging inited
[INFO     ][5s.922]: I'm alive every 5 seconds from default task
[INFO     ][10s.922]: I'm alive every 5 seconds from default task
[INFO     ][15s.922]: I'm alive every 5 seconds from default task

Log generation function

/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* init code for USB_DEVICE */
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN StartDefaultTask */
  logging_init();
  LOG(DEBUG_ALL, "Logging inited");
  /* Infinite loop */
  for (;;)
  {
    osDelay(DEFAULT_TASK_DELAY);
    // мигание зеленым светодиодом
    HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);

    LOG(INFO, "I'm alive every 5 seconds from default task");
  }
  /* USER CODE END StartDefaultTask */
}

In this example:

  1. Initializing logging: Challenge logging_init() prepares the library for work.

  2. Logging initialization: LOG(DEBUG_ALL, "Logging inited"); displays a message about the initialization of the logging system.

  3. Infinite loop: Every 5 seconds there is a delay with osDelay(DEFAULT_TASK_DELAY)the LED blinks and an information message about the task status is displayed.

This example demonstrates how easy it is to integrate a logging library into the main application task and receive clear and structured log messages for monitoring system performance.

Logging Library Overview

The logging library for STM32 using FreeRTOS provides a compact and customizable solution for debugging and monitoring applications. The main goal is to provide developers with a simple tool for displaying logs of various levels with the ability to expand functionality as needed.

Functionality

  • Various logging levels: Level support DEBUG_ALL, DEBUG_MIN, INFO, WARNING, ERR allows you to filter messages depending on your debugging needs.

  • Message Buffering: Circular buffers provide efficient memory management and prevent data loss when log volumes are high.

  • Support various output interfaces: The ability to choose between UART and USB interfaces makes the library universal for various projects.

  • Thread safety: The use of mutexes and semaphores FreeRTOS guarantees correct operation in a multi-threaded environment, preventing conflicts when accessing resources simultaneously.

Project structure

The library is organized for maximum ease of integration and modification. The main components are located in the directory liband the test project is in test/test_stm32.

  • lib/

    • logging.h: Header file with definitions, macros and function declarations for logging.

    • logging.c: Implementation of logging functions, including initialization and transmission of logs through the selected interface.

    • logging_usb.h: Extension to support USB logging.

  • test/test_stm32/

    • An example of initialization and use of logging in a real project.

    • Scripts for building and testing a project using Docker and a QEMU virtual machine, providing automated testing of the library.

Usage example

To demonstrate the ease of use of the library, we will give an example of logging from a FreeRTOS task.

#include "logging.h"
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"

// Инициализация логирования
void start_logging_task(void)
{
    logging_init();
    // Создание задачи логирования
    osThreadId_t loggingTask = osThreadNew(logging_thread, "Logger", NULL);
    if (loggingTask == NULL)
    {
        LOG_FATAL("Failed to create logging task\n");
        Error_Handler();
    }
}

// Пример логирования из задачи
void logging_thread(void *arg)
{
    const char *taskName = (const char *)arg;
    for (int i = 0; i < 100; i++)
    {
        LOG(INFO, "Task %s: message %d", taskName, i);
        osDelay(100);
    }
    osThreadExit();
}

// Основная функция приложения
int main(void)
{
    // Инициализация аппаратных компонентов
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_FREERTOS_Init();
    
    // Запуск задачи логирования
    start_logging_task();
    
    // Запуск планировщика FreeRTOS
    osKernelStart();
    
    // Основной цикл не должен достигаться
    while (1)
    {
    }
}

In this example:

  1. Initializing logging: Function logging_init() prepares the library for use by setting up the necessary resources and tasks.

  2. Creating a logging task: Task logging_thread is responsible for writing logs, taking the task name as an argument and periodically writing messages.

  3. Running the scheduler: After initialization and creation of tasks, the FreeRTOS scheduler is started using osKernelStart()which manages tasks.

This example demonstrates how easy it is to integrate a logging library into a project. The library is ready to use and allows you to quickly adapt to the specific requirements of the application.

Architecture and operation of the library in the context of RTOS

The STM32 logging library, integrated with FreeRTOS, is designed to provide efficiency and reliability in a multi-tasking environment.

FreeRTOS integration

Using mutexes and semaphores

For thread safety when writing and reading logs, the library uses FreeRTOS mutexes and semaphores. This ensures that access to shared resources does not lead to conflicts or data corruption when accessed simultaneously from different tasks or interrupts.

  • Mutexes (interface_mutex, logs_mutex):

    • interface_mutex protects access to log output interfaces (such as UART or USB) by ensuring that only one task or interrupt can send data at a time.

    • logs_mutex protects the library's internal data structures, such as log buffers, preventing multiple tasks from changing data at the same time.

  • Semaphores (logs_semaphore_store, logs_semaphore_print):

    • logs_semaphore_store synchronizes the process of writing logs to the buffer, ensuring sequential recording without gaps.

    • logs_semaphore_print controls the output of logs through the selected interface, ensuring correct and orderly output of messages.

#include "logging.h"
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os2.h"
#include "string.h"

char LOGGING_BUF[LOG_BUFFER_SIZE] = "\0";
char LOGGING_ISR_BUF[LOG_BUFFER_SIZE] = "\0";
char INTERFACE_BUFFER[INTERFACE_BUFFER_SIZE] = "\0";

char log_circular_buf[LOG_QUEUE_ROWS][LOG_BUFFER_SIZE] = {0};
uint32_t log_time_buf[LOG_QUEUE_ROWS] = {0};
uint32_t log_time_ms_buf[LOG_QUEUE_ROWS] = {0};
int log_level_buf[LOG_QUEUE_ROWS] = {0};
int logs_tail = 0;
int logs_head = 0;

int log_isr_set = 0;
uint32_t log_isr_time = 0;
uint32_t log_isr_time_ms = 0;
int log_isr_level = 0;

int init_packege_received = 0;

char log_names[ERR + 1][LOG_TYPE_SIZE] = {"DEBUG_ALL", "DEBUG_MIN", "INFO     ", "WARNING  ", "ERROR    "};

osMutexId_t interface_mutex;
osMutexId_t logs_mutex;

/* Definitions for logs semaphore */
osSemaphoreId_t logs_semaphore_store;
osSemaphoreId_t logs_semaphore_print;

typedef StaticTask_t osStaticThreadDef_t;

osThreadId_t loggingTaskHandle;
osThreadAttr_t loggingTask_attributes = {
    .name = "logging",
    .priority = osPriorityBelowNormal
};

Objectives and priorities

The library creates a separate task for processing logs, offloading the main tasks of the application and ensuring timely output of logs without delays.

  • Logging task (loggingTaskHandle):

    This task is responsible for receiving log messages, buffering them, and outputting them through the selected interface. Low task priority ensures that it does not interfere with critical application tasks, ensuring stable logging performance.

typedef StaticTask_t osStaticThreadDef_t;

osThreadId_t loggingTaskHandle;
osThreadAttr_t loggingTask_attributes = {
    .name = "logging",
    .priority = osPriorityBelowNormal
};

Log buffering

The library uses circular buffers to efficiently manage memory and prevent data loss when the volume of logs is high.

char log_circular_buf[LOG_QUEUE_ROWS][LOG_BUFFER_SIZE] = {0};
uint32_t log_time_buf[LOG_QUEUE_ROWS] = {0};
uint32_t log_time_ms_buf[LOG_QUEUE_ROWS] = {0};
int log_level_buf[LOG_QUEUE_ROWS] = {0};
int logs_tail = 0;
int logs_head = 0;

Interrupt handling

Interrupt logging (ISR) requires a special approach. The library provides mechanisms for securely recording logs from ISRs.

int log_isr_set = 0;
uint32_t log_isr_time = 0;
uint32_t log_isr_time_ms = 0;
int log_isr_level = 0;
#define LOG_ISR(level, ...)                                      \
    if (level >= LOGGING_LEVEL)                                  \
    {                                                            \
        snprintf(LOGGING_ISR_BUF, LOG_BUFFER_SIZE, __VA_ARGS__); \
        log_ISR(LOGGING_ISR_BUF, UPTIME_S, UPTIME_MS, level);    \
    }

USB CDC extension for logging

The library provides a built-in extension for logging using a virtual COM port or CDC driver. This greatly simplifies the process of sending logs via USB cable to almost any operating system. To make debugging via USB CDC easier, the extension includes several important features:

Basic Functions of the USB CDC Extension

  1. Waiting for the virtual COM port to open on the PC side

    The microcontroller software waits for the virtual COM port on the PC to open and for the CDC driver to be ready to receive data. This ensures that logs generated in the first milliseconds of the firmware are captured and delivered. Otherwise, due to the delay in initializing the virtual USB COM port, the corresponding logs may be lost.

  2. PC Read Waiting

    If logging volume is high, the logging function will block until space is freed in the circular buffer. This ensures the safety of all logs and prevents their loss even with intensive logging.

// additional include for USB logging
# include "usbd_cdc_if.h"
# include "usb_device.h"

# define INTERFACE_CDC_BUFFER_SIZE 200

extern char CDC_USB_RX_BUF[INTERFACE_CDC_BUFFER_SIZE];
extern char CDC_USB_TX_BUF[INTERFACE_CDC_BUFFER_SIZE];

# define CDC_WAIT_FOR_PC_TO_READ 1

# define INTERFACE_printf(FATAL_FLAG, ...)                                           \
    while (CDC_IsBusy() == USBD_BUSY && CDC_WAIT_FOR_PC_TO_READ == 1)   \
    {                                                                   \
        osThreadYield();                                                \
    }                                                                   \
    CDC_Transmit_FS((uint8_t *)CDC_USB_TX_BUF,                          \
                    snprintf(CDC_USB_TX_BUF, INTERFACE_CDC_BUFFER_SIZE, **VA_ARGS**));

These features work together to provide reliable and efficient USB CDC logging, allowing developers to easily monitor system performance in real time without the risk of data loss.

Task priorities and logging modes

Task priorities

By default, the logging task runs with minimum priority and is activated only if there are no higher priority tasks. This allows the main application load to receive the necessary CPU time, and logging does not interfere with critical operations. For optimal performance of the library, proper use of RTOS functions is necessary, which frees up CPU time for the actual logging.

Logging modes

The library supports several logging modes, providing flexibility and reliability:

  • ISR mode: Designed for logging inside interrupts. This mode allows you to quickly record logs from the ISR, but does not support buffering, which can lead to data loss during intensive logging.

# define LOG_ISR(level, ...)                                      \
    if (level >= LOGGING_LEVEL)                                  \
    {                                                            \
        snprintf(LOGGING_ISR_BUF, LOG_BUFFER_SIZE, **VA_ARGS**); \
        log_ISR(LOGGING_ISR_BUF, UPTIME_S, UPTIME_MS, level);    \
    }
  • Fatal mode: Provides a logging mechanism in case of critical errors where the RTOS cannot continue to operate. In this mode, logging serves as a last resort to display information before rebooting or stopping the system.

# define LOG_FATAL(...) INTERFACE_printf(FATAL_FLAG_SET, **VA_ARGS**)

These modes provide reliable logging in a variety of system operating scenarios, allowing you to preserve critical information even in conditions of limited resources or when major components fail.

Benefits of the logging library

  • Efficiency and minimizing delays: Using RTOS allows you to minimize blocking time and ensure fast data exchange between tasks.

  • Flexibility and extensibility:

    • Support for various interfaces: Possibility to select a log output interface, for example, UART or USB.

    • Various logging levels: Level support DEBUG_ALL, DEBUG_MIN, INFO, WARNING, ERROR for flexible configuration of information output.

  • Ease of use: Easy to integrate the library into existing projects on STM32 with FreeRTOS and easy to configure logging parameters through configuration files.

  • Thread safety and reliability: The use of mutexes and semaphores ensures correct operation of the library in a multi-threaded environment, preventing conflicts when accessing resources simultaneously.

  • Extensibility via USB CDC: USB CDC logging capability makes it easy to send logs to a PC without the need for additional physical interfaces.

Conclusion

The STM32 logging library using FreeRTOS is a compact, flexible, and easy-to-integrate solution for debugging and monitoring embedded systems. Its ease of use and expandability make it an ideal choice for both hobby developers and professionals looking to improve their workflow efficiency.

Additional materials

  • Source code links: Project repository for interested readers.

  • Recommendations for use: Tips for optimizing and configuring the library for various types of projects on the STM32.

Future publications

In the next article we will take a detailed look at the process of testing this logging library. We will use Docker for automated assembly and QEMU for microcontroller emulation, which will allow us to test logging via USART without the need for physical hardware. This approach provides test flexibility and repeatability, allowing potential problems to be quickly identified and resolved during development.

Using CMSIS-RTOS2

The logging library uses the library cmsis_os2which is an RTOS (Real-Time Operating System) abstraction based on CMSIS (Cortex Microcontroller Software Interface Standard) standards. Details can be found in CMSIS-RTOS2 documentation.

cmsis_os2 provides a single interface across different RTOSes, facilitating code portability between different real-time operating systems. This allows developers to use common APIs to manage tasks, mutexes, semaphores, and other synchronization facilities, regardless of the chosen RTOS. In the context of our logging library cmsis_os2 provides reliable control of multitasking and synchronization of access to resources, which is critical for the correct functioning of the logging system in a multi-threaded environment.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *