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