Source file: /~heha/enas/Convac-Ätzer/Firmware-190517.zip/Convac.h

#pragma once
// Alle Quelldateien müssen in UTF-8 ohne BOM gehalten werden!
// (Nicht mit Windows Notepad bearbeiten [generiert BOM]
// sondern bspw. mit Programmers Notepad, Notepad++ oder unter Linux)

// Damit man nicht auf die Idee kommt, Windows Notepad zu verwenden,
// sind die Zeileenden Unix-kompatibel auf "\n" gestellt.
// Damit erscheinen diese Dateien einzeilig und damit ziemlich schräg.

// Die Tabulatorsprungweite ist 8, die Einrücktiefe 1 Leerzeichen.
// Mit der Vorgabe von Visual Studio (4) erscheinen die Kommentare zerzaust.
// Der Einrückungsstil ist in etwa K&R.

/*************
 * allgemein *
 *************/

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdlib.h>		// exit, abs, labs

typedef uint8_t byte;
typedef uint16_t word;
void bootstart() __attribute__((naked,noreturn));
bool eeprom_update(const void*ram, void*eeprom, size_t len);	// <true>=busy
#define NOP2() asm volatile("rjmp .")	// 2 Takte warten
#define elemof(x) (sizeof(x)/sizeof(*(x)))

// Zahlenkonvertierung
extern char sbuf[32];		// dlgAnalog.cpp
char* utox(word v, char w);
inline char* utox(const void*v) {return utox(word(v),4);}
int strfloat(const char*p, char nk=0);
char* floatstr(int m, char nk=0);

// Optimierte Rechenroutinen
int rounding_div(long a,int b);
int rounding_shr8(long v);

struct udiv_t{word quot,rem;};
extern "C" udiv_t __udivmodhi4(word,word);
#define udiv(x,y) __udivmodhi4(x,y)

// Rechtsschieben eines Bytes, assembleroptimiert, automatisch modulo 8
inline byte shrb(byte b, byte count) {
 asm(	"	andi	%1,7	\n"
	"	breq	2f	\n"
	"1:	lsr	%0	\n"
	"	dec	%1	\n"
	"	brne	1b	\n"
	"2:			\n"
 :"=r"(b),"=d"(count):"0"(b),"1"(count));
 return b;
}
// Linksschieben, wie oben
inline byte shlb(byte b, byte count) {
 asm(	"	andi	%1,7	\n"
	"	breq	2f	\n"
	"1:	lsl	%0	\n"
	"	dec	%1	\n"
	"	brne	1b	\n"
	"2:			\n"
 :"=r"(b),"=d"(count):"0"(b),"1"(count));
 return b;
}
// Nibbles tauschen
inline byte swap(byte b) {
 asm(	"	swap	%0	\n"
 :"=r"(b):"0"(b));
 return b;
}

void printSbufRight(byte x);	// rechtsbündige (Zahlen-)Ausgabe

/******************************************************************
 * 8-Bit-Bus-I/O (Rückverdrahtung + Hosenträgerkabel zum Display) *
 ******************************************************************/

struct BUS{
 static byte ioRead(byte a);		// Lesezyklus von I/O-Adresse
 static void ioWrite(byte a, byte b);	// Schreibzyklus auf I/O-Adresse
// Basisadressen. Diese hängen vom Steckplatz ab. Bits 5 und 7 fehlen.
// Subadressen (0..7) in den Bits 2:0 werden darauf addiert
 enum BA{
  BA_DISPLAY	=0x00,
  BA_KEYPAD	=0x08,
  BA_INPUT	=0x10,
  BA_OUTPUT1	=0x18,
  BA_ANALOG	=0x40,	// eigentlich 0x20, aber Bit 5 geht nach Bit 6
  BA_OUTPUT2	=0x48,	// eigentlich 0x28, wie eben
 };
 static void setXaddr(byte a)	{PORTF=PORTF&0x8F|a<<4;}
};


/**********************************************
 * Anzeige, Tasten und Pieper (Bedienkonsole) *
 **********************************************/

// Siehe Display.h (sehr umfangreich weil grafikfähig)

extern "C" const PROGMEM byte ArialP12[];	// Schrift CP1252
extern "C" const PROGMEM char Ndi[],Ndo1[],Ndo2[];
extern "C" const PROGMEM char Na[];		// Namen der Analogkanäle
extern "C" const PROGMEM char* const Nlist[];	// Namen der Digitalkanäle

