ich habe heute einen kleinen Sketch geschrieben, der den Arduino einen Analog-Pin auslesen lässt.
Fällt der Analog-Pin unter einen Grenzwert, legt sich der Arduino nach einer voreingestellten Zeit schlafen.
Er wacht periodisch nach einer gewissen Zeitdauer wieder auf und kontrolliert den Sensorwert.
Ist dieser noch unter dem Grenzwert, legt er sich wieder hin.
Ist dieser über dem Grenzwert, bleibt er wach.
Ich benötige diese Anwenung, weil ich energiesparend einen Helligkeitssensor auslesen will.
Ich hoffe ich kann mit dem Sketch jemandem weiterhelfen.
Der Sketch ist sehr stark an folgende Anleitung angeleht:
Danke deshalb an den Autor.
Bitte auch um Anregungen, falls etwas nicht stimmten sollte.
/*
* Der folgende Sketch laesst den Arduino in den Schlafmodus gehen und nach dem Aufwachen
* einen Sensorwert an einem Analogpin periodisch auslesen.
* Unterschreitet der Sensor einen Grenzwert, bleibt er noch eine gewisse Zeit wach, bis er sich wieder schlafen legt.
* Dadurch lassen sich Anwendungen, wie zum Beispiel helligkeitsabhaengige Sachen, energiesparender umsetzen.
*
*/
//inkludieren der AVR-Bibliotheken
#include <avr/sleep.h> //Bibliothek fuer den Schlafmodus
#include <avr/power.h> //Bibliothek fuer die Abschaltung der Peripherie
#define stay_up_time 5000 //Wartezeit nach unterschreiten des Grenzwerts
#define a_Sensor A0 //Sensor-Pin
#define a_Grenzwert 100 //Grenzwert am Sensor-Pin
boolean sleep_flag = 0; //Schlaf-Merker
long time_since_last = 0; //Zeit seit dem letzen ueberschreiten des Grenzwerts
void setup() {
// put your setup code here, to run once:
init_timer_1 (); //Timer initialisieren
Serial.begin (9600); //Serial Monitor aktivieren
}
void loop() {
// put your main code here, to run repeatedly:
if (sleep_flag == 0)
{
GO_TO_SLEEP ();
}
else
{
if (analogRead (a_Sensor) >= a_Grenzwert)
{
time_since_last = millis ();
}
else
{
if (millis ()- time_since_last >= stay_up_time)
{
sleep_flag = 0;
Serial.println ("Nichts zu tun - leg dich hin");
}
}
}
Serial.println (analogRead (a_Sensor));
}
//Timer_1 initialisieren
void init_timer_1 ()
{
//beide Timer-Register null setzen
TCCR1A = 0;
TCCR1B = 0;
//Einstellung, damit bei Timer-Overflow-Interrupt ausgeloest wird
TIMSK1 |= (1 << TOIE1);
//Timer-Counter-Startwert eine Vorladung zuweisen, damit dieser ca. 4 Sekunden hochzaehlt
//kein Wert zugewiesen heisst,dass es bis zum Overflow ca. 4,19s dauert ( bei 16 MHz und Prescaler 1024 (Overflow bei 65536))
TCNT1 = 3036;
//Prescaler-Bits auf 1024 setzen
TCCR1B |= (1 << CS12) | (1 << CS10);
}
void GO_TO_SLEEP ()
{
Serial.println ("Einschlafen");
//Zeit fuers rausschicken der seriallen Daten, damit der UART-Interrupt nicht den Arduino aus dem Schlaf reisst
delay (100);
//Sleep-mode auf Idle
set_sleep_mode(SLEEP_MODE_IDLE);
//Sleep-Funktionen aktivieren
sleep_enable();
//diverse Peripherie ausschalten
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer2_disable();
power_twi_disable();
//TCNT1 fuer korrekte Schlafdauer vorladen auf den gewuenschten Wert
TCNT1 = 3036;
//Schlafen legen
sleep_cpu();
//hier gehts im Falle eines Interrups weiter (welcher Interrupt ist im IDLE Modus egal, daher das delay oben)
sleep_disable();
//Peripherie wieder aktivieren
power_all_enable();
Serial.println ("Aufgewacht");
sleep_flag = 1;
}
//Timer 1 Overflow Interrupt
ISR(TIMER1_OVF_vect)
{
//Serial.println ("Overflow Timer 1");
}
Habe mir erlaubt Änderungen vorzunehmen damit der Sketch auch 49 Tage übersteht. millis gibt unsigned long zurück. Wertebereichsüberlauf! Bei eigner Nutzung von Timern müssen immer alle relevanten Register genullt werden. Die sind beim Arduino für millis, delay und analog PWM vorgeladen. Auch wenn man das nicht nutzt. In einer ISR haben andere Funktionen mit Interrupt nichts zu suchen, auch wenn es anscheinend erstmal funktioniert. Habe auch das korrigiert.
Die if else Verschachtelung ist entflechtet. Schlafen gehen darf er eh erst, wenn der Sensorwert unter dem Schwellwert liegt. Das ist die Hauptbedingung für alles weitere. Bedeutet das sleep_flag hat erst danach eine Relevanz und kann ganz weg wenn man an dessen Stelle die Funktion aufruft. Ansonsten baut man sich mit switch case und enums einen Zustandsautomaten, wenn es mehrere wechselnde Zustände gibt zwischen denen hin und her geschalten werden kann und soll.
Für den Timer1 Counter Vorladewert legste bitte noch eine Konstante an und weist diese in beiden Codezeilen zu. Damit hast du nur noch an einer Stelle die Änderungsmöglichkeit die überall greift. Erspart Fehler bei Änderungen.
Ansonsten wenn das alles selbst erarbeitet wurde - nicht schlecht. Weitermachen.
/*
https://forum.arduino.cc/index.php?topic=577991.0
Der folgende Sketch laesst den Arduino in den Schlafmodus gehen und nach dem Aufwachen
einen Sensorwert an einem Analogpin periodisch auslesen.
Unterschreitet der Sensor einen Grenzwert, bleibt er noch eine gewisse Zeit wach, bis er sich wieder schlafen legt.
Dadurch lassen sich Anwendungen, wie zum Beispiel helligkeitsabhaengige Sachen, energiesparender umsetzen.
*/
//inkludieren der AVR-Bibliotheken
#include <avr/sleep.h> // Bibliothek fuer den Schlafmodus
#include <avr/power.h> // Bibliothek fuer die Abschaltung der Peripherie
#include <util/atomic.h>
const unsigned int stay_up_time = 5000; // Wartezeit nach unterschreiten des Grenzwertes
const byte a_Sensor = A0; // Sensor-Pin
const byte a_Grenzwert = 100; // Grenzwert am Sensor-Pin
unsigned long time_since_last = 0; // Zeit seit dem letzen überschreiten des Grenzwertes
volatile bool flag_OVF1 = false;
void setup() {
init_timer_1 (); // Timer initialisieren
Serial.begin (9600); // Serial Monitor aktivieren
}
void loop() {
if (analogRead (a_Sensor) >= a_Grenzwert) {
time_since_last = millis ();
}
else {
if (millis () - time_since_last > stay_up_time) {
Serial.println ("Nichts zu tun - leg dich hin");
GO_TO_SLEEP ();
}
}
Serial.println (analogRead (a_Sensor));
detect_T1_Overflow();
}
// ****** Funktionen ******
void detect_T1_Overflow ()
{
if (flag_OVF1) {
ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
flag_OVF1 = false;
}
Serial.println ("Overflow Timer 1");
}
}
//Timer_1 initialisieren
void init_timer_1 ()
{
cli(); // Interrupts ausschalten
TCCR1A = 0; // Register Reset
TCCR1B = 0;
TIMSK1 = 0;
TIMSK1 |= (1 << TOIE1); // Overflow Interrupt enable
//Timer-Counter-Startwert eine Vorladung zuweisen, damit dieser ca. 4 Sekunden hochzaehlt
//kein Wert zugewiesen heisst,dass es bis zum Overflow ca. 4,19s dauert ( bei 16 MHz und Prescaler 1024 (Overflow bei 65536))
TCNT1 = 3036;
TCCR1B |= (1 << CS12) | (1 << CS10); // Prescaler 1024
sei(); // Interrupts einschalten
}
void GO_TO_SLEEP ()
{
Serial.println ("Einschlafen");
Serial.flush(); // wartet bis Sendepuffer leer ist
set_sleep_mode(SLEEP_MODE_IDLE); // sleep-mode auf Idle
sleep_enable(); // Sleep-Funktionen aktivieren
// diverse Peripherie ausschalten
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer2_disable();
power_twi_disable();
TCNT1 = 3036; // TCNT1 für Schlafdauer vorladen
sleep_cpu(); // schlafen legen
// hier gehts nach aufwachen weiter
sleep_disable();
power_all_enable(); // Peripherie wieder aktivieren
Serial.println ("Aufgewacht");
}
//Timer 1 Overflow Interrupt
ISR(TIMER1_OVF_vect)
{
flag_OVF1 = true;
}
Doc_Arduino:
Habe mir erlaubt Änderungen vorzunehmen ...
Super!
Hab's gerade ausprobiert - das funktioniert ja ausgezeichnet.
Bekommt einen Platz im Ordner "Äußerst nützliche Codebeispiele", Unterkategorie "Sleep".
Vielen Dank für die Anregung!
Das es hier um "Vorstellen" und "sleep" dreht, auch noch ein Vorschlag von mir.
was ist das für eine Qualitätskontrolle, Tommy?
Mit der Bitte um Qualitätskontrolle.
Besonderheiten:
Ein nackter ATMega328p. Statt 16 oder 8MHz Quarz ein 32kHz Uhrenquarz.
Fast alle Fuses im Auslieferungszustand, also 1MHz interner Takt, kein Bootloader.
BOD steht auf 1,8V
Alle von Timer0 abhängigen Komfortfunktionen stehen nicht zur Verfügung (millis(), delay usw)
Geweckt wird exakt alle 1 Sekunde.
Läuft jetzt seit ca 4 Monaten an einer einzelnen CR2032 Knopfzelle (allerdings ohne die LED)
Dass hier die CombieLib zum toggeln der LED verwendet wird ist irrelevant. Geht auch alles andere.
/**
*
* http://www.netzmafia.de/skripten/hardware/Arduino/Sleep/index.html
*
*/
#include <util/atomic.h>
#include <avr/sleep.h>
#include <CombiePin.h>
#define AtomicSection ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
Combie::Pin::OutputPin<LED_BUILTIN> led;
volatile unsigned long uptime = 0; // in sec
unsigned long getUptime()
{
unsigned long temp = 0;
AtomicSection
{
temp = uptime;
}
return temp;
}
void goSleep()
{
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
sleep_bod_disable();
sleep_mode();
/* wake up here */
sleep_disable();
}
int main()
{
led.init();
ASSR = (1<< AS2); // Timer2 asynchron takten
_delay_ms(1000); // Einschwingzeit des 32kHz Quarzes
TCCR2A = 0;
TCNT2=0;
TCCR2B = 5; // Vorteiler 1sec Überlaufperiode
while((ASSR & (1<< TCR2BUB))); // Warte auf das Ende des Zugriffs
TIFR2 &= ~(1<<TOV2); // Interrupts löschen
TIMSK2 |= (1<<TOIE2); // Timer overflow Interrupt freischalten
sei();
for(;;)
{
goSleep();
}
}
ISR(TIMER2_OVF_vect)
{
sleep_disable();
uptime++;
led.toggle();
}
Mir fällt dabei noch die Beschaltung des analogen Sensors auf- die ich allerdings nur indirekt aus dem Sketch ablesen kann.
Demnach scheint der analoge Sensor mit dem Widerstand, der mit dem Widerstand des Sensors einen Spannungsteiler bildet, direkt zwischen Masse und 5V zu hängen. Zum Energiesparen, wäre es aber hilfreich, die Spannung über eine IO bereit zustellen, der erst beim Aufwachen auf HIGH geht. Hier bleibt aber eine etwaige Trägheit des Sensors zu beachten.
Tommy, upps stimmt, ich bin die Woche dran. Sorry.
MaHa, generell rate ich ab einen I/O als Spannungsquelle zu missbrauchen. Nur wenn man genau weiß warum und nicht anders. Für das Bsp. ist die äußere Beschaltung in meinen Augen zweitrangig. Vielleicht ergänzt David was bei ihm angeschlossen ist.
Ich hab den Analog Sensor nur im Beispiel verwendet, um es so allgemein wie möglich zu halten.
In meiner Anwendung schalte ich erst nach dem aufwachen den hochohmigen Spannungsteiler (aus Widerstand und Photo-Widerstand) mittels N-Chanel MOSFET ein und lese dann aus.
Wobei das ein und ausschalten fast schon überzogen ist für meine Anwendung.