Go Down

Topic: Arduino Weckerradio mit Alarmfunktion und, und, und... (Read 2015 times) previous topic - next topic

soundy

Hallo in die Runde,

ich bin der Neue hier.  :) Leider noch ziemlicher Anfänger, aber ich habe ein großes Projekt vor und habe schon einiges gelernt, was ich in meinem bisherigen Sketch zum geplanten "UhrenRadio" zusammen gefügt habe.

Geplant sind:

  - Uhrzeit per DCF77, NTP, RDS/DAB holen.
  - RTC DS3231 abfragen und anzeigen.
  - Temperatur DS18B20 abfragen und anzeigen.
  - Kompletten UKW/DAB/DAB+ Radio steuern.
  - Alarm-Funktion (ev. mit MP3) einbauen.
  - Und noch so manches mehr...

Hier mal der Code, der nur für die "Alarmeingabe" (Uhrzeit) per IR-Fernbedienung, gehört:

Code: [Select]
#include <LiquidCrystal_I2C.h>
#include <IRremote.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);       // Hex-Adresse, 16 x 2 Zeichen
IRrecv irrecv(11);                        // Digital Pin vom IR-Receiver
decode_results results;                   // IR-Receiver decodieren

bool backlightstate = 1;
bool writelcd = 1;
int digit = 0;
char alarmtimeinput[4] = "0000";

uint8_t alarmstunde, alarmminute;  // Holds the current alarm time

void setup() {
  Serial.begin(9600);                     // Serieller Monitor
  lcd.init();                             // Im Setup wird der LCD gestartet
  lcd.setBacklight(1);                    // LCD-Backlight einschalten
  irrecv.enableIRIn();                    // IR-Receiver initialisieren
  Serial.println("Alarm Input:");
}

void readalarminput() {

  lcd.blink();

  if (digit == 0) {
    if (writelcd == 1) {
      lcd.setCursor(0,0);
      lcd.print("Eingabe:");
      lcd.setCursor(0,1);
      lcd.print("__:__");
      lcd.setCursor(0,1);
      writelcd = 0;
    }
  }

  if (digit == 2) {
    if (writelcd == 1) {
      lcd.setCursor(3,1);
      writelcd = 0;
    } else {
      alarmstunde = (alarmtimeinput[1])*10 + (alarmtimeinput[2]);
      if (alarmstunde > 23) {
        alarmstunde = 0;
        digit = 0;
        alarmtimeinput[1] = "0";
        alarmtimeinput[2] = "0";
        lcd.clear();
        Serial.println("Stunde max. 23 !!!");     
        writelcd = 1;
        return;
      } else {
        writelcd = 1;
      }
    }   
  }

  if (digit == 4) {   
    alarmminute = (alarmtimeinput[3])*10 + (alarmtimeinput[4]);
    if (alarmminute > 59) {
      alarmminute = 0;
      digit = 2;
      alarmtimeinput[3] = "0";
      alarmtimeinput[4] = "0";
      lcd.clear();
      Serial.println("Minute max. 59 !!!");     
      return;
    } else {
      digit = 5;
      lcd.noCursor();
      lcd.setCursor(13,1);
      lcd.print("-/+");     // Derzeit noch ohne Abfrage!
 
      Serial.print("NEUER ALARM: ");
      Serial.print(alarmstunde);
      Serial.print(":");
      Serial.println(alarmminute);
    }
  }

  if (digit == 5) {
    if (results.value == 0xFFA857) {
      // Wenn + gedrückt!
      Serial.println("OK");
      lcd.clear();
      lcd.print("Alarm um ");
      lcd.print(alarmstunde);
      lcd.print(":");
      lcd.print(alarmminute);
      lcd.noCursor();
      digit = 0;
      writelcd = 0;
    }
    if (results.value == 0xFFE01F) {
      // Wenn - gedrückt!
      Serial.println("Cancel");
      digit = 0;
      writelcd = 1;
    }
  }
 
 
  if (irrecv.decode(&results)) {          // Wenn der IR-Receiver etwas empfängt... 
    // Serial.println(results.value,HEX);    // Hex-Wert seriell ausgeben 

    switch(results.value) {
      case 0xFF6897:
        Serial.println("0");
        lcd.print("0");
        digit++;
        alarmtimeinput[digit] = 0;
      break;
      case 0xFF30CF:
        Serial.println("1");
        lcd.print("1");
        digit++;
        alarmtimeinput[digit] = 1;
      break;
      case 0xFF18E7:
        Serial.println("2");
        lcd.print("2");
        digit++;
        alarmtimeinput[digit] = 2;
      break;
      case 0xFF7A85:
        Serial.println("3");
        lcd.print("3");
        digit++;
        alarmtimeinput[digit] = 3;
      break;
      case 0xFF10EF:
        Serial.println("4");
        lcd.print("4");
        digit++;
        alarmtimeinput[digit] = 4;
      break;
      case 0xFF38C7:
        Serial.println("5");
        lcd.print("5");
        digit++;
        alarmtimeinput[digit] = 5;
      break;
      case 0xFF5AA5:
        Serial.println("6");
        lcd.print("6");
        digit++;
        alarmtimeinput[digit] = 6;
      break;
      case 0xFF42BD:
        Serial.println("7");
        lcd.print("7");
        digit++;
        alarmtimeinput[digit] = 7;
      break;
      case 0xFF4AB5:
        Serial.println("8");
        lcd.print("8");
        digit++;
        alarmtimeinput[digit] = 8;
      break;
      case 0xFF52AD:
        Serial.println("9");
        lcd.print("9");
        digit++;
        alarmtimeinput[digit] = 9;
      break;
    }

  irrecv.resume();
  }
 
}

