Einleitung
Im Rahmen der Veranstaltung Batterie- und Energiemanagement im Sommersemester 2025 habe ich das Batteriepack aus dem Dogscooter zu einem vollwertigeren Batteriemanagementsystem erweitert. Bestand war ein 13s3p Batteriepack mit einem Daly-BMS. Ziel der Übungsveranstaltung war es, das Batteriepack durch folgende Arbeitspakete zu erweitern:
- Ansteuerungsschaltung eines Schützes
- Auslesen eines Stromsensors von Infineon TLI4971
- Auslesen des Daly-BMS über die UART Schnittstelle
Folgende Arbeitspakete kamen für mich noch hinzu:
- Implementierung eines Live-Plotters mit Python zum Auslesen der vom Daly ausgelesenen Werte und Speicherung in einer CSV Datei
- Automatische Schütz-Abschaltung und aktive Ansteuerung über einen Button
Alle Arbeitspakete wurden bei der Vorstellung am 15.07.2025 erfolgreich vorgestellt. Diese Dokumentation entstand freiwillig für nachfolgende Studenten.
Inhaltsverzeichnis
Gesamtsystem
Auslesen des Daly-BMS über die UART
Für das Daly BMS wurde schon eine ausführliche Bibliothek gepflegt, wo alle Register des Daly bequem ausgelesen werden können. Wir müssen nun den Mikrocontroller unser Wahl mit der UART Schnittstelle unseres Dalys verbinden. Hierfür brauchen wir nur die TX, RX und GND Pins. In meinem Projekt habe ich mich für den ESP32 von AZdelivery entschieden.
Nun müssen wir das Daly auslesen. Wie bereits erwähnt hat uns da ein fleißiger Student(?) die Arbeit schon abgenommen. Unter dem Link: https://github.com/maland16/daly-bms-uart können wir ein Github Projekt zugreifen.
Hier gibt es eine daly-bms-uart.cpp und die dazugehörige daly-bms-uart.h Datei. Darin befinden sich alle Befehle zum Auslesen des Dalys. Man kann aber auch zuerst in das Datenblatt von Daly schauen. Dort ist das UART Protokoll erklärt.
Hier wird beschrieben wie das UART Telegramm aufgebaut ist. Hierbei muss die DataID durch die entsprechende ID ersetzt werden, was uns interessiert.
Hierrüber kann je nach ID der dazugehörige Wert abgefragt werden. Ich habe im Folgenden Handschriftlich ausgewählte Register erläutert.








