diff --git a/AMS_Master_Code/Core/Inc/swo_log.h b/AMS_Master_Code/Core/Inc/swo_log.h deleted file mode 100644 index f6227e4..0000000 --- a/AMS_Master_Code/Core/Inc/swo_log.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * swo_log.h - * - * Created on: 20.01.2025 - * Author: Kilian - * - * Use the SWO interface to log messages - */ -#pragma once -#ifndef __SWO_LOG_H -#define __SWO_LOG_H -#include "stm32h7xx_hal.h" -#include -#include - -#define MAX_MESSAGE_LENGTH 256 -#define USE_MULTIPLE_CHANNELS false // if true, each log level has its own channel (FATAL : 0, ERROR : 1, etc.) -#define USE_ANSI_ESCAPE_CODES true // if true, log messages will be colored according to their log level, and the console can be cleared -#define PRINT_TIMESTAMP false // if true, timestamp (from HAL_GetTick) is printed before each log message - -#if !USE_MULTIPLE_CHANNELS - #ifndef DEBUG_CHANNEL - #define DEBUG_CHANNEL 0 // channel to output messages on - #endif - #define USE_CHANNEL_MASK_VARIABLE true -#endif - -#if USE_CHANNEL_MASK_VARIABLE - #define MASK_VARIABLE logging_mask // variable to store the channel mask, must be globally defined somewhere - extern volatile uint32_t MASK_VARIABLE; -#endif - -enum log_level_t { - LOG_LEVEL_FATAL, - LOG_LEVEL_ERROR, - LOG_LEVEL_WARNING, - LOG_LEVEL_INFO, - LOG_LEVEL_DEBUG, - LOG_LEVEL_NOISY -}; - -#if USE_ANSI_ESCAPE_CODES -[[maybe_unused]] static const char *const log_level_names[] = { - "\033[31m[FATAL]\033[0m ", "\033[31m[ERROR]\033[0m ", - "\033[93m[WARN] \033[0m ", "\033[97m[INFO] \033[0m ", - "\033[37m[DEBUG]\033[0m ", "\033[37m[NOISY]\033[0m "}; -#else -[[maybe_unused]] static const char *const log_level_names[] = { - "[FATAL] ", "[ERROR] ", "[WARN] ", "[INFO] ", "[DEBUG] ", "[NOISY] "}; -#endif - -static inline bool __ITM_channel_enabled(uint32_t channel) { -#if !USE_MULTIPLE_CHANNELS -#if USE_CHANNEL_MASK_VARIABLE - return ((ITM->TER & (1UL << DEBUG_CHANNEL)) != 0UL) && - ((MASK_VARIABLE & (1UL << channel)) != 0UL); -#else - channel = DEBUG_CHANNEL; -#endif -#endif - return ((ITM->TER & (1UL << channel)) != 0UL); -} - -// adapted from ITM_SendChar() in the CMSIS-Core -// -// channel should be between 0 and 31 -static inline uint32_t __swo_putc(uint32_t c, unsigned int channel) { -#if !USE_MULTIPLE_CHANNELS - channel = DEBUG_CHANNEL; -#endif - if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) && /* ITM enabled */ - ((ITM->TER & (1UL << channel)) != 0UL)) /* ITM Port enabled */ - { - while (ITM->PORT[channel].u32 == 0UL) { - __NOP(); - } - ITM->PORT[channel].u8 = (uint8_t)c; - } - return (c); -} - -#define DEBUG_CHANNEL_ENABLED(channel) \ - ({ \ - unsigned int ch = (channel); \ - (ch < 32) ? __ITM_channel_enabled(ch) : false; \ - }) - -[[gnu::nonnull(2), gnu::null_terminated_string_arg(2)]] -static inline void __swo_print(unsigned int channel, const char *str) { - if (!__ITM_channel_enabled(channel)) { - return; - } - while (*str) { - __swo_putc(*str++, channel); - } -} - -static inline void debug_clear_console() { - for (int i = 0; i < LOG_LEVEL_NOISY; i++) { -#if USE_ANSI_ESCAPE_CODES - __swo_print(i, "\033[2J\033[;H"); // clear screen -#else - __swo_print(i, "\n\n\n\n\n-------------------\n\n\n\n\n"); // clear screen -#endif - } -} - -[[gnu::format(printf, 2, 3)]] -static inline void debug_log(enum log_level_t level, const char *msg, ...) { - if (!DEBUG_CHANNEL_ENABLED(level)) { - return; - } - - char __swo_buffer[MAX_MESSAGE_LENGTH]; - va_list args; - va_start(args, msg); - size_t len = vsnprintf(__swo_buffer, sizeof(__swo_buffer), msg, args); - va_end(args); - - __swo_putc('\n', level); - - /* Print timestamp if enabled */ - if (PRINT_TIMESTAMP) { - char __time_buffer[16]; - if (USE_ANSI_ESCAPE_CODES) { - snprintf(__time_buffer, sizeof(__time_buffer), - "\033[90m[%lu]\033[0m ", HAL_GetTick()); - } else { - snprintf(__time_buffer, sizeof(__time_buffer), "[%lu] ", - HAL_GetTick()); - } - __swo_print(level, __time_buffer); - } - - #ifdef SWO_LOG_PREFIX - __swo_print(level, SWO_LOG_PREFIX); - #endif - - __swo_print(level, log_level_names[level]); - __swo_print(level, __swo_buffer); - - if (len >= sizeof(__swo_buffer)) { - __swo_print(level, " [message length exceeded] "); - } -} - -[[gnu::format(printf, 2, 3)]] -static inline void debug_log_cont(enum log_level_t level, const char *msg, ...) { - if (!DEBUG_CHANNEL_ENABLED(level)) { - return; - } - - char __swo_buffer[MAX_MESSAGE_LENGTH]; - va_list args; - va_start(args, msg); - size_t len = vsnprintf(__swo_buffer, sizeof(__swo_buffer), msg, args); - va_end(args); - - __swo_print(level, __swo_buffer); - - if (len >= sizeof(__swo_buffer)) { - __swo_print(level, " [message length exceeded] "); - } -} - -#endif /* __SWO_LOG_H */ diff --git a/AMS_Master_Code/Core/Lib/logger/log.c b/AMS_Master_Code/Core/Lib/logger/log.c new file mode 100644 index 0000000..6836961 --- /dev/null +++ b/AMS_Master_Code/Core/Lib/logger/log.c @@ -0,0 +1,116 @@ +/* + * log.c + * + * Created on: 21.04.2025 + * Author: Kilian + * + * Implementation of the core logging system + */ + +#include "log.h" +#include "stm32h7xx_hal.h" + +/* Backend management */ +static const log_backend_t* registered_backends[MAX_LOG_BACKENDS] = {0}; +static uint8_t backend_count = 0; + +/* Register a new logging backend */ +bool log_register_backend(const log_backend_t* backend) { + if (backend_count >= MAX_LOG_BACKENDS || backend == NULL) { + return false; + } + + registered_backends[backend_count++] = backend; + return true; +} + +/* Initialize all registered backends */ +void log_init_all_backends(void) { + /* Initialize all registered backends */ + for (uint8_t i = 0; i < backend_count; i++) { + if (registered_backends[i]->init != NULL) { + registered_backends[i]->init(); + } + } +} + +/* Check if a log level is enabled */ +bool log_is_level_enabled(log_level_t level) { + for (uint8_t i = 0; i < backend_count; i++) { + if (registered_backends[i]->is_enabled(level)) { + return true; + } + } + return false; +} + + + + + +/* Frontend implementation */ + +/* Main logging function */ +void log_message(log_level_t level, const char *msg, ...) { + log_message_t log_msg; + log_msg.level = level; + log_msg.timestamp = HAL_GetTick(); + + /* Format the message prefix (timestamp, log level) */ + size_t prefix_len = 0; + + /* Add timestamp if configured */ + if (PRINT_TIMESTAMP) { + prefix_len += snprintf(log_msg.message, MAX_MESSAGE_LENGTH, "[%lu] ", log_msg.timestamp); + } + + /* Add log level name */ + if (level <= LOG_LEVEL_NOISY) { + prefix_len += snprintf(log_msg.message + prefix_len, MAX_MESSAGE_LENGTH - prefix_len, + "%s", log_level_names[level]); + } + + /* Format the log message with variable arguments */ + va_list args; + va_start(args, msg); + log_msg.message_length = prefix_len + vsnprintf(log_msg.message + prefix_len, + MAX_MESSAGE_LENGTH - prefix_len, + msg, args); + va_end(args); + + /* Send to all enabled backends */ + for (uint8_t i = 0; i < backend_count; i++) { + if (registered_backends[i]->is_enabled(level)) { + registered_backends[i]->write(&log_msg); + } + } +} + +/* Continuation logging without timestamp or log level */ +void log_message_cont(log_level_t level, const char *msg, ...) { + log_message_t log_msg; + log_msg.level = level; + log_msg.timestamp = HAL_GetTick(); + + /* Format the log message with variable arguments */ + va_list args; + va_start(args, msg); + log_msg.message_length = vsnprintf(log_msg.message, MAX_MESSAGE_LENGTH, msg, args); + va_end(args); + + /* Send to all enabled backends */ + for (uint8_t i = 0; i < backend_count; i++) { + if (registered_backends[i]->is_enabled(level)) { + registered_backends[i]->write(&log_msg); + } + } +} + +/* Clear the console on all backends that support it */ +void log_clear_console(void) { + for (uint8_t i = 0; i < backend_count; i++) { + if (registered_backends[i]->clear != NULL) { + registered_backends[i]->clear(); + } + } +} \ No newline at end of file diff --git a/AMS_Master_Code/Core/Lib/logger/log.h b/AMS_Master_Code/Core/Lib/logger/log.h new file mode 100644 index 0000000..f36d71b --- /dev/null +++ b/AMS_Master_Code/Core/Lib/logger/log.h @@ -0,0 +1,88 @@ +/* + * log.h + * + * Created on: 21.04.2025 + * Author: Kilian + * + * Core logging system with support for multiple backends + */ +#pragma once +#ifndef __LOG_H +#define __LOG_H + +#include "stm32h7xx_hal.h" +#include +#include +#include + +/* Configuration */ +#define MAX_MESSAGE_LENGTH 256 +#define USE_ANSI_ESCAPE_CODES true // if true, log messages will be colored according to their log level +#define PRINT_TIMESTAMP false // if true, timestamp (from HAL_GetTick) is printed before each log message + +/* Maximum number of backends that can be registered */ +#define MAX_LOG_BACKENDS 4 + +/* Log level definitions */ +typedef enum { + LOG_LEVEL_FATAL, + LOG_LEVEL_ERROR, + LOG_LEVEL_WARNING, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG, + LOG_LEVEL_NOISY +} log_level_t; + +/* Log message structure */ +typedef struct { + log_level_t level; + uint32_t timestamp; + char message[MAX_MESSAGE_LENGTH]; + size_t message_length; +} log_message_t; + +/* Log backend interface */ +typedef struct { + const char* name; + void (*init)(void); + bool (*is_enabled)(log_level_t level); + void (*write)(const log_message_t* message); + void (*flush)(void); + void (*clear)(void); +} log_backend_t; + +/* Log level names with formatting for display */ +#if USE_ANSI_ESCAPE_CODES +[[maybe_unused]] static const char *const log_level_names[] = { + "\033[31m[FATAL]\033[0m ", "\033[31m[ERROR]\033[0m ", + "\033[93m[WARN] \033[0m ", "\033[97m[INFO] \033[0m ", + "\033[37m[DEBUG]\033[0m ", "\033[37m[NOISY]\033[0m "}; +#else +[[maybe_unused]] static const char *const log_level_names[] = { + "[FATAL] ", "[ERROR] ", "[WARN] ", "[INFO] ", "[DEBUG] ", "[NOISY] "}; +#endif + +/* Backend management functions */ +bool log_register_backend(const log_backend_t* backend); +void log_init_all_backends(void); + +/* Frontend logging functions */ +[[gnu::format(printf, 2, 3)]] +void log_message(log_level_t level, const char *msg, ...); + +[[gnu::format(printf, 2, 3)]] +void log_message_cont(log_level_t level, const char *msg, ...); + +void log_clear_console(void); + +bool log_is_level_enabled(log_level_t level); + +/* Convenience macros for different log levels */ +#define log_fatal(fmt, ...) log_message(LOG_LEVEL_FATAL, fmt, ##__VA_ARGS__) +#define log_error(fmt, ...) log_message(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__) +#define log_warning(fmt, ...) log_message(LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__) +#define log_info(fmt, ...) log_message(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__) +#define log_debug(fmt, ...) log_message(LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__) +#define log_noisy(fmt, ...) log_message(LOG_LEVEL_NOISY, fmt, ##__VA_ARGS__) + +#endif /* __LOG_H */ \ No newline at end of file diff --git a/AMS_Master_Code/Core/Lib/logger/swo_log.h b/AMS_Master_Code/Core/Lib/logger/swo_log.h new file mode 100644 index 0000000..8857f93 --- /dev/null +++ b/AMS_Master_Code/Core/Lib/logger/swo_log.h @@ -0,0 +1,26 @@ +/* + * swo_log.h + * + * Created on: 20.01.2025 + * Author: Kilian + * + * Compatibility header for the new logging system + * This allows existing code to continue using swo_log.h without modifications + */ +#pragma once +#ifndef __SWO_LOG_H +#define __SWO_LOG_H + +#include "log.h" +#include "swo_log_backend.h" + +/* For backward compatibility with existing code */ +#define debug_log log_message +#define debug_log_cont log_message_cont +#define debug_clear_console log_clear_console + +#define DEBUG_CHANNEL_ENABLED(level) log_is_level_enabled(level) + +/* All actual functionality is now in log.h and swo_log_backend.h */ + +#endif /* __SWO_LOG_H */ diff --git a/AMS_Master_Code/Core/Lib/logger/swo_log_backend.c b/AMS_Master_Code/Core/Lib/logger/swo_log_backend.c new file mode 100644 index 0000000..88ac8bd --- /dev/null +++ b/AMS_Master_Code/Core/Lib/logger/swo_log_backend.c @@ -0,0 +1,136 @@ +/* + * swo_log_backend.c + * + * Created on: 21.04.2025 + * Author: Kilian + * + * SWO backend implementation for the logging system + */ + +#include "swo_log_backend.h" +#include "log.h" + +/* Global variables */ +#if USE_CHANNEL_MASK_VARIABLE +volatile uint32_t MASK_VARIABLE = 0b11111; // LOG_NOISY off by default +#endif + +static bool swo_backend_registered = false; + +/* SWO utility functions */ +static inline bool __ITM_channel_enabled(uint32_t channel) { +#if !USE_MULTIPLE_CHANNELS +#if USE_CHANNEL_MASK_VARIABLE + return ((ITM->TER & (1UL << DEBUG_CHANNEL)) != 0UL) && + ((MASK_VARIABLE & (1UL << channel)) != 0UL); +#else + channel = DEBUG_CHANNEL; +#endif +#endif + return ((ITM->TER & (1UL << channel)) != 0UL); +} + +// adapted from ITM_SendChar() in the CMSIS-Core +// channel should be between 0 and 31 +static inline uint32_t __swo_putc(uint32_t c, unsigned int channel) { +#if !USE_MULTIPLE_CHANNELS + channel = DEBUG_CHANNEL; +#endif + if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) && /* ITM enabled */ + ((ITM->TER & (1UL << channel)) != 0UL)) /* ITM Port enabled */ + { + while (ITM->PORT[channel].u32 == 0UL) { + __NOP(); + } + ITM->PORT[channel].u8 = (uint8_t)c; + } + return (c); +} + +#define DEBUG_CHANNEL_ENABLED(channel) \ + ({ \ + unsigned int ch = (channel); \ + (ch < 32) ? __ITM_channel_enabled(ch) : false; \ + }) + +[[gnu::nonnull(2), gnu::null_terminated_string_arg(2)]] +static inline void __swo_print(unsigned int channel, const char *str) { + if (!__ITM_channel_enabled(channel)) { + return; + } + while (*str) { + __swo_putc(*str++, channel); + } +} + +/* SWO backend functions */ +static void swo_backend_init(void); +static bool swo_backend_is_enabled(log_level_t level); +static void swo_backend_write(const log_message_t* message); +static void swo_backend_flush(void); +static void swo_backend_clear(void); + +/* SWO backend definition */ +static const log_backend_t swo_backend = { + .name = "SWO", + .init = swo_backend_init, + .is_enabled = swo_backend_is_enabled, + .write = swo_backend_write, + .flush = swo_backend_flush, + .clear = swo_backend_clear +}; + +/* Return the SWO backend definition */ +const log_backend_t* swo_log_get_backend(void) { + return &swo_backend; +} + +/* Check if the SWO backend is registered */ +bool swo_log_is_backend_registered(void) { + return swo_backend_registered; +} + +/* Initialize the SWO backend and register it */ +void swo_log_backend_init(void) { + /* Register the backend */ + swo_backend_registered = log_register_backend(&swo_backend); +} + +/* SWO backend initialization */ +static void swo_backend_init(void) { + /* SWO is initialized by the debugger, nothing to do here */ +} + +/* Check if the SWO backend is enabled for the given log level */ +static bool swo_backend_is_enabled(log_level_t level) { +#if USE_MULTIPLE_CHANNELS + return DEBUG_CHANNEL_ENABLED(level); +#else + return (level <= LOG_LEVEL_NOISY) && DEBUG_CHANNEL_ENABLED(level); +#endif +} + +/* Write a message to the SWO backend */ +static void swo_backend_write(const log_message_t* message) { + if (message == NULL) { + return; + } + +#if USE_MULTIPLE_CHANNELS + __swo_print(message->level, message->message); +#else + __swo_print(DEBUG_CHANNEL, message->message); +#endif +} + +/* Flush the SWO backend - not needed for SWO */ +static void swo_backend_flush(void) { + /* SWO is a real-time interface, no buffering, nothing to flush */ +} + +/* Clear the console for SWO */ +static void swo_backend_clear(void) { +#if USE_ANSI_ESCAPE_CODES + __swo_print(DEBUG_CHANNEL, "\033[2J\033[H"); // ANSI escape code to clear screen and move cursor to top-left +#endif +} \ No newline at end of file diff --git a/AMS_Master_Code/Core/Lib/logger/swo_log_backend.h b/AMS_Master_Code/Core/Lib/logger/swo_log_backend.h new file mode 100644 index 0000000..849a9bd --- /dev/null +++ b/AMS_Master_Code/Core/Lib/logger/swo_log_backend.h @@ -0,0 +1,35 @@ +/* + * swo_log_backend.h + * + * Created on: 21.04.2025 + * Author: Kilian + * + * SWO backend for the logging system + */ +#pragma once +#ifndef __SWO_LOG_BACKEND_H +#define __SWO_LOG_BACKEND_H + +#include "log.h" +#include "stm32h7xx_hal.h" + +/* SWO Backend Configuration */ +#define USE_MULTIPLE_CHANNELS false // if true, each log level has its own channel (FATAL : 0, ERROR : 1, etc.) +#if !USE_MULTIPLE_CHANNELS + #ifndef DEBUG_CHANNEL + #define DEBUG_CHANNEL 0 // channel to output messages on + #endif + #define USE_CHANNEL_MASK_VARIABLE true +#endif + +#if USE_CHANNEL_MASK_VARIABLE + #define MASK_VARIABLE logging_mask // variable to store the channel mask, must be globally defined somewhere + extern volatile uint32_t MASK_VARIABLE; +#endif + +/* SWO backend API */ +void swo_log_backend_init(void); +bool swo_log_is_backend_registered(void); +const log_backend_t* swo_log_get_backend(void); + +#endif /* __SWO_LOG_BACKEND_H */ \ No newline at end of file diff --git a/AMS_Master_Code/Core/Src/main.c b/AMS_Master_Code/Core/Src/main.c index 96995fc..ab198f6 100644 --- a/AMS_Master_Code/Core/Src/main.c +++ b/AMS_Master_Code/Core/Src/main.c @@ -86,12 +86,11 @@ static void MX_SPI2_Init(void); static void MX_ADC1_Init(void); static void MX_ADC2_Init(void); /* USER CODE BEGIN PFP */ - +void init_logging(void); /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ -uint32_t volatile logging_mask = 0b11111; // no LOG_LEVEL_NOISY #define MAIN_LOOP_PERIOD 50 @@ -128,6 +127,18 @@ static void update_tsal_signals() { PRE_and_AIR__open_Pin) == GPIO_PIN_SET; } +/* Initialize logging system */ +void init_logging(void) { + /* Register and initialize SWO backend */ + swo_log_backend_init(); + + /* Initialize all registered backends */ + log_init_all_backends(); + + /* Log a test message */ + log_info("Logging system initialized"); +} + /* USER CODE END 0 */ /** @@ -169,6 +180,7 @@ int main(void) MX_ADC1_Init(); MX_ADC2_Init(); /* USER CODE BEGIN 2 */ + init_logging(); debug_clear_console(); debug_log(LOG_LEVEL_INFO, "AMS_Master on %s (%s), compiled at %s", COMMIT_BRANCH, COMMIT_HASH, COMPILE_DATE); debug_log(LOG_LEVEL_INFO, "Starting BMS...");