(Hinweis: LEGO® ist ein Markenzeichen der LEGO Group, die weder diese Website sponsort, noch autorisiert oder unterstützt.)

Einleitung

LEGO® Mindstorms ist eine wunderbare Plattform, um sich spielerisch der Robotik und Programmierung zu nähern. Kinder können ihre vorhandene LEGO®-Sammlung um Motoren und Sensoren ergänzen, und die grafische Programmier-Umgebung ist auf der einen Seite leicht zu verstehen, auf der anderen Seite enthält es bereits Konzepte wie Befehle, Schleifen, Verzweigung, Variablen und Ereignisse.

LEGO® Mindstorms ist jedoch (vor allem für den wissbegierigen Nachwuchs) nicht ganz günstig. Mein Sohn hatte zwar mittlerweile bereits eine schöne Sammlung LEGO®-Technic-Modelle mit Motoren, Lichtern und Schaltern, aber ohne Sensoren oder gar Programmierung.

Aber er hatte Interesse daran.

Also dachte ich mir: Versuchen wir mal die Lücke zu schließen und eine Open-Source-Plattform zu gründen, die auf LEGO®-Technic basiert, kostengünstig ist (Ziel <100€) und leicht verständlich. Hierfür schien Arduino bestens geeignet, aus folgenden Gründen:

  • Ein Arduino Nano kostet unter 20€, teilweise sogar unter 10€.
  • Er lässt sich mit der LEGO®-Technic-Batterie betreiben.
  • Er startet (im Gegensatz z.B. zum Raspberry Pi) in unter einer Sekunde, ein Umstand, der in Modellen mit einschaltbarer Batterie wichtig ist
  • Er hat genug Ein-/Ausgänge für ca. 10 Geräte.

Die Programmierung erfolgt in C, was nicht so intuitiv ist, was man aber mit einer eigenen grafischen IDE (oder zumindest einer besseren API) kompensieren kann. Wir haben uns zunächst für Letzteres entschieden (siehe später).

Stromversorgung

Um sich der Sache experimentell zu nähern, eignen sich die Conrad-Breadboards hervorragend, Versuchs-Platinen zum Stecken, die das Standard-Lochmaß von 1/10-Zoll verwenden. Hierzu gibt es Drahtbrücken, die man in die Shield-Anschlüsse des Arduino stecken kann:

Auf der LEGO®-Seite müssen wir etwas improvisieren, da die Stecker und Geräte nicht zum Basteln ausgelegt sind. Von LEGO® gibt es Verlängerungskabel, die sich hierfür super eignen. Trennt man sie in der Mitte auf, erhält man einen Stecker und eine Buchse, jeweils mit offenen Drahtenden:

LEGO®-Technic verwendet 4-polige Kabel mit einer interessanten Belegung: 2 Drähte entsprechen Plus- und Minuspol der Batterie, wenn diese eingeschaltet ist, keine Spannung, wenn sie ausgeschaltet ist, und umgekehrte Polung (Minus und Plus), wenn sie rückwärts eingeschaltet ist. Die anderen beiden Drähte verhalten sich genauso, nur sind diese immer gleich gepolt (Plus und Minus).

An wechselnde Polung werden typischerweise Motoren angeschlossen, damit man mit den Schaltern die Drehrichtung steuern kann. (LED-)Lampen beispielsweise werden jedoch an die gleichpoligen Drähte angeschlossen.

Letztere Belegung ist auch für unser Projekt sehr praktisch, da wir hier unseren Arduino anschließen können.

Motoren und Lampen steuern

Als nächstes wollen wir mit dem Arduino LEGO®-Motoren und Lampen steuern. Hier haben wir drei Herausforderungen:

  • Die Ströme sind für den Arduino zu hoch (er liefert maximal 40mA pro Pin).
  • Die Spannung stimmt nicht (der Arduino liefert 5V, LEGO® hat 9V).
  • Motoren benötigen steuerbare Pol-Umkehr (s.o.).

