PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Interrupt-Abfrage >>> Routine vereinfachen



frabe
31.07.2019, 13:08
Hallo.
Für ein aktuelles Projekt möchte ich 4 Kontakte; PA2, PA3, PA4, PA5 ständig auf steigende und fallende Flanken abfragen.

Im folgenden Bsp habe ich das mit einer Taste durchgefüht. Gibt es eine einfachere Möglichkeit als alles mal 4 zu schreiben?


ISR (PCINT0_vect) // Interrupt, ABFRAGE, aus Bank0, wird ausgelöst
{
if ((PINA & (1<<PINA2)) != 0) // Taster=1
{
// hier kommt eine Entprellung rein
ResetTaste_on();
}
if ((PINA & (1<<PINA2)) == 0) // Taster=0
{
// hier kommt eine Entprellung rein
ResetTaste_off();
}
}


Als Präprozessor-Makro habe ich die Kontakte folgend vereinfacht. Ich würde diese Vereinfachung irgend in die Kontakt-Abfrage einbauen - aber wie?


#define RESET (PINA, PA2)
//usw...

Ceos
31.07.2019, 13:24
spricht was dagegen eine

"handleInterrupt(uint8_t src)"

methode zu schreiben und für jeden dieser vektoren einen einfachen 3 zeiler zu schreiben um die methode entsprechend aufzurufen?!

alternativ könntest du auch alle interrupts manuell auf die gleiche methode zeigen lassen und über das interrupt register auslesen welcher interrupt ausgelöst hat (das ist die standard methode für ARM controller, 1 interrupt handler für x interrupts)

frabe
31.07.2019, 13:40
"handleInterrupt(uint8_t src)"
methode zu schreiben und für jeden dieser vektoren einen einfachen 3 zeiler zu schreiben um die methode entsprechend aufzurufen?!

Meinst du einen rotierende Abfrage über einen Timer?
Wie genau sollte die aussehen?
Grundsätzlich bin ich froh, mit der Bank-Abfrage zurecht zu kommen. Aber für jede Vereinfachung bin ich natürlich dankbar ;-)



alternativ könntest du auch alle interrupts manuell auf die gleiche methode zeigen lassen und über das interrupt register auslesen welcher interrupt ausgelöst hat (das ist die standard methode für ARM controller, 1 interrupt handler für x interrupts)

Du meinst, wie ich es geschrieben habe, für jeden Kontakt jeweils 2 if-Anweisungen?

Ceos
31.07.2019, 13:44
soweit ich dich verstnaden habe ist der code ein beispiel für einen einzelnen ISR, den du aber für 4 ISRs gerne hättest

du kannst jetzt einfach folgendes machen:




void handleISR(uint8_t src)
{
... anhand von src einfach entscheiden was zu tun ist
}

ISR (vector1)
{
handleISR(1);
}

ISR (vector2)
{
handleISR(2);
}

usw...


oder wenn du dich traust kannst du deine Vektortabelle einfach selber basteln und alle ISR vektoren auf die gleiche methode zeigen lassen und dann entweder aus dem Interrupt Flag REgister lesenw elcher interrupt es nun war oder einfach alle deine Taster wie im Beispiel abfragen egal welcher interrupt ausgelöst hat.

Mein Hintergedanke hier ist die effizienz, wenn alle Vektoren auf die gleiche Methode zeigen sparst du dir eine Runde Stack Push/Pop und den MEthodenaufruf der handleISR(uint8_t src) was bei gaaaanz schnellen applikationen bedeutsam sein kann aber meistens völlig überzogen ist ^^

bei ARM controllern gibt es meistens nicht genug vektoren für alle interrupts und dort werden i.d.R. die vektoren zusammengefast und nur ein handler aufgerufen, in welchem du dann über die interrupt flag register die quelle ausfindig machen musst

frabe
31.07.2019, 14:09
Mein Hintergedanke hier ist die effizienz, wenn alle Vektoren auf die gleiche Methode zeigen sparst du dir eine Runde Stack Push/Pop und den MEthodenaufruf der handleISR(uint8_t src) was bei gaaaanz schnellen applikationen bedeutsam sein kann

Tatsächlich gehts bei mir nicht um "Leben oder Tot" im us-Takt. Bei Geschwindigkeiten denke ich auf ms-Niveau.
Mir geht es vielmehr darum, einen sicheren einfachen Code zu schreiben, den ich nach einer Monats-Pause immer noch verstehe.



bei ARM controllern gibt es meistens nicht genug vektoren für alle interrupts und dort werden i.d.R. die vektoren zusammengefast und nur ein handler aufgerufen, in welchem du dann über die interrupt flag register die quelle ausfindig machen muss

Der ATtiny84 ist bei seiner Interruptvielfalt äußerst überschaubar.

Vielleicht ist die folgenden Routine auch völlig ok und kein bischen zu aufgebläht?


ISR (PCINT0_vect) // Interrupt, ABFRAGE, aus Bank0, wird ausgelöst
{
if ((PINA & (1<<PINA2)) != 0) // Taster "Reset" =1
{
// hier kommt eine Entprellung rein
ResetTaste_on();
}
if ((PINA & (1<<PINA2)) == 0) // Taster "Reset" =0
{
// hier kommt eine Entprellung rein
ResetTaste_off();
}
if ((PINA & (1<PINA3)) != 0)
//usw.
//bis alle 4 Taster/Kontakte nach einem Interrupt auf hi/low abgefragt sind

}

Ceos
31.07.2019, 14:24
Das Problem ist hier aber, dass du beim Auslösen von PC4 keinen PCINT0 bekommst und deshalb das Drücken verpasst!

Darum sage ich ja dass du schon wie üblich alle 4 PCINT ISRs schreiben musst aber statt deine Abfrage 4 mal ähnlich zu kopieren in den ISRs jeweils einfach eine Methode aufrufst (wie im Codebeispiel) in welcher du deine PIN Register, das Interrupt Register oder eine Variable als Indikator dazu benutzt um zu erfahren, welcher Input getriggert worden ist.

die Lösung über Makro geht natürlich auch, einfch den code als reines Makro mit Platzhalter schreiben um die richtige Pin Zuordnung und die richtige Reaktion zu bekommen.

frabe
31.07.2019, 15:49
Das Problem ist hier aber, dass du beim Auslösen von PC4 keinen PCINT0 bekommst und deshalb das Drücken verpasst!

Der ATtiny84 hat nur 2 Bänke; Bank0 mit 8* PAs und Bank1 mit 4* PBs. Zufällig liegen meine 4 Taster/Kontakte in Bank0.



Darum sage ich ja dass du schon wie üblich alle 4 PCINT ISRs schreiben musst aber statt deine Abfrage 4 mal ähnlich zu kopieren in den ISRs jeweils einfach eine Methode aufrufst (wie im Codebeispiel) in welcher du deine PIN Register, das Interrupt Register oder eine Variable als Indikator dazu benutzt um zu erfahren, welcher Input getriggert worden ist.

Kannst du mir bitte zur Verdeutlichung einen Bsp-Code schreiben?



die Lösung über Makro geht natürlich auch, einfch den code als reines Makro mit Platzhalter schreiben um die richtige Pin Zuordnung und die richtige Reaktion zu bekommen.

Kannst du mir bitte zur Verdeutlichung einen Bsp-Code schreiben?

- - - Aktualisiert - - -

Was hälst du von folgender Idee;

1. direkt das ganze Register PINA auslesen und in eine Register-Variable (volatiles) speichern.
2. sobald ein ISR(PCINT0_vect) kommt, mit der Reg-Var vergleichen.
3. das veränderte Bit als PAx auslesen, entprellen und auf 0/1 überprüfen

?

- - - Aktualisiert - - -

DENKFEHLER!
In dem Register sind Ein- und Ausgänge.
Somit könnte ich zwar das ganze Register auf ein mal speichern, aber Bit-Veränderungen nur bei meinen 4 PA-Eingängen durchführen. (?)

- - - Aktualisiert - - -

Was hälst du von folgendem Lösungsansatz?



volatile uint8_t RegisterA = PINA;

ISR (PCINT0_vect) // Interrupt, ABFRAGE, aus Bank0, wird ausgelöst
{
if(RegisterA & (1<<PINA2)) != 0) // auf Zustandveränderung PA2/RESET abfragen
{
switch (Reset_Status()) // "Reset" entprellen // return 0=low, 1=high
{
case 0: RegisterA=PINA; Reset_off(); break; // Reset wird ausgeführt
case 1: RegisterA=PINA; Reset_on(); break; // Reset wird nicht ausgeführt
}
}

// solch eine Abfrage noch für PA3, PA4, PA5 hintereinander gehangen
}

Ceos
31.07.2019, 17:35
oh okay, das sind bank interrupts, das manual was ich hatte war wohl für einen größeren Tiny, da waren das ganze einzelne pin interrupts (war ich auch von den noch größeren atmegas so gewohnt)

sorry

ich weis jetzt nicht genau was hinter Reset_Status steckt, aber solange ein mehrfachen aufrufen kein Probblem ist wäre das eine Lösung

