TFT-Display am PICO W

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
HLOO
User
Beiträge: 6
Registriert: Montag 20. Mai 2019, 17:40

Hallo
Im aktuellen Projekt möchte ich mittels Display und Microrechner ein Zeigermessgerät darstellen.
Als Microrechner habe ich den PICO W.
Das Display ist das 1,8-Zoll-TFT Farbdisplay-Modul von AZ-delivery ohne Touch-Funktion.
Nach anfänglichen Problemen hab ich es geschafft dass beide zusammenarbeiten.
Dazu nutze ich thonny als IDE und für das Display die empfohlene Adafruit ST7735 Bibliothek.
In der Bibliothek gab es vorgefertigt Rechtecke und Pixel zum ansteuern, aber keine Linien.
Also hab ich mir eine Funktion gebaut, die aus Punkten Linien (also auch den Zeiger) macht.
(Auch das war nicht so einfach wie man denken könnte, weil ich hab wie bei einer Funktion für jeden x-wert den dazugehörigen y-wert errechnet und dann die beiden als Pixel dargestellt. Das funktioniert gut solange der zeiger flach dasteht. Aber wenn er nach oben zeigt so bei 80-100° gibt es nur wenige x-Werte also besteht der Zeiger dann nur noch aus wenigen Punkten. Und für genau 90° gab es nur einen Punkte und ich musste eine Sonderfalllösung einbauen. Ich glaube hier kann ich noch so einiges optimieren. Aber das ist nicht mein aktuelles Problem)

Das Problem ist, dass der Zeiger ja einen realen Wert darstellen soll und das relativ zeitnah.
Wenn ich ihn neu zeichne, kommt ein neuer Strich bzw Zeiger dazu, der alte bleibt aber. (irgendwann ist dann das Display voll mit Zeigern)
Ich müsste das Display löschen, den gesamten Hintergrund (die Skala) neu zeichnen und den Zeiger mit dem neuen Wert zeichnen. Das funktioniert nicht, weil Display löschen und Skala zeichnen zu lange dauert.
In einem ähnlichen Fall (aber am PC) hatte ich in Python die Grafik mit Tkinter erzeugt. Dort konnte ich quasi eine Instanz der Klasse Linie (Zeiger) erzeugen, die ich dann auch so wieder löschen konnte bevor ich den nächsten Zeiger zeichnen konnte.
Ich habe gegoogelt: auch micropython unterstützt den objektorientierten Ansatz.
Aber ich habe keine Ahnung wie ich das am PICO W umsetzen kann. Geht es überhaupt?

Danke dass ihr bis hierher schon gelesen habt :) und noch viel mehr Danke, wenn ihr mir eure Ideen mitteilen könntet.
Benutzeravatar
__blackjack__
User
Beiträge: 14160
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@HLOO: Zu Linien: Da erfindet man nichts selber sondern implementiert einfach den Bresenham-Algorithmus.

`tkinter` macht das für den Programmierer einfacher, was das hinter den Kulissen macht, wäre noch aufwändiger und langsamer. Wobei man dort die Linie auch nicht löschen und neu zeichnen würde, sondern die vorhandene Linie verändern würde. Das sich die Pixelgrafik darauf hin ändert, ist dann Aufgabe von Tk, was im Ernstfall tatsächlich alles was da gezeichnet wurde, löscht und noch mal neu zeichnet.

So wirklich etwas anderes als neu zeichnen gibt es eigentlich nicht. Man kann versuchen den Bereich der neu gezeichnet werden muss zu begrenzen. Oder man zeichnet die alte Linie mit Punkten aus der Hintergrundgrafik noch mal. Also die Koordinaten berechnen, aber dann nicht Pixel in der Zeigerfarbe setzen, sondern das Pixel aus einer Kopie des Hintergrunds an der gleichen Stelle zeichnen.

Und falls Python da zu langsam ist, müsste man vielleicht auf C oder C++ ausweichen.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
DeaD_EyE
User
Beiträge: 1266
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ein Ansatz wäre die blit Methode des Framebuffers: https://docs.micropython.org/en/latest/ ... uffer.blit

Den statischen Hintergrund kannst du z.B. nach dem Zeichnen kopieren und im Quellcode als bytes einfügen.
Dann brauchst du zwei Buffer. Background und Foreground. Background ist dann das ganze Display und Foreground nur der Bereich, der aktualisiert werden soll.
Auf foreground zeichnet man dann.

Mit der blit Methode des Background-Buffers kannst du dann den Foreground Buffer einfügen. Mit key kann man angeben, welche Farbe transparent ist.
Nach dem Update dann den Background-Buffer zum Update des Displays verwenden.

Danach muss man den Background-Buffer wieder mit den Daten aus den Bytes befüllen.
Ich habe nicht nachgesehen, ob es so ist, aber wenn du Glück hast, wird durch background_buffer[:] = static_background kein neuer Speicher zugewiesen.
Falls doch, dann könnte das ein Performance-Problem sein.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
Dennis89
User
Beiträge: 1616
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich habe mit der gleichen Bibliothek auch mal so etwas ähnliches gemacht:
Bild

Das zeichnen des Zeigers habe ich damals so gemacht:

Code: Alles auswählen

from math import cos, sin, pi
import uarray
from sysfont import sysfont

