added Function to create CAN-Traces from WebUI
Some checks failed
CI-Build/Kettenoeler/pipeline/head There was a failure building this commit

This commit is contained in:
2025-08-26 23:31:35 +02:00
parent c8c67551fd
commit 98629b744d
10 changed files with 1011 additions and 123 deletions

View File

@@ -1,24 +1,42 @@
#include "can_hal.h"
#include "dtc.h"
// #include "debugger.h" // optional für Logs
#include <string.h> // memcpy, memcmp
// ==== Interner Zustand/Helper ====
// =====================
// Interner Zustand/Helper
// =====================
MCP_CAN CAN0(GPIO_CS_CAN);
static bool s_ready = false;
static bool s_ready = false;
static uint8_t s_nextFiltSlot = 0; // 0..5 (MCP2515 hat 6 Filter-Slots)
static uint16_t s_modeSettleMs = 10; // Default aus Config
// Trace-Hook
static CanTraceSink s_traceSink = nullptr;
// RAW-Sniffer-Steuerung (Filter offen + Restore der vorherigen Konfig)
static bool s_rawSnifferEnabled = false;
// Spiegel der "Normal"-Konfiguration (damit wir nach RAW wiederherstellen können)
static uint16_t s_savedStdMask[2] = {0x000, 0x000};
static struct
{
uint32_t id;
bool ext;
} s_savedFilt[6];
static uint8_t s_savedFiltCount = 0;
// 11-bit: Lib erwartet (value << 16)
static inline uint32_t _std_to_hw(uint16_t v11) { return ((uint32_t)v11) << 16; }
// „Bestätigter“ Mode-Wechsel mithilfe der Lib-Funktion setMode(newMode)
// Viele Forks nutzen intern mcp2515_requestNewMode(); wir retryen kurz.
static bool _trySetMode(uint8_t mode, uint16_t settleMs)
{
const uint32_t t0 = millis();
do {
if (CAN0.setMode(mode) == CAN_OK) return true;
do
{
if (CAN0.setMode(mode) == CAN_OK)
return true;
delay(1);
} while ((millis() - t0) < settleMs);
return false;
@@ -27,22 +45,32 @@ static bool _trySetMode(uint8_t mode, uint16_t settleMs)
// LOOPBACK-Selftest (ohne Bus)
static bool _selftest_loopback(uint16_t windowMs)
{
if (!_trySetMode(MCP_LOOPBACK, s_modeSettleMs)) return false;
if (!_trySetMode(MCP_LOOPBACK, s_modeSettleMs))
return false;
const unsigned long tid = 0x123;
uint8_t tx[8] = {0xA5, 0x5A, 0x11, 0x22, 0x33, 0x44, 0x77, 0x88};
if (CAN0.sendMsgBuf(tid, 0, 8, tx) != CAN_OK) {
if (CAN0.sendMsgBuf(tid, 0, 8, tx) != CAN_OK)
{
(void)_trySetMode(MCP_NORMAL, s_modeSettleMs);
return false;
}
bool got = false;
const uint32_t t0 = millis();
while ((millis() - t0) < windowMs) {
if (CAN0.checkReceive() == CAN_MSGAVAIL) {
unsigned long rid; uint8_t len, rx[8];
if (CAN0.readMsgBuf(&rid, &len, rx) == CAN_OK) {
if (rid == tid && len == 8 && memcmp(tx, rx, 8) == 0) { got = true; break; }
while ((millis() - t0) < windowMs)
{
if (CAN0.checkReceive() == CAN_MSGAVAIL)
{
unsigned long rid;
uint8_t len, rx[8];
if (CAN0.readMsgBuf(&rid, &len, rx) == CAN_OK)
{
if (rid == tid && len == 8 && memcmp(tx, rx, 8) == 0)
{
got = true;
break;
}
}
}
delay(1);
@@ -55,60 +83,153 @@ static bool _selftest_loopback(uint16_t windowMs)
// Optional: kurzer ListenOnly-Hörtest (nur Heuristik, keine DTC-Änderung)
static void _probe_listen_only(uint16_t ms)
{
if (ms == 0) return;
if (!_trySetMode(MCP_LISTENONLY, s_modeSettleMs)) return;
if (ms == 0)
return;
if (!_trySetMode(MCP_LISTENONLY, s_modeSettleMs))
return;
const uint32_t t0 = millis();
while ((millis() - t0) < ms) {
if (CAN0.checkReceive() == CAN_MSGAVAIL) break;
while ((millis() - t0) < ms)
{
if (CAN0.checkReceive() == CAN_MSGAVAIL)
break;
delay(1);
}
(void)_trySetMode(MCP_NORMAL, s_modeSettleMs);
}
// ==== Öffentliche API ====
bool CAN_HAL_Init(const CanHalConfig& cfg)
// Offen konfigurieren (RAW-Sniffer)
static bool _apply_open_filters()
{
s_ready = false;
s_modeSettleMs = cfg.modeSettleMs ? cfg.modeSettleMs : 10;
// 1) SPI/MCP starten
// HIER: MCP_STDEXT statt MCP_STD, damit die Lib nicht ins default/Failure läuft
if (CAN0.begin(MCP_STDEXT, cfg.baud, cfg.clock) != CAN_OK) {
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
if (!_trySetMode(MODE_CONFIG, s_modeSettleMs))
return false;
}
// 2) LoopbackSelftest (ohne Bus)
if (!_selftest_loopback(20)) {
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
// 3) Optional ListenOnlyProbe (nur Info)
_probe_listen_only(cfg.listenOnlyProbeMs);
// 4) Default: Filter/Masks neutral, Mode NORMAL
// -> Für Masken/Filter müssen wir in CONFIG sein (hier: MODE_CONFIG laut deiner Lib)
if (!_trySetMode(MODE_CONFIG, s_modeSettleMs)) {
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
// weit offen (STD)
// Masken 0 -> alles durchlassen
CAN0.init_Mask(0, 0, _std_to_hw(0x000));
CAN0.init_Mask(1, 0, _std_to_hw(0x000));
for (uint8_t i = 0; i < 6; ++i) {
// Filter egal
for (uint8_t i = 0; i < 6; ++i)
{
CAN0.init_Filt(i, 0, _std_to_hw(0x000));
}
s_nextFiltSlot = 0;
if (!_trySetMode(MCP_NORMAL, s_modeSettleMs)) {
return _trySetMode(MCP_NORMAL, s_modeSettleMs);
}
// Gespeicherte Normal-Konfiguration anwenden
static bool _apply_saved_filters()
{
if (!_trySetMode(MODE_CONFIG, s_modeSettleMs))
return false;
CAN0.init_Mask(0, 0, _std_to_hw(s_savedStdMask[0]));
CAN0.init_Mask(1, 0, _std_to_hw(s_savedStdMask[1]));
// Erst alle Filter neutralisieren
for (uint8_t i = 0; i < 6; ++i)
{
CAN0.init_Filt(i, 0, _std_to_hw(0x000));
}
// Dann gespeicherte Filter wieder setzen
s_nextFiltSlot = 0;
for (uint8_t i = 0; i < s_savedFiltCount && s_nextFiltSlot < 6; ++i)
{
const auto &F = s_savedFilt[i];
const uint32_t hwId = F.ext ? F.id : _std_to_hw((uint16_t)F.id);
CAN0.init_Filt(s_nextFiltSlot++, F.ext ? 1 : 0, hwId);
}
return _trySetMode(MCP_NORMAL, s_modeSettleMs);
}
// =====================
// Öffentliche API
// =====================
void CAN_HAL_SetTraceSink(CanTraceSink sink)
{
s_traceSink = sink;
}
void CAN_HAL_EnableRawSniffer(bool enable)
{
if (enable == s_rawSnifferEnabled)
return;
if (enable)
{
// Auf RAW öffnen
if (_apply_open_filters())
{
s_rawSnifferEnabled = true;
}
else
{
// Falls es nicht klappt, lieber Defekt melden als im Zwischending zu bleiben
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
}
}
else
{
// Gespeicherte "Normal"-Konfiguration wieder aktivieren
if (_apply_saved_filters())
{
s_rawSnifferEnabled = false;
}
else
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
}
}
}
bool CAN_HAL_IsRawSnifferEnabled()
{
return s_rawSnifferEnabled;
}
bool CAN_HAL_Init(const CanHalConfig &cfg)
{
s_ready = false;
s_modeSettleMs = cfg.modeSettleMs ? cfg.modeSettleMs : 10;
s_traceSink = nullptr;
s_rawSnifferEnabled = false;
// 1) SPI/MCP starten (STDEXT ist robust gegen Fehlpfade in Lib-Forks)
if (CAN0.begin(MCP_STDEXT, cfg.baud, cfg.clock) != CAN_OK)
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
// Erfolgreich
// 2) Loopback-Selftest (ohne Bus)
if (!_selftest_loopback(20))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
// 3) Optional Listen-Only-Probe (nur Info)
_probe_listen_only(cfg.listenOnlyProbeMs);
// 4) Default: Filter/Masks offen, Mode NORMAL
if (!_apply_open_filters())
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
// Initiale "Normal"-Spiegelung: alles offen (bis die App später echte Filter setzt)
s_savedStdMask[0] = 0x000;
s_savedStdMask[1] = 0x000;
s_savedFiltCount = 0;
for (uint8_t i = 0; i < 6; ++i)
{
s_savedFilt[i].id = 0;
s_savedFilt[i].ext = false;
}
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, false);
s_ready = true;
return true;
@@ -119,18 +240,30 @@ bool CAN_HAL_IsReady() { return s_ready; }
bool CAN_HAL_SetMode(uint8_t mode)
{
const bool ok = _trySetMode(mode, s_modeSettleMs);
if (!ok) MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
if (!ok)
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return ok;
}
bool CAN_HAL_SetMask(uint8_t bank, bool ext, uint32_t rawMask)
{
if (bank > 1) return false;
if (!CAN_HAL_SetMode(MODE_CONFIG)) return false;
if (bank > 1)
return false;
if (!CAN_HAL_SetMode(MODE_CONFIG))
return false;
const bool ok = (CAN0.init_Mask(bank, ext ? 1 : 0, rawMask) == CAN_OK);
if (!CAN_HAL_SetMode(MCP_NORMAL)) {
// Spiegeln (nur STD-11 Spiegel führen wir ext-Masken selten; bei ext ignorieren)
if (!ext)
{
const uint16_t m11 = (uint16_t)(rawMask >> 16);
s_savedStdMask[bank] = m11;
}
if (!CAN_HAL_SetMode(MCP_NORMAL))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
@@ -144,85 +277,170 @@ bool CAN_HAL_SetStdMask11(uint8_t bank, uint16_t mask11)
void CAN_HAL_ClearFilters()
{
if (!CAN_HAL_SetMode(MODE_CONFIG)) {
if (!CAN_HAL_SetMode(MODE_CONFIG))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return;
}
CAN0.init_Mask(0, 0, _std_to_hw(0x000));
CAN0.init_Mask(1, 0, _std_to_hw(0x000));
for (uint8_t i = 0; i < 6; ++i) {
for (uint8_t i = 0; i < 6; ++i)
{
CAN0.init_Filt(i, 0, _std_to_hw(0x000));
}
s_nextFiltSlot = 0;
if (!CAN_HAL_SetMode(MCP_NORMAL)) {
// Spiegel auch zurücksetzen
s_savedStdMask[0] = 0x000;
s_savedStdMask[1] = 0x000;
s_savedFiltCount = 0;
for (uint8_t i = 0; i < 6; ++i)
{
s_savedFilt[i].id = 0;
s_savedFilt[i].ext = false;
}
if (!CAN_HAL_SetMode(MCP_NORMAL))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
}
}
bool CAN_HAL_AddFilter(const CanFilter& f)
bool CAN_HAL_AddFilter(const CanFilter &f)
{
if (s_nextFiltSlot >= 6) return false;
if (!CAN_HAL_SetMode(MODE_CONFIG)) {
if (s_nextFiltSlot >= 6)
return false;
if (!CAN_HAL_SetMode(MODE_CONFIG))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
const uint32_t hwId = f.ext ? f.id : _std_to_hw((uint16_t)f.id);
const uint8_t slot = s_nextFiltSlot++;
const bool ok = (CAN0.init_Filt(slot, f.ext ? 1 : 0, hwId) == CAN_OK);
const uint8_t slot = s_nextFiltSlot++;
const bool ok = (CAN0.init_Filt(slot, f.ext ? 1 : 0, hwId) == CAN_OK);
if (!CAN_HAL_SetMode(MCP_NORMAL)) {
// Spiegeln
if (ok)
{
if (s_savedFiltCount < 6)
{
s_savedFilt[s_savedFiltCount].id = f.ext ? f.id : (uint32_t)((uint16_t)f.id);
s_savedFilt[s_savedFiltCount].ext = f.ext;
++s_savedFiltCount;
}
}
if (!CAN_HAL_SetMode(MCP_NORMAL))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
return ok;
}
bool CAN_HAL_SetFilters(const CanFilter* list, size_t count)
bool CAN_HAL_SetFilters(const CanFilter *list, size_t count)
{
if (!CAN_HAL_SetMode(MODE_CONFIG)) {
if (!CAN_HAL_SetMode(MODE_CONFIG))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
// Slots zurücksetzen
s_nextFiltSlot = 0;
for (uint8_t i = 0; i < 6; ++i) {
for (uint8_t i = 0; i < 6; ++i)
{
CAN0.init_Filt(i, 0, _std_to_hw(0x000));
}
// Setzen
for (size_t i = 0; i < count && s_nextFiltSlot < 6; ++i) {
const auto& f = list[i];
for (size_t i = 0; i < count && s_nextFiltSlot < 6; ++i)
{
const auto &f = list[i];
const uint32_t hwId = f.ext ? f.id : _std_to_hw((uint16_t)f.id);
CAN0.init_Filt(s_nextFiltSlot++, f.ext ? 1 : 0, hwId);
}
if (!CAN_HAL_SetMode(MCP_NORMAL)) {
// Spiegel aktualisieren
s_savedFiltCount = 0;
for (size_t i = 0; i < count && i < 6; ++i)
{
s_savedFilt[i].id = list[i].ext ? list[i].id : (uint32_t)((uint16_t)list[i].id);
s_savedFilt[i].ext = list[i].ext;
++s_savedFiltCount;
}
if (!CAN_HAL_SetMode(MCP_NORMAL))
{
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
return true;
}
bool CAN_HAL_Read(unsigned long& id, uint8_t& len, uint8_t data[8])
bool CAN_HAL_Read(unsigned long &id, uint8_t &len, uint8_t data[8])
{
if (CAN0.checkReceive() != CAN_MSGAVAIL) return false;
if (CAN0.readMsgBuf(&id, &len, data) != CAN_OK) {
if (CAN0.checkReceive() != CAN_MSGAVAIL)
return false;
if (CAN0.readMsgBuf(&id, &len, data) != CAN_OK)
{
// Echte Lese-Fehler -> vermutlich SPI/Controller-Problem
MaintainDTC(DTC_CAN_TRANSCEIVER_FAILED, true);
return false;
}
// MCP_CAN schreibt Flags in das ID-Wort:
// bit31 = EXT, bit30 = RTR, Rest = rohe ID (11 oder 29 Bit)
const bool ext = (id & 0x80000000UL) != 0;
const bool rtr = (id & 0x40000000UL) != 0; // aktuell nur informativ
// "Saubere" ID für Aufrufer herstellen
const uint32_t clean_id = ext ? (id & 0x1FFFFFFFUL) : (id & 0x7FFUL);
id = clean_id;
// Trace-Hook (RX)
if (s_traceSink)
{
CanLogFrame f{};
f.ts_ms = millis();
f.id = clean_id;
f.ext = ext;
f.rx = true;
f.dlc = len;
if (len)
memcpy(f.data, data, len);
s_traceSink(f);
}
// Optional: Wenn du RTR-Frames speziell behandeln willst, könntest du hier
// (rtr==true) markieren/loggen oder len=0 erzwingen. Für jetzt: einfach durchreichen.
(void)rtr;
return true;
}
uint8_t CAN_HAL_Send(unsigned long id, bool ext, uint8_t len, const uint8_t* data)
uint8_t CAN_HAL_Send(unsigned long id, bool ext, uint8_t len, const uint8_t *data)
{
// Sende-Fehler (CAN_FAILTX) müssen nicht zwingend Transceiver-Defekte sein (z. B. Bus-Off).
// Höhere Ebene kann bei Bedarf DTCs setzen. Hier nur durchreichen.
return CAN0.sendMsgBuf(id, ext ? 1 : 0, len, const_cast<uint8_t*>(data));
// Senden
uint8_t st = CAN0.sendMsgBuf(id, ext ? 1 : 0, len, const_cast<uint8_t *>(data));
// Trace-Hook (TX) nur bei Erfolg loggen optional: immer loggen
if (st == CAN_OK && s_traceSink)
{
CanLogFrame f{};
f.ts_ms = millis();
f.id = id;
f.ext = ext;
f.rx = false;
f.dlc = len;
if (len)
memcpy(f.data, data, len);
s_traceSink(f);
}
return st;
}
// ==== Diagnose/Utilities ====
@@ -233,7 +451,7 @@ uint8_t CAN_HAL_GetErrorFlags()
return CAN0.getError();
}
void CAN_HAL_GetErrorCounters(uint8_t& tec, uint8_t& rec)
void CAN_HAL_GetErrorCounters(uint8_t &tec, uint8_t &rec)
{
tec = CAN0.errorCountTX();
rec = CAN0.errorCountRX();