/* 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-8 | 0
|