void loop() {

  /*

  TODO:
  - Uhrzeit per DCF77, NTP, RDS/DAB holen.
  - RTC DS3231 abfragen und anzeigen.
  - Temperatur DS18B20 abfragen und anzeigen.
  - Kompletten UKW/DAB/DAB+ Radio steuern.
  - Alarm-Funktion (ev. mit MP3) einbauen.

  */
 
  readalarminput();
 
}


Irgendwie habe ich das Gefühl, dass dieser Teil viel kürzer möglich ist und optimiert werden könnte, da ich sonst bestimmt nicht alles in meinen Arduino rein bekomme, was noch so geplant ist.

Das Auslesen und Anzeigen der Uhrzeit bzw. Temparatur habe ich in einem anderen Sketch, aber das ist ja anscheinend noch die leichtere Übung.

Die Steuerung soll weitgehend über eine IR-Fernbedienung laufen, am Wecker soll es später nur wenige (oder keine) Knöpfe geben. Um die "Snooze"-Taste komme ich wohl nicht herum.

Hat jemand Tips, da ich garantiert nicht recht optimal programmiert habe, das ist mir bewußt. :-(

Danke schön mal und ich freue mich schon richtig auf's intensive Lernen.

Lg, Jürgen

HotSystems

Welchen Arduino setzt du ein ?
Mit den kleinen (Atmega328) kommst du sicher an die Speichergrenzen.
Warum DCF77 o. ä. und eine RTC ?
Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)

Serenifly

Das mit alarmtimeinput ist seltsam. Am manchen Stellen benutzt du es als String und an anderen Stellen als Integer und rechnest damit

Das hier ist generell falsch:
Code: [Select]

alarmtimeinput[3] = "0";

Hier weißt du einen C String auf ein einzelnes Zeichen zu

soundy

Welchen Arduino setzt du ein ?
Warum DCF77 o. ä. und eine RTC ?
Aktuell einen Arduino Nano V3 (Clone), habe aber auch einen Arduino Uno (Genuine) zur Verfügung.

DCF77, NTP (über WLAN), o.ä. damit ich eine 100% genaue Zeit habe.
RTC dafür, dass bei Stromausfall die Zeit nachher wieder vorhanden ist.

