PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Teensy 3.2 +++ Interrupt, Übergabeparameter



frabe
21.11.2019, 14:54
Hallo Zusammen.
Leider finde ich keine praktikable Lösung für die Übergabe vom Pin-Interrupt zum loop().

Aufgabe:
Im Loop gibt es verschiedene Prg-Abschnitte und eine Taste zur Auslösung von irgendwas.
Dieses Taste löst je nach Programmabschnitt eine Funktion aus.
Bsp:
Prg-Abschnitt 1 läuft ca. 1Sek, wird hierbei die Taste gedrückt leuchtet eine grüne LED.
Danach Prg-Abschnitt 2 mit gleicher Taste und roter LED, u.s.w.

Ich finde keine Verbindung vom Pin-Interrupt zu den jeweiligen Prg-Abschnitten.

Interrupt ist klar, Entprellung ist klar, Prog-Abschnitte sind klar!
Vermutlich werde ich mit einer volatile-Variable arbeiten. Sollte der ISR oder der jeweilige Prog-Abschnitt die Var verändern?

HaWe
21.11.2019, 15:17
Variablen innerhalb der loop behalten nicht ihre Werte von einer zu den nächsten loop-Durchläufen.
Wenn auch ISRs drauf zugreifen, würde ich mit globalen Variablen arbeiten,
wenn sie nur lokal in der loop gebraucht und verändert werden: mit
volatile static ...

In allen Fällen:
bitte vollständigen Code in code tags posten!

frabe
21.11.2019, 15:30
volatile static ...

volatile Variable nutze ich bereits Funktionsübergreifend.

Im u.s. Versuch steuer ich aus der ISR-Funktion den Summer inkl. Pause.
Sollte man aber nicht innerhalb einer Interrupt-Routine... oder sollte ich das nicht soooo sklavisch sehen?


void ISR_Aufgabe() {
if (ISR_Uebergabe == 1) {
SummerTon();
}
if (ISR_Uebergabe == 2) {
SummerTon();
Pausen_ms(100);
SummerTon();
}
if (ISR_Uebergabe == 3) {
SummerTon();
Pausen_ms(100);
SummerTon();
Pausen_ms(200);
SummerTon();
}
}

Im jeweiligen Prog-Abschnitt wird die Var "ISR_Uebergabe" verändert.

HaWe
21.11.2019, 15:36
volatile alleine nutzt nur etwas, wenn die Variablen nicht weiterverarbeitet oder unbemerkt (z.B. bei multithreading) verändert werden könnten. Es wird nur verhindert, dass sie in Registern verbleiben (edit) oder vom Compiler herausoptimiert bleiben.

ansonsten:
static
oder
volatile static
:!:

wo ist
ISR_Uebergabe
definiert?

frabe
21.11.2019, 15:40
Vor void setup()

volatile int ISR_Uebergabe = 0;

Ohne static funktionierts einwandfrei - Zufall?


void loop() {
digitalWrite(LEDgnPin, 1);
digitalWrite(LEDrtPin, 0);
__TP__(1);
ISR_Uebergabe = 1;
Pausen_ms(3000);

digitalWrite(LEDgnPin, 0);
digitalWrite(LEDrtPin, 1);
__TP__(2);
ISR_Uebergabe = 2;
Pausen_ms(3000);

digitalWrite(LEDgnPin, 1);
digitalWrite(LEDrtPin, 1);
__TP__(3);
ISR_Uebergabe = 3;
Pausen_ms(3000);
}

HaWe
21.11.2019, 15:44
also global?
dann ist es automatisch "static" und "volatile" brauchst du dann überhaupt nicht.

- - - Aktualisiert - - -
edit - Frage hat sich erübrigt
- dann allerdings verstehe ich deinen Code nicht, da muss jemand anderes 'ran :rolleyes:

frabe
21.11.2019, 16:19
Derzeit wird nach Pin-Interrupt, innerhalb der ISR-Funktion "ISR_Taste1()" eine Tastenentprellungs-Funktion "Entprell(Pin)" aufgerufen.
Dauert ca.30ms.
Eine Var. mit 1 für Entprellt oder 0 für Fehlerhaft kommt zurück.
Die "ISR_Uebergabe" wird innerhalb des void loop() verändert



