Meine Begeisterung für den Raspberry Pi Pico W hält an. Schon mit dem Modell ohne WLAN konnte man eine ganze Menge Projekte umsetzen. Deshalb hier noch einmal die Verlinkung zu den bisherigen Beiträgen:
Im ersten Teil hatten wir die Installation von Thonny, die Einrichtung des Raspberry Pi Pico sowie erste Anwendungsprogramme zur Nutzung der Ein- und Ausgänge kennengelernt. Im zweiten Teil ging es um die Programmierung der bekannten Schnittstellen OneWire, UART und I2C, allesamt Zweitbelegungen der Pins. Und im dritten Teil hatten wir eine Anwendung für die neueste und schnellste Schnittstelle SPI oder Serial Peripheral Interface aka 4-wire bus vorgestellt. Bereits im Herbst 2021 hatte Jörn Weise eine Wetterstation mit dem Sensor BME280 und dem 0,96 Zoll OLED SSD1306 Display gezeigt.
In dem Beitrag vom 3. Januar 2023 hatte ich die Vorteile der WLAN-Schnittstelle genutzt und Temperatur und relative Luftfeuchtigkeit mit dem Web Server im heimischen Netz bereitgestellt. Darin hatte ich bereits angekündigt, dass ich damit auch eine Smartphone-Steuerung für meine Flotte von Robot Cars realisieren möchte. Auf geht‘s
Verwendete Hardware
1 |
Raspberry Pi Pico W oder WH |
Beim Modell WH sind die Pinleisten bereits angelötet |
1 |
|
|
1 |
alternativ | |
1 |
alternativ | |
div. |
||
1 |
beliebiges Smartphone mit Browser APP |
Vorbereitung
Wer die Entwicklungsumgebung Thonny noch nicht installiert oder aktualisiert hat, hier noch einmal der Link:
Download von https://github.com/thonny/thonny/releases
Aktuell im Januar 2023: thonny-4.0.1.exe
Als Quellen für den Beitrag nutze ich vor allem die Veröffentlichungen der Raspberry Pi Foundation:
Raspberry Pi Pico W Datasheet und
Connecting to the Internet with Raspberry Pi Pico W
sowie das Buch von Paul Fuchs „HTML5 und CSS3 für Einsteiger“, mit dem mir der Einstieg in die Auszeichnungssprache HTML geglückt ist.
Nun endlich zur Smart Car Steuerung. In den vergangenen zwei Jahren hatte ich stets Fahrtstufen und einen einfachen Code für die Funkübertragung verwendet. Die Fahrtstufen haben sich bewährt, weil der Code nicht komplex sein soll und Spannungen unterhalb von 50% der Nennspannung der Motoren nur dazu führen, dass der Motor brummt, aber nicht dreht. Aktuell verwende ich einen vierstelligen Code, bei dem die ersten beiden Stellen für die Fahrt einschließlich Kurvenfahrt verwendet werden und die dritte und vierte Stelle für Zusatzfunktionen, die durch Taster ausgelöst werden können.
Beispiele für den Steuerungscode
Code 9500 für schnellste Geradeausfahrt, 5500 für Stillstand, 7700 für Vorwärtsfahrt nach rechts, Werte unter 5 stehen für Rückwärtsfahrt (1. Stelle) bzw. Linkskurven (2. Stelle)
y ↓ 0 x→ |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
9 |
← |
↖ |
↑ |
↗ |
→ |
||||
8 |
← |
↖ |
↑ |
↗ |
→ |
||||
7 |
← |
↖ |
↑ |
↗ |
→ |
||||
6 |
← |
↖ |
↑ |
↗ |
→ |
||||
5 |
← |
↖ |
0 |
↗ |
→ |
||||
4 |
← |
↙ |
↓ |
↘ |
→ |
||||
3 |
← |
↙ |
↓ |
↘ |
→ |
||||
2 |
← |
↙ |
↓ |
↘ |
→ |
||||
1 |
← |
↙ |
↓ |
↘ |
→ |
Mit der dritten und vierten Stelle kann man wie gesagt Zusatzfunktionen aktivieren, die durch bis zu 7 Taster ausgelöst werden können. Dazu wird je nach Taster der Wert der Zweierpotenz addiert. Achtung: Zählung der Taster beginnt bei null.
Taster |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
Wert |
1 |
2 |
4 |
8 |
16 |
32 |
64 |
Beispiel
Bei diesem Projekt verwende ich nur zwei Taster/Buttons, also die dritte Stelle des Codes wird nicht benötigt. Vierte Stelle Wert=0 bedeutet kein Taster gedrückt, Wert=1 bedeutet Taster 0 (hier Button für blue light) gedrückt und Wert=3 bedeutet Taster 0 und 1 (blue light und Sirene) gedrückt.
Dieser vierstellige Code kann sowohl mit dem Joystick Shield bzw. Joystick Modul als auch mit einem Keypad-Modul (z.B. dem LCD Keypad Shield) benutzt werden. Im ersten Fall werden die Werte der Joystick-Potis auf den Bereich 1 bis 9 „ge-map-ped“, im zweiten Fall werden die Werte für die Fahrtstufen jeweils bei Tastendruck erhöht bzw. vermindert. In Anlehnung an das Keypad werde ich Buttons im Browser programmieren, um die Fahrtkommandos über WLAN einzugeben.
Verkabelung
Mikrocontroller, da macht der Raspberry Pi Pico W keine Ausnahme, liefern an den Pins „Signale“, aber kaum genügend Strom für richtige Verbraucher wie unsere Motoren. Deshalb benötigen wir sogenannte Motortreiber mit ICs wie den L293 oder L298. Ich habe mich aus mehreren Gründen für die kleine Platine aus dem Smart Car Kit mit dem Motor Controller L298N entschieden. Erstens benutze ich nur zwei Motoren, warum also für vier Motoren bezahlen? Zweitens ist der IC mit einem Kühlkörper versehen und drittens kann die Platine die Spannung für den Pico W liefern.
Damit wir den Mikrocontroller an +5V und GND anschließen können, muss der JUMPER gesteckt bleiben. Hingegen werden die Kurzschlussbrücken an ENABLE A und ENABLE B entfernt. Alle sechs Steuerleitungen werden an Ausgangspins des Pico W angeschlossen. Dabei werden die ENABLE-Pins als PWM-Pins definiert, um darüber die Drehzahl der Motoren zu regeln.
Die Leitungen für Input 1 bis 4 werden an normale Pins angeschlossen und mit HIGH bzw. LOW beaufschlagt, um die Drehrichtung festzulegen. Die Enable-Pins werden als PWM deklariert. Falls ein Motor falsch dreht, kann man entweder den Motor umpolen oder die Pinnummer im Code tauschen.
Quellcode
from machine import Pin, PWM
…
m1e = PWM(Pin(11))
m11 = Pin(13,Pin.OUT)
m12 = Pin(12,Pin.OUT)
m2e = PWM(Pin(20))
m21 = Pin(19,Pin.OUT)
m22 = Pin(18,Pin.OUT)
Die Funktion motor(c1, c2) habe ich aus früheren Programmen übernommen. Dabei sind die Parameter c1 und c2 die ersten beiden Stellen des Codes.
Anpassen musste ich den Wert für
factor = 65535 * 6/vBatt # max PWm * 6 / vBatt
weil die Methode duty_u16 für die PWM einen 16-Bit-Wert erwartet und die Versorgungsspannung heruntergeregelt werden muss auf die zulässigen 6V des Motors.
Der (für mich) schwierige Teil liegt beim HTML-Code, um nicht nur Daten ins WLAN zu übertragen, sondern auch Steuersignale ans Smart Car zu übermitteln.
Mit den Zeilen
html = """<!DOCTYPE html><html>
<head><meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
.buttonGreen { background-color: #4CAF50; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
.buttonRed { background-color: #D11D53; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
.buttonBlue { background-color: #1E90FF; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
.buttonLightRed { background-color: #FF4500; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
</style></head>
<body><center><h1>Smart Car Control Panel</h1></center><br><br>
<form><center>
<center> <button class="buttonGreen" name="Up" value="1" type="submit">Up</button>
<br><br>
<center> <button class="buttonGreen" name="Left" value="2" type="submit">Left</button>
<button class="buttonRed" name="Stop" value="0" type="submit">Stop</button>
<button class="buttonGreen" name="Right" value="4" type="submit">Right</button>
<br><br>
<center> <button class="buttonGreen" name="Down" value="3" type="submit">Down</button>
<br><br>
<center> <button class="buttonBlue" name="BlueLightON" value="5" type="submit">Blue Light ON</button>
<button class="buttonLightRed" name="OFF" value="6" type="submit">OFF</button>
<br><br>
<center> <button class="buttonBlue" name="SirenON" value="7" type="submit">Siren ON</button>
<button class="buttonLightRed" name="OFF" value="8" type="submit">OFF</button>
</form>
<br><br>
</body></html>
"""
erzeuge ich im Browser (entweder PC oder Smartphone) die benötigten Buttons für die Steuerung. Als Rückmeldung wird ganz unten der aktuelle Code angezeigt. Hier ein Screenshot von meinem Smartphone:
Was passiert, wenn ein Button angeklickt wird? Der jeweilige Steuerbefehl wird mit Schrägstrich / Fragezeichen ? an die IP-Adresse des Smart Cars angehängt. Da HTML nur eine Auszeichnungssprache, aber keine Programmiersprache ist, erfolgt die Auswertung Im (Micro)- Python-Programm.
Beispiele:
Hier nun der Programmcode im Ganzen (Download). Dabei habe ich das Blinken der blauen LED und die Sirene noch nicht realisiert.
# HTTP Server Example
# Control a Robot Car and blue light/siren using a web browser
import time
import network
import socket
from machine import Pin, PWM
# define 4 digits of code
c1 = 5
c2 = 5
c3 = 0
c4 = 0
# Initialisation of blue LED
led_blue = Pin(14, Pin.OUT)
# Initialisation of motors
vBatt = 9
m1e = PWM(Pin(11))
m11 = Pin(13,Pin.OUT)
m12 = Pin(12,Pin.OUT)
m2e = PWM(Pin(20))
m21 = Pin(19,Pin.OUT)
m22 = Pin(18,Pin.OUT)
m1e.freq(1000)
m2e.freq(1000)
factor = 65535 * 6/vBatt # max PWm * 6 / vBatt
def motor(c1, c2):
y = c1 - 5 # forward/backward
x = c2 - 5 # left/right
leftWheel = y + 0.5 * x
rightWheel = y - 0.5 * x
if leftWheel > 4:
leftWheel = 4
if leftWheel < -4:
leftWheel = -4
if rightWheel > 4:
rightWheel = 4
if rightWheel < -4:
rightWheel = -4
if leftWheel < 0:
m11.off()
m12.on()
elif leftWheel > 0:
m11.on()
m12.off()
else:
m11.off()
m12.off()
# m1e.duty_u16(0)
if rightWheel < 0:
m21.off()
m22.on()
elif rightWheel > 0:
m21.on()
m22.off()
else:
m21.off()
m22.off()
# m2e.duty_u16(0)
leftPWM = int(factor * (0.2 + 0.2*abs(leftWheel)))
print("leftPWM = ", leftPWM)
rightPWM = int(factor * (0.2 + 0.2*abs(rightWheel)))
print("rightPWM = ", rightPWM)
m1e.duty_u16(leftPWM)
m2e.duty_u16(rightPWM)
ssid = '……' # your Wifi SSID
password = '……’ # your Wifi password
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
html = """<!DOCTYPE html><html>
<head><meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
.buttonGreen { background-color: #4CAF50; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
.buttonRed { background-color: #D11D53; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
.buttonBlue { background-color: #1E90FF; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
.buttonLightRed { background-color: #FF4500; border: 2px solid #000000;; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; }
text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
</style></head>
<body><center><h1>Smart Car Control Panel</h1></center><br><br>
<form><center>
<center> <button class="buttonGreen" name="Up" value="1" type="submit">Up</button>
<br><br>
<center> <button class="buttonGreen" name="Left" value="2" type="submit">Left</button>
<button class="buttonRed" name="Stop" value="0" type="submit">Stop</button>
<button class="buttonGreen" name="Right" value="4" type="submit">Right</button>
<br><br>
<center> <button class="buttonGreen" name="Down" value="3" type="submit">Down</button>
<br><br>
<center> <button class="buttonBlue" name="BlueLightON" value="5" type="submit">Blue Light ON</button>
<button class="buttonLightRed" name="OFF" value="6" type="submit">OFF</button>
<br><br>
<center> <button class="buttonBlue" name="SirenON" value="7" type="submit">Siren ON</button>
<button class="buttonLightRed" name="OFF" value="8" type="submit">OFF</button>
</form>
<br><br>
</body></html>
"""
# Wait for connect or fail
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(1)
# Handle connection error
if wlan.status() != 3:
raise RuntimeError('network connection failed')
else:
print('Connected')
status = wlan.ifconfig()
print( 'ip = ' + status[0] )
# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)
print('listening on', addr)
# Listen for connections, serve client
while True:
try:
cl, addr = s.accept()
print('client connected from', addr)
request = cl.recv(1024)
print("request:")
# print(request)
request = str(request)
StopButton = request.find('Stop=0')
if StopButton==8:
print( 'StopButton is pressed')
c1 = c2 = 5
UpButton = request.find('Up=1')
if UpButton==8:
print( 'UpButton is pressed')
if c1 < 9: c1 +=1
LeftButton = request.find('Left=2')
if LeftButton==8:
print( 'LeftButton is pressed')
if c2 > 1: c2 -=1
RightButton = request.find('Right=4')
if RightButton==8:
print( 'RightButton is pressed')
if c2 < 9: c2 +=1
DownButton = request.find('Down=3')
if DownButton==8:
print( 'DownButton is pressed')
if c1 > 1: c1 -=1
BlueLightON = request.find('BlueLightON=5')
if BlueLightON==8:
print( 'Blue Light ON is pressed')
c4 = 1
BlueLightOFF = request.find('OFF=6')
if BlueLightOFF==8:
print( 'Blue Light OFF is pressed')
c4 =0
SirenON = request.find('SirenON=7')
if SirenON==8:
print( 'SirenON is pressed')
c4 = 3
SirenOFF = request.find('OFF=8')
if SirenOFF==8:
print( 'SirenOFF is pressed')
c4 = 1
if c4==1 or c4==3:
led_blue.on()
else:
led_blue.off()
code = 1000*c1 + 100*c2 + 10*c3 + c4
print("Code = ",code)
Code = str(code)
response = html + Code
cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
cl.send(response)
cl.close()
motor(c1,c2)
except OSError as e:
cl.close()
print('connection closed')
Hier das Bild meines Versuchsaufbaus. Ich habe mich für eine Lochrasterplatine mit Buchsenleisten für den Pico W entschieden. Der Motor Controller L298N liegt unterhalb der Platine. Das rote Kabel (rechts unten im Bild) kommt vom 5V-Anschluss des L298N und wird beim Fahrbetrieb mit VSYS verbunden.
Der Programmcode ist sicher noch nicht perfekt, zeigt aber die aktiven Möglichkeiten mit HTML auf. Die blaue LED blinkt noch nicht, weil dies natürlich „non-blocking“, also ohne time.sleep(1), erfolgen soll und die Methode Timer() aus dem Modul machine bei jedem Aufruf dazu geführt hat, dass die LED schneller blinkt, so dass der Pico am Ende nichts anderes mehr gemacht hat. Die Funktion/Methode Timer.deinit() hat nicht zum Beenden des Blinkens geführt. Ob dies ein Bug ist oder mein Fehler, habe ich nicht abschließend klären können.
Dennoch können Sie den Code für die LED z.B. benutzen, um ein Relais für die Kaffeemaschine zu schalten. Also auch Hausautomation / IOT-Anwendungen sind möglich.
Während der Entwicklung habe ich das µPython-Programm am PC gestartet und am Ende auf dem Pico unter dem Namen main.py für den Autostart gespeichert. Bei Änderungen habe ich das Programm gestoppt und die Änderungen vorgenommen. Beim erneuten Start habe ich dann gelegentlich folgende Fehlermeldung in der Kommandozeile (shell) erhalten:
Traceback (most recent call last):
File "<stdin>", line 133, in <module>
OSError: [Errno 98] EADDRINUSE
Dann hilft nur, den USB-Stecker zu ziehen und den Pico wieder neu zu verbinden. Denn der Taster auf dem Pico ist kein Reset-Button, sondern heißt BOOTSEL und wird nur gedrückt, wenn man zum Beispiel µPython installieren möchte.
Kritische Bewertung der Fahreigenschaften meines Robot Cars
In unserem Wohnzimmer sind zu viele Hindernisse, um das Smart Car mit dem Smartphone unfallfrei zu steuern. Es funktioniert grundsätzlich, aber meine Funkfernsteuerung mit Joystick Shield und 433 MHz-Transceiver HC-12 funktioniert besser. Wie oben erwähnt kann man den HTML-Teil des Programms leicht modifizieren und die Buttons auf dem Smartphone für das Schalten von LEDs oder Relais benutzen.
4 comentarios
Andreas Wolter
@Michael: können Sie andere Beispiele aus den vorangegangenen Beiträgen auf dem Pi ausführen?
Sie könnten in Thonny über REPL die Befehle manuell eingeben. Zum Beispiel die imports der Bibliotheken. Dann müssten Sie sehen, ob eine Fehlermeldung erscheint. Der Server wird wahrscheinlich über die “network”-Bibliothek implementiert. Damit würde ich es zuerst versuchen.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Michael
Ich bekomme den HTTP Server nicht zum laufen muss ich das Programm in Bibliotheken aufteilen oder noch was anderes auf den Pico WH speichern . MFG Michael
Andreas Wolter
Ferdinand Weiß: um von außen den Roboter zu steuern, müssten Sie eine externe Verbindung schaffen, damit der Mikrocontroller erreichbar ist. Die IP des MC ist lokal. Sie müssten in Ihrem Internetrouter eine Weiterleitung einrichten.
Es gibt Kameras für den Pico.
Ich habe spontan das hier gefunden: https://www.youtube.com/watch?v=qLee7ThjOp4
Die Bildrate ist erwartungsgemäß gering. Aber eventuell könnte man die Sketches kombinieren.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Ferdinand Weiß
Hallo,
cooles Projekt!
Aber kann man das Auto auch von einem anderen Netzwerk oder mobilen Daten steuern? Oder was müsste man dann ändern?
Wäre es auch möglich, gleichzeitig einen Videostream einer Kamera in die Website einzubauen, um die Umgebung zu sehen?
Grüße aus Niederbayern
Ferdi