struct CONFIG{
 byte focusb;	// Fokussierte Bitnummer (bspw. mit Eingangsbeschreibung)
 byte seite;	// Seite der Ports-Anzeige, 0 = Eingänge, 1, 2 = Ausgänge
 byte focusa;	// Fokussierter Analogkanal
 byte anapos;	// 0 = Hexadezimalwert, 1 = Dezimalwert, 2 = umgerechneter Wert
 byte focusr;	// Zeilennummer im Rezepteditor
 byte focuse;	// Zeilennummer im Alarmbetrachter
 byte rsv;	// aktuelle Aktion oder Testbedingung (im Editor)
 byte runrez;	// zu startendes Rezept
};		// EEPROM-persistente Konfigurationsdaten

struct KEYS{
 static void peek();
 static byte readchar()	{peek(); byte b=buffer; buffer=0; return b;}
private:
 static byte readkey();
 static byte readkey(byte);
 static byte last;	// Enthält zuletzt abgefragten Tastenzustand
 static byte buffer;	// Enthält zuletzt gedrückte Taste, 0 nach Abholung
};

void beep(byte f, byte ms10);

void startFocusCounter();	// Fokuszähler zurücksetzen

namespace CLOCK{
void paint();	// ohne Kontextsicherung
void show();	// mit Kontextsicherung, nur wenn kein Zoom
}

/************************
 * ListView (Scrollbox) *
 ************************/

#include "Display.h"

// Malt ein Fokus-Rechteck im XOR-Modus, ohne focusVis zu beeinflussen
void xorFocusRect(byte l, byte t, byte r, byte b);

// Diese Scrollbox kann nur vertikal scrollen.
// Die „Listenelemente“ werden mittels Y-Pixelposition und Höhe angegeben.
// Benötigt globale Variable „DISP d“
struct ListView{
 byte L,T,R,B;	// Abmessungen der Scrollbox (R und B inklusive)
 byte dih;	// Default-Itemhöhe (für gleichmäßig hohe Items)
 word len;	// Länge der Daten in Pixel (= Zeilen × Zeilenhöhe)
 word pos;	// Position in Pixel, 0 = oberer Anschlag, len-B+T = unterer Anschlag
 void init(byte l, byte t, byte r, byte b)	{L=l; T=t; R=r; B=b;}
 void setLen(word j)	{len=j; onLenChange();}
	// Alles neu zeichnen: Füllt Freiraum weiß,
	// setzt redraw-Flag und Clipping-Bereich wenn len>0
	// Verändert pos nicht! (Ungünstig?)
 void onLenChange();
	// Liefert notwendige Verschiebung für scrollIntoView
	// <ip> sollte nicht größer als der (künftige) Wert von .len sein
 int posIntoView(word ip, byte ih=0);
	// Höhe der gesamten Liste; bewirkt stets Neuaufbau der Liste
	// und malt Scrollbar neu.
	// Ist Listenlänge < Boxhöhe, wird der Freibereich weiß ausgemalt.
	// Ist Listenlänge ≤ Boxhöhe, bleibt <pos> auf Null fixiert.
	// Verkleinert Clipping-Bereich, der auszumalen ist.
	// Setzt repaint-Flag
 void scrollIntoView(word ip, byte ih=0);
	// Listeneintrag der Höhe ih vollständig sichtbar machen,
	// und ggf. Inhalt verschieben und Scrollbar neu malen.
	// Verkleinert bei Returnwert <true> den Clipping-Bereich
	// zum Aktualisieren sichtbar geworderner Information
	// (Wird nicht gelöscht! Kann mit d.fillClip() erledigt werden.)
	// Setzt repaint-Flag wenn ungültige Bereiche entstehen
 void collapse(word ip, int dy);
	// Liste ab ip verkürzen oder verlängern (dy negativ)
	// und ggf. Teil-Inhalt verschieben und Scrollbar neu malen.
	// Clipping-Bereich wie bei scrollIntoView()
	// Setzt repaint-Flag wenn ungültige Bereiche entstehen
 void paintScrollbar();
	// Wird von setLen(), scrollIntoView() und collapse() gerufen.
	// Muss extra aufgerufen werden, wenn's (per Clipping) verdeckt wurde
 bool itemVisible(word, byte ih=0);	// Testet Element im sichtbaren Bereich
 bool itemInvalid(word, byte ih=0);	// Testet Element im Clipping-Bereich
 void fillItem(int ip, int ih=0, byte l=0, byte r=255);
	// mit „word ih“ (statt „byte ih“) kann auch alles gelöscht werden
	// Beachtet Grenzen der Scrollbox und den aktuellen Clipping-Bereich
	// Operation je nach d.flags (sollte zumeist R2_ZERO sein).
 void fillAll()		{fillItem(0,32767);}
 void andItemClip(word ip, byte ih=0);
	// Clipping-Bereich für Einzelelement verkleinern,
	// exklusive Rollbalken.
	// Das Element muss sichtbar sein!!
 void setFullClip();
	// Clipping-Bereich „aufreißen“ auf gesamte Scrollbox
	// inklusive Rollbalken
	// (Wird für das Hineinmalen von sich ändernden Daten benötigt
	// bzw. für ein animiertes Fokusrechteck.)
	// Keine ListView-Funktion ruft diese Funktion auf!
 void xorFocusRect(word ip, byte ih=0, byte l=0, byte r=255);
	// Element muss sichtbar sein!!
	// Clipping muss ggf. per setClip() oder setItemClip() eingeschränkt sein!
	// (Im allgemeinen ist jedoch das Item mit dem Focusrechteck
	// vollständig sichtbar.)
	// focusVis wird nicht beeinflusst
 enum{
  SBW=10,	// Breite der Scrollbar (geht von der Scrollbox weg)
  SBM=4,	// „Mitte“ der Scrollbar
  MINH=10	// Minimale Höhe der Scrollbox (wird nicht geprüft)
 };
};

