Script toggln mit Taster Pi-Pico

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Golfer83
User
Beiträge: 7
Registriert: Sonntag 19. März 2023, 12:12

Hi, ich habe ein kleines Projekt mit einem Raspberry PI Pico.
ich lasse mir auf einem 1.3" Oled die Uhrzeit mit DS3231 anzeigen und habe 2 Temperaturfühler DS18B20 verbaut.
nun möchte die Anzeigen mittels Taster "umschalten" was auch klappt aber nur 8x und dann kommt folgender Fehler:

RuntimeError: maximum recursion depth exceeded

Das "umschalten erfolgt in der while Schleife:

Code: Alles auswählen

 if button_pin.value() == False and not button_down:
        oled.fill(0)
        exec(open("KW_Temp.py").read())
        break
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Wie kommst du denn darauf das so zu lösen?

Ich bin in der micropython-Welt nicht vertaut, aber

a) importier das Modul und rufe die Funktion daraus auf
oder
b) überleg dir, ob überhaupt ein eigenes Modul nötig ist.

While-Schleifen in der micropython-Umgebung sind auch nicht ganz trivial. Das kann man aber erst sehen, wenn du sie auch zeigst.
Zuletzt geändert von sparrow am Sonntag 19. März 2023, 12:38, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Rekursion als Ersatz für Schleifen ist selten eine gute Idee, vor allem nicht bei Micro-Processoren, die nur wenig Speicher haben. Benutzt eine Schleife.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

du könntest auch auf eine steigende oder fallende Flanke des Tasters reagieren:
https://docs.micropython.org/en/latest/ ... ne.Pin.irq

Wenn du dann noch das Problem hast, dass der Taster prellt, dann findest du hier Hilfe:
viewtopic.php?p=409121#p409121

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Golfer83
User
Beiträge: 7
Registriert: Sonntag 19. März 2023, 12:12

Habe alles nochmal überarbeitet und es sieht jetzt so aus:

Code: Alles auswählen

from machine import Pin, SoftI2C
from SH1106 import SH1106_I2C
#from SSD1306 import SSD1306_I2C
#import framebuf
from time import sleep
import sev2seg32
import writer
import urtc
import utime
import machine, onewire, ds18x20, time
from ds18x20 import DS18X20
from onewire import OneWire

ds1_pin = 10 #KüWa-Temp
ds2_pin = 8 #Außen-Temp
ds1 = DS18X20(OneWire(machine.Pin(ds1_pin)))
ds2 = DS18X20(OneWire(machine.Pin(ds2_pin)))
roms1 = ds1.scan()
roms2= ds2.scan()

button_presses = 0 # the count of times the button has been pressed
last_time = 0 # the last time we pressed the button

builtin_led = machine.Pin(25, Pin.OUT)
button_pin = machine.Pin(7, machine.Pin.IN, machine.Pin.PULL_UP)

def button_pressed_handler(pin):
    utime.sleep_ms(150)  			#Entrpellen
    global button_presses, last_time
    new_time = utime.ticks_ms()
    if (new_time - last_time) > 200: 
        button_presses +=1
        last_time = new_time

button_pin.irq(trigger=machine.Pin.IRQ_RISING, handler = button_pressed_handler)

old_presses = 0

i2c=SoftI2C(sda=Pin(0), scl=Pin(1))
rtc = urtc.DS3231(i2c)
oled = SH1106_I2C(128, 64, i2c, res=None, addr=0x3C) # Init oled display
oled.rotate(180)
oled.fill(0)

