Beiträge von Der Hobbyelektroniker

    Guten Morgen Eberhard


    Dein Programm müsste eigentlich funktionieren.


    Ich verwende die Thonny - Version 3.3.13

    mit 'MicroPython v1.17 on 2021-09-02; ESP32 module with ESP32'


    Das Programm


    entspricht deiner Version.

    Bleibt noch die Datei ssd1306.py. Möglicherweise hat sich da etwas geändert. Du findest meine Version in OLEDTest.zip. ssd1306.py muss auf das Board kopiert werden. Bitte kontrolliere, ob dort die richtige Version vorhanden ist.

    OLEDTest.zip


    Sonst bleibt noch das Heltec - Board. Ich verwende die alte Version. Mit der unmittelbaren Nachfolgeversion sollte es aber auch klappen.


    Das sollte auf dem Display sichtbar sein:


    IMG_0526.jpeg



    Gruss

    René

    Hallo Eberhard,


    es hat sich seither viel in Thonny geändert. Schau dir dazu Lektion 9 an. Auch das ist nicht mehr ganz aktuell, deckt aber den ersten grossen Schritt ab.

    Auch beim Heltec - Board gibt es eine neuere Version. Dazu findest Du etwas in Lektion 10.


    Ich empfehle dir die Installation der neusten Thonny Version (momentan 3.3.13). thonny-esp ist nicht mehr notwendig.

    Esptool solltest du es unter Tools / Manage plug-ins finden. Wenn es nicht in der Liste ist, kannst du esptool im Suchfeld eingeben und Search on PyPi drücken.


    Unter Tools / Options findest du die Einstellungen für den Interpreter. Dort kannst du MicroPython on ESP32 auswählen.


    Gruss

    René

    Hallo Felix


    So richtig ist mir auch nicht klar, wieso es nicht geht.

    Du kannst entweder mit write() / read() oder mit put() / get() arbeiten. Mischen solltest Du es nicht.


    write() und read() arbeiten immer mit exakt 1 Byte.

    Ich verwendet normalerweise aber put() und get(). Dabei hängt die Anzahl geschriebener oder gelesener Bytes von der Variablen ab. Sie muss also zwingend vom selben Typ sein.

    Ein Beispiel:

    Code
    1. void setup() {
    2. Serial.begin(9600);
    3. byte speichern = 5;
    4. byte lesen;
    5. EEPROM.put(0, speichern);
    6. delay(100);
    7. EEPROM.get(0, lesen);
    8. Serial.println(lesen);
    9. }

    Das sollte eigentlich funktioniere, ich habe es aber nicht getestet.

    Eine etwas ausführlichere Erklärung findest Du in Lektion 24 meiner Videoreihe zum Arduino oder im Dokument Zusammenfassung Programmierung.


    Gruss

    René

    Hallo Tom,


    es freut mich natürlich immer, wenn meine Videos gefallen. Insbesondere, wenn der Zuschauer etwas jünger ist. Ich hoffe, dass ich das mit den Leben verständlich genug erklärt und nichts vergessen habe. Sonst einfach nachfragen, ein allfälliger Fehler kann schnell korrigiert werden.


    Ich wünsche euch auf jeden Fall viel Spass.


    Gruss

    René

    Hallo Tom,


    hier ein Vorschlag:

    Die Anzahl Leben sind eine Angelegenheit des Spielers. Also fügen wir in der Spieleklasse eine Variable Leben ein.


    Python
    1. class Spieler(Actor):
    2. def __init__(self, x, y):
    3. super().__init__('run__000')
    4. self.aktiv = False
    5. self.leben = 2 ### Eigenschaft leben erstellen
    6. # Die Grundposition speichern
    7. self.baseX = x
    8. ...

    Beim Start müssen wir jetzt unterscheiden, ob es ein neues Spiel oder nur ein Neustart nach einem Lebensverlust ist. Damit die Methode start() noch wie bisher funktioniert, verwenden wir einen Parameter mit Defaultwert.

    Die Leben werden nur auf 2 gesetzt, wenn es sich um ein vollständig neues Spiel handelt.


    Code
    1. def start(self, restart=False):
    2. # Aufrecht stehender Spieler an der Grundposition
    3. if not restart: self.leben = 2 # Bei einem neuen Spiel auf 2 setzen
    4. self.x = self.baseX
    5. ...


    In kollision() wird ein Leben weggenommen. Dabei muss getestet werden, ob aller Leben aufgebraucht sind.

    Wenn nicht, könnte man hier einen Effekt einbauen. Ich verzichte aber darauf und setze darum nur pass ein.


    Code
    1. def kollision(self):
    2. self.leben -= 1 # Leben wegnehmen
    3. if self.leben:
    4. # Es sind noch Leben vorhanden
    5. pass
    6. else:
    7. # Alle Leben aufgebraucht
    8. self.angle = -90 # Spierler fällt um
    9. self.y = self.baseY # er liegt am Boden
    10. self.stopp()


    Damit hätte der Spieler alle notwendigen Fähigkeiten.

    Der Rest wird im Hauptprogramm erledigt.


    Zuerst müssen wir in zeichne_spielfeld() das Anzeigen der Leben vorbereiten.


    Code
    1. def zeichne_spielfeld(self):
    2. ...
    3. screen.draw.text(str(self.punkte), (WIDTH - 80, 10), fontsize=60, color=farbe_schrift)
    4. screen.draw.text(str(self.spieler.leben), (WIDTH - 80, 80), fontsize=60, color=farbe_schrift) ###
    5. if self.finished:
    6. ...

    Jetzt müssen wir in update() noch den Spielverlauf beeinflussen.

    Wenn eine Kollision erfolgt ist, teilen wird das dem Spieler mit spieler.kollision() mit.

    Danach wird getestet, ob noch Leben vorhanden sind.

    Wenn ja, lassen wir die Hindernisse verschwinden, starten den Spieler neu (mit der Option restart=True) und zeichnen alles neu. Danach brechen wir die Schleife ab, da jetzt alle Hindernisse verschwunden sind.


    Ich hoffe, das war nicht all zu verwirrend.

    Es ist nur eine Minimallösung. Man könnte noch visuelle Effekte oder Töne beim Verlust eines Lebens einbauen.


    Gruss

    René

    Hallo Felix


    Ein Original - Arduino sollte eigentlich hier keine Probleme haben. delay() hat auch keinen Einfluss, da während der Datenübertragung der Sketch auf dem Arduino nicht läuft. Ich tippe eher auf eine äussere Ursache. Den USB-Port zu wechseln, wie es Reiner vorschlägt, ist immer eine gute Idee. Ein anderer Schwachpunkt ist das USB Kabel. Wenn Du ein anderes hast, einfach einmal wechseln. Ein Fehler, der während des Hochladens auftritt, kann durchaus durch ein schlechtes Kabel verursacht werden.


    Allerdings deuten die Fehlermeldungen 'Port busy' und 'Fehler beim Öffnen' auf ein anderes Problem hin. Kontrolliere auf deinem Computer, ob da keine Software ist, die sich ungefragt sämtliche seriellen Ports schnappt. Das könnte auch eine Sicherheitssoftware sein, die Schadcode über die serielle Schnittstelle abwehren möchte.


    Tatsächlich kann auch der Arduino, beziehungsweise das Programm, das darauf läuft, Probleme verursachen. Wenn sehr viele Daten per Serial.print auf den seriellen Monitor ausgegeben werden, kann das zu einer Blockade führen. Allerdings hatte ich das Problem mit einem original Arduino UNO noch nie.


    Gruss

    René

    Hallo Jürgen,


    das kann gut sein. Die Liste zeigt nur einen kleinen Teil der verfügbaren Module. Du hast aber ein Suchfeld, in das du pgzero eingeben kannst. Dann sollte Thonny es finden. Es sollten eigentlich alle Module auffindbar sein, die in einer 'richtigen' Installation mit pip installiert werden können. Die Suche läuft über den Python Package Index PyPI ( https://pypi.org ).


    Gruss

    René

    Ein ähnliches 'Detektor - Radio' hatte ich auch. Allerdings schon etwas moderner. Anstelle des aufgesteckten Kristalldetektors war eine handelsübliche Germaniumdiode eingebaut. Tagsüber war Radio Beromünster der einzige Sender, der richtig reinkam. Am Abend waren die Ausbreitungsbedingungen für Mittelwelle besser, da konnte man auch andere Stationen empfangen.

    Hallo bobinger,


    start_zeit wird in anzeigen() nicht ausgegeben. Da aktiv ebenfalls auf False gesetzt wird, erfolgt keine neue Berechnung der angezeigten Felder. Es wird auf start gewartet und immer die unveränderten Werte von total_zeit, zwischen_zeit und etappen_zeit angezeigt. Der Wert von start_zeit ist im nicht aktiven Zustand irrelevant.


    Wenn du einen vollen Rese machen möchtest, kannst du die Werte von total_zeit, zwischen_zeit und etappen_zeit auf 0 setzen. start_zeit kannst du lassen wie es ist, diese Variable wird mit start sowieso neu gesetzt.


    Gruss

    René

    Hallo The Ibis,


    leider ist die Thread - Unterstützung in Micropython wesentlich schwächer als im normalen Python. Du könntest eine eigene Thread - Klasse von _thread ableiten und die Verwaltung der Liste innerhalb deiner Klasse vornehmen. Dann könnte eine join() - Methode problemlos implementiert werden. Das Prinzip bleibt aber das gleiche, es ist dann nur etwas 'schöner' formuliert.


    Gruss

    René

    Hallo Olaf,

    es freut mich, dass Dir meine Videos gefallen. Vielen Dank!


    Ich musste auch eine Weile überlegen, bevor ich das Problem eingrenzen konnte. Das hat nichts mit der Zeile text[0] = h zu tun. Es ist tatsächlich egal, wo diese steht. Das Problem liegt beim Aufruf:


    showValues(a, s) funktioniert genauso wie changeValue(s).

    In beiden Fällen wird in s das erste Zeichen ausgetauscht.


    Der Absturz erfolgt in showValues(a, "Hallo);

    Du versuchst da das erste Zeichen des übergebenen Strings "Hallo" auszutauschen. Das ist aber keine Variable.

    Der Versuch changeValue("Hallo") aufzurufen, wird ebenfalls zu einem Absturz führen.


    Wieso es zum Absturz kommt, kann ich auch nicht sagen. Dazu kenne ich das Speichermanagement von C zu wenig gut. Ich nehme an, dass der Compiler für den direkt übergebenen String keinen entsprechenden Speicherplatz reserviert.


    Gruss

    René

    Hallo zusammen


    Auf der einen Seite finde ich die Idee sehr gut. Da könnten Leute miteinander diskutieren, die auf einem ähnlichen Niveau sind. Da jeder seine eigenen Schwerpunkte hat, könnten sie sich trotzdem gut ergänzen. Das erfordert aber genügend Leute, die da mitmachen. Das Forum ist recht klein und ich bin nicht sicher, ob sich da genügend aktive Teilnehmer zusammenfinden.


    Auf der anderen Seite könnte da eigentlich jeder mitmachen. Auch ein Fortgeschrittener ist in einigen Teilgebieten Anfänger. Ich lese hier oft Beiträge, die mir das Gefühl geben, ein blutiger Anfänger zu sein.


    Eigentlich hatte ich immer das Gefühl, dass dieses Forum anfängerfreundlich ist. Bisher ist auf beinahe jede Frage eine freundliche und ausführliche Antwort gegeben worden. Die Diskussionen sind aber oft auf einem sehr hohen Niveau, so dass ich mir schon vorstellen kann, dass Anfänger dadurch abgeschreckt werden. Deshalb werde mir überlegen, wie ich so etwas einrichten kann. Technisch ist das kein Problem, die Frage ist, wie man das präsentiert, dass sich die richtigen Leute angesprochen fühlen.


    Viele Grüsse

    René

    Hallo Kai


    Ja, ich kenne den Heise - Artikel. Er hat mir auch sehr geholfen. Allerdings ist der Sprung von simplen Python - Beispiel zur in C geschriebenen Ampelsteuerung sehr gross. Ich habe inzwischen noch andere Quellen gefunden, allerdings wird man vor lauter Informationen beinahe erschlagen. Trotzdem hat man oft Mühe, genau das Detail zu finden, das man gerade benötigt.


    Hier eine Sammlung der Links, die ich gefunden habe:


    https://www.mathema.de/blog/ra…are-zwischen-cpu-und-fpga

    https://hackspace.raspberrypi.…d-programmable-i-o-part-2

    https://hackspace.raspberrypi.org/articles/pio1


    https://datasheets.raspberrypi…2040/rp2040-datasheet.pdf

    https://datasheets.raspberrypi…spberry-pi-pico-c-sdk.pdf

    https://datasheets.raspberrypi…ry-pi-pico-python-sdk.pdf


    Ich bin gespannt, ob sich die Programmierung der Statemachines in Makerkreisen durchsetzt. Es könnte auch sein, dass alle das einfach ignorieren. Das erinnert mich etwas an die Geschichte mit dem PSOCs von Cypress. Die wären eigentlich hochinteressant. Aber es war für viele Leute absolutes Neuland und die meisten haben sich davon ferngehalten.


    Gruss

    René

    Hallo Hans,


    da hast Du natürlich recht. Allerdings ist das nur ein zusätzliches Feature, das man nicht zwingend benutzen muss. Wenn man mit den normalen Funktionen eines Mikrokontrollers zufrieden ist, erfolgt die Programmierung wie bei einem Arduino oder ESP32.


    Anfänger sollten sich nicht mit PIO - Programmierung beschäftigen. Es ist aber eine gute Idee, mit Micropython einzusteigen. Python ist sehr viel einfacher zu erlernen als C oder C++.


    Gruss

    René

    Teil 4


    Geblinkt wird mit dem set() - Befehl. Das ist einer der 9 Befehle, die die Statemachine kennt. Wie kann man jetzt gleichzeitig mehrere Pins ein- oder ausschalten?


    Code
    1. set(pins, 0b1101)[31] # Pins 2, 4, 5 ein, Pin 3 aus
    2. set(pins, 0b0010)[31] # Pin 3 ein, Pins 2, 4, 5 aus

    Im Kopf der Funktion haben wir mit set_init den Ausgangszustand für 4 Pins festgelegt. Dadurch können wir mit set() 4 Pins ansprechen. Der erste Pin liegt bei set_base (2) und geht bis set_base + 3 (5).

    Mit set() können wir diese Pins ansprechen. Jedes gesetzte Bit setzt den Pin auf HIGH, ein Bitwert von 0 setzt ihn auf LOW. Daher leuchten mit 0b1101 die Pins 2, 4, und 5, während Pin 3 dunkel bleibt.


    Mit set(pins, 0b0010) werden die Pins 2,4,5 auf LOW und Pin 3 auf HIGH geschaltet.


    Jeder set() - Befehl dauert 1 Zyklus. In eckigen Klammern geben wir eine weitere Verzögerung um 31 Zyklen an. Damit benötigt die Instruktionszeile 32 Zyklen.

    Wir arbeiten mit einer Frequenz von 2000 Hz. Somit dauert ein Zyklus 0.5 ms.


    Diese 32 Zyklen reichen nicht, um das Blinken sichtbar zu machen. Aus diesem Grund werden zusätzlich nop() - Operationen eingefügt. Ein nop() [31] braucht 1 + 31 = 32 Zyklen. Die zusätzliche Verzögerung kann maximal 31 Zyklen betragen, daher benötigen wir mehrere nops.


    Code
    1. ...
    2. nop() [31]
    3. nop() [31]
    4. ...

    Wir haben in unserer Funktion folgende Zyklen:

    1 + 31 = 32

    1 + 31 = 32

    1 + 31 = 32

    1 + 3 = 4

    1 + 31 = 32

    1 + 31 = 32

    1 + 31 = 32

    1 + 3 = 4


    Wir haben also ein Total von 200 Zyklen. Das dauert 200 x 0.5 ms = 100 ms.

    Das kann mit dem Oszilloskop verifiziert werden.



    Oszi.jpeg


    Solange die Statemachine aktiv ist, wird die Endlosschleife unabhängig von der CPU ausgeführt. Sie funktioniert auch während eines aktiven sleep() - Befehls oder wenn das Python - Programm beendet wurde.


    Die Statemachine kann aber auch durch einen Befehl deaktiviert werden:


    Code
    1. sm.active(1)
    2. time.sleep(3)
    3. sm.active(0)


    Das waren nur zwei Befehle von 9. Trotzdem lässt sich damit schon einiges machen.

    Wir haben hier nur an der Oberfläche gekratzt, PIO - Programmierung kann sehr anspruchsvoll sein. Ich blicke da auch noch nicht überall durch.

    Wenn es dann soweit ist, erstelle ich vielleicht ein Video dazu.

    Teil 3


    Die Funktion blink() definiert das Assembler - Programm für die Statemachine.


    Bereits die erste Zeile ist sehr speziell. Man nennt dieses Konstruct einen Decorator. Das gehört zur fortgeschrittenen Python - Programmierung und soll hier nicht näher besprochen werden. In unserem Fall sorgt er dafür, dass der nachfolgende Code als Assembler - Programm an die Statemachine übermittelt wird. Dabei initialisieren wir auch die verwendeten Pins. Ich gebe hier mit set_init an, welche Pins ich für den set - Befehl verwenden möchte und wie ihr Anfangszustand ist. Die üblichen Beispiele übergeben hier nur einen Zustand, verwenden also auch nur einen Ausgangspin.

    Ich übergebe hier ein Tupel von 4 identischen Zuständen, daher kann ich 4 Pins ansprechen.


    Wer das noch nicht kennt:

    Code
    1. (PIO.OUT_LOW,)*4) entspricht (PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW, PIO.OUT_LOW)

    Ist also ein normales Tupel mit 4 Elementen.


    Innerhalb der Funktion blink() ist eine Endlosschleife definiert. Sobald der Pseudobefehl wrap() erreicht wird, startet die Funktion wieder bei wrap_target(). Das benötigt keinen Taktzyklus.


    Code
    1. def blink():
    2. wrap_target()
    3. ...
    4. wrap()

    Das eigentliche Blinken erfolgt mit dem set() - Befehl. Doch das ist ein Thema für den nächsten Beitrag.

    Teil 2


    Wenn wir das Python - Programm betrachten sticht sofort die Funktion blink() ins Auge. So etwas hast du vielleicht noch nie gesehen. Aber schön der Reihe nach. Wir beginnen mit dem Start des Programms.


    Der unterste Pin ist Pin 2. Den erstellen wir ganz normal.

    Im Modul rp2 findet man die Klasse StateMachine. Davon muss eine Instanz erstellt werden.


    Code
    1. sm = rp2.StateMachine(0, blink, freq=2000, set_base=Pin(2))

    Dabei ist 0 die Statemachine - Nummer (0 .. 7) und blink die Funktion.

    freq ist die Frequenz in Hz. Ohne Angabe wird der Systemtakt (125 MHz) verwendet. Das Minimum beträgt 1908 Hz. Das ist abhängig vom Systemtakt des RP2040. Dieser beträgt beim Pi Pico 125 MHz.

    set_base definiert den ersten Pin, der für set() verwendet wird.


    Wir erstellen also eine Statemachine 0 (möglich wären 0 .. 7). Diese arbeitet mit einer Taktfrequenz von 2000 Hz. Die Funktion blink definiert den Code, den die Statemachine ausführen soll. Die unterste Pinnummer, die wir ansprechen möchten, ist 2. Die Statemachine muss dann nur noch aktiviert werden.


    Code
    1. pin2 = Pin(2)
    2. sm = rp2.StateMachine(0, blink, freq=2000, set_base=pin2)
    3. sm.active(1)


    Das Python - Programm ist damit beendet. Die Statemachine läuft aber unbeirrt weiter.

    Die Funktion blink() betrachten wir im nächsten Beitrag.