Source file: /~heha/Mikrocontroller/Displays/utft/Kalender.zip/ILI9341/Kalender.cpp

/* Müllkalender, basierend auf
   UTFT_Demo_320x240
   Mikrocontroller: ATmega328
+1608xx	Timeout bei Uhr-Einstell-Dialog
	Kalibriermöglichkeit
	Größere Schrift für Datum+Uhrzeit
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <string.h>
#include "UTFT.h"
#include "TouchScreen.h"
#include "calib.h"	// Dialog Touchscreen kalibrieren
#include "stellen.h"	// Dialog Uhr stellen
#include "kal.h"	// Dialog Monatskalender

// Dieser Datensatz: Frank Philippczyk 2016 + Januar 2017
//		 31	24	16	 8	1
EEMEM long Tonne_E[4*12];
#ifdef EELOAD
//Vorgaben für den EEPROM können mit dem Arduino-Urlader nicht dahin
//eingeschrieben werden, das muss die Software machen und dafür
//Flash-Speicher vorhalten.
const long Tonne_P[]={
/*Januar*/	0b0000000000010000000000000100000,	//6,20
		0b0000010000000000000100000000000,	//12,26
		0b0000000000000100000000000000000,	//18
		0b0000001000000100000010000001000,	//4,11,18,25
/*Februar*/	0b0000000000001000000000000010000,	//5,19
		0b0000001000000000000010000000000,	//11,25
		0b0000000000000010000000000000000,	//17
		0b0000000100000010000001000000100,	//3,10,17,24
/*März*/	0b0000000000000100000000000001000,	//4,18
		0b0000000100000000000001000000000,	//10,24
		0b0000000000000001000000000000000,	//16
		0b1000000010000001000000100000010,	//2,9,16,23,31
/*April*/	0b0010000000000000100000000000010,	//2,15,29
		0b0000000000100000000000001000000,	//7,21
		0b0000000000000000001000000000000,	//13
		0b0000100000010000001000000100000,	//6,13,20,27
/*Mai*/		0b0000100000000000001000000000000,	//13,27
		0b0000000000010000000000000100000,	//6,20
		0b0000000000000000000010000000000,	//11
		0b0000001000001000000010000001000,	//4,11,19,25
/*Juni*/	0b0000000100000000000001000000000,	//10,24
		0b0100000000000001000000000000010,	//2,16,30
		0b0000000000000000000000010000000,	//8
		0b0010000001000000100000010000001,	//1,8,15,22,29
/*Juli*/	0b0000000001000000000000010000000,	//8,22
		0b0001000000000000010000000000000,	//14,28
		0b0000000000000000000000000100000,	//6
		0b0000100000010000001000000100000,	//6,13,20,27
/*August*/	0b0000000000001000000000000010000,	//5,19
		0b0000001000000000000010000000000,	//11,25
		0b1000000000000000000000000000100,	//3,31
		0b1000000100000010000001000000100,	//3,10,17,24,31
/*September*/	0b0100000000000001000000000000010,	//2,16,30
		0b0000000001000000000000010000000,	//8,22
		0b0001000000000000000000000000000,	//28
		0b0001000000100000010000001000000,	//7,14,21,28
/*Oktober*/	0b0001000000000000010000000000000,	//14,28
		0b0000000000010000000000001000000,	//7,20
		0b0000010000000000000000000000000,	//26
		0b0000010000001000000100000100000,	//6,12,19,26
/*November*/	0b0000001000000000000010000000000,	//11,25
		0b0000000000000100000000000001000,	//4,18
		0b0000000010000000000000000000000,	//23
		0b0100000010000010000000100000100,	//3,9,17,23,30
/*Dezember*/	0b0000000010000000000000100000000,	//9,23
		0b0100000000000000100000000000001,	//1,15,30
		0b0000000000100000000000000000000,	//21
		0b0010000000100000010000001000000};	//7,14,21,29