void ISR_Taster1() { // Funktion wirdausggeführt, sobald ISR auslöst
if(Entprell(TasterPin1 == 1) {
if (ISR_Uebergabe == 1) {
SummerTon();
}
if (ISR_Uebergabe == 2) {
SummerTon();
Pausen_ms(100);
SummerTon();
}
if (ISR_Uebergabe == 3) {
SummerTon();
Pausen_ms(100);
SummerTon();
Pausen_ms(100);
SummerTon();
}
}
}

Ich Frage mich, ob ich der ISR-Routine nicht zu viel zu mute.

Rabenauge
21.11.2019, 21:49
Mach das nicht.
Das geht ganz schnell schief.
Nimm dir irgendeine Variable, die innerhalb der ISR geändert wird- nichts weiter.
Alles andere (z.B. die Variable auswerten, dass sie erst ab dem 30. Mal zählt (Entprellung für Arme, einfach, reicht aber oft) mach ausserhalb.

Wenn du sicher gehen wllst, dann mach in der ISR folgendes:

-Interrupts deaktivieren
-tun, was zu tun ist
-Interrupts aktivieren

So stellst du sicher, dass innerhalb der ISR kein Interrupt ausgelöst wird- das nämlich ist das Problem mit ISR's.
Wenn dir sowas passiert, wird die ISR aufgerufen, während sie noch läuft...du kannst dir denken, wie _das_ endet.

HaWe
22.11.2019, 09:26
Nimm dir irgendeine Variable, die innerhalb der ISR geändert wird- nichts weiter.
Alles andere (z.B. die Variable auswerten, dass sie erst ab dem 30. Mal zählt (Entprellung für Arme, einfach, reicht aber oft) mach ausserhalb.
genau, und Variable, die innerhab einer ISR geändert werden, sollten wieder global deklariert werden und volatile sein.

frabe
22.11.2019, 09:37
-Interrupts deaktivieren
-tun, was zu tun ist
-Interrupts aktivieren

DANKE - toller Hinweis!
Genau darüber hatte ich mir auch schon Gedanken gemacht.
noInterrupts(), interrupts() ist die Lösung!

Klebwax
22.11.2019, 12:24
-Interrupts deaktivieren
-tun, was zu tun ist
-Interrupts aktivieren

Vorsicht, das geht in die Hose.

Von vorne mal angefangen. Das Deaktivieren des Interrupts am Anfang ist nicht das erste, was im Interrupthandler passiert. Da laufen noch ein Dutzend oder mehr Assemblerbefehle vorher ab, wie Status und Register retten. Wenn dort der Interrupt nicht automatisch gesperrt wäre, würde der Interrupt sofort wieder zuschlagen, da das Interruptflag noch nicht gelöscht ist. Der Interrupt muß also noch vor dem ersten Assemblerbefehl gesperrt werde, und das geht nur, wenn es automatisch passiert. Und so ist es auch. Beim Eintritt in den Interrupthandler ist mindestens dieser interrupt gesperrt. Den Interrupt im Handler zu deaktivieren ist zwar nicht unbedingt schädlich aber überflüssig und käme auch viel zu spät.

Das Aktivieren ist aber schlecht. Zwischen dem letzten C-Befehl und dem Ende der Funktion wird das, was am Anfang des Handlers gesichert wurde, wiederhergestellt. Das sind nochmal ein Dutzend oder mehr Befehle. Die laufen jetzt mit offenen Interrupts und das ist gefährlich. Erst ganz am Ende, wenn der Prozessor den Interruptkontext verlassen hat, dürfen die Interrupte wieder zugelassen werden. Daher endet ein Interrupthandler auch nicht mit einem Return (from Subroutine) sondern einem Return from Interrupt. Das heißt häufig RETI. Der stellt den Interrupstatus wieder so ein, wie er vor dem Interrupt war.

MfG Klebwax

frabe
22.11.2019, 14:30
Kann sein, aber wie macht man´s besser?

Zwischen
attachInterrupt(Taster01Pin, ISR_Taster1, RISING);
und


ISR_Taster1() {
noInterrupts(); // alle Interrupts ausschalten
Variable = Entprellung(); // volatile, ~30ms
interrupts(); // alle Interrupts wieder einschalten
}

ist nicht mehr viel Platz.

HaWe
22.11.2019, 16:23
erst mal evt das hier wieder streichen (ich kann es nicht beurteilen, was nun stimmt, Klebwax vs. Rabenauge):
noInterrupts(); // alle Interrupts ausschalten
interrupts(); // alle Interrupts wieder einschalten


ich vermute, 30ms warten ist auch etwas lang für eine ISR, und wenn das so ist: dann eine Statemachine programmieren:
erst state1=0,
beim 1.Tastendruck per Interrupt: state1=1 und timestamp1=millis()
dann millis()-timestamp1 in loop() kontrollieren und sobald 30ms erreicht ist: Tastendruck verarbeiten und state1 wieder auf 0.

Ich mache das bisher ganz ohne Interrupts in einer C++ Klasse, auch zum Erkennen und Unterscheiden von kurzem Click, langem Press und Doppelklick.
Aber ich weiß ntl nicht, ob dich meine Lösung interessiert, sonst zeige ich sie dir gerne...

frabe
22.11.2019, 16:24
Ich mache das bisher ganz ohne Interrupts in einer C++ Klasse, auch zum Erkennen und Unterscheiden von kurzem Click, langem Press und Doppelklick.
Aber ich weiß ntl nicht, ob dich meine Lösung interessiert, sonst zeige ich sie dir gerne...

Natürlich interessiert mich das - kann ja durch andere (erfahrene) Gedanken nicht blöder werden ;-)
Ich finde die ISR-Auslösungen schon super - mit einer state-Mashine kombiniert - und dann als universelle Standart-Funktion mit der ich mich nie wieder beschäftigen muss... !


ich vermute, 30ms warten ist auch etwas lang für eine ISR, und wenn das so ist: dann eine Statemachine programmieren:

Daher war mein Ursprungsgedanke, dass der Interrupt ein Sprungziel in die loop()-Funktion defeniert - mehr nicht.
Dort wird dann erneut die Tastenabfrage/Entprellung und alles folgende vor genommen.
Geht das überhaupt?

HaWe
22.11.2019, 16:35
ok, hier ist die Klasse:



// Button Press Object
// (C) 2018 by dsyleixa

// This example code is in the public domain for private use.
// Use for professional or business purpose only by personal written permission
// by the author.

// history
// 0.0.8 new level 31 & debounce premature 2nd click
// 0.0.7 PCF + MCP pin-read (outline)
// 0.0.6 adjustable long press
// 0.0.5 adjustable dbl click speed, block press limit
// 0.0.4 constant long press intervall
// 0.0.3 long press= 6*short press

// ButtonClass.h
// ver 0.0.8


#ifndef __BTNCLASS__
#define __BTNCLASS__

#include <Arduino.h>

#define PCF_INPUT 8574
#define PCF_INPUT_PULLUP -8574
#define MCP_INPUT 23017
#define MCP_INPUT_PULLUP -23017
#define ANALOG_HIGH 900
#define ANALOG_LOW 100
#define ANALOG_50 50
#define ANALOG_100 100
#define ANALOG_200 200
#define ANALOG_300 300
#define ANALOG_400 400
#define ANALOG_500 500
#define ANALOG_600 600
#define ANALOG_700 700
#define ANALOG_800 800
#define ANALOG_900 900
#define ANALOG_1000 1000



class tButton {

//----------------------------------------------------------
protected:

int16_t pin;
int32_t mode;
uint32_t aktMillis, aktMillis2;
int8_t level,
dnstate, dnstate2, upstate,
btnstate, oldbtnstate;
uint32_t MINPRESSms, DBLCLICKms, LONGPRESSms, BLOCKPRESSms, DEBOUNCEms;


//-------------------------------------
int8_t dtimer() {
if (millis()-aktMillis >= LONGPRESSms) { // long press 400ms
return 3;
}
else
if (millis()-aktMillis >= MINPRESSms) {
return -1;
}
else
return 0;
}


//-------------------------------------
int8_t dtimer2() {
if (millis()-aktMillis2 >= BLOCKPRESSms) { // block press limit
return 2;
}
if (millis()-aktMillis2 >= DBLCLICKms) { // double click limit
return 1;
}
//else
return 0;
}


//-------------------------------------
int8_t readButton(int16_t _pin, int32_t _mode) {
if(_mode==INPUT) return digitalRead(_pin);
else
if(_mode==INPUT_PULLUP) return !digitalRead(_pin);
else
if(_mode==ANALOG_HIGH) return analogRead(_pin)>900;
else
if(_mode==ANALOG_LOW) return analogRead(_pin)<100;
else
if(_mode==ANALOG_50) return inRange(analogRead(_pin),50);
else
if(_mode==ANALOG_100) return inRange(analogRead(_pin),100);
else
if(_mode==ANALOG_200) return inRange(analogRead(_pin),200);
else
if(_mode==ANALOG_400) return inRange(analogRead(_pin),400);
else
if(_mode==ANALOG_800) return inRange(analogRead(_pin),800);



/*
else
if(_mode==PCF_INPUT) return !pcfRead(_pin);
else
if(_mode==PCF_INPUT_PULLUP) return !pcfRead(_pin);
else
if(_mode==MCP_INPUT) return !mcpRead(_pin);
else
if(_mode==MCP_INPUT_PULLUP) return !mcpRead(_pin);
*/

}


//----------------------------------------------------------
public:

tButton () :
pin(0xFF), aktMillis(0), aktMillis2(0),
MINPRESSms(40), // min duration for either button press
DBLCLICKms(150), // max priod between double click actions
LONGPRESSms(300), // min duration for long press
BLOCKPRESSms(200), // min wait duration after completed actions
DEBOUNCEms(20), // debounce time after btn press (idle, ignore)
mode(INPUT_PULLUP),
level(0), dnstate(0), dnstate2(0), upstate(0),
oldbtnstate(0), btnstate(0)
{ }

~tButton () { }

//-------------------------------------
void init(int16_t _pin, int32_t _mode, uint32_t _minpressms=40) {
pin = _pin;
mode = _mode;
MINPRESSms = _minpressms;

if(mode==INPUT || mode==INPUT_PULLUP) // Button at dig GPIO
{
pinMode(pin, mode);
}
else
if(mode==PCF_INPUT || mode==PCF_INPUT_PULLUP) // Button at PCF8574
{
// dummy
}
else
if(mode==MCP_INPUT || mode==MCP_INPUT_PULLUP) // Button at MCP23017
{
// dummy
}

}


//-------------------------------------
bool inRange(int val, int ref, int range=10 ) {
return( (val>ref-range)&&(val<ref+range) );
}


//-------------------------------------
void setclickdurations( uint32_t _minpressms,
uint32_t _dblclickms,
uint32_t _longpressms,
uint32_t _blockpressms )
{
MINPRESSms = _minpressms;
DBLCLICKms = _dblclickms;
LONGPRESSms = _longpressms;
BLOCKPRESSms = _blockpressms;
}


//-------------------------------------
int8_t click() { // returns 1(single), 2(double), 3(long), or 0(no press)

btnstate=readButton(pin, mode);

if(level==0) {
dnstate=0;
if(!oldbtnstate && btnstate) { // 1st new btn down:
aktMillis=millis(); // restart btn down timer
level=1;
return 0;
}
}

if(level==1) { // either btn stroke happened
//Serial.println("level1"); Serial.println(dnstate);

if(millis()-aktMillis <= DEBOUNCEms) { // debounce
return 0;
}

dnstate= dtimer(); // -1=short, 3=long pess

if(!btnstate){ // 1st btn up
if(dnstate){ // -1=short, 3=long pess

aktMillis2=millis();
if(dnstate==3) { // long press: finished !!
btnstate=0;
oldbtnstate=0;
dnstate=0;
aktMillis=millis();
aktMillis2=millis();
upstate=0;
level=4; // extra wait after long press
return 3;
}
else level=2; // short press: next level
}
}
}

if(level==2) { // short press happened
//Serial.println("level2"); Serial.println(dnstate);

upstate=dtimer2(); // check btn up pause

btnstate=readButton(pin, mode);

//Serial.print("upstate="); Serial.println(upstate);

if(btnstate) { // second btn click during pause: double click!
dnstate2=1;
//Serial.print(" dnstate2="); Serial.println(dnstate2);
}


if(upstate==0 && dnstate2) { // if double click: next level
level=3;
}
else
if(upstate>=1) { // dbl click time passed:
//Serial.println(millis()-aktMillis2); // single press finished !!
dnstate=0;
dnstate2=0;
//Serial.println(dnstate);
btnstate=0;
oldbtnstate=0;
level=4;
aktMillis=millis();
aktMillis2=millis();
upstate=0;
level=4; // extra wait after single press
return 1;
}
}

if(level==3) { // double click
if (btnstate) {
btnstate=readButton(pin, mode);
if (btnstate) level=31; // non-blocking while still pressed
return 0;
}
if (!btnstate) {
//Serial.println("level3");
dnstate=0; // double click finished !!
dnstate2=0;
upstate=0;
//Serial.println(dnstate);
oldbtnstate=0;
aktMillis=millis();
aktMillis2=millis();
level=4; // extra wait after double click
return 2;
}
}

if(level==4) { // BlockPress wait routine
upstate=dtimer2();
aktMillis=millis();
if(upstate>=2) {
level=0;
dnstate=0;
upstate=0;
aktMillis=millis();
aktMillis2=millis();
//Serial.println("level4 extra wait finished");
}
}


if(level==31) { // double click, still pressed
btnstate=readButton(pin, mode);
if (!btnstate) {
level=3;
return 0;
}
oldbtnstate=btnstate;
return 0;
}

oldbtnstate=btnstate;
return 0;
};

//-------------------------------------
int8_t state() { // returns 1(single), 2(double), 3(long), or 0(no press)
return click(); // alias
}

};

#endif




hier ist ein example:



// Button Press Lib Testcode
// (C) 2018 by dsyleixa

// This example code is in the public domain for private use.
// Use for professional or business purpose only by personal written permission
// by the author.




#include <ButtonClass.h>


tButton btn1;
tButton btn2;


void setup() {
Serial.begin(115200);
delay(2000);
pinMode(13, OUTPUT); // debug

Serial.println("Serial started\n");
btn1.init(4, INPUT_PULLUP); // <<< adjust
btn2.init(5, INPUT_PULLUP); // <<< adjust
}

//------------------------------------------------------------------------

void loop() {
int8_t btn;

btn=btn1.click();
if(btn) {
Serial.print("btn1.click()=");
Serial.println(btn);
}
btn=btn2.click();
if(btn) {
Serial.print("btn2.click()=");
Serial.println(btn);
}
// delay(10); // debug
}




den Teil für Analog-, MCP23017- und PCF8574-Buttonpads kannst du streichen, dazu kommen wir später ;)

- - - Aktualisiert - - -

ach ja,
und hier eine Beschreibung:



# Arduino


ButtonClass tButton

Instances: e.g.


tButton mybtn;



// and perhaps more:


tButton mybtn1;


tButton mybtn2;


// etc


Init:

mybtn.init(pin_number, INPUT/INPUT_PULLUP)
- or -
mybtn.init(pin_number, INPUT/INPUT_PULLUP, min_press_ms)
(for INPUT_PULLUP mode, the press action is inverted automatically)
(min_press_ms default: 60ms)



to retrieve/refresh button click states:
call repeatedly


mybtn.click()
alias: mybtn.state()


it returns a int8_t value 0, 1, 2, or 3:


0: not pressed

1: 1 short click (default minimum duration = 60ms)

2: double click = 2 quick short clicks (default: within <= 150ms)

3: a long press (minimum duration= 300ms)

frabe
22.11.2019, 16:47
WOUW!

Eigentlich wollte nur ich eine einfache Tasten/Kontakt-Entprellung von 20-30ms (ohne dalay) nach einer ISR-Auslösung programmieren.
Deine Lösung scheint mir sehr viel umfangreicher und somit aufwendiger. Auch aufgrund der 6 Status-Abfragen. DIE EierLegendeWollMilchSau ;-)

