Milan Stephan
Fotografie & IT

Zurück zur SPiC Übersicht

kaffeemaschine.c

/*
 * Bei Fehlern oder Unklarheiten, bitte Bescheid sagen (auch Typos usw.) - dann kann ich die ausbessern.
 *
 * Discord: nudelsalat#8505
 * Mail: milan.stephan@fau.de
 *
 * Original-Video: https://www.fau.tv/clip/id/17647
 * Diese Version enthält noch etwas mehr Kommentare. :)
 */

// Header einziehen
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>


// Typdefinitionen
typedef enum {
	STATE_STANDBY,
	STATE_ACTIVE,
	STATE_NO_WATER
} state_t;

typedef enum {
	EVENT_BUTTON,
	EVENT_NO_WATER,
	EVENT_ALARM
} event_t;


// Modul-lokale Variablen
static uint8_t overflow_counter = 0;        // NUR im Interrupt-Handler verwendet, kein volatile.

static volatile uint8_t button_event = 0;   // SOWOHL im Hauptprogramm, ALS AUCH im Interrupt-Handler verwendet.
static volatile uint8_t no_water_event = 0; // Müssen volatile sein, um Änderungen immer in den Speicher
static volatile uint8_t alarm_event = 0;    // zu schreiben bzw. immer aus diesem zu lesen.


// Modul-lokale Hilfsfunktionen
static void init(void) {
	// Pumpe und Zeitung: PD5 als Ausgang (active-low)
	DDRD |= (1 << PD5);
	PORTD |= (1 << PD5);

	// LEDs BLUE0, GREEN0 und RED0: PD7, PD4, PD6 als Ausgang (active-low)
	DDRD |= (1 << PD7) | (1 << PD4) | (1 << PD6);
	PORTD |= (1 << PD7) | (1 << PD4) | (1 << PD6);

	// Taster: PD2 als EIngang (active-low) mit Pull-Up, INT0 bei fallender Flanke
	DDRD &= ~(1 << PD2);
	PORTD |= (1 << PD2);
	EICRA |= (1 << ISC01);
	EICRA &= ~(1 << ISC00);
	EIMSK |= (1 << INT0);

	// Wassersensor: PD3 als Eingang (Wasser: heigh), INT1 bei fallender Flanke
	DDRD &= ~(1 << PD3);
	PORTD |= (1 << PD3);
	EICRA |= (1 << ISC11);
	EICRA &= ~(1 << ISC10);
	EIMSK |= (1 << INT1);

	// Zeitgaber: TIMER0 mit 8-Bit Zähler, Vorteiler 1024, Interrupt bei Überlauf
	TCCR0B &= ~(1 << CS01);
	TCCR0B |= (1 << CS02) | (1 << CS00);
}

static void startAlarm(void) {
	// Überlaufzähler zurücksetzen und Interrupt demaskieren
	overflow_counter = 0;
	TIMSK0 |= (1 << TOIE0);
}

static void stopAlarm(void) {
	// Interrupt für Überlauf maskieren
	TIMSK0 &= ~(1 << TOIE0);
}

static void startBrewing(void) {
	// Startet den Brühvorgang durch logische 0 an PD5
	PORTD &= ~(1 << PD5);
}

static void stopBrewing(void) {
	// Stoppt den Brühvorgang durch logische 1 and PD5
	PORTD |= (1 << PD5);
}

static uint8_t hasWater(void) {
	// Gibe 0 zurück wenn Wasser leer, sonst einen Wert ungleich 0
	return (PIND & (1 << PD3));
}

static void setLEDState(state_t state) {
	// Alle LEDs ausschalten
	PORTD |= (1 << PD7) | (1 << PD4) | (1 << PD6);

	// Die richtige in Abhängigkeit von state einschalten
	//
	// STATE_STANDBY: PD7
	// STATE_ACTIVE: PD4
	// STATE_NOWATER: PD6
	switch (state) {

		case STATE_STANDBY:
			PORTD &= ~(1 << PD7);
			break;

		case STATE_ACTIVE:
			PORTD &= ~(1 << PD4);
			break;

		case STATE_NO_WATER:
			PORTD &= ~(1 << PD6);
			break;
	}
}


// Interrupt Handler
ISR(INT0_vect) {
	button_event = 1;
}

ISR(INT1_vect) {
	no_water_event = 1;
}

ISR(TIMER0_OVF_vect) {
	++overflow_counter;
	// 2 Sekunden ~122 (1 Sekunde ~61)
	if (overflow_counter == 122) {
		overflow_counter = 0;
		alarm_event = 1;
	}
}


// Hauptprogramm
void main(void) {

	// Hardware initialisieren
	init();

	// Interrupts global freischalten
	sei();

	// Sleep-Enable Bit setzen
	sleep_enable();

	// Zustand
	state_t state = STATE_STANDBY;
	setLEDState(state);

	// Hauptschleife
	while (1) {

		// Auf Ereignis warten
		cli();
		while (button_event == 0 && no_water_event == 0 && alarm_event == 0) {
			// Wenn es nichts zu tun gibt, bleiben wir in der Schleife und legen uns schlafen.
			sei();
			sleep_cpu();
			cli();
		}

		// Nach der Schleife ist mindestens ein Ereignis eingetreten, für das wir uns interessieren.

		event_t event;
		if (button_event != 0) {
			button_event = 0;
			event = EVENT_BUTTON;
		} else if (no_water_event != 0) {
			no_water_event = 0;
			event = EVENT_NO_WATER;
		} else {
			alarm_event = 0;
			event = EVENT_ALARM;
		}
		// Interrupts müssen während den Zugriffen auf gemeinsam genutzte Variablen gesperrt bleiben.
		// Daher erst hier wieder freischalten.
		sei();

		// Transitionen ausführen
		// Neuen Zustand bestimmen
		state_t state_old = state;
		switch (state) {

			case STATE_STANDBY:
				if (event == EVENT_BUTTON) {
					if (hasWater()) {
						state = STATE_ACTIVE;
					} else {
						state = STATE_NO_WATER;
					}
				}
				break;

			case STATE_ACTIVE:
				if (event == EVENT_BUTTON || event == EVENT_NO_WATER) {
					state = STATE_STANDBY;
				}
				break;

			case STATE_NO_WATER:
				if (event == EVENT_ALARM) {
					state = STATE_STANDBY;
				}
				break;
		}

		// Auf Übergänge reagieren
		if (state == state_old) {
			continue;
		}

		// Neuen Zustand "aktivieren"
		setLEDState(state);
		switch (state) {

			case STATE_STANDBY:
				stopBrewing();
				stopAlarm();
				break;

			case STATE_ACTIVE:
				startBrewing();
				stopAlarm();
				break;

			case STATE_NO_WATER:
				stopBrewing();
				startAlarm();
				break;
		}
	}
}