Compare commits

...

4 Commits

16 changed files with 1070 additions and 257 deletions

View File

@ -22,6 +22,9 @@
#define CAN_ID_SHUNT_CURRENT_COUNTER 0x527
#define CAN_ID_SHUNT_ENERGY_COUNTER 0x528
//TEMP until final IDs are defined
#define CAN_ID_LOG_FC 0x132
void can_init(FDCAN_HandleTypeDef *handle);
HAL_StatusTypeDef can_send_status();
HAL_StatusTypeDef can_send_error(TSErrorKind kind, uint8_t arg);

View File

@ -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 <stdarg.h>
#include <stdio.h>
#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 */

@ -1 +1 @@
Subproject commit 4e3bb026f88a7ee5a89ec48dc10281e8e0a3175a
Subproject commit 887f92167d58e551abac18f4f899c74bddc13d46

View File

@ -0,0 +1,255 @@
/*
* Author: Kilian
* Date: 2025-04-20
* Description: This file contains a TX-only implementation of the ISO-TP protocol.
*
* References:
* https://munich.dissec.to/kb/chapters/isotp/isotp.html#protocol-basics-iso-15765-2
* https://www.kernel.org/doc/html/next//networking/iso15765-2.html
* https://en.wikipedia.org/wiki/ISO_15765-2
* https://ramn.readthedocs.io/en/latest/userguide/isotp_tutorial.html
*/
#include "isotp.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#define MAX_CAN_DATA_SIZE 8
#define MAX_ISOTP_DATA_SIZE_MESSAGE 4095
#define MAX_ISOTP_DATA_SINGLE 7
#define MAX_ISOTP_DATA_FIRST 6
#define MAX_ISOTP_DATA_CONSECUTIVE 7
typedef union [[gnu::packed]] {
struct {
uint8_t header;
uint8_t data[MAX_ISOTP_DATA_SINGLE];
} single;
struct {
uint8_t header_1;
uint8_t header_2;
uint8_t data[MAX_ISOTP_DATA_FIRST];
} first;
struct {
uint8_t header;
uint8_t data[MAX_ISOTP_DATA_CONSECUTIVE];
} consecutive;
} isotp_message;
enum {
ISOTP_SINGLE_FRAME = 0 << 4,
ISOTP_FIRST_FRAME = 1 << 4,
ISOTP_CONSECUTIVE_FRAME = 2 << 4,
ISOTP_FLOW_CONTROL = 3 << 4
};
struct isotp_message_state {
const uint8_t * data;
size_t remaining;
uint16_t id;
uint32_t timestamp; // Timestamp for timeout handling
enum : uint8_t {
ISOTP_NEW_MESSAGE = 0,
ISOTP_WAITING_FLOW_CONTROL = 1,
ISOTP_READY = 2,
ISOTP_DONE = 3,
ISOTP_FAILED = 4
} state;
struct {
enum : uint8_t {
ISOTP_CONTINUE = 0,
ISOTP_WAIT = 1,
ISOTP_ABORT = 2
} state;
uint8_t block_size;
uint8_t st_min; // in ms, we round up to 1 ms if sub 1 ms
uint8_t st_min_counter;
} flow_control;
uint8_t index;
};
static struct isotp_message_state tx_queue[MAX_ISOTP_MESSAGES_IN_FLIGHT] = {[0 ... MAX_ISOTP_MESSAGES_IN_FLIGHT - 1] = {.state=ISOTP_DONE }};
[[gnu::weak]] bool isotp_can_transmit(uint16_t, const uint8_t *, size_t) { return false; }
[[gnu::weak]] uint32_t isotp_can_get_time() { return 0; }
uint32_t last_called;
isotp_status_t isotp_init() {
// platform-specific initialization
last_called = isotp_can_get_time() - 100;
return (isotp_status_t) {ISOTP_OK};
}
isotp_status_t isotp_add_message(uint16_t id, const uint8_t * data, size_t datalen) {
if (datalen > MAX_ISOTP_DATA_SIZE_MESSAGE) {
return ISOTP_DATA_TOO_LONG;
}
if (data == NULL || datalen == 0) {
return ISOTP_INVALID_PARAMETER;
}
for (size_t i = 0; i < MAX_ISOTP_MESSAGES_IN_FLIGHT; i++) {
if (!(tx_queue[i].state == ISOTP_DONE || tx_queue[i].state == ISOTP_FAILED) && tx_queue[i].id == id) {
return ISOTP_MESSAGE_ALREADY_IN_FLIGHT;
}
}
for (size_t i = 0; i < MAX_ISOTP_MESSAGES_IN_FLIGHT; i++) {
if (tx_queue[i].state == ISOTP_DONE || tx_queue[i].state == ISOTP_FAILED) {
tx_queue[i] = (struct isotp_message_state) {data, datalen, id, 0, ISOTP_NEW_MESSAGE, {}, 1};
return ISOTP_OK;
}
}
return ISOTP_TOO_MANY_MESSAGES;
}
/* Macro to check CAN transmission result and handle errors */
#define TRY_CAN_TRANSMIT(id, data, size) \
if (!isotp_can_transmit(id, data, size)) { \
tx_queue[i].state = ISOTP_FAILED; \
return ISOTP_CAN_TRANSMIT_ERROR; \
}
isotp_status_t isotp_update() {
uint32_t now = isotp_can_get_time();
uint32_t delta = now - last_called;
for (size_t i = 0; i < MAX_ISOTP_MESSAGES_IN_FLIGHT; i++) {
isotp_message msg = {};
switch (tx_queue[i].state) {
case ISOTP_NEW_MESSAGE:
if (tx_queue[i].remaining <= MAX_ISOTP_DATA_SINGLE) {
msg.single.header = ISOTP_SINGLE_FRAME | (tx_queue[i].remaining & 0x0F);
memcpy(msg.single.data, tx_queue[i].data, tx_queue[i].remaining);
#if ISOTP_PADDING
memset(msg.single.data + tx_queue[i].remaining, ISOTP_PADDING, MAX_ISOTP_DATA_SINGLE - tx_queue[i].remaining);
TRY_CAN_TRANSMIT(tx_queue[i].id, (const uint8_t *)&msg, MAX_CAN_DATA_SIZE);
#else
TRY_CAN_TRANSMIT(tx_queue[i].id, (const uint8_t *)&msg, tx_queue[i].remaining + 1);
#endif
tx_queue[i].remaining = 0;
tx_queue[i].state = ISOTP_DONE;
} else {
msg.first.header_1 = ISOTP_FIRST_FRAME | ((tx_queue[i].remaining >> 8) & 0x0F);
msg.first.header_2 = tx_queue[i].remaining & 0xFF;
memcpy(msg.first.data, tx_queue[i].data, MAX_ISOTP_DATA_FIRST);
TRY_CAN_TRANSMIT(tx_queue[i].id, (const uint8_t *)&msg, MAX_CAN_DATA_SIZE);
tx_queue[i].remaining -= MAX_ISOTP_DATA_FIRST;
tx_queue[i].data += MAX_ISOTP_DATA_FIRST;
tx_queue[i].timestamp = now; // Set timestamp when entering WAITING_FLOW_CONTROL state
tx_queue[i].state = ISOTP_WAITING_FLOW_CONTROL;
}
break;
case ISOTP_READY:
ISOTP_SEND_AGAIN:
if (tx_queue[i].flow_control.st_min_counter <= delta) {
tx_queue[i].flow_control.st_min_counter = tx_queue[i].flow_control.st_min;
} else {
tx_queue[i].flow_control.st_min_counter -= delta;
break;
}
if (tx_queue[i].flow_control.block_size == 1) {
tx_queue[i].state = ISOTP_WAITING_FLOW_CONTROL;
}
if (tx_queue[i].flow_control.block_size != 0) {
tx_queue[i].flow_control.block_size--;
}
msg.consecutive.header = ISOTP_CONSECUTIVE_FRAME | (tx_queue[i].index & 0x0F);
size_t bytes_to_send = tx_queue[i].remaining <= MAX_ISOTP_DATA_CONSECUTIVE ? tx_queue[i].remaining : MAX_ISOTP_DATA_CONSECUTIVE;
memcpy(msg.consecutive.data, tx_queue[i].data, bytes_to_send);
#if ISOTP_PADDING
if (bytes_to_send < MAX_ISOTP_DATA_CONSECUTIVE) {
memset(msg.consecutive.data + bytes_to_send, ISOTP_PADDING, MAX_ISOTP_DATA_CONSECUTIVE - bytes_to_send);
TRY_CAN_TRANSMIT(tx_queue[i].id, (const uint8_t *)&msg, MAX_CAN_DATA_SIZE);
} else {
TRY_CAN_TRANSMIT(tx_queue[i].id, (const uint8_t *)&msg, MAX_CAN_DATA_SIZE);
}
#else
TRY_CAN_TRANSMIT(tx_queue[i].id, (const uint8_t *)&msg, bytes_to_send + 1);
#endif
tx_queue[i].data += bytes_to_send;
tx_queue[i].remaining -= bytes_to_send;
tx_queue[i].index++;
if (tx_queue[i].remaining == 0) {
tx_queue[i].state = ISOTP_DONE;
}
if (tx_queue[i].state == ISOTP_READY) {
if (tx_queue[i].flow_control.st_min == 0) { // no delay needed, and we are not waiting for flow control -> send again
goto ISOTP_SEND_AGAIN;
}
}
break;
case ISOTP_WAITING_FLOW_CONTROL:
if (now - tx_queue[i].timestamp > ISOTP_FC_WAIT_TIMEOUT) {
tx_queue[i].state = ISOTP_FAILED; // Timeout handling
}
break;
case ISOTP_DONE:
case ISOTP_FAILED:
break;
}
}
last_called = now;
return ISOTP_OK;
}
isotp_status_t isotp_handle_flow_control(uint16_t id, const uint8_t * data, size_t datalen) {
if (datalen < 3) {
return ISOTP_NOT_FLOW_CONTROL;
}
if (data == NULL) {
return ISOTP_INVALID_PARAMETER;
}
for (size_t i = 0; i < MAX_ISOTP_MESSAGES_IN_FLIGHT; i++) {
if (tx_queue[i].state == ISOTP_DONE || tx_queue[i].state == ISOTP_FAILED || tx_queue[i].id != id) {
continue;
}
uint8_t type = data[0] & 0xF0;
uint8_t status = data[0] & 0x0F;
uint8_t block_size = data[1];
uint8_t st_min = data[2];
if (type != ISOTP_FLOW_CONTROL) {
return ISOTP_NOT_FLOW_CONTROL;
}
switch (status) {
case 0: // Continue
tx_queue[i].flow_control.state = ISOTP_CONTINUE;
break;
case 1: // Wait
tx_queue[i].flow_control.state = ISOTP_WAIT;
tx_queue[i].state = ISOTP_WAITING_FLOW_CONTROL;
tx_queue[i].timestamp = isotp_can_get_time(); // Update timestamp for timeout handling
return ISOTP_OK;
case 2: // Abort
tx_queue[i].flow_control.state = ISOTP_ABORT;
tx_queue[i].state = ISOTP_FAILED;
return ISOTP_OK;
default:
return ISOTP_NOT_FLOW_CONTROL;
}
tx_queue[i].flow_control.block_size = block_size;
tx_queue[i].flow_control.st_min = st_min > 0x7F ? 1 : st_min; // round up to 1 ms
tx_queue[i].flow_control.st_min_counter = st_min > 0x7F ? 1 : st_min;
tx_queue[i].state = ISOTP_READY;
return ISOTP_OK;
}
return ISOTP_INVALID_PARAMETER;
}