while button_presses == 0:
    oled.fill(0)
    datetime = rtc.datetime()
    font_writer = writer.Writer(oled, sev2seg32)
    #Uhrzeit
    font_writer.set_textpos(20, 30)
    if (datetime.hour) < 10:
        font_writer.printstring("0"+str(datetime.hour))
    else:
        font_writer.printstring(str(datetime.hour))
    font_writer.set_textpos(73, 30)
    if (datetime.minute) < 10:
        font_writer.printstring("0"+str(datetime.minute))
    else:
        font_writer.printstring(str(datetime.minute))
    
    font_writer.set_textpos(60, 23)
    font_writer.printstring(":")
    
    if (datetime.day) < 10:
        oled.text("0",3,7)
    oled.text(str(datetime.day) + " / " + str(datetime.month) +  " / " + str(datetime.year),14,7)
    
    if (datetime.second) < 10:
        oled.text("0"+str(datetime.second),112,52)
    oled.text(str(datetime.second),112,52)
    
    oled.show()
    sleep(1)
    # only print on change in the button_presses value
    if button_presses != old_presses:
        print(button_presses)
        builtin_led.toggle()
        old_presses = button_presses
        while button_presses == 1:
        #if button_presses == 1:
            ds1.convert_temp()
            time.sleep_ms(750)
            for rom in roms1:
                oled.fill(0)
                oled.text("Kuehlwassertemp.",2,10)
                font_writer = writer.Writer(oled, sev2seg32)
                font_writer.set_textpos(24, 33)
                font_writer.printstring(str("%3.1f"%ds1.read_temp(rom))) #3 stellen vorkomma, 1 stelle nachkomma
                font_writer.set_textpos(90, 33)
                font_writer.printstring("C")
                oled.text("O",82,38)
                oled.show() 
                sleep(3)
        while button_presses == 2:        
        #if button_presses == 2:
            ds2.convert_temp()
            time.sleep_ms(750)
            for rom in roms2:
                oled.fill(0)
                oled.text("Aussentemperatur",0,10)
                font_writer = writer.Writer(oled, sev2seg32)
                font_writer.set_textpos(24, 33)
                font_writer.printstring(str("%3.1f"%ds2.read_temp(rom))) #3 stellen vorkomma, 1 stelle nachkomma
                font_writer.set_textpos(90, 33)
                font_writer.printstring("C")
                oled.text("O",82,38)
                oled.show() 
                sleep(3)
        while button_presses == 3:
            button_presses = 0
Es funktioniert zwar aber manchmal eben nicht ....
Taster mit Pull-UP 10K Widerstand versehen, Leitung der DS18B20 Temp.-Sensoren doppelt geschirmt.

Vll. hat einer ne Idee woran es klemmen könnte.

Danke
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

was funktioniert denn nicht? Das drücken des Tasters während der Code vielleicht zufällig im 'sleep' einer Schleife hängt?


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Golfer83
User
Beiträge: 7
Registriert: Sonntag 19. März 2023, 12:12

Also wenn ich den taster betätige springt er auch zur nächsten Schleife aber manchmal aben nicht und es wird die "Hauptschleife" abgespielt welches die Uhr ist und es reagiert kein Tastendruck. Muss dann immer den Stecker ziehen und wieder einstecken was sich doof wärend der Fahrt macht :-/
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hm dann müsste der Thread von IRQ eine Exception geworfen haben, damit der Rest aber noch weiterläuft? Aber da antwortet bestimmt noch jemand mit mehr Erfahrung.
Sonst müsstest du das Ganze am PC laufen lassen und schauen ob du Fehler bekommst? Ansonsten gibt es auch eine kleine Platine mit SD-Karten-Slot , darauf könnte man eventuell Fehler loggen. Aber vielleicht sieht auch einer noch den Fehler.


Ich hatte gerade ein paar Minuten Zeit und habe dein Code mal etwas umstrukturiert(in der Hoffnung, mir würde etwas unlogisches auffallen), ich hoffe die Funktion ist noch gleich.
Das hat sich leider etwas länger gezogen als gedacht, daher poste ich jetzt einfach mal meinen Zwischenstand, der sicherlich noch optimiert werden kann und ganz ungetestet ist:

Code: Alles auswählen

from time import sleep, sleep_ms, ticks_diff, ticks_ms

import machine
import sev2seg32
import urtc
import writer
from ds18x20 import DS18X20
from machine import Pin, SoftI2C
from onewire import OneWire
from SH1106 import SH1106_I2C

KUELWASSER_PIN = 10
AUSSEN_TEMPERATUR_PIN = 8
LED_PIN = 25
BUTTON_PIN = 7
SDA_PIN = 0
SCL_PIN = 1
# In millisecond
BOUNCE_TIME = 100


