Go Down

Topic: [SOLVED] timerone - nur einmal ausfürhen (Read 5129 times) previous topic - next topic

eatis

Jun 22, 2013, 04:13 pm Last Edit: Jun 22, 2013, 08:27 pm by eatis Reason: 1
hallo =)

ich habe ein sehr einfaches problem. mittels eines interrupts (timerone) möchte ich, dass nachdem eine taste gedrückt wird eine led angeht und nach 1 sekunde wieder aus. während der zeit soll aber das hauptprogramm weiterlaufen (daher mit interrupt). ich habe schon das ganze netz durchforstet und finde einfach keine anregung, was falsch läuft. vielleicht hat ja jemand von euch einen tip für mich =)

code:

Code: [Select]
#include "TimerOne.h"  
int ledpin=13;

void setup()
{
pinMode(ledpin, OUTPUT);
pinMode(11, INPUT);
Timer1.initialize(1000000);
Timer1.attachInterrupt(turnoff);
digitalWrite(ledpin, 0);
}
 
void turnoff()
{
digitalWrite(ledpin, 0);
Timer1.stop();
}
 
void loop()    
{
if (digitalRead(11))
{
digitalWrite(ledpin,1);
Timer1.restart();
}
}


das einzige, was passiert ist, dass die led angeht, wenn der knopf gedrückt wird und das wars...

vielen dank schonma!

TERWI

Habe aktuell ein (sehr [un]ähnliches) Problem.
Man gab mir diesen Link: http://arduino.cc/en/Tutorial/BlinkWithoutDelay

Du müsstest das, was dort in der Loop steht, in eine Funktion packen und diese mit der gewünschten Verzögerungszeit aufrufen.
In der Funktion müsstest du dann ggf. prüfen, ob irgendwas programmrelevantes passiert und reagieren.

Allerdings hilft hier die Schleife aus dem Interrupt für den Timer wenig.
Es sollte eher anders herum kommen: Der Taster ruft den Interruppt, welcher eine globale variable setzt, auf den die Timer-Schleife ragieren kann.
To young to die - never to old for rock'n roll

eatis

das mit dem blink without delay funktioniert für mich leider nicht, weil der arduino soll, während der timer zählt, noch andere sachen machen. also das ist teil eines größeren projektes, ich habe es bloß erstma in das kleinstmögliche problem verpackt um das mit dem interrupt zum laufen zu kriegen, bevor ich das große programm zermatsche. und mit timer1.restart() habe ich ja vor den interrupt zu aktivieren. oder habe ich dich falsch verstanden?

uwefed

Das verstehe ich nicht wieso millis() nicht funktionieren soll (blink without delay - Beispiel).
Grüße Uwe

TERWI

Es sollte eher anders herum kommen: Der Taster ruft den Interruppt, welcher eine globale variable setzt, auf den die Timer-Schleife ragieren kann.
To young to die - never to old for rock'n roll

nussecke

#5
Jun 22, 2013, 06:21 pm Last Edit: Jun 22, 2013, 06:38 pm by nussecke Reason: 1
Code: [Select]

int ledpin=13;
unsigned long last_led_time;
unsigned long now;
bool led_on = 0;

void setup()
{
pinMode(ledpin, OUTPUT);
pinMode(11, INPUT);
digitalWrite(ledpin, LOW);
}
 

void loop()    
{
now = micros();
if (digitalRead(11))
{
last_led_time = now;
digitalWrite(ledpin,HIGH);
led_on = 1
}
if (led_on && (now - last_led_time>1000000))
{
  digitalWrite(ledpin,LOW);
  led_on = 0;
}
RESTLICHE_AKTIONEN();
}


Also so wäre nicht ok? Die paar Mikrosekunden, die  du etwa durch die zusätzliche If Abfrage pro Loop verlierst sind dir zuviel?

EDIT:
Noch ne Frage, werden in deinem endgültigen Programm noch weitere Anschlüsse des Arduino belegt, wenn ja mit was? Dann könnte zum Beispiel ein Problem mit dem Timer entstehen, sofern du beispielsweise die Servolibrary nutzen wirst/willst.
Solange diese Tasterabfrage bzw das ausschalten der LED nicht stark zeitkritisch ist (Mikrosekundenbereich) würde ich das so lösen.

jurs


das mit dem blink without delay funktioniert für mich leider nicht, weil der arduino soll, während der timer zählt, noch andere sachen machen. also das ist teil eines größeren projektes, ich habe es bloß erstma in das kleinstmögliche problem verpackt um das mit dem interrupt zum laufen zu kriegen, bevor ich das große programm zermatsche. und mit timer1.restart() habe ich ja vor den interrupt zu aktivieren. oder habe ich dich falsch verstanden?


Falls Du keine Mikrosekundengenauigkeit benötigst sondern auch Millisekundengenauigkeit ausreicht, kannst Du wesentlich problemloser die MsTimer2 Library verwenden.

