STM32_ExtendedLibs/MyLibs/Inc/trace.h
Razvalyaev 51dc03fcbc Переструктурирование:
- MyLibs - максимально платформонезависимые библиотеки (кроме разве что RTT)
- RTT
- STM32_General - библиотеки для периферии stm32
2025-10-21 05:08:27 +03:00

591 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
**************************************************************************
* @file trace.h
* @brief Заголочный файл для работы с трассировкой.
**************************************************************************
* @addtogroup TRACE Trace defines
* @ingroup MYLIBS_DEFINES
* @brief Дефайны для работы с трассировкой
*************************************************************************/
#ifndef __TRACE_H_
#define __TRACE_H_
#include "mylibs_defs.h"
#include <string.h>
/**
* @addtogroup TRACE_SERIAL Serial trace defines
* @ingroup TRACE
* @brief Дефайны для работы с serial трассировкой (SWO, RTT)
* @details В зависимости от настроек определяется дефайн @ref my_printf() и @ref log_printf() для работы с трассировкой:
- @ref SERIAL_TRACE_ENABLE - Если трассировка отключена, то все дефайны определяются как 'ничего'
и на производительность кода не влияют
- @ref RTT_TRACE_ENABLE - для RTT это будет вызов функции SEGGER_RTT_printf()
Предварительно надо подключить библиотеку SEGGER RTT (SEGGER_RTT.h) и вызвать функцию SEGGER_RTT_Init()
- @ref SWO_TRACE_ENABLE для SWO это будет просто printf()
Предварительно надо подключить библиотеку STDOUT и retarget под ITM:
@verbatim
Manage Run-Time Environment -> Compiler -> I/O -> STDOUT -> ITM
@endverbatim
Для SWO также надо включить трассировку:
@verbatim
Options For Target -> Debug -> Debugger Settings
@endverbatim
В вкладке Debug:
- Port = SW
В вкладке Trace:
- Указать Core Clock
- Выставить Trace Port = SWO
- ITM - выбрать нужный порт (для Keil нулевой порт)
* @{
*
* @def my_printf(...)
* @brief Универсальный макрос для вывода трассировки
* @details Варианты реализации:
* - RTT_TRACE_ENABLE `SEGGER_RTT_printf(0, ...)`
* - SWO_TRACE_ENABLE - `printf(...)`
* - NO_TRACE - пустой макрос
*
* @def log_printf(TAG, fmt, ...)
* @brief Макрос логирования с поддержкой уровней @ref LOG_LEVEL
* @param TAG Тэг лога
* @param fmt, ... Форматируемая строка
* @details Варианты реализации:
* - @ref LOG_LEVEL == 0 - логирование отключено (макрос пустой)
* - @ref LOG_LEVEL == 1 - выводится время @ref local_time и TAG
* @code
[123] [ADC] Measure Done
[456] [ADC] Measure Err
* @endcode
* - @ref LOG_LEVEL >= 2 - выводится время, TAG, имя файла и номер строки
* @code
[123] [ADC] (../Core/Src/adc.c:75) Measure Done
[456] [ADC] (../Core/Src/adc.c:80) Measure Err
* @endcode
*/
#ifdef SERIAL_TRACE_ENABLE
#if defined(RTT_TRACE_ENABLE)
#undef SWO_TRACE_ENABLE
#include "SEGGER_RTT.h"
#define my_printf(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#elif defined(SWO_TRACE_ENABLE)
#undef RTT_TRACE_ENABLE
#define my_printf(...) printf(__VA_ARGS__)
#else // NO_TRACE
#define my_printf(...)
#warning No trace is selected. Serial debug wont work.
#endif // RTT_TRACE_ENABLE/SWO_TRACE_ENABLE/NO_TRACE
#else //SERIAL_TRACE_ENABLE
#define my_printf(...)
#undef RTT_TRACE_ENABLE
#undef SWO_TRACE_ENABLE
#endif //SERIAL_TRACE_ENABLE
#ifndef local_time
#define local_time() HAL_GetTick() ///< Локальное время
#endif
#ifndef LOG_LEVEL
#define LOG_LEVEL 1 ///< @brief Уровень логирования (по умолчанию == 1)
#endif
#if LOG_LEVEL == 0 // лог отключен
#define \
log_printf(TAG, fmt, ...)
#elif LOG_LEVEL == 1 // только тэг
#define log_printf(TAG, fmt, ...) \
my_printf("\n[%lu] [%s] " fmt, \
(unsigned long)local_time(), TAG, ##__VA_ARGS__)
#elif LOG_LEVEL >= 2 // всё
#define log_printf(TAG, fmt, ...) \
my_printf("\n[%lu] [%s] (%s:%d) " fmt, \
(unsigned long)local_time(), TAG, __FILE__, __LINE__, ##__VA_ARGS__)
#endif
/** TRACE_SERIAL
* @}
*/
/**
* @addtogroup TRACE_GPIO GPIO trace defines
* @ingroup TRACE
* @brief Дефайны для работы с GPIO трассировкой
* @details Определяется дефайны для работы с GPIO трассировкой:
- TRACE_GPIO_RESET() - для сброса ножки GPIO (через BSRR)
- TRACE_GPIO_SET() - для выставления ножки GPIO (через BSRR)
- Если трассировка @ref GPIO_TRACE_ENABLE отключена, то все дефайны определяются как 'ничего'
и на производительность кода не влияют
* @{
*
* @def TRACE_GPIO_RESET(_gpio_, _pin_)
* @brief Сбросить указанную ножку GPIO
* @param _gpio_ Указатель на структуру GPIO (напр. GPIOA)
* @param _pin_ Номер ножки (напр. GPIO_PIN_0)
* @details Варианты реализации:
* - GPIO_TRACE_ENABLE не определён - макрос пустой
* - GPIO_TRACE_ENABLE определён - устанавливает бит сброса через BSRR ((_pin_)<<16)
*
* @def TRACE_GPIO_SET(_gpio_, _pin_)
* @brief Установить указанную ножку GPIO
* @param _gpio_ Указатель на структуру GPIO (например GPIOA)
* @param _pin_ Номер ножки (напр. GPIO_PIN_0)
* @details Варианты реализации:
* - GPIO_TRACE_ENABLE не определён - макрос пустой
* - GPIO_TRACE_ENABLE определён - устанавливает бит установки через BSRR (_pin_)
*/
#ifndef GPIO_TRACE_ENABLE
#define TRACE_GPIO_SET(_gpio_,_pin_)
#define TRACE_GPIO_RESET(_gpio_,_pin_)
#else
#define TRACE_GPIO_SET(_gpio_,_pin_) (_gpio_)->BSRR = (((_pin_)))
#define TRACE_GPIO_RESET(_gpio_,_pin_) (_gpio_)->BSRR = ((_pin_)<<16)
#endif //GPIO_TRACE_ENABLE
/** TRACE_GPIO
* @}
*/
#if defined(HAL_MODULE_ENABLED) && defined(RTT_TRACE_ENABLE)
/**
* @addtogroup TRACE_RTT_FLASH Flash RTT Buffer
* @ingroup TRACE
* @brief Макросы и функции для сохранения/чтения RTT буфера в Flash
* @details Модуль позволяет сохранять данные RTT буфера во Flash и читать их обратно по тегам.
* Теги работают следующим образом:
* - Базовый тег (младший байт = 0): модуль сам выбирает первый свободный слот во Flash;
* новые записи получают автоинкрементированный младший байт тега (от 0x00 до 0xFF).
* - Конкретный тег (младший байт != 0): запись или чтение происходит строго с указанным тегом;
* если слот с таким тегом уже занят, запись не выполняется.
* - Автоинкремент позволяет хранить несколько последовательных записей в пределах одного базового тега,
* без необходимости вручную отслеживать адреса Flash или позиции буферов.
*
* Параметры:
* - @ref RTT_FLASH_BUFFER_SIZE - Размер буфера RTT в Flash
* - @ref RTT_FLASH_SECTOR - Сектор FLASH куда положится RTT буфер
* - @ref RTT_FLASH_SECTOR_START - Начало сектора RTT_FLASH_SECTOR
* - @ref RTT_FLASH_SECTOR_END - Конец сектора RTT_FLASH_SECTOR
*
* @{
*/
/**
* @brief Структура RTT, которая будет положена в Flash
*/
typedef struct {
uint32_t tag; ///< Уникальный идентификатор буфера
uint32_t size; ///< Размер данных
char data[RTT_FLASH_BUFFER_SIZE]; ///< Буфер RTT
} RTT_FlashHeader_t;
/**
* @brief Подготовка Flash к записи
* @details Сбрасывает ошибки Flash и ожидает готовности перед записью
*/
__STATIC_FORCEINLINE void RTT_FlashPrepare(void)
{
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGSERR | FLASH_FLAG_WRPERR | FLASH_FLAG_OPERR);
while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) {
__NOP();
}
}
/**
* @brief Сохраняет последние символы RTT-буфера в Flash по тегу
* @param tag Базовый или конкретный идентификатор буфера.
* @param tail_size Количество последних символов RTT для копирования
* @param buf_num Указатель на переменную в которую запишется номер буфера для конкретного тега
* @return >=0 — номер буфера (тег) для записи, <0 — ошибка (нет места, тег уже занят, ошибка записи в флеш)
*
* @details Автоматически копирует последние tail_size символов из RTT-буфера
* и записывает их во Flash.
* Тег может быть базовым или конкретным:
* - Если базовый (младший байт == 0) — будет выбран первый свободный слот с автоинкрементом.
* Автоинкремент формируется в пределах от 0x1 до 0xFF
* - Если конкретный (младший байт != 0) — запись выполняется только с этим тегом, иначе ошибка.
*
*/
__STATIC_FORCEINLINE int RTT_SaveToFlash(uint32_t tag, uint32_t tail_size)
{
if (tag == 0xFFFFFFFF)
return -1; // Неверный тег
SEGGER_RTT_BUFFER_UP *up = &_SEGGER_RTT.aUp[0];
unsigned buf_size = up->SizeOfBuffer;
unsigned wr = up->WrOff;
// Ограничиваем по размеру буфера RTT и RTT_FLASH_BUFFER_SIZE
unsigned n = (tail_size > buf_size) ? buf_size : tail_size;
if (n > RTT_FLASH_BUFFER_SIZE)
n = RTT_FLASH_BUFFER_SIZE;
uint32_t addr = RTT_FLASH_SECTOR_START;
RTT_FlashHeader_t *flash_hdr = NULL;
uint32_t base_tag = tag & 0xFFFFFF00;
uint32_t next_tag = (tag & 0xFF) == 0 ? tag + 1 : tag;
// Ищем первый свободный слот, параллельно автоинкрементируем тег
while ((addr + sizeof(RTT_FlashHeader_t)) <= RTT_FLASH_SECTOR_END)
{
flash_hdr = (RTT_FlashHeader_t *)addr;
if (flash_hdr->tag == 0xFFFFFFFF)
break; // Нашли свободное место
if((flash_hdr->tag & 0xFFFFFF00) == base_tag) // выбраный тег
{
if ((tag & 0xFF) == 0) // если он базовый - ищем последний
next_tag = flash_hdr->tag + 1; // автоинкремент
else
if(flash_hdr->tag == tag) // если он конкретный и уже существует - то ошибка
return -1; // конкретный тег уже занят
}
if(next_tag - tag > 0xFF)
return -1; // автоинкремент слишком большой
addr += sizeof(RTT_FlashHeader_t);
}
if ((addr + sizeof(RTT_FlashHeader_t)) > RTT_FLASH_SECTOR_END)
return -1; // Нет свободного места
// Копируем последние n символов из RTT
char temp[RTT_FLASH_BUFFER_SIZE];
unsigned valid_count = 0;
for (unsigned i = 0; i < n; i++)
{
unsigned idx = (wr + buf_size - n + i) % buf_size;
char c = up->pBuffer[idx];
if (c != 0)
temp[valid_count++] = c;
}
RTT_FlashPrepare();
// Формируем структуру в RAM
RTT_FlashHeader_t flash_data;
flash_data.tag = next_tag;
flash_data.size = valid_count;
memcpy(flash_data.data, temp, valid_count);
// Записываем структуру во Flash (по 4 байта)
const uint32_t *p = (const uint32_t *)&flash_data;
for (unsigned i = 0; i < sizeof(RTT_FlashHeader_t) / 4; i++)
{
if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i * 4, p[i]) != HAL_OK)
return -1;
}
HAL_FLASH_Lock();
__DSB();
__ISB();
return (int)(next_tag&0xFF);
}
/**
* @brief Читает последние символы RTT-буфера из Flash по тегу
* @param tag Базовый или конкретный идентификатор буфера.
* @param Buffer Буфер назначения для копирования данных
* @param tail_size Количество последних символов, которые нужно прочитать
* @param read_size Количество считанных символов
* @return >=0 — номер буфера (тег) для записи, <0 — ошибка (тег не найден или структура повреждена)
*
* @details Копирует последние tail_size символов из найденной записи Flash в Buffer.
* Тег может быть базовым или конкретным:
* - Если базовый (младший байт == 0) — будет прочитана последняя запись из группы.
* - Если конкретный (младший байт != 0) — прочитывается именно эта запись.
*/
__STATIC_FORCEINLINE int RTT_ReadFromFlash(uint32_t tag, char *Buffer, uint32_t tail_size, uint32_t *read_size)
{
if (!Buffer || tail_size == 0)
return -1; // Неверные параметры
if (tag == 0xFFFFFFFF)
return -1; // Недопустимый тег
uint32_t addr = RTT_FLASH_SECTOR_START;
RTT_FlashHeader_t *flash_hdr = NULL;
RTT_FlashHeader_t *target_hdr = NULL;
uint32_t base_tag = tag & 0xFFFFFF00;
// Поиск записи по тегу
while ((addr + sizeof(RTT_FlashHeader_t)) <= RTT_FLASH_SECTOR_END)
{
flash_hdr = (RTT_FlashHeader_t *)addr;
if (flash_hdr->tag == 0xFFFFFFFF)
break; // Достигнут конец записанных структур
// выбраный тег
if((flash_hdr->tag & 0xFFFFFF00) == base_tag)
{
if ((tag & 0xFF) == 0) // если он базовый - ищем последний
target_hdr = flash_hdr; // сохраняем последний в группе
else
if(flash_hdr->tag == tag) // если он конкретный и найден - берем его
{
target_hdr = flash_hdr;
break; // конкретный тег найден
}
}
addr += sizeof(RTT_FlashHeader_t);
}
if (!target_hdr) return -1; // Тег не найден
// Проверка корректности размера
if (target_hdr->size > RTT_FLASH_BUFFER_SIZE)
return -1; // Повреждённая запись
// Определяем количество читаемых символов
uint32_t n = (tail_size > target_hdr->size) ? target_hdr->size : tail_size;
// Начальная позиция для чтения последних tail_size символов
uint32_t start = target_hdr->size - n;
// Копируем данные из Flash в RAM
memcpy(Buffer, &target_hdr->data[start], n);
if(read_size != NULL)
{
*read_size = n;
}
__DSB();
__ISB();
return (int)(target_hdr->tag & 0xFF);
}
/**
* @brief Стирание сектора Flash с RTT-буфером
*/
__STATIC_FORCEINLINE int RTT_EraseFlash(void)
{
FLASH_EraseInitTypeDef eraseInit;
uint32_t pageError = 0;
RTT_FlashPrepare();
eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
eraseInit.Sector = RTT_FLASH_SECTOR;
eraseInit.NbSectors = 1;
if (HAL_FLASHEx_Erase(&eraseInit, &pageError) != HAL_OK)
{
return -1;
}
return 0;
HAL_FLASH_Lock();
}
/** TRACE_RTT_FLASH
* @}
*/
#else // HAL_MODULE_ENABLED && RTT_TRACE_ENABLE
#define RTT_FlashPrepare(...)
#define RTT_EraseFlash(...) 0
#define RTT_SaveToFlash(...) 0
#define RTT_ReadFromFlash(...) 0
#endif // HAL_MODULE_ENABLED && RTT_TRACE_ENABLE
/**
* @addtogroup TRACE_HARDFAULT Hardfault trace defines
* @ingroup TRACE
* @brief Модуль трассировки HardFault с возможностью сохранения RTT буфера во Flash
* @details
* Этот модуль позволяет сохранять контекст процессора и последние символы RTT буфера при возникновении HardFault.
*
* Механизм работы:
* - При срабатывании HardFault вызывается HF_HandleFault(), который:
* 1. Получает указатель на стек, где произошёл HardFault (MSP или PSP).
* 2. Выводит значения регистров R0-R3, R12, LR, PC, PSR и системных регистров SCB.
* 3. Формирует строку с регистрами и копирует последние символы RTT буфера.
* 4. Сохраняет данные во Flash с базовым тегом HF_RTT_TAG_BASE.
* - Для восстановления последнего HardFault используется HF_CheckRecovered(), который:
* 1. Читает запись во Flash по базовому тегу.
* 2. Выводит сохранённый RTT буфер и контекст регистров.
* 3. Опционально стирает Flash после восстановления.
*
* Параметры:
* - @ref HARDFAULT_SERIAL_TRACE - Включить обработку и serial трассировку Hardfault
* Если отключена то вставляются заглушки, никак не влияющие на параметры и остальную программу
* - @ref HF_RTT_TAG_BASE - Базовый тег RTT Flash для HardFault
* - @ref HF_RTT_TAIL_SIZE - Размер буфера RTT, который сохранится при Hardfault
* - @ref HF_STACK_DUMP_WORDS - Сколько слов стека будет проанализировано во время Hardfault
* - @ref HF_FLASH_ADDR - Адрес FLASH куда положится RTT буфер
* - @ref HF_RAM_END - Конец RAM памяти (чтобы во время анализа стека не выйти за пределы)
*
@code
void Hardfault()
{
HF_HandleFault();
NVIC_SystemReset();
}
int main()
{
if(HF_CheckRecovered(0))
{
//set hardfault error
RTT_EraseFlash(); // erase rtt flash after message readed
}
}
@endcode
* @{
*/
#if defined(HAL_MODULE_ENABLED) && defined(HARDFAULT_SERIAL_TRACE)
#ifndef HF_RTT_TAIL_SIZE
#define HF_RTT_TAIL_SIZE RTT_FLASH_BUFFER_SIZE ///< Размер буфера RTT, который сохранится при Hardfault
#endif
/**
* @brief Контекст стек-фрейма процессора при HardFault
* @details Сохраняет регистры R0-R3, R12, LR, PC, PSR для последующего анализа.
*/
typedef struct {
uint32_t r0; ///< Регистр R0
uint32_t r1; ///< Регистр R1
uint32_t r2; ///< Регистр R2
uint32_t r3; ///< Регистр R3
uint32_t r12; ///< Регистр R12
uint32_t lr; ///< Link Register
uint32_t pc; ///< Program Counter
uint32_t psr; ///< Program Status Register
} HF_StackFrame_t;
/**
* @brief Проверка и вывод последнего HardFault-трейса из Flash
* @details
* Функция ищет последнюю запись HardFault по базовому тегу HF_RTT_TAG_BASE
* и выводит её содержимое в консоль. После успешного вывода Flash можно опционально очистить.
*
* @return int
* - 1 — данные HardFault найдены и выведены
* - 0 — данные отсутствуют или тег не найден
*
* @note Вызов рекомендуется при инициализации приложения для анализа предыдущего сбоя.
*/
__STATIC_FORCEINLINE int HF_CheckRecovered(int erase)
{
char buffer[RTT_FLASH_BUFFER_SIZE];
uint32_t read_size = 0;
int n_hardfault = RTT_ReadFromFlash(HF_RTT_TAG_BASE, buffer, HF_RTT_TAIL_SIZE, &read_size);
if (n_hardfault > 0)
{
my_printf("\n--- Recovered HardFault RTT buffer #%u ---\n", n_hardfault);
for (int i = 0; i < read_size; i++)
{
char c = buffer[i];
if (c == 0 || c == (char)0xFF) break;
my_printf("%c", c);
}
if(erase)
RTT_EraseFlash();
my_printf("\n--------- HardFault Dump End ---------\n");
return 1;
}
return 0;
}
static HF_StackFrame_t *stack_frame;
static uint32_t stack_dump[HF_STACK_DUMP_WORDS];
static void *ret_adr[10] = {0};
/**
* @brief Обработчик HardFault
* @details
* Вызывается из прерывания HardFault или в любом месте где понятно что ошибка критическая.
* Последовательно выполняет:
* 1. Определяет активный стек (MSP или PSP) на момент сбоя.
* 2. Сохраняет значения регистров R0-R3, R12, LR, PC, PSR.
* 3. Выводит системные регистры CFSR, HFSR, DFSR, AFSR, MMFAR, BFAR.
* 4. Формирует stack trace с 3 уровнями возврата.
* 5. Копирует последние символы RTT буфера.
* 6. Сохраняет все данные во Flash через RTT_SaveToFlash с базовым тегом HF_RTT_TAG_BASE.
*
* @note Функция защищена, так как вызывается в контексте сбоя — минимизирует использование вызовов HAL.
*/
__STATIC_FORCEINLINE void HF_HandleFault(void)
{
// Получаем указатель на стек, где произошёл HardFault
__ASM volatile(
"TST lr, #4 \n"
"ITE EQ \n"
"MRSEQ %[ptr], MSP\n"
"MRSNE %[ptr], PSP\n"
: [ptr] "=r"(stack_frame)
);
my_printf("\n===== HardFault occurred! =====\n");
my_printf("R0 = 0x%08X\n", stack_frame->r0);
my_printf("R1 = 0x%08X\n", stack_frame->r1);
my_printf("R2 = 0x%08X\n", stack_frame->r2);
my_printf("R3 = 0x%08X\n", stack_frame->r3);
my_printf("R12 = 0x%08X\n", stack_frame->r12);
my_printf("LR = 0x%08X\n", stack_frame->lr);
my_printf("PC = 0x%08X\n", stack_frame->pc);
my_printf("PSR = 0x%08X\n", stack_frame->psr);
my_printf("CFSR = 0x%08X\n", SCB->CFSR);
my_printf("HFSR = 0x%08X\n", SCB->HFSR);
my_printf("DFSR = 0x%08X\n", SCB->DFSR);
my_printf("AFSR = 0x%08X\n", SCB->AFSR);
my_printf("MMFAR = 0x%08X\n", SCB->MMFAR);
my_printf("BFAR = 0x%08X\n", SCB->BFAR);
// --- Stack trace ---
my_printf("--- Stack trace ---\n");
ret_adr[0] = __builtin_return_address(0);
ret_adr[1] = __builtin_return_address(1);
ret_adr[2] = __builtin_return_address(2);
for (int i = 0; i < 3; i++) // развернуть n уровней
{
if(ret_adr[i])
my_printf(" #%d: 0x%08lX\r\n", i, ret_adr[i]); // -1 для Thumb
}
RTT_SaveToFlash(HF_RTT_TAG_BASE, HF_RTT_TAIL_SIZE);
}
#else // HAL_MODULE_ENABLED && HARDFAULT_SERIAL_TRACE
#define HF_CheckRecovered(...) 0
#define HF_HandleFault(...)
#endif // HAL_MODULE_ENABLED && HARDFAULT_SERIAL_TRACE
/** TRACE_HARDFAULT
* @}
*/
#endif //__TRACE_H_