View File

@ -0,0 +1,41 @@
#ifndef ISOTP_H
#define ISOTP_H
#include <stddef.h>
#include <stdint.h>
#define MAX_ISOTP_MESSAGES_IN_FLIGHT 10
#define ISOTP_FC_WAIT_TIMEOUT 1000 // Timeout for flow control frames (ms)
//#define ISOTP_PADDING 0xAA // optional padding byte
typedef enum : int16_t {
ISOTP_OK = 0,
ISOTP_ERROR = -1,
ISOTP_DATA_TOO_LONG = -2,
ISOTP_INVALID_PARAMETER = -3,
ISOTP_TOO_MANY_MESSAGES = -4,
ISOTP_MESSAGE_ALREADY_IN_FLIGHT = -5,
ISOTP_NOT_FLOW_CONTROL = -6,
ISOTP_CAN_TRANSMIT_ERROR = -7,
} isotp_status_t;
static inline const char *isotp_status_to_string(isotp_status_t status) {
switch (status) {
case ISOTP_OK: return "ISOTP_OK";
case ISOTP_ERROR: return "ISOTP_ERROR";
case ISOTP_DATA_TOO_LONG: return "ISOTP_DATA_TOO_LONG";
case ISOTP_INVALID_PARAMETER: return "ISOTP_INVALID_PARAMETER";
case ISOTP_TOO_MANY_MESSAGES: return "ISOTP_TOO_MANY_MESSAGES";
case ISOTP_MESSAGE_ALREADY_IN_FLIGHT: return "ISOTP_MESSAGE_ALREADY_IN_FLIGHT";
case ISOTP_NOT_FLOW_CONTROL: return "ISOTP_NOT_FLOW_CONTROL";
case ISOTP_CAN_TRANSMIT_ERROR: return "ISOTP_CAN_TRANSMIT_ERROR";
default: return "UNKNOWN_STATUS";
}
}
isotp_status_t isotp_init();
[[gnu::access(read_only, 2, 3)]] isotp_status_t isotp_add_message(uint16_t id, const uint8_t *data, size_t datalen);
isotp_status_t isotp_update();
[[gnu::access(read_only, 2, 3)]] isotp_status_t isotp_handle_flow_control(uint16_t id, const uint8_t *data, size_t datalen);
#endif // ISOTP_H