Das Beispiel für die MsTimer2 Library und einen Button ohne PullDown-Widerstand, stattdessen mit INPUT_PULLUP initialisiert und und in der Loop auf LOW getestet, läuft so (angelehnt an Deinen nicht funktionierenden Code):
Code: [Select]

#include <MsTimer2.h>
#define LEDPIN 13
#define BUTTONPIN 11

void turnoff() {
    digitalWrite(LEDPIN,LOW);
    MsTimer2::stop();
}

void setup() {
  pinMode(BUTTONPIN,INPUT_PULLUP);
  pinMode(LEDPIN,OUTPUT);
  digitalWrite(LEDPIN, LOW);
  MsTimer2::set(1000, turnoff);
}

void loop()   
{
  if (digitalRead(BUTTONPIN)==LOW)
  {
    digitalWrite(LEDPIN,HIGH);
    MsTimer2::start();
  }
}


Aber eben nur auf eine Millisekunde genau.

Grundsätzlich ist Dein Programmdesign aber vollkommen verkorkst, wenn Du so langsame Aktionen mit Timern und Interrupts lösen musst.

Ein anständiges Programmdesign
- kommt ohne jegliche Verwendung der "delay" Funktion aus
- und hält die loop-Funktion auf "Drehzahl", so dass diese tausende male pro Sekunde durchläuft.

Damit kannst Du hunderte, wenn nicht gar tausende Dinge gleichzeitig und unabhängig voneinander laufen lassen, ohne dass auch nur ein einziger Interrupt benötigt wird.

eatis

okay, vielen dank für die anregungen, das bringt mich schon ein ganzes stückchen weiter. ich verstehe zwar immer noch nicht, warum das mit dem timerone nicht funktioniert, aber ich habe ja jetzt auch genug alternativen =)

nussecke

So hab mich eben nochmal kurz hingesucht und nach dem Fehler gesucht:
Problem war, wenn du Timer1.stop setzt und nicht direkt wieder neustartest ist das System abgeschmiert.
Und das zweite Problem war, dass der Timer nach dem Restart dummerweise seine Interruptfunktion verloren hat.

Der Code müsste jetzt laufen:
Code: [Select]
#include "TimerOne.h" 
int led_pin=13;
int button_pin=11;
int button_pressed = 0;

void setup()
{
pinMode(led_pin, OUTPUT);
pinMode(button_pin, INPUT);
Timer1.initialize(1000000);           
digitalWrite(led_pin, LOW);
}
 
void turnoff()
{
if (button_pressed != 1)
{
digitalWrite(led_pin, LOW);
}
}
 
void loop()   
{
if (digitalRead(button_pin)==HIGH)
{
button_pressed = 1;
digitalWrite(led_pin, HIGH);
}
if (button_pressed == 1 && digitalRead(button_pin)==LOW)
{
Timer1.start();
Timer1.attachInterrupt(turnoff);
button_pressed = 0;
}
}


Viel Spaß damit, aber wie gesagt, wenns nicht unbedingt notwendig/zeitkritisch ist, würde ich auf Timer und Interrupts verzichten, sofern sichs noch anders lösen lässt. Beispiele dafür haben wir dir ja einige aufgezeigt.


supersonix

Ich klinke mich mal hier mit ein, sorry  :smiley-roll-blue:

@ jurs

Mein Display zeigt einen Start Text --> ich drücke eine Taste --> ein anderer Text soll erscheinen --> dieser soll nach einer bestimmten Zeit verschwinden und der Start Text wieder erscheinen.

Wo ist mein Denkfehler?

Vielen Dank
Sven

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

LiquidCrystal_I2C lcd(0x27,20,4);

char menu[7][7] = {"Funk 1",
                    "Funk 2",
                    "Funk 3",
                    "Funk 4",
                    "Funk 5",
                    "Funk 6",
                    "Funk 7"};

char normalText[6] = "Hallo";

void resetLCD()
{
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(normalText);
   MsTimer2::stop();
}

void zeigeText(int nr)
{
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(menu[nr]);
}

void setup()
{
   
   lcd.init();
   lcd.backlight();
   lcd.print(normalText);
   MsTimer2::set (2000, resetLCD);
   
   //die Pins, wo deine Knöpfe angeschlossen sind
   pinMode(11,INPUT);
   //Testbetrieb mit einem Knopf an Pin 2
/*  pinMode(3,INPUT);
   pinMode(10,INPUT);
   pinMode(11,INPUT);
   pinMode(12,INPUT);
   pinMode(13,INPUT);
   pinMode(14,INPUT);*/
}

void loop()
{
   
   int x = 0;
   x = digitalRead(11);

   if (x == HIGH)
   {
     zeigeText(0);
     MsTimer2::start();
   }
}

jurs

#11
Jun 23, 2013, 03:51 pm Last Edit: Jun 23, 2013, 04:17 pm by jurs Reason: 1

Wo ist mein Denkfehler?


Dein Denkfehler liegt darin, dass Du davon ausgehst, dass Du innerhalb von Interrupt-Behandlungsroutinen jede Art von Code ausführen darfst. Das ist eine völlig falsche Annahme.

