Forum: Mikrocontroller und Digitale Elektronik C-Struktur, unklares Verständniss - ben. Hilfe!


von C. L. (calle)


Lesenswert?

Hallo zusammen,

Ich stehe mal wieder auf dem Schlauch!

Ich möchte ein kleines Menü auf einem 4*20 LCD programmieren mit einer 
C-Strukur. Habe mich an diesem Beispiel gehalten, wie es hier auch schon 
öfter diskutiert wurde:
http://projects.higaski.at/videotutorials/videotutorial1_lcd_menus/videotutorial1_lcd_menus.html

Um das zu verstehen und das zu nutzen habe ich das ganze nun mit der 
MikroC IDE geschrieben und versucht, die Feinheiten anzupassen,
wie z.B. die unterschiedlichen Routinenbenamungen MPLAB/MikroC.
Soweit so gut, aber auf dem Ziel - PIC läuft es nicht.

Jetzt beim Fehlersuchen fehlt mir das Grundverständnis von C-Strukturen.
Hier mal der Code und meine Fragen - kann mir mal bitte jemand einen 
Schupps in die richtige Richtung geben?

Hier wird die const Struktur definiert mit dem Namen MenuEntry:
1
typedef const struct MenuStructure        // Struktur defnieren
2
{       const char *text;                 // Pointer auf Textfeld
3
        unsigned char num_menupoints;     // Variable Anzahl Menüpunkte
4
        unsigned char up;                 // Variable für up
5
        unsigned char down;               // Variable für down
6
        unsigned char enter;              // Variable für enter
7
        void ( *fp )( void );             // Pointer Void/Void Funktion
8
}MenuEntry;
dann folgen die Menüstrings:
1
const char menu_000[] = "[Main Screen]";          // 0
2
const char menu_001[] = "  Options1";             // 1
3
const char menu_002[] = "  Options2";             // 2
4
usw...
sowie die Arrays für das Menü, welche dann die Eigenschaften 
beschreiben:
1
MenuEntry menu[] =
2
{
3
        {menu_000, 11,  0,  0,  0,  0},           // 0
4
        {menu_001, 11,  1,  2, 12,  0},           // 1
5
        {menu_002, 11,  1,  3, 21,  0},           // 2
6
};

Dann gibt es eine Routine, die die Zeichen aus dem ROM(?) auf dem 
Display darstellen soll, anhand von Tastendrücken.
Das mit der Menübedienung ist jetzt erstmal noch nicht das Problem, 
sondern die Funktion und der Ablauf des gesamten Konstrukts mit der 
Pointerübergabe an sich, da bin ich mir unklar.

Hier die Routine eingekürzt ohne Fehlerbehandlung:
1
void LCDWriteStringROM(unsigned char x, unsigned char y, const char *lcd_zeichen, unsigned char clr_line)  
2
{
3
  unsigned char lcd_i = 0;
4
  unsigned char lcd_offset = 0;
5
6
  lcd_offset = strlen(*lcd_zeichen);   // Länge der Zeichenkette
7
  
8
  for(lcd_i = lcd_offset; lcd_i; lcd_i--)
9
    {
10
    Lcd_Chr_Cp(*lcd_zeichen);
11
    lcd_zeichen++;
12
    }
13
}
Diese wird dann aufgerufen in der MAIN mit:
1
LCDWriteStringROM(0,0,*menu[1].text,0);

..... SOOOOOO!

Jetzt meine Kernfragen:
- Kann mir mal "einfach" jemand erklären, wie die
  Pointer - Parameterübergabe aus der MAIN (*menu[1].text)
  in die Struktur funktioniert und wie schlussendlich die Daten fließen?

- Wie funkioniert es mit dem const char *text;?

- Woher weiß das System, welchen Menüeintrag es auswählen soll?

- Wird hiermit -> void ( *fp )( void );
  dann eine Funktion aufgerufen, die den Namen aus dem Menü haben muss?

- Ziel wäre es erstmal, provisorisch in der MAIN diese Funktion 
aufzurufen
  LCDWriteStringROM(0,0,*menu[1].text,0);
  Was passiert dann?
  Welcher Text sollte dann auf dem Display erscheinen;
  meiner Meinung nach doch "  Options1"  oder?

- Kann mein Compiler vielleicht keine Const Strukturen, compiliert
  aber ohne meckern.

- liegen die Const´s im ROM oder RAM?

Mit der Bitte um Einnordung ;-)


Display, Taktfrequenzen, PIC, Flashen usw. können wir alles 
ausschließen, funktioniert!

