Nach dem Refit wurde die gesamte Elektrik durchgesehen und wo nötig überarbeitet. Dabei wurde u.a. die Schaltertafel am Hauptfahrstand geändert.
Ergebnis: Nun wurden zwei völlig unabhängig voneinander arbeitende Scheibenwischer auf einen Schalter gelegt. Jeder Versuch, die Wischer so abzuschalten, dass beide Wischer in Endlage sind, war somit zum Scheitern verurteilt.
Wegen der bunt durcheinander gewürfelten verschiedenen Schaltertypen war diese Tafel mir eh ein Dorn im Auge, also ließ ich mir etwas einfallen.
Zu schalten waren:
Statt Schaltern schwebten mir Druckknöpfe mit Rückmeldeleuchten vor. Schon dies war Rechtfertigung genug, einen Mikrokontroller einzusetzen. Und wenn wir sowieso etwas programmierbares bauen, können wir auch gleich ein paar Logiken einbauen.
So gibt es z.B. keinen Betriebszustand, in dem ein Schiff sowohl Anker- als auch Positionsbeleuchtung führt. Also könnte man, schaltet man das eine ein, das andere gleich ausschalten. So schützt man sich davor, falsch zu signalisieren.
Die Frischwasserpumpe ist im normalen Betrieb immer eingeschaltet. Sie wird durch Druckschalter im Frischwassersystem gesteuert. Wir müssen also dafür sorgen, dass ihr Normalzustand "Ein" ist. Überhaupt wäre es sinnvoll, ihre Schaltstellung zu speichern.
Die Scheibenwischer werden einzeln geschaltet; schlicht, weil man nicht immer beide benötigt. Aber ein nettes Gimmick wäre, wenn man beide gleichzeitig einschalten könnte. Noch schöner wäre natürlich, wenn man die Scheibenwischer nach dem Ausschalten in eine Endlage fahren könnte - ohne Endschalter.
Ein Mikrokontroller des Typs Atmega328P konnte all dies steuern. Um die Baugröße und den Aufwand - immerhin für ein Einzelstück - in einem vertretbaren Rahmen zu halten, fiel die Wahl daher auf einen Arduino Nano, auf dem dieser Baustein verbaut ist.
Die Steuerung der 12v-Ausgänge sollte über Relais erfolgen, die idealerweise über Optokoppler galvanisch von der Elektronik getrennt sind, und für die Spulen der Relais musste eine zusätzliche 5V-Spannungsquelle her, da der Spannungsregler des Arduino mit den Spulen von fünf Relais hoffnungslos überfordert wäre. Dies führte zu folgender Teileliste:
sowie Lochrasterplatinen, Stiftleisten (f/m) und jede Menge Lötzinn.
Relais, die über Optokoppler angesteuert werden, verhalten sich genau gegensätzlich wie erwartet: Ist ihr Steuersignaleingang LOW, also aus, ziehen sie an. Ist er HIGH, gehen sie in Ruhestellung. Um sie also auszuschalten, legt man 5V an, um sie einzuschalten, zieht man den Pin auf Ground (GND). Für den Atmega328P ist das kein Problem.
Um die LEDs parallel mit den Relais an je einem Pin zu betreiben, legt man ihnen über den 1kOhm-Widerstand 5V an die Anode und legt die Kathode an den Pin des Arduino. An diesen legt man auch den Relais-Pin. Führt dieser im Ruhezustand 5V, so geht das Relais in Ruhestellung. An der LED liegen nun an beiden Pins 5V an, weshalb sie nicht leuchtet.
Wird nun jedoch der Arduino-Pin auf LOW gesteuert, liegt er an GND, wodurch das Relais anzieht und die LED leuchtet.
Dies Schema wenden wir auf alle Kanäle an - bis auf einen: die Frischwasserpumpe!
Sie ist ja im Normalzustand (=Ruhezustand des Relais) eingeschaltet, daher treiben wir hier das Spielchen genau andersherum:
Anode der LED an Arduino-Pin, Kathode über 1kOhm an GND. Am Relais muss die 12V-Leitung statt durch Klemme 1 und 2 durch Klemme 2 und 3 geführt werden.
Dadurch ist die Pumpe im Ruhezustand des Relais eingeschaltet und die LED leuchtet, da der Pin auf HIGH ist. Steuert man ihn LOW, zieht das Relais an und an beiden Pins der LED liegt GND.
Auf einem Schaltplan sieht das so aus:
Die Breakoutboards werden über Stiftleisten angeschlossen, für die 12V-Spannungsversorgung nutzen wir Schraubklemmen.
Da die internen Leiter überschaubar sind und wir mit Kupferdraht auch fliegend verbinden können, ist die Entflechtung schnell erledigt und es kann eine Lochrasterplatine aufgebaut werden. So wie hier:
Die Scheibenwischer einfach abzuschalten ist nicht schwer. Der Controller erkennt nach ca. 100 ms, was wir wollen; eine gezielte Abschaltung in Endlage wäre kein Problem.
Aber wir wollen mehr!
Ein Laufenlassen bis zum Endkontakt ist nicht möglich, da die Wischermotoren 40 Jahre alt sind und keine Endschalter haben. Ein Austausch der Motoren gegen neuere mit Schalter scheidet ebenfalls aus.
Hier kommt uns die Physik zu Hilfe:
Ein Elektromotor, der Arbeit verrichtet, zieht mehr Strom als einer, der leer läuft. Ein Wischermotor, der eine Gummilippe über eine Glasfläche schiebt, leistet mehr Arbeit als einer, der an der Wendestelle nur umläuft.
Die Lösung besteht darin, den Stromfluss zum Motor zu messen. Schiebt er gerade, ist dieser höher als an den Wendepunkten, wo er nur die Excenterscheibe umlaufen lässt. Dieses Abfallen des Stromes nehmen wir als Indikator dafür, dass der Wischerarm in einer der beiden Wendelagen ist und schalten erst dann ab.
Um auf verschiedene Lasten ohne Kalibrierung reagieren zu können, Messen wir während des Laufes Maximal- und Minimal-Strom und haben so eine "Range". Um abzuschalten, warten wir einfach, bis der Strom sich merklich dem Minimalwert nähert und steuern dann den Pin des Relais auf HIGH.
Dies ist der dazugehörige Programmcode in C:
/*
Name: FS_Panel.ino
Panel with 6 Buttons with LEDs to control lights, pumps and wipers.
Internal pullup reistors are used so we have to connect the buttons to a sink.
Created: 03.07.2019 08:23:01
Author: Joerg Pauly
*/
// Get the OneButton Library and all the other stuff working
#include <EEPROM.h>
#include <OneButton.h>
// As the relays close on LOW we need inverted triggers: switching a relay to ON means pull it's pin to GND
#define ON LOW
#define OFF HIGH
// define the Button's pins
int pinAnchor = A0;
int pinRunning = A1;
int pinBilge = A2;
int pinWater = A3;
int pinWiperL = A4;
int pinWiperR = A5;
// define pins for LEDs and relais
int relAnchor = 12; // D12
int relRunning = 11; // D11
int relBilge = 9; // D9
int relWater = 8; // D8
int relWiperL = 7; // D7
int relWiperR = 6; // D6
// define pins for feedback from the wipers
int febWiperL = A6;
int febWiperR = A7;
// define BOOLs to state if a wiper is to be switched off
bool flWiperL;
bool flWiperR;
// define Ints for the wiper's min and max current values
int wiperLminI;
int wiperLmaxI;
int wiperLlastI;
int wiperRminI;
int wiperRmaxI;
int wiperRlastI;
// Create Button Objects
OneButton btnAnchor(pinAnchor, true);
OneButton btnRunning(pinRunning, true);
OneButton btnBilge(pinBilge, true);
OneButton btnWater(pinWater, true);
OneButton btnWiperL(pinWiperL, true);
OneButton btnWiperR(pinWiperR, true);
// Prepare the pins an attach the Buttons
void setup()
{
// Make the buttons INPUT
pinMode(pinAnchor, INPUT_PULLUP);
pinMode(pinRunning, INPUT_PULLUP);
pinMode(pinBilge, INPUT_PULLUP);
pinMode(pinWater, INPUT_PULLUP);
pinMode(pinWiperL, INPUT_PULLUP);
pinMode(pinWiperL, INPUT_PULLUP);
// Make relais and LEDs OUTPUT
pinMode(relAnchor, OUTPUT);
pinMode(relRunning, OUTPUT);
pinMode(relBilge, OUTPUT);
pinMode(relWater, OUTPUT);
pinMode(relWiperL, OUTPUT);
pinMode(relWiperR, OUTPUT);
// Set them all to OFF
digitalWrite(relAnchor, OFF);
digitalWrite(relRunning, OFF);
digitalWrite(relBilge, OFF);
digitalWrite(relWater, OFF);
digitalWrite(relWiperL, OFF);
digitalWrite(relWiperR, OFF);
/* attach buttons to callbacks; but be aware of this:
* This µC runs on a ship's bridge, where tension may grow f***ing high.
* So, 1st of all we need to make sure that all channels are switched if
* one might not be able to differ from the 3 kinds of click... */
/* It will surely be important to set a proper light.*/
btnAnchor.attachClick(OnAnchorPressed);
btnAnchor.attachDoubleClick(OnAnchorPressed);
btnAnchor.attachDuringLongPress(OnAnchorPressed);
btnRunning.attachClick(OnRunningPressed);
btnRunning.attachDoubleClick(OnRunningPressed);
btnRunning.attachDuringLongPress(OnRunningPressed);
/* It will choose from life or death to start the bilge pump.*/
btnBilge.attachClick(OnBilgePressed);
btnBilge.attachDoubleClick(OnBilgePressed);
btnBilge.attachDuringLongPress(OnBilgePressed);
/* If you intend to use the Fresh Water Button, you're probably not in trouble...*/
btnWater.attachClick(OnWaterPressed);
/* For the wipers: If there is a clear single click just start the appropriate wiper...*/
btnWiperL.attachClick(OnWiperLPressed);
btnWiperR.attachClick(OnWiperRPressed);
/* ...but if it's unclear: Start them all!*/
btnWiperL.attachDuringLongPress(OnWiperLongPressed);
btnWiperR.attachDuringLongPress(OnWiperLongPressed);
btnWiperL.attachDoubleClick(OnWiperLongPressed);
btnWiperR.attachDoubleClick(OnWiperLongPressed);
// Get the last setting of the fresh water pump out of the EEPROM
// See the comment @ OnWaterPressed why we use HIGH/LOW here...
if (EEPROM.read(0) == 1)
{
digitalWrite(relWater, HIGH);
}
else
{
digitalWrite(relWater, LOW);
}
// Boolean flags to show if a wiper is to shut down
flWiperL = false;
flWiperR = false;
// Define some start values for wiper's min and max current that in any case will be overwritten
resetWiperLrange();
resetWiperRrange();
}
// the endless work of the MCU
void loop()
{
// check the buttons
btnAnchor.tick();
btnRunning.tick();
btnBilge.tick();
btnWater.tick();
btnWiperL.tick();
btnWiperR.tick();
// Check if any wiper is running and if it does, retrieve it's current range
if (digitalRead(relWiperL) == ON)
{
GetRangeWiperL();
}
if (digitalRead(relWiperR) == ON)
{
GetRangeWiperR();
}
// check if a wiper is to be switched off
if (flWiperL)
{
int l_range = wiperLmaxI - wiperLminI;
int l_thr = wiperLlastI - wiperLminI;
if (l_thr <= (l_range * 0.5))
{
digitalWrite(relWiperL, OFF);
resetWiperLrange();
}
}
if (flWiperR)
{
int l_range = wiperRmaxI - wiperRminI;
int l_thr = wiperRlastI - wiperRminI;
if (l_thr <= (l_range * 0.5))
{
digitalWrite(relWiperR, OFF);
resetWiperRrange();
}
}
}
// Here are the callback functions for the buttons
void OnAnchorPressed()
{
if (digitalRead(relAnchor) == OFF)
{
digitalWrite(relAnchor, ON);
digitalWrite(relRunning, OFF);
}
else
{
digitalWrite(relAnchor, OFF);
}
}
void OnRunningPressed()
{
if (digitalRead(relRunning) == OFF)
{
digitalWrite(relRunning, ON);
digitalWrite(relAnchor, OFF);
}
else
{
digitalWrite(relRunning, OFF);
}
}
void OnBilgePressed()
{
if (digitalRead(relBilge) == OFF)
{
digitalWrite(relBilge, ON);
}
else
{
digitalWrite(relBilge, OFF);
}
}
void OnWaterPressed()
{
// "Water" is the only channel that is switched on in normal condition.
// As we want to have the relays in rest position on normal we have to invert it by using the LOW/HIGH system.
if (digitalRead(relWater) == LOW)
{
digitalWrite(relWater, HIGH);
EEPROM.update(0, 1);
}
else
{
digitalWrite(relWater, LOW);
EEPROM.update(0, 0);
}
}
void OnWiperLPressed()
{
if (digitalRead(relWiperL) == OFF)
{
digitalWrite(relWiperL, ON);
}
else
{
// Just set the "kill flag"; wiper will be stopped when in rest position
flWiperL = true;
}
}
void OnWiperRPressed()
{
if (digitalRead(relWiperR) == OFF)
{
digitalWrite(relWiperR, ON);
}
else
{
// Just set the "kill flag"; wiper will be stopped when in rest position
flWiperR = true;
}
}
void OnWiperLongPressed()
{
digitalWrite(relWiperL, ON);
digitalWrite(relWiperR, ON);
}
// Get the range from min I to max I
void GetRangeWiperL()
{
int cur = analogRead(febWiperL);
if (cur < wiperLlastI && cur < wiperLminI)
{
wiperLminI = cur;
}
else if (cur > wiperLlastI && cur > wiperLmaxI)
{
wiperLmaxI = cur;
}
wiperLlastI = cur;
}
void GetRangeWiperR()
{
int cur = analogRead(febWiperR);
if (cur < wiperRlastI && cur < wiperRminI)
{
wiperRminI = cur;
}
else if (cur > wiperRlastI && cur > wiperRmaxI)
{
wiperRmaxI = cur;
}
wiperRlastI = cur;
}
void resetWiperLrange()
{
wiperLmaxI = 0;
wiperLminI = 1023;
}
void resetWiperRrange()
{
wiperRmaxI = 0;
wiperRminI = 1023;
}