automatische Hühnerstallklappe mit AVR ATMega32


Morgens mit den Hühnern aufstehen um selbige aus dem Stall zu lassen, der Alptraum aller Langschläfer.
Sowas muss sich doch automatisieren lassen, oder?

Version 1 – für Nicht-Informatiker:

Wir kaufen eine kommerzielle Lösung, Kosten ca. 150 Euro, geschätzte Montagedauer ca. 3 Stunden.

Version 2 – die Informatiker Lösung:

“Kaufen? Viel zu teuer! Dass kann ich doch selber bauen!”
Man nehme:

  • AVR Evaluation Board (Pollin) ca. 15 Euro
  • AVR ATMega32 ca. 4 Euro
  • Netzteil 9V (Pollin) ca. 5 Euro
  • Spindelmotor (Ebay) ca. 20 Euro
  • Motortreiberplatine (L298) (Ebay) 8 Euro
  • diverse Kleinteile (Fotodiode, Kabel, etc.) 10 Euro
  • Atmel AVR Studio
  • PonyProg
  • avrlib
  • AVR C-Compiler
  • Firmware Entwicklungskosten … unbezahlbar 🙂

Als Basis habe ich ein ATMEL Evaluations-Board von Pollin mit einem AVR ATMega32 in meiner Bastelkiste gefunden. Mein Jtag ICE CLone hat leider den Dienst verweigert und war einfach nicht zum debuggen zu bewegen, warum weiss ich auch nicht.
Zum Glück hatte das Board einen ISP Anschluss via RS232 und so konnte ich die Firmware mittels PonyProg auf den Chip bringen.

Hardware

Zum Öffnen der Hühnerklappe braucht man natürlich einen Motor, aber die Klappe mittels einer Schnur die auf- und abgewickelt wird zu öffnen oder zu schliessen schien mir etwas fehleranfällig und kompliziert im mechanischen Aufbau. Auch andere Selbstbauten haben mich nicht so richtig überzeugt. Nicht dass es dann bei der Frage “Wieviele Hühner habt ihr eigentlich?” heisst: “20 … und ein paar zerquetschte!”
Ein Spindelmotor der die Klappe rauf und runter dreht sollte wesentlich einfacher im Aufbau und störsicherer im Betrieb sein.
Ebay sei dank konnte ich ein günstiges Exemplar für unter 20 Euro incl. Versand erstehen.

Um den Motor zu steuern braucht man eine eine entsprechende Schaltung, in Fachkreisen auch H-Brücke genannt. So was kann man mit einem IC, ein paar Widerständen und Schutzdioden ganz einfach selber zusammenbauen. Da ich allerdings als ‘Softi’ von Hardware relativ wenig (bis überhaupt keine) Ahnung habe ist es günstiger und zeitsparender eine fertige Lösung zu benutzen. Für 8 Euro gibts eine industriell gefertigte Platine mit einem L298 Motortreiber der bis 46V und 4A vertragen kann. Dass sollte für eine kleine Hühnerklappe vorerst genügen und bietet noch ausreichend Reserven falls die Tierchen mal grösser werden.
Für die Erkennung ob die Klappe offen bzw. geschlossen ist sind zwei Reedrelais nötig. Ein Magnet der an der Klappe angebracht ist schaltet die Kontakte.

Zur Unterscheidung von Tag/Nacht bzw. Hell/Dunkel braucht es noch eine Art Lichtsensor. Sowas gibt es zwar auch als Bausatz, aber da sich der Mikroprozessor eh die meiste Zeit langweilen wird kann man ja auch den ADC (Analog-Digital-Converter) mittels einer Fotodiode verwenden.
Eine Fotodiode oder auch LDR ist ein lichtabhängiger Widerstand, d.h. je dunkler es ist desto mehr Widerstand hat die Diode bzw. je heller desto weniger. Dadurch fällt mehr oder weniger Spannung an diesem Widerstand ab. Mehr elektrotechnische Grundlagen habe ich auch nicht.
Ich weiss halt nur … es funkioniert. Die “Schaltung” ist minimalistisch:
5V — LDR — ADC0-Pin — 10k Widerstand — Masse
Am ADC des Microprozessors kann man dann die Spannung die am ADC0-Pin anliegt messen. Je heller desto mehr Spannung, je dunkler desto weniger.