/**********************************
 * Edit (Zeileneditor für Zahlen) *
 **********************************/

// Textausgabebereich vorbereiten: Clipping setzen und löschen
void prepareField(byte l, byte t, byte r, byte b, bool andclip=false);

// Einzeilige Texteingabe, Scrollen ist nicht vorgesehen
// Benötigt globale Variable „DISP d“
// Nur für ASCII, nicht für UTF-8!
struct Edit {
 byte L,T,R,B;	// Abmessungen (inklusive Rand)
 byte cursor;	// Schreibmarke
 byte maxlen;
 byte strlen;	// aktuelle Stringlänge (ohne \0)
 char text[32];	// Zeichenpuffer, nullterminiert, max. 31 ASCII-Zeichen
// void init(byte,byte,byte,byte,byte);
 void update();
 void removeLeadingZero();
	// Löscht führende Null, wenn Cursor am Ende und String voll
 void setText(const char*);
 bool handleInput(byte kode);
	// liefert FALSE bei irgendeinem Fehler:
	// - Zeichen außerhalb ASCII / nicht darstellbar
	// - String zu lang
	// - Kursorbewegung am Ende
 void xorCursor();
 enum{
  CUR=3,	// Cursor nach rechts (ESC [ C)
  CUL=4,	// Cursor nach links (ESC [ D)
  BS=8,		// Löschen nach links (BS)
 };
};

/*******************************
 * Karten (Maschinensteuerung) *
 *******************************/
// Um das Programm schneller ablaufen zu lassen,
// sind stets 8 I/O-Ports zu einem Byte zusammengefasst.
// Damit ist das Adressbyte eine Bytenummer.
struct DIGITAL{
 static byte getIo(byte);	// liefert Eingang^ios[a] oder nur ios[a]
 static void setIo(byte,byte);	// setzt ios[a] und ggf. den Ausgang
 static byte getDir(byte);	// Richtung, 1 = Ausgang
 static byte getAvail(byte);	// Verfügbarkeits-Maske, 1 = ja
 static byte getMax()		{return 11;}	// max. Bytenummer
 static bool getBit(byte);
 static void setBit(byte,bool);
 static void getPin(byte);	// Anschlussname der Bitnummer nach sbuf
 static void initOutputs();	// Alle Ausgänge mit ios[] initialisieren
 static void getInputs(byte data[12]);	// Eingänge lesen (zum Erkennen von Flanken später)
 static byte ios[12];		// Eingang: XOR-Werte; Ausgang: Spiegelbits
};

