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

/*
Badlüftersteuerung mit 2 Geschwindigkeiten
via Frequenzdrittelung am Triac

Hardware: ATtiny13
Pin	Port	Funk.
1	PB5	!RESET	(Triac für Trafo Spiegelheizung)
2	PB3		Lichtschalter, 50 Hz low-aktiv
3	PB4		Zwangseinschaltung, 50 Hz low-aktiv
4	GND
5	PB0	OC0A	Triac-Gate, low-aktiv
6	PB1	INT0	Netzfrequenz und -Phase
7	PB2	ADC1	Temperatursensor an Warmwasserleitung
8	Ucc

Funktion:
Vorbild: Übliche Lüftersteuerung.

Einsatzerfahrung:
*/

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

#define GATELEN 8	// Länge Gateimpuls in 125 µs: 8 = 1 ms
#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 TEMPON 128	// aktiviert temperaturbedingte volle Drehzahl
#define TEMPOFF 126	// mit Hysterese

FUSES={
 0x7B,	// Stromspar-Oszillator 128 kHz ohne Taktteiler
 0xF9,	// Brown-Out unter 4,3 V
};

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

register byte t0h asm("r2");	// High-Teil des Zählers
register byte t180 asm("r3");	// Zeit für 180° Phasenwinkel
register byte light asm("r4");	// Zeitzähler für Licht-Ein in 5 s
register byte dlyct asm("r16");	// Verzögerung in 5 s, max. 22 Minuten
register byte flags asm("r17");	// diverse Bits:
// Bit 1:0: Phasenzykluszähler 0..2
// Bit 2: Eingeschaltet auf 1/3 Drehzahl (Lichtschalter)
// Bit 3: Eingeschaltet auf voller Drehzahl (Licht aus)
// Bit 4: Eingeschaltet auf voller Drehzahl (Zwangsschalter)
// Bit 5: Eingeschaltet auf voller Drehzahl (Temperatur von Dusche)
// Bit 6: Vorheriger Zustand des Lichtschalters

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

static void trig(byte p) {
 byte n=flags&0x07;
 if (flags&0x38 || n==p) {
  TCCR0A=0x80;	// OC0A bei Compare-Match löschen
  TCCR0B=0x82;	// Jetzt löschen (Triac bekommt Gatestrom)
  TCCR0A=0xC0;	// OC0A bei Compare-Match setzen lassen
 }
 n=~flags&0x60;
 if (!n) {
  PORTB&=~0x20;
  TIFR0 =0x04;
  TIMSK0=0x0E;
 }
}

// Phase wird negativ (etwas nach dem Nulldurchgang wegen Spannungsteiler)
// Aufruf mit 50 Hz (INT0 High-Low-Flanke)
ISR(INT0_vect,ISR_NAKED) {
 byte t=TCNT0;		// Zählerwert abholen
 if (TIMSK0&0x02) {	// Erster Nulldurchgang entdeckt? (Länge gültig?)
  if (t>=128) {		// muss zwischen 128 und 255 liegen, sonst Störimpuls
   TCNT0=0;		// Zähler starten
   t180=t>>1;		// Spiegelbild: Phase positiv
   OCR0B=t180>>1;	// 90°
   TIFR0 =0x0A;		// Interrupts quittieren
   TIMSK0=0x0A;		// T0-Vergleich-B-Interrupt aktivieren
   flags++;
   t=~flags&3;
   if (!t) flags&=0xFC;
   trig(0x04);
   OCR0A=GATELEN;	// später
   if (dlyct && !--dlyct) {
    if (flags&0x40) flags|=0x04;	// 1/3 ein
    else{
     if (flags&0x08) {
      light =0;	// Zähler tilgen
     }else{
      if (light>=BLOWIGN) {
       flags|=0x08;
       if (light<BLOWMIN) light=BLOWMIN;
      }else{
       flags&=~0x04;	// aus
       light =0;	// nicht akkumulieren (wäre verwirrend)
      }
     }
    }
   }
   if (!++t0h) {	// Überlauf alle ≈5 Sekunden
    ADCSRA=0xD8;	// A/D-Wandler starten (mit Interrupt)
    if (flags&0x40 && light<BLOWMAX) ++light;	// Einschaltzeit messen
    if (flags&0x08 && !--light) flags&=~0x0C;	// Lüfter aus
   }
  }
 }else{
  TCNT0=0;	// Zähler starten
  TIFR0 =0x02;	// Vorherige T0-Überläufe quittieren
  TIMSK0=0x02;	// T0-Überlauf-Interrupt aktivieren
 }
 GIFR=0x40;	// Schwarm-Interrupts tilgen
 reti();
}

