Motoren über den Motortreiber L298 am Calliope mini betreiben
Für dieses Modell verwenden wir den Lego “Stunt Racer” (42095) als Grundlage.


Wir nutzen folgende Komponenten:
- Lego “Stunt Racer” mit den dazugehörigen 2 Lego L-Motoren und der Lego Power-Functions-Batteriebox
- 1 Calliope Mini mit herausgeführter Pinleiste
- 1 Motortreiber L298
- 1 Step-Down-Converter
Beim Stuntracer kann man die “Deko” weglassen und stattdessen an geeigneter Stelle Motortreiber, Stepdown-Converter und Calliope mini befestigen. Ich habe für meinen Prototyp Haushaltsgummis zur Befestigung verwendet.
Schaltplan

Wir verbinden:
a) die Batteriebox über ein modifiziertes Lego-Verlängerungskabel mit dem Eingang des Step-Down-Konverters (9 Volt und Ground),
b) den Step-Down-Konverter-Eingang (9 Volt und Ground) mit dem dem Motortreiber (VCC und Ground),
c) den Ground vom Motortreiber mit einem Ground vom Calliope,
d) die Lego-Motoren über modifizierte Lego-Verlängerungskabel mit den Anschlüssen rechts und links am Motortreiber.
e) die Pins ENA, IN1 und IN2 sowie ENB, IN3 und IN4 des Motortreibers mit sechs freien Pins am Calliope. Für die Steuerung der Geschwindigkeit sind für ENA und ENB Pins mit PWM-Funktionalität zu verwenden. In unserem Beispiel verbinden wir
- ENA mit C16
- IN1 mit P0
- IN2 mit P1
- ENB mit C17
- IN3 mit P2
- IN4 mit P3
f) den USB-Ausgang vom Step-Down-Konverter mit dem Calliope mini. Diese Verbindung muss für die Übertragung von Programmen auf den Calliope jeweils vorrübergehend deaktiviert werden.
Programmcode für Tests
Erster Test ohne Geschwindigkeitsveränderung
Das folgende mit Makecode erstellte Programm dient einem ersten Test - zunächst noch ohne Geschwindigkeitsveränderung. Jeder der beiden Motoren soll sich vorwärts drehen, stoppen und rückwärts drehen.
Zum schnellen Erstellen kann der folgende Code in die JavaScript-Ansicht von Makecode kopiert werden.
input.onButtonPressed(Button.A, () => {
pins.digitalWritePin(DigitalPin.C16, 1)
pins.digitalWritePin(DigitalPin.P0, 1)
pins.digitalWritePin(DigitalPin.P1, 0)
basic.pause(2000)
pins.digitalWritePin(DigitalPin.C16, 0)
basic.pause(2000)
pins.digitalWritePin(DigitalPin.C16, 1)
pins.digitalWritePin(DigitalPin.P0, 0)
pins.digitalWritePin(DigitalPin.P1, 1)
basic.pause(2000)
pins.digitalWritePin(DigitalPin.C16, 0)
})
input.onButtonPressed(Button.B, () => {
pins.digitalWritePin(DigitalPin.C17, 1)
pins.digitalWritePin(DigitalPin.P2, 1)
pins.digitalWritePin(DigitalPin.P3, 0)
basic.pause(2000)
pins.digitalWritePin(DigitalPin.C17, 0)
basic.pause(2000)
pins.digitalWritePin(DigitalPin.C17, 1)
pins.digitalWritePin(DigitalPin.P2, 0)
pins.digitalWritePin(DigitalPin.P3, 1)
basic.pause(2000)
pins.digitalWritePin(DigitalPin.C17, 0)
})
Beim Drücken von Button A
- startet Motor A
- fährt zwei Sekunden vorwärts
- stoppt Motor A
- wartet zwei Sekunden
- fährt zwei Sekunden rückwärts und
- stoppt dann erneut
Gleiches passiert mit Motor B beim Drücken von Button B.
Welche Pins für welchen Reaktion auf 0 bzw. 1 geschaltet werden müssen, steht in der Tabelle im Beitrag zum L298.
Erster Test mit Geschwindigkeitsveränderung
Jetzt kommt die Pulsweitenmodulation ins Spiel. Beim Drücken von Button A sollen sich beide Motoren langsamer, beim Drücken von Button B schneller drehen.
input.onButtonPressed(Button.A, function () {
pulsweite = pulsweite - 500
pins.servoSetPulse(AnalogPin.C16, pulsweite)
pins.servoSetPulse(AnalogPin.C17, pulsweite)
})
input.onButtonPressed(Button.AB, function () {
pins.servoSetPulse(AnalogPin.C16, 0)
pins.servoSetPulse(AnalogPin.C17, 0)
})
input.onButtonPressed(Button.B, function () {
pulsweite = pulsweite + 500
pins.servoSetPulse(AnalogPin.C16, pulsweite)
pins.servoSetPulse(AnalogPin.C17, pulsweite)
})
let pulsweite = 0
pulsweite = 4000
pins.digitalWritePin(DigitalPin.P0, 0)
pins.digitalWritePin(DigitalPin.P1, 1)
pins.digitalWritePin(DigitalPin.P2, 0)
pins.digitalWritePin(DigitalPin.P3, 1)
Die Variable pulsweite wird auf einen Startwert von 4.000 gesetzt, da sich darunter der Motor sowieso nicht dreht. Beide Motoren starten in Vorwärtsfahrt. Bei Drücken auf A wird pulsweite um 500µs verringert - die Motoren damit verlangsamt. Beim Drücken auf B wird pulsweite um 500µs erhöht - die Motoren werden damit schneller. Maximalwert ist die Periodendauer 20.000µs.
Damit sind nun die Grundvorraussetzungen für ein programmierbares Fahrzeug geschaffen: Über zwei Motoren können zwei Räder / Ketten sowohl vorwärts als auch rückwärts in unterschiedlichen Geschwindigkeiten angetrieben werden.
Nutzung von Funktionen
Übersichtlicher wird die Programmierung, wenn wir Funktionen für die verschiedenen Bewegungen der Motoren bzw. des Fahrzeugs definieren und diese dann im Hauptprogramm nutzen. Die Funktionen können dann auch in anderen Programmen genutzt werden.
Wir definieren zunächst für jeden Motor je eine Funktion zum Vorwärts- und eine zum Rückwärtsfahren. Dabei wird die Geschwindigkeit speed als Parameter genutzt. Speed kann zwischen 100 (Prozenzt , also volle Geschwindigkeit) und 0 (Stop) liegen. Der Wert wird dann durch Multiplikation mit 200 in den Wert umgerechnet, den die Methode servoSetPulse benötigt, nämlich die Pulsweite für PWM (Maximalwert 20.000). Zudem gibt es für jeden Motor noch eine Funktion, um diesen zu stoppen:
function fahre_vorwaerts_motor_rechts (speed: number) {
pins.servoSetPulse(AnalogPin.C16, speed * 200)
pins.digitalWritePin(DigitalPin.P0, 0)
pins.digitalWritePin(DigitalPin.P1, 1)
}
function fahre_rueckwaerts_motor_rechts (speed: number) {
pins.servoSetPulse(AnalogPin.C16, speed * 200)
pins.digitalWritePin(DigitalPin.P0, 1)
pins.digitalWritePin(DigitalPin.P1, 0)
}
function stoppe_motor_rechts () {
pins.digitalWritePin(DigitalPin.C16, 0)
}
function fahre_vorwaerts_motor_links (speed: number) {
pins.servoSetPulse(AnalogPin.C17, speed * 200)
pins.digitalWritePin(DigitalPin.P2, 0)
pins.digitalWritePin(DigitalPin.P3, 1)
}
function fahre_rueckwaerts_motor_links (speed: number) {
pins.servoSetPulse(AnalogPin.C17, speed * 200)
pins.digitalWritePin(DigitalPin.P2, 1)
pins.digitalWritePin(DigitalPin.P3, 0)
}
function stoppe_motor_links () {
pins.digitalWritePin(DigitalPin.C17, 0)
}
Wie man die Funktionen nutzt, zeigt folgendes kleines Testprogramm.
input.onButtonPressed(Button.A, function () {
fahre_vorwaerts_motor_rechts(100)
basic.pause(1000)
fahre_vorwaerts_motor_rechts(50)
basic.pause(1000)
fahre_rueckwaerts_motor_rechts(100)
basic.pause(1000)
fahre_rueckwaerts_motor_rechts(50)
basic.pause(1000)
stoppe_motor_rechts()
})
input.onButtonPressed(Button.B, function () {
fahre_vorwaerts_motor_links(100)
basic.pause(1000)
fahre_vorwaerts_motor_links(50)
basic.pause(1000)
fahre_rueckwaerts_motor_links(100)
basic.pause(1000)
fahre_rueckwaerts_motor_links(50)
basic.pause(1000)
stoppe_motor_links()
})
Aufbauend auf die Funktionen für die Motoren kann man dann Funktionen für das ganze Fahrzeug definieren:
function fahre_vorwaerts (speed: number) {
fahre_vorwaerts_motor_rechts(speed)
fahre_vorwaerts_motor_links(speed)
}
function fahre_rueckwaerts (speed: number) {
fahre_rueckwaerts_motor_rechts(speed)
fahre_rueckwaerts_motor_links(speed)
}
function fahre_rechts (speed: number) {
fahre_vorwaerts_motor_links(speed)
fahre_rueckwaerts_motor_rechts(speed)
}
function fahre_links (speed: number) {
fahre_vorwaerts_motor_rechts(speed)
fahre_rueckwaerts_motor_links(speed)
}
function stoppe () {
stoppe_motor_rechts()
stoppe_motor_links()
}
Auch hierzu ein kleines Testprogramm:
input.onButtonPressed(Button.A, function () {
fahre_vorwaerts(100)
basic.pause(1000)
fahre_vorwaerts(50)
basic.pause(1000)
fahre_rueckwaerts(100)
basic.pause(1000)
fahre_rueckwaerts(50)
basic.pause(1000)
stoppe()
})
input.onButtonPressed(Button.B, function () {
fahre_rechts(100)
basic.pause(1000)
fahre_links(100)
basic.pause(1000)
stoppe()
})
Die Funktionen zum Vorwärts- und Rückwärtsfahren kann man noch mit weiteren Parametern versehen, die es ermöglichen, Kurven zu fahren. Dabei kann entweder für kurve_links oder für kurve_rechts ein Wert ein Wert zwischen 0 und 100 gesetzt werden. Ein Wert von 0 bedeutet Geradeausfahrt, ein Wert von 100 (Prozent) den Volleinschlag in die entsprechende Richtung durch Stoppen des Motors. Die folgenden modifizierten Funktionen ersetzen dann die oben definierten.
function fahre_vorwaerts (speed: number, kurve_links: number, kurve_rechts: number) {
fahre_vorwaerts_motor_links(speed * (100 - kurve_links)/100)
fahre_vorwaerts_motor_rechts(speed * (100 - kurve_rechts)/100)
}
function fahre_rueckwaerts (speed: number, kurve_links: number, kurve_rechts: number) {
fahre_rueckwaerts_motor_links(speed * (100 - kurve_links)/100)
fahre_rueckwaerts_motor_rechts(speed * (100 - kurve_rechts)/100)
}
Im folgendem Testprogramm fährt das Fahrzeug jeweils eine Sekunde
- volle Geschwindigkeit vorwärts geradeaus
- halbe Geschwindigkeit mit leichter Kurve (30%) nach links
- volle Geschwindigkeit rückwärts
- halbe Geschwindigkeit rückwärts mit Kurve (100%) nach rechts
input.onButtonPressed(Button.A, function () {
fahre_vorwaerts(100, 0, 0)
basic.pause(1000)
fahre_vorwaerts(50, 30, 0)
basic.pause(1000)
fahre_rueckwaerts(100, 0, 0)
basic.pause(1000)
fahre_rueckwaerts(50, 0, 100)
basic.pause(1000)
stoppe()
})
Alternative Funktionsdefinitionen
Bei den nachfolgenden alternativen Funktionsdefinitionen für die Motoren kann man für speed auch negative Werte angeben. Dann drehen sich die Motoren rückwärts. Bei speed = 0 hält der Motor. Erlaubt sind also Werte von -100 bis +100.
function drehe_rechten_motor (speed: number) {
if (speed > 0) {
pins.servoSetPulse(AnalogPin.C16, speed * 200)
pins.digitalWritePin(DigitalPin.P0, 0)
pins.digitalWritePin(DigitalPin.P1, 1)
} else if (speed < 0) {
pins.servoSetPulse(AnalogPin.C16, speed * -200)
pins.digitalWritePin(DigitalPin.P0, 1)
pins.digitalWritePin(DigitalPin.P1, 0)
} else {
pins.digitalWritePin(DigitalPin.C16, 0)
}
}
function drehe_linken_motor (speed: number) {
if (speed > 0) {
pins.servoSetPulse(AnalogPin.C17, speed * 200)
pins.digitalWritePin(DigitalPin.P2, 0)
pins.digitalWritePin(DigitalPin.P3, 1)
} else if (speed < 0) {
pins.servoSetPulse(AnalogPin.C17, speed * -200)
pins.digitalWritePin(DigitalPin.P2, 1)
pins.digitalWritePin(DigitalPin.P3, 0)
} else {
pins.digitalWritePin(DigitalPin.C17, 0)
}
}
Basierend auf diesen beiden Funktionen für die Motoren kann man dann wie folgt eine Funktion fahre definieren. Parameter der Funktion sind ebenfalls die Geschwindigkeit speed sowie die Richtung richtung. Beide Parameter können Werte von -100 bis +100 annehmen. Bei der Richtung bedeutet -100 Volleinschlag nach links nach links und +100 Volleinschlag nach rechts.
function fahre (speed: number, richtung: number) {
if (richtung <= 0) {
drehe_linken_motor(speed * (100 + richtung)/100)
drehe_rechten_motor(speed)
} else {
drehe_linken_motor(speed)
drehe_rechten_motor(speed * (100 - richtung)/100)
}
}
Steuerung mit einem zweiten Calliope mini
Wie hier schon im Post Calliope steuert Calliope bei Nutzung des internen Motortreibers des Calliope beschrieben, können wir auch hier einen zweiten Calliope zur Fernsteuerung verwenden. Wir nutzen wiederum den Neigesensor. Der Unterschied ist, dass wir nun auch rückwärts fahren wollen:
Bei einer Neigung nach links soll das Fahrzeug nach links, bei einer Neigung nach rechts soll das Fahrzeug nach rechts fahren. Bei einer Neigung nach vorne soll der Calliope vorwärts, bei einer Neigung nach hinten rückwärts fahren - dabei jeweils umso schneller je stärker geneigt.
Um diesen Effekt zu erzielen, schauen wir uns die Wertebereiche an:
Als maximale Neigungwinkel nach links und rechts nehmen wir jeweils 90°, damit ergeben sich folgende Werte für Roll:
- Linksfahren: Roll von 0° geradeaus bis -90° (stark links)
- Rechtsfahren: Roll von 0° geradeaus bis 90° (stark rechts)
Die Geschwindigkeit soll 0 sein wenn der Calliope “liegt”, maximal vorwärts wenn er “auf dem Kopf steht” ist und maximal rückwärts wenn er “normal steht”. Es sollen also folgende Werte für Pitch vorliegen: Pitch von 0° = 0% Geschwindigkeit Pitch von -90° = 100% Geschwindigkeit vorwärts Pitch von 90° = 100% Geschwindigkeit rückwärts
Vor der Übertragung bilden wir die Roll-Werte für links und rechts auf eine verständlichere Skala ab: stark links = -100 geradeaus = 0 stark rechts = 100
Gleiches machen wir für die Geschwindigkeit (Pitch-Werte): vorwärts = 100 rückwärts = -100
Zur Ermittlung und Übertragung dieser Werte auf dem Sender-Calliope (Fernsteuerung) verwenden wir folgendes Programm. Dabei wird durch Drücken von Button A die Fernsteuerung aktiviert und durch Drücken von B wieder deaktiviert.
input.onButtonPressed(Button.A, function () {
aktiv = 1
})
input.onButtonPressed(Button.B, function () {
aktiv = 0
})
let richtung = 0
let geschwindigkeit = 0
let aktiv = 0
radio.setGroup(10)
aktiv = 0
basic.forever(function () {
if (aktiv == 1) {
if (input.rotation(Rotation.Pitch) < -90) {
geschwindigkeit = 100
}
if (input.rotation(Rotation.Pitch) > 90) {
geschwindigkeit = -100
}
if (input.rotation(Rotation.Pitch) >= -90 && input.rotation(Rotation.Pitch) <= 90) {
geschwindigkeit = pins.map(
input.rotation(Rotation.Pitch),
-90,
90,
100,
-100
)
}
if (input.rotation(Rotation.Roll) >= -90 && input.rotation(Rotation.Roll) <= 90) {
richtung = pins.map(
input.rotation(Rotation.Roll),
-90,
90,
-100,
100
)
}
radio.sendValue("richtung", Math.round(richtung))
radio.sendValue("geschwindigkeit", Math.round(geschwindigkeit))
} else {
radio.sendValue("geschwindigkeit", 0)
}
basic.pause(200)
})
Der Callipe auf dem Fahrzeug verwendet die oben definierten alternativen Funktionen und setzt die empfangen Werte in Motorbewegungen um. Da der Sender die Werte schon in der richtigen Skala von -100 bis 100 liefert, ist keine weitere Umrechnung erforderlich.
let geschwindigkeit = 0
let richtung = 0
radio.setGroup(10)
radio.onReceivedValue(function (name, value) {
if (name == "richtung") {
richtung = value
}
if (name == "geschwin") {
geschwindigkeit = value
}
fahre(geschwindigkeit, richtung)
})
Kittys Tech Blog