View File

@ -0,0 +1,194 @@
#include "isotp_log_backend.h"
#include "log.h"
#include "isotp.h"
#include <string.h>
/* Static variables */
static bool isotp_backend_registered = false;
static bool streaming_enabled = false;
static log_level_t streaming_level = LOG_LEVEL_NOISY;
static bool buffer_dropped_messages = false;
static bool using_buffer_2 = false;
/* Buffer for log messages */
// The buffer is dynamically split to allow double-buffering while data is being sent
// Our ISO-TP implementation requires that the message data is live in memory until the message is fully sent
// Once streaming is enabled, we swap buffers after each message is sent to ensure that the data is not overwritten
static struct {
uint32_t write_pos;
uint32_t write_pos_2;
uint8_t buffer[ISOTP_LOG_BUFFER_1_BYTES + ISOTP_LOG_BUFFER_2_BYTES];
} message_buffer = {};
/* Functions for the message buffer */
static bool buffer_message(const log_message_t* message) {
if (message == NULL || message->message_length == 0) {
return false;
}
/* Check if we have enough space - select the appropriate buffer */
uint32_t* write_pos = using_buffer_2 ? &message_buffer.write_pos_2 : &message_buffer.write_pos;
/* Calculate buffer offset - buffer 2 starts at ISOTP_LOG_BUFFER_1_BYTES */
uint32_t buffer_offset = using_buffer_2 ? ISOTP_LOG_BUFFER_1_BYTES : 0;
uint32_t max_size = using_buffer_2 ? ISOTP_LOG_BUFFER_2_BYTES : ISOTP_LOG_BUFFER_1_BYTES; // Each buffer has its own size
/* Check if we have enough space in the current buffer */
if (*write_pos + message->message_length > max_size) {
buffer_dropped_messages = true;
return false; /* Buffer full */
}
/* Write message bytes */
for (size_t i = 0; i < message->message_length; i++) {
message_buffer.buffer[buffer_offset + *write_pos] = message->message[i];
(*write_pos)++;
}
return true;
}
/* Swap buffers and prepare for sending */
static bool swap_buffers_for_sending() {
/* Don't swap if there's nothing to send */
uint32_t* current_pos = using_buffer_2 ? &message_buffer.write_pos_2 : &message_buffer.write_pos;
if (*current_pos == 0) {
return false;
}
/* Swap to the other buffer for future writes */
using_buffer_2 = !using_buffer_2;
/* Reset the new current buffer position */
uint32_t* new_current_pos = using_buffer_2 ? &message_buffer.write_pos_2 : &message_buffer.write_pos;
*new_current_pos = 0;
return true;
}
/* ISO-TP backend functions */
static void isotp_backend_init(void);
static bool isotp_backend_is_enabled(log_level_t level);
static void isotp_backend_write(const log_message_t* message);
static void isotp_backend_flush(void);
static void isotp_backend_clear(void);
/* ISO-TP backend definition */
static const log_backend_t isotp_backend = {
.name = "ISO-TP",
.init = isotp_backend_init,
.is_enabled = isotp_backend_is_enabled,
.write = isotp_backend_write,
.flush = isotp_backend_flush,
.clear = isotp_backend_clear
};
/* Return the ISO-TP backend definition */
const log_backend_t* isotp_log_get_backend(void) {
return &isotp_backend;
}
/* Check if the ISO-TP backend is registered */
bool isotp_log_is_backend_registered(void) {
return isotp_backend_registered;
}
/* Initialize the ISO-TP backend and register it */
void isotp_log_backend_init(void) {
/* Register the backend */
isotp_backend_registered = log_register_backend(&isotp_backend);
/* Initialize the ISO-TP stack if registration was successful */
if (isotp_backend_registered) {
isotp_init();
}
}
/* ISO-TP backend initialization */
static void isotp_backend_init(void) {
/* Reset the message buffer */
message_buffer.write_pos = 0;
message_buffer.write_pos_2 = 0;
buffer_dropped_messages = false;
using_buffer_2 = false;
}
/* Check if the ISO-TP backend is enabled for the given log level */
static bool isotp_backend_is_enabled(log_level_t level) {
return streaming_enabled ?
(level <= streaming_level) :
(level <= ISOTP_LOG_BUFFER_MIN_LEVEL); // log level where messages are buffered before streaming mode is enabled
}
/* Write a message to the ISO-TP backend */
static void isotp_backend_write(const log_message_t *message) {
if (message == NULL) {
return;
}
/* Buffer the message regardless of streaming state */
buffer_message(message);
/* If streaming is enabled, schedule the messages for sending */
if (streaming_enabled) {
/* Auto-flush if we have data in the buffer and are in streaming mode */
isotp_backend_flush();
}
}
/* Process buffered log messages and send them via ISO-TP */
void isotp_log_process(void) {
/* Process ISO-TP updates to send any pending messages */
isotp_update();
}
/* Flush the ISO-TP backend */
static void isotp_backend_flush(void) {
/* Swap buffers if we have data to send and we're ready to send */
if (streaming_enabled) {
/* Get the buffer that is currently being written to */
uint32_t buffer_offset = !using_buffer_2 ? 0 : ISOTP_LOG_BUFFER_1_BYTES;
uint32_t buffer_size = !using_buffer_2 ? message_buffer.write_pos : message_buffer.write_pos_2;
/* Only send if we have data and a previous send is not in progress */
if (buffer_size > 0) {
isotp_status_t status = isotp_add_message(
ISOTP_LOG_CAN_ID,
message_buffer.buffer + buffer_offset,
buffer_size
);
if (status == ISOTP_OK) {
/* Successfully queued for sending, now swap the buffers */
swap_buffers_for_sending();
if (buffer_dropped_messages) {
log_warning("Buffered messages were dropped");
buffer_dropped_messages = false;
}
}
}
}
}
/* Clear functionality not really applicable for ISO-TP */
static void isotp_backend_clear(void) {
#if USE_ANSI_ESCAPE_CODES
/* Clear the console using ANSI escape codes */
isotp_backend_write(&(const log_message_t){.message = "\033[2J\033[H", .message_length = 7});
#endif
}
/* Public function to check if the backend is enabled for a given level */
bool isotp_log_is_enabled(log_level_t level) {
return isotp_backend_is_enabled(level);
}
/* Public function to enable streaming of log messages */
void isotp_log_enable_streaming(log_level_t level) {
streaming_enabled = true;
streaming_level = level;
isotp_backend_flush();
log_info("Now streaming log messages over CAN at level %s", log_level_names[level]);
}

