Forum: Compiler & IDEs RW-Streams mit stdio


von Michael A. (micha54)


Lesenswert?

Hallo,

ich möchte bei einem Mega168 den Usart und den TWI bidirektional über 
Streams wahlweise mit Keyboard(3x4) und LCD verbinden, die üer stdin und 
stdout betrieben werden. Über udata des Files wird ein fifo eingebunden.

Eine Routing-Funktion soll wahlweise die Streams durch zeicheweises 
Kopieren verbinden (USART->LCD oder TWI->LCD oder sogar USART->TWI und 
umgekehrt).

Variante 1)
je ein Stream für Receiver und Transmitter, Streams müsssen beide 
trotzdem RW sein, weil ich put und get beide benötige für den 
fifo-Zugriff.

Variante 2)
ein RW-Stream, wobei die Hardware direkt in das Fifo arbeitet, nur für 
das Routing der Streams benutze ich die fgetc/fputc-Funktionen.

Was ist hier die "politisch korrekte Vorgehensweise", wofür steht der 
RW-Zugriff der Streams, etwa nur für die Zugriffssteuerung auf put und 
get ?

Gruß,
Michael

von Karl H. (kbuchegg)


Lesenswert?

Ich würd den Aufwand gar nicht machen, die stdin/stdout so zu 
untermauern, dass du mit den normalen C-Stream-Funktionen arbeiten 
kannst. Lohnt normalerweise den Aufwand einfach nicht.

Für jedes Device eine get/put Funktion (je nach Devicetyp), dazu noch 
eine allgemeine FIFO und du bist im Rennen. Recht viel mehr als 
zeichenweise umkopieren wirst du in deiner Applikation ja nicht 
benötigen.

von Peter D. (peda)


Lesenswert?

Michael Appelt wrote:
> Was ist hier die "politisch korrekte Vorgehensweise"

Keine Streams zu benutzen.
Ich habs noch nie gemacht auf nem MC und sehe darin auch keinen Sinn.

Auch wenns irgendwie lustig klingt, alles scheinbar gleich zu behandeln, 
man verhuddelt sich da sehr schnell und Reccourcen verbraucht es auch 
viel mehr.

Ein LCD wird ja grundsätzlich völlig anders angesprochen als die UART.
Es möchte gerne eine Position haben, wo der Text stehen soll, es 
versteht keinen Zeilenvorschub, es kann sich Text nicht merken, d.h. er 
muß für eine Zeitdauer stehen bleiben, damit er gelesen werden kann usw.

Das Keyboard verhält sich auch völlig anders als die UART, Du kannst ja 
mit 3*4 keinen Text eingeben, die Tasten erhalten erst im konkreten 
Funktionskontext ihre Bedeutung.
Oftmals muß der Keyboardtreiber Die Druckdauer (kurz, lang) getrennt 
behandeln. Die geht aber verloren, wenn die Tasten in ner FIFO landen.
Ne Tasten-FIFO ist also in der Regel Unsinn.
Der User erwartet, daß Tasten zeitnah behandelt werden.

Und das I2C ist wieder ne andere Baustelle, Du hast spezielle Ereignisse 
(Adressierung, Start, Stop, Arbitrierung), die bei der UART fehlen.
Du wirst also auf dem I2C ein völlig anderes Protokoll fahren müssen, 
das geht Dir dann im Stream auch verloren.
Ich benutze das I2C paketorientiert, d.h. statt ner FIFO habe ich einen 
Paketpuffer, der 1 oder 2 Pakete zwischenspeichert (ähnlich CAN).


Nimm extra Funktionen für UART (RX, TX), I2C, Keyboard und das LCD.
Das ist die übliche Vorgehensweise.



Peter

von Michael A. (micha54)


Lesenswert?

Peter Dannegger wrote:
>
> Auch wenns irgendwie lustig klingt, alles scheinbar gleich zu behandeln,
> man verhuddelt sich da sehr schnell und Reccourcen verbraucht es auch
> viel mehr.
>
Hallo,

genau darum geht es mir. Natürlich muß man Einschränkungen hinnehmen, 
wenn man alle Schnittstellen aufs Zeichenorientierte Minimum beschränkt.

Ich wollte halt wegen des geringen Aufwands alle meine kleinen 
Spielreien per I2C auf dieses Miniterminal führen, wo ich dann von 
beliebigen Debugausgaben bis zu ganzen Setup-Menüs alles durchführen 
kann.

Ich nutze fürs LCD z.B. den seriellen Standard von Parallax, d.h. goto 
geht per 1 Byte. Die LCD-Ausganbe arbeitet auf ein Array als Puffer, und 
per Interrupt werden alle Änderungen zum LCD übertragen.
Eine Statusmaschine für Ansi-Escapesequenzen habe ich auch in Arbeit.

Ich verstehe einerseits, daß Entwickler immer Ihr Ziel und dessen 
sparsame Realisierung im Auge haben, andererseits sind ihnen, besonders 
wenn sie E-teschnisch vorbelastet sind, aber auch viele Grundprinzipien 
garnicht bekannt, so daß erstmal hoher Aufwand vermutet wird, der aber 
garnicht zutrifft.

