Zuletzt aktualisiert: 09.04.2023
Aquaristik
Modellbau
Software
3D-Druck
Garten
Radreisen
Kontakt
Impressum

Kellerbelüftung

Viele Hinweise, Tipps und Ideen habe ich auf meinen Seiten zum Thema Kellerbelüftung und allgemeine Lüftung schon gegeben, nun möchte ich den Bau einer eigenen Kellerbelüftung vorstellen, um zu zeigen, dass man mit wenig Elektronik und ein wenig Knowhow praktisches Bauen kann.

Ersteinmal: Warum also das Ganze ? Hier ein paar Punkte:
  • Unser Keller hat vor allem im Sommer eine sehr hohe Luftfeuchtigkeit, das rührt daher, dass warme Luft an den Treppenhauswänden langsam nach unten absackt und sich dort sammelt.
  • Ein Kondenstrockner sorgt für zusätzlichen Feuchtigkeitseintrag.
  • Die Kellerfenster sind absichtlich relativ dicht, um im Sommer die feuchte warme Luft fernzuhalten, im Winter um Heizenergie zu sparen, da eine Hälfte des Kellers noch als Wohnraum genutzt wird.
  • Wir sind zu faul, immer zur richtigen Zeit zu lüften.
  • Jeder Bastler hat zuviele PC-Lüfter zum Basteln herumfliegen.
  • Die Zuverlässigkeit von "hochwertigen" Sensoren in Wetterstationen läßt leider sehr zu wünschen übrig, stellt man einen Außensensor neben einen Innenraumsensor, so wird man erhebliche Differenzen beim relativen Feuchtigkeitswert feststellen.

Zum zweiten ein paar Punkte, wie sich der Aufbau der "Anlage" gestaltet:
  • Zwei einfache PC-Lüfter kommen zum Einsatz. Der erste wird möglichst weit oben in der Scheibe des Kellerfensters verbaut. Er muss beim Laufen die Luft von innen nach außen pusten, in unserem Fall in den Lichtschacht. Ein kleines Gitter hält Insekten und Mäuse fern.
  • Der zweite PC-Lüfter wird möglichst weit unten im Kellerfenster verbaut. Er saugt über einen Luftschlauch die Luft vom oberen Ende des Lichtschachtes an und pustet sie nach innen. Auch hier sollte ein feines Gitter verwendet werden.
  • Beide PC-Lüfter hängen an derselben Stromzufuhr, keine Drehzahlregelung ist nötig.
  • Beide Lüfter werden über eine Klappe geöffnet und geschlossen, so findet bei stehenden Lüftern kein Luftaustausch statt. Die Klappe hat zu beiden Seiten hin Endschalter.
  • Jeweils ein Temperatursensor und ein Feuchtesensor messen innen und außen beide Werte und vergleichen sie. Lüften macht nur Sinn, wenn Feuchtigkeit UND Temperatur draußen geringer sind. Mit dieser logischen Verknüpfung ist man immer auf der sicheren Seite.
  • 10 Minuten pro Stunde wird gelüftet, wenn es wirklich möglich ist.
  • Die Sensoren sind nicht geeicht, außerdem will ich mir eine aufwendige Umrechnerei in absolute Feuchte ersparen. Der Temperatur- und Feuchtewert von innen und außen muss lediglich jeweils verglichen werden, hier reicht eine "<"-Relation völlig aus. Eine Eichung findet implizit statt, indem man zu Beginn beide Sensoren einige Stunden nebeneinander hängt und dann ihre Werte speichert. Diese Werte nimmt man als Offset und hat so eine Art Angleichung beider Werte.
  • 5 Leds zeigen den aktuellen Status an. Eine gelbe für den manuellen Modus. Zwei rote und zwei grüne, ob es sich jeweils für Temperatur oder Feuchte lohnt, zu lüften.
  • 3 Tasten sind nötig, manueller Modus Ein/Aus, Lüfter Ein/Aus, Sensoren abgleichen.
  • Die Offsetwerte der Sensoren müssen für den Fall einer Stromunterbrechung dauerhaft gespeichert werden.
  • Die Feuchtesensoren sind kapazitiv (110-150pF) (von Philips) und werden mithilfe eines NE555 ausgewertet. Dabei wird die Kapazität in eine Rechteckfrequenz umgesetzt, eine Änderung der Kapazität bewirkt eine Änderung der Pulsweiten.
  • Die Temperatursensoren sind einfache KTY-Sensoren (2000 Ohm).
  • Die Auswertung übernimmt ein Atmega8, mit externem 16MHz-Quarz um eine möglichst genaue Auswertung der NE555-Signale zu bekommen.