Die ersten beiden Probleme lassen sich mit MOSFETs lösen, Transistoren, die nahezu verlustfrei fremde Spannungen durchschalten können und dabei nahezu keinen Steuerstrom verbrauchen. Außerdem kann man die MOSFETs mit PWM betreiben, einer Technik, die durch schnelles Ein- und Ausschalten fast stufenlos verminderte Leistung ermöglicht, ohne dass die Verlustleistung eines Linearreglers entsteht.

Für die Pol-Umkehr nehmen wir bistabile Relais, damit wir nur während des Richtungswechsels Strom verbrauchen. Legt man ein bistabiles Relais eine Spannung an, schaltet es auf die eine Position, nimmt man die Spannung weg, bleibt es aber in dieser Position. Eine umgekehrte gepolte Spannung schaltet es wieder in die andere Position.

Um einen MOSFET zu steuern, benötigen wir einen Arduino-Pin, für das Relais zwei:

Wenn wir stets ein Relais zur Zeit wechseln, können wir für 3 Relais mit 4 Pins auskommen:

Wir wollen 3 umkehrbare, sowie 2 nicht umkehrbare Ausgänge anbieten, da wir damit den Arduino ideal ausnutzen können. Alle 5 Ausgänge sollen PWM-steuerbar sein.

Wir schließen die MOSFETs und Relais daher wie folgt an:

  • Pin 2: Relais common
  • Pin 3: MOSFET A
  • Pin 5: MOSFET B
  • Pin 6: MOSFET C
  • Pin 7: Relais A
  • Pin 8: Relais C
  • Pin 9: Relais B
  • Pin 10: MOSFET D
  • Pin 11: MOSFET E

Die Relais werden wie folgt angesteuert, um sie jeweils auf Vorwärts oder Rückwärts umzuschalten, bzw. sie so zu belassen, wie sie sind:

Pin 7 (A) Pin 9 (B) Pin 8 (C) Pin 2 (common) Effekt
0 0 0 0 keiner (stabil)
1 0 0 0 Relais 1 vorwärts
0 1 1 1 Relais 1 rückwärts
0 1 0 0 Relais 2 vorwärts
1 0 1 1 Relais 2 rückwärts
0 0 1 0 Relais 3 vorwärts
1 1 0 1 Relais 3 rückwärts

Sensoren

Nun kommen wir zu den Sensoren. Hierfür verwenden wir die Analog-Eingänge des Arduinos. Dies hat folgende Vorteile:

  • Die Eingänge verstehen ein breites Spektrum an Spannungen von 0V bis 5V.
  • Wir brauchen keine Ausgänge belegen.

Allerdings sind die Analog-Eingänge träger als die digitalen. Dies nehmen wir in Kauf.

Wir können verschiedene Sensoren an die Eingänge anschließen:

  • Taster und Schalter
  • Drehregler
  • IR-Lichtschranken
  • Ultraschall-Entfernungs-Sensoren
  • uvm.

Die Eingänge sind (wie meist bei Mikro-Controllern) hochohmig, d.h. man darf sie nicht offen betreiben, ansonsten kommt es aufgrund der Umgebungsstrahlung zu Messfehlern. Für einen Taster oder Schalter verwendet man daher Pull-Up- oder Pull-Down-Widerstände:

Alle Arduinos haben mindestens 6 Analogeingänge, daher wollen wir diese Zahl als Design-Grundlage nehmen.

Fernbedienung, Start, Stopp

Damit die Steuerung nicht sofort bei Stromversorgung startet, und damit wir eine unkontrolliert laufende Steuerung abbrechen zu können, spendieren wir noch zwei feste Taster, nämlich Reset/Stopp (rot) und Play (grün), die wir mit dem Aruduino-Reset-Pin, sowie einem Digitaleingang verbinden.

Außerdem ist es praktisch, die Schaltung per Fernbedienung steuern zu können. Hierfür schließen wir einen IR-Sensor mit Digitalausgang ebenfalls an einen Digitalpin an. Hierfür existiert bislang jedoch noch keine Programmierung im Leguino-Projekt, nur Erfahrung in anderen Projekten.

Gesamt-Schaltplan

Insgesamt sieht so also nun die Gesamtverdrahtung und Pin-Belegung aus:

Bislang haben wir leider nur einen hart verdrahteten Prototypen der Schaltung gebaut. Daraus sollte man mal eine PCB-Version von Leguino entwerfen und entwickeln. Vielleicht hast du Lust mitzumachen und etwas beizusteuern?

Gehäuse

Nachdem wir nun die Schaltung und Verdrahtung zusammenhaben, brauchen wir noch ein Case, das sich in die LEGO®-Welt einreiht.

Für die Analog-Eingänge brauchen wir außerdem eine eigene Steckerart, da LEGO®-Technic wie oben erwähnt nur Aktoren (Motoren, Lichter) vorsieht, sowie fest verdrahtete Schalter nutzt. Wir wählen Modelleisenbahn-Stecker, da diese günstig, kombinierbar und auch für kleine Hände gut handhabbar sind.

Die 2×1-Lochsteine lassen sich mit etwas aufbohren mit Modelleisenbahn-Steckern versehen, aber auch Taster und LEDs lassen sich in ihnen unterbringen. Die Ausgänge setzen wir auf unser Case einfach oben drauf.

Hier ein paar Bilder vom Aufbau des Cases und der prototypischen Schaltung, bis hin zur Fertigstellung:

Kosten

Folgende Kosten entstehen beim Bau in etwa: (vollständige Teile-Liste auf GitHub)

  • Arduino Nano: 12€
  • Relais: 10€
  • MOSFETs: 5€
  • Div. weitere Elektronik: 25€
  • LEGO®-Gehäuse: 5€
  • Power-Functions-Stecker: 20€
  • Summe: Ca. 80€

Damit haben wir das Ziel, unter 100€ zu bleiben, erreicht.

Software

Nachdem wir die Hardware für das Leguino-Projekt realisiert haben, kommen wir zur Software.

Wie bereits erwähnt, wäre die direkte Programmierung mit Arduino-C-Code zwar möglich, aber wenig intuitiv. Teilweise müssen drei Pins für einen Ausgang angesteuert werden, und dabei sind Zustände zu speichern (ist das Richtungs-Relais bereits korrekt gepolt?). Andere gleichzeitig angesteuerte Ausgänge müssen berücksichtigt werden, denn das gleichzeitige Ansteuern von zwei Relais mit unterschiedlicher Richtung ist mit unserer Schaltung nicht möglich.

Außerdem ist es angenehm, in angeschlossenen Geräten zu denken, z.B.:

  • Licht an bzw. aus
  • Motor vorwärts, stopp, halbe Fahrt, rückwärts etc.
  • Kettenantriebe benötigen sogar aus zwei Ausgängen (da sie zwei Motoren haben):
    • Volle Fahrt vorwärts bzw. rückwärts
    • Halbe Fahrt nach links
    • Auf der Stelle rechts herum drehen
    • Stopp

Ebenso wünscht man sich zeitliche Steuerung, beispielsweise „2 Sekunden Fahrt“ oder „Licht blinkt einmal pro Sekunde“, ohne die Hauptschleife der Programmierung mit entsprechender Kontrollfluss-Logik zu belasten.

Dasselbe gilt für die Eingäge. Eine starke API ist wünschenswert, die Fragen zulässt wie:

  • Wurde der Taster gedrückt?
  • Welche Stellung hat der Regler?
  • Welche Entfernung hat ein Gegenstand von dem Ultraschall-Sensor?

Folgende API wollen wir daher für Ausgänge (Motoren, Lichter, etc.) anbieten:

class SingleActor : public Actor
{
	SingleActor(int8 output);
	uint16 getCurrentValue();
	Sequence * getSequence();
	void off();
	void on();
	void on(uint16 msecs);
	void setSequence(Sequence * sequence);
	void setValue(int8 value);
	void setValue(int8 value, uint16 msecs);
};

class Motor : public SingleActor
{
	Motor(int8 output);
	void reverse();
	void reverse(uint16 msecs);
};

