1/2-Kanal Schalter

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.

12Switch.ino
/*
 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;
}