Forum: Mikrocontroller und Digitale Elektronik fgets bzw gets richtig anwenden


von Godi S. (godi22)


Lesenswert?

Hallo!

Ich habe mir ein Programm geschrieben für eine PS2 Tastatur.
Dieses Funktioniert für meine Zwecke auch ganz gut.

Jetzt habe ich eine Funktion int keyb_get(void) die Zyklisch aufgerufen 
wird und mir den char zurückgibt wenn einer vorhanden ist, sonst -1.

Diese Funktion habe ich mit fdevopen verknüpft.
FILE* kb_init() {
  ...
return fdevopen(NULL, keyb_get);
}

In der Main rufe ich die kb_init Funktion einmal auf und speichere den 
Rückgabewert in stdin.

In meiner Main-Endlosschleife rufe ich gets(myString) auf.
Eigentlich sollte ja myString erst beschrieben werden wenn ich Enter 
drücke, jedoch wird myString immer beschrieben und immer nur an der 
Position 0.

Aja myString wird in der main angelegt:
char myString[100] = { 0 };

Wie mache ich es richtig damit myString erst beim drücken von Enter 
beschrieben wird?
Den Rückgabewert von gets bzw fgets habe ich auch nicht so richtig 
verstanden. Ist das ein Pointer? wohin?

Vielen Dank für eure Hilfe!

LG,
Godi

von Udo S. (urschmitt)


Lesenswert?

Gottfried S. schrieb:
> Den Rückgabewert von gets bzw fgets habe ich auch nicht so richtig
> verstanden. Ist das ein Pointer? wohin?

Im Internet findet sich dazu (gets in google und 1. Treffer):
"char * gets ( char * str );
Return Value
On success, the function returns the same str parameter.
If the End-of-File is encountered and no characters have been read, the 
contents of str remain unchanged and a null pointer is returned.
If an error occurs, a null pointer is returned.
Use either ferror or feof to check whether an error happened or the 
End-of-File was reached."

Du musst also ein ausreichend großes Character Array anlegen und den 
Pointer dazu der Funktion übergeben. Der Rückgabewert wird nur benötigt 
um den Fehlerfall zu überprüfen. Der String ist nach dem Aufruf in dem 
character Array.

Gottfried S. schrieb:
> return fdevopen(NULL, keyb_get);
Damit legt das Betriebssystem ein Filehandle an.
benutzt du auch brav fclose() um das Handle wieder zu schließen wenn du 
es nicht mehr brauchst?

Du brauchst ein C Buch!

von Godi S. (godi22)


Lesenswert?

Udo Schmitt schrieb:
>
> Du musst also ein ausreichend großes Character Array anlegen und den
> Pointer dazu der Funktion übergeben. Der Rückgabewert wird nur benötigt
> um den Fehlerfall zu überprüfen. Der String ist nach dem Aufruf in dem
> character Array.
>
> benutzt du auch brav fclose() um das Handle wieder zu schließen wenn du
> es nicht mehr brauchst?
>
> Du brauchst ein C Buch!

Hallo!

Ja C Buch würde ich mal brauchen. ;)
fdevopen findet sich aber auch nicht in "c von a bis z"...

Danke für den Hinweis auf fclose(), benütze ich aber nicht weil zur 
Laufzeit des Programmes das File immer offen sein soll.

Ich habe ein ausreichend großes character Array angelegt.
char myString[100] = { 0 };

Vielleicht habe ich einfach ein Verständnissproblem von fgets bzw gets.
Diese Funktionen sollten ja in den Buffer so lange die Zeichen 
nacheinander schreiben bis ein new Line kommt und dann wieder von vorne 
beginnen.
Also in meinem Fall wird fgets zyklisch (Main - Endlosschleife) 
aufgerufen und fragt bei der Funktion keyb_get nach ob ein neues Zeichen 
vorhanden ist. Wenn ja dann gibt keyb_get das zeichen zurück sonst -1. 
fgets speichert das Zeichen in den Buffer. Wenn ein neues Zeichen kommt 
dann sollte fgets dieses an die nächste Position im Puffer schreiben. 
Bei mir wird aber immer nur an die erste Stelle im Buffer geschrieben 
(myString[0]).

Irgendwie bin ich ratlos...


godi

von Karl H. (kbuchegg)


Lesenswert?

Gottfried S. schrieb:

