// Müllkalender, basierend auf
// UTFT_Demo_320x240
// Mikrocontroller: ATmega328
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <string.h>
#include "UTFT.h"
#include "TouchScreen.h"
// Dieser Datensatz: Frank Philippczyk 2016 + Januar 2017
PROGMEM const char GelbeTonne[]={
/*Januar*/ 6,20,0,
/*Februar*/ 5,19,0,
/*März*/ 4,18,0,
/*April*/ 2,15,29,0,
/*Mai*/ 13,27,0,
/*Juni*/ 10,24,0,
/*Juli*/ 8,22,0,
/*August*/ 5,19,0,
/*September*/ 2,16,30,0,
/*Oktober*/ 14,28,0,
/*November*/ 11,25,0,
/*Dezember*/ 9,23,0};
PROGMEM const char GraueTonne[]={
/*Januar*/ 12,26,0,
/*Februar*/ 11,25,0,
/*März*/ 10,24,0,
/*April*/ 7,21,0,
/*Mai*/ 6,20,0,
/*Juni*/ 2,16,30,0,
/*Juli*/ 14,28,0,
/*August*/ 11,25,0,
/*September*/ 8,22,0,
/*Oktober*/ 7,20,0,
/*November*/ 4,18,0,
/*Dezember*/ 1,15,30,0};
PROGMEM const char BlaueTonne[]={
/*Januar*/ 18,0,
/*Februar*/ 17,0,
/*März*/ 16,0,
/*April*/ 13,0,
/*Mai*/ 11,0,
/*Juni*/ 8,0,
/*Juli*/ 6,0,
/*August*/ 3,31,0,
/*September*/ 28,0,
/*Oktober*/ 26,0,
/*November*/ 23,0,
/*Dezember*/ 21,0};
PROGMEM const char BrauneTonne[]={
/*Januar*/ 4,11,18,25,0,
/*Februar*/ 3,10,17,24,0,
/*März*/ 2,9,16,23,31,0,
/*April*/ 6,13,20,27,0,
/*Mai*/ 4,11,19,25,0,
/*Juni*/ 1,8,15,22,29,0,
/*Juli*/ 6,13,20,27,0,
/*August*/ 3,10,17,24,31,0,
/*September*/ 7,14,21,28,0,
/*Oktober*/ 6,12,19,26,0,
/*November*/ 3,9,17,23,30,0,
/*Dezember*/ 7,14,21,29,0};
struct _time{
char sec,min,hour;
};
struct _date{
char day,month,year,jh;
char maxday() const; // Letzten Tag des Monats ermitteln
void morgen(); // Tag um 1 erhöhen
bool ereignis(const char*p) const; // Ereignis aus o.a. Tabelle finden
char dow() const;
};
// Makros um die Kompilierzeit als Startzeit einzusetzen
// (d.h. automatisches Stellen der Uhr beim „Betanken“)
#define YEAR ( __DATE__[9]*10+__DATE__[10]-528)
#define MONTH ( __DATE__[0]=='J'&&__DATE__[1]=='a'?1:\
__DATE__[0]=='F'?2:\
__DATE__[0]=='M'&&__DATE__[2]=='r'?3:\
__DATE__[0]=='A'&&__DATE__[1]=='p'?4:\
__DATE__[0]=='M'?5:\
__DATE__[0]=='J'&&__DATE__[2]=='n'?6:\
__DATE__[0]=='J'?7:\
__DATE__[0]=='A'?8:\
__DATE__[0]=='S'?9:\
__DATE__[0]=='O'?10:\
__DATE__[0]=='N'?11:\
__DATE__[0]=='D'?12:0)
#define DAY ((__DATE__[4]>='0'?__DATE__[4]:'0')*10+__DATE__[5]-528)
#define HOUR ( __TIME__[0]*10+__TIME__[1]-528)
#define MIN ( __TIME__[3]*10+__TIME__[4]-528)
#define SEC ( __TIME__[6]*10+__TIME__[7]-528)
// 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
struct _jetzt{
_time t;
_date d;
}jetzt={{SEC,MIN,HOUR},{DAY,MONTH,YEAR,20}};
UTFT dc;
TouchScreen ts;
extern const byte ArialBP19[] PROGMEM;
extern const byte ArialBP24[] PROGMEM;
/*
void debug(const __FlashStringHelper*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;
static void ClearMiddle(word bgcolor=COLOR_BLACK) {
dc.clrOrg();
dc.setClip(2,19,dc.getDisplayXSize()-3,dc.getDisplayYSize()-19);
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;
}
/********************************
* _date-Objektfunktionen *
********************************/
bool _date::ereignis(const char*p) const{
// monatsweise vorrücken
for (byte i=month;i>1;i--) p+=strlen_P(p)+1;
// Tag suchen
return strchr_P(p,day)!=0;
}
char _date::dow() const{
char m=month, y=year;
if (m<3) m+=10, y--;
else m-=2;
return ((26*m-2)/10+day+y+(y>>2)+(jh>>2)-2*jh+14)%7;
}
char _date::maxday() const{
if (month==2) return year&3?28:29; // ohne Jahrhundertregel
return ((month>>3)^month)&1?31:30; // ungerade Monate 31 Tage, ab August (8!) gerade Monate
}
void _date::morgen() {
if (++day>maxday()) {
day=1; if (++month>12) {
month=1; ++year;
}
}
}
/*************************************
* Animation des Randes (sekündlich) *
*************************************/
struct _animate{
int y,ye;
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.fillRect(16-w,y-w,16,ye+w); // links
dc.fillRect(16,y-w,224,y); // oben
dc.fillRect(224,y-w,224+w,ye+w); // rechts
dc.fillRect(16,ye,224,ye+w); // unten
color=~color;
}
}
/****************************************
* Müll-Box anzeigen und *
* Animationskoordinaten aktualisieren *
* (1x täglich zu Mitternacht) *
****************************************/
byte Sorte(const _date&d) { // jedes Bit des Returnwertes repräsentiert eine Müllsorte
byte ret=0;
if (d.ereignis(GelbeTonne)) ret|=1;
if (d.ereignis(GraueTonne)) ret|=2;
if (d.ereignis(BlaueTonne)) ret|=4;
if (d.ereignis(BrauneTonne)) ret|=8;
return ret;
}
void gotoXdY(int x, int dy) {
dc.gotoXY(x,dc.whereY()+dy);
}
static 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 __FlashStringHelper*>(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,20); // Cursorposition für die nächste Box hinterlassen
}
/****************************************
* Uhrzeit anzeigen / aktualisieren *
* (1x pro Sekunde) *
****************************************/
void ShowDate() {
static PROGMEM const char wt[]=
"Sonntag\0Montag\0Dienstag\0Mittwoch\0Donnerstag\0Freitag\0Sonnabend";
static PROGMEM const char mn[]=
"Januar\0Februar\0März\0April\0Mai\0Juni\0"
"Juli\0August\0September\0Oktober\0November\0Dezember";
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.gotoXY(120,240);
dc.print(s);
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.gotoXY(120,259);
dc.print(s);
}
void ShowTime() {
char s[20];
sprintf_P(s,PSTR(" %02d:%02d:%02d "),jetzt.t.hour,jetzt.t.min,jetzt.t.sec);
dc.gotoXY(120,278);
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();
}
// Interruptbedienroutine für Pin-Change (Touchscreen)
// Tut nichts als den Prozessor aufzuwecken
EMPTY_INTERRUPT(PCINT0_vect);
void Rahmen() {
// Clear the screen and draw the frame
dc.setBackColor(COLOR_SILVER);
dc.clrScr();
dc.setColor(COLOR_BLACK);
dc.setFont(ArialBP19);
dc.align=2;
dc.gotoXY(120,320-19);
dc.print(F("Frank Philippczyk, 2016")); // 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+=5) {
dc.drawLine(dc.clip.L,dc.clip.T+i,dc.clip.L+(i<<1),dc.clip.B);
}
for (int i=y; i>0; i-=5) {
dc.drawLine(dc.clip.R,dc.clip.T+i,dc.clip.L+(i<<1),dc.clip.T);
}
dc.setColor(0,255,255);
for (int i=y; i>0; i-=5) {
dc.drawLine(dc.clip.L,dc.clip.T+i,dc.clip.R-(i<<1),dc.clip.T);
}
for (int i=0; i<y; i+=5) {
dc.drawLine(dc.clip.R,dc.clip.T+i,dc.clip.R-(i<<1),dc.clip.B);
}
}
/****************************************
* Uhrzeit-Eingabe per Touchscreen *
****************************************/
int buttonidx;
struct _rect{
int l,t,r,b; // anklickbare Knöpfe
}buttons[14];
_jetzt Stellen; // Zu stellende Uhrzeit
void Button(int x, int y, const char*s,
word tlcolor=RGB(240,240,240), word brcolor=RGB(128,128,128)) {
int cx=dc.getTextExtent(s);
int x1=x-(cx>>1)-10;
int x2=x1+cx+20;
int y1=y-2;
int y2=y1+4+19+4;
dc.setColor(COLOR_SILVER);
dc.fillRect(x1,y1,x2,y2);
dc.setColor(tlcolor);
dc.fillRect(x1-2,y1-2,x1,y2+2); // Schattenrand: links
dc.fillRect(x1,y1-2,x2+2,y1); // oben
dc.setColor(brcolor);
dc.fillRect(x2,y1,x2+2,y2+2); // rechts
dc.fillRect(x1,y2,x2,y2+2); // unten
dc.drawPixel(x1-1,y2+1); // links unten
dc.drawPixel(x2+1,y1-1); // rechts oben
dc.gotoXY(x,y);
dc.setColor(COLOR_BLACK);
dc.setBackColor(COLOR_SILVER);
dc.print(s);
if (tlcolor!=brcolor) {
_rect&r=buttons[buttonidx++];
r.l=x1-2;
r.t=y1-2;
r.r=x2+2;
r.b=y2+2;
}
}
void UpDown(int n) {
char s[20];
int y=dc.whereY();
sprintf_P(s,PSTR("%d"),n);
Button(120,y,s,COLOR_BLUE,COLOR_BLUE);
Button(40,y,"–"); // Mathematisches Minus!
Button(200,y,"+");
}
// Zeichnet alles oder nur das Clipping-Rechteck
void SetTime_Redraw(byte was=0xFF) {
if (was&0x80) {
Rahmen(); // Alles löschen
dc.gotoXY(120,0);
dc.print(F("Datum/Uhrzeit stellen"));
dc.setColor(RGB(200,200,200));
dc.fillRect(2,19,238,301);
}
dc.setFont(ArialBP24);
dc.align=2;
dc.gotoXY(120,30); // oben anfangen
buttonidx=0;
if (was&0x01) UpDown(Stellen.d.jh*100+Stellen.d.year); gotoXdY(120,38);
if (was&0x02) UpDown(Stellen.d.month); gotoXdY(120,38);
if (was&0x04) UpDown(Stellen.d.day); gotoXdY(120,38);
if (was&0x08) UpDown(Stellen.t.hour); gotoXdY(120,38);
if (was&0x10) UpDown(Stellen.t.min); gotoXdY(120,38);
if (was&0x20) UpDown(Stellen.t.sec); gotoXdY(120,38);
if (was&0x80) {
Button(40,260,"OK");
Button(160,260,"Abbrechen");
}
}
struct _point{
int x,y;
}touchpoint;
// Kalibrierdaten für Touchscreen
// Index 0: A/D-Ergebnis für linke Kante
// Index 1: A/D-Ergebnis für obere Kante
// Index 2: A/D-Ergebnis für rechte Kante
// Index 3: A/D-Ergebnis für untere Kante
const word calib[4]={4000,8000,65000,65000};
word wwtrafo(word x,word a,word e,word A, word E) {
return (unsigned long)(x-a)*(E-A)/(e-a)+A;
}
void Warte() {
dc.clrOrg();
dc.clrClip();
for (;;) {
if (ts.isTouching()) break;
_delay_ms(100);
sleep_cpu();
}
dc.setColor(COLOR_YELLOW);
int l=239,t=319,r=0,b=0;
while (ts.isTouching()) {
int x=touchpoint.x=wwtrafo(ts.readTouch(TOUCH_X),calib[0],calib[2],0,DISP_X_SIZE-1);
int y=touchpoint.y=wwtrafo(ts.readTouch(TOUCH_Y),calib[1],calib[3],0,DISP_Y_SIZE-1);
dc.setColor(255,240,0);
dc.fillCircle(x,y,20);
dc.setColor(COLOR_BLUE);
dc.drawCircle(x,y,20);
dc.drawCircle(x,y,21);
dc.drawCircle(x,y,22);
if (l>x-22) l=x-22;
if (t>y-22) t=y-22;
if (r<x+22) r=x+22;
if (b<y+22) b=y+22;
_delay_ms(200); // zeige Berührpunkt zur Kontrolle
}
dc.setClip(l,t,r,b);
SetTime_Redraw(); // Nur den vermatschten Bereich neu zeichnen
dc.clrClip();
}
int HitTest() {
for (int i=0; i<14; i++) {
_rect&r=buttons[i];
if (r.l<=touchpoint.x && touchpoint.x<r.r
&& r.t<=touchpoint.y && touchpoint.y<r.b) return i; // gefunden
}
return -1; // daneben!
}
void SetTime() {
Stellen=jetzt;
dc.clrClip();
SetTime_Redraw();
for(;;) {
Warte();
int ht;
switch (ht=HitTest()) {
case 0: Stellen.d.year--; if (Stellen.d.year<0) Stellen.d.year=99, Stellen.d.jh--; break;
case 1: Stellen.d.year++; if (Stellen.d.year>99) Stellen.d.year=0, Stellen.d.jh++; break;
case 2: Stellen.d.month--; if (Stellen.d.month<1) Stellen.d.month=12; break;
case 3: Stellen.d.month++; if (Stellen.d.month>12) Stellen.d.month=1; break;
case 4: Stellen.d.day--; if (Stellen.d.day<1) Stellen.d.day=Stellen.d.maxday(); break;
case 5: Stellen.d.day++; if (Stellen.d.day>Stellen.d.maxday()) Stellen.d.day=1; break;
case 6: Stellen.t.hour--; if (Stellen.t.hour<0) Stellen.t.hour=23; break;
case 7: Stellen.t.hour++; if (Stellen.t.hour>23) Stellen.t.hour=0; break;
case 8: Stellen.t.min--; if (Stellen.t.min<0) Stellen.t.min=59; break;
case 9: Stellen.t.min++; if (Stellen.t.min>59) Stellen.t.min=0; break;
case 10: Stellen.t.sec--; if (Stellen.t.sec<0) Stellen.t.sec=59; break;
case 11: Stellen.t.sec++; if (Stellen.t.sec>59) Stellen.t.sec=0; break;
case 12: jetzt=Stellen; /*nobreak*/ // Daten übernehmen und Uhr starten
case 13: return; // Daten verwerfen
default: goto noredraw;
}
SetTime_Redraw(1<<(ht>>1)); // Nur eine Knopfzeile neu malen
noredraw:;
}
}
// Hauptprogramm
int main() {
dc.InitLCD(0);
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 =0xF7; // Alles anhalten außer Timer1 (sollte Icc=8mA generieren)
PCMSK0=0x02;
PCICR =0x01; // Pegelwechselinterrupt für Touchscreen aktivieren
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.
byte hdmy[4];
eeprom_read_block(hdmy,ee_hdmy,4);
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,30); // 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()) {
SetTime();
tag=0;
}else{
dc.setColor(COLOR_WHITE);
ShowTime();
animate_morgen.paint();
}
sleep_cpu(); // auf dem Arduino-Board verlöschen 2 LEDs
}
}
Vorgefundene Kodierung: UTF-8 | 0
|