In meinem Fall ging es auch um die Implementierung einer dezentralen 
Architektur, d.h die leicht erhöhte CPU-elastung und der längere Code 
fällt bei 18,432MHz und Anschluss über relativ langsame serielle 
Schnittstellen garnicht auf. 3€ MEhrkosten/CPU sind Peanuts bei 
Einzelstücken.

Und ab einem gewissen Punkt geht die Entwicklung von Applikationen 
schneller und fehlerfreier....

Ich habe übrigens festgestellt, daß ich 2 Streams für den Usart 
btrauche, weil das Put bereits direkt den Transmitterinterrupt freigeben 
muss falls das Fifo leer ist.

Gruß,
Michael

von Günter R. (galileo14)


Angehängte Dateien:

Lesenswert?

Peter Dannegger wrote:
> Ich habs noch nie gemacht auf nem MC und sehe darin auch keinen Sinn.

Das sehe ich ganz anders, ich sehe es als höchst sinnvoll an, Streams zu 
benutzen, und zwar genau dann, wenn man wiederverwendbare 
Ausgabe-Formatierungsfunktionen schreiben und auf beliebige 
Ausgabe-Devices anwenden möchte, speziell, wenn man diese 
Ausgabe-Funktionen in eigene Bibliotheken packen möchte (was oft 
sinnvoll ist); dann kann man für ein spezielles Device einfach eine 
zeichenorientierte Ausgabe-Funktion (z.B. für LCD-Display) oder 
Ein/Ausgabe-Funktion (z.B. für UART, I2C/TWI, SPI) schreiben und in 
einen Stream einbetten, und kann dann Funktionen damit verwenden, die 
unabhängig vom speziellem Device sind.

>Ein LCD wird ja grundsätzlich völlig anders angesprochen als die UART.
>Es möchte gerne eine Position haben, wo der Text stehen soll, es
>versteht keinen Zeilenvorschub, es kann sich Text nicht merken, d.h. er
>muß für eine Zeitdauer stehen bleiben, damit er gelesen werden kann usw.

Nicht unbedingt wird ein LCD völlig anders angesprochen als ein UART; 
klar muß man die Cursor-Positionierung mit speziellen LCD-Funktionen 
machen; aber für Ausgaben, die ja wie beim UART zeichenweise geschehen, 
kann man die gleichen Funktionen verwenden. Das praktiziere ich schon 
seit 15 jahren so.

Ich habe mal ein kleines Code-Fragment angehängt, in dem beschrieben 
ist, wie man vorgehen kann (bzw. wie ich es mache). Dort steht 
beispielhaft eine Funktion "DspCharStr", mit der man eine Kette gleicher 
ASCII-Zeichen bestimmter Länge ausgeben kann (und diese Funktion ist 
noch einfach; da sind komplexere Funktionen denkbar, z.B. 
Fehlertext-Ausgaben mit Fremdsprachen-Unterstützung o.ä., die man 
wahlweise am LCD und/oder auf einem ASCII-Terminal anzeigt, oder die 
Ausgabe eines formatierten Zeit/Datumstrings von der RTC, usw.).

von Simon K. (simon) Benutzerseite


Lesenswert?

Günter R. wrote:
> Peter Dannegger wrote:
>> Ich habs noch nie gemacht auf nem MC und sehe darin auch keinen Sinn.
>
> Das sehe ich ganz anders, ich sehe es als höchst sinnvoll an, Streams zu
> benutzen, und zwar genau dann, wenn man wiederverwendbare
> Ausgabe-Formatierungsfunktionen schreiben und auf beliebige
> Ausgabe-Devices anwenden möchte, speziell, wenn man diese
> Ausgabe-Funktionen in eigene Bibliotheken packen möchte (was oft
> sinnvoll ist); dann kann man für ein spezielles Device einfach eine
> zeichenorientierte Ausgabe-Funktion (z.B. für LCD-Display) oder
> Ein/Ausgabe-Funktion (z.B. für UART, I2C/TWI, SPI) schreiben und in
> einen Stream einbetten, und kann dann Funktionen damit verwenden, die
> unabhängig vom speziellem Device sind.

Die Ideologie ist gut, aber Streams braucht man da trotzdem nicht für. 
Ich mache es oft so, dass ein bestimmtes CodeModul (beispielsweise ein 
Dateisystem) als "Eingang" genau zwei definierte LowLevel Routinen 
braucht (zum Lesen und zum Schreiben). Statt jetzt einen Stream zu 
benutzen (was Overhead verursacht), lagere ich die Funktionsdefinitionen 
als #define in die Konfigurations-Header-Datei des Modules (hier: 
Dateisystem) einfach aus.
Dann hat man im Endeffekt einen Stream, der nur im C-Code vorhanden ist 
und nicht mehr im Maschinencode. Somit spart man sich sämtlichen 
Overhead.

von Günter R. (galileo14)


Lesenswert?