// Jeder Kanal, der mehr als nur einen Binärwert verarbeitet
// aber andererseits nicht „streamt“, ist ein Analogkanal.
// Dazu zählen auch PID-Reglerkanäle; diese bieten einen Sollwert
// und eine Regelabweichung (Noch nicht implementiert!)
struct ANALOG{
 static int getIo(byte);	// liefert A/D-Wert oder letzten D/A-Wert
 static void setIo(byte,int);	// setzt D/A-Wert oder startet A/D-Umsetzung
 static int getMin(byte);	// liefert Minimalwert
 static int getMax(byte);	// liefert Maximalwert
 static char getBits(byte);	// liefert Bitzahl (für Hexadezimaldarstellung)
 static byte getMax()		{return 20;}	// max. Kanalnummer
 static bool getDir(byte c)	{return 9<=c && c<18;}	// true = Ausgang
 static void getPin(byte);	// Anschlussname nach sbuf
 static void initOutputs();	// alle D/A-Wandler initialisieren
 static struct ios_t{
  int adc12;		// Addierwert (zum Debuggen) für AD574
  byte adc8[8];		// Addierwert (zum Debuggen) für AD7828
  int dac14;		// Ausgabepuffer für AD7534
  byte dac8[8];		// Ausgabepuffer für AD7228
 }ios;
 static struct scale_t{
  int gain,offset;	// Skalierung und Verschiebung
  char nk;		// darzustellende Nachkommastellen
  char unit[7];		// Anzuzeigende Einheit (UTF-8)
 }scale[21];		// Umrechnungen (im EEPROM!?)
};

// Hilfsdaten für getPin()
extern const PROGMEM char eca[];
void mkeca(byte start, byte nr);

/****************************
 * Prozess (Programmablauf) *
 ****************************/
inline void assert(bool okay,int code) {
#ifdef DEBUG
 if (!okay) exit(code);
#endif
}

// Das Register GPIOR0 ist ideal als globaler Bitspeicher zu verwenden
// Belegung:
//	Bit 0	Fokusrechteck sichtbar
//	Bit 1	Zoom-Modus aktiv (kein Menü, kein Header, keine Uhr)
//	Bit 2	idle() darf Markierung (in Rezeptliste) malen/verschieben
//	Bit 3	Globale und lokale Sicherheit sichtbar (in Rezeptliste)
//	Bit 4	Redraw-Bit (zz. nur für Rezeptliste)
//	Bit 5	-
//	Bit 6	-
//	Bit 7	In Idle (Rekursion vermeiden) = LED-Anzeige?
// Es ist nach Reset mit Null initialisiert.
inline void xorFocusVis()	{GPIOR0^=1;}
inline void clrFocusVis()	{GPIOR0&=~1;}
inline void setFocusVis()	{GPIOR0|=1;}
#define focusVis (GPIOR0&1)	// Sichtbares Fokusrechteck (bei Inaktivität)
inline void xorZoom()	{GPIOR0^=2;}
#define zoom (GPIOR0&2)	// Vergrößerte Liste ohne Anzeige der F-Tasten; idle() darf keine Uhr malen
inline void markYes()	{GPIOR0|=4;}	// idle() darf Markierungen malen
inline void markNo()	{GPIOR0&=~4;}	// idle() darf keine Markierungen malen
#define mark (GPIOR0&4)	// Anzeige-Ändern der aktiven Aktion durch Hintergrundprozess erlaubt/verboten
inline void xorShowSafe()	{GPIOR0^=8;}
#define showSafe (GPIOR0&8)	// Anzeige von Sicherheitsfunktionen in der Rezeptliste ein/aus
inline void setRedraw()	{GPIOR0|=0x10;}	
inline void clrRedraw()	{GPIOR0&=~0x10;}
#define redraw		(GPIOR0&0x10)		// allgemeines Redraw-Flag (dlgXxx)
inline void setReLine()	{GPIOR0|=0x20;}	
inline void clrReLine()	{GPIOR0&=~0x20;}
#define reLine		(GPIOR0&0x20)		// spezielles Redraw-Flag (nur dlgRezept)
inline void setAllNew()	{GPIOR0|=0x40;}	
inline void clrAllNew()	{GPIOR0&=~0x40;}
#define allNew		(GPIOR0&0x40)		// Alles-neu-Flag (nur dlgRezet)
inline void enterIdle()	{GPIOR0|=0x80;}	
inline void leaveIdle()	{GPIOR0&=~0x80;}
#define inIdle (GPIOR0&0x80)			// Rekursion vermeiden

