/* Außenlicht-Steuerung für Adelsbergturm 3.
3 Taster, 6 Relais, Bediengesten
henni, 181116
Hardware:
1 5P Versorgungsspannung vom Festspannungsregler 78L05
2 PB0 Lokale Taste-2-LED, H-aktiv
3 PB1 Lokale Taste-1-LED, H-aktiv
4 PB3 RESET frei; evtl. Bewegungsmelder, Lichtmelder; schaltbare Steckdose
5 PB2 Taste 3 (am Eingangstor), L-aktiv
6 PA7 Taste 2 (im Haus), L-aktiv: Licht auf Vorplatz + im Wald
7 PA6 Taste 1 (im Haus), L-aktiv: Licht am Haus + Eingangstor
8 PA5 Relais 1: Stromstoßrelais, H-aktiv Licht am Haus
9 PA4 Relais 2: Relais außen, H-aktiv Eingangstor
10 PA3 Relais 3: -"- Platz vorn
11 PA2 Relais 4: -"- Platz hinten
12 PA1 Relais 5: -"- Wald vorn
13 PA0 Relais 6: -"- Wald hinten
14 00 Bezugspotenzial
Die Hardware erlaubt die Verwendung von LED-beleuchteten Tasten.
Eine Wiedergabe von Schaltzuständen über die LED ist hier nicht vorgesehen.
Taster und Relais werden mit 12 V betrieben. (Grundsatzentscheidung.)
Die Schaltschwelle liegt bei 2 V.
Die Stromaufnahme ist minimal und wird im wesentlichen vom Querstrom
des Festspannungsreglers 78L05 bestimmt (6 mA).
Ein LP2950 wäre hier besser, mit 100 µA Querstrom.
Die Stromaufnahme des Mikrocontrollers liegt deutlich unter 100 µA.
Das Transistorgrab für die Relais hätte man auch durch einen
ULN2003 ersetzen können, aber es war gerade keiner zur Hand.
Anschlussbelegung der Schraubklemmen des 2-TE-Hutschienenmoduls
1 00 = Bezugspotenzial
2 12P = Stromversorgung
3 ohne Schraube
4 !T3 = Taste 3 gegen 00, mit 1 kΩ gegen 12P
5 !T2 = dito, parallel obere Lokaltaste
6 !T1 = dito, parallel untere Lokaltaste
7 !R6 = Relaistreiber-Ausgang 6 gegen 12P mit Freilaufdiode
8 !R5
9 !R4
10 !R3
11 !R2
12 !R1
Software-Funktion:
Generell simuliert der Mikrocontroller 6 Stromstoßrelais:
Ein Tastendruck schaltet ein, der nächste schaltet aus.
Zusätzlich realisiert ist ein globales Timeout = Licht aus
nach einer langen Zeit, gegen „vergessene“ Lichter,
sowie 2 Bediengesten:
* Betätigung kurz hintereinanderweg (innerhalb 1 Sekunde)
schaltet zwischen verschiedenen Gruppen
(hier Platzbeleuchtung vorn oder hinten)
* Langes Festhalten des Tasters schaltet zwischen anderen Gruppen
(hier Platz- oder Waldbeleuchtung an Taster 2)
Der Außentaster für die Allgemeinheit kann ebenfalls Licht schalten,
dann mit kurzem Timeout von 2 Minuten („Treppenlicht“),
trotzdem kann mit diesem Taster auch ausgeschaltet werden
(keine „Verlängerung“ = Retrigger; dann muss man halt 2× drücken)
und zwischen Lichtgruppen umschalten.
Die Kombination von Mikrocontroller und normalem Relais
ist viel billiger als ein Eltako (= Stromstoßrelais-Marke)
und bietet diese vielen Features mit nur wenigen Tasten.
Verwendete Peripherie: Timer0, EEPROM
Takt: 128-kHz-Oszillator
*/
#include <avr/io.h>
#include <avr/fuse.h>
#include <avr/signature.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <stdbool.h>
typedef uint8_t byte;
FUSES={
0xE4, // 128-kHz-Oszillator ohne Taktteilung; gemessen: 125 kHz
0xD7, // EESAVE
0xFF,
};
// 16 kHz / 256 = 64 Hz: Prozessor aufwecken
EMPTY_INTERRUPT(TIM0_OVF_vect);
// Zeiten ausgehend von der 62-Hz-Interruptfrequenz:
#define KTO 62 // 1 s Tasten-Timeout (= langer Tastendruck)
#define TO1 (64L*60*60*2) // 2 Stunden
#define TO3 (64L*60*2) // 2 Minuten
#define TS 16 // Schalt- und Pausenzeit für Stromstoßrelais: 0,25 s
// Bit 6 = Taste 1
// Bit 7 = Taste 2
// Bit 3 = Taste 3
static byte keyState() {
return ~PINA&0xC0 | ~PINB&0x04; // H = Taste gedrückt
}
byte t1,t2,t3; // Timeout für Tasten
byte ts; // Timeout für Stromstoßrelais
bool r1; // Zustand des Stromstoßrelais (EEPROM!)
bool ko; // Letzter Tastendruck war außen
bool t1l; // Langer Tastendruck für Taste 1 schaltete Platzbeleuchtung ein
static void handle_ts() {
if (ts && --ts==TS) PORTA&=~0x20;
}
static void stoss() {
while (ts) { // Lokale Warteschleife
sleep_cpu();
handle_ts();
}
r1=!r1;
PORTA|=0x20;
ts=TS*2;
while (EECR&0x02);
EEDR=1; // EEAR ist noch 0
cli();
EECR=r1?0x24:0x14; // "Nur programmieren" oder "Nur löschen"
EECR|=0x02; // Aktion ausführen
sei();
}
static void off1() {
PORTA&=~0x10; // Relais 2 aus
if (r1) stoss(); // Relais 1 ansteuern
if (t1l) {
PORTA&=~0x0C; // Relais 3 und 4 (Platzbeleuchtung) aus
t1l=false;
}
}
static void off2() {
PORTA&=~0x0F; // alle 4 Relais aus
}
static void off3() {
off1(); off2();
}
// Relais-Schaltstatus
static byte state() {
return r1<<5|PORTA&0x1F;
}
int main() __attribute__((noreturn));
int main() {
MCUCR = 0x20; // <sleep> aktivieren
PRR = 0x0B; // Kein Timer1, kein USI, kein ADC
ACSR |= 0x80; // Kein Analogvergleicher
while (EECR&0x02);
EEARL = 0;
EECR = 0;
EECR |= 1;
if (EEDR!=0xFF) r1=true;
PORTA = 0xC0; // Pullups für Tasten (die mit Dioden entkoppelt angebunden sind)
PORTB = 0x0C;
DDRA = 0x3F;
DDRB = 0x03;
TCCR0B= 0x02;
TIMSK0= 0x01; // Überlauf mit 64 Hz; Byte-Zählumfang dann 4 s
byte ks=keyState();
byte th=th; // Überlaufzähler für T0 (Eigeninitialisierung vermeidet gcc-Warnung)
uint32_t to=TO3; // Gesamt-Timeout für Lampen-Leuchtdauer
// Falls Stromstoßrelais nach Stromausfall ein, dann 2 Minuten Timeout annehmen
sei();
for(;;) {
sleep_cpu(); // Einzige Interruptquelle = Zeitgeber
handle_ts(); // Stromstoßrelais behandeln
// 1. Tasten-Flanken auswerten
byte nk=keyState();
byte fl=nk&~ks; // Steigende Flanken
if (fl&0x40) { // Taste 1 gedrückt?
byte s=state()&0x30;
if (!s) stoss(); // => 0x20
else if (t1||ko) switch (s) {
case 0x20: PORTA|=0x10; break; // => 0x30 (Weiterschalten innerhalb 1 s, sonst aus)
case 0x30: stoss(); break; // => 0x10
default: off1();
}else off1(); // Bei abgelaufenem Timeout stets ausschalten
t1=KTO; // Bei jedem Tastendruck <t1> starten
}
if (fl&0x80) { // Taste 2 gedrückt?
byte s=state()&0x0F;
if (!s) PORTA|=0x0C; // => 0x0C Beide Platz-Lampen ein
else if (t2||ko) switch (s) {
case 0x0C: // => 0x08
case 0x0F:
case 0x03: PORTA&=0xFA; break; // Nur vorn
case 0x08: // => 0x04
case 0x0A:
case 0x02: PORTA=PORTA&0xF0|s>>1; break; // Nur hinten
default: off2();
}else off2();
t2=KTO;
}
if (fl&0xC0) {ko=false;} // Langes Timeout beim Betätigen innerer Tasten
if (fl&0x04) { // Äußere Taste?
byte s=state()&0x38; // Nicht die Wald-Lampen sowie hintere Platz-Lampe schalten
if (!s) {PORTA|=0x08; stoss();ko=true;} // Beim EINSCHALTEN von außen kurzes Timeout generieren
else if (t3||!ko) switch (s) {
case 0x28: PORTA|=0x10; break; // Beim WEITERSCHALTEN von außen Timeout-Auswahl unverändert
case 0x38: PORTA&=~0x08; break;
case 0x30: stoss(); break;
default: off3();
}else off3();
t3=KTO;
}
if (fl) to=ko?TO3:TO1; // Kurzes oder langes Timeout setzen
ks=nk;
// 2. Zeit / Timeout auswerten
if (t1 && !--t1 && ks&0x40) { // Taste 1 länger als 1 s gedrückt
t1=KTO; // Keine Aktion bei langem Tastendruck
t1l=true;
byte s=state()&0x30;
PORTA=PORTA&0xF3^s>>2; // Platzbeleuchtung schalten
// (als Hintertür für innere Schaltstellen mit nur 1 Taste;
// Waldbeleuchtung kann damit nicht geschaltet werden)
}
if (t2 && !--t2 && ks&0x80) { // Taste 2 länger als 1 s gedrückt?
t2=KTO; // neuer Timer mit 1 s
byte s=PORTA&0x0F;
switch (s) {
case 0x01:
case 0x02:
case 0x03: PORTA|=s<<2; break;
case 0x04:
case 0x08:
case 0x0C: PORTA=PORTA&0xF0|s>>2; break;
default: PORTA&=~0x03;
}
}
if (t3 && !--t3 && ks&0x04) {
t3=KTO; // Keine Aktion bei langem Tastendruck
}
if (!--to) off3(); // Generelles Timeout: Alles aus
// 3. LEDs steuern, Blinkfrequenz 2 Hz (5 Bit)
bool pha=(++th&0x1F)<26;
PORTB&=~0x02; if (pha && r1 || !pha && PORTA&0x10) PORTB|=0x02;
PORTB&=~0x01; if (pha && PORTA&0x05 || !pha && PORTA&0x0A) PORTB|=0x01;
// Der Schaltzustand zwischen Platz- und Waldlampen wird nicht angezeigt.
}
}
Detected encoding: UTF-8 | 0
|