ESP32 Chassi

Test-Projekt-KNOTECH-Calli:bot: Aufbau, Steuerung, Programmierung und Einsatz des Calli:bot mittels Mikrocontroller ESP32, programmiert mit Micropython für Unterrichtzwecke im Kurs 11 Lernbereich 8B Prozessdatenverarbeitung des sächsischen Lehrplanes.


   

Den Ausgangspunkt für dieses Projekt bildet die Unterrichtung des Grundkurses Informatik der Jahrgangsstufe 11. Im sächsischen Lehrplan gibt es den Lernbereich 8B mit dem Thema: Technische Informatik – Hardware und Prozessdatenverarbeitung. Im Mittelpunkt stehen hier Messen, Steuern, Regeln, Sensorik und Aktorik. Als kostengünstige Basis und weil bereits vorhanden bietet sich der Einplatinencomputer Calliope mini an, der einigen Schülern aus Arbeitsgemeinschaften oder Unterricht der Sekundarstufe I bereits bekannt war. Als weiteres Ziel soll die Behandlung der vertieften Programmierung mit Python dienen. Neben den visuellen Programmierumgebungen bietet der Calliope mini die Möglichkeit der Programmierung mit Micropython als höhere Programmiersprache. Nach der Behandlung grundlegender Themen anhand des Calliope mini fehlte noch ein abschließendes Projekt. Und bei Beschäftigung mit Robotik liegt ein Fahrroboter und autonomes Fahren sehr nahe. Glücklicherweise gibt es für den Calliope eine Menge von Hardware-Drittanbietern. So bietet die Firma Knotech einen kleinen, kompakten und günstigen Fahrroboter für den Calliope mini an, der auch schnell besorgt war.


Chassi Bilder


Wieder Erwarten öffneten sich bei der Vorbereitung der Thematik aber eine Menge zu lösender Probleme. Problem eins: Der Calli:bot (so sein liebevoller Name) ist nur für visuelle Programmierplattformen vorgesehen. Es gibt keine Module für Micropython. Will man aber hardwarenah programmieren benötigt man exakte Hardwarebeschreibungen. Problem zwei: Es gab keine offiziellen Hardwaredokumentationen, Schaltpläne oder verwendete Bausteine/Chips. Nach Anschreiben der Firma Knotech erhielt ich glücklicherweise eine, sagen wir rudimentäre/unvollständige Beschreibung zur Ansteuerung des Chassies. Zumindest grundlegende Bus-, Adress- und Bausteinfunktionen sind jetzt vorhanden. Aber ohne die Kenntnis der Bausteine und deren Datenblätter immer noch schwierig, um die Hardware effektiv auszuschöpfen.

Der bis dahin erfolgte Einsatz des Calliope mini führte zu Problem drei: Die Micropython-Firmware des Calliope erwies sich als wenig stabil und brachte jede Menge Abstürze, unvorhergesehene Programmstops und Speicherfehler (-ausnahmen) mit sich. Die kleine geschriebene Steuerbibliothek für das Chassi hängte sich permanent mit Speicherproblemen auf. Es stand schnell fest, dass eine andere Lösung her muss, wenn man nicht auf visuelle Programmierung ausweichen oder das Chassi als Fehlinvestition abbuchen will. Die favorisierte Lösung konnte nur eine andere Microcontrollerplattform sein. Und die fand sich im ESP32 der chinesischen Firma espressif.


Schaltplan der Hardware


Aufbau der Hardware

Der Aufbau der Hardware gestaltete sich recht einfach, da nur vier Anschlüsse des ESP32 benutzt werden. Das ist der zweipolige I2C-Bus und die Spannungsversorgung. Damit war es möglich alles auf einer kleinen Lochrasterplatine unterzubringen. Lediglich auf die Anordnung/Lage des Grove Anschlusses und des Anschlusses für die Spannung war zu achten. Die beiden Zuleitungen am Calli:bot sind sshr kurz ausgelegt (s. Abb). Der Sicherheit halber wurde für das ESP32 Board ein Sockel zum Aufstecken benutzt. Dadurch kann das Board einfach wieder entfernt oder ausgetauscht werden. Beim Anschluss des Calli:bot ist auf die richtige Polarität des Spannungsanschlusses zu achten.


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

