Hilfe bei: Tasten am Atmega328P über ADC auswerten

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • Hilfe bei: Tasten am Atmega328P über ADC auswerten

      Guten Tag,


      ich baue Modelle und steuere die Beleuchtung bzw. den Sound mit kleinen Bascom Programmen.
      Das funktioniert auch leidlich.
      Beispiel:
      So sah das Prinzip-Schaltbild für eine Steuerung der Beleuchtung und dem Sound eines X-Wing Fighters bei meinem letzten Projekt aus:

      comp_1_Schaltplan.jpg

      In der Grundplatte so eines Modells ist nicht sehr viel Platz; da ist mir der Arduino Nano aufgefallen.
      Die sind ja so günstig geworden, dass sich die Löterei kaum noch lohnt, zumal der Arduino sehr kompakt und leistungsfähig ist.
      Ich möchte jetzt gerne meine neuen Projekte auf den Arduino Nano umstellen.
      Verbesserungswürdig erschien mir die Auswertung der Tasten über einen ADC-Pin zu realisieren,
      statt dafür so viele Pins wie im Beispiel zuvor zu verbraten.

      Im Netz habe ich einige interessante Beispiele von DL6GL (Call eines Amateurfunkers) gefunden.

      Ich bin jetzt schon etliche Tage dabei, das Ganze zu verstehen und lauffähig zu bekommen, aber ich stoße da immer wieder auf Probleme.


      Ist hier jemand so nett, sich das mal anzusehen?
      Für Tipps und Lösungen wäre ich sehr dankbar!


      Das Programm selber funktioniert auch!
      Bis zu den Printbefehlen ist alles ok.
      Die Prints geben die gedrückte Taste richtig und flüssig aus.
      Jetzt soll aber anstatt der Ausgabe - welche Taste wurde gedrückt - das eigentliche Programm stehen.
      Also: schalte den DF-Player ein, spiele Sound 1, Laser abfeuern und und und...
      Sobald aber hier jetzt Wartezeiten durch weitere Programmteile entstehen, funktioniert die Tastenauswertung nicht mehr.

      Hat jemand eine Idee oder gar eine funktionierende Lösung?
      Auch eine Lösung mit Interrupt und GetADC wäre ok, auch wenn diese etwas langsamer arbeiten sollte.

      Das Programm:Variablentest.bas


      Gruß Dieter
    • Es gibt da so ein "Arduino LCD Keypad Shield" bei dem die Tasten auch per ADC ausgewertet werden. ere im Netz zu findende code ist zwar in C++, sollte aber recht einfach in Bascom umzusetzen sein.
      Hier mal der Arduino (C++) code:

      Source Code

      1. // Ab hier wird ein neuer Programmblock mit dem Namen "Tasterstatus" erstellt. Hier wird ausschließlich geprüft, welcher Taster gedrückt ist.
      2. int Tasterstatus()
      3. {
      4. Analogwert = analogRead(A0); // Auslesen der Taster am Analogen Pin A0.
      5. if (Analogwert > 1000) return KeinTaster;
      6. if (Analogwert < 50) return Tasterrechts;
      7. if (Analogwert < 195) return Tasteroben;
      8. if (Analogwert < 380) return Tasterunten;
      9. if (Analogwert < 555) return Tasterlinks;
      10. if (Analogwert < 790) return Tasterselect;
      11. return KeinTaster; // Ausgabe wenn kein Taster gedrückt wurde.
      12. }
      13. // Hier wird der Programmblock "Tasterstatus" abeschlossen.
      Display All
      Du müsstest also regelmäßig den ADC auslesen und dann in einer If-Then Schleife den aktuellen Analogwert zuordnen.
      Der Analogwert hängt natürlich von deiner Widerstandskette ab, aber so in etwa sollte es machbar sein.
    • thor101 wrote:

      Sobald aber hier jetzt Wartezeiten durch weitere Programmteile entstehen, funktioniert die Tastenauswertung nicht mehr.

      Hat jemand eine Idee oder gar eine funktionierende Lösung?
      Ja ich :thumbsup:

      Du brauchst im Programm praktisch 2 Zweige, die quasi parallel ablaufen. Der eine Zweig für die Tastenabfrage,
      und der andere Zweig für die Befehlsausgabe an den DF-Player. Hierbei aber auf Waits verzichten. Besser Timeouts verwenden.

      Jetzt bekomme ich gleich wieder Haue. a_62_601cd92e a_71_f9c57bbe
      Aber schau dir mal im Lexikon das Statemachine-Tutorial an.
      Mit dieser Technik kann man solche Probleme lösen.
    • Moin,



      danke, dass Sie sich mit meinem Problem beschäftigt haben!




      Ich möchte tatsächlich nur ein paar Tasten über verschiedene Spannungswerte am ADC-Pin erkennen können.

      Das Erkennen klappt ja auch so weit, aber warum die Erkennung nicht mehr richtig funktioniert, wenn weitere Programmteile folgen, ist mir nicht ganz klar.



      Ach, Beispiele in „C“ umzuschreiben ist für mich jedenfalls sehr aufwändig.

      Ich bin eben schon ein älteres Semester.






      Ich denke, Mitch64 hat den Finger in die Wunde gelegt.



      Ein grundsätzliches Problem ist die Behandlung der Interrupts.

      Leider kann man immer nur nach dem „Return“ an die Stelle zurückspringen, von der der Sprung erfolgte.






      In meinem Programmbeispiel, beim direkten Auslesen des ADC - Pins, läuft die ISR ständig.

      Also wird sie nicht durch Tastendruck, respektive der Spannungsänderung am ADC-Pin ausgelöst.

      Ich müsste Doppeltaster haben und dann mit einem zusätzlichen Pin über den PCINT einen ISR auslösen.



      Was bleibt, ist das grundsätzliche Problem - der ISR.




      Gerade wenn man Tastatureingaben macht, während das Programm in einem Bereich etwas abarbeitet,

      aber nach der ISR an einer anderen Stelle weiter machen sollte, funktioniert das nicht.






      Wenn ich das Beispiel von Mitch64 mit der „Statemachine“ richtig verstanden habe,

      ist das der Behelf, dass man im Programm nur zusätzlich nach solchen Änderungen der ISR sucht oder diese ständig abfragt.

      Da blicke ich nicht ganz durch. Sorry.






      Ich weiß nicht, aber super wäre es, am Ende der ISR den Stackpointer mittels eines Befehls verbiegen zu können,

      um dahin zurück zu springen, wohin man auch will.

      (geht auch, aber nur mit Assembler)

      $asm

      ldi R22,$00

      ldi R23,$63

      sts $de,r22

      sts $df,r23

      $end Asm



      Ich habe das mal vor ein paar Jahren in einem Programm mit vorstehenden Zeilen hinbekommen, aber bitte fragt mich nicht mehr, wie

      Ich weiß es einfach nicht mehr.




      Man liest immer wieder, dass es dann nicht sauber programmiert wäre;

      aber eine echte knackige zielgenaue Lösung ist mir noch nicht bekannt.






      Nun denn, ich werde versuchen, mit meinen Möglichkeiten eine Lösung zu finden

      - schlimmstenfalls einfach für die 5 Tasten die ich brauche, über 5 Pins mit PCINT.

      Das kriege ich hin.






      Nochmals ganz herzlichen Dank!



      Gruß Dieter
    • Wenn du mehrere Programmteile hast, die auszuführen sind,
      kannst du das doch per Variable unterscheiden.
      Und diese Variable dann per Select Case abfragen.

      Du hast z.B. 3 Programmteile die ausgeführt werden sollen.
      Nennen wir die mal PRG_TEIL_1, PRG_TEIL_2 und PRG_TEIL_3. Das wird per Konstanten definiert.
      Dann brauchst du eine Variable, wo das drin gespeichert wird. Nennen wir die mal ProgrammTeil.
      Und die wird per Select case abgefragt. (Das ist übrigens genau das Prinzip von der Statemachine).

      Dann könnte dein Programm von der Struktur her etwa so aussehen.

      BASCOM Source Code: Code-Schema

      1. ' Definieren der Programmteile (Alias Zustände)
      2. Const PRG_TEIL_1 = 10
      3. Const PRG_TEIL_2 = 20
      4. Const PRG_TEIL_3 = 30
      5. Dim ProgrammTeil as Byte ' Variable enthält den auszuführenden Programmteil
      6. ' Hauptschleife
      7. ProgramTeil = PROG_TEIL_1 ' Programmteil festlegen mit dem begonnen wird
      8. Do
      9. Select Case ProgrammTeil ' Abfrage des auszuführenden Programmteils
      10. Case PROG_TEIL_1
      11. ' Hier der Code für Programmteil 1
      12. Case PROG_TEIL_2
      13. ' Hier der Code für Programmteil 2
      14. Case PROG_TEIL_3
      15. ' Hier der Code für Programmteil 3
      16. End Select
      17. Loop
      Display All


      Das ist doch relativ einfach, oder?

      Die Tastenabfrage machst du per Aufruf einer Sub-Routine entweder vor oder nach dem Select-Case Teil.
      Die ermittelte Tasten speicherst du in einer separaten Variable.

      In den oben gegliederten Programmteilen kannst du dann diese Tasten aus der Variable abfragen und darauf reagieren.
      Um die Ausführung von einem aktuellen auf einen anderen Programmteil zu ändern wird einfach die Variable "ProgrammTeil" auf die entsprechende Konstante des gewünschten Programmteils gesetzt.

      Was du jetzt nicht machen darfst, ist in den Programmteilen PROG_TEIL_x Wait verwenden.
      Kurze Waits bis zu 50ms sollten kein Problem sein, aber längere solltest du unbedingt vermeiden.

      Verwende stattdessen einen selbst gebauten Timeout.
      Wie das funktioniert ist auch einfach.

      Richte einen Timer ein, der alle 0,1 Sekunden eine ISR aufrift.
      In dieser lässt du eine Variable herunter zählen bis auf 0. In deinem Programm kannst du dann die Variable auf den Wert 0 abprüfen. Wenn das der Fall ist,
      dann ist der Timeout abgelaufen.

      Etwa so:

      BASCOM Source Code

      1. Dim MyTimeout as Word
      2. ISR_10Hz:
      3. If MyTimeout >= 1 then Decr MyTimeout
      4. Return
      Wenn du dann z.B. 10 Sekunden warten willst, dann setzt du die Variable MyTimeout auf 100 (Sekunden x10).
      Und in deinem Programmteil brauchst du nur die Variable auf 0 prüfen.

      Dadurch, dass du nicht Wartest, läuft der Code in den einzelnen Programmteilen durch. Und danach wird die Tastenabfrage durchgeführt.
      Da wird nix blockiert.

      Hier nochmal im ganzen zum nachvollziehen

      BASCOM Source Code

      1. ' Definieren der Programmteile (Alias Zustände)
      2. Const PRG_TEIL_1 = 10
      3. Const PRG_TEIL_2 = 20
      4. Const PRG_TEIL_3 = 30
      5. Dim ProgrammTeil as Byte ' Variable enthält den auszuführenden Programmteil
      6. Dim MyTimeout as Word ' eigener Timeout
      7. Dim TastenFlags as Byte ' Hier sind die gedrückten Tasten gespeichert
      8. ' Hauptschleife
      9. ProgramTeil = PROG_TEIL_1 ' Programmteil festlegen mit dem begonnen wird
      10. Do
      11. Select Case ProgrammTeil ' Abfrage des auszuführenden Programmteils
      12. Case PROG_TEIL_1
      13. ' Hier der Code für Programmteil 1
      14. MyTimeout = 100 ' Timeout für neuen Zustand setzen
      15. ProgrammTeil = PROG_TEIL_2 ' jetzt soll Programmteil 2 ausgeführt werden
      16. Case PROG_TEIL_2
      17. ' Hier der Code für Programmteil 2
      18. If MyTimeout = 0 then ' Hier wird gewartet
      19. ' Timeout (Wartezeit) abgelaufen
      20. Programmteil = PROG_TEIL_3
      21. End If
      22. Case PROG_TEIL_3
      23. ' Hier der Code für Programmteil 3
      24. If TastenFlags.1 = 1 then ' Taste 1 gedrückt
      25. ' Hier auf die Taste reagieren.
      26. End If
      27. End Select
      28. Call Tastenabfrage()
      29. Loop
      30. ' Tastenabfrage
      31. Sub Tastenabfrage()
      32. ' Den Code bringst du bestimmt selber zusammen.
      33. ' Die Tasten werden dann in Variable Tastenflags gespeichert.
      34. ' z.B Taste 0 in Bit 0
      35. ' Taste 1 in Bit 1 etc.
      36. End Sub
      37. ISR_10Hz:
      38. If MyTimeout >= 1 then Decr MyTimeout
      39. Return
      Display All
      Im Statemachine Tutorial wird genau8 das gemacht. Allerdings gibt es Routinen zum Setzen und Abfragen der der Zustände.
      Dadurch wird das ganze Komfortabler.

      Hilft dir das weiter?
    • Moin,


      ok, es ist schwierig, wenn man nicht klar ausdrückt, was man machen möchte.
      Ich versuche das mal mit kurzen Worten. 8)
      Ich möchte erklären, warum ich nicht ohne Waits auskomme, oder doch?


      comp_Flieger.jpg


      Wen das interessiert, der findet meinen Baubericht im Wettringer-Modellbauforum.
      Rubrik: Science-Fiction und Raumfahrt „Noch ein X-Wing in 1:72“
      Ich belebe Modelle gerne mit Licht und Sound. Der linke Flieger ist fertig.
      Schaltbild wie im ersten Thread zu sehen.


      Also, konkret:
      Im Bau der rechte Flieger.
      Über fünf Tasten sollen folgende Programmteile ausgeführt werden.
      1.) Programm 1
      2.) Programm 2
      3.) Beleuchtung nur an/aus
      4.) Lautstärke minus
      5.) Lautstärke plus


      Soweit alles klar, damit würde ich mit dem „BASCOM-Quellcode: Code-Schema“ von Mitch64 klarkommen.
      Weiter zum Beispiel zu Programm 1:
      Ich rufe eine Sub (Programm1) auf in der ich
      - Die Beleuchtung auf der Grundplatte einschalte
      - Die Triebwerksbeleuchtung einschalte
      - Jetzt starte ich mittels eines Befehls die Audiosequenz am FD-Player, in der 16 Laser Feuerstöße zu hören sind.
      Sequenzdauer etwa 1,8 - 2 Sekunden.
      Mittels einer weiteren Subroutine werden dazu die Laser über Kreuz ein- bzw. ausgeschaltet.
      (Die Laser werden durch LEDs hinter Acrylglasstäben simuliert,die vorne angeschliffen sind).
      Damit man das optisch und auch in der akustischen Reihenfolge sehen und hören kann,
      muss ich in der Schleife in der die Laser ein/aus-geschaltet werden „Waits“ einfügen. Oder…
      - Sprung aus der Sub zurück in Programm 1
      - Neue Audiosequenz, R2D2 gibt einige Piepstöne ab (etwa 1 Sekunde)
      - Erneuter Start der Sub mit dem Laserfeuer
      - Zum Schluss noch eine Sub mit Audio und Licht über die LWL-Leiter um den Abschuss der Protonentorpedos zu simulieren.
      - Ende Programm 1 nach 6 - 9 Sekunden.


      Die Tasten 1 und 2 sollen zwei verschiedene Programme starten können.


      In der Zeit in der das Programm läuft, möchte ich z.B. gerne die Lautstärke ändern können.
      Während also z.B. das Programm 1 läuft, ist das doch nur möglich, wenn ich die Tastenauswertung über eine ISR auslöse?
      Wahrscheinlich müsste ich die ganze Laustärkenregelung innerhalb der ISR abarbeiten.


      So,jetzt kommt deine „MyTimeout“-Variable ins Spiel. Anstatt der Waits läuft der Zähler dieser Variablen.


      In der Zeit in der die Variable läuft, könnte ich, anstatt den Zähler nur hochlaufen zu lassen,
      die Zeit nutzen um eine Statusabfrage zu machen und z. B. die Lautstärke ändern - Wenn in diesem Timeout dafür genug Zeit bleibt.
      War das so in etwa gedacht?
      Ich muss mir das aber nochmal ganz in Ruhe ansehen.


      Gruß Dieter
    • thor101 wrote:

      Damit man das optisch und auch in der akustischen Reihenfolge sehen und hören kann,
      muss ich in der Schleife in der die Laser ein/aus-geschaltet werden „Waits“ einfügen. Oder…
      Nein musst du nicht.

      Nimm Timeouts anstelle von Waits.

      Teile deine sequenziellen Abläufe in kleine Programmeinheiten (States) auf.
      So kannst du immer von einem zum nächsten State übergehen, ohne dass die Tastenabfrage vernachlässigt wird.

      Übrigens, falls du da von DF-Player mini sprichst.
      Der senden ein Frame, wenn eine wav oder mp3 abgespielt wurde.
      Da braucht man also auch nicht warten, sondern reagiert auf dieses Telegtamm und startet daraufhin das nächste Sound-Event.

      Du solltest dir mal einen Zeitlichen Ablauf machen, was z.B. im Programm1 und 2 jeweils nacheinander abläuft mit Zeiten die das Dauert.
      Da findet sich bestimmt etwas, wie man das dann auf die Reihe kriegt.

      Tasten braucht man meist, wie auch in deinem Fall, auch nicht im Interrupt abfragen. Das kann man in der Hauptschleife machen.
      Das funktioniert aber nur, wenn man keine Waits verwendet. Das ist sowieso Zeit, in der der Controller anderes tun kann.
      Z.B. die seielle prüfen, ob das Frame rein kam etc.

      Auch wenn du jetzt keine Statemachine in dem Sinn verwenden willst, weil dir das zu kompliziert ist, musst du trotzdem auf die Waits verzichten.
      Dann klappt es auch mit der Tastenabfrage.
    • Moin,
      Ich muss gestehen, dass ich das Bascom-Programmieren
      sehr oberflächlich und pragmatisch für den Modellbau betreibe.
      Aus der Standardliteratur zu Bascom die ich benutze,
      habe ich derlei noch nicht gesehen.


      Nachdem ich nun Ihre Vorschläge in Ruhe gelesen und verstanden habe,
      bin ich voll begeistert. a_64_3a718cae
      Ich werde noch einiges lesen und üben müssen;
      aber ich werde meine Programme in der Richtung anpassen und verbessern.

      Danke für Ihre kompetente und geduldige Hilfe! a_42_02cc30b2

      Vielleicht kann ich ja bald mein erstes Programm ohne Waits und mit einer „Statemachine“ hier posten.
      (Mut vorausgesetzt). 8)

      Nochmals ganz herzlichen Dank für diesen bereichernden Wegweiser.

      Gruß Dieter
    • Moin,

      Sorry, ich muss da nochmal nachfragen.
      Die Statemachine ist ja eingehend beschrien,
      aber zum Timeout finde ich keinen richtigen Zugang.


      Ich verstehe leider doch nicht, wie ich dadurch eine Zeit bis zum nächsten Befehl überbrücke,
      irgendwie habe ich das doch falsch verstanden?

      Source Code

      1. Dim MyTimeout as Word
      2. ISR_10Hz:
      3. If MyTimeout >= 1 then Decr MyTimeout
      4. Return

      Wie handhabe ich die ISR konkret, bzw. wodurch und wie wird sie gestartet?
      Stehe da im Moment völlig auf dem Schlauch. ?(


      Wären Sie so freundlich dazu nochmal ein paar erklärende Zeilen zu verfassen.
      DANKE!


      Gruß Dieter
    • thor101 wrote:

      Die Statemachine ist ja eingehend beschrien,
      aber zum Timeout finde ich keinen richtigen Zugang.
      Ja der Timeout wird da nicht beschrieben. Das wäre ja auch ein anderes Thema.

      Die ISR läuft einfach permanent durch.
      Egal, ob man nun gerade einen Timeout braucht oder nicht.

      Der Wert MyTimeout ist dann logisch immer 0.

      Wenn du nun einen Timeout brauchst, musst du den Wert MyTimeout auf den gewünschten Timeout setzen.
      Also wenn die ISR mit 10Hz aufgerufen wird, musst du für einen Timeout von beispielsweise 2 Sekunden einen Wert von 2x10 setzen.
      MyTimeout = 20 ' Timeout 2 Sekunden

      Soweit klar?

      Wenn du das Statemachine-Tutorial gelesen hast,
      wird da ja zum Setzen und auslesen der States je eine Routine verwendet.
      Beim Setzen wird zusätzlich ein Flag gesetzt, das anzeigt, dass der Zustand geändert wurde.

      Und dieses Flag kann man mit der 3. Routine, die in Statemachine.inc abgelegt ist abfragen.

      Wenn du also nun einen Timeout in der Statemachine (oder auch sonstwo) brauchst, kannst du das so machen:

      BASCOM Source Code

      1. Sub ProgrammAblauf1()
      2. ' Der sequenzielle Ablauf der Steuerung läuft als untergeordnete Statemachine ab
      3. Call SetState(ST_PRG_1_STEP_1) ' Programm 1 starten mit Step 1
      4. Do
      5. Call ReadKeys() ' Tasten abfragen
      6. Select Case GetState()
      7. Case ST_PRG_1_STEP_1 ' Step 1
      8. If StateChanged() = TRUE then
      9. Set PIN_BELEUCHTUNG
      10. Set PIN_TRIEBWERK
      11. MyTimeout = 20 ' Nach 20 Sekunden Step 2
      12. End If
      13. If MyTimeout = 0 then Call SetState(ST_PRG_1_STEP_2)
      14. Case ST_PRG_1_STEP_2 ' Step 2 (Kreuzfeuer 16x Laser)
      15. If StateChanged() = TRUE then
      16. ShotCount = 16 ' Schüsse setzen
      17. EndIf
      18. If MyTimeout = 0 then ' Laser aus
      19. Reset PIN_LASER_LINKS
      20. If ShotCount = 0 then ' Alle Schüsse abgefeuert
      21. Call SetState(ST_PRG_1_STEP_2)
      22. Else
      23. Decr ShotCount
      24. End If
      25. ElseIf PIN_DFPLAYER_BUSY = 0 then ' DF-Player kann MP3 abspielen
      26. ' Hier Paket für Laser-Sound an DF-Player senden
      27. ' ...
      28. ' Laser einschalten
      29. Set PIN_LASER_LINKS ' Laser links einschalten
      30. MyTimeout = 2 ' Laser soll 0,2s aufleuchten
      31. End If
      32. Case ST_PRG_1_STEP_2 ' Step 3
      33. End Select
      34. Loop
      35. ' Nach Programm-Ablauf wieder zurück in Ruhestellung
      36. Call setState(ST_IDLE)
      37. End Sub
      Display All