/* Der Programmspeicher ist als dreistufiger Baum organistert:
 Rezept (obere Ebene) - nur eines davon ist aktiv
  Schritt (mittlere Ebene) - werden sequenziell abgearbeitet, jede mit einer bestimmten Zeit
   Befehl (untere Ebene) - werden quasi-gleichzeitig abgearbeitet innerhalb eines Schritts
 Der erste Schritt eines Rezepts enthält Prüfungsschritte für alle weiteren Schritte.
 Dessen Zeitangabe wird ignoriert und dient als Maximaldrehzahl für das Rezept.
 Der letzte Schritt sollte alle Aufräumarbeiten beinhalten, da bei „graceful“-Abbruch
 dieser letzte Schritt angesprungen wird.
 
 Zusätzlich gibt es eine globale Sicherungsliste,
 die _für_ _alle_ _Rezepte_ Abbruchbedingungen realisiert.
 Diese befindet sich im Speicher in Form eines „Schrittes“ vor dem ersten Rezept.
 
 Die Rezepte haben keine Namen, nur Indizes.
 */

namespace ANI{	// Animationsrechteck für Aktionsnummer oben rechts
void animate();	// mit Kontextsicherung, Aufruf mit 10 Hz
void show();	// mit Kontextsicherung
void paint();	// ohne Kontextsicherung
}

enum OPCODE{
// Belegung der Opcode-Bits:
// 7	Eintrag ins Fehler-Log wenn wahr (außer bei 6:5 = 00 und 01)
// 6:5	Aktion bei Gleichheit oder Wahrheit
//	Außer dem Logging werden keine Aktionen sofort ausgeführt,
//	sondern erst wenn die gesamte Liste der Einzelaktionen ausgeführt wurde.
//	(Es werden IMMER alle Einzelaktionen eines Schrittes ausgeführt.
//	Führen mehrere Schalter BREAK oder CONTINUE, gibt's nur eine Aktion.)
//	00	Ausgabe
//	01	Lesen, nächster Sequenzschritt wenn wahr (Timeout des _Schritts_ wenn nicht wahr in der gegebenen Zeit)
//	10	Lesen, Start verhindern wenn wahr (nur für Startknopf)
//	11	Lesen, Sequenz abbrechen wenn wahr
// 4	Veränderung statt Absolutwert prüfen (ungenutzt)
// 3	Bit-Wert (1 oder 0), Relation bei Analogwert (größer oder kleiner)
// 2:0	Länge der folgenden Daten
//	000 = keine Daten, Bitwert im Bit 2
//	001 = 1 Byte, Wertebereich 0..255 (vzl.)
//	010 = 2 Byte, Wertebereich -32766…+32766, -∞, +∞, NaN
//	alles andere ungenutzt
// Ausgang setzen, ohne Aktion
 OP_D0		=0x00,
 OP_D1		=0x08,
 OP_A8		=0x01,
 OP_A16		=0x02,
// Eingang prüfen, Aktion siehe unten
 OP_A8_LT	=0x01,
 OP_A16_LT	=0x02,	// Aktion wenn Analogwert kleiner
 OP_A8_GT	=0x09,
 OP_A16_GT	=0x0A,	// Aktion wenn Analogwert größer
// Aktionen (auf abgefragten Eingang)
 OP_OUT		=0x00,
 OP_CNT		=0x20,	// (continue) fortfahren zum nächsten Sequenzschritt
 OP_DNT		=0x40,	// (don't) Start verhindern (ungenutzt?)
 OP_BRK		=0x60,	// (break) Sequenz abbrechen
 OP_LOG		=0x80,
};

/* Virtuelle Methoden kommen hier nicht in Frage, weil
 1. diese Strukturen speichersparend in den EEPROM müssen 
 2. diese umständlich im avr-gcc compiliert werden
 */