View File

@ -0,0 +1,28 @@
#ifndef ISOTP_LOG_BACKEND_H
#define ISOTP_LOG_BACKEND_H
#include "log.h"
/* ISO-TP Backend Configuration */
#define ISOTP_LOG_BUFFER_MIN_LEVEL LOG_LEVEL_INFO // log level where messages are buffered before streaming mode is enabled
#define ISOTP_LOG_BUFFER_1_BYTES 1024 // Primary buffer, used when streaming is disabled
#define ISOTP_LOG_BUFFER_2_BYTES 256 // Secondary buffer, used for double-buffering during streaming, can be smaller
/* ISO-TP CAN ID configuration */
#ifndef ISOTP_LOG_CAN_ID
#define ISOTP_LOG_CAN_ID 0x123 // CAN ID to use for ISO-TP log messages
#endif
/* ISO-TP backend API */
void isotp_log_backend_init(void);
bool isotp_log_is_backend_registered(void);
const log_backend_t* isotp_log_get_backend(void);
void isotp_log_enable_streaming(log_level_t level);
/* Backend status check function */
bool isotp_log_is_enabled(log_level_t level);
/* Process function - should be called periodically to send buffered messages */
void isotp_log_process(void);
#endif /* ISOTP_LOG_BACKEND_H */