// Software-Ausschaltflanke für Spiegelheizungs-Triac (an !RESET)
// Aufruf mit 100 Hz
ISR(TIM0_COMPA_vect,ISR_NAKED) {
 PORTB|=0x20;
 TIMSK0=0x0A;
 reti();
}

// 1. Abtastzeitpunkt für Tasten bei 90°
// 2. Phase wird (= ist jetzt sicher) positiv bei 180°
// Aufruf mit 2×50 Hz
ISR(TIM0_COMPB_vect,ISR_NAKED) {
 if (OCR0B<64) {	// im Bereich 32..63
  DIDR0=0x27;		// Digitaleingänge aktivieren
  OCR0B=t180;
  asm("rjmp .");	// 2 Takte warten
  if (PINB&0x08) {	// Licht aus?
   if (flags&0x40) {
    flags&=~0x40;
    if (flags&0x04) {
     dlyct=OFFDELAY;	// zum Ausschalten
    }else{
     dlyct=0;		// Zeitgeber anhalten
    }
   }
  }else{		// Licht an?
   if (!(flags&0x40)) {
    flags|= 0x40;
    flags&=~0x08;	// Auf 1/3 zurück
    dlyct=ONDELAY;
   }
  }
  if (PINB&0x10) {	// Zwang aus?
   flags&=~0x10;
  }else{		// Zwang ein
   flags|= 0x10;
  }
 }else{			// im Bereich 64..127
//  DIDR0=0x3D;
//  asm("rjmp .");	// 2 Takte warten
//  if (PINB&0x02) {	// muss high sein, sonst Problem (induktive Last — oder der Trenntrafo?)
  trig(0x05);
  OCR0A=OCR0B+GATELEN;	// später
//  }else{
//   flags =0;	// alles aus
//   TIMSK0=0;
//   dlyct =0;
//   light =0;
//  }
 }
 DIDR0=0x3F;		// Eingänge wieder deaktivieren
 reti();
}

// Aufruf alle 5 Sekunden
ISR(ADC_vect,ISR_NAKED) {
 byte adc=ADCH;		// Analogwert abholen
 ADCSRA=0;		// A/D-Wandler stoppen
 if (flags&0x20) {
  if (adc<TEMPOFF) flags&=~0x20;
 }else{
  if (adc>=TEMPON) flags|=0x20;	// Heißleiter zieht Eingang hoch
 }
 reti();
}

int main() {
 CLKPR =0x80;
 CLKPR =0x01;	// 128 kHz / 2 = 64 kHz (genau richtig)
 PORTB =0x19;	// Pullups für Eingänge
 TCCR0A=0xC0;	// OC0A bei Compare-Match setzen
 TCCR0B=0x82;	// loszählen mit Vorteiler 8, Periode = 31 Hz, Compare-Match auslösen
 DDRB  =0x21;	// Triac-Gate = Ausgang
 MCUCR =0x22;	// Sleep aktivieren, INT0-Interrupt bei fallender Flanke
 GIMSK =0x40;	// INT0-Interrupt aktivieren
 DIDR0 =0x3F;	// Alle Eingänge aus
 ACSR  =0x80;	// Analogvergleicher aus
 ADMUX =0x21;	// 8 Bit, ADC1 = PB2, ratiometrisch bzgl. Ucc = 5 V
 asm(
"	clr	r4	\n"
"	clr	r16	\n"
"	clr	r17	\n"
);
 sei();		// zunächst ist INT0 die einzige Interruptquelle
 for(;;) {
  sleep_cpu();	// der Rest ist ISR-gesteuert da wenig rechenaufwändig
 }
}
Detected encoding: UTF-80