Hallo Leute
ich habe mir eine keine Bibliothek zusammen geschustert, mit der ich
VT100-Terminals ansteuern kann. Die Bibliothek liefert im Grunde nur
Strings, die die Steuersignale enthalten. Dadurch sollte die Bibliothek
ohne Probleme unter AVRs, ARMs und PICs gleichermaßen laufen (eventuell
andere Namen der eingebundenen Standarbibliotheken).
Auf welche Art man die Strings an das Terminal-Programm sendet bleibt
einem selbst überlassen, dadurch soll die Bibliothek möglichst flexibel
sein.
Ich habe selbst eine Weile nach etwas entsprechendem gesucht aber leider
nichts in die Richtung gefunden (vll bin ich auch nur zu doof für
Google). Daher stelle ich sie auch hier zur Verfügung, damit sie jeder
nutzen kann, wie er möchte. Aktuell verwende ich sie um Messdaten per
UART an den PC zu senden und dort darzustellen. Als Terminal-Programm
kann ich jedem Tera Term nur wärmstens empfehlen.
Aktuell gibt es bei der Bibliothek noch zwei Baustellen: Zum einen wird
an ein paar Stellen sprintf von mir verwendet. Diese Funktion frisst
viel Speicher und ist langsam und soll deswegen ausgetauscht werden. Bis
jetzt hatte ich auf meinem kleinen ATmega2560 zwar noch keine
Speicherprobleme aber bei kleineren AVRs ist das ein großes Minus. Des
weiteren sind keine Befehle implementiert, die eine Rückgabe des
Terminal-Programms zur Folge haben. Da weiß ich noch nicht ob und wie
ich das lösen werde, weil ich dann ja wiederum die Rückgabe des
Terminal-Programms verarbeiten muss, was ohne Zugriff auf die Hardware
schwierig ist. Eventuell habt ihr da eine Idee wie ich die beiden
Probleme lösen könnte.
Ansonsten viel Spaß damit!! :D
Gruß
Kai L.
Kai L. schrieb:> ich habe mir eine keine Bibliothek zusammen geschustert, mit der ich> VT100-Terminals ansteuern kann.
Schau Dir mal MCURSES an. Das ist eine abgespeckte Version der
(n)curses-Bibliothek, welche speziell für die Benutzung auf
Mikrocontrollern angepasst wurde. Sie "versteckt" nicht nur alle
Escape-Sequenzen vor dem Programmierer, sondern liefert auch sämtliche
Funktionalitäten mit, um ein VT100 (VT200/VT320/VT400) nicht nur
anzusteuern, sondern versteht auch alle gängigen Funktions-/Cursor- und
Navigationstasten. Diese werden direkt in numerische Tasten-Codes
umgesetzt.
MCURSES kann Attribute (Farbe, etc) setzen, Scrolling-Regions
verwenden, Einzelzeichen- und String-Ausgabe und noch einiges mehr.
Ein kleines Tetris-Spiel (Menüführungen etc.) lässt sich damit in
Nullkommanix programmieren.
Damit ist Deine "Bibliothek" mit gerade 3 simplen Funktionen, (welche
auch noch das speicherhungrige sprintf() verwendet) komplett hinfällig.
MCURSES läuft auf Unix, Linux, AVRs (ATmega, ATTiny) und anderen µCs
wie STM32. Selbst auf Z80 (SDCC) ist die Bibliothek portiert. Die
Portierung auf andere Prozessoren beschränkt sich im wesentlichen auf
eine Implementierung von putchar() und getchar(). Der Rest ist portabler
Code.
Sorry,
Frank
Anbei ist eine neue Version, die sprintf durch utoa und ein paar weitere
Zeilen zur Formatierung ersetzt. Ohne sprintf sinkt der
Speicherverbrauch in meinem kleinen Testprogramm um satte 1130 Byte. Die
Geschwindigkeit sollte entsprechend auch gestiegen sein.
Auch war vorher fälschlicherweise die stdlib.h statt der stdio.h
eingebunden, was jetzt jedoch genau passend ist.
Viel Spaß! :)
Hallo Frank,
vielen Dank für den Tipp :) Bis jetzt war ich nur auf ncourses gestoßen,
die abgespeckte Version davon kannte ich noch nicht.
Meine Mini-Bibliothek kann da von der Funktionsvielfalt natürlich nicht
mithalten, wobei sie geschätzt gerade einmal 400 Bytes groß ist (das
ganze Programm samt Schleifen, etc. umfasst 558 Bytes) und zumindest für
mich alle benötigten rudimentären Funktionen bereitstellt.
Einen Nachteil der mcourses Bibliothekt sehe ich jedoch darin, dass sie
um alle Funktionen zu realisieren auch die Hardwareseitig den UART
kontrolliert, was ich nicht möchte. In einem Fall benutze ich nämlich
meine kleine Bibliothek um auf dem ATmega2560 Daten raus zu liefern über
den UART2. Die oben beschriebene Bibliothek kann aber maximal 2 UARTs
verwalten. Daher müsste ich sie, wenn ich sie verwenden sollte
umschreiben.
Mir war es wichtig eine strikte Trennung zwischen den Befehlen und der
Hardware-Ausgabe bereit zu stellen. Das physikalische Layer, auf dem die
Daten übertragen werden wird dadurch irrelevant und ich kann ohne großen
Aufwand einfach die Funktionen einer UART-Bibliothek verwenden um die
Befehle an den PC zu senden. An der Bibliothek ändern muss ich dabei
nichts.
Ich hoffe ich habe irgendwie verständlich gemacht was mein Hintergedanke
hierbei war ;) Die mcourses Bibliothek ist ganz klar von der
Funktionalität her größer und besser, allerdings verfolge ich mit meiner
Mini-Bibliothek einen anderen Ansatz.
Gruß
Kai
Kai L. schrieb:> Einen Nachteil der mcourses Bibliothekt sehe ich jedoch darin, dass sie> um alle Funktionen zu realisieren auch die Hardwareseitig den UART> kontrolliert, was ich nicht möchte.
Das stimmt so nicht. Durch Anpassung von nur 2 Funktionen
void mcurses_phyio_putc (uint8_t ch)
und
uint8_t mcurses_phyio_getc (void)
auf das gewünschte Ausgabe-Device, kannst Du kommunizieren, worüber Du
willst. Das kann UART1, UART3, UARTX, SPI, I2C, ETHERNET oder sonstwas
sein.
> In einem Fall benutze ich nämlich> meine kleine Bibliothek um auf dem ATmega2560 Daten raus zu liefern über> den UART2. Die oben beschriebene Bibliothek kann aber maximal 2 UARTs> verwalten. Daher müsste ich sie, wenn ich sie verwenden sollte> umschreiben.
Das meinst Du nur auf den ersten Blick. MCURSES hat strikt die
logischen und die physikalischen Funktionen getrennt.
> Ich hoffe ich habe irgendwie verständlich gemacht was mein Hintergedanke> hierbei war ;) Die mcourses Bibliothek ist ganz klar von der> Funktionalität her größer und besser, allerdings verfolge ich mit meiner> Mini-Bibliothek einen anderen Ansatz.
Sie ist auf einem AVR gerade mal 1,9 KB groß - bei vielfachem
Funktionsumfang. Deine 3 Funktionen verschlingen schon 500 Byte.
Allein die Fallunterscheidung, ob x bzw. y eine 1-, 2- oder 3-stellige
Zahl ist, ist viel zu kompliziert.
statt 9 mal:
buffer[5] = 'H';
...
buffer[6] = 'H';
...
buffer[7] = 'H';
zu schreiben, macht man das mit einer einzigen Zuweisung am Schluss der
Funktion:
buffer[pos] = 'H';
Du machst für so etwas einfaches eine Fallunterscheidung von 3x3 = 9
Fällen und brauchst alleine, um den Cursor zu setzen, 68 Zeilen Code.
Hier die äquivalente Funktion in MCURSES:
static void
mymove (uint8_t y, uint8_t x)
{
mcurses_puts_P (SEQ_CSI);
mcurses_puti (y + 1);
mcurses_putc (';');
mcurses_puti (x + 1);
mcurses_putc ('H');
}
Die mcurses_puts/puti/putc-Funktionen sind alle vom Ausgabedevice
komplett unabhängig.
Sag mal: Wie lange programmierst Du schon?
Hier Deine 3 Funktionen in einer anderen Form, die nur 80 Bytes statt
322 auf einem ATmega88 an Code verbraten - bei gleicher Funktionalität:
1
staticuint8_t
2
myutoa10(uint8_ti,char*buffer)
3
{
4
uint8_tlen;
5
6
if(i>=100)
7
{
8
uint8_tii=i/100;
9
*buffer++=ii+'0';
10
i-=100*ii;
11
ii=i/10;
12
*buffer++=ii+'0';
13
i-=10*ii;
14
len=3;
15
}
16
elseif(i>=10)
17
{
18
uint8_tii=i/10;
19
*buffer++=ii+'0';
20
i-=10*ii;
21
len=2;
22
}
23
else
24
{
25
len=1;
26
}
27
28
*buffer++=i+'0';
29
returnrtc;
30
}
31
32
voidVT_CURSOR_UP_N(char*buffer,uint8_tn)
33
{
34
uint8_tpos;
35
36
//buffer benoetigt eine Mindestlaenge von 7 Zeichen
37
buffer[0]='\e';
38
buffer[1]='[';
39
pos=2;
40
pos+=myutoa10(n,&buffer[pos]);
41
buffer[pos++]='A';
42
buffer[pos]=0;
43
}
44
45
46
voidVT_CURSOR_DOWN_N(char*buffer,uint8_tn)
47
{
48
uint8_tpos;
49
50
//buffer benoetigt eine Mindestlaenge von 7 Zeichen
51
buffer[0]='\e';
52
buffer[1]='[';
53
pos=2;
54
pos+=myutoa10(n,&buffer[pos]);
55
buffer[pos++]='A';
56
buffer[pos]=0;
57
}
58
59
voidVT_CURSOR_SET(char*buffer,uint8_tx,uint8_ty)
60
{
61
uint8_tpos;
62
63
//buffer benoetigt eine Mindestlaenge von 11 Zeichen
64
buffer[0]='\e';
65
buffer[1]='[';
66
pos=2;
67
pos+=myutoa10(x,&buffer[pos]);
68
buffer[pos++]=';';
69
pos+=myutoa10(y,&buffer[pos]);
70
buffer[pos++]='H';
71
buffer[pos]=0;
72
}
Damit sinkt der erzeugte Code auf unter ein Viertel. Aber nicht nur
dieser ist um ein Vielfaches kürzer, sondern auch der C-Quelltext - und
dabei immer noch gut lesbar. Die Funktion utoa() wurde dabei durch
myutoa10() ersetzt. Diese ist nicht nur kürzer, sondern liefert auch
direkt die Länge zurück.
Code ungetestet, sehe aber auf Anhieb keinen Fehler.
Übersetzt wurde beide Versionen mit
1
intmain()
2
{
3
charbuffer[12];
4
5
VT_CURSOR_SET(buffer,10,10);
6
VT_CURSOR_UP_N(buffer,1);
7
VT_CURSOR_UP_N(buffer,1);
8
}
Alt:
Program: 322 bytes (3.9% Full)
Neu:
Program: 80 bytes (1.0% Full)
Das Konzept, erst einen Buffer zu füllen, um ihn dann in einer Schleife
auszugeben, halte ich für doppelt gemoppelt. Warum erst den Buffer
füllen? Direkt raus auf das Device! Dann braucht man auch keine
String-Längen zu berechnen, nur um zu verhindern, dass man sich den
Buffer kaputtschießt.
Frank M. schrieb:> Das meinst Du nur auf den ersten Blick. MCURSES hat strikt die> logischen und die physikalischen Funktionen getrennt.
Unter einer strikten Trennung verstehe ich, dass die verschiedenen Layer
auch in verschiedenen Bibliotheken untergebracht sind und ein
entsprechendes Header-File mit den passenden Wrappern ausreicht, um das
Programm über UART, I2C oder was auch immer kommunizieren zu lassen.
Frank M. schrieb:> Sie ist auf einem AVR gerade mal 1,9 KB groß - bei vielfachem> Funktionsumfang. Deine 3 Funktionen verschlingen schon 500 Byte.
An diesem Kommentar kann ich erkennen, dass du noch nicht verstanden
hast, was meine Intention bei dem Programm war: Ich möchte lediglich für
zum Beispiel Messungen in Tera Term eine Darstellung haben, bei der ich
meine Messwerte sehe schön und übersichtlich darstellen kann. Dafür sind
die wenigen zur Verfügung gestellten Befehle mehr als ausreichend. Und
es wird zum Beispiel auch keine Abarbeitung von empfangenen Zeichen
benötigt, die Ressourcen frisst.
Möchte ich aufwendigere Aufgaben erledigen und Tetris in meinem
Terminal-Programm spielen, greife ich natürlich auf die MCOURSES
Bibliothek zurück, weil mein Funktionsumfang dafür nicht ansatzweise
ausreicht aber das soll er ja auch nicht.
Alle 3 Funktionen zusammen belegen aktuell 366 Bytes. Die utoa-Funktion
macht davon 206 Bytes aus. In einer späteren Version kann ich die
eventuell gegen eine kleinere, effektivere Funktion austauschen. Da ist
noch Optimierungspotential vorhanden. Irgendwo habe ich mal gelesen,
dass es tolle und extrem schnelle Algorithmen zur Umwandlung von binär
zu BCD gibt aber da hatte ich bis jetzt keine Zeit mich rein zu lesen.
Frank M. schrieb:> Allein die Fallunterscheidung, ob x bzw. y eine 1-, 2- oder 3-stellige> Zahl ist, ist viel zu kompliziert.>> statt 9 mal:>> buffer[5] = 'H';> ...> buffer[6] = 'H';> ...> buffer[7] = 'H';>> zu schreiben, macht man das mit einer einzigen Zuweisung am Schluss der> Funktion:>> buffer[pos] = 'H';>> Du machst für so etwas einfaches eine Fallunterscheidung von 3x3 = 9> Fällen und brauchst alleine, um den Cursor zu setzen, 68 Zeilen Code.
Ich arbeite an dieser Stelle mit einer Fallunterscheidung, weil diese
für einen Mikrocontroller extrem simpel abzuarbeiten ist und es mir
erspart weitere temporäre Variablen anzulegen und mit diesen zu rechnen.
Das wäre für den Nutzer lesbarer aber für den Mikrocontroller mit mehr
Aufwand verbunden.
Frank M. schrieb:> Hier die äquivalente Funktion in MCURSES:>> static void> mymove (uint8_t y, uint8_t x)> {> mcurses_puts_P (SEQ_CSI);> mcurses_puti (y + 1);> mcurses_putc (';');> mcurses_puti (x + 1);> mcurses_putc ('H');> }
Auf Grund meines Ansatzes durch die Bibliothek nur Strings auszugeben,
die wiederum von Nutzer versendet werden können, ist es mir leider nicht
möglich eine Funktion so aufzubauen. Sonst hätte ich es auch so getan.
Bis auf die dadurch notwendigen Verzweigungen sind unsere beiden
Funktionen identisch.
Frank M. schrieb:> Sag mal: Wie lange programmierst Du schon?
Ich programmiere seit 3 Jahren Mikrocontroller in C und habe
demenstprechend noch viel zu lernen. Mir ist klar, das nicht alles was
ich produziere perfekt ist, allerdings kann davon ausgegangen werden,
dass ich mir bei den meisten Dingen auch etwas dabei gedacht habe wieso
ich es so und nicht anders löse.
Ich habe meine Bibliothek hier gepostet in der Hoffnung Leuten zu
helfen, die durch googlen auch nichts entsprechendes gefunden haben, und
um Feedback zu meiner Arbeit zu bekommen, was ich an welcher Stelle
hätte besser machen können.
Statt auf konstruktive Kritik treffe ich aber auf einen alten
Platzhirsch, der seine eigene Bibliothek als Maß der Dinge ansieht und
keine neuen Ideen und Ansätze zulässt. Ich habe mit meiner Arbeit keinen
Anspruch darauf auch nur ansatzweise so gut wie etablierte Lösungen zu
sein, die schon seit Jahren gepflegt werden. Sie ist aus einer
Notwendigkeit heraus und dem Antrieb etwas über die Steuerung von
Terminals zu lernen entstanden.
Wenn ich aber hier keine konstruktive Kritik bekomme um meinen eigenen
Code zu verbessern und etwas dabei zu lernen werde ich meine Ideen in
Zukunft in einer anderen Bibliothek zur Verfügung stellen.
Frank M. schrieb:> Hier Deine 3 Funktionen in einer anderen Form, die nur 80 Bytes statt> 322 auf einem ATmega88 an Code verbraten - bei gleicher Funktionalität:
Vielen Dank. So etwas verstehe ich schon eher unter konstruktiver
Kritik. :)
Bei deiner myutoa-Funktion verwendest du Divison um die einzelnen
Stellen umzuwandeln. Gibt es eventuell Möglichkeiten (gerne auch zu
Lasten der Lesbarkeit) um die Funktion ohne Division zu realisieren?
Soweit ich weiß mögen AVRs ja keine Divison (sehr viel langsamer im
Vergleich zu +, -, *).
Da myutoa auch im Gegensatz zu utoa die Anzahl der Stellen der
Dezimalzahl zurück gibt, brauche ich nicht durch if-Abfragen die Länge
die Positionen der restlichen Zeichen im Array festlegen.
Re: Bibliothek für VT100-Terminals schrieb:> Statt auf konstruktive Kritik treffe ich aber auf einen alten> Platzhirsch, der seine eigene Bibliothek als Maß der Dinge ansieht und> keine neuen Ideen und Ansätze zulässt.
Tut mir leid, dass Du das so siehst. Aber einen Source mit drei
Mini-Funktionen, welche ursprünglich einfach nur sprintf() aufriefen,
eine "Bibliothek" zu nennen, erschien mir doch ein wenig ... merkwürdig
- zumal die Header-Datei komplett weggelassen werden kann. Die drei
Funktionen lassen sich nämlich komplett ohne zu übersetzen. Kein
ernsthafter µC-Programmier verwendet für diesen schlichten Fall das
Monstrum sprintf() - schon gar nicht für die einfache Ausgabe einer
maximal dreistelligen Dezimalzahl.
Deine Version mit utoa() war zwar schon etwas sportlicher, allerdings
sieht Code, der 9-mal hingeschrieben wird - nur mit abweichenden
Konstanten - nicht gerade wie ein Programm aus. Jede Programmiersprache
kennt Variablen. Die sollte man auch nutzen ;-)
> Ich habe mit meiner Arbeit keinen> Anspruch darauf auch nur ansatzweise so gut wie etablierte Lösungen zu> sein, die schon seit Jahren gepflegt werden. Sie ist aus einer> Notwendigkeit heraus und dem Antrieb etwas über die Steuerung von> Terminals zu lernen entstanden.
Als ich den Source das erste Mal sah, fragte ich mich nach dem Nutzen:
"Was will ich mit einer 'Bibliothek', die gerade mal den Cursor
positionieren kann?"
Tut mir leid, ich kann mir da nur schwerlich eine ernsthafte Anwendung
vostellen. Es kann ja sein, dass ein schlichtes Positionieren in Deinem
konkreten Fall ja genügen mag. Aber normalerweise erhebt eine
Bibliothek den Anspruch einer gewissen allgemeineren Lösung, aus der
man schöpfen kann. Dafür klingt für mich Deine Bezeichnung "Bibliothek"
ziemlich hochgestochen.
> Wenn ich aber hier keine konstruktive Kritik bekomme um meinen eigenen> Code zu verbessern und etwas dabei zu lernen werde ich meine Ideen in> Zukunft in einer anderen Bibliothek zur Verfügung stellen.
Du musst lernen, mit Kritik umzugehen. Auch nicht-konstruktive Kritik
kann durchaus ein Ansporn sein, es besser zu machen. In diesem konkreten
Fall hielt ich es für den besseren Weg.
Kai L. schrieb:> Bei deiner myutoa-Funktion verwendest du Divison um die einzelnen> Stellen umzuwandeln. Gibt es eventuell Möglichkeiten (gerne auch zu> Lasten der Lesbarkeit) um die Funktion ohne Division zu realisieren?
Nicht, dass ich wüsste. Um die einzelnen Stellen einer Dezimalzahl zu
isolieren, musst Du teilen. utoa() arbeitet da nicht anders, teilt aber
abweichend dazu durch die angegebene Basis. Leider ist für einen µC das
Teilen durch 10 nicht so einfach wie das Teilen durch eine Zweierpotenz.
> Da myutoa auch im Gegensatz zu utoa die Anzahl der Stellen der> Dezimalzahl zurück gibt, brauche ich nicht durch if-Abfragen die Länge> die Positionen der restlichen Zeichen im Array festlegen.
Eben. Das war der Sinn der Sache. Ausserdem ist das "Ausrechnen" von
maximal 3 Stellen in einer speziellen Version tatsächlich kürzer als die
der allgemeinen Funktion utoa().