ATTINY RC Switch

ATTINY RC Switch

klein und flexibel… 1 ATTINY 45 (uC), 2 Widerstände, 2 LED’s und das war’s. 
Bauteile Kosten ca. 2-3 €. 1-2 mehr € für die Digispark Variante  

🧩 Schaltungsbeschreibung (Hardware)

Ziel:
Ein einfaches RC-Schaltmodul, das auf das PWM-Signal eines RC-Empfängers reagiert (z. B. vom Sender eines Modells) und zwei Ausgänge schaltet.

Aufbau:

U1 – ATtiny45/85:
Der Mikrocontroller wertet das RC-Signal aus und steuert zwei Ausgänge (Q1 und Q2).

RCIN (J2):

Pin 1: VCC (+5 V vom Empfänger)

Pin 2: RC-Signal (typisch 1–2 ms @ 50 Hz)

Pin 3: GND

Ausgänge (J1):

Pin 1: Ausgang Q1 → LED D1 zeigt diesen Status

Pin 2: Ausgang Q2 → LED D2 zeigt diesen Status

Pin 3: GND

LEDs D1/D2:
Über die Vorwiderstände R1/R2 (je 330 Ω) mit den Pins PB0 und PB1 verbunden.
Sie dienen als optische Anzeige der Schaltzustände (können später auch Transistorstufen oder Relais ansteuern).

Versorgung:
Der ATtiny erhält 5 V direkt vom Empfänger (VCC = 5 V, GND).

Software Code

/******************************************************************
Project     :  RC-Switch mit 2 Ausgängen (Q1, Q2) + optionalem Toggle
CPU         :  ATtiny45/85 oder Digispark
Signal      :  Servo-PWM 1–2 ms @ ~50 Hz an PB2
Ausgänge    :  Q1 = PB0, Q2 = PB1 (z.B. LEDs/Transistorstufen)
Autor       :  Matthias Drinkmann (2020–25)

Kurzbeschreibung:
- Liest das PWM-Servosignal eines RC-Empfängers.
- Teilt den Puls in drei Bereiche ein: Unten (<TH_LOWER), Mitte, Oben (>TH_UPPER).
- Schaltet daraus zwei Ausgänge:
    * Momentanbetrieb:  Oben → Q1 EIN, Unten → Q2 EIN, Mitte → beide AUS.
    * Togglebetrieb:    Beim stabilen Eintritt in Oben/Unten wird Q1/Q2 umgeschaltet
                        (Latch). Optional erst nach Rückkehr in die Mitte (Center-Reset).
- Keine Failsafe-Funktion enthalten, da nicht sinvoll und keine zuverlässige Funktion
LIZENZ: Public Domain / ohne Gewähr
******************************************************************/

// ---------------------- Konfiguration ---------------------------
// Pins (ATTiny85-Arduino-Kennung: 0=PB0, 1=PB1, 2=PB2, ...)
#define RC_PIN      2   // PB2 (Pin 7) — RC-Signaleingang (vom Empfänger)
#define Q1_PIN      0   // PB0 (Pin 5) — Ausgang Q1 / LED1
#define Q2_PIN      1   // PB1 (Pin 6) — Ausgang Q2 / LED2

// Schaltschwellen um die Mittelstellung (in Mikrosekunden)
const uint16_t MID_US       = 1500;                 // nominelle Mitte ~1.5 ms
const uint16_t DEADBAND_US  = 120;                  // Totzone ±120 µs zur Entkopplung
const uint16_t TH_UPPER     = MID_US + DEADBAND_US; // > → "Oben"-Zone
const uint16_t TH_LOWER     = MID_US - DEADBAND_US; // < → "Unten"-Zone

// Messparameter für pulseIn (keine Failsafe-Logik!)
const uint16_t PULSE_MIN    = 800;                  // minimal plausibler Puls
const uint16_t PULSE_MAX    = 2200;                 // maximal plausibler Puls
const uint32_t TIMEOUT_US   = 30000;                // Timeout pro pulseIn (us)