struct BEFEHL{		// 2..9 Byte
 byte operation;	// Kode siehe oben
 byte index;		// Nummer des Analogkanals oder Bits
 byte value[];		// Wert (Länge je nach operation[2:0] = 0..7)
	void	apply()		const;
	void	applyMult(byte)	const;	// Liste abarbeiten
	void	print(byte i)	const;	// Anweisung auf Display schreiben
	byte	vlen()		const	{return operation&7;}
 const	byte*	end()		const	{return value+vlen();}		// Datenende
 const	BEFEHL*	fin()		const	{return (const BEFEHL*)end();}	// Strukturende
	byte	getLines()	const	{return 1;}
	int	getv()		const;	// Datenwert lesen
	bool	setv(int data);		// Datenwert setzen, liefert FALSE wenn data zu groß oder NaN, verschiebt ggf. Speicher
	bool	swapAna();		// Schaltet Analog/Digitalkanal um, verschiebt stets Speicher, neuer Analogwert = 0
	bool	sscanKanal(const char*);// Zu Kanaladresse (≥0) konvertieren und setzen, false bei Fehler
 const	char*	sprintKanal(bool dot=true)	const;	// Digital- oder Analogadresse formatieren
 const	char*	sprintVal(bool) const;	// Wert ggf. mit Einheit nach sbuf ausspucken (Kommaposition!)
	bool	sscanVal(const char*);	// wie setv() aber ggf. mit Kommaposition
};

union N{	// Eine Kombination aus Elementanzahl des Unterbaums und Collapse-Bit
 byte b;	// beides zusammen (Kodeoptimierung)
 struct{
  byte c:7;	// Anzahl der Unterelemente: 0 = kein [+] oder [-] darstellen, maximal 63 (Bit 6 ungenutzt)!
  byte f:1;	// 0 = geöffnet, [-] darstellen; 1 = geschlossen, [+] darstellen
 };
};

struct SCHRIT{		// Länge schwierig zu kalkulieren
 int ms10;		// Zeit für den Schritt, bei Schritt 0 = max. Drehzahl
 N n;			// Anzahl folgender <b> sowie Aufklapp-Status
 BEFEHL be[];		// (variablel-lange) Liste der Einzelaktionen
	void	print(byte i)	const;	// Schritt auf Display schreiben
 const	BEFEHL*	be0()		const	{return be;}
 const	BEFEHL*	operator+(byte j) const	{const BEFEHL*p=be0();for(;j;--j)p=p->fin();return p;}		// Index
 const	BEFEHL&	operator[](byte j) const{return *operator+(j);}
 const	BEFEHL*	end()		const	{return operator+(n.c);}	// Listenende
 const	SCHRIT*	fin()		const	{return (const SCHRIT*)end();}// Strukturende
	byte	getLines()	const	{return 1+(!n.f?n.b:0);}	// für den TreeView
	void	apply()		const	{be->applyMult(n.c);}
};

struct REZEPT{		// Länge schwierig zu kalkulieren
 N n;			// Anzahl folgender <a> sowie Aufklapp-Status
 SCHRIT st[];		// (variabel-lange) Sequenz, die erste enthält die rezeptlokalen Prüfungen mit der Zeit
	void	print(byte i)	const;
 const	SCHRIT*	st0()		const	{return st;}		// Beginn der Aktionen
 const	SCHRIT*	operator+(byte j) const	{const SCHRIT*p=st0();for(;j;--j)p=p->fin();return p;}
	SCHRIT&	operator[](byte j)	{return *(SCHRIT*)operator+(j);}
 const	SCHRIT*	end()		const	{return operator+(n.c);}	// Listenende
 const	REZEPT*	fin()		const	{return (const REZEPT*)end();}// Strukturende
	byte	getLines()	const	{byte k=1; if (!n.f) {
					  const SCHRIT*p=st;
					  for(byte j=n.b;j;p=p->fin(),--j)
					  if (showSafe||p!=st) k+=p->getLines();
					 }
					 return k;}
};

// Dynamisch verwaltete Rezeptliste
extern struct RLISTE{
 byte nRezept;
 REZEPT re[];
 const	REZEPT*	operator+(byte j) const {const REZEPT*p=re;for(;j;--j)p=p->fin();return p;}
	REZEPT&	operator[](byte j)	{return *(REZEPT*)operator+(j);}
 const	REZEPT*	end()		const	{return operator+(nRezept);}	// Listenende
	word	size()		const	{return (const byte*)end()-(const byte*)this;}
	byte	getLines()	const	{byte k=0; const REZEPT*p=re;for(byte j=nRezept;j;p=p->fin(),--j) k+=p->getLines(); return k;}
}*rl;
extern word gslen;	// Länge aller dynamischen Daten = Ergebnis von gs.size() + rl.size()

