/*
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
}
}
Vorgefundene Kodierung: UTF-8 | 0
|