#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-8 | 0
|