/* Beispiel:		Tür	Motor	Medium	Drehz.	Zeit/s
 Tür schließen: 	Z	-	-	-	0,5
 Spindel hochfahren:	0	1	-	200	4
 Ätzmedium drauf:	-	-	1	-	0,3
 Verteilen:		-	-	0	-	60
 Runterfahren		-	-	-	0	4
 Tür öffnen		A	0	-	-	0,5
 Ende			0	-	-	-	-
 */

union GS{	// Globale Sicherheitsliste
 SCHRIT st;		// Zeit wird vorerst ignoriert, evtl. globale Maximaldrehzahl?
 RLISTE* fin() const {return (RLISTE*)(st.fin());}
 static word calcsizes();	// berechnet rl und gslen, liefert gslen
 byte getLines() const {return (showSafe?st.getLines():0)+rl->getLines();}	// alles zusammen
 static bool move(byte*dst,byte*src);	// Speicherschwanz in eedata bewegen, Lücke ausnullen, false wenn voll
};
// Die Rezeptliste schließt sich an

extern union EEDATA {	// sollte genau 1024 Bytes groß sein!
 byte space[1024];	// Ganzer EEPROM
 struct{
  CONFIG c;		// Konfiguration der Benutzerschnittstelle (8 Bytes)
  GS gs;		// Globale Sicherheitskonfiguration (minimal 3 Bytes)
 };			// Dahinter Rezeptliste (minimal 1 Byte)
}eedata;
#define C eedata.c	// Abkürzung für Konfigurationszugriff
#define eesave (*(EEDATA*)0)	// EEPROM uninitialisiert, ab Adresse 0

extern struct RUN{
 byte rezept;	// aktives Rezept, 0-basiert
 byte aktion;	// aktiver Schritt im Rezept, 1-basiert (weil Index 0 = rezeptbasierte Sicherheit), 0 = inaktiv
 byte einzel;	// aktive Einzelaktion im Schritt (zum Logging), 0-basiert
 int time;	// Zeitzähler für Aktion
 byte status;	// gesetzte Bits für den Durchlauf aller Befehle eines Schritts
		//	7	≥ 1 Befehl liefert FALSCH und hat ALARM gesetzt
		//	3	≥ 1 Befehl liefert FALSCH und hat ABBRUCH als Aktion
		//	2	≥ 1 Befehl liefert FALSCH und hat NO START als Aktion
		//	1	≥ 1 Befehl liefert FALSCH und hat CONTINUE als Aktion
		//	0	≥ 1 Befehl hat CONTINUE als Aktion
		// 
 byte percentage;	// 0 = erster Durchlauf, 0xFF (rund 256) = letzter Durchlauf,
		// alle Zwischenwerte zum Rechtsschieben des Ergebnisses nach Multiplikation
 static void init();	// vor dem ersten tick() aufzurufen
 const REZEPT*re;	// aktives Rezept (nur während idle() gültig!)
 const SCHRIT*st;	// aktiver Schritt (nur während idle() gültig!)
 static void start(byte); // Prozess starten
 static void next(byte n=0);	// Schritt n oder nächsten Schritt (n==0) im Rezept ausführen oder Rezept beenden (n jenseits r->n.c)
 static void tick();	// Aufruf alle ~100 ms
 static void applyStatus();	// wertet run.status aus und löscht behandelte „Interrupt“-Bits
}run;

inline const REZEPT* curRezept()	{return *rl+run.rezept;}
inline const SCHRIT* curSchrit()	{return *run.re+run.aktion;}	// Überladener +-Operator!

struct ALARM{
 union{
  const void*p;
  const BEFEHL*be;	// Zeiger in Rezeptliste nur für print()
 };
 word time;	// Zeitstempel des _letzten_ Auftretens
 word occ;	// Anzahl des Auftretens (um die Liste nicht zu überfrachten)
 void print();
 void clear()			{occ=0;}
 static void clearAll();
 static ALARM*newest()		{return extreme(true);}
 static ALARM*oldest()		{return extreme(false);}
 static ALARM*extreme(bool);
 static ALARM*find(const void*id);		// sucht Alarm mit <id>
 static ALARM*generate(const void*id);
 static void move(const void*to,const void*from);	// beim Editieren der Rezeptliste: Verschiebungen mitverfolgen
 static byte n();			// Anzahl Alarme mit occ != 0
 static ALARM*nth(byte);		// indizierter Alarm mit occ!=0
 static ALARM alarm[10];		// hier: max. 10 Fehler (Alarme)
};


