Die Software

Der Artikel beschreibt die Softwareseite des Test-Projekt-KNOTECH-Calli:bot mittels Mikrocontroller ESP32. Programmiert wurde der ESP32 mit der Programmiersprache Micropython unter Verwendung der IDE Thonny (in Arbeit).


Vorbereitung des ESP32

Um den ESP32 mit Micropython programmieren zu können, muss zuerst die passende Firmware eingespielt werden. Mit der benutzten Programmierumgebung “Thonny” war dies nicht sonderlich schwierig. Thonny bietet direkte Unterstützung für Micropython (ESP32) an. Die Firmware ist über “https://micropython.org/download/esp32/” frei downloadbar und über Thonny aufspielbar. Auf micropythen.org findet man die notwendigen Dokumentationen.


Ermittlung der I2C-Adresse des Calli:bot

Zuerst wird die ESP32-Platine per USB an den Computer angeschlossen und danach die Programmierumgebung Thonny gestartet. Im Normalfall meldet sich Micropython beim Start von Thonny mit der Ausschrift:

MicroPython v1.9.4-631-g338635ccc on 2018-10-09; ESP32 module with ESP32
Type "help()" for more information.
>>> 

Wenn sich MicroPython nicht meldet, sollte man „Stop/Retsart backend (Ctrl+F2)“ ausführen und/oder einen Reset am ESP-Baustein auslösen. Danach werden die folgenden Kommandos eingegeben:

>>> import machine
>>> from machine import I2C
>>> i2c = I2C(scl=Pin(22), sda=Pin(23), freq=400000)
>>> i2c.scan()
[32, 33]

Als Ergebnis der Prüfung werden die Adressen 32 und 33 ausgegeben. Die Adresse 32 ist für Motorensteuerung und die 33 für den I2C-Baustein, der die weiteren Elemente (LED, RGB-LED, Liniensensoren, IR und Ultraschallsensor) der Hardware bedient.


Quelltexte

Modul „chassiESP32“