Quelle: siehe o.g. Link


Gruß Calle

von Clemens (Gast)


Lesenswert?

LCDWriteStringROM(0,0,*menu[1].text,0);
*menu[1].text sieht seltasam aus.
LCDWriteStringROM(0,0,&menu[1].text,0); ??

von Karl H. (kbuchegg)


Lesenswert?

C. L. schrieb:

> Diese wird dann aufgerufen in der MAIN mit:
>
1
> LCDWriteStringROM(0,0,*menu[1].text,0);
2
>
>

Der Stern muss weg. Der hat da nichts zu suchen.
menu[1].text ist bereits ein Pointer.

> Jetzt meine Kernfragen:
> - Kann mir mal "einfach" jemand erklären, wie die
>   Pointer - Parameterübergabe aus der MAIN (*menu[1].text)
>   in die Struktur funktioniert und wie schlussendlich die Daten fließen?

Wenn du dir den Stern wegdenkst, hast du dann immer noch Probleme?
>
> - Wie funkioniert es mit dem const char *text;?

Ist ein Pointer wie jeder andere. Das const besagt lediglich, dass die 
Funktion nicht verusuchen wird das, worauf der Pointer zeigt, zu 
verändern. Vor Einführung des const in die Sprache war das eine reine 
Absprache unter Entwicklern. Nach Einführung des const in die Sprache, 
ermöglichte dieses const die Einhaltung dieser Absprache auch formal zu 
prüfen. -> Mehr Sicherheit, dass sich Funktkionen auch tatsächlich an 
Zusagen halten, die sie in ihren Argumentlisten versprechen.

> - Woher weiß das System, welchen Menüeintrag es auswählen soll?

steht doch da: menu[1]

> - Wird hiermit -> void ( *fp )( void );
>   dann eine Funktion aufgerufen, die den Namen aus dem Menü haben muss?

Ja. Das ist ein Funktionspointer.

> - Ziel wäre es erstmal, provisorisch in der MAIN diese Funktion
> aufzurufen
>   LCDWriteStringROM(0,0,*menu[1].text,0);

Stern weg.

>   Was passiert dann?

aus dem Array menu
1
              menu
wird das Array Element mit dem Index 1 ausgewählt
1
              menu[1]
dieses Array Element ist aber ein Struktur Objekt, laut Definition. Es 
hat also mehrere Member. Unter anderem einen Member, der mit text 
bezeichnet ist. Dieser Member ist ein Pointer auf einen Text.
Also wird mit
1
              menu[1].text
der Inhalt dieses Pointers (die Adresse, unter der der Text zu finden 
ist) ausgelesen und diese Adresse an die Funktion übergeben, die dann 
ihr Ding macht.

>   Welcher Text sollte dann auf dem Display erscheinen;
>   meiner Meinung nach doch "  Options1"  oder?

Ganz genau

> - Kann mein Compiler vielleicht keine Const Strukturen, compiliert
>   aber ohne meckern.

Unwahrscheinlich. Wenn der Compiler mit const nichts anfangen könnte 
(weil zu alt), dann würde es eine Fehlermeldung geben.

> - liegen die Const´s im ROM oder RAM?

Kommt auf den Compiler an.
Die Sprache C hat dazu nicht viel zu sagen. Das const bedeutet erst mal 
nur, dass du als Programmierer festlegst, dass sich dieser Wert niemals 
ändern wird. Bzw. in einer Funktionsschnittstelle eben, dass die 
Funktion auch nicht versuchen wird, ihn zu ändern.
Das ist erst mal die Grundvoraussetzung, das etwas ins ROM verschoben 
werden kann. Die Sprachdefinition hat dazu allerdings nichts zu sagen, 
die kennt kein RAM oder ROM, sondern für die gibt es einfach nur 
Speicher.

Einige Compiler treiben das allerdings soweit, dass sie alles was const 
markiert ist, ins ROM verschieben. Wenn dann die Zugriffe ins ROM auch 
noch anders compiliert werden müssen, als die ins RAM, ist es 
lebenswichtig, das man hier absolut sauber const-Korrekt arbeitet. Ob 
dein Compiler das so macht oder nicht, findest du in der Compilerdoku.


Nimm erst mal den Stern raus. Wundert mich, dass du das so überhaupt 
durch den COmpiler gebracht hast. Eigentlich hätte es da einen saftigen 
Datentyp Fehler geben müssen.

von Karl H. (kbuchegg)


