Wir wollen einen frei gelagerten Stab mit einem Motor in Schwung vesetzen, ihn gleichmäßig pendeln lassen, ihn bis zum Überschlag aufschaukeln und wenn möglich sogar auf der Spitze balancieren.

Angetrieben wird das Pendel über einen Schrittmotor und einen kurzen Steuerhebel (ca. 10 cm). Dieser Hebel sitzt auf der Achse des Motors und hat am anderen Ende eine Achse mit einem inkrementellen Winkelgeber, an dem das Pendel gelagert ist.

Wir benutzen einen ESP32 (LILY TTGO) und einen Treiberbaustein (Pololu DRV8255 oder A4988) sowie ein Steckbrett. Außerdem benötigen wir einen Schrittmotor, einen Kondensator, den Drehgeber und evtl. eine Tastatur sowie entsprechende Kabel. Zur Stromversorgung nutzen wir neben der USB-Versorgung (5Volt) noch ein zweites Netztteil (24Volt, 1 A). Außerdem können wir natürlich das Display des TTGO für Ausgaben verwenden.

Vervollständigt wird die Hardware durch ein Keyboard mit 16 Tasten und einen Sensor (Infrarot-Näherungssensor, Gabellichtschranle oder Reed-Relais), der als Bezugspunkt für die Kalibrierung der absoluten Position des Motorarms dient.

Das Experiment hat viele Facetten: Man muss die elektrischen Teile auswählen und beschaffen, die Mechanik konstruieren, einen Schaltplan entwerfen, die beiden Platinen auf dem Steckbrett montieren und verdrahten (Schaltplan!). Außerdem muss man die Peripherie anschließen (Motor, Drehgeber, Sensor, Tastatur, 24V-Netzteil) und natürlich die Software entwickeln.

Das Ganze eignet sich als Demonstrationsobjekt für einen Tag der offenen Tür, vielleicht auch als Facharbeit in der Schule und auf alle Fälle als Nachweis von Interessen, Fachwissen und Geschicklichkeit bei einer Bewerbung.

Hinweise

Der mechanische Aufbau muss solide sein, der Motor wird an der Kante der Tischplatte mit einer Klammer befestigt, so dass das Pendel frei schwingen kann. Das Pendellager kann der Drehgeber übernehmen, wenn er mechanisch dafür ausgelegt ist, Andernfalls eignet sich ein Kugellager (8mm Innendurchmesser, 22 mm Außendurchmesser, 7mm dick). Einfach nur Kunststoff als Gleitlager würde zu viel Reibung erzeugen und ausleiern. Das Lager wird in den Steuerhebel eingepresst. Der Pendelstab hat eine fest verbundene Achse, die durch das Lager gesteckt wird (strammer Sitz). Auf dieser Achse sitzt der Drehgeber (sofern er nicht selbst die Pendelachse zur Verfügung stellt).

Der Motor sollte im 1/8 oder 1/16 Schritt-Betrieb angesteuert werden, damit er ruckfrei läuft und genau positionieren kann (3200 Schritte pro Umdrehung bei 1/16-Modus). Wenn das Pendel schwingt, dann muss der Motor ein gewisses Haltemoment aufbringen. Außerdem soll er trotz der hohen Schrittzahl den Steuerhebel ziemlich rasch beschleunigen können, vor allem, wenn es darum geht, den Stab in der Balance zu halten. Deshalb benutzen wir als Motorspannung 24 Volt (12 Volt würden zur Not auch gehen). Den Motorstrom stellen wir mit dem Trimmpoti auf dem Treiberbaustein auf ca. 700 mA ein. Das ist so wenig, dass der Motor nicht heiß wird und auch der Kühlkörper des Motortreibers wird nicht zu warm. Es reicht aber aus, weil unser Pendel ja nicht viel wiegt. Falls der Drehgeber relativ schwer ist, sollten wir einen mechanischen Gewichtsausgleich vorsehen und müssen den Motorstrom evtl. noch etwas höher einstellen. In diesem Fall sollten wir den Motor nicht komplett mit Kunststoff umkleiden, damit die Wärme abgeführt werden kann.

Die Elektrik wird auf einem Steckbrett (Breadboard) aufgebaut:

Am oberen Rand sieht man den DRV8255 mit dem Trimmpoti (rechte untere Ecke) und dem kleinen Kühlkörper (Mitte oben). Die vier Motorleitungen gehören zu dem Stecker oben links. Auf der rechten Seite des DRV8255 sind seine Steuereingänge, die über einige Drähte mit dem TTGO verbunden sind, der den größten Teil des Steckbretts einnimmt.
Damit man den TTGO auf das Breadboard stecken kann, muss man auf beiden Seiten eine Reihe sekrechter Stifte anlöten. Vorsicht beim Einpressen! Man braucht einigen Druck und darf dabei das Display nicht beschädigen. Links oben ist noch ein kleiner Kondensator für die Motorspannung zu sehen. Der Drehgeber ist mit den Pins 2 und 15 verbunden. Rechts unten ist noch ein LED Streifen angesteckt, über den wir optional die Stellung des Pendels andeuten.

Man kann zusätzlich noch eine Tastatur vorsehen, über die der Benutzer den Motor direkt steuern kann. Dabei muss man natürlich die automatische Positionsregelung deaktivieren. Näheres zu einer Tastatur mit 16 Tasten findet sich in einem gesonderten Beitrag.

Man sollte noch eine Halterung für das Breadboard konstruieren, damit es nicht lose herumhängt. Dort können auch die Drucktaster Platz finden.

Software

DREHGEBER

Wir entwickeln die Software in mehreren Etappen. Zunächst befassen wir uns mit dem Drehgeber. Wir müssen absolut alle Signaländerungen an den beiden Signalleitungen des Drehgebers in der Software auffangen und verarbeiten, denn der Drehgeber arbeitet INKREMENTELL. Nur wenn wir richtig zählen, wissen wir, in welcher Stellung sich die Achse befindet, Außerdem müssen wir eine Initialposition als 0 festlegen.

Der optische Drehgeber hat zwei open-collector Ausgänge, die man direkt mit GPIOs des ESP32 verbinden kann. Dabei ist wichtig, dass die GPIOs als Input mit Pullup-Widerstand konfiguriert sind. Keinesfalls dürfen diese Pins als Output definiert sein, da sie ansonsten den Drehgeber beschädigen könnten, wenn sie HIGH Potential annehmen. Wir verwenden Pin 2 und Pin 15.

Der Drehgeber hat zwei Ringe mit jeweils 600 feinen Strichen auf einer Scheibe, die optisch abgetastet werden. Die beiden Ringe sind leicht gegeneinander verschoben. Die Signale des Drehgebers sehen daher so aus:

   +---------+         +---------+         +---  1
   |         |         |         |         |
---+         +---------+         +---------+     0

        +---------+         +---------+          1
        |         |         |         |
--------+         +---------+         +--------  0
   A    B    C    D    A    B    C    D    A

Es handelt sich um zwei Rechteck-Funktionen, die um 90° phasenverschoben sind. Wir detektieren jeden Wechsel an irgendeiner der beiden Signalleitungen. Das bedeutet, dass wir bei einer vollen Umdrehung 4*600 = 2.400 Interrupts bekommen. Bei jedem Interrupt aktualisieren wir die Position. Eine volle Umdrehung geht also von Position 0 bis 2400. Wenn das Pendel 60 cm lang ist, dann sehen wir einen Impulswechsel bei einer Bewegung der Spitze um ca. 1 mm (tan alpha = sin alpha = alpha bei kleinen Winkeln, 600 Pulse für 90°, also 1 Puls bei einem Seitenverhältnis von 600mm: 1mm). Das ist für unsere Zwecke sicher eine ausreichende Auflösung.

Wenn sich die Achse im Uhrzeigersinn dreht, sehen wir an den beiden Pins die Signale in folgender Reihenfolge A-B-C-D-A-..
Dreht sich die Achse umgekehrt, so sehen wir auch die umgekehrte Reihenfolge, also D-C-B-A-D …
Jedesmal, wenn sich irgendeines der beiden Signale ändert, lösen wir einen Interrupt aus und betrachten dann die Zustände der beiden Pins. Wir vergleichen diese Zustände mit dem zuletzt vorher beobachteten Zuständen und können so die Drehrichtung erkennen.