/***********
 * dlgUtil *
 ***********/

extern volatile union TICKER{
 byte b;	// maximal     2,55
 word w;	// maximal 10:55,35
 uint32_t l;	// maximal 500 Tage
}ticker; 	// Umlaufender 10-ms-Zähler
extern byte tic;	// Snapshot um eine bestimmte Zeit zu warten
inline void ticStart() {tic=ticker.b;}

void idle();
void wait(byte ms10);
void beepon(byte f);
void beep(byte f, byte ms10);
byte getkey(byte ms10=0);
void paintMenuItem(byte x, const char*s=NULL,wchar_t prefix=0);	// x=0, 40 usw.
void paintMenuZoom();	// "Zoom" für F6 ausgeben
void paintMenu(const char*s=NULL,byte mask=0x3F,byte change=0xBF);
void errorbeep();	// Für falsche, unwirksame Tasten
bool yesno();		// modal, wartet bis F1 (yes) oder F2 (no) gedrückt
bool ticCheck(byte ms10);
void onZoomClear();
const char*strend1(const char*s,byte i=1);
void centerTitle(const char*s,byte y=0);
byte transcode(byte b);
extern Edit ed;		// 2 statische "Widgets" für alle Fälle
extern ListView lv;
word lvPixelY(byte val);
word lvScreenY(byte line);
byte lvInit(byte pos,byte max);			// Liefert ggf. eingeschränktes <pos>
void lvXorFocus(byte line);	// für Rezept- und Alarmliste, unabhängig vom Clipping
byte updown(byte pos,byte action,byte max);	// 0xF1=down, sonst up, 0..max
void process();

/************************************
 * dlgDigital, dlgAnalog, dlgRezept *
 ************************************/
void dlgDigital();
void dlgAnalog();
const void*rsbNext(const void*p,byte type);
extern struct ITER{	// Tree-Iterator (für Rezept-Editor aber auch für den Prozess)
 union{			// Zeiger vom Chamäleon-Typ
  const void*p;
  const byte*b;
  const REZEPT*re;
  const SCHRIT*st;
  const BEFEHL*be;
  void*vp;
  byte*vb;
  REZEPT*vre;
  SCHRIT*vst;
  BEFEHL*vbe;
 };
 union{
  struct{
   byte ire,ist,ibe;	// Iteratoren (werden von whereami() und paint() benutzt)
  };
  byte i[3];		// via "type" erreichbarer Iterator
 };
 byte type;		// Pointer-Typ: 0=REZEPT*, 1=SCHRIT*, 2=BEFEHL*
 void whereami();	// Zeiger und Indizes aus Zeilennummer C.focusr
 byte line();		// Zeilennummer und Zeiger aus Indizes ire,ist,ibe,type
 const void*next() const	{return rsbNext(p,type);}	// nächsten Zeiger liefern
 N&getParentCounter()	{return getParentCounter(type);}	// zum Erniedrigen
 N&getParentCounter(byte);	// zum Erhöhen
 N&getCounter();	// zur Abfrage/Manipulation des Aufklappzustandes; Laufzeitfehler wenn iter.type>=2
 inline void init() {re=rl->re;}	// beim Start von der Rezeptliste
 inline void initgs() {st=&eedata.gs.st;}	// beim Start von globaler Sicherheit
 ITER(){};		// Leerer Standardkonstruktor bleibt
 ITER(const void*id);	// Zeiger-Such-Konstruktor für Alarm-Log
}giter;			// in main.cpp
void xorAktionMarker();
void dlgRezept();
void dlgAlarm();

/***********
 * USB-CDC *
 ***********/
void usbInit();
void usbPoll();
bool usbBackup();	// Binärdaten senden (EEROM-Backup)
bool usbRestore();	// Binärdaten empfangen (Timeout??)
void usbPurge();	// Dateneingangsqueue leeren
extern byte usbCfg;	// Bit 0: Configured
Detected encoding: UTF-80