Da gebe ich Dir recht; diese Methode kenne ich auch, ich verwende sie 
auch, allerdings für andere Dinge (Auslagerung hardware-bezogener 
Funktionen, um eine Funktion hardware-unabhängig zu machen und in eine 
Bibliothek aufzunehmen, z.B. RS485-Enable/Disabling für eine 
Block-Puffer-I/O).

Die von Dir beschriebene Methode spart Ressourcen, das stimmt. Sie ist 
allerdings m.E. etwas "unschön", da sie für Zeichen-I/O zwei 
Funktionen-Zeiger benötigt. Die Stream-Methode kommt mit einem Zeiger 
(auf das "Dev0") aus, was beim Weiterreichen von verschachtelten 
Funktionen der Übersichtlichkeit sehr dienlich ist. Und wenn man einen 
großzügigen Prozessor hat (z.B. ATmega128), kommt es nicht auf das 
letzte Byte an.

Ich habe mir im übrigen auch einen eigenen File-Control-Block FILEX 
("eXtended") definiert, der drei Funktionen verwaltet, nämlich "serin", 
"serout" und "serstat"; mit "serstat" kann man abfragen, ob beim 
UART-Empfänger ein Zeichen vorliegt; wichtig für 
Multitasking-Anwendungen, bei denen man z.B. mit ESC etwas abbrechen 
möchte. Auch hier kann man mit nur einem Zeiger über beliebig viele 
Ebenen den Stream weiterreichen. Und solche Funktionen sind 
hardware-unabhängig, sie lassen sich leicht in Bibliotheken unterbringen 
(okay: Deine Methode auch). Klar, kostet wieder etwas Ressourcen.

Aber wie überall: der persönliche Geschmack entscheidet. Somit gibt es 
nicht "Besser" oder "Schlechter".

von Michael A. (micha54)


Lesenswert?

Hallo,

die Idee mit den defines ist hier nicht nutzbar weil ich per Menü 
dynamisch vom lokalen Betrieb auf RS232 und I2C umschalten will.

Dazu eignet sich ein select oder eben eine function-referenz. Mehr macht 
aber ein Stream aber auch nicht, ein pointer für put und einer für get.
Der Overhead wird meines Erachtens völlig überbewertet, vermutlich weil 
printf mit formatstrings gelegentlich sehr aufwändig arbeitet.

Gruß,
Michael

von Karl H. (kbuchegg)


Lesenswert?

Michael Appelt wrote:
> Dazu eignet sich ein select oder eben eine function-referenz. Mehr macht
> aber ein Stream aber auch nicht, ein pointer für put und einer für get.

Da wär ich mir nicht so sicher.
Ein Stream muss auch noch Buchführen ob er eof ist, ob er geöffnet 
wurde, in welchem Modus er geöffnet wurde, eventuelle Daten die er vom 
Betriebssystem hat (Filehandle od dgl.) Datenbuffer etc.

Wieviel davon auf dem WinAvr vorhanden ist, kann ich nicht sagen. 
Einfach mal in stdio.h reinschauen, da müsste die FILE-Struktur 
deklariert sein.

Auf einem PC (VC++) sieht sie zb so aus
1
struct _iobuf {
2
        char *_ptr;
3
        int   _cnt;
4
        char *_base;
5
        int   _flag;
6
        int   _file;
7
        int   _charbuf;
8
        int   _bufsiz;
9
        char *_tmpfname;
10
        };
11
typedef struct _iobuf FILE;

von Michael A. (micha54)


Lesenswert?

Karl heinz Buchegger wrote:
>
> Auf einem PC (VC++) sieht sie zb so aus
> [/C]

Hallo,

Ja, aber das läuft dann auf dem Mega168 wirklich nicht mehr, evtl habe 
ich das am Anfang nicht so klar erwähnt.
BTW ist VC++ kein sehr gutes Beispiel, ich schau da lieber in die Linux 
Kernelquellen ;-)

Also die Idee mit dem eigenen filedescriptor ist nicht blöd, 
andererseits liebe ich function refs, und kann mit der stdio des AVR 
bisher gut auskommen.

Ach so, I2C ist tatsächlich erstmal kein typisches Streamdevice, aber 
wenn es um die LCD-Ausgabe geht, dann führt die Definition von Paketen 
doch immer wieder auf einen Strom von N bytes.

Gruß,
Michael

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl heinz Buchegger wrote:

> Ein Stream muss auch noch Buchführen ob er eof ist, ob er geöffnet
> wurde, in welchem Modus er geöffnet wurde, eventuelle Daten die er vom
> Betriebssystem hat (Filehandle od dgl.) Datenbuffer etc.

Nein, so viel Overhead treibt avr-libc da nicht.  EOF kommt direkt
von der lowlevel-Funktion, gepuffert wird (derzeit) rein gar nichts.
Der Stream ist also in der Tat eine nette Abstraktion, wenn man
verschiedene IO-Kanäle auf obererer Ebene mit einem gemeinsamen API
benutzen möchte, und man hat den Vorteil der formatierten Ausgabe
über fprintf().

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.