Beispiel: Unser letzter Zustand war (0 0), d.h. wir haben uns zwischen D und A befunden (aus welcher Richtung wir dorthin gelangt waren, ist egal). Wir erhalten einen Interrupt und stellen fest, dass das untere Signal auf 1 gewechselt hat, d.h. wir befinden uns jetzt zwischen C und D. Daraus können wir schließen. dass eine Drehung entgegen dem Uhrzeigersinn vorliegt. Wir dekrementieren daher die aktuelle Position. Hätte die obere Leitung nach 1 gewechselt, so müssten wir die Position inkrementieren. Würden wir allerdings (1 1) sehen, so hätten wir ein Problem, denn dieser Zustand kann nicht direkt nach (0 0) auftreten.
Ob man in der Software versucht, eine solche Fehlersituation zu erkennen, ist eine Frage der Abwägung, denn auch das Prüfen auf das Vorliegen eines Fehlers kostet Zeit. Aber es macht die Software natürlich robuster. Es ist auch schwierig zu testen: Wie soll man einen Fehler absichtlich hervorrufen, der eigentlich nicht auftreten darf? Man müsste dazu beim Test innerhalb der Interruptbehandlung irgendetwas tun, was solange dauert, dass man den nächsten Impulswechsel verpasst.


Hier kommt ein Programm, das den Drehgeber ausliest und die Position mitverfolgt. Bei Tests mit „normalen“ Pendelgeschwindigkeiten (mehrfacher Überschlag inclusive) sind keine Zählfehler aufgetreten auf einem ESP32. Mit einem Arduino müsste man noch separat testen.

// A sample program to read pulses from an optical encoder

// on an ARDUINO you MUST use pins #2 and #3
#define WHITE_WIRE_PIN 15
#define GREEN_WIRE_PIN 2

void setup() {
	// Arduino UNO should use 9600 baud
  Serial.begin (115200);
  Serial.println("angle tracker 1.0");

	// the encoder has open collector connections, so we need a pullup resistor.
	// caution : NEVER connect the WHITE or GREEN WIRE to a GPIO which is configured as OUTPUT
	//           because if the OUTPUT IS HIGH it might burn the transistor inside the encoder
	pinMode(WHITE_WIRE_PIN, INPUT_PULLUP); // internal pullup input pin 2 
	pinMode(GREEN_WIRE_PIN, INPUT_PULLUP); // internalเป็น pullup input pin 3

	// Use two interrupt function to catch rising pulses
	attachInterrupt(digitalPinToInterrupt(WHITE_WIRE_PIN), onRotaryChange, CHANGE);
	attachInterrupt(digitalPinToInterrupt(GREEN_WIRE_PIN), onRotaryChange, CHANGE);
}

// the position of the axis; changes whenever an interrupt happens due to rotation of the encoder
volatile int pos=0, posM1=0;

unsigned long showTime = millis(); // returns the number of milliseconds passed since the Arduino board began running the current program

volatile int lastState= (digitalRead(GREEN_WIRE_PIN)==HIGH ? 1 : 0) + (digitalRead(WHITE_WIRE_PIN)==HIGH ? 2:0); // condition ? result_if_true : result_if_false

void loop() {
  if (millis()>=showTime) {
    Serial.println(String(showTime)+"\t"+String(pos)+"\t"+String(i1)+"\t"+String(i2));
    showTime+=5000;
  }
  posM1=pos;
}

void onRotaryChange() {
  // state = 0 .. 3 depending on input signal level
  int state = (digitalRead(GREEN_WIRE_PIN)==HIGH ? 1 : 0) + (digitalRead(WHITE_WIRE_PIN)==HIGH ? 2:0);
  if      (lastState==0) pos += state==1 ? 1 : -1;
  else if (lastState==1) pos += state==3 ? 1 : -1;
  else if (lastState==2) pos += state==0 ? 1 : -1;
  else if (lastState==3) pos += state==2 ? 1 : -1;
  lastState=state;
}

SIMULATION

Es gibt eine sehr schöne Webseite, auf der ein starres Pendel simuliert wird. Ich habe den Code analysiert und umgearbeitet, so dass wir ihn vielleicht auf dem Arduino verwenden können. Der Einfachkeit halber habe ich allerdings vorerst JavaScript benutzt. Um das Script laufen zu lassen, muss man unter Windows oder Unix „NodeJS“ installieren. Das Programm gibt Zeitstempel und die zugehörigen Winkelpositionen des Pendelstabs aus. Wenn man die Zahlen ansieht, kann man sehr gut erkennen, wie sich das Pendel bewegt und auch einen Überschlag macht.

Das Programm enthält den allgemeinen Code für die Simulation und drei Beispiele, die nacheinander ausgeführt werden.

// simulate position of a pendulum with fix bar
// numerical approximation of w'' = -K sin(w) by Symplectic Euler.

var g 			= 9.81;					// gravitation

