Schrittzähler
von Waveshare nachgebaut auf
-dem runden 1.28-Zoll-IPS-LCD-Display 240 x 240 Pixel
- nutzt den ACC Beschleunigungssensor in Micropython
Hardware
- Rundes 1,28-Zoll-IPS-LCD-Display
- RP Pico 2040 (inclusive)
- USB-A zu USB-C Kabel
Schrittzähler
von Waveshare nachgebaut auf-dem runden 1.28-Zoll-IPS-LCD-Display 240 x 240 Pixel
- nutzt den ACC Beschleunigungssensor in Micropython
- RP Pico 2040 (inclusive)
- USB-A zu USB-C Kabel
Im 'Anleitung 4' habe ich das runde Pico-LCD-1.28 Display von Waveshare vorgestellt und ein paar Anregungen für die Inbetriebnahme unter
Circuitpython gegeben. Dabei habe ich es als sehr schade empfunden, dass es in Circuitpython bisher keine Beispielprogramme gibt. Insbesondere
fehlt noch eine Bibliothek QMI8658 zur Nutzung des Sensors. Der eingebaute Sensor ermöglicht die Messung der Beschleunigung in 3 Achsen
(3-Achsen-Beschleunigungsmesser) und der Orientierung in 3 Achsen (3 Achsen-Gyroskop). Da Waveshare in Micropython
ein Beispielprogramm mit der 'class LCD_1inch28' und 'class QMI8658' bereitstellt, habe ich mich diesmal dafür entschieden.
Los gehts
Eine Sache sei noch vorab erwähnt. Diese Anleitung ist für evtl. erste Schritte bei der Pico-Nutzung sehr schwer und erfordert etwas Erfahrung.
Im Auslieferungszustand hat das Display eine Firmware, welche unterschiedliche Farben und Werte des Sensors anzeigt. Damit kann der Nutzer noch gar nichts
anfangen. Es sieht nur, dass irgend etwas funktioniert.
Laden Sie sich also eine aktuelle Firmware (uf2-Datei) von Micropython für den RP Pico 2040 z.B. von
hier
herunter und flashen damit das Display. Anleitungen gibt es dafür im Netz reichlich.
Als nächstes brauchen Sie die Datei 'RP2040-LCD-1.28.py'. Die können Sie von Waveshare oder
hier
von meiner Seite herunterladen. Entpacken Sie die zip-Datei, schließen das Display über den USB-A zu USB-C Anschluss an und
starten das Programm. Was Sie sehen ist ähnlich wenig, wie bei der mitgelieferten Firmware, aber jetzt kann man den vollständigen
Quellcode einsehen und analysieren. Das bringt uns nach vorne.
Schauen wir uns das Programm zusammen an:
1 from machine import Pin,I2C,SPI,PWM,ADC 2 import framebuf 3 import time 7 I2C_SDA = 6 8 I2C_SDL = 7 10 DC = 8 11 CS = 9 12 SCK = 10 13 MOSI = 11 14 RST = 12 16 BL = 25 18 Vbat_Pin = 29 22 class LCD_1inch28(framebuf.FrameBuffer): 23 def __init__(self): 24 self.width = 240 25 self.height = 240 336 class QMI8658(object): 337 def __init__(self,address=0X6B): 338 self._address = address 339 self._bus = I2C(id=1,scl=Pin(I2C_SDL),sda=Pin(I2C_SDA),freq=100_000) 340 bRet=self.WhoAmI() 341 if bRet : 342 self.Read_Revision() 343 else : 344 return NULL 345 self.Config_apply() 397 def Read_XYZ(self): 398 xyz=[0,0,0,0,0,0] 399 #QMI8658AccRange_8g 400 acc_lsb_div=(1<<12) 401 #QMI8658GyrRange_512dps 402 gyro_lsb_div = 64 403 for i in range(3): 404 xyz[i]=raw_xyz[i]/acc_lsb_div#(acc_lsb_div/1000.0) 405 xyz[i+3]=raw_xyz[i+3]*1.0/gyro_lsb_div 406 return xyz 411 if __name__=='__main__': 413 LCD = LCD_1inch28() 414 LCD.set_bl_pwm(65535) 415 qmi8658=QMI8658() 416 Vbat= ADC(Pin(Vbat_Pin)) 418 while(True): 419 #read QMI8658 420 xyz=qmi8658.Read_XYZ() 421 422 LCD.fill(LCD.white) 423 424 LCD.fill_rect(0,0,240,40,LCD.red) 425 LCD.text("RP2040-LCD-1.28",60,25,LCD.white) 426 427 LCD.fill_rect(0,40,240,40,LCD.blue) 428 LCD.text("Waveshare",80,57,LCD.white) 429 430 LCD.fill_rect(0,80,120,120,0x1805) 431 LCD.text("ACC_X={:+.2f}".format(xyz[0]),20,100-3,LCD.white) 432 LCD.text("ACC_Y={:+.2f}".format(xyz[1]),20,140-3,LCD.white) 433 LCD.text("ACC_Z={:+.2f}".format(xyz[2]),20,180-3,LCD.white) 434 435 LCD.fill_rect(120,80,120,120,0xF073) 436 LCD.text("GYR_X={:+3.2f}".format(xyz[3]),125,100-3,LCD.white) 437 LCD.text("GYR_Y={:+3.2f}".format(xyz[4]),125,140-3,LCD.white) 438 LCD.text("GYR_Z={:+3.2f}".format(xyz[5]),125,180-3,LCD.white) 439 440 LCD.fill_rect(0,200,240,40,0x180f) 441 reading = Vbat.read_u16()*3.3/65535*2 442 LCD.text("Vbat={:.2f}".format(reading),80,215,LCD.white) 443 444 LCD.show() 445 time.sleep(0.1)
In den Zeilen 1 bis 3 werden Module importiert. Dann erfolgt die Variablendefinition der Pins in den Zeilen 7 bis 18. Es folgt ab
Zeile 22 bis 333 die Klasse 'class LCD_1inch28' für die Displayansteuerung. Diese brauchen Sie bei jedem Programm, welches das runde-LCD-Display
1.28 nutzt. Ebenfalls die Klasse 'class QMI8658' für den Sensor Zeilen 336 bis 407. Der Rest in der 'while' Schleife ist das eigentliche
Hauptprogramm, was den darzustellenden Inhalt für das Display enthält. Durch die riesige Menge an Zeilen, sieht man den Wald vor lauter Bäumen nicht mehr.
Deshalb habe ich diesen Teil des Programms ausgelagert in eine Datei 'init_lcd_1_28.py', die von jedem späteren Programm
importiert werden kann. Ebenso habe ich eine Datei 'font.py' ausgelagert. In der Originalversion ist nur eine winzige Schriftgröße
möglich. Mit der importierten Font-Map sind es immerhin drei. Sie können sich diese beiden Dateien wieder
herunterladen
und im Hauptverzeichnis des Pico-Displays speichern. Am Ende dieser beiden Dateien sehen Sie auch, wie man das automatische Starten verhindert, wenn
es von woanders importiert wird. Im Hauptprogramm werden dann später einfach die benötigten Klassen aufgerufen. Dadurch ist sehr viel für die Übersichtlichkeit
des Hauptprogramms getan. Übrig bleibt folgender Code, mit der identischen Funktion, wie das Ursprungsprogramm 'RP2040-LCD-1.28.py' von Waveshare.
1 from machine import Pin,I2C,SPI,PWM,ADC 2 import framebuf 3 import time 4 import init_lcd_1_28 5 import font 6 7 Vbat_Pin = 29 8 9 LCD = init_lcd_1_28.LCD_1inch28() 10 LCD.set_bl_pwm(65535) 11 qmi8658=init_lcd_1_28.QMI8658() 12 Vbat= ADC(Pin(Vbat_Pin)) 13 14 while(True): 15 #read QMI8658 16 xyz=qmi8658.Read_XYZ() 17 18 LCD.fill(LCD.white) 19 20 LCD.fill_rect(0,0,240,40,LCD.red) 21 LCD.text("RP2040-LCD-1.28",60,25,LCD.white) 22 23 LCD.fill_rect(0,40,240,40,LCD.blue) 24 LCD.text("Waveshare",80,57,LCD.white) 25 26 LCD.fill_rect(0,80,120,120,0x1805) 27 LCD.text("ACC_X={:+.2f}".format(xyz[0]),20,100-3,LCD.white) 28 LCD.text("ACC_Y={:+.2f}".format(xyz[1]),20,140-3,LCD.white) 29 LCD.text("ACC_Z={:+.2f}".format(xyz[2]),20,180-3,LCD.white) 30 31 LCD.fill_rect(120,80,120,120,0xF073) 32 LCD.text("GYR_X={:+3.2f}".format(xyz[3]),125,100-3,LCD.white) 33 LCD.text("GYR_Y={:+3.2f}".format(xyz[4]),125,140-3,LCD.white) 34 LCD.text("GYR_Z={:+3.2f}".format(xyz[5]),125,180-3,LCD.white) 35 36 LCD.fill_rect(0,200,240,40,0x180f) 37 reading = Vbat.read_u16()*3.3/65535*2 38 LCD.text("Vbat={:.2f}".format(reading),80,215,LCD.white) 39 40 LCD.show() 41 time.sleep(0.1)
Anhand des Fotos lassen sich die Programmzeilen und die Funktion gut erkennen.
Mit diesen Kenntnissen bauen wir jetzt das Display für den Schrittzähler auf. Aufgefallen ist, dass Farbhintergründe verwendet wurden, die in der 'init-Datei' gar nicht definiert wurden. Es lassen sich nämlich die hexadezimalen Werte auch direkt angeben. Z.B. beim Rechteck:
Aber auch bei den anderen Zeichenobjekten oder bei Text geht das. Beim Text hatte ich schon darauf hingewiesen, dass auch die Schriftgröße von 1 bis 3 veränderlich sein soll. Mit Hilfe von zwei Funktionen (printstring und printchar) wird aus der Anweisung
die Anweisung
printstring("Textstring",x0,y0,Größe,Farbe)
Wenn Sie sich die [cmap] in der Datei 'font.py' ansehen, können auch deutsche Umlaute ä, ö, ü und ß dargestellt werden. Es werden dafür die Codes von Zeichen verwendet, die sonst wohl nicht gebraucht werden. Das Programm 'schrittzaehler.py' sieht bis hier dann so aus:
1 from machine import Pin,I2C,SPI,PWM,ADC 2 import framebuf 3 import time 4 import init_lcd_1_28 5 import font 6 import math 7 8 Vbat_Pin = 29 9 10 LCD = init_lcd_1_28.LCD_1inch28() 11 LCD.set_bl_pwm(65535) 12 qmi8658=init_lcd_1_28.QMI8658() 13 Vbat= ADC(Pin(Vbat_Pin)) 14 15 LCD.white = 0xffff 16 LCD.black = 0x0000 17 LCD.gray = 0x0300 18 LCD.darkgray = 0xc618 19 LCD.red = 0x07E0 20 LCD.green = 0x001f 21 LCD.glade = 0x0134 22 LCD.blue = 0xf800 23 LCD.yellow = 0xE0FF 24 LCD.cyan = 0x780F 25 LCD.purple = 0x7be0 26 LCD.creme = 0x7BEF 27 LCD.beige = 0xAFE5 28 LCD.brown = 0x8059 29 30 def printchar(letter,xpos,ypos,size,color): 31 origin = xpos 32 charval = ord(letter) 33 index = charval-32 34 character = font.cmap[index] 35 rows = [character[i:i+5] for i in range(0,len(character),5)] 36 for row in rows: 37 for bit in row: 38 if bit == '1': 39 LCD.pixel(xpos,ypos,color) 40 if size==2: 41 LCD.pixel(xpos,ypos+1,color) 42 LCD.pixel(xpos+1,ypos,color) 43 LCD.pixel(xpos+1,ypos+1,color) 44 if size==3: 45 LCD.pixel(xpos,ypos+1,color) 46 LCD.pixel(xpos,ypos+2,color) 47 LCD.pixel(xpos+1,ypos,color) 48 LCD.pixel(xpos+2,ypos,color) 49 LCD.pixel(xpos+1,ypos+1,color) 50 LCD.pixel(xpos+2,ypos+2,color) 51 xpos+=size 52 xpos=origin 53 ypos+=size 54 55 def printstring(string,xpos,ypos,size,color): 56 if size == 1: 57 spacing = 8 59 spacing = 14 60 if size == 3: 61 spacing = 22 62 for i in string: 63 printchar(i,xpos,ypos,size,color) 64 xpos+=spacing 65
Hinweisen möchte ich noch einmal auf die Zeilen 10 bis 12. Dort wird das Display initialisiert und aus Bequemlichkeit habe ich in den Zeilen 15 bis 28 eine ganze Reihe an Farben definiert. Nachfolgend wird auch noch eine Funktion zur Darstellung eines Kreises definiert, der z.B. den Laufbalken für die Schritte abrundet. Zum Verständnis des weiteren Quelltextes geben die Kommentarzeilen entsprechend Aufschluss. Ergänzen Sie diesen Teil und schon kann das Experimentieren beginnen.
66 def circle(x,y,r,c): 67 LCD.hline(x-r,y,r*2,c) 68 for i in range(1,r): 69 a = int(math.sqrt(r*r-i*i)) 70 LCD.hline(x-a,y+i,a*2,c) 71 LCD.hline(x-a,y-i,a*2,c) 72 73 current_time = time.localtime() 74 hour = current_time[3] 75 minute = current_time[4] 76 second = current_time[5] 77 78 # Hintergrund 79 LCD.fill(LCD.darkgray) 80 LCD.fill_rect(0,130,240,120,LCD.creme) 81 # Balken für Schritte 82 circle(50,130,20,LCD.green) 83 circle(190,130,20,LCD.glade) 84 LCD.fill_rect(50,110,140,40,LCD.glade) 85 86 step = 3 87 ziel = 8000 88 wert = -1 89 prozent = step / ziel * 100 90 91 while(True): 92 #read QMI8658 93 xyz=qmi8658.Read_XYZ() 94 # Display wird x-Achse bewegt 95 #wert_0 = (-2)*xyz[0] 96 # Display wird y-Achse bewegt 97 wert_1 = (-2)*xyz[1] 98 # Display wird z-Achse bewegt 99 wert_2 = (-2)*xyz[2] 100 # Spannung in % anzeigen anzeigen 101 LCD.rect(105,10,30,10,LCD.white) 102 LCD.rect(135,12,2,5,LCD.white) 103 LCD.fill_rect(107,12,24,6,LCD.glade) 104 reading = Vbat.read_u16()*3.3/65535*2 105 printstring(str(int(reading/4.5*100)) + "%",110,30,1,LCD.white) 106 # Zeit anzeigen 107 printstring("Trainingszeit:",30,55,2,LCD.beige) 108 if second == 59: 109 printstring(zeit,70,80,3,LCD.darkgray) 110 current_time = time.localtime() 111 hour = current_time[3] 112 minute = current_time[4] 113 second = current_time[5] 114 zeit = str(hour) + ":" + str(minute) 115 if minute < 10: 116 zeit = str(hour) + ":0" + str(minute) 117 printstring(zeit,70,80,3,LCD.white) 118 # Sensorwert im Terminal sehen 119 #print(wert_1,",",wert_2) 120 if wert_2 > 2 or wert_1 > 1.8: 121 # Laufbalken 122 LCD.fill_rect(50,110,int(prozent*1.3),40,LCD.green) 123 printstring(str(step-1) + "/" + str(ziel),65,155,2,LCD.creme) 124 meter = int((step-1)*0.762) 125 printstring("s="+str(meter)+"m",75,180,2,LCD.creme) 126 cal = int(int((step-0.38)*0.38)*4.18)/1000 127 printstring(str(cal)+"kJ",75,205,2,LCD.creme) 128 #time.sleep(0.5) 129 step += 1 130 prozent = step / ziel * 100 131 printstring(str(step) + "/" + str(ziel),65,155,2,LCD.black) 132 printstring("Schritte",70,120,2,LCD.white) 133 meter = int(step * 0.762) 134 printstring("s="+str(meter)+"m",75,180,2,LCD.black) 135 cal = int(int(step * 0.38)*4.18)/1000 136 printstring(str(cal)+"kJ",75,205,2,LCD.black) 137 138 LCD.show() 139 time.sleep(0.2)
Mit dem Ergebnis der Experimente war ich noch nicht zufrieden. Z.B. wurden die Schritte in bestimmten Positionen auch im Ruhezustand weitergezählt. Außerdem hat sich für mich die Funktion des ACC-Sensors noch nicht voll erschlossen. Deshalb habe ich das Programm von der Inbetriebnahme (siehe weiter oben) noch einmal zur Hand genommen, um die Werte des Sensors in allen möglichen Positionen aufzunehmen. Dafür werden in entsprechenden Größen ACC_x, ACC_y und ACC_z angezeigt. Die gelesenen Werte werden zur besseren Weiterverarbeitung mit 10 multipliziert. Hier ist der Programmcode:
1 from machine import Pin,I2C,SPI,PWM,ADC 2 import framebuf 3 import time 4 import init_lcd_1_28 5 import font 6 7 Vbat_Pin = 29 8 9 LCD = init_lcd_1_28.LCD_1inch28() 10 LCD.set_bl_pwm(65535) 11 qmi8658=init_lcd_1_28.QMI8658() 12 Vbat= ADC(Pin(Vbat_Pin)) 13 14 def printchar(letter,xpos,ypos,size,color): 15 origin = xpos 16 charval = ord(letter) 17 index = charval-32 18 character = font.cmap[index] 19 rows = [character[i:i+5] for i in range(0,len(character),5)] 20 for row in rows: 21 for bit in row: 22 if bit == '1': 23 LCD.pixel(xpos,ypos,color) 24 if size==2: 25 LCD.pixel(xpos,ypos+1,color) 26 LCD.pixel(xpos+1,ypos,color) 27 LCD.pixel(xpos+1,ypos+1,color) 28 if size==3: 29 LCD.pixel(xpos,ypos+1,color) 30 LCD.pixel(xpos,ypos+2,color) 31 LCD.pixel(xpos+1,ypos,color) 32 LCD.pixel(xpos+2,ypos,color) 33 LCD.pixel(xpos+1,ypos+1,color) 34 LCD.pixel(xpos+2,ypos+2,color) 35 xpos+=size 36 xpos=origin 37 ypos+=size 38 39 def printstring(string,xpos,ypos,size,color): 40 if size == 1: 41 spacing = 8 42 if size == 2: 43 spacing = 14 44 if size == 3: 45 spacing = 22 46 for i in string: 47 printchar(i,xpos,ypos,size,color) 48 xpos+=spacing 49 50 while(True): 51 #read QMI8658 52 xyz=qmi8658.Read_XYZ() 53 54 LCD.fill(LCD.white) 55 56 LCD.fill_rect(0,0,240,40,LCD.red) 57 LCD.text("RP2040-LCD-1.28",60,25,LCD.white) 58 59 LCD.fill_rect(0,40,240,40,LCD.blue) 60 LCD.text("Waveshare",80,57,LCD.white) 61 62 LCD.fill_rect(0,80,240,120,0x1805) 63 printstring("ACC_X={:+.2f}".format(10*xyz[0]),20,100-3,2,LCD.white) 64 printstring("ACC_Y={:+.2f}".format(10*xyz[1]),20,140-3,2,LCD.white) 65 printstring("ACC_Z={:+.2f}".format(10*xyz[2]),20,180-3,2,LCD.white) 66 LCD.fill_rect(0,200,240,40,0x180f) 67 reading = Vbat.read_u16()*3.3/65535*2 68 LCD.text("Vbat={:.2f}".format(reading),80,215,LCD.white) 69 LCD.show() 70 time.sleep(0.1)
Als Ergenis ist eine Übersicht für die unterschiedlichen Positionen, wie das Display gehalten werden kann, herausgekommen,
die ich in einer pdf-Datei gespeichert habe.
Diese kann als
pdf-Datei
angesehen oder heruntergeladen werden. Ich hoffe, dass damit auch vielen anderen Usern weitergeholfen werden kann.
Mit dieser weiterreichenden Erkenntnis habe ich dann das Programm ab der Variablendefinition in Zeile 86 wie folgt geändert bzw.
ergänzt.
86 zeit = "" 87 step = 1 88 stepstop = 0 89 start = 0 90 ziel = 8000 91 wert = -1 92 prozent = step / ziel * 100 93 94 while(True): 95 #read QMI8658 96 xyz=qmi8658.Read_XYZ() 97 # Display wird rel. zur x-Achse bewegt 98 wert_x = (10)*xyz[0] 99 # Display wird rel. zur y-Achse bewegt 100 wert_y = (10)*xyz[1] 101 # Display wird rel. zur z-Achse bewegt 102 wert_z = (10)*xyz[2] 103 # Spannung in % anzeigen anzeigen 104 LCD.rect(105,10,30,10,LCD.white) 105 LCD.rect(135,12,2,5,LCD.white) 106 LCD.fill_rect(107,12,24,6,LCD.glade) 107 reading = Vbat.read_u16()*3.3/65535*2 108 printstring(str(int(reading/4.5*100)) + "%",110,30,1,LCD.white) 109 # Zeit anzeigen 110 printstring("Trainingszeit:",30,55,2,LCD.beige) 111 if second == 59: 112 printstring(zeit,70,80,3,LCD.darkgray) 113 current_time = time.localtime() 114 hour = current_time[3] 115 minute = current_time[4] 116 second = current_time[5] 117 zeit = str(hour) + ":" + str(minute) 118 if minute < 10: 119 zeit = str(hour) + ":0" + str(minute) 120 printstring(zeit,70,80,3,LCD.white) 121 # 122 # Arm noch oben bewegen 123 # 124 if (abs(wert_x) < 7 and abs(wert_y) > 3 or abs(wert_x) < 7 and abs(wert_z) > 3) and stepstop == 0: 125 #if (abs(wert_x) < 7 and abs(wert_y) > 3) and stepstop == 0: 126 #if (abs(wert_x) < 7 and abs(wert_z) > 3) and stepstop == 0: 127 start = time.ticks_ms() 128 step += 1 129 stepstop = 1 130 # Laufbalken 131 LCD.fill_rect(50,110,int(prozent*1.3),40,LCD.green) 132 printstring(str(step-1) + "/" + str(ziel),65,155,2,LCD.creme) 133 meter = int((step-1)*0.762) 134 printstring("s="+str(meter)+"m",75,180,2,LCD.creme) 135 cal = int(int((step-0.38)*0.38)*4.18)/1000 136 printstring(str(cal)+"kJ",75,205,2,LCD.creme) 137 prozent = step / ziel * 100 138 printstring(str(step) + "/" + str(ziel),65,155,2,LCD.black) 139 printstring("Schritte",70,120,2,LCD.white) 140 meter = int(step * 0.762) 141 printstring("s="+str(meter)+"m",75,180,2,LCD.black) 142 cal = int(int(step * 0.38)*4.18)/1000 143 printstring(str(cal)+"kJ",75,205,2,LCD.black) 144 # 145 # Arm wieder nach unten 146 # 147 if (abs(wert_x) > 7 and abs(wert_y) < 3 or abs(wert_x) > 7 and abs(wert_z) < 3) and stepstop == 1: 148 #if (abs(wert_x) > 7 and abs(wert_y) < 3) and stepstop == 1: 149 #if (abs(wert_x) > 7 and abs(wert_z) < 3) and stepstop == 1: 150 if (time.ticks_ms() - start)> 1000: 151 stepstop = 0 152 LCD.show() 153 time.sleep(0.2)
Ich habe für die Bewegung zwei Richtungen gewählt, so als wenn man z.B. eine Fitnessuhr seitlich am Arm
schwenkt oder den Arm nach vorn und hinten bewegt. Mit der so erreichten Funktionsweise kann ich leben. Wenn Sie die Anleitung
nachvollziehen, rechnen Sie damit, dass die Sensoren eine gewisse Streuung haben und Sie nicht genau identische Werte bekommen. Aber die
Tendenz sollte schon gleich sein.
In der jetzigen Version wird auch nur noch gezählt, wenn eine Bewegung erfolgt und es ist jedes Mal eine Rückbewegung quasi
in die Ausgangsrichtung erforderlich, bevor erneut ein Schritt gezählt wird. Dies erledigt die Variable 'stepstop'.
Die Uhrzeit wird noch mit 'localtime()' bestimmt. Das geht natürlich nur, wenn das Gerät am PC angeschlossen ist. Geht auch anders,
weiss ich, aber hier und jetzt noch nicht. Ist das Gerät an der Powerbank angeschlossen, zeigt es eben die 'Trainingszeit' bei Null
angefangen an. Probieren Sie ein wenig mit dem Programm herum und lernen dabei das Display kennen. Genau das war nämlich
das Anliegen dieser Anleitung.
Viel Spass und Erfolg beim Ausprobieren.
Viel Spass und Erfolg beim Ausprobieren.