Das mit alarmtimeinput ist seltsam. Am manchen Stellen benutzt du es als String und an anderen Stellen als Integer und rechnest damit.
Ich habe auch Beispiele gesucht und in einem war das so umgesetzt. Als Einstieg sind eben "Beispiele" oft am einfachsten, weil man erkennt, wie andere es machen - wie wäre das ganze einfacher und sinnvoll lösbar?

Serenifly

Es geht nicht um einfacher und sinnvoll, sondern darum dass das schlichtweg falsch ist. Das mag vielleicht kompilieren aber das heißt nicht dass es richtig ist und keine Probleme verursacht. Aktiviere mal die Warnungen denn sieht du gleich sowas:
Quote
warning: invalid conversion from 'const char*' to 'char' [-fpermissive]
Wenn du das Integer behandeln willst dann speichere auch Integer ab. Und lass das mit den Strings weg

HotSystems

Aktuell einen Arduino Nano V3 (Clone), habe aber auch einen Arduino Uno (Genuine) zur Verfügung.

DCF77, NTP (über WLAN), o.ä. damit ich eine 100% genaue Zeit habe.
RTC dafür, dass bei Stromausfall die Zeit nachher wieder vorhanden ist.
Wenn du mit der RTC3231 arbeitest, brauchst du kein DCF77 oder NTP. Die RTC ist sehr genau und braucht keine weiteren Komponenten. Hast du NTP eingerichtet, wird auch nach einem Stromausfall schnell wieder die richtige Zeit angezeigt. Bei DCF77 dauert es immer ein wenig.

Jedoch sehe ich Probleme das alles in einem Atmega328 (Nano oder Uno) sauber zum Laufen zu bringen. Allein IRremote ist sehr speicherhungrig. Da ist es schon angebracht die IR-Steuerung in einen weiteren Controller (evtl. ATtiny85) auszulagern.
Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)

soundy

Es geht nicht um einfacher und sinnvoll, sondern darum dass das schlichtweg falsch ist.
Das ist schlichtweg ein Anfängerfehler, wenn man " und ' als gleichwertig behandelt. :-(

Ich habe nun aus alarmtimeinput[1] = "0"; das korrekte alarmtimeinput[1] = '0' gemacht.

Die Kompilerfehlermeldungen habe ich nicht aktiviert gehabt, es handelt sich um eine Standartinstallation, wo das offensichtlich bis auf grobe Fehler komplett abgeschaltet ist. Nun habe ich es eingeschaltet und bin froh, dass es so ist - DANKE FÜR DEN TIPP :)



Wenn du mit der RTC3231 arbeitest, brauchst du kein DCF77 oder NTP. Die RTC ist sehr genau und braucht keine weiteren Komponenten. Hast du NTP eingerichtet, wird auch nach einem Stromausfall schnell wieder die richtige Zeit angezeigt. Bei DCF77 dauert es immer ein wenig.
DCF77 möchte ich ohnehin vermeiden, da es bei uns massive Probleme mit dem Empfang gibt (Stahlbetonbau). Lieber würde ich auf NTP (über ein WLAN-Shield) gehen, aber das lasse ich derzeit noch weg, um die Angelegenheit nicht noch komplexer zu machen.



Hier nochmal der komplette Sketch:

Code: [Select]
#include <LiquidCrystal_I2C.h>
#include <IRremote.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);       // Hex-Adresse, 16 x 2 Zeichen
IRrecv irrecv(11);                        // Digital Pin vom IR-Receiver
decode_results results;                   // IR-Receiver decodieren

bool backlightstate = 1;
bool writelcd = 1;
int digit = 0;
char alarmtimeinput[] = "0000";

uint8_t alarmstunde, alarmminute;  // Holds the current alarm time

void setup() {
  Serial.begin(9600);                     // Serieller Monitor
  lcd.init();                             // Im Setup wird der LCD gestartet
  lcd.setBacklight(1);                    // LCD-Backlight einschalten
  irrecv.enableIRIn();                    // IR-Receiver initialisieren
  Serial.println("Alarm Input:");
}

