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

#include "Convac.h"
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <stdlib.h>	// exit()

/**************
 * Buszugriff *
 **************/

byte BUS::ioRead(byte a) {
 PORTD = a;
 PORTE&=~0x40;	// Kartenauswahl aktivieren
 PORTF&=~0x02;	// !IOSTB aktivieren
 NOP2();	// Display: 140 ns
 if ((a&0xF8)==BA_DISPLAY) {	// Sonderfall Display
  _delay_us(1);
  PORTD|= 0x04;	// E auf High
  _delay_us(2);
 }
 if (a==BA_ANALOG+1) _delay_us(2.4);	// Sonderfall AD7828
 byte b= PINB;	// Byte einlesen
 if ((a&0xF8)==BA_DISPLAY) {
  PORTD&=~0x04;	// E auf Low (mitten im Zyklus)
  _delay_us(2);
 }
 PORTF|= 0x02;
 PORTE|= 0x40;
 return b;
}

void BUS::ioWrite(byte a, byte b) {
 PORTD = a;
 PORTF&=~0x01;	// !WR aktivieren (wird vom Display ignoriert)
 PORTB = b;	// Byte anlegen
 DDRB  = 0xFF;
 PORTE&=~0x40;	// Kartenauswahl aktivieren
 PORTF&=~0x02;	// !IOSTB aktivieren
 NOP2();
 if ((a&0xF8)==BA_DISPLAY) {
  _delay_us(1);
  PORTD|= 0x04;	// E auf High
  _delay_us(2);
  PORTD&=~0x04;	// E auf Low (mitten im Zyklus)
  _delay_us(2);
 }
 PORTF|= 0x02;
 PORTE|= 0x40;
 DDRB  = 0x00;
 PORTB = 0xFF;	// Pullups
 PORTF|= 0x01;
}

/****************************
 * EEPROM-gespiegelte Daten *
 ****************************/

EEDATA eedata;	// Genau 1024 Bytes groß!
		// (Der ATmega32U4 hat ungewöhnlich wenig EEPROM.)
RLISTE *rl;	// Dynamisch verwaltete Rezeptliste, hinter der Globalen Sicherheit
word gslen;	// Länge der dynamischen Daten in Bytes = Ergebnis von gs->size()

/*
 Die Rezeptspeicherverwaltung geht von dicht hintereinendergelegten Arrays aus.
 Statt Speicher zu fragmentieren wird Platz mittels memmove verwaltet.
 Der RAM ist größer als der EEPROM, daher wird die gesamte Liste im RAM gehalten
 und im Hintergrund mit dem EEPROM synchronisiert.
 Alle Arrays und Strukturen sind dynamisch. Bei knapp 1 KByte ist dann Schluss.
 */

/*****************
 * Hilfsroutinen *
 *****************/

void bootstart() {
 cli();
 PORTF|=0x80;		// Ausgabetreiber am Bus deaktivieren
 USBCON=0x20;		// USB aus
 UHWCON=0;		// USB-Spannungsregler aus
 PLLCSR=0;		// PLL aus
// WDTCSR=0x18;
// WDTCSR=0;		// Watchdog aus (sonst verreckt der Atmel-Urlader)
 SP=RAMEND;
 asm("jmp 0x7E00");	// Adresse des Urladers (ggf. ändern!)
}

// Asynchrones Update: Wartet nicht sondern stößt EEPROM-Schreibvorgang nur an
bool eeprom_update(const void*ram, void*eeprom, size_t len) {
 if (EECR&0x02) return true;
 const byte*src=(const byte*)ram;
 byte*dst=(byte*)eeprom;
 for (;len;src++,dst++,len--) {
  EEAR=(word)dst;
  EECR|=1;
  if (EEDR!=*src) {
   EEDR=*src;	// Adresse steht noch in EEAR
   cli();
   EECR|=0x04;
   sei();
   EECR|=0x02;	// immer noch mit gesperrten Interrupts (wird zu SBI compiliert)
   return true;
  }
 }
 return false;
}

volatile TICKER ticker;