Tatsächlich ist es so: Innerhalb von Interrupt-Behandlungsroutinen darf nur ganz bestimmter Code ausgeführt werden, und zwar nur solcher Code, der für die Ausführung keinerlei Interrupts benötigt und der auch keine laufenden Timer braucht.

Daher kannst Du z.B. innerhalb einer ISR-Routine keine Ausgaben auf Deinem LCD-Display machen, da die entsprechenden Library-Aufrufe die Buchstaben auf das LCD selbst interruptgesteuert oder timergesteuert (weiß ich selbst nicht so genau) raustickern. Und "Interrupt im Interrupt" ausführen geht nicht, genausowenig wie Timing mit millis() und micros(). Also crasht an der Stelle Dein Programm, wenn Du es trotzdem versuchst.

Du bist also innerhalb von Interrupt-Behandlungsroutinen extrem beschränkt, welcher Code überhaupt ausgeführt werden kann und verwendet werden darf.

Also: Innerhalb einer Interrupt-Routine keine Library-Funktionen aufrufen, von denen Du nicht sicher weißt, dass zu deren Ausführung keinerlei Interrupts benötigt werden und die auch nicht Verzögerungen mit delay(), millis() und micros() realisieren wollen!

Wenn Du das, was Du vorhast, tatsächlich mit Interrupt-Programmierung machen möchtest, müssen alle Ausgaben auf dem LCD außerhalb der Interrupt-Routine stattfinden. Du könntest es beispielsweise so machen, dass innerhalb der ISR nur ein Status-Flag zurückgesetzt wird:
Code: [Select]

volatile int textNum=-1;

void resetLCD()
{
 textNum=-1;
}


Das Setzen einer Variablen in der ISR ist "interruptsicher", weil keine Library-Funktion dazu aufgerufen werden muß. Und vor der Variablen-Deklaration siehst Du ein "volatile", das ist Pflicht für Variablen, auf die sowohl aus "normalem Code" als auch von "Interrupt Code" aus zugegriffen werden sollen.

Die Ausgaberoutine, die nur von normalem Code aus aufgerufen werden darf, kann dann abhängig vom übergebenen Parameter die Ausgabe vornehmen:
Code: [Select]

void zeigeText(int nr)
{
  lcd.clear();
  lcd.setCursor(0,0);
  if (textNum>=0)
    lcd.print(menu[nr]);
  else
    lcd.print(normalText);  
}


Und die loop macht folgendes:
1. Wenn eine Taste gedrückt wird, wird die Nummer des anzuzeigenden Textes geändert und der Interrupt-Timer gestartet.
2. Wenn sich die Nummer des anzuzeigenden Textes geändert hat, wird der passende Text angezeigt

Code: [Select]

int letzteTextNum;
void loop()
{
 if (digitalRead(11))  
 {
    textNum=0;
    MsTimer2::start();
 }
 if (textNum!=letzteTextNum)
 {
   zeigeText(textNum);  
   letzteTextNum=textNum;
 }  
}


Solche Anzeigenänderungen macht man normalerweise aber nicht mit Timer-Interrupts.
Viel zu kompliziert und fehlerträchtig.

supersonix

@ jurs

vielen Dank für die ausführliche Erklärung !!

Was würdest du als Alternative vorschlagen?

VG
Sven

nussecke

http://forum.arduino.cc/index.php?topic=173451.msg1288452#msg1288452

Ganz klassisch ähnlich zum BlinkingLight Beispiel

Musst du halt nur für deine Bedürfnisse umbasteln. Aber die Struktur kann ja so bleiben.

jurs


@ jurs

vielen Dank für die ausführliche Erklärung !!

Was würdest du als Alternative vorschlagen?


Interrupt-Code komplett rausschmeißen aus dem Sketch.

Du brauchst eine Anzeigeroutine, die je nach übergebener Nummer einen Text oder auch einen anderen Text ausgibt.

Du brauchst eine Loop, die beim Drücken einen anderen Text setzt und nach Zeitablauf nach dem Drücken wieder den Standardtext.
Und die bei Änderungen des anzuzeigenden Textes jeweils die Funktion zur Textausgabe aufruft.

Die zwei Kernfunktionen also:
Code: [Select]

void zeigeText(int nr)
{
   lcd.clear();
   lcd.setCursor(0,0);
   if (nr>=0)
     lcd.print(menu[nr]);
   else
     lcd.print(normalText); 
}

int textNum,letzteTextNum;
unsigned long keyPressedMillis;
void loop()
{
  if (digitalRead(11))     // Beim Drücken von Button an Pin-11
  {
     textNum=0;           // anzuzeigende textNum setzen
     keyPressedMillis=millis(); // und merken, wann die Taste gedrückt wurde
     
  }
  else if (millis()-keyPressedMillis >1000) textNum=-1; // nach einer Sekunde auf Standardtext setzen
  if (textNum!=letzteTextNum) // falls sich der Text seit dem letzten loop-Durchlauf geändert hat
  {
    zeigeText(textNum); 
    letzteTextNum=textNum;
  } 
}

Go Up