View File

@ -0,0 +1,114 @@
/*
* 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 = {level, HAL_GetTick(), "\n", 0};
/* Format the message prefix (timestamp, log level) */
size_t prefix_len = 1;
/* Add timestamp if configured */
if (PRINT_TIMESTAMP) {
prefix_len += snprintf(log_msg.message + prefix_len, 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();
}
}
}

View File

@ -0,0 +1,93 @@
/*
* 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 <stdint.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
/* Configuration */
#define MAX_MESSAGE_LENGTH 384
#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, ...);
#ifdef LOG_PREFIX
#define log_message(level, fmt, ...) \
log_message(level, LOG_PREFIX fmt, ##__VA_ARGS__)
#endif
[[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 */

View File

@ -0,0 +1,30 @@
/*
* 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
#ifdef SWO_LOG_PREFIX
#define LOG_PREFIX SWO_LOG_PREFIX
#endif
#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 */

View File

@ -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
}

View File

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

View File

@ -1,6 +1,9 @@
#include "can.h"
#include "imd_monitoring.h"
#include "isotp.h"
#include "isotp_log_backend.h"
#include "log.h"
#include "main.h"
#include "shunt_monitoring.h"
#include "battery.h"
@ -12,6 +15,14 @@
#include <math.h>
#include <stdint.h>
bool isotp_can_transmit(uint16_t id, const uint8_t *data, size_t datalen) {
return ftcan_transmit(id, data, datalen) == HAL_OK;
}
uint32_t isotp_can_get_time() {
return HAL_GetTick();
}
void can_init(FDCAN_HandleTypeDef *handle) {
ftcan_init(handle);
ftcan_add_filter(CAN_ID_SHUNT_BASE, 0xFF0);
@ -25,7 +36,7 @@ HAL_StatusTypeDef can_send_status() {
uint8_t data[8];
data[0] = ts_state.current_state | (sdc_closed << 7);
data[1] = roundf(current_soc);
ftcan_marshal_unsigned(&data[2], min_voltage, 2); //was declared in slave_monitoring.c
ftcan_marshal_unsigned(&data[2], min_voltage, 2);
ftcan_marshal_signed(&data[4], max_temp, 2);
data[6] = imd_data.state | (imd_data.ok << 7);
if (imd_data.r_iso < 0xFFF) {
@ -50,24 +61,21 @@ HAL_StatusTypeDef can_send_error(TSErrorKind kind, uint8_t arg) {
return ftcan_transmit(CAN_ID_AMS_ERROR, data, sizeof(data));
}
void ftcan_msg_received_cb(uint16_t id, size_t, const uint8_t *data) {
void ftcan_msg_received_cb(uint16_t id, size_t len, const uint8_t *data) {
if ((id & 0xFF0) == CAN_ID_SHUNT_BASE) {
shunt_handle_can_msg(id, data);
return;
}
// else if ((id & 0xFF0) == CAN_ID_SLAVE_STATUS_BASE) {
// slaves_handle_status(data);
// return;
// }
// switch (id) {
// case CAN_ID_SLAVE_PANIC:
// slaves_handle_panic(data);
// break;
// case CAN_ID_SLAVE_LOG:
// slaves_handle_log(data);
// break;
// case CAN_ID_AMS_IN:
// ts_sm_handle_ams_in(data);
// break;
// }
}
switch (id) {
case CAN_ID_LOG_FC:
auto status = isotp_handle_flow_control(ISOTP_LOG_CAN_ID, data, len);
if (status != ISOTP_OK) {
log_debug("Error when handling flow control: %s", isotp_status_to_string(status));
}
break;
case CAN_ID_AMS_IN:
ts_sm_handle_ams_in(data);
break;
}
}

View File

@ -22,6 +22,8 @@
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "battery.h"
#include "isotp.h"
#include "isotp_log_backend.h"
#define SWO_LOG_PREFIX "[MAIN] "
#include "swo_log.h"
@ -86,23 +88,22 @@ 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
#define ISO_TP_UPDATE_PERIOD 1
static void loop_delay() {
static uint32_t last_loop = 0;
uint32_t dt = HAL_GetTick() - last_loop;
if (dt < MAIN_LOOP_PERIOD) {
HAL_Delay(MAIN_LOOP_PERIOD - dt);
//HAL_GPIO_WritePin(STATUS2_GPIO_Port, STATUS2_Pin, GPIO_PIN_RESET);
} else {
//HAL_GPIO_WritePin(STATUS2_GPIO_Port, STATUS2_Pin, GPIO_PIN_SET);
while (dt < MAIN_LOOP_PERIOD) {
HAL_Delay(ISO_TP_UPDATE_PERIOD);
isotp_update(); // make sure isotp messages are processed reasonably quickly
dt = HAL_GetTick() - last_loop;
}
last_loop = HAL_GetTick();
}
@ -128,6 +129,20 @@ 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();
isotp_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 +184,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...");
@ -182,6 +198,10 @@ int main(void)
// init for master functions
can_init(&hfdcan1);
// for testing. in the final code can log streaming will be enabled by can message
isotp_log_enable_streaming(LOG_LEVEL_INFO);
shunt_init();
ts_sm_init();
soc_init();
@ -196,6 +216,7 @@ int main(void)
int error_count = 0;
while (1)
{
isotp_update();
//left over from slave communication test, could be nicer and in an additional function !!
if (error_count > 25) {
debug_log(LOG_LEVEL_ERROR, "Too many errors, restarting BMS...");
@ -209,7 +230,6 @@ int main(void)
}
error_count = 0;
}
uint32_t lastTimestamp = HAL_GetTick();
update_sdc();
update_tsal_signals();
@ -226,6 +246,7 @@ int main(void)
soc_update();
imd_update();
can_send_status();
isotp_update();
loop_delay();
/* USER CODE END WHILE */

View File

@ -1,4 +1,5 @@
#include "imd_monitoring.h"
#include "log.h"
#include "main.h"
#include "soc_estimation.h"
#include "stm32h7xx_hal.h"
@ -8,49 +9,62 @@
#include "battery.h"
#define DEBUG_CHANNEL 1 // channel to output messages on
#include "swo_log.h"
#include "swo_log_backend.h"
void print_master_status() {
debug_clear_console();
auto backend = swo_log_get_backend();
debug_log(LOG_LEVEL_INFO, "------ AMS_Master on %s (%s), compiled at %s ------", COMMIT_BRANCH,
if (!backend->is_enabled(LOG_LEVEL_INFO)) {
return; // No need to print if the backend is not enabled for this log level
}
log_message_t log_msg = {};
#define swo_write(...) \
snprintf(log_msg.message, sizeof(log_msg.message), __VA_ARGS__); \
backend->write(&log_msg); \
// clear the console
backend->clear();
swo_write("------ AMS_Master on %s (%s), compiled at %s ------", COMMIT_BRANCH,
COMMIT_HASH, COMPILE_DATE);
debug_log(LOG_LEVEL_INFO, "\nGeneral:");
debug_log(LOG_LEVEL_INFO, " State: %s", TSStateToString(ts_state.current_state));
debug_log(LOG_LEVEL_INFO, " Target: %s", TSStateToString(ts_state.target_state));
debug_log(LOG_LEVEL_INFO, " Err Source: %s", ts_state.error_source == 0 ? "NONE" : (ts_state.error_source == 0b11 ? "SHUNT, SLAVE" : (ts_state.error_source == 0b01 ? "SHUNT" : "SLAVE")));
debug_log(LOG_LEVEL_INFO, " Err type: %s", ts_state.error_type == 0 ? "NONE" : (ts_state.error_type == 0x01 ? "SLAVE_TIMEOUT" : (ts_state.error_type == 0x02 ? "SLAVE_PANIC" : (ts_state.error_type == 0x03 ? "SHUNT_TIMEOUT" : (ts_state.error_type == 0x04 ? "SHUNT_OVERCURRENT" : (ts_state.error_type == 0x05 ? "SHUNT_OVERTEMP" : "UNKNOWN"))))));
debug_log(LOG_LEVEL_INFO, " HV active: %s", hv_active ? "YES" : "NO");
swo_write( "\nGeneral:");
swo_write( " State: %s", TSStateToString(ts_state.current_state));
swo_write( " Target: %s", TSStateToString(ts_state.target_state));
swo_write( " Err Source: %s", ts_state.error_source == 0 ? "NONE" : (ts_state.error_source == 0b11 ? "SHUNT, SLAVE" : (ts_state.error_source == 0b01 ? "SHUNT" : "SLAVE")));
swo_write(" Err type: %s", ts_state.error_type == 0 ? "NONE" : (ts_state.error_type == 0x01 ? "SLAVE_TIMEOUT" : (ts_state.error_type == 0x02 ? "SLAVE_PANIC" : (ts_state.error_type == 0x03 ? "SHUNT_TIMEOUT" : (ts_state.error_type == 0x04 ? "SHUNT_OVERCURRENT" : (ts_state.error_type == 0x05 ? "SHUNT_OVERTEMP" : "UNKNOWN"))))));
swo_write(" HV active: %s", hv_active ? "YES" : "NO");
debug_log(LOG_LEVEL_INFO, "\nRelay positions:");
debug_log(LOG_LEVEL_INFO, " SDC: %s", sdc_closed ? "CLOSED" : "OPEN");
debug_log(LOG_LEVEL_INFO, " Air-: %s", neg_air_closed ? "CLOSED" : "OPEN");
debug_log(LOG_LEVEL_INFO, " Air+: %s", pos_air_closed ? "CLOSED" : "OPEN");
debug_log(LOG_LEVEL_INFO, " Precharge: %s", precharge_closed ? "CLOSED" : "OPEN");
debug_log(LOG_LEVEL_INFO, " Precharge/Air+ sense: %s", pre_and_air_open ? "HIGH" : "LOW");
swo_write("\nRelay positions:");
swo_write(" SDC: %s", sdc_closed ? "CLOSED" : "OPEN");
swo_write(" Air-: %s", neg_air_closed ? "CLOSED" : "OPEN");
swo_write(" Air+: %s", pos_air_closed ? "CLOSED" : "OPEN");
swo_write(" Precharge: %s", precharge_closed ? "CLOSED" : "OPEN");
swo_write(" Precharge/Air+ sense: %s", pre_and_air_open ? "HIGH" : "LOW");
debug_log(LOG_LEVEL_INFO, "\nIMD data:");
debug_log(LOG_LEVEL_INFO, " State: %s", IMDStateToString(imd_data.state));
debug_log(LOG_LEVEL_INFO, " R_iso: %lu kOhm", imd_data.r_iso);
debug_log(LOG_LEVEL_INFO, " Frequency: %lu Hz", imd_data.freq);
debug_log(LOG_LEVEL_INFO, " Duty cycle: %lu %%", imd_data.duty_cycle);
debug_log(LOG_LEVEL_INFO, " Last high: %lu ms", imd_data.last_high);
debug_log(LOG_LEVEL_INFO, " OK: %s", imd_data.ok ? "YES" : "NO");
swo_write("\nIMD data:");
swo_write(" State: %s", IMDStateToString(imd_data.state));
swo_write(" R_iso: %lu kOhm", imd_data.r_iso);
swo_write(" Frequency: %lu Hz", imd_data.freq);
swo_write(" Duty cycle: %lu %%", imd_data.duty_cycle);
swo_write(" Last high: %lu ms", imd_data.last_high);
swo_write(" OK: %s", imd_data.ok ? "YES" : "NO");
debug_log(LOG_LEVEL_INFO, "\nShunt data:");
debug_log(LOG_LEVEL_INFO, " Voltage: %ld mV", shunt_data.voltage_bat);
debug_log(LOG_LEVEL_INFO, " Current: %ld mA", shunt_data.current);
debug_log(LOG_LEVEL_INFO, " Power: %ld W", shunt_data.power);
debug_log(LOG_LEVEL_INFO, " Energy: %ld Ws", shunt_data.energy);
debug_log(LOG_LEVEL_INFO, " Temp: %ld °C", shunt_data.busbartemp / 10);
debug_log(LOG_LEVEL_INFO, " Time delta: %lu ms", HAL_GetTick() - shunt_data.last_message);
swo_write("\nShunt data:");
swo_write(" Voltage: %ld mV", shunt_data.voltage_bat);
swo_write(" Current: %ld mA", shunt_data.current);
swo_write(" Power: %ld W", shunt_data.power);
swo_write(" Energy: %ld Ws", shunt_data.energy);
swo_write(" Temp: %ld °C", shunt_data.busbartemp / 10);
swo_write(" Time delta: %lu ms", HAL_GetTick() - shunt_data.last_message);
debug_log(LOG_LEVEL_INFO, "\nBattery data:");
debug_log(LOG_LEVEL_INFO, " Min/Max voltage: %d mV / %d mV", min_voltage, max_voltage);
debug_log(LOG_LEVEL_INFO, " Min/Max temp: %d °C / %d °C", min_temp, max_temp);
debug_log(LOG_LEVEL_INFO, " SoC: %.2f %%", current_soc);
debug_log(LOG_LEVEL_INFO, " Module data: Min V | Max V | Std Dev | Min T | Max T");
swo_write("\nBattery data:");
swo_write(" Min/Max voltage: %d mV / %d mV", min_voltage, max_voltage);
swo_write(" Min/Max temp: %d °C / %d °C", min_temp, max_temp);
swo_write(" SoC: %.2f %%", current_soc);
swo_write(" Module data: Min V | Max V | Std Dev | Min T | Max T");
auto max_std_dev = 0.0f;
for (size_t i = 0; i < N_BMS; i++) {
if (module_std_deviation[i] > max_std_dev) {
@ -62,20 +76,20 @@ void print_master_status() {
#define COLOR_MIN "\033[38;5;75m" //blue for min
#define COLOR_MAX "\033[38;5;203m" //red for max
#define COLOR_RESET "\033[0m"
debug_log(LOG_LEVEL_INFO, " %2zu: %s%5d mV%s | %s%5d mV%s | %s%.2f mV%s | %s%3d °C%s | %s%3d °C%s", i,
swo_write(" %2zu: %s%5d mV%s | %s%5d mV%s | %s%.2f mV%s | %s%3d °C%s | %s%3d °C%s", i,
(module_voltages[i].min == min_voltage) ? COLOR_MIN : "", (module_voltages[i].min), COLOR_RESET,
(module_voltages[i].max == max_voltage) ? COLOR_MAX : "", (module_voltages[i].max), COLOR_RESET,
(module_std_deviation[i] > max_std_dev) ? COLOR_MAX : "", module_std_deviation[i], COLOR_RESET,
(module_temps[i].min == min_temp) ? COLOR_MIN : "", (module_temps[i].min), COLOR_RESET,
(module_temps[i].max == max_temp) ? COLOR_MAX : "", (module_temps[i].max), COLOR_RESET);
#else
debug_log(LOG_LEVEL_INFO, " %2zu: %5d mV | %5d mV | %.2f mV | %3d °C | %3d °C", i,
swo_write(" %2zu: %5d mV | %5d mV | %.2f mV | %3d °C | %3d °C", i,
module_voltages[i].min, module_voltages[i].max,
module_std_deviation[i],
module_temps[i].min, module_temps[i].max);
#endif
}
debug_log(LOG_LEVEL_INFO, "\n------ Updated at %lu ------", HAL_GetTick());
swo_write("\n------ Updated at %lu ------", HAL_GetTick());
}

View File

@ -5,57 +5,71 @@
#include <string.h>
#define DEBUG_CHANNEL 2 // channel to output messages on
#include "swo_log.h"
#include "swo_log_backend.h"
void print_battery_info() {
debug_clear_console();
debug_log(LOG_LEVEL_INFO, "------ AMS_Master on %s (%s), compiled at %s ------\n", COMMIT_BRANCH,
auto backend = swo_log_get_backend();
if (!backend->is_enabled(LOG_LEVEL_INFO)) {
return; // No need to print if the backend is not enabled for this log level
}
log_message_t log_msg = {};
#define swo_write(...) \
snprintf(log_msg.message, sizeof(log_msg.message), __VA_ARGS__); \
backend->write(&log_msg); \
// clear the console
backend->clear();
swo_write("------ AMS_Master on %s (%s), compiled at %s ------\n", COMMIT_BRANCH,
COMMIT_HASH, COMPILE_DATE);
for (size_t i = 0; i < N_BMS; i++) {
debug_log(LOG_LEVEL_INFO, "Module %d status:", i);
debug_log(LOG_LEVEL_INFO, " BMS ID: 0x%08lx%08lx", (uint32_t)(modules[i].bmsID >> 32), (uint32_t)(modules[i].bmsID & 0xFFFFFFFF));
swo_write("Module %d status:", i);
swo_write(" BMS ID: 0x%08lx%08lx", (uint32_t)(modules[i].bmsID >> 32), (uint32_t)(modules[i].bmsID & 0xFFFFFFFF));
// Print cell voltages in 4x4 format
debug_log(LOG_LEVEL_INFO, " Cell voltages (mV):");
debug_log(LOG_LEVEL_INFO, " C0: %4d C1: %4d C2: %4d C3: %4d",
swo_write(" Cell voltages (mV):");
swo_write(" C0: %4d C1: %4d C2: %4d C3: %4d",
modules[i].cellVoltages[0], modules[i].cellVoltages[1],
modules[i].cellVoltages[2], modules[i].cellVoltages[3]);
debug_log(LOG_LEVEL_INFO, " C4: %4d C5: %4d C6: %4d C7: %4d",
swo_write(" C4: %4d C5: %4d C6: %4d C7: %4d",
modules[i].cellVoltages[4], modules[i].cellVoltages[5],
modules[i].cellVoltages[6], modules[i].cellVoltages[7]);
debug_log(LOG_LEVEL_INFO, " C8: %4d C9: %4d C10: %4d C11: %4d",
swo_write(" C8: %4d C9: %4d C10: %4d C11: %4d",
modules[i].cellVoltages[8], modules[i].cellVoltages[9],
modules[i].cellVoltages[10], modules[i].cellVoltages[11]);
debug_log(LOG_LEVEL_INFO, " C12: %4d C13: %4d C14: %4d C15: %4d",
swo_write(" C12: %4d C13: %4d C14: %4d C15: %4d",
modules[i].cellVoltages[12], modules[i].cellVoltages[13],
modules[i].cellVoltages[14], modules[i].cellVoltages[15]);
// Print GPIO values
debug_log(LOG_LEVEL_INFO, " GPIO voltages (mV):");
debug_log(LOG_LEVEL_INFO,
swo_write(" GPIO voltages (mV):");
swo_write(
" G0: %4d G1: %4d G2: %4d G3: %4d G4: %4d",
modules[i].auxVoltages[0], modules[i].auxVoltages[1],
modules[i].auxVoltages[2], modules[i].auxVoltages[3],
modules[i].auxVoltages[4]);
debug_log(LOG_LEVEL_INFO,
swo_write(
" G5: %4d G6: %4d G7: %4d G8: %4d G9: %4d",
modules[i].auxVoltages[5], modules[i].auxVoltages[6],
modules[i].auxVoltages[7], modules[i].auxVoltages[8],
modules[i].auxVoltages[9]);
// Print temperatures
debug_log(LOG_LEVEL_INFO, " GPIO as temperatures (°C):");
debug_log(LOG_LEVEL_INFO,
swo_write(" GPIO as temperatures (°C):");
swo_write(
" G0: %4d G1: %4d G2: %4d G3: %4d G4: %4d",
cellTemps[i][0], cellTemps[i][1], cellTemps[i][2],
cellTemps[i][3], cellTemps[i][4]);
debug_log(LOG_LEVEL_INFO,
swo_write(
" G5: %4d G6: %4d G7: %4d G8: %4d G9: %4d",
cellTemps[i][5], cellTemps[i][6], cellTemps[i][7],
cellTemps[i][8], cellTemps[i][9]);
debug_log(LOG_LEVEL_INFO,
swo_write(
" Internal temp: %d, VAnalog: %d, VDigital: %d, VRef: %d",
modules[i].internalDieTemp, modules[i].analogSupplyVoltage,
modules[i].digitalSupplyVoltage, modules[i].refVoltage);
@ -126,18 +140,11 @@ void print_battery_info() {
hasFlags = true;
}
debug_log(LOG_LEVEL_INFO, " Status flags: %s",
swo_write(" Status flags: %s",
hasFlags ? flagBuffer : "[none]");
debug_log(LOG_LEVEL_INFO, " Conversion counter: %d",
swo_write(" Conversion counter: %d",
modules[i].status.CCTS);
// Check for over/under voltage
if (modules[i].overVoltage || modules[i].underVoltage) {
debug_log(LOG_LEVEL_WARNING,
" Module %d voltage issues - OV: 0x%08lX, UV: 0x%08lX", i,
modules[i].overVoltage, modules[i].underVoltage);
}
}
debug_log(LOG_LEVEL_INFO, "\n------ Updated at %lu ------", HAL_GetTick());
swo_write("\n------ Updated at %lu ------", HAL_GetTick());
}