Die Hardware Seite ist damit erledigt. Aber die Intelligenz der Steuerung liegt ja in der Software.
Deshalb ist Softwareentwicklung ja auch so teuer! 😉

Hardware

Fertiger Aufbau im ‘Labor’
Aufbau im 'Labor'

Software

Jetzt kommt der spassige Teil!

Einer von vielen Versuchen:
System states

Helligkeitssensor
Um Ungenauigkeiten die z.B. durch vorbeifahrende Autos und deren Scheinwerfer verursacht werden können zu vermeiden benutze ich den Mittelwert aus n Samples. Bei einer genügend grossen Anzahl von Samples sollten sich Messungenauigkeiten somit einigermassen “glätten” lassen.
Ein Sample besteht aus 3 Messungen, jedes Sample wird in einen Ringbuffer gefüttert, aus diesem wird der Mittelwert zur Helligkeitsbestimmung ermittelt.
Mit einem intelligenten Algorithmus könnten man dass natürlich wesentlich effizienter und einfacher erledigen, allerdings will ich nur eine Klappe für einen Hühnerstall steuern … und keinen Flugzeugträger. 😉
Die Min/Max Grenzwerte für Tag und Nacht sind im EEPROM abgelegt um den Sensor einfacher kalibrierbar zu machen ohne jedesmal die Firmware neu kompilieren zu müssen.

Der Zustandsautomat für die Helligkeitsmessung hat 4 Zustände:

UNKNOWN: nicht genügend Werte
DAY: wenn es Tag ist
DUSKDAWN: Übergang von Tag <-> Nacht
NIGHT: wenn es Nacht ist

Sensoren bzw. Taster
Das Board hat 3 Taster von denen ich 2 als “Auf” und “Ab” Schalter verwenden, damit man die Klappe auch manuell steuern kann.
Das Erkennen der Klappenposition wird von den Reedkontakten am oberen bzw. unteren Ende der Öffnung erledigt. Ist der obere Kontakt geschlossen ist die Klappe geöffnet, ist der untere geschlossen ist die Klappe zu.
Der Zustand der Sensoren, Taster gedrückt bzw. nicht gedrückt und Endschalter geschlossen bzw nicht geschlossen wird über digitale IO Ports gelesen.
Um korrekte Signale zu bekommen muss auch hier eine Entprellung vorgenommen werden. Die Taster des Evaluation Boards haben zwar eine hardwaremässige Entprellung, aber die beiden Reedrelais werden gegen Masse geschaltet. Ist einfacher im Aufbau da ich die internen Pullup Widerstände benutzen kann und zudem die gängigere Variante.

Man kann seitenweise Forendiskussionen nachlesen bzgl. ‘der einzig wahre und richtige’ Entprell-Algorithmus.
Für meine Zwecke sollte diese Quick&Dirty Lösung reichen.
Der Zustand des jeweiligen PortPins wird in einem 10ms Interrupt in ein State-Byte geshiftet. Besitzt das Byte den Wert 0xff bzw. 0x00 wechselt der Zustand des Schalters, ansonsten bleibt der letzte Zustand gültig.
D.h. nach 80ms kann ein Wechsel erkannt werden. Sollte schnell genug sein.

	sensor->state = (sensor->state<<1);
 	if(pin) {
		sensor->state |= 0x01;
	} else {
		sensor->state &= 0xfe;
	}
 
	// state changed?
	if(sensor->state==0xff) {
		ptast->key = ON;
	} else if(sensor->state==0x00) {
		ptast->key = OFF;
	} else {
                // keep old state
	}

Die Statemachine für die Klappe hat folgende Zustände:

DOOR_MOVING: (kein Schalter geschlossen)
DOOR_OPEN: Klappe ist offen (Schalter oben geschlossen)
DOOR_CLOSE: Klappe ist geschlossen (Schalter unten geschlossen)
DOOR_ERROR: Fehlerzustand

