Tipp für case-Schleife in C gesucht

  • Ich bin mal wieder ein wenig am Programmieren und bräuchte mal eine Idee. Es geht um eine switch/case-Sequenz, bei der die Tasten der Fernbedienung ausgewertet werden.


    Code
    //etwas vereinfacht
    #define VOLup 16
    #define VOLdown 17
    
    
    switch (code)
    {
      case VOLup: { ...break;}
      case VOLup: { ...break;}
     }


    Problem ist, das an den case-Ausdrücken Konstanten verlangt werden. Ich möchte die aber eigentlich dynamisch haben, denn ich will zur Laufzeit zwischen verschiedenen Fernbedienungen umschalten können und die haben oft unterschiedliche Codes. Wahrscheinlich wird es mit case gar nicht funktionieren, oder hat jemand eine Idee?


    Danke schon mal.

  • Wie du schon erkannt hast: case-Lables müssen konstant sein. Sie werden schon zur Compile-Zeit in eine konstante Sprungtabelle übersetzt.


    Du kannst aber durchaus mehrere case-Labels vor den gleichen Block schreiben. Das funktioniert natürlich nur, wenn die Codes der Fernbedienungen in unterschiedlichen Nummernkreisen liegen. Das könntest du aber auch erzwingen, in dem du einen Offset zu jedem Code addierst, der spezifisch für die jeweilige Fernbedienung ist.


    BTW: Beim case: braucht du keine {}-Klammern. Außer du willst lokale Variablen mit definierter Sichtbarkeit und Lebenszeit haben, z.B. für RAII Techniken.


  • Nabend


    grad keine Zeit, aber ich habe irgendwie was im Kopf das da etwas mit 'nem Pointer möglich war. Versuch mal zu googeln "c case ponter" oder so. Vllt. findeste was, ich habe grade keine Zeit zum gucken.
    Edit: ach mist ich glaube ich war mit pointern zu funktionen durcheinander gekommen.

  • jkunz: ist schon eine sehr gute Idee. Das funktioniert zumindest solange, wie ich die Codes der FB vorher kenne. Ich wollte das allerdings noch vom Anwender eingebbar machen und im EEPROM speichern. Das wird dann leider auch nicht gehen.


    Sind if-then-else Schleifen eigentlich wesentlich langsamer als eine case-Anweisung? Am Ende ist das Ergebnis das gleiche, es ist nur nicht so schön übersichtlich. Sicher dauert der Vergleich dann länger, da der ja komplett zur Laufzeit berechnet wird.


    Bloody: function pointer kann man sicher gut einsetzen. Das werde ich in einer späteren Version mit state machine auch noch machen. Aber das Problem der Auswertung dürfte damit nicht gelöst sein.

  • Vielleicht geht's ja andersrum, also dass Du praktisch den abzufragenden Wert "modifizierst"...


    also, Du hast "intern" bei der Case-Abfrage immer die selben Werte für die selbe Aktion, z.b. Case 1 = was passieren soll bei Vol up, Case 2 = was passieren soll bei Vol down, usw.


    dann vor dem Case-Dings nachschauen in ner Tabelle: von Fernbedienung kommt z.B. Vol Up = Code 16, das ist dann Deine interne 1, kommt Code 17 ist es Deine 2 für die Case-Abfrage usw.


    Und diese Tabelle kannst Du dann ja beliebig zur Laufzeit ändern - also dass eben z.B. die 1 für "Vol up" dann bei (meinetwegen) Code 54 rauskommt (wenn das eben bei der gewünschten FB der Knopf ist)...


    k.A. wie man das in C macht (Array..?), aber es müsste eben im RAM sein, und dann auch wie schon gesagt im EEPROM gesichert werden bei Änderungen, damit man's nicht jedes Mal neu eingeben muss...


    EDIT: Ich kenne mich mit C wie gesagt nicht wirklich aus, aber letztlich wird da ja Maschinencoe draus.. ;) - also in Assembler wäre so ein Case-Konstrukt so, dass man den Wert eben mit ner Konstante vergleicht:


    cpi Wert, 1
    breq dahin was bei 1 passieren soll


    cpi Wert, 2
    breq dahin was bei 2 passieren soll


    usw.


    wenn ich das mit ner 2. Variable vergleichen will, dann muss ich halt 2 Register vergleichen, evtl. (wenn die 2 Variable im Ram liegt) vorher laden:


    lds temp0, Fall1 ; "Fall1" ist eine RAM-Adresse
    cpi Wert, temp0
    breq dahin was bei 1 passieren soll


    usw.


    wäre hier also auch nur ein Takt mehr - aber k.A., wie der C-Compiler sowas umsetzt...?


    macht man in C nicht auch immer Funktionen..? - damit wäre doch auch ne if-Abfrage recht übersichtlich:


    if Wert == Code_fuer_VolUp then { LED_Heller };
    if Wert == Code_fuer_Voldown then { LED_dunkler };
    if Wert == Code_fuer_Menu then { Zurueck_zum_menu };


    usw. - oder wie man das genau schreibt... und "Code_fuer_VolUp" etc. wären hier eben Variablen, die man auch zur Laufzeit ändern kann...

    It's only light - but we like it!


    Da es sich in letzter Zeit häuft: Ich beantworte keine PNs mit Fragen, die sich auch im Forum beantworten lassen!
    Insbesondere solche von Mitgliedern mit 0 Beiträgen, die dann meist auch noch Sachen fragen, die bereits im entsprechenden Thread beantwortet wurden.
    Ich bin keine private Bastler-Hotline, technische Tipps etc. sollen möglichst vielen Lesern im Forum helfen!

    Einmal editiert, zuletzt von Pesi ()

  • Also ich würde das mit If-Anweisungen realisieren, ungefähr so:


    Es besteht natürlich auch die möglichkeit, dass du erst deine empfangenen Codes (so wie es Pesi am Anfang seines Beitrags geschrieben hat) in interne codes umwandelst, dass würde sich aber wohl nur lohnen, wenn du die Codes an mehreren Stellen verwendest oder fremde switch-case-Konstruckte ohne Änderung übernehmen willst. Das liegt daran, dass du ja die Codes dabei dann 2 mal auswerten musst, was natürlich mehr Code und Systemzeit erfordert.


    Gruß
    Karsten


    EDIT: habe gerade noch mal nachgedacht, wie man es noch machen könnte (Pesi hatte sowas auch schon angesprochen):


    Der Nachteil an dieser Version ist jedoch, dass das Array genau so viele Bytes groß sein muss, wie es FB-Codes gibt.

  • Grundsätzlich muss man bei solchen Problemen auf die verschiedenen Wertebereiche schauen, um entscheiden zu können, wie man das am geschicktesten kodiert. Je nach dem ist die eine oder andere Methode besser.


    Noch schneller ist es, wenn man ein Array aus Funktionspointern anlegt, der über den gesamten Wertebereich der Codes läuft. Dann braucht man gar keine if oder case Kaskade mehr sondern kann einfach


    Code
    keyactions[code]();


    aufrufen. Um da Speicher zu sparen, kann man aus dem Array noch unbenutzte Teile mit if-Anweisungen rausschneiden. Also z.B.


    Code
    if (code > 150 && code < 220) {
     return; // invalid code
    }
    if (code > 150) {
      code -= 70; // we cut off 150 to 220 so adapt code to match the array
    }



    Fabi

  • Das Problem dürfte aber sein, dass jeder Code bei unterschiedlichen Fernbedienungen unterschiedliche Bedeutung haben kann. Hier mal das Array mit den Codes.


    Code
    const FBtypes FB[4]  PROGMEM = {
    
    
    { 0, 43, 44, 53, 13, 46, 42, 60, 56, 16, 17, 32, 33, 11, 47, 59, 19, 18, 20, 21, 12, 10 },
    { 5, 53, 54, 41, 45, 50, 52, 55, 39, 16, 17, 32, 33, 11, 15, 60, 44, 46, 47, 42, 12, 10 },
    { 8, 43, 44, 99, 13, 46, 42, 60, 56, 16, 17, 32, 33, 34, 99, 99, 99, 99, 99, 99, 12, 10 },
    { 0, 0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }
    
    
    };


    die Codes mit 99 sind nicht implementiert, der letzte Bereich ist für eigene Definitionen vorgesehen. Aber vielleicht kann man das mit den Funktionszeigern trotzdem machen, indem man wie oben vorgeschlagen einfach für jede FB einen Wert addiert. Das wäre dann recht genial. Also ich sehe schon, alles sehr gute Ideen hier. Vielen Dank.

  • Das Problem dürfte aber sein, dass jeder Code bei unterschiedlichen Fernbedienungen unterschiedliche Bedeutung haben kann.


    Meiner Meinung nach spielt das kein Rollte. Soweit ich das verstanden habe soll der Anwender ja den verwendeten Fernbedienungstyp zuvor einstellen. Wenn dieser festgelegt ist kannst du ja eine genaue Zuordnung von Fernbedienungscode zu Aktion festlegen. Eine zeitgleiche Verwendung von unterschiedlichen Fernbedienungen ohne etwas umzustellen wäre aber bei dieser Methode nicht möglich.
    Ich persönlich würde das ganze auch per Array mit Funktionspointern auf die jeweilige Aktion machen. Dann hättest du pro Fernbedienung eben einen Array mit N Elementen. Wobei N durch den höchsten Fernbedienungscode definiert würde.


    also so z.B.:

    Code
    void* PhillipsCodes[] =  {(*VolUp)(),(*VolDown)(),...
    };
  • Die Idee mit der Übersetzungstabelle in einem Array wurde ja schon genannt. So würd ich das auch machen, wenn es dynamisch rekonfigurierbar sein muss. Also pro Fernbedienungstyp eine Tabelle / Array und in dem Array einen internen Funktionscode hinterlegen.


    Funktionszeiger in die Tabelle zu legen wird wohl gehen, könnte aber ineffizient sein. Der interne Funktionscode kommt mit einem Byte aus, die Funktionsadresse braucht mindestens zwei Bytes. Wenn man mit Funktionszeigern arbeitet macht man es dem Compiler auch unmöglich die Funktionen bei der Optimierung automatisch zu inlinen. Das kostet dann nochmal einige Bytes pro Funktion.

  • Ich habe noch eine andere Idee, die funktionieren könnte. Weiß nur nicht, wie effektiv das ist. Ich habe ja oben schon mal das Array mit den Codes gezeigt. Wäre es effektiv, den Code einfach zu "übersetzen"? Und zwar so: ich habe die FB aus dem 2. Block (Zeile 4) aktiv. Zeile 3 ist der Master-Code, der konstant ist und dann auch einfach in der Case-Schleife verwendet wird. Wenn jetzt ein Code ankommt, suche ich den im Array. So bekomme ich den Index und gehe einfach in Zeile 3 mit dem Master-Code und verwende den. Beispiel zu obigem Array:


    Fernbedienung [1] ist aktiv; Code 50 wird gesendet und gesucht; das entspricht FB[1,5]
    für die Auswertung im Case nutze ich dann FB[0,5]


    Einziges Problem dieser Methode: ich brauch eine effektive Suchfunktion im Array.

  • Naja, das Suchen ist ja im Prinzip einfach, Schleife durchlaufen, und wenn der gesuchte Code gefunden ist, dann abbrechen...


    also so in der Art (k.A. wie man das genau schreibt):


    Empfangener Code ist in Received_Code, der interne in Master_Code, Maxcode = maximale Anzahl der internen Codes, afb = aktive Fernbedienung


    for ( i = 1, i <= Maxcode, i ++);


    if FB[afb,i] == Received_Code (dann Schleife verlassen, k.A. wie das geht)


    next i;


    Master_Code = FB[0,i]


    also praktisch in Deinem Beispiel, afb = 1, die Schleife läuft bis 5, da kommt ja dann bei FB[1,5] 50 raus, also wird die Schleife verlassen und dann aus FB[0,i]m was ja dann FB[0,5] ist, der interne Code rausgeholt...


    bzw. das wäre gar nicht nötig, weil Du kannst ja dann gleich i als internen Code nehmen, also 5 ist das, was passiert, wenn von der FB1 der Code 50 kommt...


    und wenn Die Schleife komplett durchlaufen wird, ohne dass der Cde gefunden wurde, dann ist er ungültig und es wird nix gemacht...


    wenn Du nur ne Hand voll Codes hast, die Dich interessieren (heller, dunkler, Farbe weiterschalten, ... sagen wir mal 16 Codes) dann sparst Du Dir natürlich viel Platz im Array gegenüber der andersrum-Methode, wo Du ja für jede FB sämtliche Codes im Array haben musst, egal ob die nun gebraucht werden oder nicht...


    und so ne Schleife 16x durchlaufen und 2 Werte vergleichen kann ja auch in C nicht lange dauern...

    It's only light - but we like it!


    Da es sich in letzter Zeit häuft: Ich beantworte keine PNs mit Fragen, die sich auch im Forum beantworten lassen!
    Insbesondere solche von Mitgliedern mit 0 Beiträgen, die dann meist auch noch Sachen fragen, die bereits im entsprechenden Thread beantwortet wurden.
    Ich bin keine private Bastler-Hotline, technische Tipps etc. sollen möglichst vielen Lesern im Forum helfen!

  • Hab den vorschlag von Pesi mal versucht in code umzusetzen:



    Sieht eigentlich sehr kompakt aus die Lösung.

  • ah, so schreibt man das, vielen Dank!


    wegen dem "nicht notwendig": Das wäre aber schon ne gute Idee, und zwar, dass man die internen Codes nach Häufigkeit des Vorkommens sortiert, also z.B. Heller und Dunkler ganz am Anfang (command 1 und 2) usw., und dann die Schleife verlässt, wenn der Code gefunden wurde...


    weil dann muss die nicht jedes mal ihre 16x (oder wie viel auch immer) durchlaufen, sondern ist eben bei oft gebrauchten Kommandos dann deutlich kürzer...

    It's only light - but we like it!


    Da es sich in letzter Zeit häuft: Ich beantworte keine PNs mit Fragen, die sich auch im Forum beantworten lassen!
    Insbesondere solche von Mitgliedern mit 0 Beiträgen, die dann meist auch noch Sachen fragen, die bereits im entsprechenden Thread beantwortet wurden.
    Ich bin keine private Bastler-Hotline, technische Tipps etc. sollen möglichst vielen Lesern im Forum helfen!

  • Ja, ich denke so in etwa werde ich es aufbauen. Ich denke aber, dass ich es als while-Schleife aufbaue, die dann bei "found" automatisch beendet wird. Das mit dem Sortieren und dann schneller finden, war eigentlich meine Idee, aber da muss man die Matrix dann immer umsortieren, was auch Zeit kostet. Aber häufig benutzte Codes werde ich dann mal an den Anfang sortieren. Das ist eine gute Idee. Ich werde mal schauen, dass ich das jetzt am WE fertig machen kann.

  • ahja, stimmt, das mit dem "while" gibt's ja auch noch - an sowas denke ich nie, weil in asm ist das letztlich immer das selbe, halt nur unterschiedlich dadurch, *wo* ich vergleiche und dann springe... ;)


    aber da muss man die Matrix dann immer umsortieren, was auch Zeit kostet.

    ich meinte das jetzt nicht so, dass die SW irgendwie automatisch schaut, welcher Code wie oft gebraucht wird und dann die Tabelle ständig dynamisch umsortiert - *das* wäre echt völlig übertriebener Aufwand! :D


    sondern eben nur so, dass man sich halt vorher überlegt, welche Tasten am häufigsten gedrückt werden, und die dann weiter vorne auflistet - that's all!


    und selbst wenn nicht, wie gesagt, so viele Codes hat man ja nicht, selbst wenn die Schleife jedes mal 20x durchlaufen würde, dauert das in C ja auch nicht lang...

    It's only light - but we like it!


    Da es sich in letzter Zeit häuft: Ich beantworte keine PNs mit Fragen, die sich auch im Forum beantworten lassen!
    Insbesondere solche von Mitgliedern mit 0 Beiträgen, die dann meist auch noch Sachen fragen, die bereits im entsprechenden Thread beantwortet wurden.
    Ich bin keine private Bastler-Hotline, technische Tipps etc. sollen möglichst vielen Lesern im Forum helfen!

  • Ähhhhm. Wenn in einem "Array" Werte gesucht werden, dann doch bitte möglichst mittels binärer Suche. Von Sonderfällen abgesehen ist das mit O(log n) die effizienteste Suche und obendrein sehr einfach. (Siehe C-Beispiel im Wikipedia Artikel.) Setzt allerdings ein sortiertes "Array" voraus. Aber je nach dem ist das ja ohnehin statisch und damit vorsortierbar oder es wird mal geschwind durch Quicksort gedreht.

  • Naja, das Sortieren würde dann aber bedeuten, ich muss den Master-Code (erste Eintrag des Arrays) in der gleichen Reihenfolge sortieren wie die Zeile mit der aktiven Fernbedienung. Das halte ich für recht aufwendig zu machen (hab auch keine Idee dazu, außer den Index in einer weiteren Dimension mitzuspeichern).


    Ich würde es ja manuell so sortieren, dass die Codes, die am wahrscheinlichsten sind, zuerst im Array auftauchen. Dann wird die Schleife eher nur kurz durchlaufen. An der Stelle bin ich auch gerade. Das Lesen aus dem EEPROM ist mir als Block schon mal gelungen.

  • Wo ist das Problem?
    Ein "Array" in C ist so und so nur ein Zeiger. ("a[2]" ist semantisch gleich "*(a+2)") Je nach dem Fernbedienungstyp initialisiert man das "Array" mit dem Zeiger auf die Code-Tabelle der einen oder der anderen Fernbedienung. Ein mehrdimensionales Array ist nicht nötig und würde glatt mal die doppelte Speicherkapazität belegen.


    BTW: Die avr-libc hat bsearch(3) und qsort(3) schon fix und fertig eingebaut.

  • Wenn ich deinen Vorshclag richtig verstanden habe, dann würde ich das nicht machen, da durch das sortieren die Verbindung zwischen i und dem internen FB-Code verlohren geht. Das sortieren würde bedeuten, dass du die Zuweisung "command=i;", wie ich es in dem letzten Code gemacht habe nicht machen kannst, da die Codes ja quasie "durcheinander" sind und du müstest ein doppelt so großes array anlegen, damit du dann wieder die Zuweisung zum internen FB-Code hast.


    Du kannst gerne deinen Vorschlag mal in C-Code hier posten, dann kann man das besser vergleichen/diskutieren.


    Bei dem Mehrdimensionalen Array kommt es darauf an, ob man während der Systemzeit einen anderen Fernbedienungstyp auswählen möchte oder nicht. Anfangs höhrte es sich das so an, dass turi das vor hat. Somit kannst du nicht einfach sagen, dass das nicht notwendig ist. Wenn nur eine FB genutzt werden soll, hast du natürlich Recht, dass das nicht notwendig.