Dosieranlage/main_kommentiert.c

2036 lines
41 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stm32f10x.h>
/**
* CMSIS-Header für STM32F10x.
* Enthält:
* - Registerdefinitionen
* - Peripheral-Strukturen
* - Basisadressen
*/
#include <armv30_std.h>
/**
* armv30_std Bibliothek.
*/
#include <Nextion.h>
/**
* Nextion-HMI-Bibliothek.
* Stellt UI-Kommunikation über UART bereit.
*/
#include <stdbool.h>
/**
* Stellt bool, true, false bereit.
*/
/**
* @brief Globale Nextion-UI-Objekte
*
* @details
* Jedes Objekt repräsentiert ein UI-Element im HMI.
* Diese Strukturen enthalten:
*/
NexEventObj fuellstandInd; /**< ProgressBar für Füllstand */
NexEventObj pumpeInd; /**< Anzeige Pumpenzustand */
NexEventObj leerInd; /**< Anzeige Leerblasen */
NexEventObj feinInd; /**< Anzeige Feinventil */
NexEventObj grobInd; /**< Anzeige Grobventil */
NexEventObj fehlerInd; /**< Fehleranzeige */
NexEventObj fuellstandText; /**< Textfeld für Literanzeige */
/**
* @brief Berechnet Bit-Banding Alias-Adresse
*
* @param a Basisadresse des Registers
* @param b Bitnummer
*
* @return Alias-Adresse für atomaren Bitzugriff
*
* @details
* Bit-Banding mappt jedes einzelne Bit
* auf eine 32-Bit Alias-Adresse.
*
* Formel:
* Alias = PERIPH_BB_BASE + (ByteOffset * 32) + (Bit * 4)
*/
#define BITBAND_PERI(a,b) ((PERIPH_BB_BASE + (a-PERIPH_BASE)*32 + (b*4)))
/**
* @brief Atomare GPIO-Ausgänge via Bit-Banding
*
* ODR = Output Data Register
*/
#define FEHLER *((volatile unsigned long *)(BITBAND_PERI(GPIOC_ODR,1))) /**< PC1, V3 */
#define LEERBLASEN *((volatile unsigned long *)(BITBAND_PERI(GPIOC_ODR,2))) /**< PC2, V4 */
#define FEIN *((volatile unsigned long *)(BITBAND_PERI(GPIOC_ODR,13))) /**< PC13, V5 */
#define GROB *((volatile unsigned long *)(BITBAND_PERI(GPIOB_ODR,7))) /**< PB7, V6 */
#define PUMPE *((volatile unsigned long *)(BITBAND_PERI(GPIOB_ODR,6))) /**< PB6, V7 */
/**
* @brief DIP-Schalter via Bit-Banding
*
* IDR = Input Data Register
*/
#define Switch5 *((volatile unsigned long *)(BITBAND_PERI(GPIOB_IDR,12))) // DIL5
#define Switch4 *((volatile unsigned long *)(BITBAND_PERI(GPIOB_IDR,15))) // DIL4
#define Switch6 *((volatile unsigned long *)(BITBAND_PERI(GPIOB_IDR,14))) // DIL6
#define Switch7 *((volatile unsigned long *)(BITBAND_PERI(GPIOB_IDR,13))) // DIL7
#define Switch1 *((volatile unsigned long *)(BITBAND_PERI(GPIOC_IDR,10))) // DIL1
#define Switch0 *((volatile unsigned long *)(BITBAND_PERI(GPIOC_IDR,6))) // DIL0
#define Switch2 *((volatile unsigned long *)(BITBAND_PERI(GPIOA_IDR,11))) // DIL2
#define Switch3 *((volatile unsigned long *)(BITBAND_PERI(GPIOA_IDR,12))) // DIL3
/**
* @brief Nextion-Konstanten
*/
#define NEX_PID_MAIN 0 /**< Hauptseite */
#define NEX_CID_PROG 1 /**< Komponenten-ID */
/**
* @brief Regelparameter / Konstanten
*/
#define SOLLWERT 0x7F /**< Sollwert = 127d */
#define GAP 0x5F /**< Grobabschaltpunkt = 95d */
#define NACHLAUF 0x5 /**< Nachlaufmenge = 5l */
/**
* @brief Array mit Zeigern auf alle DIP-Schalter
*
* Vorteil:
* - Schleifenbasierte Auswertung möglich
* - Kein mehrfacher Code
*/
volatile unsigned long* Switch[8] =
{
&Switch0,
&Switch1,
&Switch2,
&Switch3,
&Switch4,
&Switch5,
&Switch6,
&Switch7
};
/**
* @brief Software-Nachlaufkompensations Variable
*/
int nachlaufOffset = 0;
/**
* @brief SysTick Interrupt Service Routine
*
*/
void SysTick_Handler()
{
STD_IncTick(); /**< Erhöht / Incrementiert den globalen System-Tick-Zähler */
}
/**
* @brief Initialisiert und konfiguriert alle verwendeten GPIO-Ports (A, B, C)
*
* @details
* Diese Funktion aktiviert zunächst die Taktversorgung der GPIO-Peripherie
* über das RCC (Reset and Clock Control).
*
* Anschließend werden die Konfigurationsregister CRL (Pins 07)
* und CRH (Pins 815) beschrieben.
*
* STM32F1 GPIO-Konfigurationsstruktur:
* ---------------------------------------------------------
* Pro Pin existieren 4 Konfigurationsbits:
*
* MODE ... Ausgangsgeschwindigkeit
* CNF ... Betriebsart (Push-Pull, Open-Drain, Input...)
*
* MODE:
* 00 = Input
* 01 = Output 10 MHz
* 10 = Output 2 MHz
* 11 = Output 50 MHz
*
* CNF bei Output:
* 00 = Push-Pull
* 01 = Open-Drain
* 10 = Alternate Function Push-Pull
* 11 = Alternate Function Open-Drain
*
* CNF bei Input:
* 00 = Analog
* 01 = Floating
* 10 = Pull-Up / Pull-Down
*
* Wichtig:
* Bei Input Pull-Up/Pull-Down bestimmt das ODR-Bit:
* ODR = 1 ... Pull-Up
* ODR = 0 ... Pull-Down
*/
void PortConfig(void)
{
/**
* @brief Aktivierung der GPIO-Takte
*
* APB2ENR = Advanced Peripheral Bus 2 Enable Register
* GPIOA/B/C liegen auf APB2.
*/
RCC -> APB2ENR |= RCC_APB2ENR_IOPCEN; /**< GPIOC Clock Enable */
RCC -> APB2ENR |= RCC_APB2ENR_IOPBEN; /**< GPIOB Clock Enable */
RCC -> APB2ENR |= RCC_APB2ENR_IOPAEN; /**< GPIOA Clock Enable */
/**
* ============================
* Konfiguration GPIOB
* ============================
*/
GPIOB -> CRL &= 0x00000000;
/**
* CRL Reset (Pins 07).
* &= 0 löscht alle 32 Bits.
*/
GPIOB -> CRL = 0x22222222;
/**
* 0x2 = 0010b pro Pin
* MODE=10 (2 MHz Output)
* CNF=00 (Push-Pull)
*
* Ergebnis:
* PB0PB7 = 2 MHz Push-Pull Ausgang
*/
GPIOB -> CRH &= 0x00000000;
/**
* Reset von PB8PB15
*/
GPIOB -> CRH |= 0x88880000;
/**
* 0x8 = 1000b pro Pin
* MODE=00 (Input)
* CNF=10 (Pull-Up/Pull-Down)
*
* PB12PB15 werden als Eingang mit Pull-Widerstand konfiguriert.
*/
/**
* ============================
* Konfiguration GPIOA
* ============================
*/
GPIOA -> CRL &= 0x00000000; /**< Reset Pins PA0PA7 */
GPIOA -> CRL = 0x22222222;
/**
* PA0PA7 = 2 MHz Push-Pull Outputs
*/
GPIOA -> CRH &= 0x00000000; /**< Reset Pins PA8PA15 */
GPIOA -> CRH |= 0x00088000;
/**
* 0x8 entspricht wieder:
* Input mit Pull-Up/Pull-Down
*/
/**
* ============================
* Konfiguration GPIOC
* ============================
*/
GPIOC -> CRL &= 0x00000000; /**< Reset PC0PC7 */
GPIOC -> CRL |= 0x28222222;
/**
* Mischung aus:
* 0x2 ... Output 2 MHz Push-Pull
* 0x8 ... Input Pull-Up/Pull-Down
*/
GPIOC -> CRH &= 0x00000000; /**< Reset PC8PC15 */
GPIOC -> CRH |= 0x22222822;
/**
* Mischung aus:
* 0x2 ... Output 2 MHz Push-Pull
* 0x8 ... Input Pull-Up/Pull-Down
*/
/**
* ============================
* Initialisierung der Ausgangspegel
* ============================
*
* ODR = Output Data Register
*
* Bei Output:
* 1 = High
* 0 = Low
*
* Bei Input Pull:
* 1 = Pull-Up
* 0 = Pull-Down
*/
GPIOA -> ODR &= 0x0000;
GPIOB -> ODR &= 0x0000;
GPIOC -> ODR &= 0x0000;
/**
* Setzt alle Ausgänge zunächst auf LOW.
*/
GPIOA -> ODR |= 0x1800;
/**
* Aktiviert Pull-Ups bzw. setzt bestimmte Pins High.
*/
GPIOB -> ODR |= 0xF000;
/**
* Setzt PB12PB15 High ... Pull-Up aktiv.
*/
GPIOC -> ODR |= 0x0440;
/**
* Setzt definierte Pins auf High.
*/
}
/**
* @brief Initialisiert die serielle Schnittstelle USART2 (19200 Baud, n, 8, 1)
*
* @details
* Diese Funktion konfiguriert:
* - Taktversorgung (RCC)
* - GPIO-Pins für TX und RX
* - USART2 Parameter (Datenformat, Baudrate)
* - Aktivierung von Sender und Empfänger
*
* Frame-Format (8N1):
* -> | Start | 8 Datenbits | Stop | ->
*
* Keine Parität
*/
void uart2_init(void)
{
/**
* ==========================
* 1. Taktaktivierung
* ==========================
*/
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN;
/**
* AFIOEN ... Aktiviert Alternate Function IO
* IOPAEN ... Aktiviert GPIOA
*/
RCC->APB1ENR |= 0x20000;
/**
* Bit 17 = USART2EN
*
* Aktiviert Taktversorgung für USART2 auf APB1.
*/
/**
* ==========================
* 2. GPIO Konfiguration
* ==========================
*
* PA2 ... TX
* PA3 ... RX
*
* CRL steuert Pins 07.
* Jeder Pin besitzt 4 Konfigurationsbits.
*/
GPIOA->CRL &= 0xFFFFF0FF;
/**
* Löscht Konfiguration von PA2 (Bits 8-11).
* Maske: 1111 1111 1111 1111 1111 0000 1111 1111
*/
GPIOA->CRL |= 0xB00;
/**
* 0xB = 1011b
*
* MODE=11 ... Output 50 MHz
* CNF=10 ... Alternate Function Push-Pull
*
* Ergebnis:
* PA2 = USART2_TX
*/
GPIOA->CRL &= 0xFFFF0FFF;
/**
* Löscht Konfiguration von PA3 (Bits 12-15).
*/
GPIOA->CRL |= 0x4000;
/**
* 0x4 = 0100b
*
* MODE=00 ... Input
* CNF=01 ... Floating Input
*
* Ergebnis:
* PA3 = USART2_RX
*/
/**
* ==========================
* 3. USART Konfiguration
* ==========================
*/
USART2->CR1 &= ~0x1000;
/**
* M-Bit = 0
* 8 Datenbits
*
* M=1 ... 9 Bit
*/
USART2->CR1 &= ~0x0400;
/**
* PCE = 0
* Parity Control deaktiviert
*
* Kein Paritätsbit
*/
USART2->CR2 &= ~0x3000;
/**
* STOP[12-13] = 00
* 1 Stopbit
*/
USART2->BRR = 0x1D4C;
/**
* Baudrate Register
*
* Mantisse + Fraction ergeben 0x1D4C
*/
USART2->CR1 |= 0x0C;
/**
* Bit 3 = TE (Transmitter Enable)
* Bit 2 = RE (Receiver Enable)
*
* Aktiviert Sende- und Empfangseinheit.
*/
USART2->CR1 |= 0x2000;
/**
* UE Bit = USART Enable
*
* Startet die USART-Hardware.
*/
}
/**
* @brief Sendet ein einzelnes Zeichen über USART2 (Polling-basiert)
*
* @param zeichen ASCII-Zeichen, das übertragen werden soll
*
* @details
* Diese Funktion implementiert eine blockierende UART-Übertragung.
* Es wird solange gewartet, bis das TXE-Bit (Transmit Data Register Empty)
* im Statusregister (SR) gesetzt ist.
*
* Ablauf:
* 1. Prüfen, ob Datenregister leer ist (TXE = 1)
* 2. Schreiben des Zeichens in das DR-Register
* 3. Hardware übernimmt serielle Ausgabe automatisch
*
* Technischer Hintergrund:
* - SR Bit 7 = TXE
* - DR = Data Register
* - Schreiben in DR startet die Übertragung
*
* TXE bedeutet:
* Das Datenregister ist leer und bereit für neue Daten.
*
* Wichtig:
* TXE ? TC
* TXE = Datenregister leer
* TC = Übertragung vollständig abgeschlossen (inkl. Stopbit)
*
* Diese Funktion wartet nur auf TXE, nicht auf TC.
*
* @note
* Blockierend CPU wartet aktiv (Busy Waiting).
* Für hohe Datenraten oder Multitasking ungeeignet.
*/
void uart2_putChar(char zeichen)
{
/**
* Warten bis TXE (Bit 7 im Statusregister) gesetzt ist.
*
* USART2->SR & 0x80:
* 0x80 = 1000 0000b ? Bit 7
*
* Solange TXE = 0 bleibt,
* befindet sich noch ein Zeichen im Datenregister.
*/
while(!((USART2->SR & 0x80) == 0x80));
/**
* Schreiben des Zeichens in das Datenregister.
*
* DR ist 9 Bit breit (bei 9-Bit Modus),
* hier jedoch 8 Bit aktiv (8N1).
*
* Das Schreiben startet sofort:
* - Startbit wird generiert
* - 8 Datenbits werden seriell ausgegeben
* - Stopbit folgt
*
* Bitzeit bei 9600 Baud:
* 1 / 9600 ˜ 104 µs pro Bit
*
* Gesamtdauer pro Zeichen:
* 10 Bit ? ~1.04 ms
*/
USART2->DR = zeichen;
}
/**
* @brief Sendet eine nullterminierte Zeichenkette über USART2
*
* @param string Zeiger auf ein C-String-Array (char*)
*
* @details
* Diese Funktion überträgt eine ASCII-Zeichenkette,
* die im klassischen C-Format vorliegt:
*
* Stringstruktur:
* --------------------------------
* 'H' 'a' 'l' 'l' 'o' '\0'
* --------------------------------
*
* Das Stringende wird durch das Nullbyte ('\0')
* signalisiert. Dieses wird nicht mitgesendet.
*
* Die Funktion arbeitet blockierend und ruft
* für jedes Zeichen uart2_putChar() auf.
*
* Laufzeitverhalten:
* Bei 9600 Baud dauert ein Zeichen ˜ 1.04 ms.
* Ein 20-Zeichen-String benötigt also ˜ 20 ms.
*
* @note
* Keine Längenprüfung.
* Kein Timeout.
* Kein Schutz gegen NULL-Pointer.
*/
void uart2_putString(char* string)
{
/**
* Schleife läuft solange:
* *string != 0
*
* Das Dereferenzieren (*string) liest
* das aktuelle Zeichen im Speicher.
*
* ASCII-Wert 0 entspricht '\0'
* ? Ende der Zeichenkette.
*/
while(*string)
{
/**
* Übergibt aktuelles Zeichen
* an uart2_putChar().
*
* *string liest das Zeichen.
* string++ erhöht den Pointer
* auf das nächste Zeichen.
*
* Post-Increment:
* Erst lesen, dann erhöhen.
*/
uart2_putChar(*string++);
}
}
/**
* @brief Liest ein einzelnes Zeichen von USART2 (Polling-basiert)
*
* @return Empfangenes ASCII-Zeichen (8 Bit)
*
* @details
* Diese Funktion wartet solange, bis das RXNE-Bit
* (Receive Data Register Not Empty) im Statusregister (SR)
* gesetzt ist.
*
* Sobald RXNE = 1:
* - Ein vollständiges Zeichen wurde empfangen
* - Das Datenregister (DR) enthält gültige Daten
*
* Danach wird DR gelesen und als char zurückgegeben.
*
* Hardware-Ablauf:
* -----------------------------------------
* UART Leitung ? Shift Register ? DR
* -----------------------------------------
*
* RXNE wird gesetzt, sobald:
* - Startbit erkannt
* - 8 Datenbits korrekt empfangen
* - Stopbit validiert
*
* @note
* Blockierend CPU wartet aktiv.
* Keine Fehlerprüfung (Framing, Overrun etc.).
*/
char uart2_getChar(void)
{
/**
* Warten bis RXNE gesetzt ist.
*
* USART2->SR & 0x20:
* 0x20 = 0010 0000b ? Bit 5
*
* RXNE = 1 bedeutet:
* Datenregister enthält neue Empfangsdaten.
*
* Solange RXNE = 0 bleibt,
* ist noch kein vollständiges Zeichen eingetroffen.
*/
while(!(USART2->SR & 0x20));
/**
* Lesen des Datenregisters.
*
* DR ist 9 Bit breit,
* hier aber im 8-Bit-Modus (M=0).
*
* & 0xFF maskiert auf 8 Bit.
*
* Wichtiger Nebeneffekt:
* Das Lesen von DR löscht das RXNE-Flag automatisch.
*/
return ((char) (USART2->DR & 0xFF));
}
/**
* @brief Initialisiert ein Nextion-Event-Objekt
*
* @param obj Zeiger auf das zu initialisierende NexEventObj
* @param pid Page-ID (Seitenkennung im Nextion-Display)
* @param cid Component-ID (Objektkennung auf der Seite)
* @param name Name des UI-Elements (z.B. "pumpeInd")
*
* @details
* Diese Funktion setzt die Grundparameter eines
* Nextion-Objekts und versetzt es in einen definierten
* Startzustand.
*
* Strukturtyp (vereinfacht angenommen):
*
* typedef struct {
* uint8_t pid; // Page-ID
* uint8_t cid; // Component-ID
* const char *name; // Objektname im HMI
* void (*pophandler)(void); // Handler bei Loslassen
* void (*pushhandler)(void); // Handler bei Drücken
* } NexEventObj;
*
* Zweck:
* - Kapselung der UI-Komponenten
* - Einheitliche Initialisierung
* - Vermeidung undefinierter Pointer
*
* @note
* static ? nur innerhalb dieser Datei sichtbar.
* Keine globale Symbol-Exposition.
*/
static void NexEventObj_Init(NexEventObj *obj, uint8_t pid, uint8_t cid, const char *name)
{
/**
* Speichert die Seiten-ID.
*
* pid referenziert die Seite im Nextion-HMI-Editor.
* Beispiel:
* page0 ? pid = 0
*/
obj->pid = pid;
/**
* Speichert die Component-ID.
*
* Jede Komponente auf einer Seite
* besitzt eine eindeutige cid.
*/
obj->cid = cid;
/**
* Speichert den Namen des UI-Elements.
*
* Wichtig für textbasierte Befehle wie:
* "fuellstand.val=50"
*
* const char* bedeutet:
* - Zeiger auf statischen String
* - Inhalt wird nicht verändert
*/
obj->name = name;
/**
* Setzt den Pop-Handler auf NULL.
*
* pophandler wird ausgelöst,
* wenn ein Button losgelassen wird.
*
* NULL verhindert:
* - Zufällige Funktionsaufrufe
* - HardFault durch ungültige Funktionspointer
*/
obj->pophandler = NULL;
/**
* Setzt den Push-Handler auf NULL.
*
* pushhandler wird ausgelöst,
* wenn ein Button gedrückt wird.
*
* Initial NULL ? sicherer Zustand.
*/
obj->pushhandler = NULL;
}
/**
* @brief Initialisiert alle verwendeten Nextion-UI-Objekte
*
* @details
* Diese Funktion ruft für jedes globale NexEventObj
* die Initialisierungsroutine NexEventObj_Init() auf.
*
* Ziel:
* - Verknüpfung der Firmware-Objekte
* mit den UI-Elementen im Nextion-Display
* - Setzen von Page-ID (pid)
* - Setzen von Component-ID (cid)
* - Hinterlegen des Objekt-Namens
*
* Architekturprinzip:
* Firmware kennt das UI nur über:
* - pid (Seite)
* - cid (Komponente)
* - name (String im HMI)
*
* Dadurch entsteht eine klare Trennung zwischen:
* Embedded-Logik ? HMI-Darstellung
*
* @note
* Alle Objekte liegen auf derselben Page (NEX_PID_MAIN)
* und vermutlich im selben Komponenten-Kontext.
*/
void configNexObjects(void)
{
/**
* Füllstandsanzeige (ProgressBar)
*
* "fuellstand" muss exakt so
* im Nextion-Editor heißen.
*
* Wird später über:
* NexProgressBar_setValue()
* angesteuert.
*/
NexEventObj_Init(&fuellstandInd,
NEX_PID_MAIN,
NEX_CID_PROG,
"fuellstand");
/**
* Fehleranzeige (ProgressBar oder LED)
*/
NexEventObj_Init(&fehlerInd,
NEX_PID_MAIN,
NEX_CID_PROG,
"fehlerInd");
/**
* Anzeige für Leerblas-Ventil
*/
NexEventObj_Init(&leerInd,
NEX_PID_MAIN,
NEX_CID_PROG,
"leerInd");
/**
* Anzeige für Feinventil
*/
NexEventObj_Init(&feinInd,
NEX_PID_MAIN,
NEX_CID_PROG,
"feinInd");
/**
* Anzeige für Grobventil
*/
NexEventObj_Init(&grobInd,
NEX_PID_MAIN,
NEX_CID_PROG,
"grobInd");
/**
* Anzeige für Pumpenzustand
*/
NexEventObj_Init(&pumpeInd,
NEX_PID_MAIN,
NEX_CID_PROG,
"pumpeInd");
/**
* Textfeld für numerische Füllstandsausgabe
*
* Wahrscheinlich ein NexText-Objekt.
*
* Achtung:
* Name im HMI muss exakt "fuellstandn" heißen.
*/
NexEventObj_Init(&fuellstandText,
NEX_PID_MAIN,
NEX_CID_PROG,
"fuellstandn");
}
/**
* @brief Setzt oder löscht den globalen Fehlerzustand
*
* @param state true ? Fehler aktiv
* false ? Fehler behoben
*
* @details
* Diese Funktion realisiert eine zustandsabhängige
* Fehleranzeige mit folgenden Eigenschaften:
*
* - Entprellung auf Software-Ebene (keine Doppelupdates)
* - Initialisierung beim ersten Aufruf
* - Synchronisierung von:
* • HMI-Anzeige (Nextion ProgressBar)
* • Hardware-Ausgang (Bit-Banding GPIO)
* • UART-Debugausgabe
*
* Implementiert als einfache endliche Zustandsmaschine (FSM):
*
* Zustände:
* 0 ? Kein Fehler
* 1 ? Fehler aktiv
*
* Übergänge erfolgen nur bei Zustandsänderung.
*
* @note
* Verwendet statische Variablen zur Zustandsspeicherung.
*/
void fehler(bool state)
{
/**
* Speichert den letzten gesetzten Zustand.
* static ? bleibt über Funktionsaufrufe erhalten.
*
* Initialwert = true
* (wird beim ersten Aufruf ignoriert)
*/
static bool lastStateF = true;
/**
* Kennzeichnet den ersten Funktionsaufruf.
* Dient zur Initialisierung der Anzeige.
*/
static bool firstCallF = true;
/**
* Initialisierungsblock
*
* Beim allerersten Aufruf:
* - Anzeige wird auf 0 gesetzt
* - Keine Hardwareaktion
* - Keine UART-Meldung
*/
if (firstCallF)
{
firstCallF = false; /**< Initialisierung abgeschlossen */
/**
* Setzt ProgressBar auf 0%.
* Anzeige: kein Fehler.
*/
NexProgressBar_setValue(&fehlerInd, 0);
return; /**< Funktion verlassen */
}
/**
* Falls sich der Zustand nicht geändert hat:
* ? Keine Aktion notwendig.
*
* Verhindert:
* - unnötige UART-Ausgaben
* - unnötige HMI-Kommandos
* - unnötige GPIO-Schaltvorgänge
*/
else if (state == lastStateF)
{
return;
}
/**
* Aktualisiert gespeicherten Zustand.
*/
lastStateF = state;
/**
* Fehler aktiv
*/
if(state)
{
/**
* HMI: Fehleranzeige auf 100%
*/
NexProgressBar_setValue(&fehlerInd, 100);
/**
* Hardware-Ausgang setzen.
*
* FEHLER ist ein Bit-Banding-Makro:
* ? Direkter atomarer Zugriff auf ein einzelnes ODR-Bit.
*
* Elektrisch:
* GPIO-Pin wird High.
* Mögliche Nutzung:
* - Fehler-LED
* - Summer
* - Sicherheitsabschaltung
*/
FEHLER = 1;
/**
* UART-Debugmeldung.
*
* \r\n = CR + LF
* Windows-kompatible Zeilenumbrüche.
*/
uart2_putString("Fehler aufgetreten!\r\n");
}
else
{
/**
* HMI: Anzeige zurücksetzen
*/
NexProgressBar_setValue(&fehlerInd, 0);
/**
* Hardware-Ausgang löschen.
*/
FEHLER = 0;
/**
* UART-Meldung
*/
uart2_putString("Fehler behoben!\r\n");
}
return;
}
/**
* @brief Steuert das Leerblasventil (Ein/Aus) mit Zustandsüberwachung
*
* @param state true ? Ventil öffnen
* false ? Ventil schließen
*
* @details
* Diese Funktion implementiert eine zustandsbasierte
* Aktorsteuerung für das Leerblasventil.
*
* Eigenschaften:
* - Initialisierung beim ersten Aufruf
* - Keine Mehrfachschaltungen bei gleichem Zustand
* - Synchronisierung von:
* • HMI-Anzeige (Nextion ProgressBar)
* • GPIO-Ausgang (Bit-Banding)
* • UART-Debugmeldung
*
* Systemtechnisch handelt es sich um eine
* einfache 2-Zustands-FSM:
*
* Zustand 0 ? Ventil geschlossen
* Zustand 1 ? Ventil geöffnet
*
* @note
* Keine Entprellung notwendig, da rein softwareseitiger Aufruf.
*/
void leerBlasen(bool state)
{
/**
* Speichert den zuletzt gesetzten Zustand.
* static ? bleibt über Funktionsaufrufe erhalten.
*
* Kein Initialwert gesetzt ? implizit 0.
*/
static bool lastStateB;
/**
* Kennzeichnet ersten Funktionsaufruf.
* Dient zur Anzeigeinitialisierung.
*/
static bool firstCallB = true;
/**
* Initialisierung beim ersten Aufruf.
*
* Verhindert:
* - unerwünschtes Einschalten beim Systemstart
* - falsche Anzeigezustände
*/
if (firstCallB)
{
firstCallB = false;
/**
* HMI-Anzeige auf 0%
* ? Ventil geschlossen
*/
NexProgressBar_setValue(&leerInd, 0);
return;
}
/**
* Wenn Zustand unverändert:
* ? keine Aktion.
*
* Verhindert:
* - unnötige UART-Ausgaben
* - unnötiges GPIO-Toggeln
* - unnötige HMI-Kommandos
*/
else if (state == lastStateB)
{
return;
}
/**
* Zustand aktualisieren.
*/
lastStateB = state;
/**
* Ventil öffnen
*/
if(state)
{
/**
* HMI: Anzeige 100%
*/
NexProgressBar_setValue(&leerInd, 100);
/**
* GPIO setzen via Bit-Banding.
*
* Atomarer Zugriff auf einzelnes ODR-Bit.
*
* Elektrische Wirkung:
* - Ausgang High
* - Transistor / Treiber aktiviert
* - Magnetventil bekommt Versorgung
*/
LEERBLASEN = 1;
/**
* Debugmeldung über UART.
*
* Blockierend (~30 ms bei 9600 Baud).
*/
uart2_putString("Leerblasventil geoeffnet!\r\n");
}
else
{
/**
* HMI zurücksetzen.
*/
NexProgressBar_setValue(&leerInd, 0);
/**
* GPIO löschen.
*
* Magnetfeld des Ventils bricht zusammen.
* Achtung:
* Freilaufdiode notwendig!
*/
LEERBLASEN = 0;
/**
* Debugmeldung.
*/
uart2_putString("Leerblasventil geschlossen!\r\n");
}
return;
}
/**
* @brief Steuert das Fein-Dosierventil (Ein/Aus) mit Zustandsüberwachung
*
* @param state true ? Feinventil öffnen
* false ? Feinventil schließen
*
* @details
* Diese Funktion implementiert eine einfache
* zustandsabhängige Aktorsteuerung (Finite State Machine)
* für das Feinventil der Dosieranlage.
*
* Aufgaben:
* - Initialisierung beim ersten Funktionsaufruf
* - Vermeidung redundanter Schaltvorgänge
* - Synchronisierung von:
* • Nextion-HMI-Anzeige
* • GPIO-Ausgang (Bit-Banding)
* • UART-Debugausgabe
*
* Zustände:
* 0 ? Ventil geschlossen
* 1 ? Ventil geöffnet
*
* @note
* Die Funktion ist deterministisch und besitzt
* keine dynamische Speicherallokation.
*/
void fein(bool state)
{
/**
* Speichert den letzten Ventilzustand.
* static ? persistiert über Funktionsaufrufe.
*
* Implizit initialisiert mit 0 (false).
*/
static bool lastStateFein;
/**
* Kennzeichnet den ersten Aufruf der Funktion.
* Dient zur sauberen Initialisierung der Anzeige.
*/
static bool firstCallFein = true;
/**
* Initialisierungsblock.
*
* Beim ersten Aufruf:
* - Anzeige wird auf 0 gesetzt
* - Keine Hardwareänderung
*/
if (firstCallFein)
{
firstCallFein = false;
/**
* ProgressBar = 0%
* ? Ventil geschlossen anzeigen.
*/
NexProgressBar_setValue(&feinInd, 0);
return;
}
/**
* Falls sich der gewünschte Zustand
* nicht geändert hat:
*
* ? Keine Aktion durchführen.
*
* Verhindert:
* - unnötiges Toggeln der Ventilspule
* - unnötige UART-Ausgaben
* - unnötige HMI-Kommandos
*/
else if (state == lastStateFein)
{
return;
}
/**
* Zustand aktualisieren.
*/
lastStateFein = state;
/**
* Ventil öffnen
*/
if(state)
{
/**
* HMI-Anzeige auf 100%.
*/
NexProgressBar_setValue(&feinInd, 100);
/**
* GPIO-Bit setzen (Bit-Banding).
*
* Atomarer Zugriff auf einzelnes ODR-Bit.
*
* Elektrisch:
* - Ausgang HIGH
* - Treibertransistor aktiviert
* - Magnetspule des Feinventils wird bestromt
*/
FEIN = 1;
/**
* UART-Debugmeldung.
*
* Blockierend (~2530 ms bei 9600 Baud).
*/
uart2_putString("Feinventil geoeffnet!\r\n");
}
else
{
/**
* Anzeige zurücksetzen.
*/
NexProgressBar_setValue(&feinInd, 0);
/**
* Ventil abschalten.
*
* Strom durch Spule wird unterbrochen.
* Magnetfeld bricht zusammen.
*/
FEIN = 0;
/**
* Debugmeldung.
*/
uart2_putString("Feinventil geschlossen!\r\n");
}
return;
}
/**
* @brief Steuert das Grobventil (Ein/Aus) mit Zustandsüberwachung
*
* @param state true ? Grobventil öffnen
* false ? Grobventil schließen
*
* @details
* Diese Funktion implementiert eine einfache,
* zustandsabhängige Aktorsteuerung (Finite State Machine)
* für das Grobventil der Dosieranlage.
*
* Eigenschaften:
* - Initialisierung beim ersten Aufruf
* - Keine redundanten Schaltvorgänge
* - Synchronisierung von:
* • Nextion-HMI-Anzeige
* • GPIO-Ausgang (Bit-Banding)
* • UART-Debugausgabe
*
* Funktionale Rolle im System:
* Das Grobventil liefert hohen Volumenstrom
* und wird typischerweise bei großem Abstand
* zum Sollwert aktiviert.
*/
void grob(bool state)
{
/**
* Speichert den zuletzt gesetzten Zustand.
* static ? Wert bleibt zwischen Funktionsaufrufen erhalten.
*
* Implizite Initialisierung = false.
*/
static bool lastStateGrob;
/**
* Kennzeichnet den ersten Funktionsaufruf.
* Dient der Anzeigeinitialisierung.
*/
static bool firstCallGrob = true;
/**
* Initialisierungsblock.
*
* Beim ersten Aufruf:
* - Anzeige wird auf 0 gesetzt
* - Keine Hardwareänderung
*/
if (firstCallGrob)
{
firstCallGrob = false;
/**
* HMI-Anzeige ? 0%
* Ventil geschlossen darstellen.
*/
NexProgressBar_setValue(&grobInd, 0);
return;
}
/**
* Falls gewünschter Zustand identisch
* mit dem letzten Zustand ist:
*
* ? Keine Aktion durchführen.
*
* Verhindert:
* - unnötige Magnetspulen-Schaltzyklen
* - unnötige UART-Last
* - unnötige HMI-Kommunikation
*/
else if (state == lastStateGrob)
{
return;
}
/**
* Aktualisierung des gespeicherten Zustands.
*/
lastStateGrob = state;
/**
* Grobventil öffnen
*/
if(state)
{
/**
* Anzeige 100%
*/
NexProgressBar_setValue(&grobInd, 100);
/**
* GPIO setzen via Bit-Banding.
*
* Atomarer Zugriff auf ein einzelnes ODR-Bit.
*
* Elektrisch:
* - Ausgang HIGH
* - Treibertransistor aktiviert
* - Magnetspule des Grobventils wird bestromt
*/
GROB = 1;
/**
* Debugmeldung.
* Blockierend bei 9600 Baud.
*/
uart2_putString("Grobventil geoeffnet!\r\n");
}
else
{
/**
* Anzeige zurücksetzen.
*/
NexProgressBar_setValue(&grobInd, 0);
/**
* Ventil deaktivieren.
*
* Spulenstrom wird unterbrochen.
* Magnetfeld kollabiert.
*/
GROB = 0;
/**
* Debugmeldung.
*/
uart2_putString("Grobventil geschlossen!\r\n");
}
return;
}
/**
* @brief Steuert die Förderpumpe der Dosieranlage (Ein/Aus)
*
* @param state true ? Pumpe einschalten
* false ? Pumpe ausschalten
*
* @details
* Diese Funktion implementiert eine einfache
* zustandsbasierte Steuerlogik (Finite State Machine)
* für die Hauptpumpe.
*
* Eigenschaften:
* - Initialisierung beim ersten Aufruf
* - Keine mehrfachen Schaltvorgänge bei gleichem Zustand
* - Synchronisierung von:
* • Nextion-HMI-Anzeige (ProgressBar)
* • GPIO-Ausgang (Bit-Banding)
* • UART-Debugausgabe
*
* Systemtechnische Rolle:
* Die Pumpe stellt den Systemdruck und Volumenstrom bereit.
* Ventile regeln anschließend die Feindosierung.
*
* @note
* Blockierende UART-Ausgabe bei Zustandswechsel.
*/
void pumpe(bool state)
{
/**
* Speichert den zuletzt gesetzten Pumpenzustand.
* static ? persistiert über Funktionsaufrufe.
*
* Initialwert implizit = false.
*/
static bool lastState;
/**
* Kennzeichnet ersten Funktionsaufruf.
* Dient zur Initialisierung der Anzeige.
*/
static bool firstCall = true;
/**
* Initialisierungsphase.
*
* Beim ersten Aufruf:
* - Anzeige wird auf 0 gesetzt
* - Keine GPIO-Aktion
*/
if (firstCall)
{
firstCall = false;
/**
* ProgressBar auf 0%
* ? Pumpe aus anzeigen.
*/
NexProgressBar_setValue(&pumpeInd, 0);
return;
}
/**
* Falls der gewünschte Zustand
* identisch mit dem letzten Zustand ist:
*
* ? Keine Aktion.
*
* Verhindert:
* - unnötiges Schalten von Relais/MOSFET
* - unnötige UART-Ausgaben
* - unnötige HMI-Kommunikation
*/
else if (state == lastState)
{
return;
}
/**
* Zustand aktualisieren.
*/
lastState = state;
/**
* Einschalten der Pumpe
*/
if(state)
{
/**
* Anzeige 100%
*/
NexProgressBar_setValue(&pumpeInd, 100);
/**
* GPIO-Bit setzen (Bit-Banding).
*
* Elektrisch:
* - Ausgang HIGH
* - Treibertransistor aktiviert
* - Motor erhält Versorgung
*
* Bei DC-Motor:
* ? Einschaltstrom (Inrush) beachten
*/
PUMPE = 1;
/**
* Debugmeldung.
* Blockiert CPU für ~2030 ms bei 9600 Baud.
*/
uart2_putString("Pumpe eingeschaltet!\r\n");
}
else
{
/**
* Anzeige zurücksetzen.
*/
NexProgressBar_setValue(&pumpeInd, 0);
/**
* GPIO löschen.
*
* Motor wird stromlos.
* Induktive Last ? Freilaufdiode erforderlich.
*/
PUMPE = 0;
/**
* Debugmeldung.
*/
uart2_putString("Pumpe ausgeschaltet!\r\n");
}
return;
}
/**
* @brief Aktualisiert die Füllstandsanzeige (0100 %)
*
* @param value Füllstandswert in Prozent (0100)
*
* @details
* Diese Funktion übernimmt die Anzeige des aktuellen
* Füllstands auf dem Nextion-Display.
*
* Eigenschaften:
* - Initialisierung beim ersten Aufruf
* - Keine redundante Aktualisierung bei gleichem Wert
* - Plausibilitätsprüfung des Wertebereichs
* - Debug-Ausgabe bei Änderung
*
* Wertebereich:
* 0 ? leer
* 100 ? voll
*
* @note
* Datentyp int8_t erlaubt Werte von -128 bis +127.
* Daher ist eine Bereichsprüfung notwendig.
*/
void fuellstand(int8_t value)
{
/**
* Kennzeichnet den ersten Funktionsaufruf.
* Dient zur Initialisierung der Anzeige.
*/
static bool firstCall = true;
/**
* Speichert den zuletzt angezeigten Wert.
*
* Initialwert -1 dient als ungültiger Marker,
* da gültiger Bereich 0100 ist.
*/
static int8_t lastValue = -1;
/**
* Initialisierungsphase.
*
* Beim ersten Aufruf:
* - Anzeige auf 0 setzen
* - Keine weitere Aktion
*/
if(firstCall)
{
firstCall = false;
/**
* ProgressBar auf 0%
*/
NexProgressBar_setValue(&fuellstandInd, 0);
return;
}
/**
* Falls sich der Wert nicht geändert hat:
*
* ? Keine Aktualisierung durchführen.
*
* Verhindert:
* - unnötige UART-Ausgaben
* - unnötige HMI-Kommandos
*/
else if (value == lastValue)
{
return;
}
/**
* Speichert neuen Wert.
*/
lastValue = value;
/**
* Plausibilitätsprüfung.
*
* Nur Werte zwischen 0 und 100 %
* werden akzeptiert.
*/
if(value >= 0 && value <= 100)
{
/**
* Anzeige des Füllstands.
*
* Übergabe direkt als Prozentwert.
*/
NexProgressBar_setValue(&fuellstandInd, value);
/**
* Debugmeldung.
* Blockierend (~20 ms bei 9600 Baud).
*/
uart2_putString("Fuellstand geaendert!\r\n");
}
else
{
/**
* Ungültiger Wert ? Anzeige zurücksetzen.
*
* Sicherheitsmaßnahme gegen
* fehlerhafte Berechnungen.
*/
NexProgressBar_setValue(&fuellstandInd, 0);
/**
* Debugmeldung bei Fehler.
*/
uart2_putString("Uengueltiger Fuellstandswert!\r\n");
}
}
/**
* @brief Skaliert einen Messwert auf Prozent (0100 %)
*
* @param value Eingangsgröße (z.B. ADC-Wert, Schalterwert)
* @param max Maximalwert der Eingangsgröße
*
* @return Prozentwert (0100)
*
* @details
* Diese Funktion führt eine lineare Skalierung durch:
*
* Prozent = value / max * 100
*
* Um Rundungsfehler bei Ganzzahlarithmetik zu vermeiden,
* wird vor der Division max/2 addiert.
*
* Formel mit Rundung:
*
* (value * 100 + max/2) / max
*
* Dadurch wird mathematisch korrekt gerundet
* (statt immer abzurunden).
*
* Eigenschaften:
* - Integer-Arithmetik (keine Float-Berechnung)
* - Deterministisch
* - Sehr schnell
* - Geeignet für Cortex-M3 ohne FPU
*
* @note
* max darf nicht 0 sein (Division durch 0!).
*/
uint8_t toPercent(uint8_t value, uint8_t max)
{
/**
* Clipping:
* Falls value größer als max ist,
* wird value auf max begrenzt.
*
* Verhindert:
* - Prozentwerte > 100
* - Überlauf bei Berechnung
*/
if (value > max)
value = max;
/**
* Prozentberechnung mit Rundung.
*
* Schritt 1:
* value * 100
* ? Skalierung auf Prozentbasis
*
* Schritt 2:
* + max/2
* ? Rundung auf nächstes Ganzzahl-Ergebnis
*
* Schritt 3:
* Division durch max
*
* Ganzzahl-Division rundet normalerweise ab.
* Durch +max/2 wird korrekt gerundet.
*/
return (value * 100 + max / 2) / max;
}
/**
* @brief Setzt den numerischen Füllstandstext und aktualisiert die Prozentanzeige
*
* @param value Absoluter Füllwert (0255)
*
* @details
* Diese Funktion übernimmt zwei Aufgaben:
*
* 1. Formatierung des absoluten Füllwerts als Text
* ? Anzeige in Litern auf dem Nextion-Display
*
* 2. Umrechnung des Wertes in Prozent
* ? Aktualisierung der ProgressBar
*
* Systemarchitektur:
* Absolutwert (0255) ? Textanzeige "xxx l"
* Absolutwert (0255) ? Skalierung ? Prozentanzeige
*
* @note
* Verwendet sprintf(), daher relativ hoher Stack- und Laufzeitbedarf.
*/
void setFuellstand(uint8_t value)
{
/**
* Lokaler Zeichenpuffer.
*
* Größe: 20 Byte
*
* Maximaler Inhalt:
* "255 l" + '\0'
*
* 20 Byte sind mehr als ausreichend.
*/
char buf[20];
/**
* Formatierung des Wertes als String.
*
* "%u l"
*
* %u ? unsigned Integer
* " l" ? Einheit Liter
*
* Beispiel:
* value = 123
* buf = "123 l"
*
* sprintf:
* - erzeugt Null-terminierten String
* - blockierend
* - relativ rechenintensiv
*/
sprintf(buf, "%u l", value);
/**
* Übergibt den formatierten Text
* an das Nextion-Textobjekt.
*
* Intern wird ein UART-Befehl erzeugt:
* "fuellstandn.txt=\"123 l\""
*/
NexText_setText(&fuellstandText, buf);
/**
* Aktualisiert zusätzlich die Prozentanzeige.
*
* value (0255) wird linear auf
* 0100 % skaliert.
*
* 255 entspricht 100 %
*/
fuellstand(toPercent(value, 255));
}
/**
* @brief Wandelt den Zustand der DIP-Schalter in einen Integerwert um
*
* @return Ganzzahlige Bitkodierung der Schalterzustände
*
* @details
* Diese Funktion liest die digitalen Eingänge aus dem
* globalen Pointer-Array `Switch[]` ein und erzeugt daraus
* eine Bitmaske.
*
* Logik:
* - Jeder Schalter repräsentiert ein Bit.
* - Schalter gedrückt (Low-Pegel) ? Bit wird gesetzt.
* - Schalter offen (High-Pegel) ? Bit bleibt 0.
*
* Daraus ergibt sich:
* result = Summe( 2^i ) für alle aktiven Schalter.
*
* Beispiel:
* Switch0 = aktiv
* Switch1 = inaktiv
* Switch2 = aktiv
*
* ? result = 0000 0101b = 5
*
* @note
* Die Eingänge sind offenbar mit Pull-Ups beschaltet
* (Low-Active-Logik).
*/
int SwitchesToInt(void)
{
/**
* Ergebnisvariable.
*
* Initialwert = 0
* Enthält am Ende die Bitmaske.
*/
int result = 0;
/**
* Schleifenindex (06).
*/
uint8_t i = 0;
/**
* Schleife über 7 Schalter.
*
* Hinweis:
* Switch[7] wird hier NICHT berücksichtigt.
* (Vermutlich separater Start-/Stop-Schalter)
*/
for (i = 0; i < 7; i++)
{
/**
* Dereferenzieren des Zeigers:
*
* Switch[i] ? Zeiger auf ein Bit-Banding-Register
* *Switch[i] ? aktueller GPIO-Pegel (0 oder 1)
*
* Low-Active-Logik:
* 0 ? Schalter gedrückt
*/
if (!*Switch[i])
{
/**
* Setzt Bit i im Ergebnis.
*
* (1 << i)
* erzeugt eine Maske:
*
* i = 0 ? 0000 0001
* i = 1 ? 0000 0010
* i = 2 ? 0000 0100
* ...
*
* result |= Maske
* ? Bit wird gesetzt, andere bleiben erhalten.
*/
result |= (1 << i);
}
}
/**
* Rückgabe der gebildeten Bitmaske.
*/
return result;
}
/**
* @brief Hauptprogramm der Dosieranlage
*
* @details
* Implementiert:
* - Initialisierung aller Peripherien
* - HMI-Anbindung (Nextion)
* - 2-Stufen-Dosierstrategie (Grob + Fein)
* - Nachlaufkompensation
* - Leitungsleerblasen nach Dosierende
*
* Regelprinzip:
* - Grobventil ? hoher Durchfluss (Vorregelung)
* - Feinventil ? präzise Enddosierung
* - Pumpe ? Systemdruck
* - Nachlaufmodell ? Kompensation von Restvolumen
*
* System arbeitet rein polling-basiert.
*/
int main(void)
{
/* =============================
* Hardware-Initialisierung
* ============================= */
PortConfig(); /**< GPIO-Konfiguration */
uart2_init(); /**< UART2 initialisieren */
/**
* Nextion-Eventliste.
* Hier leer ? keine Touch-Events registriert.
*/
NexEventObj *nextlisten_list[] = {NULL};
configNexObjects(); /**< UI-Objekte initialisieren */
Nex_Init(0); /**< Nextion initialisieren */
Init_Nex_UI(); /**< UI-Startzustand */
/**
* Alle Anzeigen und Aktoren in definierten Startzustand setzen.
*/
fuellstand(0);
fehler(false);
leerBlasen(false);
fein(false);
grob(false);
pumpe(false);
wait_sys_ms(20); /**< kurze Stabilisierungspause */
/**
* Statusvariablen
*/
bool leerGeblasen = false; /**< merkt, ob Dosierung stattfand */
bool leitungBenutzt = false; /**< merkt, ob Durchfluss stattfand */
uart2_putString("Dosieranlage V01\r\n");
/* =============================
* Hauptschleife
* ============================= */
while(1)
{
/**
* Nextion Event-Handling (falls später erweitert)
*/
Nex_Event_Loop(nextlisten_list);
wait_sys_ms(5); /**< Entprell-/Taktintervall */
/**
* Istwert aus DIP-Schaltern lesen
* (0127)
*/
uint8_t istWert = SwitchesToInt();
/**
* Effektiver Wert inkl. Nachlaufkompensation
*/
uint16_t effektiverWert = istWert + nachlaufOffset;
/**
* Start-Schalter (Switch[7]) Low-Active?
* ? Dosierung aktiv
*/
if(*Switch[7] == 0)
{
leerGeblasen = true;
leitungBenutzt = false;
pumpe(true); /**< Pumpe einschalten */
/* =============================
* Feinregelung
* ============================= */
if(effektiverWert < SOLLWERT - NACHLAUF)
{
fein(true); /**< Feinventil öffnen */
leitungBenutzt = true;
}
else
{
fein(false);
}
/* =============================
* Grobregelung
* ============================= */
if(effektiverWert < GAP)
{
grob(true); /**< Grobventil öffnen */
leitungBenutzt = true;
}
else
{
grob(false);
}
/**
* Anzeige aktualisieren
*/
setFuellstand(effektiverWert);
wait_sys_ms(5);
}
else
{
/**
* Dosierung wurde beendet
*/
if(leerGeblasen)
{
pumpe(false);
grob(false);
fein(false);
/**
* Leitung leerblasen (Restvolumen entfernen)
*/
leerBlasen(true);
if(leitungBenutzt)
{
/**
* 3 Sekunden Nachlaufkompensation
* Aufgeteilt in NACHLAUF Schritte
*/
int time = 3000 / NACHLAUF;
int rest = 3000 % NACHLAUF;
uint8_t i = 0;
for(i = 0; i < NACHLAUF; i++)
{
wait_sys_ms(time);
/**
* Restzeit verteilen
*/
if(rest > 0)
{
wait_sys_ms(1);
rest -= 1;
}
/**
* Offset erhöhen
* ? simuliert Restvolumen
*/
nachlaufOffset += 1;
setFuellstand(istWert + nachlaufOffset);
}
}
else
{
wait_sys_ms(3000);
}
leerBlasen(false);
leerGeblasen = false;
}
}
}
return 0; /**< wird nie erreicht */
}