Lesenswert?

Hier
1
void LCDWriteStringROM(unsigned char x, unsigned char y, const char *lcd_zeichen, unsigned char clr_line)  
2
{
3
  unsigned char lcd_i = 0;
4
  unsigned char lcd_offset = 0;
5
6
  lcd_offset = strlen(*lcd_zeichen);   // Länge der Zeichenkette

ist der Stern auch Quatsch.

Ich kapiers nicht. Wie hast du das alles durch den Compiler gebracht? 
Das sind Pointer-Grundlagen-Fehler! Wenn dir dein Compiler das wirklich 
durchgehen lässt, dann wechsle den Compiler, denn dann taugt der nichts.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> Ich kapiers nicht. Wie hast du das alles durch den Compiler gebracht?
> Das sind Pointer-Grundlagen-Fehler! Wenn dir dein Compiler das wirklich
> durchgehen lässt, dann wechsle den Compiler, denn dann taugt der nichts.

Jetzt mal davon abgesehen, dass der ganze Funktionsaufruf zu strlen 
Quatsch ist, denn den braucht in Wirklichkeit keiner. Wozu soll es gut 
sein, dass strlen erst mal Zeichen zählt indem es den String abklappert, 
nur damit du dann in deiner Funktion im Prinzip noch mal dasselbe 
machst, nur komplizierter mit einer eigenem Zähler und for-Schleife. Das 
'Abklappern' bis zum Stringende kannst du auch selbst machen und gleich 
schreiben:
1
void LCDWriteStringROM(unsigned char x, unsigned char y, const char *lcd_zeichen, unsigned char clr_line)  
2
{
3
  while( *lcd_zeichen != '\0' )
4
  {
5
    Lcd_Chr_Cp(*lcd_zeichen);
6
    lcd_zeichen++;
7
  }
8
}

und gut ists. Was anderes macht strlen auch nicht, nur dass es eben 
nicht das Zeichen ausgibt, sondern einen Zähler erhöht und die so 
ermittelte Zahl an Zeichen vor dem '\0' zurückgibt.
1
size_t strlen( const char* txt )
2
{
3
  size_t cnt = 0;
4
5
  while( *txt != '\0' )
6
  {
7
    cnt++;
8
    txt++;
9
  }
10
11
  return cnt;
12
}

von Karl H. (kbuchegg)


Lesenswert?

Clemens schrieb:
> LCDWriteStringROM(0,0,*menu[1].text,0);
> *menu[1].text sieht seltasam aus.
> LCDWriteStringROM(0,0,&menu[1].text,0); ??

Auch wenn es manchmal harmlos ist, bei einem Funktionsargument einen & 
vor ein Array zu schreiben, weil die Compiler diesen Fehler ignorieren: 
Hier wäre das ein fataler Fehler. Die Funktion würde dann nicht die 
Adresse des Strings kriegen, sondern die Adresse des Pointers in dem 
steht, wo der String zu finden ist.
Ganz abgesehen davon, dass dann auch die Datentypen nicht stimmen 
würden. Der Datentyp von &menu[1].text wäre ein 'const char **', während 
die Funktion einen 'const char *' haben will.

Es hilft nichts. Die Sache mit Pointern, Sternen und 
Adress_of_Operatoren muss man als C Programmierer beherrschen. Sonst 
wird das nichts. Sterne und & nach Lust und Laune zu verteilen, bis der 
Compiler ruhig ist, führt nur ins Desaster.

von C. L. (calle)


Lesenswert?

Karl Heinz schrieb:
> Der Stern muss weg. Der hat da nichts zu suchen.
> menu[1].text ist bereits ein Pointer.

Das war erstmal der Schlüssel zu Erfolg!! Jetzt tut sich was auf dem 
Zielsystem!

Karl Heinz schrieb:
> lcd_offset = strlen(*lcd_zeichen);   // Länge der Zeichenkette

Diese Ermittlung macht Probleme, ohne Stern geht nicht, da string 
übergeben werden muss. Zudem ist das Ergebnis nicht plausibel.
Habe das einfach gegen Deine Routine ersetzt und siehe da, fluppt 
einwandfrei!

Soweit habe ich erstmal das Etappenziel erreicht.

Jetzt werde ich mal sehen, das ich die eingentlichen 
Steuerungsfunktionen schreibe. Wenn es wieder hakt, dann melde ich mich 
nochmal!

Wie schnell man Hilfe bekommen kann ist echt erstaunlich!

DD = Dickes Dankeschön bis hier her mal an Euch!!

Carsten

von Karl H. (kbuchegg)


Lesenswert?

C. L. schrieb:

>
> Karl Heinz schrieb:
>> lcd_offset = strlen(*lcd_zeichen);   // Länge der Zeichenkette
>
> Diese Ermittlung macht Probleme, ohne Stern geht nicht, da string
> übergeben werden muss.

Sorry. aber das ist Blödsinn.
Wenn lcd_zeichen ein const char* ist, dann hat es bereits den richtigen 
Datentyp um direkt an strlen übergeben zu werden.

Was maximal sein kann, das ist diese automatische RAM/ROM Sache, so dass 
strlen für einen String im ROM die falsche Funktion ist. Bei PIC bzw. 
PIC-Compilern bin ich mir da immer etwas unsicher, ob das auch heute 
noch so ist, bzw. bei welchen PIC-Typen bzw. Compilern das noch so ist.
Aber prinzipiell ist
1
void foo( const char* str )
2
{
3
  size_z i = strlen( str );
4
}
absolut korrekt. Ein Stern wäre hier völlig falsch.

>  da string übergeben werden muss.

Einen string kannst du prinzipiell nicht übergeben. Was du übergeben 
kannst, das ist die Startadresse des Strings. Genau die steht aber in 
der lokalen Variablen lcd_zeichen drinn. Deren Inhalt (da es ja eine 
Pointer-Variable ist) ist genau die Startadresse des Strings, die diese 
Funktion von ihrem Aufrufer gekriegt hat. *lcd_Zeichen wäre gar kein 
Pointer mehr, sondern der erste Buchstabe des Strings. Und den wirst du 
kaum an strlen übergeben können.

von C. L. (calle)


Lesenswert?

... ja, ich habe das mit der Funktion strlen(*lcd_zeichen); jetzt 
verworfen und habe mich weiter um die Menüsteuerung gekümmert.

Jetzt läuft fast alles gut und ich kann quasi im Menü hin und her 
browsen.
Wenn ich jetzt die aufzurufene Funktion eintrage, meckert der Compiler!

Hier sind die Menüstrings und die Entry´s programmiert:

const char menu_000[] = " [Hauptmenue]       ";                  // 0
const char menu_001[] = "  Options1          ";                  // 1
const char menu_002[] = "  Options2          ";                  // 2
const char menu_003[] = "  Options3          ";                  // 3
const char menu_004[] = "  Options4          ";                  // 4
const char menu_005[] = "  Options5          ";                  // 5
const char menu_006[] = "  start             ";                  // 6
const char menu_007[] = "  Options6          ";                  // 7
const char menu_008[] = "  Options7          ";                  // 8
const char menu_009[] = "  Options8          ";                  // 9
const char menu_010[] = "  return            ";                  // 10


MenuEntry menu[] =
{
        {menu_001, 11,  1,  2, 12,  0},
        {menu_002, 11,  1,  3, 21,  0},
        {menu_003, 11,  2,  4, 30,  0},
        {menu_004, 11,  3,  5,  4,  0},
        {menu_005, 11,  4,  6,  5,  0},
        {menu_006, 11,  5,  7,  6,  start},
        {menu_007, 11,  6,  8,  7,  0},
        {menu_008, 11,  7,  9,  8,  0},
        {menu_009, 11,  8, 10,  9,  0},
        {menu_010, 11,  9, 10, 10,  0},                 // 10
}

Die Funktionen werden mit dem ENTER Button dann aufgerufen via
                 if (ImpulsEnter)
                 {  //wenn 6, dann start aufrufen
                    if (menu[selected].fp != 0)  menu[selected].fp();
                    selected = menu[selected].enter;
                 }

Kann mir da noch mal jemand helfen?
Angemeckert wird die Zeile {menu_006, 11,  5,  7,  6,  start},
Es muss an dem Aufruf der Routine liegen.
Diese sieht zu Test so aus:

void start (void)
{
Lcd_Cmd(_LCD_CLEAR);               // Clear display
Lcd_Out(1,1,"Aufgehts !!");
DELAY_MS(500);
}

Mit der Bitte um Hilfe.
Das wäre dann das letzte was ich jetzt noch benötige.

Gruß und schönen Abend!

Carsten

von Karl H. (kbuchegg)


Lesenswert?

C. L. schrieb:

> Wenn ich jetzt die aufzurufene Funktion eintrage, meckert der Compiler!

'meckert' ist keine vernünftige Fehlerbeschreibung.
Da steht sicher nicht
1
Error 345, Line 68: Ich meckere jetzt
sondern eine ordentliche Fehlermeldung, die du auch lesen und verstehen 
solltest.

Ich kann mir schon vorstellen, was das Problem ist. Aber du sollst auch 
selbst ein wenig nachdenken. Zumal es hier wieder um eine grundsätzliche 
Eigenheit der Sprache bzw. des Compilers geht, der sich eben nicht kreuz 
und quer aus deinem Programmtext die Einzelteile zusammensucht sondern 
den Text von oben nach unten liest. Und das hat nun mal Konsequenzen, 
die in dem Satz zusammengefasst werden können "Verwendet werden kann nur 
das, was bereits bekannt ist. Insbesondere muss mindestens eine 
Deklaration vor einer Verwendung im Quelltext auftauchen"

von C. L. (calle)


Angehängte Dateien:

Lesenswert?

Hallo,

hier mal die Fehler mit Bild...
...ja, hätte ich auch eher machen können, sry.

Der Compiler sagt "undeclared identifier in expression"
dieser Ausdruck wundert mich trotzdem, weil die Funktion doch existiert.

Karl Heinz schrieb:
> Ich kann mir schon vorstellen, was das Problem ist. Aber du sollst auch
> selbst ein wenig nachdenken. Zumal es hier wieder um eine grundsätzliche
> Eigenheit der Sprache bzw. des Compilers geht, der sich eben nicht kreuz
> und quer aus deinem Programmtext die Einzelteile zusammensucht sondern
> den Text von oben nach unten liest. Und das hat nun mal Konsequenzen,
> die in dem Satz zusammengefasst werden können "Verwendet werden kann nur
> das, was bereits bekannt ist. Insbesondere muss mindestens eine
> Deklaration vor einer Verwendung im Quelltext auftauchen"

Hmmm. Nachdenken mache ich ja... nur das Durchblicken fehlt noch!
Aus meiner Sicht ist doch alles richtig und in der Compilerdoku steht da 
nichts klares oder artverwandtes drin. Im Netz stehen mit anderen 
Compilern genau die prinzipgleichen Anweisungen drin. Habe auch schon 
alles mögliche probiert, wie Aufruf mit Klammern usw. aber nichts ist 
zielführend.

Dein Satz war Gold Wert.
> Zumal es hier wieder um eine grundsätzliche
> Eigenheit der Sprache bzw. des Compilers geht, der sich eben nicht kreuz
> und quer aus deinem Programmtext die Einzelteile zusammensucht sondern
> den Text von oben nach unten liest.

Der Compiler kann nur kennen, was er schon durchforstet hat!

Also die Zielanwendung funktioniert, wie ich es möchte.

Danke Karl Heinz - und auch die anderen ;-}