Als letztes geht es an die detailierte Beschreibung:

  • Für die Lüfter benötigen wir ein 12V Netzteil, ein digitaler AVR Ausgang am AVR schaltet beide über einen BD109 in Emitterschaltung ein und aus.
  • Die Klappe hat zwei Endschalter, die beide an ein jeweils einen digitalen Eingang gehen. Der Motor zur Bewegung der Klappe hat ein Getriebe und wird per PWM angesteuert. Das geschieht über zwei digitale Ausgänge, nämlich Ausgang 1 für auf, Ausgang 2 für zu. Die Ausgänge werden an eine H-Brücke gelegt, somit ist klar, dass die Software so gestaltet sein sollte, dass immer nur einer der digitalen Ausgänge auf High sein sollte, oder keiner. Die PWM-Ansteuerung wird relativ einfach über delays realisiert, hat die Klappe eine Endposition erreicht, wird der Strom vom Motor genommen.



  • Zwei analoge Eingänge sind für die ohmschen Temperatursensoren nötig.
  • Die Feuchtesensoren werden über jeweils einen NE555 in eine Rechteckfrequenz umgewandelt. Diese Wandlung sollte möglichst nah am Sensor durchgeführt werden, das Ausgangssignal kann dann über ein längeres Kabel an jeweils einen analogen Eingang am AVR geführt werden.



  • Timerfunktionen werden der Einfachheit halber nicht mit Interrupts sondern mit delays gelöst, so genau müssen die 10 Minuten Lüftungszeit nicht sein.
  • 5 digitale Ausgänge für die Leds.
  • 3 digitale Eingänge für die Taster.
  • Die Offsetwerte müssen im EEPROM gespeichert werden.

Ein Foto der Platine, zugegeben sie ist etwas prototypartig aber schon über 3 Jahre ohne Ausfall im Einsatz:



Ein Temperaturlogger zeichnet genau das auf, was man als Ergebnis erwartet, Stück für Stück fällt der Feuchtigkeitswert:



Hier der Code:

/*

- 1 x Relais für Lüfter
- 1 x Led gelb
- 2 x rot 
- 2 x grün
- 3 Knöpfe
- 2 x Sensoren


-Dig OUT: Motor öffnen
-Dig OUT: Motor schließen
-Dig OUT: Led für manuell
-Dig OUT: Led 1 für Temp
-Dig OUT: Led 2 für Temp
-Dig OUT: Led 1 für Feuchte
-Dig OUT: Led 2 für Feuchte
-Dig OUT: Lüfter An Aus
-Dig IN: Endschalter 1
-Dig IN: Endschalter 2
-Dig IN: Knopf für Abgleich
-Dig IN: Knopf für Manuell
-Dig IN: Knopf für An/Aus
-ADC IN: Sensor 1 ohmsch 
-ADC IN: Sensor 2 ohmsch
-Dig IN: Sensor 3 freq.
-Dig IN: Sensor 4 freq.

--------------------------------------------------


PC6: Widerstand 10k nach GND
XTAL1, XTAL2: Quarz 16 MHz
PB2: 



PD0: Led rot für Temp 
PD1: Led grün für Temp
PD7: Led rot für Feuchte
PB8: Led grün für Feuchte


PD2: Interrupt Endschalter1
PD3: Interrupt Endschalter2
PD4: Led für manuell
PD5: Motor öffnen
PD6: Motor schliessen
  
PB1: Lüfter 1
PB3: Abgleich Knopf
PB4: Manuell Knopf
PB5: An/Aus
PC0: Sensor 1 ohmsch
PC1: Sensor 2 ohmsch
PC3: Sensor 3
PC4: Sensor 4

*/

#define F_CPU 16000000UL

#include 
#include 
#include 
#include 
#include 
#include "../helpers.h"