ISR(TIMER1_CAPT_vect,ISR_NAKED) {
#if 1
 asm volatile(
"	push	r0		\n"
"	push	r16		\n"
"	 in	r0,%0		\n"
"	 lds	r16,ticker	\n"
"	 subi	r16,-1		\n"
"	 sts	ticker,r16	\n"
"	 brcs	1f		\n"
"	 lds	r16,ticker+1\n"
"	 sbci	r16,-1		\n"
"	 sts	ticker+1,r16\n"
"	 lds	r16,ticker+2\n"
"	 sbci	r16,-1		\n"
"	 sts	ticker+2,r16\n"
"	 lds	r16,ticker+3\n"
"	 sbci	r16,-1		\n"
"	 sts	ticker+3,r16\n"
"1:	 out	%0,r0		\n"
"	pop	r16		\n"
"	pop	r0		\n"
"	reti			\n"
 ::"I"(_SFR_IO_ADDR(SREG)):"memory");
#else
 ++ticker.l;	// compiliert zeitraubend mit vielen push/pop
#endif
}

// Die Idle-Prozedur wartet und ruft zwei Prozeduren zyklisch auf.
// Außerdem wird der EEPROM dem RAM nachgeführt.
void idle() {
 static byte proctic;
 static byte clocktic;
 if (10<=byte(ticker.b-proctic)) {
  proctic+=10;
  process();
  KEYS::peek();
 }else if (100<=byte(ticker.b-clocktic)) {
  clocktic+=100;
  CLOCK::show();	// Hier keine Rekursion zu befürchten
 }else{
  eeprom_update((byte*)&eedata,(byte*)&eesave,sizeof(CONFIG)+gslen);	// alles!
  usbPoll();
  PORTD|= 0x20;		// Schlafmodus (= !Last) anzeigen
  sleep_cpu();		// maximal 10 ms
  PORTD&=~0x20;		// (das Restprogramm beschreibt PORTD.5 mit Null)
 }
}

// Wartet (Mindest-)Zeit [beißt sich nicht mit tick()]
void wait(byte ms10) {
 byte tic=ticker.b;
 do idle(); while (ms10>byte(ticker.b-tic));
}

// Piepser an OC4D mit variabler Frequenz und Tastverhältnis etwa 50% ansteuern
void beepon(byte f) {
 OCR4C =f;	// immer im CTC-Modus
 OCR4D =f>>1;	// 50%
 TCCR4C=0x09;	// OC4D aktivieren
 TCCR4B=0x08;	// CK/128 => 488 Hz
}

static void beepoff() {
 TCCR4B=0;	// Timer anhalten (wirkungslos!?)
 TCCR4C=0x00;	// OC4D freigeben
}

void beep(byte f, byte ms10) {
 beepon(f);
 wait(ms10);
// OCR4C =f;		// sollte die Frequenz festlegen, ändert aber nichts
// wait(1);
 beepoff();
}

// Blockierende Version mit Tastenpiep, <ms10>=0 wartet unendlich.
// ruft beim Warten zyklisch idle() auf für Hintergrundaktivität
// Die Funktionstasten liefern (logischerweise) 0xF1..0xF6
byte getkey(byte ms10) {
 byte c;
 byte tic=ticker.b;
 do{
  idle();	// mindestens 1× (sonst kommt bei vielen Tastendrücken der Watchdog)
  if (ms10 && ms10<=byte(ticker.b-tic)) return 0;
 }while (!(c=KEYS::readchar()));
 beep(0xFF,5);
 return c;
}

/**********************************************
 * Menüsystem (= Funktionstastenbeschriftung) *
 **********************************************/

// Malt einen Menüpunkt (Funktionstastenbeschriftung)
// <prefix> ist für Checkboxen und Radiobuttons gedacht.
void paintMenuItem(byte x, const char*s,wchar_t prefix) {
 prepareField(x+1,53,x+39,63);
 prepareField(x+1,53,x+39,63);
 if (s) {
  if (prefix) {
   d.X=x+2;			// linksbündig
   d.print(prefix);		// Schaltzeichen
   d.X+=2;			// Freiraum
  }else d.X=x+20-(d.getTextExtent(s)>>1);	// Text zentrieren
  d.print(s);			// Text ausgeben
 }
}

void paintMenuZoom() {
 paintMenuItem(5*40,F("Zoom"),24);	// (logischerweise stets leere) Checkmark
}