>>> 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.


Fertiges Chassi

   

Die fertige ESP32 Lochrasterplatine wird statt des eigentlich vorgesehenen Calliope mini aufgeschraubt. Dazu werden zwei passende Löcher in die Platine gebohrt. Anschließend werden die beiden Kabelanschlüsse des Calli:bot an der Grove-Buchse der Platine und den Stromanschlusspins aufgesteckt (s. Abb). Fertig.


Quelltexte

Modul “chassiESP32”


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 # 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 == 'l': self.buf = bytearray([self.motorLinks, self.drl, self.vl]) return list(self.buf) elif m == 'r': self.buf = bytearray([self.motorRechts, self.drr, self.vr]) return list(self.buf) elif m == 'a': 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 = 'l', r = 'v', g = 0): # Motorauswahl, Drehrichtung und Geschwindigkeit """Setzt die Werte für die Motoren des Chasis. m = Motor = 'l' für linken Motor m = Motor = 'r' für rechten Motor m = Motor = 'a' für alle/beide Motoren r = Drehrichtung = 'v' für vorwärts r = Drehrichtung = 'r' für rückwärts g = Geschwindigkeit von 0..255""" if m == 'l' and r == 'v': self.drl = 0 self.vl = g self.buf = bytearray([self.motorLinks, self.drl, self.vl]) elif m == 'l' and r == 'r': self.drl = 1 self.vl = g self.buf = bytearray([self.motorLinks, self.drl, self.vl]) elif m == 'r' and r == 'v': self.drr = 0 self.vr = g self.buf = bytearray([self.motorRechts, self.drr, self.vr]) elif m == 'r' and r == 'r': self.drr = 1 self.vr = g self.buf = bytearray([self.motorRechts, self.drr, self.vr]) else: return self.i2cSetMotor(self.buf) def stopMotor(self, m): if m == 'l': self.vl = 0 self.buf = bytearray([self.motorLinks, 0, self.vl]) elif m =='r': self.vr = 0 self.buf = bytearray([self.motorRechts, 0, self.vr]) elif m == 'a': 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

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 chassi = Chassis() chassi.setMotor('l', 'v', 100) # Motor links, vorwärts mit Wert 100 fahren chassi.setMotor('r', 'v', 100) # Motor rechts, vorwärts mit Wert 100 fahren sleep(5) chassi.stopMotor('a') # beide Motoren stoppen chassi.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 chassi = Chassis() for i in range(10): chassi.setLEDRot(0,0) sleep(1) chassi.setLEDRot(1,1) sleep(1) print(chassi.getLEDRot()) # Ausgabe des LED-Zustand chassi.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 chassi = Chassis() for i in range(0, 20): s = int.from_bytes(chassi.getUtraschallSensor(), 'big') print(s, 'mm') sleep(1) chassi.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 chassi = Chassis() for i in range(0, 20): print(int.from_bytes(chassi.getLinienSensor(), 'small') sleep(1) chassi.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 chassi = Chassis() for i in range(0,8): for j in range(0,16): # setRGBLed = Nr-Led = 1..4, Farbe = 0..7, Helligkeit = 0..15 chassi.setRGBLed(1,i,j) chassi.setRGBLed(2,i,j) chassi.setRGBLed(3,i,j) chassi.setRGBLed(4,i,j) sleep(1) chassi.setRGBLed(1,0,0) chassi.setRGBLed(2,0,0) chassi.setRGBLed(3,0,0) chassi.setRGBLed(4,0,0) chassi.i2cStop() # I2C-Bus stoppen