class InfoSystem:
    def __init__(self, display, rtc, led, kuelwasser_sensor, aussen_temperatur_sensor):
        self.display = display
        self.clock = rtc
        self.led = led
        self.kuehlwasser_sensor = kuelwasser_sensor
        self.aussen_temperatur_sensor = aussen_temperatur_sensor
        self.button_state = 0
        self.change_infosystem = None
        self.timestamp = ticks_ms()

    def update_button_state(self):
        while not ticks_diff(ticks_ms(), self.timestamp) >= BOUNCE_TIME:
            pass
        self.timestamp = ticks_ms()
        self.led.toggle()
        self.button_state += 1 if self.button_state < 3 else 0
        self.change_infosystem = True

    def show_date_time(self):
        while True:
            self.display.fill(0)
            datetime = self.clock.datetime()
            font_writer = writer.Writer(self.display, sev2seg32)
            font_writer.set_textpos(20, 30)
            font_writer.printstring("{:02d}".format(datetime.hour))
            font_writer.set_textpos(73, 30)
            font_writer.printstring("{:02d}".format(datetime.minute))
            font_writer.set_textpos(60, 23)
            font_writer.printstring(":")
            self.display.text(
                "{:02d} / {:02d} / {}".format(
                    datetime.day, datetime.month, datetime.year
                ),
                14,
                7,
            )
            self.display.text("{:02d}".format(datetime.second), 112, 52)
            self.display.text(str(datetime.second), 112, 52)
            self.display.show()
            if self.change_infosystem:
                self.change_infosystem = False
                break
            sleep(1)

    def show_cooling_water(self):
        while True:
            self.update_display(self.kuehlwasser_sensor, "Kuehlwassertemperatur")
            if self.change_infosystem:
                self.change_infosystem = False
                break

    def show_outside_temperature(self):
        while True:
            self.update_display(self.aussen_temperatur_sensor, "Aussentemperatur")
            if self.change_infosystem:
                self.change_infosystem = False
                break

    def update_display(self, sensor, description):
        sensor.convert_temp()
        sleep_ms(750)
        for rom in sensor.scan():
            self.display.fill(0)
            self.display.text(description, 0, 10)
            font_writer = writer.Writer(self.display, sev2seg32)
            font_writer.set_textpos(24, 33)
            font_writer.printstring("{:.3f}".format(sensor.read_temp(rom)))
            font_writer.set_textpos(90, 33)
            font_writer.printstring("C")
            self.display.text("O", 82, 38)
            self.display.show()
            sleep(3)


def main():
    kuehlwasser_sensor = DS18X20(OneWire(machine.Pin(KUELWASSER_PIN)))
    aussen_temperatur_sensor = DS18X20(OneWire(machine.Pin(AUSSEN_TEMPERATUR_PIN)))

    builtin_led = machine.Pin(LED_PIN, Pin.OUT)
    i2c = SoftI2C(sda=Pin(SDA_PIN), scl=Pin(SCL_PIN))
    rtc = urtc.DS3231(i2c)
    oled = SH1106_I2C(128, 64, i2c, res=None, addr=0x3C)
    oled.rotate(180)
    oled.fill(0)
    info_system = InfoSystem(
        oled, rtc, builtin_led, kuehlwasser_sensor, aussen_temperatur_sensor
    )
    button = machine.Pin(BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)
    button.irq(trigger=machine.Pin.IRQ_RAISING, handler=info_system.update_button_state)
    while True:
        if info_system.button_state == 0:
            info_system.show_date_time()
        elif info_system.button_state == 1:
            info_system.show_cooling_water()
        elif info_system.button_state == 2:
            info_system.show_outside_temperature()


if __name__ == "__main__":
    main()
Ich hab mit dem Sensor leider keine Erfahrung, und habe fast über all deine 'sleep' 's mal drin gelassen. Die Entprellzeit kannst du jetzt über eine Konstante einstellen. Ich will den IRQ nicht durch ein 'sleep' unterbrechen, sondern ich will das alles registriert wird, aber in einem bestimmten Zeitraum nichts passiert.

Mal zusammen gefasst, man will keine keine globalen Variabeln, das macht ein Programm sehr unübersichtlich, schlecht wartbar und bei größeren Programmen wird man bei der Fehlersuche verrückt. Wenn man sich einen Zustand merken will, dann braucht man eine Klasse.
Code wiederholt man nicht gern, sondern steckt den lieber in Funktionen und ruft die mit passenden Argumenten auf.
Strings puzzelt man nicht mit '+' zusammen, MP hat die 'format'-Methode, da kann man die Ausgabe gleich mit führender 0 formatieren, dann fällt deine Abfrage weg. Ich bin mir nicht sicher, aber ich glaube aktuelle MP-Versionen haben sogar 'f'-Strings.
Wenn du Namen sprechend wählst, dann sparst du dir die Kommentare, die den Namen erklären.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das Problem wird ein prellender Taster sein, der dazu führt, dass die viel zu restriktive Logik, die auf 3 prüft, nicht mehr greift. Weil ein physikalischer Druck auf den Taster *zwei* Additionen ausgelöst hat. Und schon steht das Ding bei 4.