//		 31	24	16	 8	1
#endif
// Dem Arduino fehlt eine Echtzeituhr.
// Daher wird die Uhr bei Stromausfall wenigstens „angehalten“.
// Da eine Stromausfall-Früherkennung (Puffer-Elko und Signalisierungseingang)
// fehlt, muss die aktuelle Uhrzeit im EEPROM nachgeführt werden.
// Um den EEPROM nicht übermäßig mit Schreibvorgängen zu belasten,
// wird jedes Bit nur stündlich gelöscht und beschrieben,
// macht <9000 Zugriffe pro Jahr
// und damit eine von Atmel garantierte Haltbarkeit von über 10 Jahren.
// Die Minuten werden bitweise (in 60 Bits) gespeichert.
// Sekunden werden nicht gespeichert.
// Beim Restart wird kurzerhand Sekunde 55 angesetzt.
EEMEM byte ee_hdmy[4];	// Stunde,Tag,Monat,Jahr als Bytes
EEMEM byte ee_min[8];	// 60 Bits für „verbrauchte“ Minuten
EEMEM calib_t ee_calib;

UTFT dc;
TouchScreen ts;

extern const byte ArialBP19[] PROGMEM;
extern const byte ArialBP24[] PROGMEM;

#ifdef DEBUG
void debug(const __FlashString*fmt,...) {
 static UTFT dc2;
 dc2.orient=0;
 dc2.flags=COOKED;
 dc2.clrClip();
// dc2.clrScale();
 dc2.setColor(COLOR_BLACK);
 dc2.setBackColor(COLOR_WHITE);
 dc2.setFont(TimesP18);
 va_list va;
 va_start(va,fmt);
 dc2.vprintf(fmt,va);
 va_end(va);
 dc2.X=0;
 dc2.Y+=dc2.getFontHeight();
 if (dc2.Y>DISP_Y_SIZE-14) dc2.Y=0;
}

int deb1,deb2;
#endif

static void ClearMiddle(word bgcolor=COLOR_BLACK) {
 dc.clrOrg();
 dc.setClip(2,19,dc.getDisplayXSize()-3,dc.getDisplayYSize()-3);
 dc.setBackColor(bgcolor);
 dc.clrScr();
 dc.setColor(~bgcolor);
}

const char*stridx_P(const char*s, int idx) {
 if (idx) do s+=strlen_P(s)+1; while(--idx);
 return s;
}

/*************************************
 * Animation des Randes (sekündlich) *
 *************************************/

struct _animate{
 int y,ye;	// Position und Größe des „morgen“-Bereichs
 word color;	// Für animierten Rand für „morgen“
 void paint();
}animate_morgen;

void _animate::paint() {	// jede Sekunde
 if (y) {
  dc.setColor(color);
  const int w=10;		// Randbreite
  dc.drawRect(5,y-w-1,235,ye+w+1,w);
  color=~color;
 }
}

/****************************************
 * Müll-Box anzeigen und		*
 * Animationskoordinaten aktualisieren	*
 * (1x täglich zu Mitternacht)		*
 ****************************************/
long Tonne[4*12];
byte Ereignis(const _date&d,byte i);

static byte Sorte(const _date&d) {		// jedes Bit des Returnwertes repräsentiert eine Müllsorte
 byte ret=0,mask=1;
 for (byte i=0; i<4; i++,mask<<=1) if (Ereignis(d,i)) ret|=mask;
 return ret;
}

void gotoXdY(int x, int dy) {
 dc.gotoXY(x,dc.whereY()+dy);
}

extern PROGMEM const word bgcolors[9];
PROGMEM const word bgcolors[9]={COLOR_BLACK,		// kennzeichnende Hintergrundfarben
  COLOR_YELLOW,COLOR_GRAY,COLOR_BLUE,RGB(139,69,19),
  COLOR_PURPLE,COLOR_LIME,COLOR_AQUA,COLOR_TEAL};
