Source file: /~heha/basteln/Haus/Licht/vf_rel/vf_rel.zip/vf_rel.c

/* 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-80