HaWe
22.11.2019, 17:10
ja, ich sagte ja: anders - und nicht unbedingt das, was DU suchtest.

Mxt
23.11.2019, 09:31
Wenn es nur um ein einfaches Beispiel zur Tastenentprellung geht, ist das beim Teensy schon dabei, wenn man die mitgelieferten Libraries mit installiert hat.

Die Bounce2 Library
https://github.com/thomasfredericks/Bounce2
macht so was, auf drei verschiedene Arten. Bounce2 und Vorgänger Bounce sind in Teensyduino enthalten, den Quelltext kann man sich ansehen, der ist nicht groß und arbeitet ohne Interrupts.

30 ms in einer ISR sind für einen so schnellen Controller viel zu lang, das Sperren der Interrupts bringt natürlich alles durcheinander (USB, Serial, millis) usw.

Klebwax
23.11.2019, 19:13
Die Bounce2 Library
https://github.com/thomasfredericks/Bounce2
macht so was, auf drei verschiedene Arten. Bounce2 und Vorgänger Bounce sind in Teensyduino enthalten, den Quelltext kann man sich ansehen, der ist nicht groß und arbeitet ohne Interrupts.

30 ms in einer ISR sind für einen so schnellen Controller viel zu lang, das Sperren der Interrupts bringt natürlich alles durcheinander (USB, Serial, millis) usw.

