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

#include "Convac.h"
#include <stdlib.h>

/*===============*
 * Analog-Editor *
 *===============*/

// Universeller Stringpuffer, nicht auf dem Stack weil umständlich für AVR
char sbuf[32];

static char hexchar(byte n) {
 if (n>=10) n+=7;
 n+='0';
 return n;
}

// fest nach sbuf
char* utox(word v, char w) {
 if (w<2) w=2;
 char*p=sbuf;
 do{
  *p++=hexchar(v&0x0F);
  v>>=4;
 }while (--w/*>0||v*/);
 *p=0;
 return strrev(sbuf);
}

static word xtou(const char*p) {
 word r=0;
 if (*p) for (;;) {
  byte b=*p++;
  if (!b) {		// Stringende: prüfen!
   return r;
  }else if ((b-='0')<10) goto add;
  else if ((b-=17)<6) {
   b+=10;
add:			// if (r>0xFFF) aber für gcc optimiert
   if (byte(r>>8)>=byte(0x10)) break;	// Überlauf
   r=(r<<4)+b;
  }else break;
 }
 return 0xFFFF;	// dann einen zu großen Wert als NAN liefern
}

static int lastVal[21];

// Horizontale Listenaufteilung (Ziffernbreite = 5)
//  0..11	Kanalnummer (2 Ziffern + 2 Pixel)
// 12..39	Hexzahl (4 Buchstaben/Ziffern + 4 Pixel)
// 40..68	Dezzahl (5 Ziffern + 4 Pixel)
// 69..104	Phys. Wert (5 Ziffern, 1 Vorzeichen, 1 Dezimalkomma)
// 106..133	Einheit (hier nicht editierbar)
// 136..229	Kanalname

const PROGMEM byte inputx[]={12,40,69,105};	// Spaltenpositionen

// Festkommazahl ausgeben (spart Gleitkommaarithmetik), stets nach sbuf
// Bei nk=0 wird einfach eine Dezimalzahl ohne Komma ausgegeben (wie itoa)
// nk<0 (zur Ausgabe zusätzlicher Nullen) ist nicht erlaubt.
// Kein automatisches Abschneiden von Nachkomma-Nullen.
char* floatstr(int m, char nk) {
 if (word(m-32767)<3) {
  if (m&1) {
   sbuf[0]=m>0?'+':'-';			// 0x7FFF =  32767 = +∞
   return strcpy_P(sbuf+1,PSTR("∞"))-1;	// 0x8001 = -32767 = -∞
  }
  return strcpy_P(sbuf,PSTR("NaN"));	// 0x8000 = -32768 = NaN
 }
 udiv_t q;
 q.quot=abs(m);
 char*p=sbuf;
 do{
  q=udiv(q.quot,10);
  *p++=q.rem+'0';
  if (!--nk) *p++=',';		// gleich Komma ausgeben
 }while(q.quot||nk>=0);
 if (m<0) *p++='-';
 *p=0;
 return strrev(sbuf);
}

static word akku10(word w, byte b) {
 if (w>3276) goto inf;
 w=w*10+b;
 if (w>32767) inf: w=32767;	// +INF
 return w;
}

// Festkommazahl einlesen, liefert 0x8000 (NAN) bei Fehler, ±INF bei Überlauf
// Komma nicht erforderlich. Liefert Fehler auch bei leerem String.
// Fehlende Kommastellen werden durch Nullen ergänzt, zu viele ignoriert.
// nk<0 (zum Ignorieren von Vorkomma-Nullen) ist nicht erlaubt.
int strfloat(const char*p, char nk) {
 word r=0;
 byte flags=1;			// suche Vorkommaziffern
 if (*p=='-') {flags|=8; p++;}	// Vorzeichen gefunden
 for(;;){
  char c=*p++;
  if (!c) {			// Stringende? Flags prüfen!
   if (flags&3) break;		// Bit 0: Keine Vorkommaziffern
				// Bit 1: Komma aber keine Nachkommaziffern
   while (--nk>=0) r=akku10(r,0);	// Nachkommastellen „anhängen“
   return flags&8?-r:r;
  }else if (c==',') {
   if (flags&4) break;		// Bit 2: Zweites Komma
   flags|=6;			// ab jetzt Kommastellen mitzählen
  }else if ('0'<=c && c<='9') {
   if (flags&4) {
    flags&=~2;			// Nachkommaziffer gefunden
    if (--nk>=0) goto add;	// Zählende Nachkommastellen dazu
   }else{			// aber überschüssige ignorieren (hier nicht runden)
    flags&=~1;			// Vorkommaziffer gefunden
add:r=akku10(r,c-'0');
   }
  }else break;			// Alle anderen Zeichen: Fehler
 }
 return -32768;
}

// Rundet nach Division durch 256 vorzeichenrichtig (v ist faktisch 24 Bit)
//   10 (0x00000A) ?  0 (0x0000)
//  200 (0x0000C8) ?  1 (0x0001)
//  -10 (0xFFFFF6) ?  0 (0x0000) — Achtung! Vorzeichen wechselt!
// -200 (0xFFFF38) ? -1 (0xFFFF)
int rounding_shr8(long v) {
 char lsb=v;
 int r=v>>8;
 lsb^=r>>8;
 if (lsb<0) r++;
 return r;
}