Schützansteuerung
Für die Schützansteuerung müssen wir ein Schütz mithilfe der 3.3 Volt vom ESP32 ansteuern. Die Versorgunsgsspannung hierbei lag bei 12 Volt von einer RND Quelle.
Hierbei ist der erste BC648 und der IFR630 zur Ansteuerung selbst da und der zweite BC648 ganz links als Inverter gedacht. Hierbei wird einfach der Ausgangspin auf High oder Low gesetzt zur Ansteuerung.
Stromsensor TLI4971 Infineon
Der Stromsensor gibt eine Differenzspannung je nach angelegter Stromstärke aus. Bei meinen Messungen waren es ca. 50mV pro Ampere.
Diese müssen intern umgerechnet werden. Für die Ansteuerung habe ich im Datenblatt eine Schaltung gefunden, um die Ergebnisse zu optimieren.
CAN Kommunikation
In meinem Projekt kommunizieren die 3 ESP32 über CAN miteinander. Dabei ist ein ESP für die Schützansteuerung zuständig, der zweite zum Auslesen des Stromsensors und der letzte zum Auslesen der BMS Werte über Daly und der Anzeige auf einem LCD Display.
Das Gesamtsystem sah wie folgt aus:
Hier ein Foto wie es in echt aussah:
Hierbei schickt der Sensor ESP die Messwerte des Stromsensors. Er schickt 4 Datenbytes mit der ID 0x100 über CAN. Hierbei empfängt der BMS ESP die Werte und wandelt diese über die IEEE754 Float Norm in einen Wert zurück. Hier in Beispiel wie die Kommunikation mit einem Oszilloskop aussieht:
Diese Werte müssen noch umgewandelt. Hier ein Rechenbeispiel:
Der ESP32 zur Schützansteuerung schickt den Status seiner beiden Ausgänge über CAN ebenfalls. Mit der ID 200 und 2 Bytes schickt er die Daten um das Display zu aktualisieren. Der ESP32 Daly/Display schickt ein Steuersignal an das Schütz mit der ID 0x101 wenn der Stromwert über 10A kommt und schaltet somit über CAN das Schütz ab.
MCP2515
Der ESP32 hat vom Werk aus KEINEN CAN-Transceiver. Deshalb wurden 5 Stück bestellt. Diese benutzen die SPI Ausgänge des ESP32 und wandeln diese in eine funktionierende CAN Schnittstelle um. Und auch dafür gibt es die Bibliothek MCP_Can welche uns die Kommunikation abnimmt.
Messfehler
Manchmal treten bei der Messung Fehler auf. Diese habe ich mir mal genauer angeschaut.
Intern wird über die Bibliothek der RX Buffer jedes Mal geleert und er Prüft auch die Checksum. Es werden einfach fehlerhafte Daten geschickt.
Webserver JSON
Ich schicke die Daten auf einen Webserver welcher dann diese für den Python Plotter bereitstellt.
Mithilfe des ESP32 kann ich dann eine Oberfläche erstellen, wo ich mir die Ausgelesenen Werte direkt auf einer Website darstellen kann.
Der Code dafür sieht folgendermaßen aus:
Die Werte werden somit periodisch ausgelesen und auf den Webserver geschickt dieser sieht dann so aus:
Code - Stromsensor
#include <SPI.h> // Für die Kommunikation mit dem MCP2515 CAN-Modul
#include <mcp_can.h> // CAN-Bibliothek für MCP2515
// ---------- Pin-Definitionen ----------
#define CAN_CS 5 // Chip Select Pin für das CAN-Modul (MCP2515)
#define CAN_INT 4 // Interrupt-Pin für das CAN-Modul
const int pinVOUT = 32; // ADC-Eingang für VOUT (Spannungsabfall über Shunt)
const int pinVREF = 33; // ADC-Eingang für VREF (Referenzspannung)
const float adcMax = 4095.0; // Maximale ADC-Ausgabe (12 Bit ADC)
const float vcc = 3.3; // Versorgungsspannung des ADC (in Volt)
const int N = 200; // Anzahl der Messungen zur Mittelwertbildung
const int delayMs = 5; // Wartezeit zwischen zwei ADC-Messungen (in ms)
const float m = 0.0206; // Steigung für die Stromberechnung (Kalibrierung)
const float b = -0.1182; // Offset für die Stromberechnung (Kalibrierung)
MCP_CAN CAN(CAN_CS); // CAN-Objekt
// ---------- Initialisierung ----------
void setup() {
Serial.begin(115200); // Serielle Konsole starten
// CAN-Modul initialisieren (500 kbit/s, 8 MHz Quarz)
if (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK) {
Serial.println("CAN BUS Shield init ok!");
} else {
Serial.println("CAN BUS Shield init fail");
while (1); // Stoppe das Programm, falls CAN nicht startet
}
CAN.setMode(MCP_NORMAL); // CAN in Normalbetrieb setzen
}
// ---------- Hauptprogrammschleife ----------
void loop() {
float sumVOUT = 0, sumVREF = 0; // Summen für gleitenden Mittelwert
// --- Mehrfachmessung zur Mittelwertbildung ---
for (int i = 0; i < N; i++) {
sumVOUT += analogRead(pinVOUT); // Lese ADC-Wert für VOUT
sumVREF += analogRead(pinVREF); // Lese ADC-Wert für VREF
delay(delayMs); // Kurze Wartezeit zwischen Messungen
}
// --- Durchschnitt berechnen ---
float avgVOUT = sumVOUT / N; // Mittelwert VOUT
float avgVREF = sumVREF / N; // Mittelwert VREF
// --- ADC-Werte in Spannung umrechnen ---
float voltageVOUT = (avgVOUT / adcMax) * vcc; // VOUT in Volt
float voltageVREF = (avgVREF / adcMax) * vcc; // VREF in Volt
// --- Umrechnung in Millivolt ---
float voltageVOUT_mV = voltageVOUT * 1000.0;
float voltageVREF_mV = voltageVREF * 1000.0;
// --- Spannungsabfall berechnen ---
float spannungsabfall = voltageVOUT_mV - voltageVREF_mV; // Differenz (mV)
// --- Strom berechnen (Kalibrierte Formel) ---
float current = m * spannungsabfall + b; // Strom in Ampere
// --- Seriell ausgeben ---
Serial.print("Gemessener Strom: ");
Serial.print(current, 3);
Serial.println(" A (wird gesendet)");
// --- Stromwert als 4 Byte Float für CAN vorbereiten ---
byte currentBytes[4];
memcpy(currentBytes, ¤t, sizeof(current)); // Float in Byte-Array kopieren
// --- Stromwert per CAN senden (ID 0x100, 4 Byte) ---
if (CAN.sendMsgBuf(0x100, 0, 4, currentBytes) == CAN_OK) {
Serial.println("CAN: Stromwert gesendet");
} else {
Serial.println("CAN: Fehler beim Senden");
}
delay(1000); // Warte 1 Sekunde bis zur nächsten Messung
}
✅ Copied!
Code - Display
#include <Arduino.h>
#include <daly-bms-uart.h>
#include <WiFi.h> // WLAN-Funktionen für ESP32
#include <WebServer.h> // Webserver für ESP32
#include <ArduinoJson.h> // JSON-Handling für Webserver
#include <SPI.h> // SPI-Kommunikation (für CAN-Modul)
#include <mcp_can.h> // CAN-Bus Bibliothek für MCP2515
#include <Wire.h> // I2C-Kommunikation (z.B. für LCD)
#include <LiquidCrystal_I2C.h> // LCD-Display über I2C
// ---------- Hardware-Pin-Definitionen ----------
// UART-Pins für die Verbindung zum BMS
#define RX1 16
#define TX1 17
Daly_BMS_UART bms(Serial1); // Daly BMS Objekt auf Serial1
// CAN-Bus Pins
#define CAN_CS 5 // Chip Select Pin für MCP2515
#define CAN_INT 4 // Interrupt-Pin für MCP2515
MCP_CAN CAN(CAN_CS); // CAN-Objekt
// LCD Display (I2C-Adresse 0x27, 16 Zeichen, 2 Zeilen)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ---------- WiFi & Webserver ----------
const char* ssid = "BMS-Monitor"; // Name des WLANs (Access Point)
const char* password = "12345678"; // Passwort für WLAN
WebServer server(80); // Webserver auf Port 80
// ---------- Globale Variablen ----------
float current_sensor = 0.0; // Wert des externen Stromsensors (über CAN)
bool s1_status = false; // Status Schütz 1 (z.B. Hauptschütz)
bool s2_status = false; // Status invertierter Ausgang (z.B. Vorladung)
// Fehlerflags (bleiben bis zum Reset erhalten)
bool error_I = false; // Fehlerflag für Strom
bool error_U = false; // Fehlerflag für Spannung
bool error_S = false; // Fehlerflag für SOC
// ---------- Setup ----------
void setup() {
Serial.begin(115200); // Serielle Konsole starten
Serial1.begin(9600, SERIAL_8N1, RX1, TX1); // Serielle Schnittstelle für BMS initialisieren
bms.Init(); // BMS initialisieren
// CAN-Bus initialisieren (500kbit/s, 8MHz Quarz)
if (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK) {
Serial.println("CAN BUS Init ok!");
} else {
Serial.println("CAN BUS Init fail");
while (1); // Stoppe das Programm, falls CAN nicht startet
}
CAN.setMode(MCP_NORMAL); // CAN in Normalbetrieb setzen
pinMode(CAN_INT, INPUT_PULLUP); // Interrupt-Pin als Eingang mit Pullup
lcd.init(); // LCD initialisieren
lcd.backlight(); // LCD Hintergrundbeleuchtung einschalten
// WLAN Access Point starten
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
// Webserver-Routen festlegen
server.on("/", handleRoot); // HTML-Seite
server.on("/data", handleData); // JSON-Daten
server.begin(); // Webserver starten
}
// ---------- Webserver: HTML-Seite ----------
void handleRoot() {
// HTML-Seite als Raw-String Literal
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<style>
body {font-family: Arial; padding: 20px;}
.card {background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 15px;}
h2 {color: #34495e; border-bottom: 2px solid #3498db;}
.cellgrid {display: grid; grid-template-columns: repeat(3, 1fr);}
</style>
<script>
// Holt alle 1s aktuelle Daten vom Server und aktualisiert die Anzeige
function updateData() {
fetch('/data')
.then(response => response.json())
.then(data => {
document.getElementById('voltage').innerText = data.voltage;
document.getElementById('soc').innerText = data.soc;
document.getElementById('current').innerText = data.current;
document.getElementById('current_sensor').innerText = data.current_sensor;
for(let i=1; i<=13; i++) {
document.getElementById('cell'+i).innerText = data.cells[i-1];
}
document.getElementById('tempMax').innerText = data.tempMax;
document.getElementById('tempMin').innerText = data.tempMin;
document.getElementById('tempAvg').innerText = data.tempAvg;
document.getElementById('status').innerText = data.status;
document.getElementById('maxCell').innerText = data.maxCell;
document.getElementById('maxCellNum').innerText = data.maxCellNum;
document.getElementById('minCell').innerText = data.minCell;
document.getElementById('minCellNum').innerText = data.minCellNum;
document.getElementById('chargeMos').innerText = data.chargeMos;
document.getElementById('dischargeMos').innerText = data.dischargeMos;
for(let i=1; i<=13; i++) {
document.getElementById('bal'+i).innerText = data.balancing[i-1];
}
});
}
setInterval(updateData, 1000);
document.addEventListener('DOMContentLoaded', updateData);
</script>
</head>
<body>
<h1>BMS Live-Daten</h1>
<!-- Verschiedene Karten für Werte, Zellspannungen, Temperaturen und Balancing -->
<div class="card">
<h2>Gesamtwerte</h2>
<div>Spannung: <span id="voltage">0.00</span> V</div>
<div>SOC: <span id="soc">0.0</span> %</div>
<div>Strom (BMS): <span id="current">0.000</span> A</div>
<div>Strom (Sensor): <span id="current_sensor">0.000</span> A</div>
</div>
<div class="card">
<h2>Zellspannungen</h2>
<div class="cellgrid">
<!-- Einzelne Zellen -->
<div>Zelle 1: <span id="cell1">0.000</span> V</div>
<div>Zelle 2: <span id="cell2">0.000</span> V</div>
<div>Zelle 3: <span id="cell3">0.000</span> V</div>
<div>Zelle 4: <span id="cell4">0.000</span> V</div>
<div>Zelle 5: <span id="cell5">0.000</span> V</div>
<div>Zelle 6: <span id="cell6">0.000</span> V</div>
<div>Zelle 7: <span id="cell7">0.000</span> V</div>
<div>Zelle 8: <span id="cell8">0.000</span> V</div>
<div>Zelle 9: <span id="cell9">0.000</span> V</div>
<div>Zelle 10: <span id="cell10">0.000</span> V</div>
<div>Zelle 11: <span id="cell11">0.000</span> V</div>
<div>Zelle 12: <span id="cell12">0.000</span> V</div>
<div>Zelle 13: <span id="cell13">0.000</span> V</div>
</div>
</div>
<div class="card">
<h2>Temperaturen & Status</h2>
<div>Max. Temp: <span id="tempMax">0</span> °C</div>
<div>Min. Temp: <span id="tempMin">0</span> °C</div>
<div>Durchschnittstemperatur: <span id="tempAvg">0</span> °C</div>
<div>Status: <span id="status">-</span></div>
<div>Max. Zellenspannung: <span id="maxCell">0.000</span> V (Zelle <span id="maxCellNum">0</span>)</div>
<div>Min. Zellenspannung: <span id="minCell">0.000</span> V (Zelle <span id="minCellNum">0</span>)</div>
<div>Charge MOS: <span id="chargeMos">AUS</span></div>
<div>Discharge MOS: <span id="dischargeMos">AUS</span></div>
</div>
<div class="card">
<h2>Zellen-Balancing</h2>
<div class="cellgrid">
<!-- Balancing Status für jede Zelle -->
<div>Zelle 1: <span id="bal1">AUS</span></div>
<div>Zelle 2: <span id="bal2">AUS</span></div>
<div>Zelle 3: <span id="bal3">AUS</span></div>
<div>Zelle 4: <span id="bal4">AUS</span></div>
<div>Zelle 5: <span id="bal5">AUS</span></div>
<div>Zelle 6: <span id="bal6">AUS</span></div>
<div>Zelle 7: <span id="bal7">AUS</span></div>
<div>Zelle 8: <span id="bal8">AUS</span></div>
<div>Zelle 9: <span id="bal9">AUS</span></div>
<div>Zelle 10: <span id="bal10">AUS</span></div>
<div>Zelle 11: <span id="bal11">AUS</span></div>
<div>Zelle 12: <span id="bal12">AUS</span></div>
<div>Zelle 13: <span id="bal13">AUS</span></div>
</div>
</div>
</body>
</html>
)rawliteral";
server.send(200, "text/html; charset=UTF-8", html); // HTML-Seite senden
}
// ---------- Webserver: JSON-Daten ----------
void handleData() {
DynamicJsonDocument doc(2048); // JSON-Dokument mit 2kB RAM
// Gesamtwerte
doc["voltage"] = String(bms.get.packVoltage, 2); // Batteriespannung (V)
doc["soc"] = String(bms.get.packSOC, 1); // State of Charge (%)
doc["current"] = String(bms.get.packCurrent / 1.0, 3); // Strom (A, BMS)
doc["current_sensor"] = String(current_sensor, 3); // Strom (A, externer Sensor)
// Zellspannungen als Array
JsonArray cells = doc.createNestedArray("cells");
for (int i = 0; i < 13; i++) {
cells.add(String(bms.get.cellVmV[i] / 1000.0, 3)); // Zellspannung in V
}
// Temperatur- und Statuswerte
doc["tempMax"] = bms.get.tempMax; // Maximale Temperatur
doc["tempMin"] = bms.get.tempMin; // Minimale Temperatur
doc["tempAvg"] = bms.get.tempAverage; // Durchschnittstemperatur
doc["status"] = bms.get.chargeDischargeStatus == 0 ? "Entladen" : "Laden";
doc["maxCell"] = String(bms.get.maxCellmV / 1000.0, 3); // Höchste Zellenspannung (V)
doc["maxCellNum"] = bms.get.maxCellVNum; // Zellnummer mit max. Spannung
doc["minCell"] = String(bms.get.minCellmV / 1000.0, 3); // Niedrigste Zellenspannung (V)
doc["minCellNum"] = bms.get.minCellVNum; // Zellnummer mit min. Spannung
doc["chargeMos"] = bms.get.chargeFetState ? "AN" : "AUS"; // Lade-MOSFET Status
doc["dischargeMos"] = bms.get.disChargeFetState ? "AN" : "AUS"; // Entlade-MOSFET Status
// Balancing-Zustände als Array
JsonArray balancing = doc.createNestedArray("balancing");
for (int i = 0; i < 13; i++) {
balancing.add(bms.get.cellBalanceState[i] ? "AN" : "AUS");
}
// JSON serialisieren und senden
String jsonString;
serializeJson(doc, jsonString);
server.send(200, "application/json; charset=UTF-8", jsonString);
}
// ---------- Hauptprogrammschleife ----------
void loop() {
bms.update(); // BMS-Werte aktualisieren
server.handleClient(); // Webserver-Requests bearbeiten
// --- CAN-Bus: Stromsensorwert und Schützstatus empfangen ---
if (!digitalRead(CAN_INT)) { // Wenn eine CAN-Nachricht empfangen wurde
long unsigned int rxId;
unsigned char len = 0;
byte buf[8];
if (CAN.readMsgBuf(&rxId, &len, buf) == CAN_OK) {
Serial.print("Empfangen CAN ID: 0x");
Serial.print(rxId, HEX);
Serial.print(" Daten: ");
for (int i = 0; i < len; i++) {
Serial.print(buf[i], HEX);
Serial.print(" ");
}
Serial.println();
// Stromsensorwert (CAN-ID 0x100, 4 Bytes Float)
if (rxId == 0x100 && len == 4) {
memcpy(¤t_sensor, buf, 4); // Bytes in float kopieren
Serial.print("Aktualisierter Stromsensorwert: ");
Serial.println(current_sensor, 3);
}
// Schützstatus (CAN-ID 0x200, 2 Bytes: S1 und S2)
if (rxId == 0x200 && len == 2) {
s1_status = (buf[0] == 1);
s2_status = (buf[1] == 1);
}
}
}
// --- Fehlererkennung für Daly-Werte (bleibt bis Reset) ---
float strom_daly = bms.get.packCurrent / 1.0;
float spannung_daly = bms.get.packVoltage;
float soc_daly = bms.get.packSOC;
if (!error_I && (strom_daly > 20.0 || strom_daly < -20.0)) error_I = true;
if (!error_U && (spannung_daly > 60.0 || spannung_daly < -60.0)) error_U = true;
if (!error_S && (soc_daly > 101.0 || soc_daly < -1.0)) error_S = true;
// --- Schützsteuerung je nach Stromsensorwert ---
static bool lastSchuetzSoll = false; // Letzter Sollwert merken (nur bei Änderung senden)
bool schuetzSoll = (current_sensor > 10.0) || (current_sensor < -10.0); // Schütz AN bei großem Strom
if (schuetzSoll != lastSchuetzSoll) {
byte data[1] = {schuetzSoll ? 1 : 0};
if (CAN.sendMsgBuf(0x101, 0, 1, data) == CAN_OK) {
Serial.print("Gesendet Schützsteuerbefehl: ");
Serial.println(schuetzSoll ? "AN" : "AUS");
} else {
Serial.println("Fehler beim Senden Schützsteuerbefehl");
}
lastSchuetzSoll = schuetzSoll;
}
// --- LCD-Anzeige ---
// Zeile 1: S1/S2 Status + Fehlerflags (rechts)
lcd.setCursor(0, 0);
lcd.print("S1:");
lcd.print(s1_status ? "An" : "Aus");
lcd.print(" S2:");
lcd.print(s2_status ? "An" : "Aus");
// Fehlerflags ganz rechts anzeigen
lcd.setCursor(13, 0);
if (error_I) lcd.print("I"); else lcd.print(" ");
if (error_U) lcd.print("U"); else lcd.print(" ");
if (error_S) lcd.print("S"); else lcd.print(" ");
// Zeile 2: Stromsensorwert / BMS-Strom
lcd.setCursor(0, 1);
lcd.print("I:");
lcd.print(current_sensor, 2);
lcd.print("/");
lcd.print(strom_daly, 2);
lcd.print("A ");
delay(100); // Kurze Pause, um Flackern zu vermeiden
}
✅ Copied!
Code - Schütz
#include <SPI.h>
#include <mcp_can.h>
// ---------- Pin-Definitionen ----------
const int buttonPin = 21; // Taster-Eingang (mit Pullup)
const int greenLedPin = 22; // Grüne LED
const int redLedPin = 25; // Rote LED
const int outputPin1 = 26; // S1-Ausgang (PWM)
const int outputPin2 = 27; // S2-Ausgang (PWM invertiert)
#define CAN_CS 5
#define CAN_INT 4
MCP_CAN CAN(CAN_CS);
// ---------- Globale Variablen ----------
bool s1_state = false;
bool s2_state = true;
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
bool toggleRequested = false;
float stromwert = 0.0;
bool stromOverride = false;
// ---------- PWM-Timing (Softwarebasiert) ----------
const unsigned long pwmPeriod = 10UL; // 10ms Periodendauer (=100 Hz)
const float dutyCycle = 0.60; // 60% Duty-Cycle
unsigned long pwmOnTime = pwmPeriod * dutyCycle;
unsigned long pwmOffTime = pwmPeriod - pwmOnTime;
unsigned long pwmLastToggle = 0;
bool pwmOutputState = true;
// ---------- Boost-Zeit nach Umschaltung ----------
bool boostPhaseActive = true;
unsigned long boostStartTime = 0;
const unsigned long boostDuration = 1000UL; // 1 Sekunde Boost
// ---------- Setup-Funktion ----------
void setup() {
Serial.begin(115200);
pinMode(buttonPin, INPUT_PULLUP);
pinMode(greenLedPin, OUTPUT);
pinMode(redLedPin, OUTPUT);
pinMode(outputPin1, OUTPUT);
pinMode(outputPin2, OUTPUT);
// Startzustand
digitalWrite(outputPin1, LOW); // S1 aus
digitalWrite(outputPin2, HIGH); // S2 an (invertiert)
digitalWrite(greenLedPin, LOW); // LED aus
digitalWrite(redLedPin, HIGH); // LED an
if (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK) {
Serial.println("CAN BUS Init ok!");
} else {
Serial.println("CAN BUS Init fail");
while (1);
}
CAN.setMode(MCP_NORMAL);
pinMode(CAN_INT, INPUT_PULLUP);
// Start Booster setzen
boostPhaseActive = true;
boostStartTime = millis();
}
// ---------- Setze Ausgänge & starte Booster ----------
void setOutputs(bool s1) {
s1_state = s1;
s2_state = !s1;
// LEDs setzen
digitalWrite(greenLedPin, s1_state ? HIGH : LOW);
digitalWrite(redLedPin, s1_state ? LOW : HIGH);
// Booster aktivieren
boostPhaseActive = true;
boostStartTime = millis();
pwmLastToggle = millis(); // PWM zurücksetzen
pwmOutputState = true; // Start mit HIGH
// Starte mit 100% an
digitalWrite(outputPin1, s1_state ? HIGH : LOW);
digitalWrite(outputPin2, s2_state ? HIGH : LOW);
}
// ---------- Status per CAN senden ----------
void sendStatus() {
byte data[2] = { s1_state ? 1 : 0, s2_state ? 1 : 0 };
CAN.sendMsgBuf(0x200, 0, 2, data);
}
// ---------- PWM-Logik ----------
void updatePwmOutputs() {
unsigned long now = millis();
// 1 Sekunde 100% EIN nach Umschalten
if (boostPhaseActive) {
if ((now - boostStartTime) >= boostDuration) {
boostPhaseActive = false;
pwmLastToggle = now;
pwmOutputState = true;
} else {
// Während Boost-Phase: immer EIN
digitalWrite(outputPin1, s1_state ? HIGH : LOW);
digitalWrite(outputPin2, s2_state ? HIGH : LOW);
return;
}
}
// PWM-Phase
if (pwmOutputState && (now - pwmLastToggle >= pwmOnTime)) {
pwmOutputState = false;
pwmLastToggle = now;
} else if (!pwmOutputState && (now - pwmLastToggle >= pwmOffTime)) {
pwmOutputState = true;
pwmLastToggle = now;
}
// Ausgabe setzen
digitalWrite(outputPin1, (s1_state && pwmOutputState) ? HIGH : LOW);
digitalWrite(outputPin2, (s2_state && pwmOutputState) ? HIGH : LOW);
}
// ---------- Hauptschleife ----------
void loop() {
// --- Taster-Handling (entprellt) ---
bool buttonState = digitalRead(buttonPin);
if (buttonState != lastButtonState) {
lastDebounceTime = millis();
lastButtonState = buttonState;
}
if ((millis() - lastDebounceTime) > 50 && buttonState == LOW && !toggleRequested) {
toggleRequested = true;
}
if (buttonState == HIGH && toggleRequested) {
if (!stromOverride) {
setOutputs(!s1_state); // Toggle
sendStatus();
Serial.print("Button-Toggle: S1=");
Serial.print(s1_state ? "AN" : "AUS");
Serial.print(" S2=");
Serial.println(s2_state ? "AN" : "AUS");
}
toggleRequested = false;
}
// --- CAN empfangen ---
if (!digitalRead(CAN_INT)) {
long unsigned int rxId;
unsigned char len = 0;
byte buf[8];
if (CAN.readMsgBuf(&rxId, &len, buf) == CAN_OK) {
// Steuerbefehl für S1
if (rxId == 0x101 && len == 1) {
bool newState = (buf[0] == 1);
setOutputs(newState);
sendStatus();
stromOverride = true;
Serial.print("CAN-Steuerung: S1=");
Serial.print(s1_state ? "AN" : "AUS");
Serial.print(" S2=");
Serial.println(s2_state ? "AN" : "AUS");
}
// Stromwert empfangen
if (rxId == 0x100 && len == 4) {
memcpy(&stromwert, buf, 4);
Serial.print("Stromsensorwert empfangen: ");
Serial.println(stromwert, 3);
}
}
}
// --- Strombereich prüfen, ggf. stromOverride zurücksetzen ---
if (stromOverride && stromwert <= 10.0 && stromwert >= -10.0) {
stromOverride = false;
Serial.println("Strombereich normal, Buttonsteuerung wieder aktiv.");
}
// --- PWM an Outputs ausgeben ---
updatePwmOutputs(); // Hier wird PWM berechnet und angewendet
}
✅ Copied!
Python Plotter
Um das Projekt zum Laufen zu bringen, habe ich den Umgebung PYCharm benutzt – ein Python Editor. Dieser kann hierüber heruntergeladen werden https://www.jetbrains.com/pycharm/
