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
intZaehler=0;
7
8
ISR(INT0)
9
{
10
Zaehler++;
11
}// ende interrupt-routine
12
13
intmain(void)
14
{
15
16
// steigende Flanken an INT0 sollen gezaehlt werden
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.
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
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.
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.
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.
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.
@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)
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.
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).
>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!!!
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.
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
inti;
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 :-(
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.
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:
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.
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...
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
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
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.
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.
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.
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?
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.:
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.
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.
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!
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.
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.
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.
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.
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
staticuint8_ta=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!
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((unsignedchar)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.
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.
... 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
...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
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.
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((unsignedchar)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?
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?
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...
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.
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.
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)
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.