Die von dir gezeigte Library und der Code von HaWe haben ein gemeinsames Problem: sie erwarten, daß loop() nicht länger als einige Millisekunden läuft. Dauert da eine Berechnung mal etwas länger, können kurze und lange Tastendrücke nicht mehr unterschieden werden oder gehen ganz verloren. Für ne Demo mag das passen, in einer nutzbaren Anwendung ist das aber unbrauchbar.



void loop() {
debouncer.update(); // Update the Bounce instance
if ( debouncer.fell() ) { // Call code if button transitions from HIGH to LOW
ledState = !ledState; // Toggle LED state
digitalWrite(LED_PIN,ledState); // Apply new LED state
}
}

In jedem loop() Durchlauf wird hier debouncer.update() aufgerufen, ist der Abstand großer als die Zeit, die die Taste gedrückt wird, wird sie nicht erkannt.

Normalerweise erledigt man das in einem Timerinterrupt, der so im einstelligen Millisekundenbereich kommt. Da können alle Tasten bzw. Eingänge entprellt werden und auch Drehencoder ausgewertet werden. In diesem wird dann z.B. der gezeigte oder ein ähnlicher Code abgearbeitet. Die Ergebnisse können dann aus loop() abgefragt werden, wenn der Code dort bereit ist, sie zu verarbeiten.