class Tread : public Actor
{
	Tread(int8 leftOutput, int8 rightOutput);
	uint16 getCurrentLeftValue();
	uint16 getCurrentRightValue();
	void move(int8 direction, int8 thrust);
	void move(int8 direction, int8 thrust, uint16 msecs);
	void moveLeft(int8 thrust);
	void moveLeft(int8 thrust, uint16 msecs);
	void moveRight(int8 thrust);
	void moveRight(int8 thrust, uint16 msecs);
	void moveStraight(int8 thrust);
	void moveStraight(int8 thrust, uint16 msecs);
	void stop();
	void turnAroundLeft(int8 thrust);
	void turnAroundLeft(int8 thrust, uint16 msecs);
	void turnAroundRight(int8 thrust);
	void turnAroundRight(int8 thrust, uint16 msecs);
};

Und diese API soll es für Sensoren geben:

class SingleSensor : public Sensor
{
	SingleSensor(int8 input);
	uint16 getRawValue();
	virtual void update(uint16 timeStep);
};

class Switch : public SingleSensor
{
	Switch(int8 input);
	bool isOff();
	bool isOn();
	bool waitOff(uint16 timeout = 0);
	bool waitOn(uint16 timeout = 0);
};

class LightSensor : public SingleSensor
{
	LightSensor(int8 input);
	int16 getBrightness();
	bool waitBrighter(int16 value, uint16 timeout = 0);
	bool waitDarker(int16 value, uint16 timeout = 0);
};

class LightBarrier : public SingleSensor
{
	LightBarrier(int8 input);
	bool isClear();
	bool isHit();
	void setThresholds(int16 hitThreshold, int16 clearThreshold);
	bool waitClear(uint16 timeout = 0);
	bool waitHit(uint16 timeout = 0);
};

class DistanceSensor : public SingleSensor
{
	DistanceSensor(int8 input);
	int16 getDistanceCm();
	int16 getDistanceMm();
	bool waitFartherCm(int16 cm, uint16 timeout = 0);
	bool waitFartherMm(int16 mm, uint16 timeout = 0);
	bool waitNearerCm(int16 cm, uint16 timeout = 0);
	bool waitNearerMm(int16 mm, uint16 timeout = 0);
};

Ein einfaches Programm, das diese APIs verwendet, könnte so aussehen:

#include <Leguino.h>

DistanceSensor * entfernung;
Motor * motor;

void setup() {
	// Leguino starten (entweder mit WAIT_FOR_PLAY oder NO_WAIT).
	// Diese Zeile muss immer als erstes in dem Setup stehen.
	leguino.setup(WAIT_FOR_PLAY);
	
	// Sensoren anlegen und mit Anschluessen verbinden
	leguino.add(entfernung = new DistanceSensor(IN_1));

	// Aktoren anlegen und mit Anschluessen verbinden
	leguino.add(motor = new Motor(OUT_A));
}

void loop() {
	// Leguino aktualisieren. Diese Zeile muss immer als erstes in der Loop stehen.
	leguino.update();

	if (entfernung->getDistanceCm() > 50) {
		// Wenn die Entfernung vor dem Sensor größer als 50cm ist, gehe vor
		motor->on();
	} else if (entfernung->getDistanceCm() < 30) {
		// Wenn die Entfernung vor dem Sensor kleiner als 30cm ist, gehe zurück
		motor->reverse();
	} else {
		// Sonst stopp
		motor->stopp();
	}
}

Beispiele für jede Art von Aktor und Sensor sind im GitHub-Projekt enthalten.

Projekt „Roboter-Hund“

Folgenden Roboter haben mein Sohn und ich mit Leguino gebaut. Er hat Entfernungssensoren und nähert sich damit seinem vorliegenden Objekt auf 30cm wie ein Hündchen seinem Herrchen.

Projekt „Süßigkeiten-Automat“

Für ein Schulprojekt hat mein Sohn eine Maschine gebaut, die bei Geldeinwurf Süßigkeiten ausspuckt. Damit hatte er sogar 2 Stände an Schule und Forschungsanstalt, die Erlöse gingen in ein Projekt zur Rettung von Bienen.

Download und Mitmachen

Lust, das Projekt nachzubauen? Oder Verbesserungen, Erweiterungen, Fotos beisteuern?Alle Source-Codes, Schaltpläne und Anleitungen sind als GitHub-Projekt veröffentlicht:

Viel Spaß beim Downloaden und Forken!