Forum: Projekte & Code Bibliothek für VT100-Terminals


von Kai L. (kcl_93)


Angehängte Dateien:

Lesenswert?

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.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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

: Bearbeitet durch Moderator
von Kai L. (kcl_93)


Angehängte Dateien:

Lesenswert?

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ß! :)

von Kai L. (kcl_93)


Lesenswert?

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

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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?

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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
static uint8_t
2
myutoa10 (uint8_t i, char * buffer)
3
{
4
    uint8_t len;
5
6
    if (i >= 100)
7
    {
8
        uint8_t ii = 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
    else if (i >= 10)
17
    {
18
        uint8_t ii = 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
    return rtc;
30
}
31
32
void VT_CURSOR_UP_N(char* buffer, uint8_t n)
33
{
34
    uint8_t pos;
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
void VT_CURSOR_DOWN_N(char* buffer, uint8_t n)
47
{
48
    uint8_t pos;
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
void VT_CURSOR_SET(char* buffer, uint8_t x, uint8_t y)
60
{
61
    uint8_t pos;
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
int main ()
2
{
3
    char buffer[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.

: Bearbeitet durch Moderator
von Re: Bibliothek für VT100-Terminals (Gast)


Lesenswert?

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.

von Kai L. (kcl_93)


Lesenswert?

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.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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().

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.