void readalarminput() {

  lcd.blink();

  if (digit == 0) {
    if (writelcd == 1) {
      lcd.setCursor(0,0);
      lcd.print("Eingabe:");
      lcd.setCursor(0,1);
      lcd.print("__:__");
      lcd.setCursor(0,1);
      writelcd = 0;
    }
  }

  if (digit == 2) {
    if (writelcd == 1) {
      lcd.setCursor(3,1);
      writelcd = 0;
    } else {
      alarmstunde = (alarmtimeinput[1])*10 + (alarmtimeinput[2]);
      if (alarmstunde > 23) {
        alarmstunde = 0;
        digit = 0;
        alarmtimeinput[1] = '0';
        alarmtimeinput[2] = '0';
        lcd.clear();
        Serial.println("Stunde max. 23 !!!");     
        writelcd = 1;
        return;
      } else {
        writelcd = 1;
      }
    }   
  }

  if (digit == 4) {   
    alarmminute = (alarmtimeinput[3])*10 + (alarmtimeinput[4]);
    if (alarmminute > 59) {
      alarmminute = 0;
      digit = 2;
      alarmtimeinput[3] = '0';
      alarmtimeinput[4] = '0';
      lcd.clear();
      Serial.println("Minute max. 59 !!!");     
      return;
    } else {
      digit = 5;
      lcd.noCursor();
      lcd.setCursor(13,1);
      lcd.print("-/+");     // Derzeit noch ohne Abfrage!
 
      Serial.print("NEUER ALARM: ");
      Serial.print(alarmstunde);
      Serial.print(":");
      Serial.println(alarmminute);
    }
  }

  if (digit == 5) {
    if (results.value == 0xFFA857) {
      // Wenn + gedrückt!
      Serial.println("OK");
      lcd.clear();
      lcd.print("Alarm um ");
      lcd.print(alarmstunde);
      lcd.print(":");
      lcd.print(alarmminute);
      lcd.noCursor();
      digit = 0;
      writelcd = 0;
    }
    if (results.value == 0xFFE01F) {
      // Wenn - gedrückt!
      Serial.println("Cancel");
      digit = 0;
      writelcd = 1;
    }
  }
 
 
  if (irrecv.decode(&results)) {          // Wenn der IR-Receiver etwas empfängt... 
    // Serial.println(results.value,HEX);    // Hex-Wert seriell ausgeben 

    switch(results.value) {
      case 0xFF6897:
        Serial.println("0");
        lcd.print("0");
        digit++;
        alarmtimeinput[digit] = 0;
      break;
      case 0xFF30CF:
        Serial.println("1");
        lcd.print("1");
        digit++;
        alarmtimeinput[digit] = 1;
      break;
      case 0xFF18E7:
        Serial.println("2");
        lcd.print("2");
        digit++;
        alarmtimeinput[digit] = 2;
      break;
      case 0xFF7A85:
        Serial.println("3");
        lcd.print("3");
        digit++;
        alarmtimeinput[digit] = 3;
      break;
      case 0xFF10EF:
        Serial.println("4");
        lcd.print("4");
        digit++;
        alarmtimeinput[digit] = 4;
      break;
      case 0xFF38C7:
        Serial.println("5");
        lcd.print("5");
        digit++;
        alarmtimeinput[digit] = 5;
      break;
      case 0xFF5AA5:
        Serial.println("6");
        lcd.print("6");
        digit++;
        alarmtimeinput[digit] = 6;
      break;
      case 0xFF42BD:
        Serial.println("7");
        lcd.print("7");
        digit++;
        alarmtimeinput[digit] = 7;
      break;
      case 0xFF4AB5:
        Serial.println("8");
        lcd.print("8");
        digit++;
        alarmtimeinput[digit] = 8;
      break;
      case 0xFF52AD:
        Serial.println("9");
        lcd.print("9");
        digit++;
        alarmtimeinput[digit] = 9;
      break;
    }

  irrecv.resume();
  }
 
}

