diff --git a/main_kommentiert.c b/main_kommentiert.c new file mode 100644 index 0000000..9fcd3a1 --- /dev/null +++ b/main_kommentiert.c @@ -0,0 +1,2035 @@ +#include +/** + * CMSIS-Header für STM32F10x. + * Enthält: + * - Registerdefinitionen + * - Peripheral-Strukturen + * - Basisadressen + */ + +#include +/** + * armv30_std Bibliothek. + */ + +#include +/** + * Nextion-HMI-Bibliothek. + * Stellt UI-Kommunikation über UART bereit. + */ + +#include +/** + * 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 0–7) + * und CRH (Pins 8–15) 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 0–7). + * &= 0 löscht alle 32 Bits. + */ + + GPIOB -> CRL = 0x22222222; + /** + * 0x2 = 0010b pro Pin + * MODE=10 (2 MHz Output) + * CNF=00 (Push-Pull) + * + * Ergebnis: + * PB0–PB7 = 2 MHz Push-Pull Ausgang + */ + + GPIOB -> CRH &= 0x00000000; + /** + * Reset von PB8–PB15 + */ + + GPIOB -> CRH |= 0x88880000; + /** + * 0x8 = 1000b pro Pin + * MODE=00 (Input) + * CNF=10 (Pull-Up/Pull-Down) + * + * PB12–PB15 werden als Eingang mit Pull-Widerstand konfiguriert. + */ + + /** + * ============================ + * Konfiguration GPIOA + * ============================ + */ + + GPIOA -> CRL &= 0x00000000; /**< Reset Pins PA0–PA7 */ + GPIOA -> CRL = 0x22222222; + /** + * PA0–PA7 = 2 MHz Push-Pull Outputs + */ + + GPIOA -> CRH &= 0x00000000; /**< Reset Pins PA8–PA15 */ + GPIOA -> CRH |= 0x00088000; + /** + * 0x8 entspricht wieder: + * Input mit Pull-Up/Pull-Down + */ + + /** + * ============================ + * Konfiguration GPIOC + * ============================ + */ + + GPIOC -> CRL &= 0x00000000; /**< Reset PC0–PC7 */ + + GPIOC -> CRL |= 0x28222222; + /** + * Mischung aus: + * 0x2 ... Output 2 MHz Push-Pull + * 0x8 ... Input Pull-Up/Pull-Down + */ + + GPIOC -> CRH &= 0x00000000; /**< Reset PC8–PC15 */ + + 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 PB12–PB15 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 0–7. + * 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 (~25–30 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 ~20–30 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 (0–100 %) + * + * @param value Füllstandswert in Prozent (0–100) + * + * @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 0–100 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 (0–100 %) + * + * @param value Eingangsgröße (z.B. ADC-Wert, Schalterwert) + * @param max Maximalwert der Eingangsgröße + * + * @return Prozentwert (0–100) + * + * @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 (0–255) + * + * @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 (0–255) ? Textanzeige "xxx l" + * Absolutwert (0–255) ? 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 (0–255) wird linear auf + * 0–100 % 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 (0–6). + */ + 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 + * (0–127) + */ + 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 */ +} +