int rounding_div(long a,int b) {
#if 0
 char vz=a>>24^b>>8; 	// gcc generiert viel längeren Kode
#else
 char vz; asm volatile(
	"	mov	%0,%D1	\n"
	"	eor	%0,%B2	\n"
 :"=r"(vz):"r"(a),"r"(b));
#endif
 a=labs(a); b=abs(b);
 a+=(unsigned)b>>1;	// die Hälfte vom vzl. Divisor dazu
 int q=(unsigned long)a/(unsigned)b;	// vzl. Division ist kürzer
 if (vz<0) q=-q;	// Ergebnisvorzeichen erstellen
 return q;		// (In der derzeitigen Programmlogik immer positiv)
}

static void clearMenu() {	// die Trennzeile wird nicht gelöscht!
 d.flags=d.R2_ZERO;
 d.fillRect(0,53,239,63);	// vorherige Menüeinträge löschen
}

// Zeichenzahl für Zeile <j> (Kanal) und Spalte <k> (Darstellung) ermitteln
// Benötigt <sbuf>
static byte MaxChar(byte j, byte k) {
 switch (k) {
  case 0: return abs(ANALOG::getBits(j))+3>>2;
  case 1:
  byte m=strlen(floatstr(ANALOG::getMin(j)));
  byte p=strlen(floatstr(ANALOG::getMax(j)));
  return p>m?p:m;
 }
 return 7;	// -32,768 — muss reichen
}

void printSbufRight(byte x) {
 d.X=x-d.getTextExtent(sbuf);
 d.print(sbuf);
}

// Zahl in String umwandeln, für Zeile <j> und Spalte <k>
// Rechtsbündig ausgeben wenn <x> angegeben
static void ValToStr(byte j, byte k, int v, byte x=0) {
 switch (k) {
  case 0: utox(v,MaxChar(j,0)); break;
  case 1: floatstr(v); break;
  default:
  const ANALOG::scale_t*k=ANALOG::scale+j;
  v=rounding_shr8((long)v*k->gain)+k->offset;
  floatstr(v,k->nk);
 }
 if (x) printSbufRight(x);
}

// Fokusrechteck für Analogwert setzen / wegnehmen
static void xorFocus() {
 d.saveClrClip();
 byte l=pgm_read_byte(inputx+C.anapos);	// links
 byte r=pgm_read_byte(inputx+C.anapos+1)-1;	// rechts
 lv.xorFocusRect(C.focusa,0,l,r);
 d.restoreClip();
}

static void prepareMiddleA() {
 prepareField(52,0,187,10);
}

static void showEditHelp() {
 prepareMiddleA();
 d.print(ANALOG::getDir(C.focusa)?F("Vorgabe"):F("Addierwert"));
 d.print(F(" eingeben"));
}

// Zahlen und Funktionstasten umrechnen
byte atranscode(byte b) {
 if (!C.anapos) return b+='A';
 b=transcode(b);
 if (C.anapos==1 && (b=='-' || b==',')) b='\a';
 return b;
}

// Zeigt für focusa ob's ein Eingang (A/D-Wandler) oder Ausgang (D/A-Wandler) ist
static void showInOut() {
 prepareMiddleA();
 d.print(floatstr(C.focusa));
 d.print(F(": "));
 d.print(ANALOG::getDir(C.focusa)?F("Aus"):F("Ein"));
 d.print(F("gang, max = "));
 d.print(floatstr(ANALOG::getMax(C.focusa)));
}

// Modaler Zeileneditor
// (Während der Eingabe aktualisieren sich die anderen Werte der Liste nicht)
static void EditAnalog(byte action) {
 ed.L=pgm_read_byte(inputx+C.anapos);	// links
 ed.R=pgm_read_byte(inputx+C.anapos+1)-1;// rechts
 ed.T=lvScreenY(C.focusa);		// oben
 ed.B=ed.T+d.getFontHeight()-1;	// unten
 ed.cursor=0xFF;
 ed.maxlen=MaxChar(C.focusa,C.anapos);
 ValToStr(C.focusa,C.anapos,lastVal[C.focusa]);
 ed.setText(sbuf);	// markiert alles
 if (!zoom) {
  const char*fs;
  byte mask=0x3F;
  switch (C.anapos) {
   case 0: fs=F("A\0B\0C\0D\0E\0F"); break;
   case 1: if (ANALOG::getMin(C.focusa)>=0) mask&=~0x08; // kein "Minus"
   default: {
    if (C.anapos==1 || !ANALOG::scale[C.focusa].nk) mask&=~0x10; // kein "Komma"
    fs=F("\x1F\0\x1E\0\0Minus\0Komma\0\x7F");
   }
  }
//  d.clrClip();
  paintMenu(fs,mask);
  showEditHelp();  
 }
 if (!action) ed.update();
 for(;;action=0) {
  if (!action) action=getkey(50);	// mit 2 Hz Nullen generieren
  switch (action) {
   case '\n': {	// SET: Wert übernehmen
    int v;
    switch (C.anapos) {
     case 0: v=xtou(ed.text); break;	// hier: -1 = Fehler
     case 1: v=strfloat(ed.text); break;
     default:
     const ANALOG::scale_t*k=ANALOG::scale+C.focusa;
     v=strfloat(ed.text,k->nk);
     if (word(v-32767)<3) goto error;	// bei +INF, NAN oder -INF
     v=rounding_div(long(v-k->offset)<<8,k->gain);
    }
    if (!ANALOG::getDir(C.focusa)) v-=lastVal[C.focusa];	// hm...
    if (v<ANALOG::getMin(C.focusa)
     || v>ANALOG::getMax(C.focusa)) error: beep(100,20);
    else ANALOG::setIo(C.focusa,v);
   }return;
   case '\r': return;
   default: {
    if (action&0x80) action=atranscode(action-0xF1);
    if (action>=' ') ed.removeLeadingZero(); // Sinnlose führende Null entfernen
    if (!ed.handleInput(action)) beep(80,20); continue;
   }
  }
 }
}