Dennis Code ist anders kaputt, er hört auf zu inkremetieren.

Code: Alles auswählen

self.button_state = (self.button_state + 1) % 3
ist die richtige Vorgehensweise.

Das ganze Geschleife und gewarte ist alles redundant. Statt kompliziert in while schleifen zu stecken, die dann innerhalb eine abbruchbedingung und Wartezeit haben, macht man *eine* Hauptschleife, die je nach Zustand verzweigt. Und einmal wartet.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

ach Mist, klar, bei mir bleibt 'button_state' bei 3 hängen, weil ich immer 0 addiere, anstatt auf 0 zurück setzt 🤦‍♂️
Danke für die Verbesserung.

Code: Alles auswählen

from time import sleep, sleep_ms, ticks_diff, ticks_ms

import machine
import sev2seg32
import urtc
import writer
from ds18x20 import DS18X20
from machine import Pin, SoftI2C
from onewire import OneWire
from SH1106 import SH1106_I2C

KUELWASSER_PIN = 10
AUSSEN_TEMPERATUR_PIN = 8
LED_PIN = 25
BUTTON_PIN = 7
SDA_PIN = 0
SCL_PIN = 1
# In millisecond
BOUNCE_TIME = 100


class InfoSystem:
    def __init__(self, display, rtc, led, kuelwasser_sensor, aussen_temperatur_sensor):
        self.display = display
        self.clock = rtc
        self.led = led
        self.kuehlwasser_sensor = kuelwasser_sensor
        self.aussen_temperatur_sensor = aussen_temperatur_sensor
        self.button_state = 0
        self.change_infosystem = None
        self.timestamp = ticks_ms()

    def update_button_state(self):
        while not ticks_diff(ticks_ms(), self.timestamp) >= BOUNCE_TIME:
            pass
        self.timestamp = ticks_ms()
        self.led.toggle()
        self.button_state = (self.button_state + 1) % 3
        self.change_infosystem = True

    def show_date_time(self):
        self.display.fill(0)
        datetime = self.clock.datetime()
        font_writer = writer.Writer(self.display, sev2seg32)
        font_writer.set_textpos(20, 30)
        font_writer.printstring("{:02d}".format(datetime.hour))
        font_writer.set_textpos(73, 30)
        font_writer.printstring("{:02d}".format(datetime.minute))
        font_writer.set_textpos(60, 23)
        font_writer.printstring(":")
        self.display.text(
            "{:02d} / {:02d} / {}".format(
                datetime.day, datetime.month, datetime.year
            ),
            14,
            7,
        )
        self.display.text("{:02d}".format(datetime.second), 112, 52)
        self.display.text(str(datetime.second), 112, 52)
        self.display.show()

    def show_cooling_water(self):
        self.update_display(self.kuehlwasser_sensor, "Kuehlwassertemperatur")

    def show_outside_temperature(self):
        self.update_display(self.aussen_temperatur_sensor, "Aussentemperatur")

    def update_display(self, sensor, description):
        sensor.convert_temp()
        sleep_ms(750)
        for rom in sensor.scan():
            self.display.fill(0)
            self.display.text(description, 0, 10)
            font_writer = writer.Writer(self.display, sev2seg32)
            font_writer.set_textpos(24, 33)
            font_writer.printstring("{:.3f}".format(sensor.read_temp(rom)))
            font_writer.set_textpos(90, 33)
            font_writer.printstring("C")
            self.display.text("O", 82, 38)
            self.display.show()
            if self.change_infosystem:
                break
            sleep(3)


def main():
    kuehlwasser_sensor = DS18X20(OneWire(machine.Pin(KUELWASSER_PIN)))
    aussen_temperatur_sensor = DS18X20(OneWire(machine.Pin(AUSSEN_TEMPERATUR_PIN)))

    builtin_led = machine.Pin(LED_PIN, Pin.OUT)
    i2c = SoftI2C(sda=Pin(SDA_PIN), scl=Pin(SCL_PIN))
    rtc = urtc.DS3231(i2c)
    oled = SH1106_I2C(128, 64, i2c, res=None, addr=0x3C)
    oled.rotate(180)
    oled.fill(0)
    info_system = InfoSystem(
        oled, rtc, builtin_led, kuehlwasser_sensor, aussen_temperatur_sensor
    )
    button = machine.Pin(BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)
    button.irq(trigger=machine.Pin.IRQ_RAISING, handler=info_system.update_button_state)
    while True:
        if info_system.button_state == 0:
            info_system.show_date_time()
        elif info_system.button_state == 1:
            info_system.show_cooling_water()
        elif info_system.button_state == 2:
            info_system.show_outside_temperature()
        sleep_ms(500)