class Scala():
    
    def __init__(self, tft, x_position, y_position, radius, color_low, color_middle, color_high, pointer_color, width, height, co2, xa_start, ya_start, xa_end, ya_end, color_screen):
        self.tft = tft
        self.x_position = x_position
        self.y_position = y_position
        self.radius = radius
        self.color_low = color_low
        self.color_middle = color_middle
        self.color_high = color_high
        self.pointer_color = pointer_color
        self.width = width
        self.height = height
        self.co2 = co2
        self.xa_start = xa_start
        self.ya_start = ya_start
        self.xa_end = xa_end
        self.ya_end = ya_end
        self.black = color_screen

    def byteswapping(color):
        #
        # Byteswapping to creat RGB-Colors
        #
        color_swapped = color >>8 | (color & 0xff) << 8
        return color_swapped
               
    def do_speedo(self):
        array_size = self.width * self.height
        memory = uarray.array('H', [self.tft.BLACK for _ in range(array_size)])
        
        color_low = Scala.byteswapping(self.color_low)
        color_middle = Scala.byteswapping(self.color_middle)
        color_high = Scala.byteswapping(self.color_high)
        pointer_color = Scala.byteswapping(self.pointer_color)
        #
        # Textcolor like value-color
        #
        # '-400' to have a Scale from 400 =0° and 1400 = 180°
        #
        co2 = self.co2 - 400
        if self.co2 < 1400 and self.co2 > 400:
            degree = (180/1000) * co2
        elif self.co2 < 400:
            degree = 0.5
        elif self.co2 > 1400:
            degree = 180
        if degree < 70:
            text_color = self.color_low
        elif degree > 70 and degree < 140:
            text_color = self.color_middle
        elif degree > 140 and degree < 181:
            text_color = self.color_high
        #
        # creat half circle with different colors
        #
        for arc in range(181):
            rad = arc / 180 *pi
            x = self.x_position - int(cos(rad)*self.radius)
            y = self.y_position - int(sin(rad)*self.radius)

            if arc < 70:
                color = color_low
            elif arc >70 and arc < 140:
                color = color_middle
            elif arc > 140 and arc < 180:
                color = color_high
            
            if x in range(self.width) and y in range(self.height):
                memory[x + (self.width * y)] = color
        #
        # creat pointer to show CO2-value
        #
        for pointer in range(self.radius):
            rad = degree /180 * pi
            x_pointer = self.x_position - int(cos(rad) * pointer)
            y_pointer = self.y_position - int(sin(rad) * pointer)
            if x_pointer in range(self.width) and y_pointer in range(self.height):
                memory[x_pointer + (self.width * y_pointer)] = pointer_color
        #
        # creat circle for pointer, just a little bit optic
        #
        for point in range(359):
            rad = point / 180 * pi
            x_point = self.x_position - int(cos(rad) * 2)
            y_point = self.y_position - int(sin(rad) * 2)
            if x_point in range(self.width) and y_point in range(self.height):
                memory[x_point + (self.width * y_point)] = pointer_color                
         #
         # write buffer to display
         #
        self.tft.image(self.xa_start, self.ya_start, self.xa_end, self.ya_end, memory)
        self.tft.text((99, 97), "....ppm" , self.black, sysfont, 1, nowrap=True)
        self.tft.text((99, 97), "{}ppm".format(self.co2) , text_color, sysfont, 1, nowrap=True)
        if self.co2 > 1399:
            self.tft.text((99, 107), "Lueften!" , text_color, sysfont, 1, nowrap=True)
        else:
            self.tft.text((99, 107), "Lueften!" , self.black, sysfont, 1, nowrap=True)
            
Könnte man sicherlich auch mal überarbeiten, aber das Problem dass du mit dem Zeiger beschreibst hatte ich nicht.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
HLOO
User
Beiträge: 6
Registriert: Montag 20. Mai 2019, 17:40

Hallo,
danke für dein Feedback. Ich habe mir dein Programm angeschaut. Ich kann dort nicht erkennen, wo oft du dort den Wert oder die Anzeige aktualisierst. Ich denke das wird nicht so oft sein, und dann ist es auch kein Problem wenn das Neuzeichnen etwas länger dauert, wobei du wesentlich weniger zeichnest, es also ohnehin schneller geht als bei mir. (das Aufwendige ist die Skala mit den ganzen kleinen Teilstrichen. Die kann ich aber auch nicht weglassen) Bei mir sollte die Anzeige schon in so gut wie in Echtzeit erfolgen. Ich glaube ich komm um den framebuffer nicht herum. (Das hatte weiter oben ein anderer Nutzer empfohlen) Also von reinen durchlesen ist es genau was ich brauche, aber ich hab es noch nicht zum Laufen bekommen. Also noch einmal vielen dank für deine Hilfe.
HLOO
User
Beiträge: 6
Registriert: Montag 20. Mai 2019, 17:40

Hihi, man merkt gleich, dass ich hier nicht so oft bin: Ich dachte ich könnte für jeden Beitrag eine separate Antwort schicken, weil mir ja auch jeder von den 3 Beiträgen auf seine Art geholfen hat.
Also das obendrüber war die Antwort auf Dennis Beitrag.

@blackjack: das mit dem Bresenham Algorithmus hab ich noch nicht gewusst und klingt auch sehr sinnvoll. Wenn ich das andere hinbekommen habe, werde ich mir das genauer anschauen und irgendwie einbauen. Im Moment funktioniert auch meine Funktion so recht und schlecht.

@dead-eye:
Das mit dem framebuffer scheint dafür der einzig sinnvolle weg zu sein. Also rein vom Durchlesen genau das was ich brauch. Aber bei der Durchsetzung hab ich noch so meine Probleme. Das soll heißen: ich habe es schon probiert, aber noch nicht hinbekommen. (Ich geh tagsüber auch arbeiten und mach das als Hobby zum Feierabend und am WE, aber ich bin zuversichtlich und bekomme das schon hin)

Also noch einmal vielen Dank für eure Hilfe.
Antworten