/*
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-8 | 0
|