Author: Dipl.-Ing. Wilfried Klaas
Board: Arduino Duemilanove, ATTiny85
Hier mein eigenes Projekt. Ein einfacher 1/2Kanalschalter. Die Software ist geschrieben auf dem Arduino, läuft aber auch auf einem ATTiny85.
Platinen für das Projekt gibt's bei mir. Genauso wie auch fertig programmierte Chips. Aber hier wollen wir ja was lernen.
Zum Testen würde ich dsan ganze erstmal auf einer Steckplatine aufbauen und zwar so:
Der Empfänger kommt an Pin 2, die beiden LED's (an Pin 0 und 1) sind stellvertretend für die späteren Relais.
Mit den beiden Schaltern kann man jeden Kanal zwischen Toggle und Dauerbetrieb umschalten.
Impulsbetrieb heisst, einmal Hebel bewegen schaltet ein, nochmal bewegen schaltet aus.
Schaltbetrieb heisst, solange der Hebel ja nach Kanal in die eine oder andere Richtung steht, ist die LED an.
Zunächst ein paar Konstanten. Sowas wie die verwendeten Pins oder auch die Konstanten für die RC Anbindung.
Im setup()
gibt's nix besonderes. Die Ein/Ausgänge werden entsprechend konfiguriert.
Dann die loop()
. Zunächst werden mit digitalRead die Einstellungen für Dauer/Imüulsbetrieb der Kanäle gelesen. Dann wird der aktuelle Wert vom Empfänger gelesen. Befindet sich dieser innerhalb der gültigen Werte kommt der Wert in einen Ringspeicher. DAnn lese ich den aktuellen gemittelten Wert aus dem Ringspeicher.
Zunächst müsssen wir noch den Nullpunkt bestimmen, dseswegen werden gleich nach dem Start und nachdem die Empfängersignale gültig sind, einfach erstmal 10 Werte gelesen. Daraus wird der Mittelwert bestimmt und das gibt den Nullpunkt.
Erst nach dem Lesen der 10 Werte ist der Schalter einsatzbereit. Im Else Zweig der if (initNP) geht's dann zur Ausgabe.
Beim Schaltbetrieb wird einfach der RC-Wert mit dem Nullpunkt verglichen und der entsprechende Kanal aktiviert.
Beim Impulsbetrieb wird nach den Flanken geschaut. Wird eine positive (oder negative Flanke ja nach Kanal) entdeckt, dann wird der entsprechende Ausgang getoggelt, d.h. umgeschaltet.
Eine Besonderheit ist die Mittelwertbildung der RC-Signale und die Fehlerbehandlung. Kommen mehr als 3 fehlerhafte Impulse vom Empfänger (oder auch garkeine) dann werden beide Kanäle bageschaltet. (Failsafe)
DIe Mittelwertbildung geht über den Ringpuffer, der höchste und der niedrigste Wert werden ignoriert und der Mittelwert wird von den restilichen von 8 Werten gebildet. Das Verzögert zwar den Schaltprozess, aber gelegentliche Ausreisser, die immer mal wieder vorkommen, werden so sicher unterdrückt. Insgesamt hat der Schalter dann eine Verzögerungszeit von max. 200ms. Das ist für die meisten Einsatzbereiche tolerierbar.
Mit leichten Modifikationen kann das Programm auch auf einem normalen Arduino laufen. Einfach nur die Pins anpassen.
Hier der Quellcode.
/* Schaltprogramm für den ATTiny 85. Der Empfänger wird vom Pin 2 gelesen, die Ausgänge sind auf Pin 0 und 1. Über den Pin 3/4 wird gelesen, ob der jeweilige Kanal im Impulsbetrieb oder Dauerbetrieb geschaltet werden soll. Impulsbetrieb: einmaliges Umlegen des Hebels schaltet ein bzw. aus Dauerbetrieb: das Relais ist solange geschlossen, wie der Hebel in die entsprechende Richtung gelegt wird. Progbrammablauf: Der Nullpunkt wird zunächst durch interpolation der ersten 10 Werte vom Empfänger festgestellt. Fehlerhafte Werte, also Werte ausserhalb von 900-2100 werden ignoriert. Werden mehr als 3 fehlerhafte Werte hintereinander gefunden, werden die Ausgänge auf null geschaltet. (Failsafe) Die Schaltschwelle für den normalbetrieb ist auf 250ms festgelegt. */ //const byte RC_INT = 0; // das ist INT 0 aber Pin 2!!!! const byte PIN_RC = 2; // das ist INT 0 aber Pin 2!!!! // Ausgänge const byte CAN_1 = 0; const byte CAN_2 = 1; // Eingänge const byte PIN_IMPULS_1 = 3; const byte PIN_IMPULS_2 = 4; // Maximale Anzahl der Fehler const byte MAX_ERRORS = 3; // Konstanten für die RC Erkennung const int MIN_RC_VALUE = 900; const int MAX_RC_VALUE = 2100; /* Konstante für die Erkennung des Schaltbefehls. Bei Kanal 1 wäre das Nullpunkt + SWITCH_STEP bei Kanal 2 entsprechend Nullpunkt - SWITCH_STEP */ const int SWITCH_STEP = 250; // letzter Wert vom Empfänger volatile unsigned long RcValue; // Anzahl der tolerierten Fehler byte error = MAX_ERRORS; // Größe des Pufferspeichers const byte stackSize = 10; // Ringspeicher mit den letzten 10 Werten vom Empfänger (Fehlerwerte werden direkt ausgeblendet.) int stack[stackSize]; // Laufvariable des Ringspeichers byte stackIndex = 0; // Impulseinstellung pro Kanal boolean impuls_1; boolean impuls_2; // Speicher für alte Werte pro Kanal boolean oldValue_can1 = 0; boolean oldValue_can2 = 0; // Zwischenspeicher int myRcValue; int maxSwitch; int minSwitch; // Nullpunktbestimmung boolean initNP; void setup() { // Kanäle auf Ausgang, und dann deaktivieren pinMode(CAN_1, OUTPUT); pinMode(CAN_2, OUTPUT); digitalWrite(CAN_1, LOW); digitalWrite(CAN_2, LOW); // Eingang für Impuls 1 pinMode(PIN_IMPULS_1, INPUT); digitalWrite(PIN_IMPULS_1, HIGH); // Eingang für Impuls 2 pinMode(PIN_IMPULS_2, INPUT); digitalWrite(PIN_IMPULS_2, HIGH); // Eingang für RC pinMode(PIN_RC, INPUT); digitalWrite(PIN_RC, HIGH); initNP = true; error = MAX_ERRORS; } void loop() { // Lesen der aktuellen Impuls-Einstellungen von den Kanälen impuls_1 = digitalRead(PIN_IMPULS_1) == 0; impuls_2 = digitalRead(PIN_IMPULS_2) == 0; // Aktuellen RC-Wert lesen RcValue = pulseIn(PIN_RC, HIGH, 100000); // Wert innerhalb der Toleranzgrenze if ((RcValue > MIN_RC_VALUE) && (RcValue < MAX_RC_VALUE)) { // Fehlerspeicher zurück setzen error = MAX_ERRORS; // Wert in den Ringpuffer schreiben stack[stackIndex%stackSize] = RcValue; stackIndex++; // den aktiven RC Wert holen myRcValue = getRcValue(); // Nullpunktsbestimmung ? if (initNP) { // Schon alle Werte gelesen? if (stackIndex == stackSize) { // die ersten 10 Werte dienen der Nullpunkt bestimmung int nullpoint = myRcValue; initNP = false; // Schaltschwellen definieren maxSwitch = nullpoint + SWITCH_STEP; minSwitch = nullpoint - SWITCH_STEP; } } else { // Kanal 1 auf Impuls? if (impuls_1) { doImpuls1(); } else { // Ausgang schalten. digitalWrite(CAN_1, myRcValue > maxSwitch); } // Kanal 2 auf Impuls? if (impuls_2) { doImpuls2(); } else { // direkt schalten digitalWrite(CAN_2, myRcValue < minSwitch); } // ein bisschen verzögern delay(100); } } else { // Fehlerfall if (error == 0) { // failsafe ... digitalWrite(CAN_1, LOW); digitalWrite(CAN_2, LOW); } else { // Fehler zählen error--; } } } // Impulsschalten für Kanal 1 void doImpuls1() { // Änderung am Empfängerwert ? boolean newValue = myRcValue > maxSwitch; if (oldValue_can1 != newValue) { // neuen Wert schreiben, aber nur an der positiven Flanke if (newValue) { digitalWrite(CAN_1, !digitalRead(CAN_1 )); } oldValue_can1 = newValue; } } // Impulsschalten für Kanal 2 void doImpuls2() { // Änderung am Empfängerwert boolean newValue = myRcValue < minSwitch; if (oldValue_can2 != newValue) { // Nur Schalten bei negativer Flanke if (newValue) { digitalWrite(CAN_2, !digitalRead(CAN_2 )); } oldValue_can2 = newValue; } } /* Bestimmung des durchschmittlichen RC-Wertes. Es werden zunächst alle Werte aufsummiert. Der höchste und der Niedrigste werden jedoch ignoriert. Dann wird der Mittelwert gebildet. */ int getRcValue() { int all = 0; int minValue = stack[0]; int maxValue = stack[0]; all = stack[0]; for (int i = 1; i < stackSize; i++) { int value = stack[i]; // Neuer Höchstwert ? if (value > maxValue) { // neuen speichern maxValue = value; } // Neuer Niedrigstwert? if (value < minValue) { // neuen speichern minValue = value; } all += value; } // Höchst und Niedrigstwert wieder abziehen all -= minValue; all -= maxValue; // Mittelwert bilden all = all / (stackSize - 2); return all; }