void loop() {

  /*

  TODO:
  - Uhrzeit per DCF77, NTP, RDS/DAB holen.
  - RTC DS3231 abfragen und anzeigen.
  - Temperatur DS18B20 abfragen und anzeigen.
  - Kompletten UKW/DAB/DAB+ Radio steuern.
  - Alarm-Funktion (ev. mit MP3) einbauen.

  */
 
  readalarminput();
 
}


Ist eine so einfache Eingabe einer Zeit sooo aufwendig? Kann es sein, dass man sowas viel einfacher programmieren kann?

Bei meinem Arduino Nano V3 Clone (zumindest derzeit noch) meldet der Kompiler:

Code: [Select]
Der Sketch verwendet 11058 Bytes (35%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes.
Globale Variablen verwenden 791 Bytes (38%) des dynamischen Speichers, 1257 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.


Mir kommt das viel zu viel vor... :-(

agmue

Was hältst Du von der Verwendung einer Funktion (ungetestet)?

Code: [Select]
      case 0xFF6897:
        ausgabealarm(0);
      break;
...
void ausgabealarm(byte num) {
        Serial.println(num);
        lcd.print(num);
        digit++;
        alarmtimeinput[digit] = num;
}
Die Vorstellungskraft ist wichtiger als Wissen, denn Wissen ist begrenzt. (Albert Einstein)

soundy

Wooowww, das ist genial, vielen Dank. :-)

Im Gesamtskript (nicht nur der obige Teil, der hier gepostet ist)
habe ich nun 15712 statt 16090 Byte laut Kompiler.

Gibt es eigentlich eine (fertige) Bibliothek für solche Eingaben und Prüfungen vom Benutzer? Ich habe bisher nichts gefunden, aber eventuell suche ich auch falsch - das würde es ggf. deutlich vereinfachen.

agmue

Gibt es eigentlich eine (fertige) Bibliothek für solche Eingaben und Prüfungen vom Benutzer?
Irgendwo im WWW möglicherweise schon, nur bringt Dir das nichts, denn eine Bibliothek ist nur ausgelagerter Code. Verschiedene hier im Forum verlagern Code in mehrere Dateien, die man dann über Tabs im Editor der IDE sieht. Das kann dem Menschen helfen, die Übersicht zu bewahren, dem Kompiler ist das wurscht.

Auch kannst Du zwischen prozeduraler Programmierung und OOP wählen, da gibt es immer Argumente in die eine und die andere Richtung, manchmal ist es projektabhängig.

Wenn Du den µC rocken willst, dann nutzt Du Assembler, ist dann aber schon was für Spezialisten. Ich kenne so einen, darum sei es hier erwähnt.

Was Du selbst schreibst, verstehst Du auch, ein, wie ich finde, nicht zu unterschätzendes Argument.

Es gibt hardwarenahe Biobliotheken, die möchte ich keinesfalls missen, den Rest macht man besser selber.

Programmieren ist kein Sammeln von APPs, sondern harte Arbeit. Mir macht das Spaß!


Du könntest auch mal diese Variante probieren:

Code: [Select]
const uint16_t ircodes[] PROGMEM = {0x6897, 0x30CF, 0x18E7, 0x7A85, 0x10EF, 0x38C7, 0x5AA5, 0x42BD, 0x4AB5, 0x52AD};
const byte anzcodes = sizeof(ircodes) / sizeof(ircodes[0]);

void setup() {
  Serial.begin(9600);                     // Serieller Monitor
  Serial.println("Start");
  uint32_t result = 0xFF18E7;
  Serial.println(entschluesseln(result & 0xFFFF));
}

int8_t entschluesseln(uint16_t code) {
  for (byte j = 0; j < anzcodes; j++) {
    if (pgm_read_word(&ircodes[j]) == code) return j;
  }
  return -1;  // Fehler
}

void loop() {
}
Die Vorstellungskraft ist wichtiger als Wissen, denn Wissen ist begrenzt. (Albert Einstein)

Whandall

Wenn Du den µC rocken willst, dann nutzt Du Assembler, ist dann aber schon was für Spezialisten. Ich kenne so einen, darum sei es hier erwähnt.
Dem kann ich dir nicht zustimmen.

Wenn man mit einem nicht-trivialen Projekt fertig werden will, meidet man Assembler wo immer möglich.  ;)
 
