Source file: /~heha/basteln/Haus/Lüfter/luft4.zip/main.cpp

/*
Badlüftersteuerung für Helios-Lüfter 100 m²/h (40 W) mit 2 Geschwindigkeiten
via Phasenanschnitt am Triac, netzverbunden

Hardware: ATtiny13
Pin	Port	Funktion
1	PB5	!RESET	-
2	PB3		Debug
3	PB4		Netzfrequenz und -Phase
4	GND
5	PB0	OC0A	Triac-Gate, low-aktiv
6	PB1		Zwang: Wenn KEINE Impulse kommen
7	PB2		Licht: Wenn KEINE Impulse kommen
8	Ucc
Die 3 Flankeneingänge haben externe Pulldowns.

Funktion:
Vorbild: Übliche Lüftersteuerung.
Vorgesehene Erweiterungen (mit Netztrennung, anderer Mikrocontroller):
+ Temperatursensor am Warmwasserrohr zur Dusche
+ Klodeckel-hochgeklappt-Sensor (Magnetschalter SM351LT)
+ Sitzende Person (Dehnmessstreifen am Klogestell)
+ Sperre für Raumluft (= volle Kloabsaugung)
+ Spiegelheizung
+ USB

Einsatzerfahrung:
16,7-Hz-Geräusche erinnern ans Bahnfahren, leiser als bei Phasenanschnitt.
Eine andere Art der Drehzahlreduktion muss her.
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/fuse.h>
#include <avr/signature.h>	// gepatcht: "const" 'raus

#define GATELEN 16	// Länge Gateimpuls in 125 µs: 16 = 2 ms (1 ms reicht im Versuch nicht!)
#define ONDELAY 50	// Einschaltverzögerung in 20 ms: 50 = 1 Sekunde
#define OFFDELAY 50	// Ausschaltverzögerung in 20 ms: 50 = 1 Sekunde
#define BLOWIGN 12
#define BLOWMIN 12	// Ausblaszeit nach Licht in 5-s-Schritten
#define BLOWMAX 60
#define FLANKEN 16	// Verzögerung der Wirkung der beiden Eingänge in 20 ms: 16 = 0,3 s
// Schwingungspaketsteuerung erweist sich am Kondensatormotor als geräuschärmer.
// Die Leistungsaufnahme verringert sich dabei nur von 40 W auf 30 W,
// während sich der Luftstrom vermutlich halbiert.
// Besser wäre wohl ein Vorwiderstand

FUSES={
 0x7B,	// Stromspar-Oszillator 128 kHz ohne Taktteiler
 0xFB,	// Brown-Out unter 2,7 V
};

typedef unsigned char byte;
#define NOINIT __attribute__((section(".noinit")))

register byte t0h asm("r2");	// Zählt 20-ms-Abschnitte, Überlauf bei ≈5 s
register byte t180 asm("r3");	// Zeit für 180° Phasenwinkel = halber Zählerwert bei negativer Flanke
register byte light asm("r4");	// Zeitzähler für Licht-Ein in 5 s
register byte opinb asm("r5");	// Vorhergehendes PINB
register byte cnti1 asm("r6");	// Zähle Flanken an PB1: runter bei Flanke (100 Hz), hoch mit 50 Hz
register byte cnti2 asm("r7");	// Zähle Flanken an PB2: runter bei Flanke (100 Hz), hoch mit 50 Hz
register byte dlyct asm("r16");	// Verzögerung in 5 s, max. 22 Minuten
register byte flags asm("r17");	// diverse Bits:
// Bit 0: Lichtschalter Ein
// Bit 1: Zwangslüftung Ein
// Bit 2: Eingeschaltet auf reduzierter Drehzahl (Lichtschalter)
// Bit 3: Eingeschaltet auf voller Drehzahl (Licht aus)
// Bit 7+6: Zykluszähler für reduzierte Drehzahl (2/3 Leistung)

static void initTimer() {
 TCCR0A=0xC0;	// OC0A bei Compare-Match setzen
 TCCR0B=0x82;	// loszählen mit Vorteiler 8, Periode = 31 Hz, Compare-Match auslösen
}

// Der Zähler darf nie überlaufen, sonst fehlen Nulldurchgänge
ISR(TIM0_OVF_vect,ISR_NAKED) {
 initTimer();
 flags =0;		// alles aus
 TIMSK0=0;
 dlyct =0;
 light =0;
 reti();
}

// Aufruf mit 50 Hz: Inkrementiert cnti1 und cnti2 bis 16
static void handle50Hz() {
 if (cnti1&FLANKEN) {	// Anschlag bei 16: Licht ein
  if (!(flags&1)) {	// Licht war aus?
   flags|=1;		// Licht an
   flags&=~0x08;	// Falls auf voller Leistung, Leistung reduzieren
   dlyct=ONDELAY;
  }
 }else ++cnti1;
 if (cnti2&FLANKEN) flags|=2; else ++cnti2;	// Anschlag bei 16: Zwangslüftung ein
}

static void handleDly() {
 if (flags&1) flags|=0x04;	// Mit verringerter Leistung anfangen
 else{				// Licht aus?
  if (flags&0x08) light=0;	// Volle Leistung? Zähler tilgen
  else{
   if (light>=BLOWIGN) {	// War Mindestzeit ein?
    flags|=0x08;		// Volle Leistung
    if (light<BLOWMIN) light=BLOWMIN;	// für minimale Zeit
   }else{
    flags&=~0x04;		// ganz aus
    light =0;			// nicht akkumulieren (wäre verwirrend)
   }
  }
 }
}

static void fire() {
 byte t=OCR0B;
 OCR0A=t+GATELEN;	// Zündimpuls später wegnehmen; Zeit wegen induktiver Last
 if (t<128) OCR0B=t+t180;	// Nächsten Zündimpuls vorbereiten
 else TIMSK0=0x02;	// Keine weiteren Impulse auslösen
 TCCR0A=0x80;		// Clear OCR0A on Compare Match
 TCCR0B=0x82;		// OCR0A = LOW: Triac zünden
 TCCR0A=0xC0;		// Set OCR0A on Compare Match (macht die Hardware)
}

// Phase bei (0° oder 180°) plus Phasenwinkel
// Aufruf mit 100 Hz
ISR(TIM0_COMPB_vect,ISR_NAKED) {
 fire();
 reti();
}

ISR(PCINT0_vect,ISR_NAKED) {
 byte cpinb=PINB^opinb;	// Pegelwechsel-Bits
 opinb^=cpinb;
 if (cpinb&0x10 && !(opinb&0x10)) {	// Fallende Flanke
// Phase wird negativ (etwas nach dem Nulldurchgang wegen Spannungsteiler), mit 50 Hz
  if (TIMSK0&0x02) {	// Zweiter Nulldurchgang? (Länge gültig?)
   byte t=TCNT0;	// Zählerwert abholen
   if (t>=128) {	// muss zwischen 128 und 255 liegen, sonst Störimpuls: ignorieren
    PORTB&=~8;
    flags+=64;		// Zykluszähler
    if (!(flags&0xC0)) flags|=64;	// stets 1-2-3 zählen
    TCNT0=0;		// Zähler starten
    t180=t>>1;		// Spiegelbild: Phase positiv
    OCR0B=0;
    TIFR0=0x08;		// Evtl. anhängigen OCR0B-Interrupt tilgen
    if (flags&0x0A
    || flags&0x04 && ~(flags|~0xC0)) {	// Bei 1-2 triggern, bei 3 nicht
     fire();
     TIMSK0=0x0A;
    }	// umgehend triggern
    if (dlyct && !--dlyct) handleDly();
    handle50Hz();
    if (!++t0h) {	// Überlauf alle ≈5 Sekunden
     if (flags&1 && light<BLOWMAX) ++light;	// Einschaltzeit messen
     if (flags&0x08 && !--light) flags&=~0x0C;	// Lüfter aus
    }
    PORTB|=8;
   }
  }else{
   TCNT0 =0;	// Zähler starten
   TIFR0 =0x02;	// Vorherige T0-Überläufe quittieren
   TIMSK0=0x02;	// T0-Überlauf-Interrupt aktivieren
  }
 }
// Bei (beiden) Pegelwechseln cnti1 und cnti2 dekrementieren bis 0
 if (cpinb&4 && cnti1 && !--cnti1 && flags&1) {	// Anschlag bei 0: Licht aus
  flags&=~1;		// Jetzt aus
  if (flags&0x04) {
   dlyct=OFFDELAY;	// zum Ausschalten
  }else{
   dlyct=0;		// Zeitgeber anhalten
  }
 }
 if (cpinb&2 && cnti2 && !--cnti2) flags&=~2;
 reti();
}

int main() {
 CLKPR =0x80;
 CLKPR =0x01;	// 128 kHz / 2 = 64 kHz (genau richtig)
// PORTB =0x29;	// Keine Pullups, Ausgang auf H
 initTimer();
 DDRB  =0x09;	// Triac-Gate = Ausgang; PB3 = Debug-Ausgang
 MCUCR =0x20;	// Sleep aktivieren
 PCMSK =0x16;	// Da wackelt's
 GIMSK =0x20;	// Pegelwechsel-Interrupt aktivieren
 ACSR  =0x80;	// Analogvergleicher aus
 asm(
"	clr	r4	\n"
"	in	r5,%0	\n"	//opinb
"	clr	r6	\n"
"	clr	r7	\n"
"	clr	r16	\n"	//dlynct
"	clr	r17	\n"	//flags
::"I"(_SFR_IO_ADDR(PINB)));
 sei();		// Pegelwechsel zunächst einzige Interruptquelle
 for(;;) {
  sleep_cpu();	// der Rest ist ISR-gesteuert da wenig rechenaufwändig
 }
}
Detected encoding: UTF-80