Für einen effektiven, verständlichen und einfachen Zugriff auf die Chassi-Komponenten war es sinnvoll ein Modul mit einer geeigneten Klasse zu schreiben. Die Klasse „Chassis“ definiert dafür geeignete Methoden. Die Motorsteuerung stellt die Methoden getMotor(), setMotor() und stopMotor() zur Verfügung. Der Zustand der beiden roten LED vorn lässt sich mittels getLEDRot() abfragen. Wie auch bei getMotor() wird als Ergebnis eine Liste zurückgegeben. getMotor() liefert hier 3 oder 6 Werte in der Liste, je nach Abfrage eines oder beider Motoren, während getLEDRot() stets zwei Werte mit 0 oder 1 für die linke und rechte LED angibt. Mit den Methoden setLEDRot…() lassen sich die roten LED gemeinsam oder einzeln an- oder ausschalten.


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 # Modul: chassiESP32 für # Calli:bot (Knotech-Chassi) mit ESP32 # Letzte Änderung: 18.05.2020 # Autor: Wolfgang Rafelt from machine import I2C, Pin from time import sleep class Chassis(object): """Klasse für das Fahrgestell der Firma Knotech des Calli:bot, gesteuert von einem ESP32.""" def __init__(self): # i2c-Byte-Adressen des Chassis self.i2cAdrMotoren = 32 self.i2cAdrAdapter = 33 # Byte-Adressen des Motors self.motorLinks = 0 self.motorRechts = 2 # Drehrichtung vor=0/rück=1 self.drl = 0 self.drr = 0 # Geschwindigkeit v=0..255 self.vl = 0 self.vr = 0 # LED self.ledrl = 0 self.ledrr = 0 # Speicherpuffer für i2c write-Anweisung self.i2c = I2C(scl=Pin(22), sda=Pin(23), freq=400000) # i2c-Bus Initialisierung self.buf = bytearray() def i2cStop(self): self.i2c.stop() def i2cSetMotor(self, buf): self.i2c.writeto(self.i2cAdrMotoren, self.buf) def i2cSetAdapter(self, buf): self.i2c.writeto(self.i2cAdrAdapter, self.buf) def getMotor(self, m): if m == 0: self.buf = bytearray([self.motorLinks, self.drl, self.vl]) return list(self.buf) elif m == 1: self.buf = bytearray([self.motorRechts, self.drr, self.vr]) return list(self.buf) elif m == 2: self.buf = bytearray([self.motorLinks, self.drl, self.vl]) Werte = list(self.buf) self.buf = bytearray([self.motorRechts, self.drr, self.vr]) Werte = Werte + list(self.buf) return Werte def setMotor(self, m = 0, r = 0, g = 0): # Motorauswahl, Drehrichtung und Geschwindigkeit """Setzt die Werte für die Motoren des Chasis. m = Motor = 0 für linken Motor m = Motor = 1 für rechten Motor m = Motor = 2 für alle/beide Motoren r = Drehrichtung = 0 für vorwärts r = Drehrichtung = 1 für rückwärts g = Geschwindigkeit von 0..255""" if m == 0 and r == 0: self.drl = 0 self.vl = g self.buf = bytearray([self.motorLinks, self.drl, self.vl]) elif m == 0 and r == 1: self.drl = 1 self.vl = g self.buf = bytearray([self.motorLinks, self.drl, self.vl]) elif m == 1 and r == 0: self.drr = 0 self.vr = g self.buf = bytearray([self.motorRechts, self.drr, self.vr]) elif m == 1 and r == 1: self.drr = 1 self.vr = g self.buf = bytearray([self.motorRechts, self.drr, self.vr]) elif m == 2 and r == 0: self.drl = 0 self.drr = 0 self.vl = g self.vr = g self.buf = bytearray([self.motorLinks, self.drl, self.vl]) self.i2cSetMotor(self.buf) self.buf = bytearray([self.motorRechts, self.drr, self.vr]) elif m == 2 and r == 1: self.drl = 1 self.drr = 1 self.vl = g self.vr = g self.buf = bytearray([self.motorLinks, self.drl, self.vl]) self.i2cSetMotor(self.buf) self.buf = bytearray([self.motorRechts, self.drr, self.vr]) self.i2cSetMotor(self.buf) def stopMotor(self, m): if m == 0: self.vl = 0 self.buf = bytearray([self.motorLinks, 0, self.vl]) elif m == 1: self.vr = 0 self.buf = bytearray([self.motorRechts, 0, self.vr]) elif m == 2: self.vl = 0 self.vr = 0 self.buf = bytearray([self.motorLinks, 0, self.vl]) self.i2cSetMotor(self.buf) self.buf = bytearray([self.motorRechts, 0, self.vr]) self.i2cSetMotor(self.buf) def getLEDRot(self): return [self.ledrl, self.ledrr>>1] def setLEDRot(self, ledrl, ledrr): self.ledrl = ledrl self.ledrr = ledrr<<1 self.buf = bytearray([0, self.ledrl + self.ledrr]) self.i2cSetAdapter(self.buf) def setLedRotLinksOn(self): self.ledrl = 1 self.buf = bytearray([0, self.ledrl + self.ledrr]) self.i2cSetAdapter(self.buf) def setLedRotLinksOff(self): self.ledrl = 0 self.buf = bytearray([0, self.ledrl + self.ledrr]) self.i2cSetAdapter(self.buf) def setLedRotRechtsOn(self): self.ledrr = 2 self.buf = bytearray([0, self.ledrl + self.ledrr]) self.i2cSetAdapter(self.buf) def setLedRotRechtsOff(self): self.ledrr = 0 self.buf = bytearray([0, self.ledrl + self.ledrr]) self.i2cSetAdapter(self.buf) def setRGBLed(self, led, farbe, hell): ''' led = vornLinks = 1 hintenLinks = 2 vornRechts = 3 hintenRechts = 4 farbe = 0..7 hell = 0..16 Daten für RGB-LEDs: Zusammensetzung eines RGB-Datenbytes: Bit (0..2) = Farbe (Wert 0..7) Bit 3 = nicht genutzt Bit (4..7) = Helligkeit (0 = aus, 15 Stufen) Das Farbspektrum der RGB-LEDs ist stark reduziert, um die Programmierung seitens der Schüler zu vereinfachen. Wert Farbe 0 schwarz (aus) 1 grün 2 rot 3 gelb 4 blau 5 türkis 6 violett 7 weiß ''' self.buf = bytearray([led, farbe + (hell<<4)]) self.i2cSetAdapter(self.buf) def getUtraschallSensor(self): """Methode liefert den 16-Bit Bytewert der Entfernung in mmm.""" us = self.i2c.readfrom(33,3) # 3 Byte einlesen uss = bytearray() # 1. Byte: IR-Sensor uss.append(us[1]) # 2. Byte: niederwertiges Byte des Ultraschall-Sensors uss.append(us[2]) # 3. Byte: höherwertiges Byte des Ultraschall-Sensors return uss def getLinienSensor(self): # 1 Byte einlesen """Methode liefert als Bytewert den Zustand der Liniensensoren. 0=beide dunkel, 1=links dunkel, 2=rechts dunkel, 3=beide hell Bit[0]: linker Sensor: 0=dunkel, 1=hell Bit[1]: rechter Sensor: 0=dunkel, 1=hell""" ls = self.i2c.readfrom(33,1) return ls

