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