Введение

Встроенные системы на базе микроконтроллеров STM32 широко применяются в различных областях — от бытовой электроники до промышленного оборудования и IoT-устройств. Одной из ключевых задач при разработке таких систем является отладка и мониторинг работы приложений. Эффективное логирование существенно облегчает эти процессы, позволяя разработчикам быстро выявлять и устранять ошибки, а также анализировать поведение системы в реальном времени.

Цель данной статьи — познакомить вас с библиотекой логирования для STM32, разработанной с использованием RTOS FreeRTOS. Эта библиотека упрощает процесс отладки RTOS-приложений, предоставляя удобные инструменты для вывода и управления логами. Хотя решение не рассчитано на корпоративный уровень, оно уже доказало свою эффективность в различных проектах, ускоряя процесс разработки и повышая качество конечного продукта. Благодаря использованию функций FreeRTOS, библиотеку легко адаптировать под другие микроконтроллеры, изменив методы взаимодействия с периферией.

Мы рассмотрим основные возможности библиотеки, её архитектуру и интеграцию с FreeRTOS, а также преимущества использования данного решения в ваших проектах. Примеры кода и подробные объяснения помогут легко интегрировать библиотеку в проекты на STM32 и использовать её функционал для эффективной отладки и мониторинга приложений.

Пример выводимых логов

Для лучшего понимания работы библиотеки логирования приведем пример вывода логов и соответствующую функцию, генерирующую эти логи.

[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

Функция генерации логов

/* 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 */
}

В данном примере:

  1. Инициализация логирования: Вызов logging_init() подготавливает библиотеку к работе.

  2. Логирование инициализации: LOG(DEBUG_ALL, "Logging inited"); выводит сообщение о инициализации системы логирования.

  3. Бесконечный цикл: Каждые 5 секунд происходит задержка с помощью osDelay(DEFAULT_TASK_DELAY), мигание светодиода и вывод информационного сообщения о состоянии задачи.

Этот пример демонстрирует, как легко интегрировать библиотеку логирования в основную задачу приложения и получать понятные и структурированные лог-сообщения для мониторинга работы системы.

Обзор библиотеки логирования

Библиотека логирования для STM32 с использованием FreeRTOS представляет собой компактное и настраиваемое решение для отладки и мониторинга приложений. Основная цель — предоставить разработчикам простой инструмент для вывода логов различных уровней с возможностью расширения функционала по необходимости.

Функциональные возможности

  • Различные уровни логирования: Поддержка уровней DEBUG_ALL, DEBUG_MIN, INFO, WARNING, ERR позволяет фильтровать сообщения в зависимости от потребностей отладки.

  • Буферизация сообщений: Циклические буферы обеспечивают эффективное управление памятью и предотвращают потерю данных при высоком объёме логов.

  • Поддержка различных интерфейсов вывода: Возможность выбора между UART и USB интерфейсами делает библиотеку универсальной для различных проектов.

  • Потокобезопасность: Использование мьютексов и семафоров FreeRTOS гарантирует корректную работу в многопоточной среде, предотвращая конфликты при одновременном доступе к ресурсам.

Структура проекта

Библиотека организована для максимальной простоты интеграции и модификации. Основные компоненты находятся в директории lib, а тестовый проект — в test/test_stm32.

  • lib/

    • logging.h: Заголовочный файл с определениями, макросами и декларациями функций для логирования.

    • logging.c: Реализация функций логирования, включая инициализацию и передачу логов через выбранный интерфейс.

    • logging_usb.h: Расширение для поддержки логирования через USB.

  • test/test_stm32/

    • Пример инициализации и использования логирования в реальном проекте.

    • Скрипты для сборки и тестирования проекта с использованием Docker и QEMU виртуальной машины, обеспечивающие автоматизированное тестирование библиотеки.

Пример использования

Для демонстрации простоты использования библиотеки приведём пример записи логов из задачи FreeRTOS.

#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)
    {
    }
}

В этом примере:

  1. Инициализация логирования: Функция logging_init() подготавливает библиотеку к работе, настраивая необходимые ресурсы и задачи.

  2. Создание задачи логирования: Задача logging_thread отвечает за запись логов, принимая имя задачи в качестве аргумента и периодически записывая сообщения.

  3. Запуск планировщика: После инициализации и создания задач запускается планировщик FreeRTOS с помощью osKernelStart(), который управляет задачами.

Этот пример демонстрирует, насколько легко интегрировать библиотеку логирования в проект. Библиотека готова к использованию и позволяет быстро адаптироваться под специфические требования приложения.

Архитектура и работа библиотеки в контексте RTOS

Библиотека логирования для STM32, интегрированная с FreeRTOS, обладает продуманной архитектурой, обеспечивающей эффективность и надежность в многозадачной среде.

Интеграция с FreeRTOS

Использование мьютексов и семафоров

Для потокобезопасности при записи и чтении логов библиотека использует мьютексы и семафоры FreeRTOS. Это гарантирует, что доступ к общим ресурсам не приведет к конфликтам или повреждению данных при одновременном обращении из разных задач или прерываний.

  • Мьютексы (interface_mutex, logs_mutex):

    • interface_mutex защищает доступ к интерфейсам вывода логов (например, UART или USB), обеспечивая, что только одна задача или прерывание может одновременно отправлять данные.

    • logs_mutex защищает внутренние структуры данных библиотеки, такие как буферы логов, предотвращая одновременное изменение данных несколькими задачами.

  • Семафоры (logs_semaphore_store, logs_semaphore_print):

    • logs_semaphore_store синхронизирует процесс записи логов в буфер, обеспечивая последовательную запись без пропусков.

    • logs_semaphore_print управляет выводом логов через выбранный интерфейс, гарантируя корректный и упорядоченный вывод сообщений.

#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
};

Задачи и приоритеты

Библиотека создает отдельную задачу для обработки логов, разгружая основные задачи приложения и обеспечивая своевременный вывод логов без задержек.

  • Задача логирования (loggingTaskHandle):

    • Имя задачи: "logging"

    • Приоритет: osPriorityBelowNormal

    Эта задача отвечает за прием логовых сообщений, их буферизацию и вывод через выбранный интерфейс. Низкий приоритет задачи гарантирует, что она не будет мешать критическим задачам приложения, обеспечивая стабильную работу логирования.

typedef StaticTask_t osStaticThreadDef_t;

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

Буферизация логов

Библиотека использует циклические буферы для эффективного управления памятью и предотвращения потери данных при высоком объёме логов.

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;

Обработка прерываний

Логирование из прерываний (ISR) требует особого подхода. Библиотека предоставляет механизмы для безопасной записи логов из ISR.

  • Переменные для ISR:

    • log_isr_set: Флаг наличия новых логовых сообщений из ISR.

    • log_isr_time, log_isr_time_ms: Время создания логовых сообщений из ISR.

    • log_isr_level: Уровень логирования для сообщений из ISR.

int log_isr_set = 0;
uint32_t log_isr_time = 0;
uint32_t log_isr_time_ms = 0;
int log_isr_level = 0;
  • Функции логирования из ISR:

#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 для логирования

Библиотека предоставляет встроенное расширение для логирования с использованием виртуального COM-порта или CDC-драйвера. Это значительно упрощает процесс отправки логов через USB-кабель на практически любую операционную систему. Для облегчения отладки через USB CDC в расширение включены несколько важных функций:

Основные функции расширения USB CDC

  1. Ожидание открытия виртуального COM-порта на стороне ПК

    Программное обеспечение микроконтроллера ожидает открытия виртуального COM-порта на ПК, а также готовности CDC-драйвера к приему данных. Это гарантирует, что логи, генерируемые в первые миллисекунды работы прошивки, будут захвачены и доставлены. В противном случае, из-за задержки инициализации виртуального COM-порта USB, соответствующие логи могут быть потеряны.

  2. Ожидание чтения ПК

    При высоком объёме логирования функция логирования будет блокироваться до тех пор, пока в циклическом буфере не освободится место. Это обеспечивает сохранность всех логов и предотвращает их потерю даже при интенсивном журналировании.

// 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**));

Эти функции совместно обеспечивают надёжное и эффективное логирование через USB CDC, позволяя разработчикам легко отслеживать работу системы в реальном времени без риска потери данных.

Приоритеты задач и режимы логирования

Приоритеты задач

По умолчанию, задача логирования выполняется с минимальным приоритетом и активируется только в случае отсутствия более приоритетных задач. Это позволяет основной нагрузке приложения получать необходимое CPU время, а логированию не мешать критическим операциям. Для оптимальной работы библиотеки необходимо правильное использование функций RTOS, что позволяет освободить время процессора для фактического логирования.

Режимы логирования

Библиотека поддерживает несколько режимов логирования, обеспечивающих гибкость и надежность:

  • ISR-режим: Предназначен для логирования внутри прерываний. Этот режим позволяет быстро записывать логи из ISR, однако не поддерживает буферизацию, что может привести к потере данных при интенсивном журналировании.

# 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);    \
    }
  • Фатальный режим: Предоставляет механизм логирования в случае критических ошибок, когда RTOS не может продолжать работу. В этом режиме логирование служит последним средством для вывода информации перед перезагрузкой или остановкой системы.

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

Эти режимы обеспечивают надежное логирование в различных сценариях эксплуатации системы, позволяя сохранять критическую информацию даже в условиях ограниченных ресурсов или при отказе основных компонентов.