void dlgAnalog() {
vorn:
 d.clear();
 if (!zoom) {
  d.print(F("Analog"));
  onZoomClear();
 }
 byte maxch=ANALOG::getMax();
 C.focusa=lvInit(C.focusa,maxch);	// setzt Clipping
afteredit:
 startFocusCounter();
 setFocusVis();
 setRedraw();
 clrReLine();
 if (!zoom) {
  d.saveClrClip();
  paintMenu(F("\x1C\0\x1D\0\x1F\0\x1E\0Alles 0"),0x1F);
  paintMenuZoom();
  showInOut();
  d.restoreClip();
 }
// Diese Schleife hat 2 Modi:
// repaint==true: Neuzeichnen ungültiger Bereiche (also alles)
// repaint==false: Wertänderungen ermitteln und neuzeichnen (nur Zahlen)
 for(;;) {
  if (redraw) {			// Cursor immer weg
   d.flags=d.R2_ZERO;
   d.fillClip();		// invaliden Bereich löschen
  }else{
   lv.setFullClip();		// macht alle "sichtbaren" Items "invalid"
   if (ticCheck(50)) {
    xorFocus(); 		// mit 2 Hz blinken realisieren
    xorFocusVis();
   }
  }
  const char*fs=FP(Na);
  for (byte j=0; j<21; j++) {	// Datenzeilen
   int v=ANALOG::getIo(j);	// Wert lesen
   if (!reLine && lastVal[j]!=v) {
    setReLine();		// zweites Bit = tatsächliche Wertänderung
    lastVal[j]=v;
   }
   word y=lvPixelY(j);
   if ((redraw||reLine) && lv.itemInvalid(j)) {
   	// im sichtbaren Bereich, wenigstens teilweise
    if (!redraw&&reLine) {	// ausschließlich Wertänderung?
     d.flags=d.R2_ZERO;
     lv.fillItem(j,0,12,104);	// löschen (nur die Zahlen)
    }
    d.flags=d.COLOR|d.R2_COPYPEN;
    d.Y=y-lv.pos+lv.T;		// Position auf Bildschirm
    if (redraw) {		// Erstdurchlauf oder Scrollverschiebung
     d.X=0;
     d.print(floatstr(j));
    }
// Die drei Zahlen stets aktualisieren
    ValToStr(j,0,v,39);
    ValToStr(j,1,v,69);
    ValToStr(j,2,v,104);
    if (redraw) {		// Erstdurchlauf oder Scrollverschiebung
     d.X=107;
     d.print(ANALOG::scale[j].unit);
     d.X=140;
     d.print(fs);
    }
    if (j==C.focusa && focusVis) xorFocus();
   }
   clrReLine();
   fs=strend1(fs);
   idle();			// zeilenweise idlen
  }
  clrRedraw();
  switch (byte action=getkey(5)) {	// Tasten auswerten
   case 0: continue;
   case '\r': return;
   case 0xF1:
   case 0xF2: {			// vertikal
    if (focusVis) xorFocus();
    C.focusa=updown(C.focusa,action,maxch);
    if (!zoom) showInOut();
    d.clrClip();
    clrRedraw();
    lv.scrollIntoView(C.focusa);
    startFocusCounter();
    if (!redraw) xorFocus();	// sonst erst nach dem Neuzeichnen
   }continue;
   case 0xF3:
   case 0xF4: {			// horizontal
    if (focusVis) xorFocus();
    C.anapos=updown(C.anapos,action-2,2);
    startFocusCounter();
    if (!redraw) xorFocus();	// hier: sofort
   }continue;
   case 0xF5: {
    memset(&ANALOG::ios,0,sizeof ANALOG::ios);
    ANALOG::initOutputs();
   }continue;
   case 0xF6: {
    xorZoom();
   }goto vorn;
   case '\n': action=0; //nobreak;	//SET-Taste
   default: {	// Zifferntasten
    EditAnalog(action);
    d.clrClip();
    if (!zoom) clearMenu();
    lv.andItemClip(C.focusa);
   }goto afteredit;
  }
 }
}

Detected encoding: UTF-80