So ein Interrupt existiert beim Arduino, der kümmert sich da um die Millis (und lastet die CPU dabei sicher nicht aus). Leider habe ich bisher noch nichts gesehen, wie man eigenen Code in den vorhandenen Interrupt einbinden kann. Das einzige, was ich mal gesehen habe, war einen zweiten Timerinterrupt aufzusetzen, der dann parallel zum Millis-Interrupt läuft. Das ist natürlich suboptimal und für mich eine der großen Schwächen des Arduino.

Inka hat für seine Stepper am Anfang die AccelStepper Library eingesetzt. da heiß es in der Doku:


Poll the motor and step it if a step is due, implementing accelerations and decelerations to acheive the target position. You must call this as frequently as possible, but at least once per minimum step time interval, preferably in your main loop.

Der Code in loop() muß hier kürzer als die Zeit für eine Schritt sein. Da ist bei Mikroschritten eigentlich gar nichts mehr in loop() möglich. Auch der Aufruf der Stepper-Lib gehört in einen (den) Timerinterrupt.

MfG Klebwax

Mxt
23.11.2019, 20:44
Die von dir gezeigte Library und der Code von HaWe haben ein gemeinsames Problem:

Das ist im Prinzip richtig, aber zumindest im Falle meiner Antwort didaktisch so gewollt. Ich denke der Fragesteller sollte erstmal dazu gebracht werden, seinen Code so hinzukriegen, dass er nicht blockierend wartet. Das geht am besten in der Loop.