#define configureAsDO(ddr, pin_or_port, port_bit) (ddr |= (1<size = 40;
	h->index = 0;
	h->filled = 0;

	for (unsigned char i = 0; i < 40; i++) {
		h->values[i] = 0;	
	}
}

uint16_t getHystereseResult(struct Hysterese * h) {
	uint16_t sorted_values[40];

	for(unsigned char n=0; n<40; n++) {
		sorted_values[n]=h->values[n];
	}

	for (unsigned char n = 39; n > 1; n--) {
    	for (unsigned char i = 0; i < n - 1; i++) {
      		if (sorted_values[i] > sorted_values[i+1]) {
        		uint16_t temp = sorted_values[i];
				sorted_values[i] = sorted_values[i+1];
				sorted_values[i+1] = temp; 
			}
		}
	}

	return sorted_values[35];
}	

void insertIntoHysterese(struct Hysterese * h, uint16_t value) {
	if (h->index == (39) && h->filled == 0) {
		h->values[39] = value;
		h->filled = 1;
		h->index = 0;
	} else {
		h->values[h->index] = value;
		h->index = (h->index + 1) % 40;
	}
}

static volatile uint8_t * eepromaddress = (uint8_t *) 0;

uint16_t ADC_Read(uint8_t channel)
{
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
  ADCSRA |= (1< t) {
			break;
		}
	}

	while(1) {
		adc=ADC_Read_Avg(channel, 4);

		if (adc < t) {
			break;
		}
	}

	while(1) {
		adc=ADC_Read_Avg(channel, 4);

		if (adc > t) {
			break;
		}
		
		_delay_us(1);		
		x++;
	}

	return x;
}

struct Hysterese TEMP1;
struct Hysterese TEMP2;
struct Hysterese HUM1;
struct Hysterese HUM2;

unsigned char doTempAndHumdityLeds() {
	unsigned char ok1 = 0;
	unsigned char ok2 = 0;
	unsigned char doAbgleich = 0;
	uint16_t temp1 = 0;
	uint16_t temp2 = 0;
	uint16_t hum1 = 0;
	uint16_t hum2 = 0;

	if (isDIPressed(DDRB, PINB, PB3)) { // PB3: Abgleich Knopf
		doAbgleich = 1;
		sleep(800);
	}
	
	temp1 = ADC_Read_Avg(0, 50);
	temp2 = ADC_Read_Avg(1, 50);

	// -------------------------------------------------------------------------

	hum1 = getHum(3);
	hum2 = getHum(4);

	// -------------------------------------------------------------------------

	insertIntoHysterese(&TEMP1, temp1);	
	insertIntoHysterese(&TEMP2, temp2);	
	insertIntoHysterese(&HUM1, hum1);	
	insertIntoHysterese(&HUM2, hum2);	

	if (TEMP1.filled == 0 || HUM1.filled == 0 || TEMP2.filled == 0 || HUM2.filled == 0) {
		setDOToHigh(DDRD, PORTD, PD0);
		setDOToHigh(DDRD, PORTD, PD1);
		setDOToHigh(DDRD, PORTD, PD7);
		setDOToHigh(DDRB, PORTB, PB0);
		return 0;
	} else {
		temp1 = getHystereseResult(&TEMP1);
		temp2 = getHystereseResult(&TEMP2);
		hum1 = getHystereseResult(&HUM1);
		hum2 = getHystereseResult(&HUM2);

		// temperatures
		// outdoor = 1
		// indoor = 2
		if((temp1 + abgleichT1) > (temp2 + abgleichT2)) {
			setDOToHigh(DDRD, PORTD, PD0);
			setDOToLow(DDRD, PORTD, PD1);
			ok1 = 0;
		} else {
			setDOToLow(DDRD, PORTD, PD0);
			setDOToHigh(DDRD, PORTD, PD1);
			ok1 = 1;
		}

		// outdoor = 1
		// indoor = 2
		// steigende feuchtigkeit -> höherer zähler
		if((hum1 + abgleichH1) > (hum2 + abgleichH2)) {
			setDOToHigh(DDRD, PORTD, PD7);
			setDOToLow(DDRB, PORTB, PB0);
			ok2 = 0;
		} else { // hum1 muss höher sein
			setDOToLow(DDRD, PORTD, PD7);
			setDOToHigh(DDRB, PORTB, PB0);
			ok2 = 1;
		}

		if (doAbgleich) { // PB3: Abgleich Knopf
			if (temp1 > temp2) {
				abgleichT1 = 0;
				abgleichT2 = (temp1 - temp2);
			} else {
				abgleichT1 = (temp2 - temp1);
				abgleichT2 = 0;
			}

			if (hum1 > hum2) {
				abgleichH1 = 0;
				abgleichH2 = (hum1 - hum2);
			} else {
				abgleichH1 = (hum2 - hum1);
				abgleichH2 = 0;
			}

			eepromaddress = (uint8_t *) 0;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichT1 >> 8));
			eepromaddress = (uint8_t *) 1;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichT1 % 256));
			eepromaddress = (uint8_t *) 2;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichT2 >> 8));
			eepromaddress = (uint8_t *) 3;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichT2 % 256));
			eepromaddress = (uint8_t *) 4;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichH1 >> 8));
			eepromaddress = (uint8_t *) 5;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichH1 % 256));
			eepromaddress = (uint8_t *) 6;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichH2 >> 8));
			eepromaddress = (uint8_t *) 7;
			eeprom_write_byte((uint8_t *) eepromaddress, (uint8_t) (abgleichH2 % 256));
		}

		return (ok1 == 1) && (ok2 == 1);
	}
}