Carsten

von Christian (Gast)


Lesenswert?

C. L. schrieb:
> Der Compiler sagt "undeclared identifier in expression"

Der Compiler sagt "undeclared identifier "start" in expression"

hast du den Parameter start deklariert und definiert?

von Michael (Gast)


Lesenswert?

Ich kaue ja auch nicht gerne Lösungen vor, aber das Erste, was mir hier 
einfällt:
Du hast die Funktion unterhalb deiner Main-Funktion definiert, (oder in 
welcher auch immer das Menü gesteuert wird) und oberhalb keinen 
Funktionsprototypen.

Das kann ich aber nicht genau sagen, da du nur Codeschnipsel zeigst...

Erwarten würde ich sowas:
1
void start(void); 
2
3
int main(void)
4
{
5
    start();   // das funktioniert nur, wenn vorher "start" bekannt ist
6
    return 0;
7
}
8
9
void start(void)
10
{
11
    //hier die funktionsinhalte
12
}

von C. L. (calle)


Lesenswert?

Hi!

Lösungen vorkauen ist immer so eine Sache!
Am besten lernt man natürlich, wenn man in die richtige Richtung 
gestoßen wird. Aber hinhalten oder den falschen Ton an den Tag zu legen 
ist auch nicht korrekt. Gut, das es hier nicht so war.

Zum Thema:
Ja genau das war ja auch das Problem.
Die Routine war zwar definiert aber dem Compiler an der Aufrufposition 
noch nicht bekannt.
Die Routine "start" ist zwar vor der MAIN aber hinter der eingentlichen 
Menüroutine, die die Funktion start aufruft.

Kurz umgeschoben und es hat auf anhieb funktioniert.

Danke

Carsten

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.