ISR und main

Timer1 Interrupt ‘weckt’ den ATMEGA aus dem SleepMode auf.
Hier wird nur ein Flag gesetzt, welches in der Haupt-Schleife ausgewertet wird.

ISR(TIMER1_OVF_vect) // timer_overflow
{
	// restart counter
	TCNT1H = TCNT1H_VALUE;
	TCNT1L = TCNT1L_VALUE;
	wokeup = TRUE;
}

Der Timer0 Interrupt liest im 10ms Intervall die Taster und Magnetkontakte ein.

ISR(TIMER0_OVF_vect) // timer_overflow
{
	// restart counter
	TCNT0 = TCNT0_VALUE;
 
	readInput(&sensor[BUTTON_UP], PIND &  (1 << KEY_BUTTON_UP));
	readInput(&sensor[BUTTON_DOWN], PIND & (1 << KEY_BUTTON_DOWN));
	readInput(&sensor[LIMITSWITCH_UP], PINC & (1<< KEY_SWITCH_UP));
	readInput(&sensor[LIMITSWITCH_DOWN], PINC & (1 << KEY_SWITCH_DOWN));
	readInput(&sensor[BUTTON_3], PIND & (1 << KEY_BUTTON_3));
 
        round++;
	if(getTasterInit()==TRUE) {
		checkDoorStatus();
	} else {
	    if(round==8) {
		setTasterInit(TRUE);
	    }
        }
	round=round%8;
}

In der Main Routine des Programm wird durch die System Statemachine ‘geloopt’.