Heutige Compiler erzeugen Kode, der sich absolut mit mühevoll handoptimierten Assembler Kode messen kann.

Zudem ist Assembler nicht portabel, fast unwartbar und sehr schwer zu lesen,
speziell auf einem 8-Bitter, wo jede kleine Operation einen Haufen von Instruktionen erfordert.

Weiterhin bieten Compiler auch die Möglichkeit Passagen oder ganze Funktionen in Assember einzubetten,
wenn ich auch die Syntax dafür beim Arduino Compiler für sehr gewöhnungsbedürftig halte.
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

agmue

Teensy 3.2 mit Arduino IDE gegen UNO Assembler bringt interessante Erkenntnisse.

Ich habe aber nur den Teensy beigesteuert und das Ergebnis gesehen, sonst bin ich nur staunender Zuschauer.

Doch dies ist OT und bringt den TO nicht weiter.
Die Vorstellungskraft ist wichtiger als Wissen, denn Wissen ist begrenzt. (Albert Einstein)

Whandall

Eine IDE hat nichts mit dem Compiler zu tun, teensy und UNO sind völlig andere Architekturen,
haben andere Compiler (mindestens andere Kodegeneratoren) und Assembler.

Das ist so gut wie Äpfel und Birnen, OT natürlich und hat noch nicht einmal etwas mit deiner Ausage

Quote
Wenn Du den µC rocken willst, dann nutzt Du Assembler
oder mit meinem Widerspruch dagegen zu tun.
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

qualidat

Pack doch dein Projekt gleich in einen ESP8266, z.B. WEMOS. Der hat mehr Speicher, WLAN onboard und genug Resourcen, auch noch ein Webradio zu integrieren ...

Für die Bedienoberfläche würde ich über ein Nextion-Display, gesteuert über Serial nachdenken. Mit einer gut gemachten GUI kann man "Fehleingaben" durch den Benutzer unterbinden - mal abgesehen von z.B. einer falschen Weckzeit, was aber aus technischer Sicht kein Fehler ist.

Übrigens - ein wirklich intelligenter Wecker ist eine echte Herausforderung, z.B. verschiedene Weckzeiten an verschiedenen Tagen, ohne den jedesmal komplett neu zu setzen, oder Weckzeiten einmalig, mehrmals, täglich, nur jeden x. Tag, variabel mit Piepsen, Summen, Stream vom Speicher/NAS oder Radio ... usw. Da kann man richtig drin aufgehen :-)

HotSystems

Pack doch dein Projekt gleich in einen ESP8266, z.B. WEMOS. Der hat mehr Speicher, WLAN onboard und genug Resourcen, auch noch ein Webradio zu integrieren ...

Für die Bedienoberfläche würde ich über ein Nextion-Display, gesteuert über Serial nachdenken. Mit einer gut gemachten GUI kann man "Fehleingaben" durch den Benutzer unterbinden - mal abgesehen von z.B. einer falschen Weckzeit, was aber aus technischer Sicht kein Fehler ist.
.....
Sicher eine sehr gute Idee, die ich allerdings keinem Anfänger vorschlagen würde.
Der hat mit den üblichen Grundlagen schon genug zu tun.

Mein Projekt "Radiowecker" habe ich mit einem Atmega328-Standalone realisiert und einem Nextion zzgl. MP3-Player.

Der macht sein Ding prima ist allerdings nicht für Anfänger.
Ein Update wird sicher mit einem Wemos und WLan-Radio aufgebaut.
Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)

Go Up