du könntest das Ergebnis in eine Variable packen und dann die Variable in den if's abfragen wenn der mehrfache Aufruf ein Problem ist

frabe
01.08.2019, 09:54
oh okay, das sind bank interrupts, das manual was ich hatte war wohl für einen größeren Tiny, da waren das ganze einzelne pin interrupts (war ich auch von den noch größeren atmegas so gewohnt)

Bis auf die Interrupt-Vielfalt ist der kleine ATtiny84 sehr angenehm zu verarbeiten.



ich weis jetzt nicht genau was hinter Reset_Status steckt, aber solange ein mehrfachen aufrufen kein Probblem ist wäre das eine Lösung

Reset_Status() entprellt (ohne _delay_ms()) einen Kontakt und gibt als steigende Flanke eine 1 und als fallende Flanke eine 0 zurück.
In meinem Bsp wird erst einmal das "alte" Register nach Veränderung ausgewertet und "nur" der veränderte Kontakt entprellt und verarbeitet.
Hoffentlich bin ich hier nicht auf dem Holzweg.



du könntest das Ergebnis in eine Variable packen und dann die Variable in den if's abfragen wenn der mehrfache Aufruf ein Problem ist

Klinkt nach Vereinfachung :-)
Kannst du mir bitte ein Bsp-Code schicken der deine Idee verdeutlicht?

Ceos
01.08.2019, 11:48
volatile uint8_t RegisterA = PINA;

ISR (PCINT0_vect) // Interrupt, ABFRAGE, aus Bank0, wird ausgelöst
{
uint8_t resetVal = Reset_Status();
if(RegisterA & (1<<PINA2)) != 0) // auf Zustandveränderung PA2/RESET abfragen
{
switch (resetVal) // "Reset" entprellen // return 0=low, 1=high
{
case 0: RegisterA=PINA; Reset_off(); break; // Reset wird ausgeführt
case 1: RegisterA=PINA; Reset_on(); break; // Reset wird nicht ausgeführt
}
}
if(RegisterA & (1<<PINA3)) != 0) // auf Zustandveränderung PA3/RESET abfragen
{
switch (resetVal) // "Reset" entprellen // return 0=low, 1=high
{
case 0: RegisterA=PINA; Reset_off(); break; // Reset wird ausgeführt
case 1: RegisterA=PINA; Reset_on(); break; // Reset wird nicht ausgeführt
}
}

// solch eine Abfrage noch für PA4, PA5 hintereinander gehangen
}

frabe
01.08.2019, 13:01
Ahhh - Missverständnis!

Reset_Status() testet nur die "Reset"-Taste an PA2,
PA3 heisst Start_Status(), usw.
Alles eigenständige Funktionen, in dehnen entprellt und der letzte Zustand ausgegeben wird.

Dann scheint mein Weg doch nicht so schlecht.

Nur bei der Auswertung bin ich mir noch nicht sicher.


if(RegisterA & (1<<PINA2)) != 0)

Die Zeile sollte die "alte, gespeicherte" Pinbelegung PA2 (RegisterA, vor dem Interrupt) mit dem "neuen, aktuellen" Zustand PA2 vergleichen um heraus zu finden, ob PA2 den Interrupt ausgelöst hat.
1- Ist eine Veränderung zu erkennen, wird PA2 entprellt, danach Zustand 0/1 abfragen und weiter bearbeiten.
2 -Wenn nicht, wird PA3 usw. abgefragt.

- - - Aktualisiert - - -

Müsste der alt/neu-Vergleich nicht eher folgend aussehen?


volatile uint8_t RegA_alt = PINA;