if __name__ == "__main__":
    main()
Wieviele 'roms' werden denn da in der 'for'-Schleife gelesen? Gibt es da überhaupt mehrere?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Dennis89: die while-Schleife in update_button_state ist falsch, das ist ja nur ein umständlich geschriebenes sleep.
Also, statt zu warten reagiert man sofort und ignoriert weitere Signale, die danach innerhalb der Bound-Time auftreten:

Code: Alles auswählen

    def update_button_state(self):
        now = ticks_ms()
        if self.lasttime + BOUNCE_TIME > now:
            return
        self.lasttime = now
        self.led.toggle()
        self.button_state = (self.button_state + 1) % 3
        self.change_infosystem = True
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

da habe ich mich ja wieder von meiner besten Seite gezeigt.
Dir auch danke für die Verbesserung.

Ich vermute mal, dass der Code für die zwei Sensoren dieser hier ist. 'scan' gibt eine Liste mit den Geräten zurück, die an den Bus angeschlossen sind. Hier haben wir zwei Sensoren, aber die laufen nicht über die gleiche Verbindung(?). Dann kommt immer ein Gerät zurück und wir brauchen keine 'for'-Schleife und die flag zum abbrechen der Schleife würde auch wegfallen. Der verlinkte Code bietet auch noch eine "Single"-Klasse an, die kann man nutzen wenn nur ein Gerät am Bus hängt, das wäre, falls meine Aussage richtig ist, hier doch perfekt. Oder?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Golfer83
User
Beiträge: 7
Registriert: Sonntag 19. März 2023, 12:12

Hallo danke für das zahlreiche Feedback. Dennis sein Programm läuft so "halb" sobald ich den Taster betätige kommt ein Fehler. Screenshot des Fehlers kommt später.

Weiterhin habe ich herausfinden können das der Taster arg prellt aber nur wenn der Motor des Autos läuft und der Counter weit über 3 geht und dann geht natürlich gar nix mehr. Habe die letzte Zeile mit >=3 angepasst aber die Anzeige wechselt von allein hin und her.

Werde die Tasterleitung schirmen evtl. bringst was.

Danke euch
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hast du da pull-ups/downs verbaut? Sonst ist das eine Antenne. Das zu schirmen ist eher extrem.
Golfer83
User
Beiträge: 7
Registriert: Sonntag 19. März 2023, 12:12

__deets__ hat geschrieben: Donnerstag 18. Mai 2023, 02:41 Hast du da pull-ups/downs verbaut? Sonst ist das eine Antenne. Das zu schirmen ist eher extrem.
Ja habe ich.

Habe die Schaltung im KFZ verbaut und sobald der Motor
läuft springt das hin und her.
Anfangs hatte ich Probleme mit den DS18B20 da die Kabel nicht geschirmt waren welche ich geschirmt habe und dann ging's.
Golfer83
User
Beiträge: 7
Registriert: Sonntag 19. März 2023, 12:12

So .... Habe die Tasterleitung geschirmt und es springt nicht mehr von alleine aber der Taster prellt trotz pulldown arg, liegt das evtl. an dem langen "hub" des Tasters 🤔.
Wie lang kann eine Entprellzei max. sein?
Golfer83
User
Beiträge: 7
Registriert: Sonntag 19. März 2023, 12:12

@Dennis89
Bekomme dein Programm nicht ans laufen, sobald ich den Taster betätige folgene Ausgabe:
"TypeError: function takes 1 positional arguments but 2 were given"
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

`update_button_state` braucht halt ein zusätzliches Argument `pin`.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Golfer83 hat geschrieben: Donnerstag 18. Mai 2023, 17:30 So .... Habe die Tasterleitung geschirmt und es springt nicht mehr von alleine aber der Taster prellt trotz pulldown arg, liegt das evtl. an dem langen "hub" des Tasters 🤔.
Wie lang kann eine Entprellzei max. sein?
So lange du willst, du kannst das ja ueber Zeitstempel selbst programmieren.
Antworten