static PROGMEM const word fgcolors[9]={COLOR_YELLOW,		// kontrastreiche Vordergrundfarben
  COLOR_BLACK,COLOR_BLACK,COLOR_WHITE,COLOR_WHITE,
  COLOR_WHITE,COLOR_BLACK,COLOR_WHITE,COLOR_WHITE};

void ShowMuell(char tag) {
 _date d=jetzt.d;
 for (char i=0; i<tag; i++) d.morgen();
 byte sorte=Sorte(d);
 char lsb=0;
 if (sorte) lsb=__builtin_ctz(sorte)+1;	// Bitnummer ermitteln
 char lines=__builtin_popcount(sorte);		// Anzahl auszugebender Textzeilen
 if (!lines) lines=1;
 word bc=pgm_read_word(bgcolors+lsb);
 word fc=pgm_read_word(fgcolors+lsb);
 dc.setColor(bc);
 int y=dc.whereY();
 int ye=y+34+20*lines;				// Jede Textzeile 20 Pixel
 if (tag==1) {
  if (sorte) animate_morgen.y=y, animate_morgen.ye=ye, animate_morgen.color=fc;
  else animate_morgen.y=0;
 }
 dc.fillRoundRect(16,y,224,ye);
 dc.setColor(fc);
 dc.drawRoundRect(16,y,224,ye);
 dc.setBackColor(bc);
 dc.gotoXY(30,y+4);
 static PROGMEM const char hm[]="heute\0morgen\0übermorgen";
 dc.print(reinterpret_cast<const __FlashString*>(stridx_P(hm,tag)));
 gotoXdY(30,24);
 if (sorte) {
  for (byte m=0x80;m;m>>=1) if (m&sorte) {
   switch (m) {
    case 1: dc.print(F("Gelbe Tonne")); break;
    case 2: dc.print(F("Graue Tonne")); break;
    case 4: dc.print(F("Blaue Tonne")); break;
    case 8: dc.print(F("Braune Tonne")); break;
//    case 16: dc.print(F("Schadstoffmobil")); break;
//    case 32: dc.print(F("Essensreste")); break;
//    case 64: dc.print(F("Pferdeäpfel")); break;
//    case 128: dc.print(F("Geburtstag")); 
      gotoXdY(30,dc.getFontHeight()); dc.printf(F("%s"),"Mama?"); break;
   }
   gotoXdY(30,20);
  }
 }else{
  dc.print(F("Kein Abfall"));
  gotoXdY(30,20);
 }
 gotoXdY(16,19);	// Cursorposition für die nächste Box hinterlassen
}

/****************************************
 * Uhrzeit anzeigen / aktualisieren	*
 * (1x pro Sekunde)			*
 ****************************************/
PROGMEM const char wt[]=
  "Sonntag\0Montag\0Dienstag\0Mittwoch\0Donnerstag\0Freitag\0Sonnabend";
extern PROGMEM const char mn[];
PROGMEM const char mn[]=
  "Januar\0Februar\0März\0April\0Mai\0Juni\0"
  "Juli\0August\0September\0Oktober\0November\0Dezember";

void ShowDate() {
 dc.setBackColor(COLOR_BLACK);
 dc.setColor(COLOR_WHITE);
 const char*p=stridx_P(wt,jetzt.d.dow());	// Wochentag
 char s[20];

 sprintf_P(s,PSTR(" %S "),p);
 dc.print(s,120,240);

 p=stridx_P(mn,jetzt.d.month-1);	// Monatsname
 sprintf_P(s,PSTR(" %d. %S %d%02d "),jetzt.d.day,p,jetzt.d.jh,jetzt.d.year);
 dc.print(s,120,265);
}

void ShowTime() {
 char s[20];
 sprintf_P(s,PSTR(" %02d:%02d:%02d "),jetzt.t.hour,jetzt.t.min,jetzt.t.sec);
 dc.gotoXY(120,290);
 dc.print(s);
}