// <mask> erlaubt das gezielte Ausblenden von Tastenfunktionen,
// wobei das entsprechende Feld weiß ausgemalt (gelöscht) wird.
// <change> erlaubt das Vermeiden der (langsamen) Textausgabe,
// wenn sich nur wenig ändert.
// Die Bits darin gehen von links nach rechts (Bit 0 = Taste F1 usw.)
// Change Bit 7 lässt die Trennlinien malen.
void paintMenu(const char*s,byte mask,byte change) {
 if (!change) return;
 idle();
 d.flags=d.COLOR|d.R2_COPYPEN;	// kein COOKED
 if (change&0x80) {
  d.clrClip();
  d.drawLine(0,52,239,52);	// waagerecht oben
 }
 byte chg=change;		// Bit 7 retten für senkrechte Linien
 for (byte x=0;;) {
  if (change&1) paintMenuItem(x,mask&1?s:NULL);
  x+=40;
  d.clrClip();
  if (x==240) break;
  if (chg&0x80) d.drawLine(x,53,x,63);	// Trennlinie senkrecht
  change>>=1;
  if (mask>>=1) s=strend1(s);	// nächster Teilstring
 }
 idle();
}

void errorbeep() {
 beep(128,20);
}

// Modaler Unterdialog, pfeift auf zoom.
// Bei aktivem "zoom" muss eben alles nachher neu gemalt werden.
bool yesno() {
 paintMenu(F("Ja\0Nein"),3);
 switch (getkey()) {
  case 0xF1: return true;
  case 0xF2:
  case '\r': return false;
 }
 errorbeep();		// Alle anderen Tasten (auch Ziffern) anmeckern
 return false;		// sowie auch raus
}

byte tic;	// Snapshot um eine bestimmte Zeit zu warten
bool ticCheck(byte ms10) {	// liefert true mit einer festen Frequenz
 if (ms10>byte(ticker.b-tic)) return false;
 tic+=ms10;
 return true; 
}

/*
// Wartet Zeit vom letzten Aufruf von tick(), d.h. generiert eine Frequenz
// ¦ Ein Argument von 50 generiert 2 Hz.
static void ticWait(byte ms10) {
 do idle(); while (!ticCheck(ms10));
}
*/

void xorFocusRect(byte l, byte t, byte r, byte b) {
 d.flags=d.COLOR|d.R2_XORPEN;
 d.drawRect(l,t,r,b);
}

// Startet Zähler und setzt das Sichtbarkeitsbit
void startFocusCounter() {
 ticStart();
 setFocusVis();
}

/*===================*
 * Uhr und Animation *
 *===================*/

// Dezimalzahl zweistellig mit führender Null ausgeben, d = 99
static void outDD(word z) {
 udiv_t q=udiv(z,10);
 d.print(q.quot+'0');
 d.print(q.rem+'0');
}

// eine Zeit in "hh:mm:ss" ausgeben, maximal 18:12:15
// Benötigt 34×11 Pixel
static void outTime(word s) {
 udiv_t q=udiv(s,60);
 udiv_t r=udiv(q.quot,60);
 outDD(r.quot);
 d.print(':');
 outDD(r.rem);
 d.print(':');
 outDD(q.rem);
}

static DISP dispsave;	// ein paar Byte zur Kontextsicherung

namespace CLOCK{
void paint() {
 prepareField(188,0,223,10);	// 36×11 Pixel
 d.X=189;			// etwas Platz links
 outTime(ticker.l/100U);	// Sekunden Uptime (endet bei 18:12:15)
}

// Uhr aus Idle-Kontext heraus aktualisieren, Aufrufr alle 1 s
void show() {
 if (zoom) return;		// keine Uhr
 dispsave=d;
 paint();
 d=dispsave;
}
}/*namespace CLOCK*/

namespace ANI{
enum{
 W=12,	// „zählende“ Pixel: Breite-3
 H=8,	// Höhe-3
 ANILEN=W+H+W+H,
};
static byte state;	// Animationsstatus 0..39

// Animationsrechteck 14×11 Pixel mit angefasten Ecken (RoundRect),
static void setPixel(byte nr) {
 while (nr>=ANILEN) nr-=ANILEN;
 byte x,y;
 if (nr<W) {		// oben
  x=nr+1; y=0;
  if (!nr) y++;		// Ecke links oben
 }else if ((nr-=W)<H) {	// rechts
  x=W+2; y=nr+1;
  if (!nr) x--;		// Ecke rechts oben
 }else if ((nr-=H)<W) {	// unten
  x=W+1-nr; y=H+2;	// linksherum
  if (!nr) y--;		// Ecke rechts unten
 }else{ nr-=W;		// links
  x=0; y=H+1-nr;	// nach oben
  if (!nr) x++;		// Ecke links unten
 }
 d.drawPixel(x+225,y);
}

// Animierten Prozessfortschritt anzeigen: Umrandung der Aktionsnummer animieren
// (mit geringstmöglichem Prozessoraufwand)
void animate() {
 byte i=state;
 if (!zoom) {
  dispsave=d;
  d.clrClip();
  d.flags=d.COLOR|d.R2_XORPEN;
  setPixel(i);		// Pixel löschen am Schwanz
  setPixel(i+ANILEN/2);	// Pixel setzen am Kopfende zur Hälfte voraus
  d=dispsave;
 }
 if (++i>=ANILEN) i=0; state=i;
}

void paint() {
 prepareField(224,0,239,10);	// löscht auch die „Schlange“
 byte n=run.aktion;
 if (n) {
  d.X=228;			// 2 Pixel Luft links und rechts (Optik)
  outDD(n);			// Zahl ausgeben
  for (byte i=0; i<ANILEN/2; i++) setPixel(state+i);	// alle Pixel setzen
 }
}

void show() {
 if (!run.aktion) state=0;
 if (zoom) return;
 dispsave=d;
 paint();
 d=dispsave;
}
}/*namespace ANI*/