Dieser Thread ist ja offensichtlich in einem gewissen Bezug zu dem hier
https://www.roboternetz.de/community/threads/74138-Teensy-3-2-Interrupttimer-via-RTC
Ich bin mir nicht sicher, ob der Fragesteller schon so weit ist, mit der IntervalTimer Library zu arbeiten. Er würde sicher auch wieder versuchen, die Timer ISR für 30 ms zu blockiern.

Auf das ganze "Arduino ist ungeeignet" und "habe ich nicht gesehen" gehe ich gar nicht weiter ein. In der Frage geht es um einen Teensy 3.2. Die Doku für die Interval Library ist hier
https://www.pjrc.com/teensy/td_timing_IntervalTimer.html

Ja, es gäbe Alternativen z.B. mit den FTM, PDB oder LPTMR Timern. Nein, den Systick, der millis zählt, würde ich eher nicht erweitern.

HaWe
23.11.2019, 23:39
ich habe für single-thread Programme bisher keine loops, die länger als 20ms gedauert haben (was schon sehr viel ist für schnelle MCUs wie esp8266/32, Arduino Due, SAMD51), und wenn einzelne Funktionen in Einzelfällen in speziellen Programmen viel Zeit brauchen, verwende ich den ESP32 oder den Due mit multithreading, die sind mit dem Teensy vergleichbar - dann lauft die btn lib in einem eigenen thread sehr schnell durch und die zeitintensive Funktion tut es auch ungestört in einem eigenen Thread.
Alles also kein Problem, aber @Klebwax: du kannst sicher gerne deinen eigenen kompilierbaren Code hier posten, er muss nur eben auch für den Teensy funktionieren (edit: und ntl passend für Arduino IDE und API, die für den Teensy verwendet werden).
Für den Teensy andererseits gibt es eine eigene MT lib (falls die Teensy-Entprell-Lib nicht ausreicht): TeensyThread, doch ob das für den OP wirklch ein Zeit-Problem ist, wird er selber erstmal testen und dann mitteilen müssen.
Das einfachste wäre IMO aber als erster Schritt eine einfache Statemachine, die einfach mindestens 30ms wartet.

Mxt
24.11.2019, 08:46
Für den Teensy andererseits gibt es eine eigene MT lib (falls die Teensy-Entprell-Lib nicht ausreicht): TeensyThread, doch ob das für den OP wirklch ein Zeit-Problem ist, wird er selber erstmal testen und dann mitteilen müssen.
Das einfachste wäre IMO aber als erster Schritt eine einfache Statemachine, die einfach mindestens 30ms wartet.

Ja, das sehe ich auch so.

Wichtig ist es noch zu wissen: Auf einem ARM haben Interrupts Prioritäten und können sich teilweise gegenseitig unterbrechen. Nur so ist es auch möglich, die vielen Hardwarekomponenten des Chips quasi parallel zu benutzen. Ein Interrupt sollte also nur wie ein kurzes Augenzwinkern sein, nur die allernötigsten Dinge tun. Niemals aktiv warten und erst recht nicht während des Wartens alle anderen Interrupts sperren. Damit legt man nur Sachen lahm, die man wahrscheinlich als nächstes gebraucht hätte.

Im Prinzip ist es schon richtig, dass die Zeit in der Loop nur das ist "was übrig bleibt". Aber, wie HaWe schon sagte, ist das bei einem schnellen Controller sehr viel. Außerdem ist die Arbeit mit der loop nun mal der "Arduino Way" und die Programme bleiben portabel zu anderen Typen. Außerdem bekommt man bei loop Programmen besser ein Gefühl für die vorhandene Rechenleistung. Wenn man alles in Timer Interrupts packt, sieht man das nicht so gut.

Wenn man später feststellt, dass manche Dinge in der Loop nicht so gut aufgehoben sind, kan man im nächsten Schritt zu einem Timer oder ggf. DMA greifen.

Klebwax
24.11.2019, 11:28
Im Prinzip ist es schon richtig, dass die Zeit in der Loop nur das ist "was übrig bleibt". Aber, wie HaWe schon sagte, ist das bei einem schnellen Controller sehr viel.

Nun, alle Embeded-Programme haben die "Mainloop". Wenn man aber das Timing fürs Tastenentprellen oder die Mikroschritte in die Mainloop zieht, sind brauchbare Systeme kaum möglich. Ich versuche mir gerade vorzustellen, wie ich einen G-Code Interpreter aufbaue, der immer kürzer als ein Mikroschritt des Steppers läuft. Ansonsten würde der Motor schon bei der Ansteuerung Schritte verlieren. Man kann natürlich immer einen schnelleren Prozessor nehmen muß sich aber fragen, warum es andere mit einem langsameren auch schaffen.