ISR (PCINT0_vect)
{
uint8_t RegA_neu = PINA;
uint8_t RegA_diff = RegA_alt ^ RegA_neu; // hier sollte in jedem Reg.eintrag eine "1" stehen, wenn sich zum "alten" (Vor-Interrupt-Auslösung) eine Veränderung ergeben hat

If((RegA_diff & (1<<PINA2)) != 1)
{
//wenn Ja, mach was
{

If((RegA_diff & (1<<PINA3)) != 1)
{
//wenn Ja, mach was
}

//usw. mit PA4, PA5

Ceos
01.08.2019, 13:45
entweder machst du deine Variablen noch zusätzlich static oder legst sie global an, sonst wirst du nie einen unterschied sehen weil sie immer neu initialisiert werden

ob das XOR da nicht lustige seiteneffekte auslöst würde ich gern prüfen, komme heute aber definitiv nicht mehr dazu mich da etwas tiefer reinzudenken oder zu antworten

frabe
01.08.2019, 14:32
Seiteneffekte?

Rein logisch müsste der XOR alle Unterschiede mit einer 1 markieren.
Baue mir gerade ein Testprogramm auf.

Moppi
01.08.2019, 15:21
so kann man es auch beschreiben.
Rein logisch müsste der XOR alle Unterschiede mit einer 1 markieren.


0+0 = 0, 0+1 = 1, 1+0 = 1, 1+1 = 0

Holomino
02.08.2019, 06:45
Geht das jetzt nur um Tastenabfragen an den Inputs?
Dann würde ich NICHT den PortChange-Interrupt verwenden, sondern einen Timer im ms-Bereich. Der alleine bewerkstelligt schon die Entprellung.
Der Code gestaltet sich vielleicht mithilfe einer Loop etwas schöner, wobei ich auch gleich das Allgemeingültige vom Anwendungsspezifischen trennen würde:


uint8_t prevPINA;

void InitKeys()
{
prevPINA = PINA;
}

ISR (TIM0_OVF_vect)
{
uint8_t actPINA = PINA; //buffer actual state
uint8_t chPINA = prevPINA ^ actPINA; //get changes with XOR
uint8_t setPINA = chPINA & actPINA; // get new set pins
uint8_t clPINA = chPINA & ~actPINA; // get new cleared pins

prevPINA = actPINA; //save actual PINA in prevPINA for next ISR call

for (uint8_t i = 0; i<8; i++)
{
if ((setPINA >>i) & 0x01)
KeyDown(i);

if ((clPINA >>i) & 0x01)
KeyUp(i);
}
}



InitKeys wird einmalig nach dem Controllerreset aufgerufen, die ISR wäre hier z.B. der TimerOverflow.

Das Applikationsspezifische:


#define KEY_ENTER 0
#define KEY_ESC 1
#define KEY_UP 6
#define KEY_DOWN 7

void KeyDown(uint8_t key)
{
switch (key)
{
case KEY_ENTER:
//TODO
break;

case KEY_ESC:
//TODO
break;


}


}


void KeyUp(uint8_t key)
{
//Das gleiche in grün
}


Ob man nun alle Pins mit Tasten belegt hat oder nur einige, ist dem allgemeinen Teil egal. Das wird erst durch die cases in den Switches entschieden.

(nicht kompiliert, daher ohne Gewähr! Aber mal so als Idee)

Ceos
02.08.2019, 06:55
Als ich die Antwort geschreiben hatte hate ich wohl ein Brett vor dem Kopf, ich dachte du XORst in die gleiche Variable zurück und konnte mir irgendwie keinen Reim drauf machen wie das funktionieren sollte ^^

frabe
02.08.2019, 09:14
Dankefür eure Ausführungen!


Geht das jetzt nur um Tastenabfragen an den Inputs?
Dann würde ich NICHT den PortChange-Interrupt verwenden, sondern einen Timer im ms-Bereich. Der alleine bewerkstelligt schon die Entprellung.

Wo siehst du den Vorteil gegenüber einem PortChange-Interrupt ?
Der Timer-I. würde alle ca.10ms aktiv werden, obwohl Sek, Min oder Std nichts passiert.





uint8_t setPINA = chPINA & actPINA; // get new set pins
uint8_t clPINA = chPINA & ~actPINA; // get new cleared pins

prevPINA = actPINA; //save actual PINA in prevPINA for next ISR call


...warum dieser Aufwand?
In deinem chPINA stehen doch alle Pin-Änderungen. Danach müssten die entsprechenden Bits PA2-PA5 auf 1 ausgewertet werden. Habe ich hier irgend etwas übersehen?
Nach jeder chPINA-Auswertung kommt dann sofort eine erneute Aktualisierung+Speicherung in einer volatile-Variable.


prevPINA = PINA;

Holomino
02.08.2019, 10:38
Wo siehst du den Vorteil gegenüber einem PortChange-Interrupt ?
Der Timer-I. würde alle ca.10ms aktiv werden, obwohl Sek, Min oder Std nichts passiert.

Weil Tasten nun mal prellen.



...warum dieser Aufwand?
In deinem chPINA stehen doch alle Pin-Änderungen. Danach müssten die entsprechenden Bits PA2-PA5 auf 1 ausgewertet werden. Habe ich hier irgend etwas übersehen?

Weil man bei Tasten auf Drücken und Loslassen üblicherweise unterschiedlich reagiert. Das chPINA zeigt beides an. Auch auf PCs findest Du genau diese vom Betriebssystem bereitgestellten Funktionen KeyDown()/KeyUp(). Warum also das Rad neu erfinden?

frabe
02.08.2019, 15:39
Erst einmal vielen Dank für eure Mühe!

Folgenden Code finde ich sehr einfach und übersichtlich.
Bei "volatile uint8_t RegA_akt = PINA" hat der Compiler gemeckert, daher den Umweg über InitKeys().
Tastenentprellung ist noch nicht drin.


volatile uint8_t RegA_akt; // übergeordnete Variable

void InitKeys(void)
{
RegA_akt = PINA; // aktuelles Register A wird gespeichert
}

ISR (PCINT0_vect) // Interrupt, aus Bank0, wird ausgelöst
{
uint8_t Auswahl = 0;
uint8_t RegA_neu = PINA;
uint8_t RegA_diff = RegA_akt ^ RegA_neu; // veränderte Bits werden mit 1 belegt

if((RegA_diff & (1<<PINA4)) != 0) // PA4 wurde verändert
{
if((RegA_neu & (1<<PINA4)) == 0) Auswahl=40; // PA4 ist low
if((RegA_neu & (1<<PINA4)) != 0) Auswahl=41; // PA4 ist high
}

if((RegA_diff & (1<<PINA5)) != 0) // PA5 wurde verändert
{
if((RegA_neu & (1<<PINA5)) == 0) Auswahl=50; // PA5 ist low
if((RegA_neu & (1<<PINA5)) != 0) Auswahl=51; // PA5 ist high
}

switch(Auswahl)
{
case 40: LEDaus(); Auswahl=0; InitKeys(); break; // PA4 low
case 41: LEDein(); Auswahl=0; InitKeys(); break; // PA4 high
case 50: SUMaus(); Auswahl=0; InitKeys(); break; // PA5 low
case 51: SUMein(); Auswahl=0; InitKeys(); break; // PA4 high
}
}


@Holomino, deine Tastenentprellung habe ich noch nicht in Gänze verstanden.

Wie kann man aus einem ISR, einen Wert zurück geben, außer eine erneute volatile-Variable zu verwenden?



Euch ein angenehmes Wochenende :cool:

Holomino
02.08.2019, 16:31
@Holomino, deine Tastenentprellung habe ich noch nicht in Gänze verstanden.


Gesetzt den Fall, Du schließt wirklich Taster an (DAS ist mir in Deiner Anwendung nicht ganz klar, Du schriebt, Du wolltest "erst einmal" Taster anschließen, ansonsten schriebst Du nur von "Kontakten"):

Die ATTinys setzen das Status-Flag des Port-Change beim Eintritt in die entsprechende ISR zurück. Sollte der Eingang danach noch im µs-Bereich "flimmern",
löst das schon während der laufenden ISR den nächsten Interrupt aus - nicht sofort, da beim Betreten einer ISR implizit das "global interrupt enabled"-Flag zurückgesetzt wird. Das gesetzte Statusflag wirkt aber sofort nach Verlassen der ISR (hier wird implizit auch wieder das "global interrupt enabled" gesetzt) und Dein Programm springt direkt wieder in die gleiche ISR. Das merkst Du z.B., wenn Du in Deiner ISR einen Zähler inkrementierst. Der springt dann mal zwei oder mehr Schritte pro Tastendruck, je nachdem, wie lange das Prellen dauert und wie schnell Dein ISR-Code abläuft.

Wenn Du im ms-Bereich über einen Timer einfach nur den Port abfragst, hast Du dieses Problem nicht. Das Abtasten der Pin-Zustände im zeitlichen Raster wirkt wie ein digitaler Tiefpass, der das Prellen oberhalb der halben Abtastfrequenz (siehe Nyquist- oder Shannon-Theorem) herausfiltert.

021aet04
03.08.2019, 15:16
Wenn du mechanische Schaltkontakte nutzt (die Prellen) ist es besser wenn du einen Timer zum Auslesen verwendest.

Ich hätte noch eine Verbesserung des Codes. Du prüfst mit einer If auf "0" und mit einer If auf "1". Bei einer If-Abfrage gibt es aber auch noch "else", das wird immer angesprungen wenn das If-Ergebnis nicht "1" ist.
Ein Beispiel als Pseudocode:

if (PB3 == 1)
{
Led = 1
}

if (PB3 != 1)
{
Led = 0
}

ist das gleiche wie:

if (PB3 == 1)
{
Led = 1
}
else
{
Led = 0
}

das ist auch das gleiche:

if (PB3)
{
Led = 1
}
else
{
Led = 0
}

Das if wird immer ausgeführt wenn das Ergebnis wahr ist und das ist immer der Fall wenn es nicht 0 ist. Wenn du auf "0" abfragen willst musst du aber "if (Variable == 0)" schreiben. Bei der while Schleife ist es das gleiche, deswegen sind auch "while(1)" Schleifen endlos.

MfG Hannes

frabe
06.08.2019, 09:14
löst das schon während der laufenden ISR den nächsten Interrupt aus - nicht sofort, da beim Betreten einer ISR implizit das "global interrupt enabled"-Flag zurückgesetzt wird. Das gesetzte Statusflag wirkt aber sofort nach Verlassen der ISR (hier wird implizit auch wieder das "global interrupt enabled" gesetzt) und Dein Programm springt direkt wieder in die gleiche ISR. Das merkst Du z.B., wenn Du in Deiner ISR einen Zähler inkrementierst. Der springt dann mal zwei oder mehr Schritte pro Tastendruck, je nachdem, wie lange das Prellen dauert und wie schnell Dein ISR-Code abläuft.

Interessanter Aspekt, da ich davon ausging, dass erst nach dem Verlassen der ISR der nächste Interrupt durchgelassen wird. Das "speichern" des Flag innerhalb eines ISR ist mir neu und macht einiges unübersichtlicher...
Daher wollte ich auch innerhalb des ISR die Tastenentpreller (später teils Tasten, Türkontakte, Bewegungsmelder, etc) einsetzen. Nach deiner Beschreibung, aber vollkommen unsinnig.



Wenn Du im ms-Bereich über einen Timer einfach nur den Port abfragst, hast Du dieses Problem nicht. Das Abtasten der Pin-Zustände im zeitlichen Raster wirkt wie ein digitaler Tiefpass, der das Prellen oberhalb der halben Abtastfrequenz (siehe Nyquist- oder Shannon-Theorem) herausfiltert.

Hier habe ich immer noch "Bauchgrummeln", da auch ohne minuten-/stundenlange Kontaktbetätigung, im ms-Interval ISR-Routiniert wird - unnötige Energie und Rechenleistung!?

Wie sieht es mit einem Kompromiss als Königsweg aus?
Bsp:
PortChange-Interrupt löst den Timer-Interrupt aus, der diesen wiederum ablöst und eine Tastenentpreller einleitet. Erst danach DARF der "global interrupt enabled" des PortChange wieder gesetzt werden.

Holomino
06.08.2019, 11:24
Hier habe ich immer noch "Bauchgrummeln", da auch ohne minuten-/stundenlange Kontaktbetätigung, im ms-Interval ISR-Routiniert wird - unnötige Energie und Rechenleistung!?


Löse Dich von dieser Vorstellung. Ohne Sleep-Modi (die eigentlich nur im Batteriebetrieb interessant sind) läuft der Controller in der main-Loop immer in der Runde. Da spart man nix. Die Verluste von Netzteil und Regler bewegen sich üblicherweise weit oberhalb der vom Controller aufgenommenen Leistung.

Der von mir gepostete ISR-Rahmen dürfte incl. Stack-Push/-Pop nicht mehr als 10..20µs bei 8MHz dauern. Im Zyklus von 1..8ms aufgerufen (so schnell muss man erst mal tippen können) und bezogen auf die Gesamtleistung des Controllers reden wir also über 0,125 .. 2% Rechenlast für die Timerroutine.

frabe
07.08.2019, 11:18
Hallo - nochmal.

Die Methoden die ihr mir mit dem Timer-ISR aufgezeigt habt ist toll und funktioniert. Soweit kann ich auch als Anfänger jede Code-Zeile nachvollziehen.
Hier ist es so, das alle Kontakte des Register A kontinuierlich abgefragt werden. Sobald eine Veränderung statt findet, wird dieser Kontakt auf Prellung überprüft.
Zum Entprellen wird einfach eine Variable hoch gezählt. Sollte 30ms keine Veränderung statt finden, ist dieser Kontakt entprellt!
Danach kommt der Befehl "tu was".

Aber ... eigentlich brauche ich eine Kontaktabfrage aus dem main() heraus.
Innerhalb des Prg-ablauf gibt es verschiedene Bereiche. Mal ist es wichtig Kontakt PA4 dauernd zu überwachen, wärend dessen mir Eingang PA5 vollkommen egal ist.
Erst wenn ich PA4 benötige, würde ich auf Veränderung und Entprellung abfragen.


Oder ich sehe vor lauter Bäumen den Wald nicht mehr... :confused:

Holomino
07.08.2019, 12:29
Ich denke mal, es ist kein Problem, den Code so zu erweitern, dass Du auch aus der main() auf die Tasten reagieren kannst. Tatsächlich hatte ich die Änderungen hier schon reingeschrieben, dann allerdings wieder gelöscht - ganz einfach weil es jetzt gänzlich unterschiedliche Konzepte für unterschiedliche Anwendungen gibt (nix für Ungut, aber bei "ich bräuchte" von nem Youngster lohnt es sich manchmal zu hinterfragen, ob er es denn wirklich braucht).

Eine tastengesteuertes Displaymenü z.B. würde ich mit Funktionspointern zu erschlagen suchen. Andere logische Zusammenhänge eher mit einer einfachen StateMachine. Ob das dann allerdings immer in der main() liegt oder ebenfalls in der Timer-ISR, hängt alleine davon ab, wie lange der Ablauf der Aktionen ist. Eigentlich präferiere ich die Timer-ISR, weil sie sich ideal für mehrere "asynchone" Aufgaben eignet (Das ist zwar nicht wirklich asynchron, aber mit Prescalern kann man fabelhaft passende Abfragezyklen bauen und so die Rechenleistung optimal aufteilen). Die main() hebe ich mir dann gerne für niederpriorisierte Aufgaben auf.

Wir müssten jetzt also erst einmal darüber sprechen, was genau Du vor hast.

frabe
07.08.2019, 13:35
Ein Reaktionsspiel; erst wenn Teil A erfolgt ist, läuft Teil B ab - usw.
Zwichendurch werden immer wieder Reaktionszeiten ermittelt, die unterschiedliche Wege vorgeben.
Bsp: Teil A, Reaktion langsam, spring in Teil B.
Teil A, Reaktion schnell, spring in Teil C.

Natürlich eine StateMachine!

Innerhalb der einzelnen Teile (A, B) würde ich die jeweiligen Tastenabfrage über Interrupts erledigen.
Somit kann mir der jeweilige Kontakt nicht mehr "durchrutschen" oder das Programm verlangsamen.
Bsp: in Teil A wird die Taste PA4 und in Teil B wird die Taste PA5 auf Veränderung überwacht.

Innerhalb der jeweiligen Blöche A, B etc. werden bestimmte elektro-mechanischen Kontakte (Tasten, Türkontakte, Bewegungsmelder etc) benötigt - daher Entprellung notwendig!
Wenn das Prinzip einmal läuft, braucht man das Prinzip nur noch kaskadieren und/oder verschachteln.
Zur Zeit baue ich einfach nur eine Versuchsplattform mit 2 Tastern und 2 Ausgängen (LED, Summer) auf.

Der ATtiny84 hat nur 2 Timer-Interrupts. Einen lasse ich im 1ms-Takt als Zeitmesser laufen.
Den habe ich auch als Eingangsabfrage nutzen wollen - ala "EierlegendeWollmilchSau" - bis ich jetzt nur noch "Bäume, aber keinen Wald" sehe...

- - - Aktualisiert - - -

Somit müsste nach meinem Verständnis eine Eingangsabfrage blockweise mit einem Timer-Interrupt erfolgen.
Bsp:
Teil A wird gestartet, PA4 wird dauerhaft überwacht, wären dessen laufen sonstige (niederpriorisierte) Dinge.
Teil A wird beendet, PA4-Abfrage gestoppt.
Teil B startet, PA5 und PB1 überwacht,
bis PB1 nicht mehr gebraucht wird, aber PA5 weiter überwacht wird.
usw.

D.h. die einzelnen Eingangsüberwachungen (PA4, PA5, PB1) werden nach Bedarf ein- und ausgeschaltet.
Königslösung?!

Holomino
07.08.2019, 17:21
Ein schönes Beispiel, klingt sehr einfach, hat es aber in sich. Mal so grob geplant mit 2 LEDs und 2 Tastern.

Statusaufteilung:
- Idle: Warte auf Key1Up, um das Spiel zu starten
- Started: Blinke 3x mit LED1, wobei das Erlöschen des 3. Blinkzyklus den Spielstart signalisiert (das 3. Blinken kann man fieserweise noch mit einem leichten Random verzögern)
- Measure: Messe die Zeit bis zum Key2Down und entscheide anhand der Reaktionszeit zwischen Success oder Fail
- Success: Gebe Erfolgsmeldung LED1/2 blinken 10 mal im Wechseltakt.
- Fail: Nur einmal blinken
Success und Fail wechseln zurück zu Idle.
Obacht bei "Started". Wer hier schon vor dem Startsignal wild auf Key2 herumtippt, wird mit Spielabbruch "bestraft".
Ein Timeoverflow in "Measure" sollte das System nach 10s in den "Idle" zurücksetzen

spinnerte Erweiterungsmöglichkeiten:
- Highscore in EEPROM speichern
- Tendenzen besser/schlechter erfassen (also mit vorhergehendem Ergebnis vergleichen)
- Schwierigeres Startverhalten bei guten Ergebnissen (Startblinken wird schneller oder wilder mit mehr Random)

Was uns in den einzelnen States interessiert:
- KeyUp/KeyDown
- Timerzyklus für statusinternen Zähler
Diese Funktionen würde ich jedem Status optional zur Verfügung stellen, egal, ob sie gebraucht werden oder nicht. Zusätzlich bekommt jeder Status eine Init-Funktion, in der er sich entsprechend (erst einmal ganz abstrakt ausgedrückt) am System anmeldet und seine internen Variablen initialisiert.


Aktoren:
- LEDs
- optionale statische Variablen für vorhergehende Ergebnisse
- optional EEPROM für Highscore
Diese "Aktoren" kommen in separate Module, quasi als "globale Funktionen".

Aber bevor ich jetzt codemäßig anfange loszuwirbeln, erst einmal Dein Kommentar. Weiter geht es also nach der nächsten Maus.

frabe
08.08.2019, 08:32
Moin.
Bin gerade auf dem Sprung - konzentriert komme ich esrt zu Mittag dazu.



Ein schönes Beispiel, klingt sehr einfach, hat es aber in sich. Mal so grob geplant mit 2 LEDs und 2 Tastern.

Mein Testaufbau sieht erst mal genau so aus. Das Spiel wird wesentlich komplexer. Mit Analog-Eingangs-Auswertungen, etc.
Die Struktur steht auch soweit. Nur wollte ich endlich mal mit Interrupts arbeiten - NEULAND für mich - DAS macht es jetzt gerade etwas komplizierter, wird, wenn ich´s begreife und kontrolliere wesentlich schneller, flexibler und modularer!



(das 3. Blinken kann man fieserweise noch mit einem leichten Random verzögern)

...ohhhhh, welch B Ö S E R Gedanke ... ;)

frabe
08.08.2019, 10:33
se
Aber bevor ich jetzt codemäßig anfange loszuwirbeln, erst einmal Dein Kommentar. Weiter geht es also nach der nächsten Maus.

Danke für deine Gedanken und Mühe!

Bis auf deine "spinnerte Erweiterungsmöglichkeiten:" ähneld deine Auflistung meinem Projekt.
ABER, ich brauche keinen fertigen Code als Kopiervorlage. Viel mehr geht es mir ums Verstehen, Funktionen in universelle/autonome Module packen, um diese später in weitere Projekte mit gutem Gewissen einsetzen zu können.

JETZT geht es mir um eine Interrupt-Eingangsabfrage, die ich nach Bedarf ein/ausschalten kann.
Aber nicht generell, sondern jeder Eingang autonom.

Bsp:
Interrupt-Überwachung/Auslösung, inkl. (einfacher) Tastenentprellung
# PA4, PA5 ein
# PA4 aus
# PB2 ein
# PB2 aus
# PA4 ein
# PA4, PA5 aus

Holomino
08.08.2019, 13:28
Hmmm,

wenn ich Dir die Zustandcodierung für "Idle" gebe:


#include <avr/io.h>
#include "Statemanager.h"
#include "LED.h"
#include "Started.h"

uint16_t IdleCounter;

void Idle_KeyDown(uint8_t index)
{
// NOT USED
}

void Idle_KeyUp(uint8_t index)
{
if (index == 1)
Started_Init();
}



void Idle_TimerTick()
{
IdleCounter++;
if (IdleCounter > 2000)
{
IdleCounter = 0;
ToggleLED1();

}
}

void Idle_Init()
{
IdleCounter = 0;
ClearLED1();
ClearLED2();
AttachState(Idle_KeyDown, Idle_KeyUp, Idle_TimerTick);
}


...dazu noch die Beschreibung von "Started"...



#include <avr/io.h>
#include <stdlib.h> //required for rand()
#include "Statemanager.h"
#include "LED.h"
#include "Idle.h"


uint16_t StartedCounter;
uint8_t RandomDelay;
void Started_KeyDown(uint8_t index)
{
//Mogelvorbeugung: Abbruch, wenn in diesem Zustand die Reaktionstaste gedrückt wird
if (index == 2)
Idle_Init();
}

void Started_KeyUp(uint8_t index)
{
//Mogelvorbeugung: Abbruch, wenn in diesem Zustand die Reaktionstaste gedrückt wird
if (index == 2)
Idle_Init();
}

void Started_TimerTick()
{
//LED Startsequenz
switch(StartedCounter)
{
case 0:
case 1000:
case 2000:
SetLED2();
break;

case 300:
case 1300:
ClearLED2();
break;
}

if (StartedCounter >= (2000 + RandomDelay))
{
//MeasureInit();
}
StartedCounter++;
}

void Started_Init()
{
StartedCounter = 0;
ClearLED1();
ClearLED2();
RandomDelay = rand() % 500; //0..500 ticks delay
AttachState(Started_KeyDown, Started_KeyUp, Started_TimerTick);
}


...mit folgenden Infos...
- die Beiden ähneln sich doch sehr im Aufbau, wie man unschwer erkennt.
- Jeder Status captured für sich die Eingangsdaten KeyDown/KeyUp/TimerTick durch die Anmeldung über AttachState.
- Dieses "Capture" läuft über Funktionszeiger in einem Statemanager, den Du von weiter oben auch schon teilweise kennst

(.h)


#ifndef STATEMANAGER_H_
#define STATEMANAGER_H_


// function prototypes
typedef void ( *KeyDownCallback ) ( uint8_t );
typedef void ( *KeyUpCallback ) ( uint8_t );
typedef void ( *TimerTickCallback ) ( );

void AttachState(KeyDownCallback keyDown, KeyUpCallback keyUp, TimerTickCallback timerTick);
void Statemanager_Init();

#endif /* STATEMANAGER_H_ */


(.c)


#include <avr/io.h>
#include <avr/interrupt.h>
#include "Statemanager.h"

KeyDownCallback keyDownHandler;
KeyUpCallback keyUpHandler;
TimerTickCallback timerTickHandler;

uint8_t prevPINA;

void Statemanager_Init()
{
//TODO: Init Timer to 1ms tick

prevPINA = PINA;
}




ISR (TIM0_OVF_vect)
{
//Emit Tick
if (timerTickHandler != 0)
timerTickHandler();


uint8_t actPINA = PINA; //buffer actual state
uint8_t chPINA = prevPINA ^ actPINA; //get changes with XOR
uint8_t setPINA = chPINA & actPINA; // get new set pins
uint8_t clPINA = chPINA & ~actPINA; // get new cleared pins

prevPINA = actPINA; //save actual PINA in prevPINA for next ISR call

for (uint8_t i = 0; i<8; i++)
{
if ((setPINA >>i) & 0x01)
if (keyDownHandler != 0)
keyDownHandler(i); //Emit KeyDown

if ((clPINA >>i) & 0x01)
if (keyUpHandler != 0)
keyUpHandler(i); //Emit KeyUp
}
}


void AttachState(KeyDownCallback keyDown, KeyUpCallback keyUp, TimerTickCallback timerTick)
{
keyDownHandler = keyDown;
keyUpHandler = keyUp;
timerTickHandler = timerTick;
}



...erkennst Du vielleicht, worauf ich hinaus will:

Man braucht die main nur zur Initialisierung,...


#include <avr/io.h>
#include <avr/interrupt.h>
#include "Idle.h"
#include "StateManager.h"

int main(void)
{
//TODO: PORT INIT
Statemanager_Init();
Idle_Init();
sei(); //Enable interrupts

/* Replace with your application code */
while (1)
{
}
}


…, neue States lassen sich über eine Codevorlage als Modul per C&P und anschließendem Ersetzen von "State_" durch den Statusnamen sehr schnell hinzufügen,...


#include <avr/io.h>
#include "Statemanager.h"
#include "LED.h"
void State_KeyDown(uint8_t index)
{
}

void State_KeyUp(uint8_t index)
{
}

void State_TimerTick()
{

}

void State_Init()
{
AttachState(State_KeyDown, State_KeyUp, State_TimerTick);
}

…, danach kümmert man sich nur noch um das Wesentliche (das Verhalten des States in den vorgegebenen Funktionsrahmen zu codieren).

Damit man von einem State zum Nächsten springen kann, braucht man noch jeweils einen Minimalheader:
Beispiel Started.h


#ifndef STARTED_H_
#define STARTED_H_
void Started_Init();
#endif /* STARTED_H_ */

...mickrig!


Ob das der Königsweg ist, vermag ich nicht zu sagen (Gib 100 Programmierern ein Problem und Du bekommst 100 Lösungen). Aber selbst wenn Du den Timer im 1ms-Takt laufen lässt, sind das bei 8MHz 8000 Takte. Der gesamte oben geschriebene Code nimmt als Kompilat aber nur 1kB Flash in Anspruch. Da ist also bei den berühmten 1..4 cycles per instruction der AVRs noch eine ganze Menge Luft nach oben.

Bist Du immer noch der Meinung, Du brauchst eine Übergabe der Keys in die main()?

frabe
08.08.2019, 14:19
...bin gerade etwas überfordert - brauche etwas mehr Zeit...

Moppi
08.08.2019, 14:48
@frabe

Ich habe mal ein wenig quer gelesen, die letzten Einträge hier.

Wenn Du mit Interrupt noch nichts gemacht hast, gehe erst mal in eine Doku zum Tiny und schau mal, wie man einen Interrupt programmiert. Also ein- und ausschaltet und wie man eine Interrupt Service Routine dafür erstellt. Dann machst Du ein kleines Testprogramm, zum probieren. Auf diese Weise lernst Du es verstehen und siehst, welche zeitlichen Reserven in einer Interrupt-Unterbrechung vorhanden sind und wie Du damit arbeiten kannst. Ich denke, das ist für Deine Fragestellung zuträglich. Zuerst verstehen, dann das "Pflichtenheft". Die Anforderungen müssen mit dem zusammenpassen, was umsetzbar ist. Also einfach klein anfangen, normal ergibt sich dann der Rest ziemlich von allein.

MfG

frabe
08.08.2019, 15:16
Wenn Du mit Interrupt noch nichts gemacht hast, gehe erst mal in eine Doku zum Tiny und schau mal, wie man einen Interrupt programmiert.
MfG
Timer-Interrupt und PortChange-Interrupt habe ich in Testprog sicher zu Laufen gebracht - dank Datenbätter und Tuts.
Aktiviert werde die ISR via sei(); - aber Alle zeitgleich - DAS ist derzeit mein Problem...
Ich suche nach einer einfachen(!) sequenziellen Ein-/Ausschaltung bestimmter Pins, die dann durch ISR überwacht werden. Siehe hierzu meinen vorletzten Eintrag.

Holomino
08.08.2019, 15:45
...bin gerade etwas überfordert - brauche etwas mehr Zeit...

Nimm sie Dir!

Der Unterschied zwischen Genie und Normalo ist: Das Genie versteht die Relativitätstheorie sofort, der Normalo braucht dafür etwas Zeit.
...manchmal auch etwas mehr als das Leben hergibt.
:-)

Moppi
09.08.2019, 01:04
Hmmm. Ich hoffe, dass ich Dich richtig verstanden habe.

Das gemeinsame Ein- und Ausschalten aller Interrupts ist normalerweise kein Problem, sondern so vorgesehen.
Um einzelne Interruptquellen zu maskieren, gibt es (normalerweise) ein Interrupt-Maskierungs-Register.
Das scheint sogar bei den ATtiny und dem ATmega für einzelne Pins zu funktionieren.

PCMSK – Pin Change Mask Register

Datenblatt ATtiny24/44/84 (7701G–AVR–02/15)

Seite 47 und 48

Vielleicht ist es das, was Du suchst.

frabe
09.08.2019, 11:10
Verstehe!
Sowohl beim Timer-Interrupt als auch bei PortChange-Interrupt habe ich immer alles Eingeschaltet.
Über Funktionen könnte ich auch Pin-genaue Konfiguration gewährleisten. Hier könnte ich über ausgelagerte Funktionen immer nur einen Bit im Register PCMSK0 ein/ausschalten.

So sah bis lang mein PortChange-Interrupt-Konfig aus;


int main(void)
{
DDRB |= (1<<PB0);
DDRA |= (1<<PA6);
DDRA &= ~((1<<PA4)|(1<<PA5));

//Interrupt-Routine wird defeniert
GIMSK = (1<<PCIE0); // Bank0 wird eingeschaltet (PA0-PA7) // BANK1 beinhaltet PB0-PB3
PCMSK0 = (1<<PCINT4)|(1<<PCINT5); // PCINT4(PA4) und PCINT5(PA5) könnte Interrupt auslösen
sei(); // Interrupt´s werden gestartet

LEDaus();
SUMaus();
StartBlock();

while(1)
{


}
}

frabe
14.08.2019, 12:48
Hallo.
Habe wieder einwenig Zeit zu experimentieren.

Bei meinem derzeitigen Schritt geht es darum, innerhalb der while(1), oder eines ISR bestimmte Pin ein- oder auszuschalten.

Folgender Versuch klappt nicht - PA5 bleibt dauerhaft im Eingangs-Modus.


LEDgn_ein();
DDRA &= ~(1<<PA5); // Eingang!
_delay_ms(1000);
LEDgn_aus();
DDRA |= (1<<PA5); //Ausgang, somit kein Eingang?
_delay_ms(1000);

Aber warum?

Ceos
14.08.2019, 12:57
Wie kommst du darauf dass es nicht funktioniert?! Misst du mit einem Messgerät? Hast du in PORTA denn auch PA5 gesetzt?

Nur weil du den Pin aus Ausgang stellst gibt er nicht automatisch ein High Signal aus, du musst auch das PA5 im PORTA setzen, damit er den Ausgang high setzt.

Das DDR bestimmt nur ob dein Pin aktiv (5V mit PA5 in PORTA oder GND ohne PA5) oder passiv (High-Z oder Pull Up wenn PORTA gesetzt ist) ist

frabe
14.08.2019, 13:35
Wie kommst du darauf dass es nicht funktioniert?!

Soll:
Sobald LED/gn kann PA5 als Eingang, Taster über einen Timer-ISR eine LED/rt schalten.
Solang LED/gn ausgeschaltet ist, kann auf Taster PA5 rumgetippt werden > nichts passiert.
Ist:
Taster an PA5 kann jederzeit steuern. Also dauerhaft als ein Eingang konfiguriert.

Ceos
14.08.2019, 13:52
Sorry das ist etwas zu kurzsilbig, ich versteh der Satz gerade nicht mal

Der ISR und auch das PIN Register funktionieren unabhängig vom DDR Register!

Observe that, if enabled, theinterrupts will trigger even if the INT0 or PCINT[5:0] pins are configured as outputs

du musst den Interrupt also schon aus/blindschalten und dann den Pin über das PORT Register entsprechend setze.

Der Taster an einem auf output geschalteten Pin bedeutet natürlich auch ein Kurzschluss, ist also generell nicht empfehlenswert!

021aet04
14.08.2019, 13:58
Bei solchen Dingen musst du aufpassen. Du kannst einen Kurzschluss erzeugen.

Wie sieht deine Schaltung aus?

Der Programmteil ist in Ordnung und sollte so funktioieren. Es kann sein das du es noch irgendwo anders änderst (ungewollt). Was macht "LEDgn_ein();" bzw "LEDgn_aus();" genau (Led ein/ausschalten ist klar, wie sieht aber die Funktion aus).

Ich würde das aber etwas anders lösen. Du hast einmal das Eingangsbit von PA5 und einmal ein "Merkerbit" mit dem du das einlesen "freigibst". Diese beiden Bits verknüpfst du mit einem "Und".

MfG Hannes

frabe
14.08.2019, 14:13
@ceos
Bei meinem Timer-Interrupt werden innerhalb des ISR die Eingänge PA4, PA5 abgefragt.
Im main() werden für gewöhnlich die DDR einmalig als Ein- oder Ausgänge konfiguriert.
Nun möchte ich aber innerhalb der while(1) jeden einzelnen Eingang nach Bedarf "Stumm" schalten, so dass im ISR, if((PINA & (1<<PINA5)) != 0) //1=tu was, ins "leere" läuft.
Daher meine Idee, den entsprechenden Pin, "mal eben" als Ausgang zu konfigurieren.

@021aet04
5V==High==1
Kurzschluss auf der Ausgangsleitung? Wenn der uC/Out==low==0, uC-intern direkt mit dem Gnd verbunden ist, ist das tatsächlich möglich.

Ceos
14.08.2019, 14:18
wie aus der Doku zitiert, das geht leider nciht, du kannst also nur den
PCMSK0 umkonfigurieren um deine ISR zu blockieren. Zusätzlich solltest du alelrdings in deinen Abfragen auch den Pin ausmaskieren, sonst löst er trotzdem aus, wenn du beide Taster drückst.

Du musst also alles maskieren

frabe
14.08.2019, 14:29
PCMSK0 umkonfigurieren um deine ISR zu blockieren.

PCMSK0 = (1<<PCINT4)|(1<<PCINT5);
konfiguriere ich nur beim PortChange-Interrupt, nicht aber beim Timer-Interrupt.

021aet04
14.08.2019, 15:16
Das 5V ein High ist, ist normal. Die Frage ist, wie hast du den Taster angeschlossen (gegen 5V oder Masse, Verwendest du einen Widerstand)?

Normalerweise gibt es kein Problem, solltest du aber versehentlich den Port schalten und du die Taste dürcken, hast du ein Problem (z.B. Ausgang auf high und Taste auf Masse). Es reicht schon wenn du dich verschreibst.

MfG Hannes

frabe
15.08.2019, 09:33
Du hast natürlich Recht - in diesem Fall kann nichts passieren - man/ich sollte aber "Narrensicher" entwickeln, könnte ja mal in andere Hände gelangen.
Mittlerweile halte ich die Lösung mit der Ein/Ausgangs-Umschaltung für praxisfern.

Vielleicht ist eine andere Variante mit dem "Stummschalten" durch volatile-Variablen sinnvoll.
Bsp:
Im main(), sequneziell PA4 und/oder PA5; Variable "PA4freischalt=1;", "PA5freischalt=1;"
Innerhalb des ISR (Timer) oder dessen Funktionsaufruf; if (PA4freischalt == 1) {} // Eingangsimpuls wird weiter verarbeitet

021aet04
15.08.2019, 10:03
Das ist die Variante die ich gemeint habe. Ich würde aber eine Bytevariable nehmen und einzelne Bits einem Eingang zuordnen. Bei dir ist es "Resourcenverschwendung" weil du für jeden Eingang eine Bytevariable verwendest.

Also z.B. eine Hilfsvariable "char hilfsvariable" => Bit 0 = Freigabe PA4, Bit 1 = Freigabe PA5
Der Code sieht dann so aus

if ((PINA & (1<<PA4)) && (hilfsvariable & (1<<0))){...}

Das gleiche gilt auch für PA5. Die Schleife wird nur ausgeführt wenn PA4 == 1 und hilfsvariable == 1.

MfG Hannes

frabe
15.08.2019, 10:58
Verstehe, das Prinzip ist ok, aber zu viel Speicherplatz verschwendet wenn zB. 10 Eingänge ins Spiel kommen. Hier ist mir (als Anfänger) die transparenz und einfacherere Lesbarkeit wichtig!
Ich hatte nur bedenken wegen der Übersicht bei mehreren volatile-Variablen. Sehe hier aber keinen Ausweg.

- - - Aktualisiert - - -

Ist es eigentlich normal, dass bei Timer-ISR mehrere volatile-Variable eingesetzt werden?
Folgend schon wieder eine, nur um einen Intervall-Zähler zu erhalten;


volatile uint8_t EingIntervall=0;

ISR(TIM1_COMPA_vect)
{
OverflowZaehler++;
EingIntervall++;

if(OverflowZaehler >= 60000) // 1ms*60 000 = 1Min
{
OverflowZaehlerReset();
}

if (EingIntervall==10) // alle 10[ms] werden die Eingangskontakte abgefragt
{
RegA_KontaktAbfrage(); // Eingänge auf Veränderung analysiert
EingIntervall=0;
}
}

021aet04
15.08.2019, 13:14
Meiner Meinung nach ist es nicht besser lesbar. Wie man die Hilfsvariable nennt ist egal, mann kann sie z.B. auch "FreigabeEingaenge" oder sonst wie nennen.
Ob man 1 Variable benötigt, oder 3 wird egal sein. Wenn man aber mehrere hat und so arbeitet bekommt man irgendwann ein Speicherproblem (z.B. wenn man Eingänge eines ganzen Ports steuern will). Deswegen sollte man sorgsam mit den Resourcen umgehen.

Man kann auch mit #define arbeiten.
z.B.


#define PA4_ein hilfsvariable |= (1<<0)
#define PA4_aus hilfsvariable &= ~(1<<0)
#define PA4_freigabe hilfsvariable & (1<<0)


Wenn du es nutzen willst schreibst du:


PA4_ein; // PA4 freigeben
PA4_aus; // PA4 sperren

if ((PINA & (1<<PA4)) && PA4_freigabe) {...} // Abfrage auf PA4 und Freigabe PA4



Bei der ISR kann man es so machen wie du und mehrere Variablen nehmen, oder du konfigurierst die ISR so das du alle 10ms einen Interrupt bekommst (geht nicht immer).
Wenn man z.B. alle 10ms einen Int haben will muss man den Timer so anpassen das du, wenn du den Timer von 0-Max laufen lässt, über die 10ms kommst (Prescaler anpassen). Anschließend musst du einen "Startwert" für den Timer ausrechnen. Den musst du in der ISR jedes mal neu übergeben. Also wenn du z.B. bei einem 8bit Timer jede ISR den Wert 250 in das TCNT Register schreibst zählt er von 250 bis 255 und löst dann wieder einen Int aus.

MfG Hannes

frabe
15.08.2019, 13:50
Bei der ISR kann man es so machen wie du und mehrere Variablen nehmen, oder du konfigurierst die ISR so das du alle 10ms einen Interrupt bekommst (geht nicht immer).

Dieser Timer ist als Generaltaktgeber (1ms) für das gesamte Prog konfiguriert.
Da hängen später auch Zeitstufen/Countdownzähler etc drann.

Die Idee mit #define finde ich gut!
Auch die LED-Steuerungen, LEDgn_ein() etc. könnte ich direkt mit #define bestimmen.
Wo siehst du die Grenze zw. Funktion() und #define ?



#define LEDgn_ein PORTB |= (1<<PB0)
#define LEDgn_aus PORTB &= ~(1<<PB0)

// statt

void LEDgn_ein(void) // LED einschalten
{
PORTB |= (1<<PB0);
}

void LEDgn_aus(void) // LED ausschalten
{
PORTB &= ~(1<<PB0);
}

021aet04
15.08.2019, 14:22
Wenn du eine 1ms ISR benötigst geht es nicht anders als du es machst.

Es hat jeder seinen eigenen Programmierstil. Wichtig ist nur das es lesbar bleibt. Du musst denken das du z.B. in einem Jahr das Programm anschaust und du bzw jemand anderes ohne "Studium" deinen Code ohne Probleme lesen können musst. Deswegen solltest du Variablen, Defines, Funktionen,... so benennen das es logisch ist und du nicht erst suchen muss was das gerade macht.

Defines kannst du immer nehmen wenn du nur eine "Aktion" hast, wie z.B. einen Ausgang setzen. Wenn du mehrere Dinge machen musst und diese öfters ausführst, solltest du Funktionen verwenden.

Wichtig wird es aber wenn du eigene Bibliotheken erstellst. Dann hast du eine Headerdatei (*.h) und eine dazugehörende Codedatei (*.c). In die Headerdatei kommen die ganzen Defines und Funktionsprototypen und in die Codedatei kommen die eigentlichen Funktionen.

MfG Hannes

frabe
20.08.2019, 14:48
Hallo Leute.
Habe an meinem Code weiter gearbeitet, hänge aber gerade an Tasten-/Kontaktentprellung fest.

Bis auf folgende Anweisung mit "i"-Schleife, funktioniert alle einwandfrei - dank Eurer anregungen!
"i" in RegA_Abfrage() sollte die 10ms-Schleifen hoch zählen. Sind 30ms (Entprellzeit) keine Veränderung vorgekommen, werden die veränderten Eingänge ausgewertet und verarbeitet.

Irgendwo scheint sich hier ein Logikfehler zu verstecken, da RegA_Auswertung() nicht mehr angesprochen wird.



ISR(TIM1_COMPA_vect)
{
OverflowZaehler++;
EingIntervall++;

if(OverflowZaehler >= 60000) // 1ms*60 000 = 1Min // uint16_t 0…65.535
{
OverflowZaehlerReset();
}
if (EingIntervall==10) // alle 10[ms] werden die Eingangskontakte abgefragt
{
RegA_Abfrage(); // Eingänge auf Veränderung analysiert
EingIntervall=0;
}
}

void RegA_Abfrage(void)
{
uint8_t RegA_neu;
uint8_t RegA_dif;
uint8_t i;

RegA_neu = PINA; // PINA-Regist. wird ausgelesen
if(((RegA_dif = RegA_akt ^ RegA_neu) !=0) && i>=3) // Änderungen gegenüber Vor-Interrupt=1 UND >=30ms
{
RegA_Auswertung(RegA_dif, RegA_neu); // Pins einzeld auswerten
i=0;
}
i++;
RegA_akt = RegA_neu; // akt. PINA in Vor-Interrupt speichern, für den nächsten ISR
}

void RegA_Auswertung (uint8_t RegA_dif, uint8_t RegA_neu) // Pins einzeld auswerten
{
uint8_t RegA_high;
uint8_t RegA_low;
uint8_t Zuweisung=0;

RegA_high = RegA_dif & RegA_neu; // Pins von 0 auf 1 = 1
RegA_low = RegA_dif & ~RegA_neu; // Pins von 1 auf 0 = 1

if(((RegA_high & (1<<PINA4)) != 0) && (PA4frei==1)) Zuweisung=41; // PA4 wurde high
if(((RegA_low & (1<<PINA4)) != 0) && (PA4frei==1)) Zuweisung=40; // PA4 wurde low
if(((RegA_high & (1<<PINA5)) != 0) && (PA5frei==1)) Zuweisung=51; // PA5 wurde high
if(((RegA_low & (1<<PINA5)) != 0) && (PA5frei==1)) Zuweisung=50; // PA5 wurde low

switch(Zuweisung)
{
case 41: LEDrt_ein; Zuweisung=0; break; // PA4 high
case 40: LEDrt_aus; Zuweisung=0; break; // PA4 low
case 51: SUM_ein; Zuweisung=0; break; // PA5 high
case 50: SUM_aus; Zuweisung=0; break; // PA5 low
}
}

Bemerkung:
- OverflowZaehler ist static und wird mit 1ms, über Timer-Interrupt getaktet/hoch gezählt.
- PA4frei, PA5frei static-Var., kommen aus dem main und schalten die jeweiligen Pin ein/aus.
- RegA_akt, RegA_neu, EingIntervall sind static-Var.

- - - Aktualisiert - - -

Fehler gefunden!
"i" muss eine static-Variable sein!
Klappt jetzt prime - was haltet ihr von der Struktur?

021aet04
20.08.2019, 20:02
Ich finde den Code relativ unübersichtlich.

Ich würde es nicht auf so viele Funktionen aufteilen. Ich würde Funktionen nur verwenden, wenn du es öfter als 1x benötigst oder wenn man diese Funktion auch in anderen Projekten genau so verwenden kann (dann würde ich es aber in eine eigenen Bibliothek geben). Das Problem wenn du es auf so viele Funktionen aufteilst ist, das du immer suchen musst.

Ich würde alle Funktionen direkt in der ISR machen. Was mir auch auffällt ist, das du in der Auswertung zuerst eine Zahl einer Variable zuweißt und anschließend diese Variable abfrägst. Ich würde z.B. "Ledxx_ein", Ledxx_aus,... direkt in die "If"-Abfrage schreiben.

Edit:
Wenn ich deinen Code richtig verstanden habe (Pinabfrage und Weiterverarbeitung) sollte dieser Teil das gleiche machen.

Man sollte, wenn es geht, auf >, >=, < oder <= Abfragen und nicht auf == wie du es bei dem 10ms Takt gemacht hast. Der Grund ist das du theoretisch den Wert auf z.B. 11 stellen kannst und dann wird weitergezählt bis der Zähler überläuft und zählt dann wieder bis 10. Wenn du eine Bytevariable hast zählt er 255ms zu lange.

Ich habe bei dem 10ms Takt "5" genommen, weil bei diesem Programm ca. alle 2ms ein INT ausgelöst wird, bei dir muss "10" stehen.
Mit der Variable "Eingang_Freigabe" kannst du bestimmen welche Eingänge abgefragt werden. Du kannst so darauf zugreifen wie ich es im main gemacht habe (nach dem "sei();").
In der "10ms-Abfrage" wird geprüft ob der Eingang freigegeben ist und ob der Eingang vom vorhergehendem Eingang geändert hat (wird ebenfalls mit der Eingangsfreigabe verknüpft).
Wenn das alles zutrifft wird der "Prellzeitzähler" um 1 erhöht, weicht er ab wird sofort der Zähler auf 0 gesetzt und der Durchlauf wird erneut gestartet. Sollte der Zähler den Maximalwert erreichen (Eingang ist konstant) werden die Aktionen ausgeführt. Bei meinem Beispiel steuert PB0 den Ausgang PB4 und PB2 den Ausgang PB5.

Solltest du das Programm nicht ganz verstehen, einfach melden.


#define F_CPU 8000000UL


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>


volatile unsigned char Prellzeit=0, Eingang_alt=0, Eingang_Freigabe=0, Takt_10ms=0;

#define Ledgn_ein PORTB |= (1<<PB4)
#define Ledgn_aus PORTB &= ~(1<<PB4)
#define Ledrt_ein PORTB |= (1<<PB5)
#define Ledrt_aus PORTB &= ~(1<<PB5)


ISR (TIMER0_OVF_vect)
{
if (Takt_10ms >= 5)
{
if ((PINB & Eingang_Freigabe) & (Eingang_alt & Eingang_Freigabe))
{
Prellzeit++;

if (Prellzeit >= 3)
{
if (Eingang_alt & (1<<PB0)) Ledgn_ein;
else Ledgn_aus;

if (Eingang_alt & (1<<PB2)) Ledrt_ein;
else Ledrt_aus;


}
}
else
{
Prellzeit = 0;
}

Eingang_alt = PINB;
Takt_10ms = 0;
}

Takt_10ms++;
}


int main(void)
{
TCCR0B |= (1<<CS00) | (1<<CS01);
TIMSK |= (1<<TOIE0);

sei();

Eingang_Freigabe = (1<<PB0) | (1<<PB2);

while (1)
{

}
}

MfG Hannes

frabe
23.08.2019, 11:09
Erst ein mal vielen Dank für deine Gedanken und Anregungen!



Ich finde den Code relativ unübersichtlich.

Ich weis was du meinst. Ich habe schon für Massenabfragen vorbereitet. Bei mir werden alle PAx überwacht.
Tatsächlich benutze ich aber nur 2 - daher werde ich mal in mich gehen und die "EierLegendeWollmilchSau" hinterfragen.

Ist es nicht wesentlich Recorcenschohnender, das gesamte Register auf Veränderungen abzufragen, als jede 1ms (oder 10ms) jeden Eingang einzelnd abzufragen? Was ist wenn ich 4 Eingänge pro Register nutze, ist das dann anders?



Ich würde alle Funktionen direkt in der ISR machen. Was mir auch auffällt ist, das du in der Auswertung zuerst eine Zahl einer Variable zuweißt und anschließend diese Variable abfrägst. Ich würde z.B. "Ledxx_ein", Ledxx_aus,... direkt in die "If"-Abfrage schreiben.

LedXX_ein, LedXX_aus ist nur ein Test-Platzhalter für aufwendigere Anweisungen.
Aber auch hier sehe ich die "Kanone die auf Spatzen zielt".



Solltest du das Programm nicht ganz verstehen, einfach melden.

Wieso nicht? Ist dein Code doch viel leichter lesbar und einfacher.



Man sollte, wenn es geht, auf >, >=, < oder <= Abfragen und nicht auf == wie du es bei dem 10ms Takt gemacht hast. Der Grund ist das du theoretisch den Wert auf z.B. 11 stellen kannst und dann wird weitergezählt bis der Zähler überläuft und zählt dann wieder bis 10. Wenn du eine Bytevariable hast zählt er 255ms zu lange.

Gute Idee.
Bei >= bringt ein "Verschlucken" nur eine minimale Unsauberkeit, aber kein Durchlaufen mit sich.



Ich habe bei dem 10ms Takt "5" genommen, weil bei diesem Programm ca. alle 2ms ein INT ausgelöst wird, bei dir muss "10" stehen.

Alle 10ms wird bei mir ausserhalb des ISR der EIngangszustand abgefragt. Sollte sich dieser 3 mal nicht ändern (30ms) wird der neue Zustand gesetzt.
Der 1ms-Takt "OverflowZaehler" wird später im main() noch anderweitig als Stopuhr verwendet.

021aet04
24.08.2019, 09:48
Deine Variante ist, vermutlich (nicht getestet), so Resourcenschonend wie meine. Ich habe z.B. nur Variablen die ich unbedingt benötige, Das ist die Freigabe und der alte Zustand der Eingänge. Du hast noch viel mehr. Du hast viel mehr Variablen (auch wenn diese nur temporär sind), das ist Speicher (Ram) und zusätzlich gibt es mehr Code, das ist auch Speicher (Flash).

Bei meiner Variante ist es gleich wie bei deiner. Zuerst wird auf Veränderung geachtet. Erst wenn 3x hintereinander der Zustand gleich bleibt wird der Eingang abgefragt (dafür ist "Prellzeit" zuständig). Was man eventuell noch ändern könnte ist, den Zustand des Pinregisters 1x in eine temporäre Variable schreiben und mit dieser dann weiterarbeiten. Ich frage das PIN-Register 2x ab (einmal bei der Abfrage und einmal wenn ich den Zustand in die Variable "Eingang_alt"). In dieser Zeit könnte sich der Wert allerdings ändern.

Du fragst auch jeden Pin einzeln ab, du fragst sogar 2x ab und ich nur einmal. Du schaust ob es High ist und dann ob es low ist. Das kannst du dir eigentlich sparen, denn es gibt keinen 3ten Zustand und somit ist es automatisch low wenn es nicht high ist.

Wenn du auf bestimmte Muster von den Eingängen abfragst, kannst du mit switch arbeiten. Als Beispiel du willst etwas ausführen wenn am PortB nur PB0 und PB2 high sind und alle anderen low kannst du auf 0x03 abfragen.

Also in etwa so:

switch (Eingangsbyte)
{
case 0x03:
........
........
Mach was
........
........
break;
}



https://www.roboternetz.de/community/images/misc/quote_icon.png Zitat von 021aet04 https://www.roboternetz.de/community/images/buttons/viewpost-right.png (https://www.roboternetz.de/community/showthread.php?p=654477#post654477)
Man sollte, wenn es geht, auf >, >=, < oder <= Abfragen und nicht auf == wie du es bei dem 10ms Takt gemacht hast. Der Grund ist das du theoretisch den Wert auf z.B. 11 stellen kannst und dann wird weitergezählt bis der Zähler überläuft und zählt dann wieder bis 10. Wenn du eine Bytevariable hast zählt er 255ms zu lange.



Gute Idee.
Bei >= bringt ein "Verschlucken" nur eine minimale Unsauberkeit, aber kein Durchlaufen mit sich.

"Verschlucken" wird er sich nicht, solltest du aber irgendwie auf diese Variable von "Außen" zugreifen können, kann man diesen Wert ändern. Bei deiner Anwendung wird es aber nicht sein (außer dein Speicher sollte kaputtgehen, was aber eher unwahrscheinlich ist). Bei einer SPS, wäre es aber möglich (somit sollte man immer so arbeiten).




Ich habe nur die ISR gemacht (und die Konfiguration des Timers nur über Prescaler), mit einem Attiny2313 mit 8MHz, dadurch stimmt das ganze Timing nicht exakt mit deinem Programm überein.

MfG Hannes

frabe
27.08.2019, 15:15
Danke, Hannes.
Deine Ausführungen sind für mich sehr hilfreich!

frabe
05.09.2019, 15:19
Frage:
Wie "legetim" ist es, aus einem ISR mit longjmp in das main() zurück zu springen, sollten bestimmte Bedingungen innerhalb des ISR erfüllt sein?
Hintergrund:
Mehrfache Tastenabfrage+Entprellung via Timer-ISR. Je nach Ablaufposition innerhalb des main() sollen unterschiedliche Befehle ausgeführt werden. Einfaches Bsp: ein delay() abbrechen/überspringen.

Klebwax
06.09.2019, 06:57
Frage:
Wie "legetim" ist es, aus einem ISR mit longjmp in das main() zurück zu springen, sollten bestimmte Bedingungen innerhalb des ISR erfüllt sein?

Was bedeuted legitim hier? Die Frage ist: darf man es benutzen? Wenn es zulässig ist, dann darf man es auch. Eine zweite Frage wäre, ist es sinnvoll?

Einige aber sicher nicht alle Antworten, die mir so einfallen.

Ich benutze C schon ziemlich lange, hatte aber noch nie das Bedürfnis, longjmp zu benutzen. Ich hab es also noch nie für sinnvoll gehalten.

Setjmp/longjmp ist ein Minenfeld. In MISRA (https://de.wikipedia.org/wiki/MISRA-Cæ) ist es IMHO verboten. Wer sich sehr gut in C auskennt, mag vielleicht in diesem Minenfeld navigieren können, warum Anfänger das tun wollen, erschließt sich mir nicht.

Ein Interrupthandler verhält sich zwar wie eine normale C-Funktion, ist aber keine normale Funktion. Das sieht man an den Atributen, die sie hat. Der Code den der Compiler erzeugt und der vor der ersten Zeile C-Code der Funktion läuft ist ebenso wie der Code, der bei einem return erzeugt wird, ein anderer. Verlässt man eine solche Funktion, wird das, was sonst beim return gemacht wird, ausgelassen. Die Konsequenzen hab ich jetzt nicht untersucht.

Ob das ganze Konzept
Je nach Ablaufposition innerhalb des main() sollen unterschiedliche Befehle ausgeführt werden. Sinn macht, hab ich jetzt nicht angesehen.

MfG Klebwax

Sisor
06.09.2019, 09:04
Frage:
Wie "legetim" ist es, aus einem ISR mit longjmp in das main() zurück zu springen, sollten bestimmte Bedingungen innerhalb des ISR erfüllt sein?
Hintergrund:
Mehrfache Tastenabfrage+Entprellung via Timer-ISR. Je nach Ablaufposition innerhalb des main() sollen unterschiedliche Befehle ausgeführt werden. Einfaches Bsp: ein delay() abbrechen/überspringen.

Wie legitim? Auf einer Skala von 0 bis 100 eine solide 0,001. Mit anderen Worten: Ohne sehr gute Gründe überhaupt nicht.
In C mit normalen Sprachfeatures nur die Möglichkeit über ein return eine Funktion ( und ja: ISR ist streng genommen ein Makro, wird aber vom Kompiler zu einer void ISR_Name (void)-Funktion reduziert) zu verlassen (In void()-Funktionen trägt der Kompiler das return nachträglich ein, falls es nicht ausdrücklich dort steht.) Das hat hauptsächlich den Grund eine korrekte Funktion des Aufrufstapels (https://de.wikipedia.org/wiki/Aufrufstapel) zu gewährleisten. Mit Sprüngen aus einer Funktion ist das Verhalten des Programms daher undefiniert.

Dein Problem kann man anders lösen, z.B. mit einer abbrechbaren warte-Funktion wie dieser:


volatile bool abbrechen = false;
void warte(uint ms) {
while(--ms>0 && !abbrechen) delay(1);
abbrechen = false;
}