void onZoomClear() {
 CLOCK::paint();
 ANI::paint();
}

// Text oben zentriert ausgeben
void centerTitle(const char*s,byte y) {
 prepareField(52,y,187,y+10);  // 52 Pixel Platz oben rechts für die Uhr lassen!
 if (s) {
  byte x=d.getTextExtent(s);
  d.X=x>136?52:120-(x>>1);	// zu langen Text linksbündig ausgeben und rechts clippen lassen
  d.print(s);
 }
}
 

ListView lv;
Edit ed;

// Vertikale Pixelposition eines Items in der Liste bestimmen
// (Sichtbarkeit ist ein anderes Thema!)
word lvPixelY(byte val) {
 return val*lv.dih;	// lv.H muss zuerst gesetzt werden!
}

word lvScreenY(byte line) {
 return lvPixelY(line)-lv.pos+lv.T;	// oben
}

// Funktionstasten umrechnen, <b> ab 0, nicht ab 0xF1
byte transcode(byte b) {
 static const PROGMEM byte tbl[6]={ed.CUL,ed.CUR,'.','-',',',ed.BS};
 return pgm_read_byte(tbl+b);
}

byte lvInit(byte pos,byte max) {
 if (pos>max) pos=max;
 lv.dih=d.getFontHeight(); 
 lv.init(0,zoom?0:11,239,zoom?63:50);
 lv.pos+=lv.posIntoView(pos);
 d.clrClip();		// damit die Scrollbar gemalt wird
 lv.setLen(lvPixelY(++max));
 clrFocusVis();
 return pos;
}

// generiert Blinken und führt das Fokus-Sichtbarkeitsbit mit
// (Nur für ganze Zeilen, hier Rezeptliste und Alarmliste)
void lvXorFocus(byte line) {
 d.saveClrClip();
 lv.xorFocusRect(line);
 xorFocusVis();
 d.restoreClip();
}

byte updown(byte focus,byte action,byte max) {
 focus++; if (action==0xF1) focus-=2;
 if (focus>max) focus=action==0xF1?max:0;
 return focus;
}

/******************
 * Programmablauf *
 ******************/

// Aufruf alle 100 ms im Idle-Kontext
void process() {
 if (!inIdle) {	// Nicht rekursiv aufrufen bloß weil's piept
  enterIdle();
  wdt_reset();	// Überwachung des regelmäßigen Aufrufs (sonst Bluescreen)
  run.tick();	// Überwacht auch externe Schalter und Tasten
  leaveIdle();
 }
}

/*=====================*
 * Maschinensicherheit *
 *=====================*/

ISR(WDT_vect,ISR_NAKED) {
 exit(-2);
}

ISR(BADISR_vect,ISR_NAKED) {
 exit(-1);
}

// der „Bluescreen Of Death“ (BSOD) ist hier:
extern "C" void exit(int) __attribute__((noreturn));
extern "C" void exit(int code) {
 PORTF|=0x80;	// Alle Ausgabetreiber auf dem Bus hochohmig
 beepon(100);
 cli();		// nötig für normalen exit()-Aufruf
 d.init();
 d.clear();
 d.print(F("Absturz! Softwarefehler!"));
 // 1 = unerwarteter Interrupt, 2 = Watchdog-Interrupt, 3 = EEPROM-Programmdaten korrupt, nun gelöscht
 d.print(floatstr(code),0,20);
 d.print(F("Neustart: Maschine aus- und einschalten"),0,40);
 bootstart();
 //for(;;);	// keine Rückkehr!
}
Detected encoding: UTF-80