> Vielleicht habe ich einfach ein Verständnissproblem von fgets bzw gets.
> Diese Funktionen sollten ja in den Buffer so lange die Zeichen
> nacheinander schreiben bis ein new Line kommt und dann wieder von vorne
> beginnen.

Das ist so schon alles richtig.

> Also in meinem Fall wird fgets zyklisch (Main - Endlosschleife)
> aufgerufen und fragt bei der Funktion keyb_get nach ob ein neues Zeichen
> vorhanden ist. Wenn ja dann gibt keyb_get das zeichen zurück sonst -1.
> fgets speichert das Zeichen in den Buffer. Wenn ein neues Zeichen kommt
> dann sollte fgets dieses an die nächste Position im Puffer schreiben.

Was aber fgets nicht macht:
Du kannst nicht fgets mehrmals aufrufen und hoffen, dass dir das fgets 
magisch die Zeichen an den bereits vorhandenen Buffer dranhängt. fgets 
weiß nicht wo es zuletzt aufgehört hat zu schreiben.

> Bei mir wird aber immer nur an die erste Stelle im Buffer geschrieben
> (myString[0]).

Zeig mal deinen Code. Ich denke du verwendest fgets falsch bzw. frage 
ich mich gerade, ob nicht fgets für das was du vorhast überhaupt das 
falsche Werkzeug ist und du dir durch das Beharren auf fgets mehr Arbeit 
aufzwirbelst als eigentlich notwendig.

Hinweis: die Standard-IO Funktionen kann man auf einem µC, bei dem die 
Ein/Ausgaben sowieso speziell sind, meist nicht sinnvoll benutzen. Man 
zeiht da nur einen Rattenschwanz an Code ins System ohne dafür etwas 
großartig nützliches zu bekommen.

von Godi S. (godi22)


Lesenswert?

Danke für deine ausführliche Antwort.
Auf fgets beharre ich deshalb weil es leider so gefordert wird. Habe 
aber schon gelesen, dass es die Performance eher beeinträchtigt als es 
an Komfort bringt.

Hier mal die wichtigsten Codestellen:
1
FILE *lcdout, *kbin;
2
3
/************************************************************************/
4
/* this function return a character for the standard I/O
5
/************************************************************************/
6
int keyb_get(void) {
7
  int16_t read = readElement();
8
  
9
  //without ECHO
10
  if(kb_echo == OFF) {    
11
    return read;
12
  }    
13
  else {  
14
  //with ECHO  
15
  if (read >= 0) {
16
    char c = read;
17
    lcd_put(c);
18
  }
19
  return read;
20
  }    
21
}
22
23
/************************************************************************/
24
/* This function initialize the keyboard  
25
/************************************************************************/
26
FILE* kb_init() {
27
  receiveKb_init();
28
  kb_LED.set = FALSE;
29
  kb_reset();  
30
return fdevopen(NULL, keyb_get);
31
}
32
33
/************************************************************************/
34
/* main program
35
/************************************************************************/
36
int main(void) {  
37
  char myString[100] = { 0 };  
38
  
39
  //initial receive data from keyboard
40
  kbin = kb_init();
41
    
42
  keyb_set_echo(ON);
43
44
  //initial the LCD
45
  lcdout = lcd_init();
46
47
  //Set the LCD Contrast
48
  lcd_set_contrast(0x00);
49
  //set the cursor to the home position
50
  lcd_home();
51
52
  // main program 
53
  while(1)
54
  {       
55
    fgets(myString, 100, kbin);
56
    // Hier sollte dann mit myString weitergearbeitet werden wenn fgets fertig ist.      
57
    
58
  }  
59
}

>Du kannst nicht fgets mehrmals aufrufen und hoffen, dass dir das fgets
>magisch die Zeichen an den bereits vorhandenen Buffer dranhängt. fgets
>weiß nicht wo es zuletzt aufgehört hat zu schreiben.

Daran wird es scheitern...

godi

von Karl H. (kbuchegg)


Lesenswert?

Gottfried S. schrieb:

> int keyb_get(void) {
>   int16_t read = readElement();

aus dem Rest deiner Beschreibung entnehme ich:
readElement wartet nicht auf einen Tastendruck


>   while(1)
>   {
>     fgets(myString, 100, kbin);
>     // Hier sollte dann mit myString weitergearbeitet werden wenn fgets
> fertig ist.

Tja. Damit erklärt sich das dann hier.
Für fgets bedeutet 'fertig' auch der Umstand, dass keine Taste gedrückt 
wurde. Deine keyb_get meldet im Grunde EOF an fgets zurück, woraufhin 
fgets aufhört keyb_get zu rufen und sich Zeichen zu holen.

>>Du kannst nicht fgets mehrmals aufrufen und hoffen, dass dir das fgets
>>magisch die Zeichen an den bereits vorhandenen Buffer dranhängt. fgets
>>weiß nicht wo es zuletzt aufgehört hat zu schreiben.
>
> Daran wird es scheitern...

ganz genau.

Du müsstest diesen fgets wiederrum in eine while Schleife stecken, die 
solange läuft, solange nicht das letzte empfangene Zeichen ein \n war.


ABER:
Wozu das ganze: fgets ist nicht dafür eingerichtet, dass es von sich aus 
unterbrechbar ist. D.h. 1 Call von fgets liefert eine Eingabezeile oder 
aber alles aus dem Eingabestrom bis zum 'End Of File' (janachdem was 
vorher angetroffen wird). Du hast für dich definiert, das die End Of 
File Bedingung dadurch erfüllt ist, dass keine Taste gedrückt wurde. Und 
genau so verhält sich dann auch fgets.


Dein konkretes Problem liegt also darin, dass du für keyb_get eine 
Definition getroffen hast, die dir das ganze fgets ad Absurdum führt. 
Das C-Stream Konzept passt nun mal nicht wirklich zu interaktiven 
Tastendrücken. Das ist leider ein Problem, für das dir Standard-C keine 
richtige Lösung anbieten kann. Das C-Konzept sieht vor, dass solange 
gewartet wird, bis dein Benutzer eine Taste drückt oder aber ein 
tatsächliches EOF daherkommt. Nichts tun, im Sinne von "Wenn eine 
Eingabe da ist, hol sie dir und ansonsten steig gleich aus der Funktion 
aus" ist nicht voegesehen.

Wenn du es dir leisten kann, dass fgets Däumchen dreht und auf 
Tastendrücke wartet, kannst du das natürlich immer noch erreichen, indem 
deine keyb_get dieses Däumchen drehen realisiert und solange wartet, bis 
ein Tastendruck daherkommt.
1
int keyb_get(void)
2
{
3
  int16_t read;
4
5
  // warte auf einen Tastendruck
6
  while( ( read = readElement() ) < 0 )
7
    ;  
8
9
  if( read == 4 )    // Strg-D ist EOF
10
    return EOF;
11
12
  if(kb_echo)
13
    lcd_putc((char)read);
14
15
  return read;
16
}

nur ist das natürlich eine Lösung, wie man sie genau auf einem µC 
eigentlich nicht haben will. ZUmindest solange nicht, solange der µC 
während des Wartens auf einen Tastendruck auch noch andere Dinge 
erledigen können soll.

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


Lesenswert?

gets() lässt sich übrigens prinzipbedingt gar nicht "richtig aufrufen".
Das liegt daran, dass es keine Möglichkeit hat zu wissen, wie groß
der übergebene Puffer denn ist, den der Nutzer bereitgestellt hat.
Damit besteht prinzipiell das Risiko, dass der Puffer zu klein ist
und über dessen Grenzen geschrieben wird.

Daher sollte man gets() niemals benutzen.

von Godi S. (godi22)


Lesenswert?

Vielen Dank für eure ausführlichen Antworten!
Hat mir endlich weitergeholfen.

Ok jetzt werde ich mir selbst eine Funktion schreiben die mir die 
Zeichen zu einen String bis zur new Line zusammenfügt.

von Karl H. (kbuchegg)


Lesenswert?

Gottfried S. schrieb:
> Vielen Dank für eure ausführlichen Antworten!
> Hat mir endlich weitergeholfen.
>
> Ok jetzt werde ich mir selbst eine Funktion schreiben die mir die
> Zeichen zu einen String bis zur new Line zusammenfügt.


Ist sowieso die vernünftigste Variante.
Denn ich gehe jede Wette ein, dass dein Eingabezeilen-Leser auch über 
ein paar Line-Editing-Möglichkeiten verfügen soll. So Dinge wie: 
Backspace auswerten und das letzte eingebebene Zeichen wieder löschen, 
eventuell mit dem Cursor in der Zeile links/rechts rumfahren, 
unterschiedliches Verhalten ja nachdem ob Einfüge- oder 
Überschreibmodus, Kommando zum Löschen der Eingabezeile ab 
Cursorposition etc.

All das sind Dinge die du klarerweise von fgets nicht bekommst.

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.