Compare commits
4 Commits
4ecb91ba39
...
765f1e5ee1
Author | SHA1 | Date | |
---|---|---|---|
765f1e5ee1 | |||
354b6dc453 | |||
99eefa4283 | |||
9cc314ad7c |
@ -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);
|
||||
|
@ -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
|
255
AMS_Master_Code/Core/Lib/isotp/isotp.c
Normal file
255
AMS_Master_Code/Core/Lib/isotp/isotp.c
Normal 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;
|
||||
}
|
||||
|
41
AMS_Master_Code/Core/Lib/isotp/isotp.h
Normal file
41
AMS_Master_Code/Core/Lib/isotp/isotp.h
Normal 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
|
194
AMS_Master_Code/Core/Lib/logger/isotp_log_backend.c
Normal file
194
AMS_Master_Code/Core/Lib/logger/isotp_log_backend.c
Normal 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]);
|
||||
}
|
28
AMS_Master_Code/Core/Lib/logger/isotp_log_backend.h
Normal file
28
AMS_Master_Code/Core/Lib/logger/isotp_log_backend.h
Normal 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 */
|
114
AMS_Master_Code/Core/Lib/logger/log.c
Normal file
114
AMS_Master_Code/Core/Lib/logger/log.c
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
93
AMS_Master_Code/Core/Lib/logger/log.h
Normal file
93
AMS_Master_Code/Core/Lib/logger/log.h
Normal 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 */
|
30
AMS_Master_Code/Core/Lib/logger/swo_log.h
Normal file
30
AMS_Master_Code/Core/Lib/logger/swo_log.h
Normal 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 */
|
136
AMS_Master_Code/Core/Lib/logger/swo_log_backend.c
Normal file
136
AMS_Master_Code/Core/Lib/logger/swo_log_backend.c
Normal 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
|
||||
}
|
35
AMS_Master_Code/Core/Lib/logger/swo_log_backend.h
Normal file
35
AMS_Master_Code/Core/Lib/logger/swo_log_backend.h
Normal 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 */
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user