// Ausgangspegel: true = aktiv HIGH (LED an bei HIGH / Transistor an)
// Falls deine Treiberstufe invertiert ist, setze auf false.
const bool     ACTIVE_HIGH  = true;

// -------- Toggle-Optionen (pro Ausgang) -------------------------
// 0 = Momentanbetrieb (klassisch), 1 = Toggle-/Latch-Betrieb
#define TOGGLE_Q1   1   // Q1 reagiert auf Zone "Oben"
#define TOGGLE_Q2   1   // Q2 reagiert auf Zone "Unten"

// Optional: Für einen neuen Toggle muss vorher die Mitte passiert werden.
// Verhindert Mehrfach-Toggles bei Jitter am Zonenrand.
#define REQUIRE_CENTER_RESET  1   // 1 = Mitte zwingend, 0 = sofort wiederholbar

// Mindestaufenthalt in einer Zone, bevor diese als "stabil" gilt (ms)
// Entprellung gegen Jitter/Noise im Servosignal.
const uint16_t ZONE_MIN_MS   = 30;

// ----------------------------------------------------------------

// Zonenklassifizierung
enum Zone : uint8_t { Z_NONE, Z_LOWER, Z_MID, Z_UPPER };

// Zeitmarker für Entprellung
uint32_t zone_enter_ms = 0;  // Zeitpunkt, an dem die aktuelle Zone erstmals erkannt wurde

// "Roh"-Zonen-Tracker (für Entprellung) und letzter bestätigter Zustand
Zone     last_zone     = Z_NONE; // letzte bestätigte (stabile) Zone

// Latch-/Momentan-Zustände der Ausgänge
bool q1_state = false;  // aktueller Zustand Q1 (wirkt direkt auf Pin via setOutputs)
bool q2_state = false;  // aktueller Zustand Q2

// Armierung der Toggles, wenn Center-Reset aktiv ist
bool q1_center_armed = true;  // Q1 darf toggeln, wenn zuvor Mitte passiert wurde
bool q2_center_armed = true;  // Q2 darf toggeln, wenn zuvor Mitte passiert wurde

// ---------------------- Hilfsfunktionen -------------------------

// Setzt die beiden Ausgänge (inkl. optionaler Invertierung)
void setOutputs(bool q1_on, bool q2_on) {
  // XOR mit !ACTIVE_HIGH invertiert bei Bedarf den Ausgangspegel
  digitalWrite(Q1_PIN, (q1_on ^ !ACTIVE_HIGH) ? HIGH : LOW);
  digitalWrite(Q2_PIN, (q2_on ^ !ACTIVE_HIGH) ? HIGH : LOW);
}

// Ordnet eine Pulslänge einer Zone zu
Zone classifyPulse(uint32_t p) {
  if (p > TH_UPPER) return Z_UPPER;  // Knüppel oben
  if (p < TH_LOWER) return Z_LOWER;  // Knüppel unten
  return Z_MID;                      // innerhalb der Totzone → Mitte
}

// --------------------------- Setup ------------------------------
void setup() {
  // RC-Eingang: üblicherweise DIREKT vom Empfänger (Push-Pull). Kein Pullup nötig.
  // Falls die Leitung offen "floatet", externen Pull-Down (47–100 kΩ) vorsehen.
  pinMode(RC_PIN, INPUT);

  // Ausgänge initialisieren
  pinMode(Q1_PIN, OUTPUT);
  pinMode(Q2_PIN, OUTPUT);
  setOutputs(false, false);          // beide aus

  // Startwerte für Entprellung und Logik
  last_zone     = Z_MID;             // mit "Mitte" beginnen
  zone_enter_ms = millis();          // Startzeitpunkt für Zone
  q1_state = false;
  q2_state = false;
  q1_center_armed = true;
  q2_center_armed = true;
}