Die Arm sind schon mächtig, in einem Hoverboard steuert einer das Timing von 2 BLDC und das Balancieren, aber sicher nicht mit einem Arduino Framework.


Außerdem ist die Arbeit mit der loop nun mal der "Arduino Way" und die Programme bleiben portabel zu anderen Typen. Außerdem bekommt man bei loop Programmen besser ein Gefühl für die vorhandene Rechenleistung. Wenn man alles in Timer Interrupts packt, sieht man das nicht so gut.

Richtig, ein System, programmieren zu lernen und die Komplexität von Interrupten und Dingen wie DMA vor dem Anwender zu kapseln. Zum Üben, wie man etwas macht ohne die CPU mit delays zu blockieren mag das angehen.

Ich versuche meine Systeme so aufzubauen, daß die Mainloop möglichst leer ist. Eigentlich will ich da nur ein sleep() sehen. Der ganze Rest läuft in Interrupt Handlern. Nested Interrupts können einem dabei helfen, es geht häufig aber auch so. Bei alten 386 SBCs konnte man das direkt fühlen. Wenn der im DOS-Prompt stand, wurde die CPU richtig heiß, stand er im Linux-Prompt blieb sie kalt. Da war wohl ein sleep() in der Mainloop.

MfG Klebwax

HaWe
24.11.2019, 12:30
Klebwax,
du bist hier im Arduino Forum, da arbeitet man mit der Arduino IDE und Arduino APIs, aber nichtsdestotrotz mit völlig legalem C++, das man sogar auf Raspberry Pis zum Laufen bekommt. Und dabei sogar zusätzlich MT mit pthread, std::thread oder teensyThread zur Verügung stellt.
Wenn du also hier Lösungen vorschlägst, dann ist es wschl für den OP wenig zweckdienlich, soweit du hier die gesamte Teensy-Arduino-Programmierbasis schlechtredest und dann noch nicht einmal einen konstruktiven compilierbaren Gegenvorschlag als Alternative für das Problem des OPs anbietest:
Das ist eine ziemlich billige Miesmachertour, wie schon öfters.

frabe
24.11.2019, 13:57
Ich DANEK euch Allen für eure tollen Beiträge, Gedanken und Vorschläge.


Klebwax,
Das ist eine ziemlich billige Miesmachertour, wie schon öfters.
Ich denke, dass er nur die Grenzen des Arduino/Teensy-Machbaren aufzeigen wollte. Das ist völlig ok und hat mir nur kurz verwirrt ;-)
Was ich bei allen Beiträgen heraus lese ist aber, dass eis keine "kleine", "schnelle" und "universelle" Lösung gibt.

Mit der "Teensy-Entprell-Lib" werde ich mich auseinandersetzen, befürchte aber eine Einschränkung und Teensy-Abhängigkeit. Kann ja sein, dass ich mal einen Arduino programmiere und die Teensy-Lib hier nicht funktioniert.
daher mein Bestreben, möglichst allgemeingültig/universell.

Bei meine Anwendungen wird es nicht auf die [ms] ankommen.
Es wird in Richtung Zeitmessen, MIDI-Steuerung, etc. gehen.
Derzeit steht nichts fest - ausser Grundlagen-Module verstehen und sameln für ein zukünftige Anwendungen.

Pin-Interrups (später Timer-Interrupts (hiermit habe ich mich bei uC erfolgreich beschäftigt)) sind schon ein gewaltiges Werkzeug, auf das ich nicht verzichten werde.

Um einen ISR nicht warten zu lassen, hatte ich die Idee, dass durch den Interrupt auf ein Sprungziel im loop() verwiesen wird. ISR wird nach wenigen verlassen und Entprellung etc. passiert im loop().
[U]Geht das Überhaupt? Wenn JA, wie?

HaWe
24.11.2019, 15:02
1. limitiert das Arduino API überhaupt nicht die Möglichkeiten (das hatten Mxt+ich bereits beschrieben) und
2. geht es am einfachsten mit der Statemachine, wie ich schon erklärt habe, insb. wenn deine loop-Durchläufe nicht ewig dauern (ansonsten gäbe es auch dafür eine Lösung).
Lies doch oben noch mal nach!

Die ISR verweist normalerweise auf kein Sprungziel in der loop() (ist mir auch unklar, wie das überhaupt gehen könnte), aber sie kann eine globale Variable verändern ("Semaphor"), die in der loop regelmäßig abgefragt wird und wo dann je nach Wert auch entsprechend weiter gearbeitet wird (war auch schon erwähnt worden).
(Ich selber arbeite nie mit Pin-Interrupts, sondern mit multithreading falls erforderlich oder aber notfalls auch mit timer-Interrupts, daher kann ich dir bei deinem Intr-Setup nicht weiterhelfen)