// Zeit um 1 Sekunde erhöhen
void AddSec() {
 if (++jetzt.t.sec==60) {
  jetzt.t.sec=0; if (++jetzt.t.min==60) {
   jetzt.t.min=0; if (++jetzt.t.hour==24) {
    jetzt.t.hour=0; jetzt.d.morgen();
   }
  }
 }
}

// Interruptbedienroutine für Timer1 (Uhr) im Sekundentakt
ISR(TIMER1_COMPA_vect) {
 AddSec();
}

void Rahmen(int top=19, int bot=2) {
  // Clear the screen and draw the frame
 dc.setColor(160,160,160);
 dc.setBackColor(160,160,160);
 dc.fillRectW(0,0,240,top);		// Nur den Rahmen malen,
 dc.fillRectW(0,320-bot,240,bot);	// das Innere zu löschen kostet Zeit
 dc.fillRectW(0,top,2,320-top-bot);	// und macht der AUfrufer mit seiner Farbe
 dc.fillRectW(238,top,2,320-top-bot);	// Links und rechts sind's fest 2 Pixel
 dc.setColor(COLOR_BLACK);
 dc.setFont(ArialBP19);
 dc.align=2;
// dc.print(F("Michael Schulze, 2016"),120,320-19);	// Straßenzug, Jahr
}

// Hintergrund malen (jeden Tag oder nach Touch-Ereignis neu)
void Hintergrund() {
 Rahmen();

 dc.gotoXY(120,0);
 dc.print(F("Müllkalender"));

 ClearMiddle(COLOR_WHITE);

 dc.clrOrg();

 static const word colors[6] PROGMEM={0,RGB(255,0,255),COLOR_RED,COLOR_GREEN,COLOR_BLUE,COLOR_YELLOW};

 ClearMiddle();

// Draw some filled, rounded rectangles
 for (int i=1; i<6; i++) {
  dc.setColor(pgm_read_word(colors+i));
  int j=i*20;
  dc.fillRoundRect(190-j,130+j,250-j,190+j);
 }

// Draw some filled circles
 for (int i=1; i<6; i++) {
  dc.setColor(pgm_read_word(colors+i));
  int j=i*20;
  dc.fillCircle(20+j,80+j,30);
 }

// Draw some lines in a pattern
 dc.setColor(COLOR_RED);
 int y=dc.clip.B-dc.clip.T;
 for (int i=0; i<y; i+=10) {
  dc.drawLine(dc.clip.L,dc.clip.T+i,dc.clip.L+i,dc.clip.B);
 }
 for (int i=y; i>0; i-=10) {
  dc.drawLine(dc.clip.R,dc.clip.T+i,dc.clip.L+i,dc.clip.T);
 }
 dc.setColor(COLOR_AQUA);
 for (int i=y; i>0; i-=10) {
  dc.drawLine(dc.clip.L,dc.clip.T+i,dc.clip.R-i,dc.clip.T);
 }
 for (int i=0; i<y; i+=10) {
  dc.drawLine(dc.clip.R,dc.clip.T+i,dc.clip.R-i,dc.clip.B);
 }
}