// ---------------------------- Loop ------------------------------
void loop() {
  // 1) Pulsdauer messen (HIGH-Phase des Servosignals)
  //    pulseIn blockiert bis TIMEOUT_US oder bis die gewünschte Flanke erkannt wurde.
  //    Das ist für kleine ATTiny-Sketches ausreichend präzise und kompakt.
  unsigned long p = pulseIn(RC_PIN, HIGH, TIMEOUT_US);

  // 2) Nur plausible Pulslängen auswerten, Rest ignorieren
  if (p >= PULSE_MIN && p <= PULSE_MAX) {

    // 3) Zone aus der Pulslänge bestimmen
    Zone z = classifyPulse(p);

    // 4) Entprellung: Wechsel? → Stoppuhr neu starten
    static Zone last_raw = Z_NONE;         // letzte "rohe" (unbestätigte) Zone
    if (z != last_raw) {
      last_raw = z;
      zone_enter_ms = millis();            // ab jetzt Verweilzeit in neuer roher Zone messen
    }

    // 5) Gilt die Zone schon als stabil?
    if (millis() - zone_enter_ms >= ZONE_MIN_MS) {
      // "Stabile" Zone übernehmen
      last_zone = z;

      // ---------------------- Toggle-/Momentanlogik ----------------------

      // a) Mitte:  Momentanbetrieb → Ausgänge aus; Toggle-Betrieb → bleibt unverändert.
      //    Zusätzlich: Center-Reset-Armierung setzen (falls gefordert).
      if (z == Z_MID) {
        if (!TOGGLE_Q1) q1_state = false;  // Momentanbetrieb → aus
        if (!TOGGLE_Q2) q2_state = false;  // Momentanbetrieb → aus

        if (REQUIRE_CENTER_RESET) {
          // ab jetzt darf beim nächsten Betreten von Oben/Unten wieder getoggelt werden
          q1_center_armed = true;
          q2_center_armed = true;
        }
      }

      // b) Oben-Zone:  Q1 steuern
      if (z == Z_UPPER) {
        if (TOGGLE_Q1) {
          // Toggle-Bedingung:
          // - mit Center-Reset: nur wenn vorher Mitte passiert wurde (armed)
          // - ohne Center-Reset: jedes Mal, wenn die Zone stabil erreicht wird
          bool may_toggle = (REQUIRE_CENTER_RESET ? q1_center_armed : true);
          if (may_toggle) {
            q1_state = !q1_state;         // Q1 umschalten
            if (REQUIRE_CENTER_RESET) {
              q1_center_armed = false;    // bis zur nächsten Mitte gesperrt
            }
          }
        } else {
          // Momentanbetrieb: in Oben → Q1 ein
          q1_state = true;
        }

        // Falls Q2 im Momentanbetrieb: in Oben grundsätzlich aus
        if (!TOGGLE_Q2) q2_state = false;
      }

      // c) Unten-Zone: Q2 steuern
      if (z == Z_LOWER) {
        if (TOGGLE_Q2) {
          bool may_toggle = (REQUIRE_CENTER_RESET ? q2_center_armed : true);
          if (may_toggle) {
            q2_state = !q2_state;         // Q2 umschalten
            if (REQUIRE_CENTER_RESET) {
              q2_center_armed = false;    // bis zur nächsten Mitte gesperrt
            }
          }
        } else {
          // Momentanbetrieb: in Unten → Q2 ein
          q2_state = true;
        }

        // Falls Q1 im Momentanbetrieb: in Unten grundsätzlich aus
        if (!TOGGLE_Q1) q1_state = false;
      }

      // 6) Physische Ausgänge setzen
      setOutputs(q1_state, q2_state);
    }
  }

  // 7) Kleine Pause: entlastet CPU, beruhigt pulseIn-Zeitverhalten
  delay(5);
}

Simulation der Schaltung (YouTube)


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert