Forum: Mikrocontroller und Digitale Elektronik Interrupts in Assembler und C


von Anfänger (Gast)


Lesenswert?

Ich habe eine Verständnisfrage, weil ich unsicher bin.

Interrupts in Assembler hab ich denk ich verstanden. Sind die Interrupts 
aktiviert, springt der uC an die entsprechende Speicherstelle und der 
dortige Befehl wird ausgeführt - in der Regel ein jmp-Befehl, um das zu 
dem Interrupt gehörende "Unterprogramm" aufzurufen.

Im GCC-Tutorial bin ich etwas unsicher.

Wenn ich in C mit
1
 sei ();
 die Interrupts aktiviere, dann schreibe ich "einfach" eine Funktion 
mit?
1
 
2
3
ISR (InterruptX) { Programmcode }

Beispielprogramm - Zaehlen von Impulsen:
1
// Include-Dateien
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
// Globale Variablen
6
int Zaehler = 0;
7
8
ISR (INT0)
9
{
10
  Zaehler ++;
11
} // ende interrupt-routine
12
13
int main(void)
14
{
15
    
16
  // steigende Flanken an INT0 sollen gezaehlt werden
17
  ISC01 = 1;
18
  ISC00 = 1;
19
  
20
  // Interrupts einschalten
21
  sei ();
22
  
23
  while(1)
24
    {
25
        if (Zaehler = 100)
26
    {
27
      // Tu irgendwas ...
28
      Zaehler = 0;
29
    }      
30
    } // Ende Hauptschleife
31
} // ende main

von Heiko N. (heikon)


Lesenswert?

ja, weil er deine Interrupt Funktion an die Stelle der Einsprungtabelle 
für den entsprechenden Interrupt einhängt.
Somit führt er für dich dann automatisch den JMP aus.

von troll (Gast)


Lesenswert?

Anfänger schrieb:
> // Globale Variablen
> int Zaehler = 0;
volatile
atomar

von katastrophenheinz (Gast)


Lesenswert?

Hi,

nein. Das Ganze ist zweistufig.
- sei() ermöglicht grundsätzlich die Unterbrechbrechbarkeit.
- Jede einzelne Unterbrechungsart muss dann individuell zugelassen (mit 
sog. InterruptEnable-Flags in irgendwelchen Statusregistern.)

Für INT0 ist das beim mega328 das register EIMSK, das Bit heißt 
sinnigerweise auch INT0. Bei anderen Typen kann der Registername 
variieren.

Erst Setzen dieses Bit auf 1 und sei() bewirken, daß INT0 
"durchgelassen" wird.

Für andere Situationen gibt es andere Interrupts und andere 
Statusregister,
das Prinzip ist immer dasselbe. Genaueres im Manual

von Anfänger (Gast)


Lesenswert?

ok, so hab ich den Code nochmal ueberarbeitet:
1
// Include-Dateien
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
// Globale Variablen
6
volatile int Zaehler = 0;
7
8
ISR (INT0_vect)
9
{
10
  cli ();
11
  Zaehler ++;
12
  sei ();
13
} // ende interrupt-routine
14
15
int main(void)
16
{
17
    
18
  // steigende Flanken an INT0 sollen gezaehlt werden
19
  // ISC01 = 1;
20
  // ISC00 = 1;
21
  EICRA &= ( (1<<ISC00) & (1<<ISC01) );
22
  EIMSK &= (1<<INT0); // Interrupt zulassen!
23
  
24
  // Interrupts einschalten
25
  sei ();
26
  
27
  while(1)
28
    {
29
        if (Zaehler = 100)
30
    {
31
      // Tu irgendwas ...
32
      Zaehler = 0;
33
    }      
34
    } // Ende Hauptschleife
35
} // ende main

von katastrophenheinz (Gast)


Lesenswert?

falsche operatoren - Setzen von Bits mit "oder"
1
EICRA |= ( (1<<ISC00) | (1<<ISC01) );
2
  EIMSK |= (1<<INT0); // Interrupt zulassen!

von katastrophenheinz (Gast)


Lesenswert?

cli() und sei() in der ISR sind überflüssig
macht der Controller automatisch

von troll (Gast)


Lesenswert?

Anfänger schrieb:
> ISR (INT0_vect)
> {
>   cli ();  <--------------
>   Zaehler ++;
>   sei ();  <-------------
> }

Ganz böse, hat in einer ISR nichts verloren. Der atomare Zugriff bezieht 
sich auf die Hauptschleife in main, nicht auf die ISR. Wenn der µP diese 
anspringt wird das I-Flag automatisch gelöscht (und am Ende der ISR 
wieder gesetzt), der Zugriff in der ISR ist also immer atomar.

von katastrophenheinz (Gast)


Lesenswert?

und aus
1
 if (Zaehler = 100)
mache
1
 if (Zaehler == 100)

das sollte aber der Compiler auch anmeckern.

von Anfänger (Gast)


Lesenswert?

katastrophenheinz schrieb:
> falsche operatoren - Setzen von Bits mit "oder"EICRA |= ( (1<<ISC00) | 
(1<<ISC01) );
>   EIMSK |= (1<<INT0); // Interrupt zulassen!

vielen Dank für den Hinweis, ich hab es wieder verwechselt mit dem 
Löschen einzelner Bits, dass geht glaub ich so
1
EICRA &= ~(1<<ISC00);

aber vielen Dank, dass Ihr mir so schnell kurz geholfen habt und ich 
wieder was dazulernen konnte heute.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Anfänger schrieb:
> if (Zaehler = 100)
>     {
>       // Tu irgendwas ...
>       Zaehler = 0;

volatile hast du zwar nun beachtet, aber atomar nach wie vor
noch nicht.  Solange du nur auf "Zaehler == 100" testest (das zweite
Gleichheitszeichen fehlt dir auch noch ;-), passiert da nichts, aber
sowie du dann den Code auf "Zaehler == 1000" erweiterst, wirst du
dich wundern, warum er gelegentlich zur falschen Zeit zurückgesetzt
wird.

von Anfänger (Gast)


Lesenswert?

katastrophenheinz schrieb:
> cli() und sei() in der ISR sind überflüssig
> macht der Controller automatisch

aber nur in c, in Assembler muss ich doch die Interrupts ab- und 
anschalten? So zumindest hab ich es aus dem Assembler-Tutorial in 
Erinnerung.

von Karl H. (kbuchegg)


Lesenswert?

@Anfänger

> cli() und sei() in der ISR sind überflüssig

Nicht nur das.
Sie sind sogar gefährlich!
(Nicht das da jetzt der Eindruck entsteht: Na ja, von mir aus sind die 
überflüssig, aber macht ja nix)


Das Problem ist, dass damit die Interrupts einen Tick zu früh wieder 
freigegeben werden. Dadurch ist es dann möglich, dass eine ISR von einem 
weiteren Interrupt (kann durchaus auch derselbe Auslöser sein) 
unterbrochen werden kann. Ad infinitum. Und irgendwann ist dann der 
Stack aufgebraucht um das ganze wieder zu entwirren. Der Absturz des 
Programms ist dann vorprogrammiert.

Also merken: In einer ISR kümmerst du dich nicht um cli() und sei(). Das 
macht der µC automatisch. (Und genau deswegen gibt es auch in Assembler 
eine spezielle Return Instruktion "reti", die wie ein normaler "ret" 
funktioniert, nur das eben zusätzlich noch die Interrupts wieder 
freigegeben werden)

von Andreas M. (amesser)


Lesenswert?

katastrophenheinz schrieb:
> cli() und sei() in der ISR sind überflüssig
> macht der Controller automatisch

Überflüssig ist leicht untertrieben. Das sei() bewirkt das der 
Controller noch während er in der Interruptroutine ist Interrupts wieder 
aktiviert -> Nested Interrupts.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Anfänger schrieb:
>> cli() und sei() in der ISR sind überflüssig
>> macht der Controller automatisch
>
> aber nur in c, in Assembler muss ich doch die Interrupts ab- und
> anschalten?

Nein.  Das macht die Controller-Hardware (bei einem AVR).

von Tobias H. (obazda)


Lesenswert?

>Zaehler = 0;

Ist so auch gefährlich!

Es ist ein 16 Bit Wert --> Der Compiler macht sowas in der Art:

LowByte = 0
HighByte = 0

so, jetzt kann dazwischen(!!) der Interrupt zuschlagen!
Folgt: LowByte = 1 (du hast ja die Zaehler++ Anweisung)
Dann wird HighByte=0 gesetzt

Folg aber: Zaehler hat den Wert 1!!!! Nicht 0!!!

von Marwin (Gast)


Lesenswert?

Nun schreit nicht immer gleich Feuer, wenn einer die Interrupts von Hand 
wieder frei gibt. Ja, wenn man das falsch macht, kann man sich mit 
verschachtelten Interrupts in den Fuss schiessen. Und? Ich habe schon 
auf Plattformen gearbeitet, da waren die Interrupts generell nicht 
gesperrt und im Handbuch stand bei der Erklaerung, wie man sie sperrt 
eine grosse Brandrede, was da Alles Schlimmes passieren kann. Also genau 
umgekehrt wie hier :-)

Beim Programmieren kann man sich aber auch mit allerlei anderen Dingen 
in den Fuss schiessen - laesst man Alles weg, womit das moeglich ist, 
kann man keine Programme mehr schreiben.

Viel wichtiger waere hier mal wieder der Hinweis gewesen, dass der OT 
das Pferd von der falschen Seite her aufzäumt: Erst C lernen, dann 
Mikrocontroller programmieren lernen. Nicht Letzteres und C "nebenher" 
anhand von Beispielen und Rumprobieren zu lernen versuchen.

von Tobias H. (obazda)


Lesenswert?

Sorry,
Abhilfe:

Cli();
Zaehler=0;
Sei();

von Anfänger (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> volatile hast du zwar nun beachtet, aber atomar nach wie vor
> noch nicht.

Was ist denn Eurer Meinung nach ein geeigneter Weg, um das atomare 
Problem zu lösen? Eine einfache Variante wäre es, das Programm in einem 
Salzstock einzulagern, aber nicht so schlampig wie in der Asse.

So wie ich es jetzt verstanden hab, können die Interrupts die Variable 
verändern, während zB. bei mir die if-Abfrage durchgeführt wird und 
durch den Interrupt eine Unterbrechnung stattfindet.

"atomar" war blau hinterlegt und ich habe mir den Text dazu durchgelsen:

"Abhilfe: Wenn man sich nicht wirklich ganz sicher ist, sollten um 
kritische Aktivitäten herum jedesmal die Interrupts abgeschaltet 
werden."

sollte ich es jetzt so abwandeln:
1
while (1)
2
{
3
 cli ();
4
 if ...
5
 sei ();
6
}

Mir fällt noch ein - vielleicht einfach eine Sicherungskopie machen?
1
int i;
2
3
while (1)
4
{
5
 i = Zaehler;
6
 if (i == 100)
7
   {
8
     // Tu was...
9
     Zaehler = 0; // ABER auch hier koennte der Interrupt die if-Abfrage unterbrechen :-(
10
   }
11
}

von Programmierer (Gast)


Lesenswert?

Es ist sauberer, statt cli()/sei() die Sequenz
SREG sichern
cli()
...machwasatomar...
SEG wieder herstellen
zu verwenden. Damit stellst du in jedem Fall den atomaren Zugriff 
sicher, pfuschst aber nicht am Interrupt Flag rum. Das kann bei 
Call-Back Funktionen wichtig sein. Ja, kein Anfängerthema, ich weiss.

von katastrophenheinz (Gast)


Lesenswert?

Hi,

in deinem Beispielprogramm passiert nicht viel, wenn du die 
entsprechenden Codeblöcke nicht unteilbar machst.

Ums sauber zu programmieren sollte die Abfrage und die evtl. darauf 
folgende Manipulation des Zählers unteilbar gemacht werden:
1
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
2
{
3
    if (Zaehler == 100)
4
    {
5
      // Tu irgendwas ...
6
      Zaehler = 0;
7
    }
8
}
In der Tat kein Anfängerthema...

von xfr (Gast)


Lesenswert?

Anfänger schrieb:
> sollte ich es jetzt so abwandeln:
>
> while (1)
> {
>  cli ();
>  if ...
>  sei ();
> }

Das wäre die übliche Vorgehensweise. Für richtige Programme solltest Du 
allerdings die Interrupts immer nur so lange ausschalten, wie wirklich 
nötig. Also nicht grundsätzlich die komplette Hauptschleife atomar 
machen, sondern immer wenn es möglich ist, Interrupts auch wieder 
zulassen.

> Mir fällt noch ein - vielleicht einfach eine Sicherungskopie machen?
>
> int i;
>
> while (1)
> {
>  i = Zaehler;
>  if (i == 100)
>    {
>      // Tu was...
>      Zaehler = 0; // ABER auch hier koennte der Interrupt die if-Abfrage
> unterbrechen :-(
>    }
> }

Dann könnte der Interrupt während der Zuweisen von Zaehler auf i 
auftreten. In diesem Beispiel wäre auch eine Möglichkeit statt
1
if (Zaehler == 100)
zu schreiben
1
if (Zaehler >= 100)
Dann wäre es nicht schlimm, wenn der Interrupt zwischendurch den Zähler 
nochmal erhöht hat und Du im Hauptprogramm den Wert 100 verpasst hast.

Das kommt aber ganz auf die Anwendung an, was akzeptabel ist und was 
nicht. Die sicherste Möglichkeit ist, gerade als Anfänger, die 
Interrupts immer auszuschalten, wenn auf Variablen zugriffen wird, die 
auch in der ISR verwendet werden. Und diese Variablen auch immer 
volatile zu machen.

von katastrophenheinz (Gast)


Lesenswert?

Andere Alternative ( auch gerne verwendet ) ist das setzen einer 
globalen Flag-Variable in der ISR und die Abfrage des Flag im normalen 
Programm:

1
ISR (INT0_vect)
2
{
3
  Zaehler++;
4
  if (Zaehler == 100 ) flags |= (1<<OVF_FLAG);
5
} // ende interrupt-routine

und im Hauptprogramm in deiner endlosen While schleife
1
  ...
2
  if ( flags & (1<<OVF_FLAG ) {
3
    cli();flags &= ~(1<<OFV_FLAG);sei();
4
    // Tu irgendwas 
5
  }
6
  ...

von troll (Gast)


Lesenswert?

katastrophenheinz schrieb:
> Andere Alternative ( auch gerne verwendet ) ist das setzen einer
> globalen Flag-Variable in der ISR und die Abfrage des Flag im normalen
> Programm:
Wobei wenn man die Flaggen in einer uint8_t speichert und nur jeweils 
eine setzt/löscht das ganze automatisch atomar sein sollte, Stichwort 
sbr/cbr.

Aber ich glaub wir schweifen ab und verwirren den armen TO...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Anfänger schrieb:

> Mir fällt noch ein - vielleicht einfach eine Sicherungskopie machen?
>
> [c]
>
> int i;
>
> while (1)
> {
>  i = Zaehler;
>  if (i == 100)

Ja, aber um "i = Zaehler" gehört wieder die Interruptsperre, genauso
dann später um die Zuweisung "Zaehler = 0".

In der avr-libc gibt's auch schicke Makros dafür:

http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

von katastrophenheinz (Gast)


Lesenswert?

Da fehlt noch ein entscheidender Teil in der ISR:

ISR (INT0_vect)
{
  Zaehler++;
  if (Zaehler == 100 ) {
     Zaehler = 0;
     flags |= (1<<OVF_FLAG);
  }
} // ende interrupt-routine

von bitte löschen (Gast)


Lesenswert?

Wahrscheinlich interessant für Dich:
http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
1
#include <util/atomic.h>
2
3
void EineFunktion()
4
{
5
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
6
  {
7
  }
8
}
9
10
...

von katastrophenheinz (Gast)


Lesenswert?

troll schrieb:
> Wobei wenn man die Flaggen in einer uint8_t speichert und nur jeweils
>
> eine setzt/löscht das ganze automatisch atomar sein sollte, Stichwort
>
> sbr/cbr.

Ja, das ist jetzt hardcore: sbr/cbr ist nur auf Registern < 0x1f atomar.
bei normalen RAM-Variablen muss das Set/Reset auch mit cli()... sei() 
geklammert werden.

von xfr (Gast)


Lesenswert?

katastrophenheinz schrieb:
> troll schrieb:
>> Wobei wenn man die Flaggen in einer uint8_t speichert und nur jeweils
>>
>> eine setzt/löscht das ganze automatisch atomar sein sollte, Stichwort
>>
>> sbr/cbr.
>
> Ja, das ist jetzt hardcore: sbr/cbr ist nur auf Registern < 0x1f atomar.
> bei normalen RAM-Variablen muss das Set/Reset auch mit cli()... sei()
> geklammert werden.

Aber doch nicht, wenn man die Variable wie in diesem Beispiel als Flag 
mit nur einem Bit verwendet wird. Dann gibt es nur zwei Zustände, 
gesetzt oder nicht. Wenn der Interrupt sowohl vor als auch nach dem 
Zurücksetzen des Flags auftreten darf, ohne Schaden anzurichten, dann 
darf er das auch dazwischen. Denn was anderes als 0 oder 1 kann am Ende 
ja nicht bei rauskommen.

von katastrophenheinz (Gast)


Lesenswert?

xfr schrieb:
> Aber doch nicht, wenn man die Variable wie in diesem Beispiel als Flag
> mit nur einem Bit verwendet wird. Dann gibt es nur zwei Zustände,
> gesetzt oder nicht. Wenn der Interrupt sowohl vor als auch nach dem
> Zurücksetzen des Flags auftreten darf, ohne Schaden anzurichten, dann
> darf er das auch dazwischen. Denn was anderes als 0 oder 1 kann am Ende
> ja nicht bei rauskommen.


Ja du hast recht, solange im Flag-Byte nur ein Bit genutzt wird, kann 
nichts passieren und für diesen speziellen Fall kann man die 
cli();...;sei();-Klammerung weglassen.

von Anfänger (Gast)


Lesenswert?

Ich danke Euch alle für die vielen Hinweise und Tipps, die ihr mir 
gegeben habt. In der Tat hätte ich nicht damit gerechnet, dass sowas mit 
einer Variable so aufwendig sein kann.

katastrophenheinz schrieb:
> Andere Alternative ( auch gerne verwendet ) ist das setzen einer
> globalen Flag-Variable in der ISR und die Abfrage des Flag im normalen
> Programm: ....

Ich glaube, diese Variante scheint mir erstmal eine geeignete Variante 
zu sein - damit brauche ich die Interrupts nicht zu unterbrechen, denn 
durch die Unterbrechung könnten theoretisch Impulse am Eingang verloren 
gehen.

Philipp K. schrieb:
> ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
>   {
>   }

Zu der Funktion ATOMIC_BLOCK (ATOMIC_RESTORESTATE) { ... } hab ich eine 
kleine Frage - so wie ich es verstanden hab, ist es "einfach" eine 
verkürzte Schreibweise für

SREG sichern,
Interrupts aus,
Programmblock,
SREG wiederherstellen,
Interrupts an?


Jetzt stellt sich für mich nur noch eine kleine andere Frage
katastrophenheinz schrieb:
> flags |= (1<<OVF_FLAG);

Es müsste dann aber grundsätzlich sichergestellt sein, dass bei dieser 
Methode das Overflow-Flag nicht auch von anderen Funktionen verwendet 
wird? Sicherlich - in diesem einfachen Beispiel von mir jetzt nicht, 
aber bei anderen Programmen, zB. wenn ich später noch diese UART-Tools 
von der Fleury-Bibliothek nutze oder andere Module?

von katastrophenheinz (Gast)


Lesenswert?

Anfänger schrieb:
> Zu der Funktion ATOMIC_BLOCK (ATOMIC_RESTORESTATE) { ... } hab ich eine
> kleine Frage - so wie ich es verstanden hab, ist es "einfach" eine
> verkürzte Schreibweise für
>
> SREG sichern,
> Interrupts aus,
> Programmblock,
> SREG wiederherstellen,
> Interrupts an?

Hi,

nicht ganz:
ATOMIC_BLOCK (ATOMIC_RESTORESTATE) bedeutet
SREG sichern, ( und damit I-Flag sichern )
Interrupts aus,
Programmblock,
SREG wiederherstellen ( und damit I-Flag wiederherstellen ),

d.h. nach Ende des Blocks ist das I-Flag so, wie es bei Eintritt war ( 
gesezt oder ungesetzt. Je nach Parameter von ATOMIC_BLOCK kann das 
Verhalten auch anders sein. RTFM.

>Jetzt stellt sich für mich nur noch eine kleine andere Frage
>katastrophenheinz schrieb:
>> flags |= (1<<OVF_FLAG);

Die Technik hierbei ist, daß man spezifische Bits nur für spezifische 
Ereignisse verwendet. D.h. wenn du das für z.B. UART erweitern wolltest,
dann könntest du weitere Bits dafür verwenden, z.B.:
1
#define OVF_FLAG      1  // Zaehlerüberlauf
2
#define GOT_STR_FLAG  2  // Zeichenkette komplett empfangen
3
#define STR_SENT_FLAG 3  // Zeichenkette komplett gesendet

von troll (Gast)


Lesenswert?

katastrophenheinz schrieb:
>
1
> #define OVF_FLAG      1  // Zaehlerüberlauf
2
> #define GOT_STR_FLAG  2  // Zeichenkette komplett empfangen
3
> #define STR_SENT_FLAG 3  // Zeichenkette komplett gesendet
4
>

Nicht eher 1 - 2 - 4 ?

von Karl H. (kbuchegg)


Lesenswert?

troll schrieb:
> katastrophenheinz schrieb:
>>
1
>> #define OVF_FLAG      1  // Zaehlerüberlauf
2
>> #define GOT_STR_FLAG  2  // Zeichenkette komplett empfangen
3
>> #define STR_SENT_FLAG 3  // Zeichenkette komplett gesendet
4
>>
>
> Nicht eher 1 - 2 - 4 ?

Solange er es in dieser Form (sinngemäss) verwendet

>> flags |= (1<<OVF_FLAG);

... dann nicht.

von troll (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Solange er es in dieser Form (sinngemäss) verwendet
>
>>> flags |= (1<<OVF_FLAG);
>
> ... dann nicht.

Argh, stimmt. Man soll halt nicht in Eile posten.

von troll (Gast)


Lesenswert?

Wobei, wenn man 1<<FLAG nutzt kann man auch bei 0 anfangen zu zählen.

von Anfänger (Gast)


Lesenswert?

Wie groß ist eigentlich die Variable "flags"? Ich habe jetzt mal in dem 
Atmega88-Datenblatt geschaut, bin aber noch nicht so ganz schlau daraus 
geworden, weil ich eine dierekte Variable mit Namen flags (zB. EICRA 
finde ich in dem Datenblatt).

Wenn ich den Debugger starte und mir die Prozessorvariablen anschaue, 
dann gibt es den Statusregister, ich glaube, dass ist das SREG, von dem 
hier vorhin auch gesprochen wurde, dass es ggf. manchmal gesichert 
werden sollte, da gibts das Zeroflag, Carryflag und noch mehr. Diese 
haben aber jetzt nichts mit der Variable "flags" zu tun, oder?

Oder ist "flags" eine Art Speicherstelle im uC, die ich als Anwender 
frei benutzen kann? Und wenn ja, nochmal die Frage von oben - wie groß 
ist es dann? Vom Bauchgefühl würd ich sagen, 8bit breit, bin mir aber 
nicht sicher.

von Karl H. (kbuchegg)


Lesenswert?

Anfänger schrieb:
> Wie groß ist eigentlich die Variable "flags"?

So groß wie du sie machst :-)

Machst du einen
volatile uint8_t flags;
dann hast du da 8 Bit

Machst du einen
volatile uint16_t flags;
dann hast du 16 Bit

Machst du einen
volatile uint32_t flags;
dann hast du 32 Bit

>Ich habe jetzt mal in dem
> Atmega88-Datenblatt geschaut, bin aber noch nicht so ganz schlau daraus
> geworden, weil ich eine dierekte Variable mit Namen flags (zB. EICRA
> finde ich in dem Datenblatt).

Die gibts auch nicht.
DU definierst dir deine Variablen!

von auch Anfänger (Gast)


Lesenswert?

in den oben gegebenen Beispielen steht "flags" als Platzhalter für 
Register die ein oder mehrere Flags enthalten. Also 8 Bit.

von katastrophenheinz (Gast)


Lesenswert?

troll schrieb:
> Wobei, wenn man 1<<FLAG nutzt kann man auch bei 0 anfangen zu zählen.
...ich wußte, das diese Anmerkung kommen würde ;-)

von katastrophenheinz (Gast)


Lesenswert?

auch Anfänger schrieb:
> in den oben gegebenen Beispielen steht "flags" als Platzhalter für
> Register die ein oder mehrere Flags enthalten. Also 8 Bit.

ja und nein

1. in den Beispielen oben steht flag für eine Variable im RAM, und die 
kann -im Rahmen des RAM- beliebig groß werden.

Hier muß man, wenn man den mehr als ein Flag-Bit nutzt, unabhängig von 
der Breite der Variable, Manipulationen auf der flag-Variablen mit 
cli().. sei() oder ATOMIC_BLOCK(ATOMIC_RESTORESTATE) klammern. Warum? 
Zugriffe auf diese RAM-Variablen sind immer länger als eine 
Maschinenanweisung ( nämlich: 1.lade Speicherzelle in Register / 2. 
manipuliere Register / 3. schreibe zurück ), so daß bei Interrupts, die 
innerhalb dieser Sequenz auftreten, und die den Flag-Wert manipulieren, 
Inkonsistenzen auftreten können. Dahingehend, daß nach dem Ende der ISR 
ein falscher weil veralteter Flag-Wert zurückgeschrieben wird.

2. man kann statt einer Variablen auch ein ungenutztes Register aus dem 
Bereich 00-0x1f verwenden, z.B. GPIOR0 (hat nicht jeder Typ), oder jedes 
andere, nicht verwendete und nebenwirkungsfreie Register in diesem 
Bereich. Für diesen Adressbereich kann der Compiler das Testen und 
Setzen von Bits in einem Maschinenbefehl bewerkstelligen und damit 
braucht man dann die cli()..sei() Klammerung nicht. Aber hier gilt die 
8-Bit-Limitierung. Man kann natürlich mehr als ein Register dafür 
belegen, und diese 8-Bit-Limitierung damit auf n*8 bit hochschrauben, 
solange es denn freie Register im Breich 0x00-0x1f gibt.

3. Im Zweifelsfall besser die cli()..sei()-Klammerung bzw 
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) einmal zu viel als zu wenig. 
cli()..sei() dann, wenn ich 100% sicher bin, daß ich dort nur mit 
gesetztem I-Bit reinlaufe. ATOMIC_BLOCK(ATOMIC_RESTORESTATE) dann, wenn 
ich keine Aussage über den Zustand des I-Bit treffen kann.

von Anfänger (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Die gibts auch nicht.
> DU definierst dir deine Variablen!

Ja toll, da kann ich ja lange nach einem Phantom namens flags im 
Datenblatt suchen :-). Vielen Dank, dass Ihr Euch so viel Zeit genommen 
habt, meine Fragen zu beantworten und mir zu helfen.

von Karl H. (kbuchegg)


Lesenswert?

Anfänger schrieb:
> Karl Heinz Buchegger schrieb:
>> Die gibts auch nicht.
>> DU definierst dir deine Variablen!
>
> Ja toll, da kann ich ja lange nach einem Phantom namens flags im
> Datenblatt suchen :-).

Kleiner Hinweis.

Wenn du in C auf ein Wort stösst, welches komplett in Grossbuchstaben 
geschrieben ist, dann handelt es sich der üblichen Konvention nach immer 
um ein Makro. Und auch umgekehrt: wenn etwas als Makro implementiert 
ist, dann wird es derselben Konvention nach immer komplett in 
Grossbuchstaben geschriebem.
Alle(!) Prozessorregister sind per Makro in C verfügbar gemacht.

d.h. wenn etwas, wie 'flags', nicht in Grossbuchstaben geschrieben ist, 
dann handelt es sich dabei mit 99.9% Wahrscheinlichkeit, nicht um ein 
Prozessorregister.

Umgekehrt, wenn etwas wie PORTC komplett in Grossbuchstaben geschrieben 
ist, dann ist das auf jeden Fall erst mal ein Makro. Als normaler 
Programmierer nimmt man aber Makronamen wie NR_LED (für 'Number of Led' 
also die Anzahl der LED) oder KEY_PLUS (für den Taster, der entweder mit 
einem Plus beschriftet ist oder der die Erhöhungsfunktionalität 
auslöst). Niemand würde ernsthaft ein Makro TIMSK0 in seinem 
Anwenderprogramm definieren, weil das viel zu unleserlich wäre. D.h. in 
dem Fall stehen die Chancen recht hoch, dass es sich dabei um etwas 
AVR-prozessorspezifisches handelt, das man dann auch mit diesem Begriff 
im Datenblatt mit der Suche schnell findet.

 Vielen Dank, dass Ihr Euch so viel Zeit genommen
> habt, meine Fragen zu beantworten und mir zu helfen.

von auch Anfänger (Gast)


Lesenswert?

Ich danke auch allen Beteiligten für den thread, der trotz meines 
unqualifizierten Einfurfs so informativ ist.

von W.S. (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Wenn du in C auf ein Wort stösst, welches komplett in Grossbuchstaben
> geschrieben ist, dann handelt es sich der üblichen Konvention nach..

Ach Heinz, die "üblichen Konventionen" - das ist eben nur 
Programmierer-Schnickschnack, der mal erfunden wurde wegen der vielen 
Unzulänglichkeiten von C. Besser wäre es gewesen, verläßliche Datentypen 
zu definieren. Was ich schon an Umdefinitionen für char und int gesehen 
habe, geht auf keine Kuhhaut.

Ich (und die meisten anderen Leute, die uC programmieren) halte es ganz 
einfach so, daß ich die Bezeichnungen die im Datenblatt des uC 
vorkommen, eben genau so verwende wie im Datenblatt geschrieben.

Wenn dort "IO2PIN" (alles groß) steht, dann benutze ich das auch genau 
so, ohne (weil es ja kein Makro sondern ein Hardwareregister ist) 
irgendwo zu "io0pin" umzudefinieren.

W.S.

von Bronco (Gast)


Lesenswert?

Anfänger schrieb:
> In der Tat hätte ich nicht damit gerechnet, dass sowas mit
> einer Variable so aufwendig sein kann.

Der Punkt ist, daß eine Variable in C mehrere Speicherworte belegen 
kann bzw. ein Variablenzugriff in C aus mehreren Assemblerbefehlen 
bestehen kann. Diese Sequenz aus Assemblerbefehlen kann vom Interrupt 
unterbrochen werden, sofern man keine Gegenmaßnahmen ergreift.

Auch wichtig: Read-Modify-Write
1
static uint8_t a = 0;
2
 ...
3
 a++;
Die Variable "a" sei im RAM (im Gegensatz zu Registern) gespeichert.
Nun könntest Du denken: "Ah, nur 8Bit, das kann der AVR in einem 
Assemblerbefehl!" und schenkst Dir die Atomar-Maßnahmen.
Aber: Der Befehl "a++" besteht aus drei Schritten (Assemblerbefehlen):
1. "a" aus dem RAM in ein Register lesen
2. Das Register inkrementieren
3. Den Registerinhalt an die RAM-Adresse von "a" zurückschreiben

Kommt Dein Interrupt dazwischen und verändert "a" im RAM, geht diese 
Änderung mit dem 3. Schritt verloren!

von Anfänger (Gast)


Lesenswert?

Ich habe leider nochmal ein Problem mit meinem Testprogramm
1
#ifndef F_CPU
2
#define F_CPU 1000000UL
3
#endif
4
5
#include <stdlib.h>
6
#include <avr/io.h>
7
#include <avr/interrupt.h>
8
#include <avr/pgmspace.h>
9
#include <util/delay.h>
10
#include <stdio.h>
11
#include "uart.h"
12
13
14
/* 9600 baud */
15
#define UART_BAUD_RATE      2400
16
17
void Schalter1 (void)
18
{
19
  PORTC &= ~(1<<PORTC0); // Bit 0 loeschen, LED an
20
  _delay_ms(2000);
21
  PORTC |= (1 << PORTC0);  // Bit 0 setzen, LED aus
22
  uart_putc ('1');
23
}
24
void Schalter2 (void)
25
{
26
  PORTC &= ~(1<<PORTC1); // Bit 0 loeschen, LED an
27
  _delay_ms(2000);
28
  PORTC |= (1 << PORTC1);  // Bit 0 setzen, LED aus} 
29
   uart_putc ('y');  
30
 }
31
32
ISR (INT0_vect)
33
{
34
  uart_puts ("\n Der Interrupt INT0 wurde ausgelöst!");
35
} // Ende Interrupt INT0
36
37
ISR (INT1_vect)
38
{
39
  uart_puts ("\n Der Interrupt INT1 wurde ausgelöst!");
40
} // Ende Interrupt INT1
41
42
43
int main(void)
44
{
45
  // eigene Variablen
46
  unsigned int Zeichen;  
47
  // Baudrate einstellen und BITS in UBBR setzen24
48
  uart_init (UART_BAUD_SELECT (UART_BAUD_RATE, F_CPU));
49
  
50
  // Datenrichtung festsetzen
51
  DDRB = 0x00;  // alles Eingänge
52
  PORTB = 0xFF; // alle PullUps einschalten
53
  
54
  DDRC = 0x3F;  // Pin 0-5 Ausgänge!24
55
  
56
  // Interrupts INT0 INT1 einschalten!
57
  EIMSK |= (1<<INT0);  // INT0-Bit setzen
58
  EIMSK |= (1<<INT1);  // INT1-Bit setzen
59
  
60
    // INT0 INT1 steigende Flanken zaehlen
61
  EICRA |= ( (1<<ISC00) & (1<<ISC01) & (1<<ISC10) & (1<<ISC11));
62
  
63
  // INT0 INT1 Eingänge
64
  // DDRD |= ( (1<<PIND2) & (1<<PIND3));
65
  
66
  // interne PullUps von INT0 INT1 einschalten:
67
  // PORTD |= ( (1<<PIND2) & (1<<PIND3));
68
    
69
  // Interrupts einschalten
70
  sei ();
71
  
72
  PORTC = 0xFF;  // alle LEDs aus
73
  
74
  uart_puts("\n Alle LEDs werden zum Testen eingeschaltet und laufen 10sec.");
75
  PORTC &= ~(1<<PORTC1); // Bit 0 loeschen, LED an
76
  _delay_ms(10000);
77
    PORTC |= (1 << PORTC1);  // Bit 0 setzen, LED aus
78
  uart_puts("\n Alle LEDs werden nun ausgeschaltet!");
79
    
80
  while (1)
81
  {
82
    if ( !(PINB & (1<<PINB0)) ) 
83
    {
84
      Schalter1();
85
    }
86
    
87
    if ( !(PINB & (1<<PINB1)) )
88
    {
89
      Schalter2();
90
    }  
91
    
92
    // Zeichen vom UART einlesen:
93
    Zeichen = uart_getc();
94
    // Zeichen überprüfen
95
    if ( !(Zeichen & UART_NO_DATA) )
96
      if ( !(Zeichen & UART_FRAME_ERROR))
97
        if ( !(Zeichen & UART_OVERRUN_ERROR))
98
           {
99
           // Zeichen zurück an Empfänger senden:
100
           uart_puts ("\n Folgendes Zeichen wurde empfangen: ");
101
           uart_putc ((unsigned char)Zeichen);
102
         }
103
  } // Ende while
104
}

Das Programm lief zunächst mit dem UART fehlerfrei. Zeichen, die ich in 
PuTTY eingegeben hatte, wurden wieder zurückgesendet und zwei Taster 
habe ich eingebaut, die eine LED aufleuchten lassen und ein Testzeichen 
per UART an den PC übermitteln.

Im nächsten Schritt wollte ich das Programm erweitern und die Eingänge 
INT0/INT1 hinzufügen.

Dazu hab ich die ISR (INT0_vect) ergänzt, die Interrupts mit EIMASK ... 
eingeschaltet, dann gesagt, dass steigende Flanken gezählt werden sollen 
mit EICRA - also wenn ein Taster gedrückt wird.

Dann hab ich noch gesagt, dass die PINS, wo INT0 und INT1 da sind - 
Eingänge sein sollen DDRD, und die internen PullUps eingeschaltet, weil 
ich einfach den Kontakt wie bei den Schaltern PINB0 und PINB2 mit Masse 
verbinden wollte. Dabei war mir dann aufgefallen, dass es ja dann 
fallende Flanken sind und keine steigenden. Dass habe ich jetzt noch 
nicht ausprobiert.

Aber - der Atmega88 macht nun garnix mehr. Ich habe vorsichtshalber 
nochmal einen "Test" eingebaut, die beiden Lampen sollen 10 Sekunden 
laufen, damit ich es sehe. Aber der uC erreicht nicht mal diese Position 
sondern hängt sich vorher irgendwo auf. Ich habe in den Interrupts 
gesagt, dass ein Satz an den UART gesendet werden soll, wenn der 
jeweilige Interrupt ausgelöst wurde - aber da erscheint auch nichts. 
Meine erste Vermutung war, dass irgendwas beim Setzen mit den 
EIn/ausgängen und den PullUps an INT0/INT1 falsch sein könnte und der uC 
dauerhaft die Interrupts auslöst, darum hab ich verschiedene Varianten 
ausprobiert, mal PORTD weggelassen mal DDRD und so, leider weis ich 
nicht, wo mein Fehlerchen sein könnte. Ich habe das Programm so in 
Analogie zu dem Programm oben aufgebaut, nur dass ich die Anzahl der 
Tastendrücke bis 100 noch nicht zählen wollte - dass wäre dann der 
nächste Schritt gewesen, wenn es klappen würde, einfach so INT0/Int1 
auszulösen und den Text an den UART zu senden.

Wenn mir jemand von Euch helfen kann, würde ich mich sehr drüber freuen.

von katastrophenheinz (Gast)


Lesenswert?

Schuss ins Blaue:
Wie ist uart_puts realisiert ?
Nonblocking d.h. nicht warten bis alles gesendet, sondern in 
zwischenpuffer schreiben und interruptgesteuert rausschreiben?

Wenn das so ist, dann vermute ich, daß der uart-sendepuffer überläuft
und dir im RAM so ziemlich alles überbügelt, was es da so gibt, incl 
Stack.

Denn Int1 und Int0 reagieren auch auf jedes Tasterprellen und die ISR 
wird sehr oft aufgerufwn, auch wenn du nur einmal tastest.

von katastrophenheinz (Gast)


Lesenswert?

... und falls uart_puts nonblocking implementiert ist:
Das wird auch nicht gehen, weil die UART-Interrupts eine niedrigere Prio
als INT0, INT1 haben. D.h. wenn du aus INT0 und INT1 eine 
UART-Schreiboperation aufrufst und wartest, daß die fertig wird, dann 
wird die nie ferig, weil der uart mit seinen Interrupts nie drankommt.

Abhilfe: flags

von katastrophenheinz (Gast)


Angehängte Dateien:

Lesenswert?

...probier mal das.
Und denk dran: Durch Schalterprellen werden Int0 und Int1 sehr oft 
aufgerufen, d.h. in kürzester Zeit ist dein Schreibpuffer vollgelaufen.

wenn dein implemenierung von uart_puts solange wartet, bis das letzte 
Zeichen geschrieben ist, dann wird das programm nur sinnloses Zeug 
ausgeben, weil während des Wartens auf letztes Zeichen raus schon 
weitere Interrupts von Int0 / Int1 kommen

von katastrophenheinz (Gast)


Lesenswert?

Ich hab mir nochmal die uart-Bibliothek von P.Fleury angeguckt - von den 
funktionsaufrufen her, die du verwendest, nutzt du vermutlich das Ding.
Es ist so, wie ich vermutet habe: uart_puts wartet - falls Sendepuffer 
voll  - auf freien Platz. Wenn du jetzt aus einer INTx-ISR-Routine 
heraus uart_puts aufrufst, dann wartet der Aufruf so lange, bis alle 
Zeichen im Sendepuffer untergebracht werden konnten. Im Sendepuffer wird 
immer dann platz frei, wenn der uart ein zeichen rausschreibt. Damit er 
das tun kann, muß er jedoch mit seinen Interrupts drankommen. Das tut er 
aber nicht weil das I-FLag mit Beginn von Int0/int1 rückgesetzt wurde.
D.h. du hast deinen ersten klassischen deadlock programmiert. Herzlichen 
Glückwunsch!
Was tun?
- Sendepuffer größer machen: Hilft nur bedingt und löst das Problem 
nicht
- Interrupts in INT0 / INT1 wieder zulassen: Kann man machen, wenn man 
sicher ist, daß es keine kaskadierenden Interrupts gibt ( oder die sich 
zumindest im Rahmen halten) -> gefährlich
- keine UART-Schreiboperationen in Interrupt-Routinen: kann man machen
- Blocking calls in der UART-Bibliothek ausschließen: kann man auch 
machen, erfordert das Ändern in fremdem Programmcode
- Nutzung von Flags:noch besser, so hab ich dein Programm umgebaut
Ich finde das Design der UART-Bibliothek an dieser Stelle nicht rund, 
weil aus non-blocking-calls in bestimmten Situationen ein blocking-call 
wird - just my 2 cents.

von Anfänger (Gast)


Lesenswert?

Hi Katastrophenheinz!

Vielen Dank, dass Du Dir so viel Zeit genommen hast, um mir zu helfen.

Ich habe Keks.c auch noch ein bißchen umgebastelt:
1
#ifndef F_CPU
2
#define F_CPU 1000000UL
3
#endif
4
5
// Baudrate UART
6
#define UART_BAUD_RATE      2400
7
8
9
10
#include <stdlib.h>
11
#include <avr/io.h>
12
#include <avr/interrupt.h>
13
#include <avr/pgmspace.h>
14
#include <util/delay.h>
15
#include <stdio.h>
16
#include "uart.h"
17
#include <util/atomic.h>
18
19
volatile uint8_t flags=0;
20
#define F_INT0_Overflow  0
21
#define F_INT1_Overflow  1
22
#define F_INT0_BIT_Overflow (1<<F_INT0_Overflow)
23
#define F_INT1_BIT_Overflow (1<<F_INT1_Overflow)
24
25
volatile int Zaehl_Int0=0, Zaehl_Int1=0;
26
27
void Schalter1 (void)
28
{
29
  PORTC &= ~(1<<PORTC0); // Bit 0 loeschen, LED an
30
  _delay_ms(2000);
31
  PORTC |= (1 << PORTC0);  // Bit 0 setzen, LED aus
32
  uart_putc ('1');
33
}
34
void Schalter2 (void)
35
{
36
  PORTC &= ~(1<<PORTC1); // Bit 0 loeschen, LED an
37
  _delay_ms(2000);
38
    PORTC |= (1 << PORTC1);  // Bit 0 setzen, LED aus}
39
    uart_putc ('2');
40
 }
41
42
ISR (INT0_vect)
43
{
44
  Zaehl_Int0 ++;
45
  if (Zaehl_Int0 == 30)
46
  {
47
    Zaehl_Int0 = 0;
48
    flags |= F_INT0_BIT_Overflow;
49
  }
50
} // Ende Interrupt INT0
51
52
53
int main(void)
54
{
55
  // eigene Variablen
56
  unsigned int Zeichen;  
57
  // Baudrate einstellen und BITS in UBBR setzen24
58
  uart_init (UART_BAUD_SELECT (UART_BAUD_RATE, F_CPU));
59
  
60
  // Datenrichtung festsetzen
61
  DDRB = 0x00;  // alles Eingänge
62
  PORTB = 0xFF; // alle PullUps einschalten
63
  
64
  DDRC = 0x3F;  // Pin 0-5 Ausgänge!24
65
  
66
  // Interrupts INT0 INT1 einschalten!
67
  EIMSK |= (1<<INT0);  // INT0-Bit setzen
68
  // EIMSK |= (1<<INT1);  // INT1-Bit setzen
69
  
70
    // INT0 INT1 steigende Flanken zaehlen
71
   EICRA = ( (1<<ISC00) | (1<<ISC01) | (1<<ISC10) | (1<<ISC11));
72
   // EICRA &= ( ~(1<<ISC00) & ~(1<<ISC01) & ~(1<<ISC10) & ~(1<<ISC11));
73
   
74
  // INT0 INT1 Eingänge
75
  DDRD &= ( ~(1<<PIND2) & ~(1<<PIND3));
76
  
77
  // interne PullUps von INT0 INT1 einschalten:
78
  PORTD = ( (1<<PIND2) | (1<<PIND3) );
79
    
80
  // Interrupts einschalten
81
  sei ();
82
  
83
  PORTC = 0xFF;  // alle LEDs aus
84
  
85
  uart_puts("\n Alle LEDs werden zum Testen eingeschaltet und laufen 10sec.");
86
  PORTC &= ( ~(1<<PORTC1) & ~(1<<PORTC0) ) ; // Bit 0 loeschen, LED an
87
  _delay_ms(5000);
88
  PORTC |= 0xFF; //(1 << PORTC1);  // Bit 0 setzen, LED aus
89
  uart_puts("\n Alle LEDs werden nun ausgeschaltet!");
90
    
91
  while (1)
92
  {
93
    if ( flags & F_INT0_BIT_Overflow )
94
  {
95
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
96
    {
97
      flags &= ~F_INT0_BIT_Overflow;
98
    }
99
        uart_puts ("X");
100
    }
101
    
102
  /* if ( flags & F_INT1_BIT_Overflow )
103
  {
104
        cli(); flags &= ~F_INT1_BIT_Overflow; sei();
105
        uart_puts ("Got Int1");
106
    } */
107
      
108
    if ( !(PINB & (1<<PINB0)) ) 
109
    {
110
      Schalter1();
111
    }
112
    
113
    if ( !(PINB & (1<<PINB1)) )
114
    {
115
      Schalter2();
116
    }  
117
    
118
    // Zeichen vom UART einlesen:
119
    Zeichen = uart_getc();
120
    // Zeichen überprüfen
121
    if ( !(Zeichen & UART_NO_DATA) )
122
      if ( !(Zeichen & UART_FRAME_ERROR))
123
        if ( !(Zeichen & UART_OVERRUN_ERROR))
124
           {
125
              // Zeichen zurück an Empfänger senden:
126
              uart_puts ("\n Folgendes Zeichen wurde empfangen: ");
127
              uart_putc ((unsigned char)Zeichen);
128
           }
129
  } // Ende while
130
}

Jetzt kommt in der Tat noch das Problem mit dem Tastenentprellen :-(
Am liebsten würde ich gerne in die Interruptroutine eine Pause einbauen 
mit _delay_ms (100); Inzwischen bin ich mir aber nicht so sicher, ob das 
mit der delay-Funktion klappt, da sie ja auch wieder bestimmt Interrupts 
aufruft, die delay-Funktion muss ja irgendwie den Timer nutzen, und die 
Timer-Interrupts kommen auch erst nach dem INT0-Interrupt. Würde es was 
helfen, das Interrupt-Flag in EIMSK abzuschalten, damit notfalls andere 
Interrupts funktionieren, sich nur nicht dann als im Kreis EIMSK 
aufrufen kann?

von Anfänger (Gast)


Lesenswert?

Aber es kann doch manchmal auch "unpraktisch" sein, eine Aufgabe durch 
einen Interrupt zu erledigen?

Könnte man vielleicht sagen, INT0 und INT1 sind "nur" dann geeignet, 
wenn das zu zählende Signal ein "sauberer" Pegel ohne Geschwinge durch 
Prellerei ist? Weil sonst hat man echt das Problem, dass man in sich 
verschachtelte Interrupts hat?

von katastrophenheinz (Gast)


Lesenswert?

Hi,

die Bezeichner sind natürlich dir überlassen.
ATOMIC_BLOCK ist an diesen Stellen zwar ok, aber unökonomisch, weil du 
ja sicher weißt, das beim reinlaufen in diesen Block das I-Flag gesetzt 
ist.
Von daher geht auch cli()..sei(). ATOMIC_Block ist aber nicht falsch.
delay_ms vernwendet eine dumpfe tue-nichts-schleife. solltest du aber 
nicht machen, denn wenn du delay_ms in interrupts aufrufst, dann sind 
natürlich auch solange die interrupts gesperrt.

Warten macht man üblicherweise mit Timern. D.h. nimm einen Timer, lass 
denn im free-Running mode mit einer bestimmten Frequenz laufen, sagen 
wir 1kHz, d.h. 1 ms pro tick. wenn du jetzt 10 ms warten willst dann 
geht das so: OCR auf TCNT+10 setzen und OCR-Match Interrupts zulassen. 
Der entsprechende Interrupt tritt dann nach 10 ms auf. Das wars.

dazu gibts bestimmt was im AVR-Tutorial und guck dir an, wie andere das 
gemacht haben. Timer sind nicht ganz so einfach, weil tonnenweise 
einstellmöglichkeiten, aber auch kein hexenwerk. kriegst du schon hin

eins noch: warum lässt du deinen atmega nicht mit höhere Fre. rennen?
Hat den vorteil, daß auch höhere Baudraten mit geringen baudratenfehlern 
möglich sind. 2400 ist ja Fernmeldetechnik von 1968...

von katastrophenheinz (Gast)


Lesenswert?

Anfänger schrieb:
> Aber es kann doch manchmal auch "unpraktisch" sein, eine Aufgabe durch
>
> einen Interrupt zu erledigen?
>
>
>
> Könnte man vielleicht sagen, INT0 und INT1 sind "nur" dann geeignet,
>
> wenn das zu zählende Signal ein "sauberer" Pegel ohne Geschwinge durch
>
> Prellerei ist? Weil sonst hat man echt das Problem, dass man in sich
>
> verschachtelte Interrupts hat?

nein, genau das Entprellen macht man üblicherweise mit Timern.
D.h. wenn die erste Pegeländerung auftritt x ms warten und wenn der 
Pegel dann immer noch anliegt, dann hast du einen stabilen Zustand. 
Dabei erfolgt nur die erkennung der ersten Pegeländerung im Interrupt. 
Danach PinChange-Interrupt deaktivieren, timer starten und nach Ablauf 
des Timers im Hauptprogramm den Input-Port abfragen und dann wieder 
PinChange-interrupts zulassen.

von Anfänger (Gast)


Lesenswert?

katastrophenheinz schrieb:
> eins noch: warum lässt du deinen atmega nicht mit höhere Fre. rennen?

ich hab angst, beim Setzen dieser Fuse-Bits einen Fehler zu machen, um 
einen externen Oszillator anzuschließen, dies wollte ich machen, wenn 
ich etwas mehr Erfahrung habe.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> Kleiner Hinweis.
>
> Wenn du in C auf ein Wort stösst, welches komplett in Grossbuchstaben
> geschrieben ist, dann handelt es sich der üblichen Konvention nach immer
> um ein Makro. Und auch umgekehrt: wenn etwas als Makro implementiert
> ist, dann wird es derselben Konvention nach immer komplett in
> Grossbuchstaben geschriebem.
> Alle(!) Prozessorregister sind per Makro in C verfügbar gemacht.
>
> d.h. wenn etwas, wie 'flags', nicht in Grossbuchstaben geschrieben ist,
> dann handelt es sich dabei mit 99.9% Wahrscheinlichkeit, nicht um ein
> Prozessorregister.
>
> Umgekehrt, wenn etwas wie PORTC komplett in Grossbuchstaben geschrieben
> ist, dann ist das auf jeden Fall erst mal ein Makro.

Ein paar Beispiele:

- BYTE aus den M$-Headern (ein typedef, manchmal vielleicht auch nicht)
- pgm_read_byte aus der AVR-Libc (ein Makro)

von katastrophenheinz (Gast)


Lesenswert?

Welchen programierer verwendest du?
du musst nur bei ckdiv8 den haken wegmachen.
alles andere bleibt unverändert.
Aber deine Entscheidung, das nicht zu tun ist auch nachvollziebar.

von Anfänger (Gast)


Lesenswert?

katastrophenheinz schrieb:
> Welchen programierer verwendest du?

den AVRIPS mk II.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.