Преимущества библиотеки логирования

  • Эффективность и минимизация задержек: Использование RTOS позволяет минимизировать время блокировок и обеспечить быстрый обмен данными между задачами.

  • Гибкость и расширяемость:

    • Поддержка различных интерфейсов: Возможность выбора интерфейса вывода логов, например, UART или USB.

    • Различные уровни логирования: Поддержка уровней DEBUG_ALL, DEBUG_MIN, INFO, WARNING, ERROR для гибкой настройки вывода информации.

  • Удобство использования: Простота интеграции библиотеки в существующие проекты на STM32 с FreeRTOS и легкость настройки параметров логирования через конфигурационные файлы.

  • Потокобезопасность и надежность: Использование мьютексов и семафоров обеспечивает корректную работу библиотеки в многопоточной среде, предотвращая конфликты при одновременном доступе к ресурсам.

  • Расширяемость через USB CDC: Возможность логирования через USB CDC позволяет легко отправлять логи на ПК без необходимости наличия дополнительных физических интерфейсов.

Заключение

Библиотека логирования для STM32 с использованием FreeRTOS — это компактное, гибкое и легко интегрируемое решение для отладки и мониторинга встроенных систем. Её простота в использовании и возможность расширения делают её идеальным выбором как для хобби-разработчиков, так и для профессионалов, стремящихся повысить эффективность рабочего процесса.

Дополнительные материалы

  • Ссылки на исходный код: Репозиторий проекта для заинтересованных читателей.

  • Рекомендации по использованию: Советы по оптимизации и настройке библиотеки для различных типов проектов на STM32.

Будущие публикации

В ближайшей следующей статье мы подробно рассмотрим процесс тестирования данной библиотеки логирования. Будем использовать Docker для автоматизированной сборки и QEMU для эмуляции микроконтроллера, что позволит протестировать логирование через USART без необходимости наличия физического оборудования. Этот подход обеспечивает гибкость и повторяемость тестов, позволяя быстро выявлять и устранять возможные проблемы в процессе разработки.

Использование CMSIS-RTOS2

В библиотеке логирования используется библиотека cmsis_os2, которая представляет собой абстракцию RTOS (Real-Time Operating System) на основе стандартов CMSIS (Cortex Microcontroller Software Interface Standard). Подробности можно найти в документации CMSIS-RTOS2.

cmsis_os2 предоставляет единый интерфейс для различных RTOS, упрощая переносимость кода между разными операционными системами реального времени. Это позволяет разработчикам использовать общие API для управления задачами, мьютексами, семафорами и другими средствами синхронизации, независимо от выбранного RTOS. В контексте нашей библиотеки логирования cmsis_os2 обеспечивает надежное управление многозадачностью и синхронизацией доступа к ресурсам, что является критически важным для корректного функционирования системы логирования в многопоточной среде.

Комментарии (5)


  1. 4chemist
    03.11.2024 22:18

    На ESP32 беспроводно смотрю логи через телнет https://github.com/h2zero/esp-libtelnet - Remote console over telnet for esp32 devices. Можете там почерпнуть идею о перенаправлении stdout и stderr.


  1. HardWrMan
    03.11.2024 22:18

    Когда надо отлаживать что-то что нельзя тормозить то использую свободные ноги (или навешиваю расширитель пинов на регистре или ещё какой вариант), которыми дёргаю одной командой в контрольных точках. Затем осциллографом или хорошим LA контролирую изменения на этих пинах. Наглядно видно что куда зачем и т.д. Пины реюзабельны (добавление контекста в виде второго пина, например), так что всё зависит только от воображения. Математику отлаживаю классически, через SWD и OpenOCD. Вывод в UART обычно пишу свой, чтобы был максимально маленьким и использовал аппаратуру (прерывания, DMA). Но он бывает нужен реально редко.


    1. AleksandrVi Автор
      03.11.2024 22:18

      Хороший метод! когда процессы быстрые, однотипные и частые. Логи скорее для сложной логики внутри mcu


      1. HardWrMan
        03.11.2024 22:18

        Текстовые логи они нужны, но у них задача не для отладки всего а в основном просто передать текущее состояние сложных логических машин. У меня есть проект с RTOS, который с ключом DEBUG собирается с UART, куда несколько задач гадят всяким в виде "[Задача]: Сообщение". Чтобы понять что происходит и как они реагируют на внешние раздражители - хватает. Но в боевом режиме эта отладка - это лишняя нагрузка на ядро, поэтому и ключ присутствует. А одна команда "дёрнуть ногой" позволяет отслеживать моменты с затратами всего десятка наносекунд.


  1. andreykorol
    03.11.2024 22:18

    SEGGER's Real Time Transfer (RTT) часто использую