Mxt
24.11.2019, 15:48
Mit der "Teensy-Entprell-Lib" werde ich mich auseinandersetzen, befürchte aber eine Einschränkung und Teensy-Abhängigkeit. Kann ja sein, dass ich mal einen Arduino programmiere und die Teensy-Lib hier nicht funktioniert.
daher mein Bestreben, möglichst allgemeingültig/universell.

Nein, das ist keine spezielle Teensy Bibliothek. Die funktioniert auf allen Arduinos.


Ein Pin-Interrupt ist was feines, wenn man damit z.B. auf Daten von einem externen IC reagiert, z.B. AD-Wandler, Netzwerk oder so. Aber bei einem prellenden Taster ist ein Pin-Interrupt doof. Wenn man da die ISR kurz hält, kommt der Interrupt beim Prellen ja immer wieder, da ist man dann wieder beim Versuch des Sperrens.

Wenn Interrupt dann über einen Timer. Da habe ich kein Problem mit dem was Klebwax gesagt hat. Man macht im Prinzip das gleiche, was die Bounce2 Library macht. Nur das die mit millis schaut, wie lange das Signal anliegt. Bei einem Timer, der z.B. alle 0,1 oder 1 ms auslöst, kann man ja einfach zählen.

Falls Du Bücher liest, schau mal nach "Practical UML Statecharts in C/C++". Der Mann stellt da zwar sein eigenes Framework vor, was auch ganz lehrreich ist, vergleicht es aber auch mit diversen gängigen Methoden in C und C++ Statemachines zu bauen. Insoweit eine schöne Übersicht. Im übrigen hat er die gegenteilige Meinung zu Klebwax, was die Notwendigkeit von Timern in professionellen Anwendungen betrifft und verdient auch noch Geld damit. :-)


Zum Entprellen eines Tasters mit einem Timer habe ich jetzt kein Beispiel, aber hier mal ein Extrakt aus einem Sketch für einen Drehgeber (Encoder). Ich habe den Code von diesem Artikel
https://www.mikrocontroller.net/articles/Drehgeber
umgeschrieben auf die Teensy IntervalTimer Lib:


constexpr int led = 13;
constexpr int knob_SW = 2;
constexpr int knob_DT = 5;
constexpr int knob_CLK = 6;

IntervalTimer myTimer;

int encoderPosCount = 0;
volatile int encoderDelta = 0;
int encoderLast = 0;

void encoderISR() {
int encoderNew = 0;

// convert gray to binary
if (digitalReadFast(knob_DT)) {
encoderNew = 3;
}

if (digitalReadFast(knob_CLK)) {
encoderNew ^= 1;
}

int diff = encoderLast - encoderNew;

if (diff & 1) {
encoderLast = encoderNew;
// bit 1 = direction (+/-)
encoderDelta += (diff & 2) - 1;
}
}

int readEncoder() {
noInterrupts();

int val = encoderDelta;
encoderDelta = 0;

interrupts();

return val;
}

int readEncoder2() {
noInterrupts();

int val = encoderDelta;
encoderDelta = val & 1;

interrupts();

return val >> 1;
}

int readEncoder4() {
noInterrupts();

int val = encoderDelta;
encoderDelta = val & 3;

interrupts();

return val >> 2;
}

void setup() {
pinMode(led, OUTPUT);
pinMode(knob_SW, INPUT);
pinMode(knob_DT, INPUT);
pinMode(knob_CLK, INPUT);

int encoderNew = 0;

if (digitalReadFast(knob_DT))
{
encoderNew = 3;
}

if (digitalReadFast(knob_CLK)) {
encoderNew ^= 1;
}

encoderLast = encoderNew;

myTimer.begin(encoderISR, 1000);
}

void loop() {

encoderPosCount += readEncoder2();

// Ausgabe entfernt

delay(100);
}

Moppi
24.11.2019, 17:45
Hallo,

Ich habs in der Vergangenheit so gelöst:








//Arduino-Code
//


void setup(){
pinMode(x,INPUT_PULLUP); //Taster
}


void digitalWait(int Pin) {
int i=8; //Zählervariable, 8 Durchläufe (min. 240ms Wartezeit, in Schleife, nachdem Taster losgelassen wurde)
while(i>0) {
while(!digitalRead(Pin)) {i=8;} //Taster gedrückt, dann setze Zähler i zurück
if (digitalRead(Pin)) { //Taster nicht gedrückt?
delay(30); //dann warte 30ms
i--; //und dekrementiere den Zähler i
}
}
}


void loop(){
if (!digitalRead(x)) { //Ist Taster an Input x gedrückt?
Mach_Was_Du_Machen_Sollst();
Mach_Noch_Mehr();
digitalWait(x); //sicherstellen, dass Taster losgelassen wurde
}
}








Vielleicht lässt sich das auf einem Teensy ähnlich machen, wenn das so ausreichend wäre.




MfG