int main()
{
    // HW intitialisierung, Timer Interrupts, UART, Ports, etc.
   ...
	printf("hello chicken!\n");
	// read config from eeprom
	eeprom_read_block(&configdata,0,sizeof(configdata));
 
	if(configdata.duskdawnmin==DEFAULT &&
		configdata.duskdawnmax == DEFAULT) {
		configdata.duskdawnmin = DUSKDAWNMIN;
		configdata.duskdawnmax = DUSKDAWNMAX;
		printf("no eeprom data ... using default\n");
	}
	printf("DUSKDAWNMIN %i\n",configdata.duskdawnmin);
	printf("DUSKDAWNMAX %i\n",configdata.duskdawnmax);
 
	setEngineStatus(ENGINE_STOP);
	disableEngine();
	LED2_ON;
	sei();
	checkSensors();
 
        // try to set system state on startup
        if(getDoorStatus()==DOOR_CLOSE) {
                systemstatus = SYSTEM_CLOSE;
        } else if(getDoorStatus()==DOOR_OPEN) {
                 systemstatus = SYSTEM_OPEN;      
        } else {
                // door not open/close ... set init state
                systemstatus = SYSTEM_INIT;
        }
 
	// --- main loop ---
 
	for (;;) {
	switch(systemstatus) {
		case  SYSTEM_INIT:
                        // user action requested
			if(getPinStatus(BUTTON_UP)==ON) {
				systemstatus = SYSTEM_START_OPENING;
			}
			if(getPinStatus(BUTTON_DOWN)==ON) {
				systemstatus = SYSTEM_START_CLOSING;
			}
		break;
 		case  SYSTEM_START_OPENING:
			checkSensors();
			setEngineStatus(ENGINE_MOVE_UP);
			enableEngine();
			systemstatus = SYSTEM_OPENING;
			enableErrorTimer();
		break;
 		case  SYSTEM_START_CLOSING:
			checkSensors();
			setEngineStatus(ENGINE_MOVE_DOWN);
			enableEngine();
			systemstatus = SYSTEM_CLOSING;
			enableErrorTimer();
		break;
 		case  SYSTEM_CLOSING:
			// check for timeout
			checkError(&systemstatus);
                        // door closed
			if(getDoorStatus()==DOOR_CLOSE) {
				disableEngine();
				setEngineStatus(ENGINE_STOP);
				disableErrorTimer();
				systemstatus = SYSTEM_CLOSE;
			}
		break;
		case  SYSTEM_OPENING:
			// check for timeout
			checkError(&systemstatus);
                        // door opened
			if(getDoorStatus()==DOOR_OPEN) {
				disableEngine();
				setEngineStatus(ENGINE_STOP);
				disableErrorTimer();
				systemstatus = SYSTEM_OPEN;
			}
		break;
 		case  SYSTEM_OPEN:
			// if woke up from tmr1 ... do adc
			checkADC();
			checkSensors();
			if(getPinStatus(BUTTON_DOWN)==ON || 
				getLightStatus()==NIGHT) {
				systemstatus = SYSTEM_START_CLOSING;
			} else {
				// goto sleep
				goSleep();
			}
		break;
 		case  SYSTEM_CLOSE:
			// if woke up from tmr1 ... do adc
			checkADC();
			checkSensors();
			if(getPinStatus(BUTTON_UP)==ON || 
				getLightStatus()==DAY) {
				systemstatus = SYSTEM_START_OPENING;
			} else {
				// goto sleep
				goSleep();
			}
		break;
 		case SYSTEM_ERROR:
				disableEngine();
				setEngineStatus(ENGINE_STOP);
				// flash error led
		break;
		default:
		break;
        }
	return 0;
}

Wir alle sollten Strom sparen – deshalb benutze ich den IDLE Sleepmode des AVR im SYSTEM_CLOSE und SYSTEM_OPEN State, da sich das Programm in 99% der Zeit in diesen Zuständen befinden wird. Angeblich sinkt die Stromaufnahme im IDLE Mode auf 0,3mA was ca. 70% Ersparnis gegenüber dem ACTIVE Mode bedeutet. Da eine Lichtmessung pro Minute meiner Meinung nach völlig ausreichend ist spricht nichts dagegen den Controller schlafen zu legen wenn er eh gerade nichts zu tun hat.

Kunde beim präsentieren der Rechnung

Die ‘Kundschaft’ ist noch etwas skeptisch, aber der Beta-Test läuft!

Andere Leute bauen auch gute Hühnerklappen

Nachdem ich mit der Installation fertig war habe ich mal bei YouTube nach automatischen Hühnerklappen gesucht. Hier sind meine 3 Favoriten:

Automatic Chicken Coop Door Opener Project
Die Verriegelung (gegen Waschbären) finde ich ausgeklügelt. Ausserdem wird ein Akkuschrauber als Motor benutzt … wenn der Mann mal kein Informatiker ist!

Automatic Timed Chicken Coop Door
Hier ist der mechanische Aufbau sehr interessant. Ich glaube da wird ein Scheibenwischermotor benutzt.

Automatic Chicken Door Opener
Absolut coole Konstruktion! Ich verstehe es zwar nicht, finde es aber trotzdem gut! 🙂

Be Sociable, Share!
  1. #1 by Frank on 10. September 2011 - 22:34

    Hallo, bisher der für mich interessanteste Ansatz, bei dem Preis sogar eine Dämmerungssteuerung einzubauen. Den Aufbau der Schaltung traue ich mir (mit Unterstützung im Freundeskreis) zu – Aber wie sieht das mit der Programmierung aus? Dürfte ich Ihr Programm benutzen? Und könnte man mit dem Aufbau auch eine Zeitsteuerung (wegen eines mögliche Hahns im kommende Sommer) realisieren, so dass die Klappe im Sommer nicht ganz so früh aufgeht?

    Mit besten Grüßen, Frank

  2. #2 by alois koller on 13. September 2011 - 11:07

    wenn du dir mein c-code gehacke zutraust, kannst du die sourcen gerne haben. 🙂
    allerdings gibts natuerlich keine doku und ich kann und werde keinen support leisten.
    fuer deine zeitsteuerung wuerde ich ein dcf77 modul vorschlagen.
    ist guenstig und da gibts schon fertigen c-code den man benutzen kann. google ist dein freund.
    sowas hatte ich zuerst auch angedacht, mich dann aber fuer den lichtsensor entschieden.

(will not be published)