void openKlappe() {
	setDOToLow(DDRD, PORTD, PD6); // PD6: Motor schliessen
	setDOToHigh(DDRD, PORTD, PD5); // PD5: Motor öffnen

	while(1) {
		if (isDIPressed(DDRD, PIND, PD2)) {
			break;	
		}
	}

	setDOToLow(DDRD, PORTD, PD6); // PD6: Motor schliessen
	setDOToLow(DDRD, PORTD, PD5); // PD5: Motor öffnen
}

void closeKlappe() {
	setDOToLow(DDRD, PORTD, PD5); // PD5: Motor öffnen
	setDOToHigh(DDRD, PORTD, PD6); // PD6: Motor schliessen

	while(1) {
		if (isDIPressed(DDRD, PIND, PD3)) {
			break;	
		}
	}

	setDOToLow(DDRD, PORTD, PD6); // PD6: Motor schliessen
	setDOToLow(DDRD, PORTD, PD5); // PD5: Motor öffnen
}

int main(void){
	cli();

	initHysterese(&TEMP1);
	initHysterese(&TEMP2);
	initHysterese(&HUM1);
	initHysterese(&HUM2);

	uint16_t result;
	ADMUX = (1<<REFS1) | (1<<REFS0);
	ADCSRA = (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
	ADCSRA |= (1<<ADEN);
	ADCSRA |= (1<<ADSC);                 
	while (ADCSRA & (1<<ADSC) );
	result = ADCW;


	eepromaddress = (uint8_t *) 0;
	abgleichT1 = eeprom_read_byte((uint8_t *) eepromaddress) * 256;
	eepromaddress = (uint8_t *) 1;
	abgleichT1 += eeprom_read_byte((uint8_t *) eepromaddress);

	eepromaddress = (uint8_t *) 2;
	abgleichT2 = eeprom_read_byte((uint8_t *) eepromaddress) * 256;
	eepromaddress = (uint8_t *) 3;
	abgleichT2 += eeprom_read_byte((uint8_t *) eepromaddress);

	eepromaddress = (uint8_t *) 4;
	abgleichH1 = eeprom_read_byte((uint8_t *) eepromaddress) * 256;
	eepromaddress = (uint8_t *) 5;
	abgleichH1 += eeprom_read_byte((uint8_t *) eepromaddress);

	eepromaddress = (uint8_t *) 6;
	abgleichH2 = eeprom_read_byte((uint8_t *) eepromaddress) * 256;
	eepromaddress = (uint8_t *) 7;
	abgleichH2 += eeprom_read_byte((uint8_t *) eepromaddress);

	configureAsDO(DDRD, PORTD, PD0);
	configureAsDO(DDRD, PORTD, PD1);
	configureAsDO(DDRD, PORTD, PD2);
	configureAsDO(DDRD, PORTD, PD3);
	configureAsDO(DDRD, PORTD, PD4);
	
	configureAsDO(DDRD, PORTD, PD5);
	configureAsDO(DDRD, PORTD, PD6);
	configureAsDO(DDRD, PORTD, PD7);
	configureAsDO(DDRB, PORTB, PB2);
	configureAsDO(DDRB, PORTB, PB0);
	configureAsDO(DDRB, PORTB, PB1);

	configureAsDI(DDRB, PINB, PB5);
	configureAsDI(DDRB, PINB, PB4);
	configureAsDI(DDRB, PINB, PB3);
	configureAsDI(DDRC, PINC, PC3);
	configureAsDI(DDRC, PINC, PC4);

	setDOToLow(DDRD, PORTD, PD0); // PD0: Led rot für Temp
	setDOToLow(DDRD, PORTD, PD1); // PD1: Led grün für Temp
	setDOToLow(DDRD, PORTD, PD7); // PD7: Led rot für Feuchte
	setDOToLow(DDRB, PORTB, PB0); // PB0: Led grün für Feuchte
	setDOToLow(DDRD, PORTD, PD4); // PD4: Led für manuell
	setDOToLow(DDRD, PORTD, PD5); // PD5: Motor öffnen
	setDOToLow(DDRD, PORTD, PD6); // PD6: Motor schliessen
	setDOToLow(DDRB, PORTB, PB1); // PB1: Lüfter
	setDOToLow(DDRB, PORTB, PB2); // PB2: Load capicitor 1

	unsigned char initMode = 1;

	while(1){
		// manueller Modus
		if (initMode == 1 || isDIPressed(DDRB, PINB, PB4)) { // Manueller Modus
			initMode = 0;
			setDOToHigh(DDRD, PORTD, PD4); // PD4: Led für manuell
			setDOToLow(DDRB, PORTB, PB1); // PB1: Lüfter
			unsigned char luefter = 0;

			sleep(800);
			
			while(1) {
				if (luefter == 0 && isDIPressed(DDRB, PINB, PB5)) { // Ein / Aus
					sleep(800);
					openKlappe();
					setDOToHigh(DDRB, PORTB, PB1); // PB1: Lüfter
					luefter = 1;
				} else if (luefter == 1 && isDIPressed(DDRB, PINB, PB5)) { // Ein / Aus
					sleep(800);
					closeKlappe();
					setDOToLow(DDRB, PORTB, PB1); // PB1: Lüfter
					luefter = 0;
				} else if (isDIPressed(DDRB, PINB, PB4)) {
					sleep(800);
					luefter = 0;
					break;
				}

				doTempAndHumdityLeds();
			}
		
			setDOToLow(DDRB, PORTB, PB1); // PB1: Lüfter
			closeKlappe();
		} else { // Automatikmodus
			setDOToLow(DDRD, PORTD, PD4); // PD4: Led für manuell
			
			unsigned char exitFull = 0;

			while (exitFull == 0) {
				if (isDIPressed(DDRB, PINB, PB4)) {
					sleep(800);
					exitFull = 1;
					break;
				}

				if(doTempAndHumdityLeds() == 1) {
					openKlappe();
					setDOToHigh(DDRB, PORTB, PB1); // PB1: Lüfter
					
					// Lüften für 10 minuten
					for (unsigned int i = 0; i < 600; i++) {
						sleep(1000);
						doTempAndHumdityLeds();

						if (isDIPressed(DDRB, PINB, PB4)) {
							sleep(800);
							exitFull = 1;
							break;
						}
					}

					closeKlappe();
					setDOToLow(DDRB, PORTB, PB1); // PB1: Lüfter

					if(exitFull == 1) {
						break;
					}
					
					// eine stunde warten
					for (unsigned int i = 0; i < 3600; i++) {
						sleep(1000);
						doTempAndHumdityLeds();

						if (isDIPressed(DDRB, PINB, PB4)) {
							sleep(800);
							exitFull = 1;
							break;
						}
					}

					if(exitFull == 1) {
						break;
					}
				}					
			}
		}
	}
}


Copyright © 2023 Evolutec Alle Rechte vorbehalten.