/** ************************************************************************** * @file trace.h * @brief Заголочный файл для работы с трассировкой. ************************************************************************** * @addtogroup TRACE Trace defines * @ingroup MYLIBS_DEFINES * @brief Дефайны для работы с трассировкой *************************************************************************/ #ifndef __TRACE_H_ #define __TRACE_H_ #include "mylibs_defs.h" #include /** * @addtogroup TRACE_SERIAL Serial trace defines * @ingroup TRACE * @brief Дефайны для работы с serial трассировкой (SWO, RTT) * @details Определяется дефайн @ref my_printf() и @ref log_printf() для работы с serial трассировкой: - для RTT это будет вызов функции SEGGER_RTT_printf(), с подключением библиотеки SEGGER_RTT.h - для SWO это будет просто printf() Но библиотеку STDOUT надо подключить самостоятельно: @verbatim Manage Run-Time Environment -> Compiler -> I/O -> STDOUT @endverbatim Для SWO также надо включить трассировку: @verbatim Options For Target -> Debug -> Debugger Settings @endverbatim В вкладке Debug: - Port = SW В вкладке Trace: - Указать Core Clock - Выставить Trace Port = SWO - ITM - выбрать нужный порт (для Keil нулевой порт) - Если трассировка @ref SERIAL_TRACE_ENABLE отключена, то все дефайны определяются как 'ничего' и на производительность кода не влияют Если трассировка отключена, все макросы пустые и не влияют на производительность * @{ * * @def my_printf(...) * @brief Универсальный макрос для вывода трассировки * @details Варианты реализации: * - RTT_TRACE_ENABLE `SEGGER_RTT_printf(0, ...)` * - SWO_TRACE_ENABLE - `printf(...)` * - NO_TRACE - пустой макрос * * @def log_printf(TAG, fmt, ...) * @brief Макрос логирования с поддержкой уровней LOG_LEVEL * @param TAG Тэг лога * @param fmt, ... Форматируемая строка * @details Варианты реализации: * - LOG_LEVEL == 0 - логирование отключено (макрос пустой) * - LOG_LEVEL == 1 - выводится время и TAG * - LOG_LEVEL >= 2 - выводится время, TAG, имя файла и номер строки */ #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 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)uwTick, TAG, ##__VA_ARGS__) #elif LOG_LEVEL >= 2 // всё #define log_printf(TAG, fmt, ...) \ my_printf("\n[%lu] [%s] (%s:%d) " fmt, \ (unsigned long)uwTick, 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 или позиции буферов. * @{ */ /** * @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 после восстановления. @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_