// Hauptprogramm
int main() {
#ifdef EELOAD
 eeprom_update_block(Tonne_P,Tonne_E,sizeof Tonne_E);
#endif
 eeprom_read_block(Tonne,Tonne_E,sizeof Tonne);
 dc.InitLCD();
 ts.Init();
 TCCR1A=0x00;	// CTC-Modus mit TOP=OCR1A
 TCCR1B=0x0D;	// Vorteiler 1024
 OCR1A =15625-1;// Interruptfrequenz 1 Hz (exakt!)
 TIMSK1=0x02;	// OCIE1A
 SMCR  =0x01;	// nur CPU anhalten
 PRR   =0xF6;	// Alles anhalten außer Timer1 und ADC
 sei();

// 1. Die Start-Uhrzeit kann wegen eines Fehlers in avr-gcc (C-Compiler)
// nicht im EEPROM vorgegeben werden (siehe avr-gcc-bug.cpp)
// 2. Beim Arduino ist die EESAVE-Fuse mit 1 gesetzt und (via USB) unveränderlich.
// Angeblich!
// Daher ist beim Firmware-Update der EEPROM-Speicher mit 0xFF gefüllt.
// Nee!! Der Arduino-Urlader kann den EEPROM nicht beschreiben,
// und EESAVE ist auf 0 programmiert: der EEPROM-Inhalt bleibt bestehen.
 byte hdmy[4];
 eeprom_read_block(hdmy,ee_hdmy,4);
 eeprom_read_block(&calib,&ee_calib,sizeof calib);
 if (hdmy[0]<24
 && byte(hdmy[1]-1)<31
 && byte(hdmy[2]-1)<12
 && hdmy[3]<100) {	// gültiges Datum im EEPROM?
  memcpy(&jetzt.t.hour,hdmy,4);	// Dann nimm dieses!
  jetzt.t.min=0;
  for (char i=8; --i>=0;) {			// Suche höchstwertiges 0-Bit in ee_min
   byte b=~eeprom_read_byte(ee_min+i);
   if (b) {
    jetzt.t.min=(i<<3)+16-__builtin_clz(b);	// Dies bestimmt die letzte Minute vor dem Stromausfall (Bit 59 wird nie gesetzt)
    break;
   }
  }
  jetzt.t.sec=55;	// gegen Ende
 }
 
 byte tag=0;
 byte minute=jetzt.t.min;
 for(;;) {
  if (minute!=jetzt.t.min) {
   eeprom_update_block(&jetzt.t.hour,&ee_hdmy,4);
   if (jetzt.t.min==0) {
    for (char i=0; i<8; i++) {
     while(EECR&0x02);
     EEAR=word(ee_min+i);
     cli();
     EECR=0x14;	// EEPROM-Byte nur löschen (1,8 ms), nicht programmieren
     EECR=0x16;
     sei();
    }
   }else{
    while(EECR&0x02);
    EEAR=word(ee_min+(minute>>3));
    EEDR=~(1<<(minute&7));	// Wenn Minute 0 vergangen, brenne 0b11111110, bei Minute 1 brenne 0b11111101 usw.
    cli();
    EECR=0x24;	// EEPROM-Byte nur programmieren (1,8 ms), nicht löschen. Daher bleiben Null-Bits stehen.
    EECR=0x26;
    sei();
   }
   EECR=0;
   minute=jetzt.t.min;
  }
  if (tag!=jetzt.d.day) {
   tag=jetzt.d.day;
   Hintergrund();
   dc.setFont(ArialBP24);
   dc.align=0;
   dc.gotoXY(16,25);	// oben anfangen
   ShowMuell(0);	// heute (oben)
   ShowMuell(1);	// morgen (darunter)
   ShowMuell(2);
//   dc.setFont(ArialBP19);	// kleiner Font
   dc.align=2;			// zentrieren
   ShowDate();
  }

  if (ts.isTouching()) {
   int y=ts.readTouch(ts.TOUCH_Y);	// Hier nicht transformieren, Kalibrierung liegt evtl. gar nicht vor
   if (y<512) {			// obere Hälfte geklickt: Touchscreen rekalibrieren
    if (calibrate()) eeprom_update_block(&calib,&ee_calib,sizeof calib);
   }else if (ts.readTouch(ts.TOUCH_X)<512) {
    if (MonthCal()) eeprom_update_block(Tonne,Tonne_E,sizeof Tonne);	// unten links: Monatskalender
    else eeprom_read_block(Tonne,Tonne_E,sizeof Tonne);
   }else SetTime();		// unten rechts: Uhr stellen
   tag=0;
  }else{
   dc.setColor(COLOR_WHITE);
   ShowTime();
   animate_morgen.paint();
  }
  ts.wait(-2);		// warten bis Touch oder Interrupt
 }
}
Detected encoding: UTF-80