1683 lines
33 KiB
C
1683 lines
33 KiB
C
#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 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 = 0x0753;
|
||
/**
|
||
* 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
|
||
*
|
||
* @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
|
||
*/
|
||
void uart2_putChar(char zeichen)
|
||
{
|
||
/**
|
||
* Warten bis TXE (Bit 7 im Statusregister) gesetzt ist.
|
||
*
|
||
* USART2->SR & 0x80:
|
||
* 0x80 = 1000 0000b ... Bit 7 auf 1
|
||
*
|
||
* Solange TXE = 0 bleibt,
|
||
* befindet sich noch ein Zeichen im Datenregister.
|
||
*/
|
||
while(!((USART2->SR & 0x80) == 0x80));
|
||
|
||
/**
|
||
* Schreiben des Zeichens in das Datenregister.
|
||
*
|
||
* Das Schreiben startet sofort:
|
||
* - Startbit wird generiert
|
||
* - 8 Datenbits werden seriell ausgegeben
|
||
* - Stopbit folgt
|
||
*/
|
||
USART2->DR = zeichen;
|
||
}
|
||
|
||
/**
|
||
* @brief Sendet eine Zeichenkette über USART2
|
||
*
|
||
* @param string Zeiger auf ein char-Array (char*)
|
||
*
|
||
* @details
|
||
* Diese Funktion überträgt eine ASCII-Zeichenkette,
|
||
*
|
||
* char-Array ist mit (\n) nullterminiert!
|
||
* Dieses wird nicht mitgesendet.
|
||
*
|
||
* Die Funktion arbeitet blockierend und ruft
|
||
* für jedes Zeichen uart2_putChar() auf.
|
||
*/
|
||
void uart2_putString(char* string)
|
||
{
|
||
/**
|
||
* Schleife läuft solange:
|
||
* *string != 0
|
||
*
|
||
* Das Dereferenzieren (*string) liest
|
||
* das aktuelle Zeichen im Speicher.
|
||
*/
|
||
while(*string)
|
||
{
|
||
/**
|
||
* Übergibt aktuelles Zeichen
|
||
* an uart2_putChar().
|
||
*
|
||
* *string liest das Zeichen.
|
||
* string++ erhöht den Pointer
|
||
* auf das nächste Zeichen.
|
||
*/
|
||
uart2_putChar(*string++);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @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.
|
||
*/
|
||
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.
|
||
*/
|
||
obj->name = name;
|
||
|
||
/**
|
||
* Setzt den Pop-Handler auf NULL.
|
||
*
|
||
* pophandler wird ausgelöst,
|
||
* wenn ein Button losgelassen wird.
|
||
*/
|
||
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.
|
||
*
|
||
* @note
|
||
* Alle Objekte liegen auf derselben Page (NEX_PID_MAIN)
|
||
*/
|
||
void configNexObjects(void)
|
||
{
|
||
/**
|
||
* Füllstandsanzeige (ProgressBar)
|
||
*/
|
||
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
|
||
*/
|
||
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
|
||
*
|
||
* Zustände:
|
||
* 0 ... Kein Fehler
|
||
* 1 ... Fehler aktiv
|
||
*/
|
||
void fehler(bool state)
|
||
{
|
||
/**
|
||
* Speichert den letzten gesetzten Zustand.
|
||
* static ... bleibt über Funktionsaufrufe erhalten.
|
||
*
|
||
* Initialwert = true
|
||
*/
|
||
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.
|
||
*/
|
||
FEHLER = 1;
|
||
|
||
/**
|
||
* UART-Debugmeldung.
|
||
*/
|
||
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
|
||
*
|
||
* Zustand 0 ... Ventil geschlossen
|
||
* Zustand 1 ... Ventil geöffnet
|
||
*/
|
||
void leerBlasen(bool state)
|
||
{
|
||
/**
|
||
* Speichert den zuletzt gesetzten Zustand.
|
||
* static ... bleibt über Funktionsaufrufe erhalten.
|
||
*/
|
||
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.
|
||
*/
|
||
LEERBLASEN = 1;
|
||
|
||
/**
|
||
* Debugmeldung über UART.
|
||
*/
|
||
uart2_putString("Leerblasventil geoeffnet!\r\n");
|
||
}
|
||
else
|
||
{
|
||
/**
|
||
* HMI zurücksetzen.
|
||
*/
|
||
NexProgressBar_setValue(&leerInd, 0);
|
||
|
||
/**
|
||
* GPIO löschen.
|
||
*/
|
||
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 Aktorsteuerung 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
|
||
*/
|
||
void fein(bool state)
|
||
{
|
||
/**
|
||
* Speichert den letzten Ventilzustand.
|
||
* static ... persistiert über Funktionsaufrufe.
|
||
*/
|
||
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.
|
||
*/
|
||
FEIN = 1;
|
||
|
||
/**
|
||
* UART-Debugmeldung.
|
||
*/
|
||
uart2_putString("Feinventil geoeffnet!\r\n");
|
||
}
|
||
else
|
||
{
|
||
/**
|
||
* Anzeige zurücksetzen.
|
||
*/
|
||
NexProgressBar_setValue(&feinInd, 0);
|
||
|
||
/**
|
||
* Feinventil abschalten.
|
||
*/
|
||
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 Aktorsteuerung 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
|
||
*/
|
||
void grob(bool state)
|
||
{
|
||
/**
|
||
* Speichert den zuletzt gesetzten Zustand.
|
||
* static ... Wert bleibt zwischen Funktionsaufrufen erhalten.
|
||
*/
|
||
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.
|
||
*/
|
||
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.
|
||
*/
|
||
GROB = 1;
|
||
|
||
/**
|
||
* Debugmeldung.
|
||
*/
|
||
uart2_putString("Grobventil geoeffnet!\r\n");
|
||
}
|
||
else
|
||
{
|
||
/**
|
||
* Anzeige zurücksetzen.
|
||
*/
|
||
NexProgressBar_setValue(&grobInd, 0);
|
||
|
||
/**
|
||
* Ventil deaktivieren.
|
||
*/
|
||
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 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
|
||
*/
|
||
void pumpe(bool state)
|
||
{
|
||
/**
|
||
* Speichert den zuletzt gesetzten Pumpenzustand.
|
||
* static ... persistiert über Funktionsaufrufe.
|
||
*/
|
||
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.
|
||
*/
|
||
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).
|
||
*/
|
||
PUMPE = 1;
|
||
|
||
/**
|
||
* Debugmeldung.
|
||
*/
|
||
uart2_putString("Pumpe eingeschaltet!\r\n");
|
||
}
|
||
else
|
||
{
|
||
/**
|
||
* Anzeige zurücksetzen.
|
||
*/
|
||
NexProgressBar_setValue(&pumpeInd, 0);
|
||
|
||
/**
|
||
* GPIO löschen.
|
||
*/
|
||
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.
|
||
*/
|
||
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.
|
||
*/
|
||
uart2_putString("Fuellstand geaendert!\r\n");
|
||
}
|
||
else
|
||
{
|
||
/**
|
||
* Ungültiger Wert ... Anzeige zurücksetzen.
|
||
*/
|
||
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
|
||
* @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).
|
||
*
|
||
* @note
|
||
* max darf nicht 0 sein (Division durch 0!).
|
||
*/
|
||
uint8_t toPercent(uint8_t value, uint8_t max)
|
||
{
|
||
/**
|
||
* Falls value größer als max ist,
|
||
* wird value auf max begrenzt.
|
||
*
|
||
* Verhindert:
|
||
* - Prozentwerte > 100
|
||
*/
|
||
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
|
||
*/
|
||
void setFuellstand(uint8_t value)
|
||
{
|
||
/**
|
||
* Lokaler Zeichenpuffer.
|
||
*
|
||
* Größe: 20 Byte
|
||
*/
|
||
char buf[20];
|
||
|
||
/**
|
||
* Formatierung des Wertes als String.
|
||
*
|
||
* "%u l":
|
||
* - %u ... unsigned Integer
|
||
* - " l" ... Einheit Liter
|
||
*
|
||
* sprintf:
|
||
* - erzeugt Null-terminierten String
|
||
*/
|
||
sprintf(buf, "%u l", value);
|
||
|
||
/**
|
||
* Übergibt den formatierten Text
|
||
* an das Nextion-Textobjekt.
|
||
*/
|
||
NexText_setText(&fuellstandText, buf);
|
||
|
||
/**
|
||
* Aktualisiert zusätzlich die Prozentanzeige.
|
||
*
|
||
* value (0–255) wird linear auf
|
||
* 0–100 % skaliert.
|
||
*/
|
||
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
|
||
*/
|
||
int SwitchesToInt(void)
|
||
{
|
||
/**
|
||
* Ergebnisvariable.
|
||
*
|
||
* Initialwert = 0
|
||
* Enthält am Ende die Bitmaske.
|
||
*/
|
||
int result = 0;
|
||
|
||
/**
|
||
* Zählvariable (0–6).
|
||
*/
|
||
uint8_t i = 0;
|
||
|
||
/**
|
||
* Schleife über 7 Schalter.
|
||
*
|
||
* Hinweis:
|
||
* Switch[7] wird hier NICHT berücksichtigt.
|
||
*/
|
||
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)
|
||
*
|
||
* Active-Low-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
|
||
*/
|
||
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
|
||
*/
|
||
Nex_Event_Loop(nextlisten_list);
|
||
|
||
wait_sys_ms(5); /**< Entprell-/Taktintervall */
|
||
|
||
/**
|
||
* Istwert aus DIP-Schaltern lesen
|
||
* (0–127)
|
||
*/
|
||
uint8_t istWert = SwitchesToInt();
|
||
|
||
/**
|
||
* Effektiver Wert inklusive 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; /**< 3 Sekunden in passende Teile dividieren */
|
||
int rest = 3000 % NACHLAUF; /**< Rest berechnen */
|
||
|
||
fein(true);
|
||
grob(true);
|
||
|
||
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);
|
||
}
|
||
|
||
fein(false);
|
||
grob(false);
|
||
leerBlasen(false);
|
||
leerGeblasen = false;
|
||
}
|
||
}
|
||
}
|
||
}
|