var len			= 1; 	 				// length of pendulum (meters)
var K 			= len / g;

var t 			= 0;					// simulation time
var dt 			= 0.01;					// time step size (sec)

var damping 	= 1;					// e.g. 0.999;  speed loss per step; 1 = no damping

var pos, posOld, speed, speedOld;		// position of pendulum (rad, 0 = hanging down) and angle speed (rad/sec)


function step() {
	pos 		= posChange(posOld, speedOld);
	speed 		= speedChange(posOld, speedOld, damping);
	posOld 		= pos;
	speedOld 	= speed;
	t+=dt;

	if (Math.round(t*100) % 10 == 1) process.stdout.write("\nt="+Math.round(t*100)/100+"\t");
	process.stdout.write(Math.round(pos/Math.PI*180)+"\t");	// show in degrees
}

function posChange(p,s) {
	//Symplectic Euler in p and s
	var pp =(p + dt*s) ;
	if (pp > Math.PI) {
		console.log ("turnover CW");
		pp = -2*Math.PI + pp;	// keep within -PI < pos < PI
	}
	else if (pp < -Math.PI) {
		console.log ("turnover CCW");
		pp = 2*Math.PI + pp;	// keep within -PI < pos < PI
	}
	return pp ;
}

function speedChange(p,s,damp) {
	return damp * (s - (K * Math.sin(p+dt*s) ));
}

// EXAMPLE (A):
// ============

// damped pendulum starting in horizontal position
// with a medium strength downward kick
// we see one turnover´, then it reverses direction at a peek of 165 degrees,
// and after 10 seconds the pendulum has calmed down

damping		= 0.99
posOld 		= Math.PI/2;
speedOld 	= -15;
t			= 0;

console.log("\nExample A:\n\n"+Math.round(t*100)/100+"\t"+Math.round(posOld/Math.PI*180));	// show in degrees

// watch 1.000 steps = 10 seconds
for (var s=0;s<1000;s++) step();



// EXAMPLE (B):
// ============

// using a pendulum with a very small damping
// this time we start from top position with a small kick
// we observe one full turn around, the next time it 
// doesn´t quite reach 180 degrees (only 176), but it keeps swinging
// for a long time with the peeks decreasing slowly

damping		= 0.9999;
posOld 		= Math.PI;
speedOld 	= 1;
t			= 0;
console.log("\n\n"+Math.round(t*100)/100+"\t"+Math.round(posOld/Math.PI*180));	// show in degrees

// watch 10.000 steps = 100 seconds
for (var s=0;s<10000;s++) step();

// the pendulum is still almost horizontal (85 degrees) at its peek
console.log("\nExample B:\n\n");

// watch another 10.000 steps
for (var s=0;s<10000;s++) step();


// EXAMPLE (C)
// ===========

// to measure the damping of a real pendulum we should 
// let it drop from a horizontal position and wait until
// it has come to a standstill ~ after 60 seconds - we
// should ignore very tiny movements (+/- 3 degrees) at the end
// because real friction is not linear for very small movements

damping		= 0.999;
posOld 		= Math.PI/2;
speedOld 	= 0;
t			= 0;
console.log("\n\nExample C:\n\n"+Math.round(t*100)/100+"\t"+Math.round(posOld/Math.PI*180));	// show in degrees

// watch 10.000 steps = 100 seconds
for (var s=0;s<10000;s++) step();

MOTORANSTEUERUNG

Wir schreiben ein kleines Programm, das die Motorachse von 0 (= Hebel zeigt nach unten) nach PI/2 (Hebel zeigt nach rechts) bewegt.
Wir benutzen ene Winkelgeschwindigkeit, die etwa doppelt so hoch ist wie das Pendel für eine Viertel-Schwingung benötigt.
Wenn ein Viertel der Schwingungsdauer (voller Zyklus) des Pendels vorbei ist, bewegt sich der Motor (wieder mit der doppelten Geschwindigkeit des Pendels) zurück nach 0. Das Ganz machen wir dreimal. Auf diese Weise sollte es gelingen, das Pendel bis zu einer gewissen Höhe aufzuschaukeln. Spätestens, wenn die Auslenkung 45° beträgt, wird die Schwingungsdauer des Pendels deutlich zunehmen; wir müssen daher immer längere Pausen zwischen unseren Bewegungen einlegen. Mit ein wenig Probieren kann man die Motorbwegungen so vorgeben, dass das Pendel einen Überschlag macht. Das ist unser erses großes Ziel.