/*
Wertet einen Drehgeber aus und zeigt die Position auf einem µOLED-128-G1
Display von 4D Systems als Zahl und Bargraph an, und eine Uhr.
*/

#include <stdint.h>
#include "hardware.h"
#include "display.h"

// aktuelle Drehgeberposition
static volatile uint8_t g_position = 0;

// zählt von 0 bis 999
static volatile uint16_t g_millisecondCounter = 0;

// Uhrzeit
static volatile uint8_t g_secondCounter = 0;
static volatile uint8_t g_minuteCounter = 0;
static volatile uint8_t g_hourCounter = 0;

// Tabelle für alle Zustandübergange der Eingänge,
// abgebildet auf die Zählrichtung
// A oldA B oldB Zähler
// 0 0    0 0    0
// 0 0    0 1    1
// 0 0    1 0    -1
// 0 0    1 1    0
// 0 1    0 0    -1
// 0 1    0 1    0
// 0 1    1 0    0
// 0 1    1 1    1
// 1 0    0 0    1
// 1 0    0 1    0
// 1 0    1 0    0
// 1 0    1 1    -1
// 1 1    0 0    0
// 1 1    0 1    -1
// 1 1    1 0    1
// 1 1    1 1    0
#if 0
// Tabelle für ideale Drehgeber, ohne prellen und mit einem Schritt pro Rasterung
static const int8_t g_delta2[16] = {
  0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
};
#else
// für ALPS EC11 und EC12 Encoder:
// Änderungen auf B nicht auswerten und Änderungen auf A nur asuwerten, wenn B=0
// ergibt ein Schritt pro Rasterung und kein springen zwischen zwei Werten wegen Kontakt B
// siehe http://www.mikrocontroller.net/articles/Drehgeber für Details
static const int8_t g_delta[16] = {
  0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0
};
#endif

// liefert die Richtung, in der sich ein Drehgeber gedreht hat: -1, 1 oder 0
static int8_t getDelta(uint8_t a, uint8_t oldA, uint8_t b, uint8_t oldB)
{
  return g_delta[oldB | (b << 1) | (oldA << 2) | (a << 3)];
}

// wird mit 1 kHz aufgerufen
void onInterrupt(void)
{
	// Drehgeber auswerten
	static uint8_t oldA = 0;
	static uint8_t oldB = 0;
	uint8_t a = getA();
	uint8_t b = getB();
	int8_t delta = getDelta(a, oldA, b, oldB);
	if (delta < 0) g_position--;
	if (delta > 0) g_position++;
	oldA = a;
	oldB = b;
	
	// Millisekundenzähler erhöhen
	g_millisecondCounter++;
	if (g_millisecondCounter == 1000) {
		g_millisecondCounter = 0;
		g_secondCounter++;
		if (g_secondCounter == 60) {
			g_secondCounter = 0;
			g_minuteCounter++;
			if (g_minuteCounter == 60) {
				g_minuteCounter = 0;
				g_hourCounter++;
				if (g_hourCounter == 23) {
					g_hourCounter = 0;
				}
			}
		}
	}
}

// Hauptprogramm
int main(void)
{
	// Hardware und Display initialisieren
	initHardware();
	initDisplay();
	
	// statischen Text ausgeben
	drawString(0, 0, COLOR_RED, 1, "R");
	drawString(10, 0, COLOR_GREEN, 1, "G");
	drawString(20, 0, COLOR_BLUE, 1, "B");
	drawString(0, 20, COLOR_LIGHT_SEA_GREEN, 2, "Position");
	
	// Endlosschleife: Drehgeberposition und Uhrzeit anzeigen
	while (1) {
		// Interrupt sperren und aktuelle Position und Millisekundenzähler lesen
		disableInterrupts();
		uint16_t milliseconds = g_millisecondCounter;
		uint8_t newPosition = g_position;
		enableInterrupts();
		
		// Drehgeber-Wert mit 1 Hz blinkend anzeigen
		uint32_t color;
		if (milliseconds > 500) {
			color = milliseconds >> 3;
		} else {
			color = (1000 - milliseconds) >> 3;
		}
		color &= 0xff;
		color <<= 8;
		drawInt(0, 50, color, 5, newPosition);
		
		// Wert per Draw Rectangle anzeigen
		uint8_t pos = newPosition;
		if (pos > 128) pos = 128;
		drawRectangle(0, 110, pos, 128, COLOR_YELLOW);
		drawRectangle(pos + 1, 110, 128, 128, COLOR_BLACK);
		
		// aktuelle Zeit anzeigen
		drawTime(60, 0, COLOR_WHITE, g_hourCounter, g_minuteCounter, g_secondCounter);
	}
}