Für die RGB-LED wurde auf eine Abfrage (getRGBLed()) über die Variablenwerte led, farbe und hell verzichtet. Mittels setRGBLed() und den zugehörigen Parametern wird je eine der 4 Farb-LED geschaltet. Das Abschalten der RGB-LED erfolgt durch die Angabe der Parameter farbe=0 und hell=0. Die beiden Methoden getUtraschallSensor() und getLinienSensor() fragen die entsprechenden Sensoren ab und liefern als Ergebnis den 16-Bit Bytewert der Entfernung in mm bzw. als Bytewert den Zustand der beiden Liniensensoren. Die notwendigen Informationen für die Nutzung des Moduls können in der Dokumentation nachgelesen werden. Die Dokumentation wurde kompakt auf ein A4-Blatt begrenzt.

Kurzdokumentation des Moduls chassiESP32 als pdf-Datei zum Download.


Einfache Testprogramme der Chassi-Komponenten


1 2 3 4 5 6 7 8 9 10 11 12 13 # Testprogramm Motoren from chassiESP32 import * from time import sleep # Hauptprogramm # Chassi fährt fünf Sekunden vorwärts myChassi = Chassis() myChassi.setMotor(0, 0, 100) # Motor links, vorwärts mit Wert 100 fahren myChassi.setMotor(1, 0, 100) # Motor rechts, vorwärts mit Wert 100 fahren sleep(5) myChassi.stopMotor(2) # beide Motoren stoppen myChassi.i2cStop() # I2C-Bus stoppen

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Testprogramm vordere rote LED from chassiESP32 import * from time import sleep # Hauptprogramm # die roten LED blinken 10x im Sekundentakt myChassi = Chassis() for i in range(10): myChassi.setLEDRot(0,0) sleep(1) myChassi.setLEDRot(1,1) sleep(1) print(myChassi.getLEDRot()) # Ausgabe des LED-Zustand myChassi.i2cStop() # I2C-Bus stoppen

1 2 3 4 5 6 7 8 9 10 11 12 13 # Testprogramm Ultraschallsensor from chassiESP32 import * from time import sleep # Hauptprogramm # 20x Auslesen des Ultraschallsensors myChassi = Chassis() for i in range(0, 20): s = int.from_bytes(myChassi.getUtraschallSensor(), 'big') print(s, 'mm') sleep(1) myChassi.i2cStop() # I2C-Bus stoppen

1 2 3 4 5 6 7 8 9 10 11 12 13 # Testprogramm Liniensensor from chassiESP32 import * from time import sleep # Hauptprogramm # 20x Auslesen des Liniensensors myChassi = Chassis() for i in range(0, 20): print(int.from_bytes(myChassi.getLinienSensor(), 'small') sleep(1) myChassi.i2cStop() # I2C-Bus stoppen

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # Testprogramm RGB-LED's from chassiESP32 import * from time import sleep # Hauptprogramm # Durchlauf der vier RGB-LED mit Farben und Helligkeiten myChassi = Chassis() for i in range(0,8): for j in range(0,16): # setRGBLed = Nr-Led = 1..4, Farbe = 0..7, Helligkeit = 0..15 myChassi.setRGBLed(1,i,j) myChassi.setRGBLed(2,i,j) myChassi.setRGBLed(3,i,j) myChassi.setRGBLed(4,i,j) sleep(1) myChassi.setRGBLed(1,0,0) myChassi.setRGBLed(2,0,0) myChassi.setRGBLed(3,0,0) myChassi.setRGBLed(4,0,0) myChassi.i2cStop() # I2C-Bus stoppen