Hi,
Ich habe ein Programm für den ATMEGa8 und ich versuche es nach zu
vollziehen.
zum Beispiel:
outb(MCUCR,(inp(MCUCR) | BV(SE)) & (~BV(SM0) | ~BV(SM1) | ~BV(SM2)));
Im Endeffekt bedeutet diese Befehlzeile ddoch ass SE=1,SM0=0,SM1=0,SM2=0
wird (Sleepmode=IDLE). BV() und ~BV() schreibt dabei das Bit=1 bzw. 0.
Wie funktioniert der bitweise Oder-Operator"|" diesem Zusammenhang und
warum wird hier MCUCR erst gelesen?
Und noch was anderes:
DAs:
DDRD=0x07
DDRB=0xc2 (Datenrichtungsregister)
PORTD=0xff
PORTB=0xff
adressiert die POrts mit bestimmten Adressen. Kann diese Adressierung
was mit dem Timer zu tun haben, das wenn er überläuft (255=0xff)sollen
bei den Ports mit dem Programm weitergemacht werden, oder welche
Bedeutung kann diese Adressierung haben?
Hat einer nen Plan?
mfg
> outb(MCUCR,(inp(MCUCR) | BV(SE)) & (~BV(SM0) | ~BV(SM1) | ~BV(SM2)));> Im Endeffekt bedeutet diese Befehlzeile ddoch ass SE=1,SM0=0,SM1=0,SM2=0
Mit dem ersten Teil hast du recht (SE=1)
Der zweite Teil dürfte aber ein Denkfehler vom originalen Autor
sein. Schaun wir mal warum:
outp und inp sind alte Schreibweisen. Mit heutigem gcc würde
man schreiben
MCUCR |= (1 << SE);
MCUCR &= ~(1 << SMO) | ~( 1 << SM1 ) | ~( 1 << SM2 );
Das setzen des SE Bits ist in Ordnung. Nur das Löschen
geht so nicht.
Zuallererst: In C arbeitet man mit Bytes. Auch die sog.
Bitoperationen arbeiten immer mindestens mit Bytes.
Wie löscht man ein bestimmtes Bit, sagen wir mal das SM0
Bit?
Angenommen MCUCR enthällt den Wert 0b11111111. Also alle
Bits gesetzt. Ich weiss jetzt nicht auswendig welches Bit
das Bit SM0 ist, ich nehme einfach mal an es wäre das Bit 3.
Um also Bit 3 zu löschen, braucht man eine Maske, so dass
beim UND-en des Bytes mit dieser Maske alle Bits bis auf
Bit3 unangetastet bleiben und Bit 3 mit Sicherheit auf
0 gesetzt wird. Die Maske muss dabei an allen Stellen
bis auf Bit 3 eine 1 aufweisen, nur Bit 3 muss 0 sein.
0b11111111 original Wert
0b11110111 Maske
UND ----------
0b11110111
(Du wendest die Bit-Verknüpfung UND einfach auf jeweils
übereinanderstehende Bits an. Es gibt keinen Übertrag oder
sonst was. Einfach nur: 0 UND 0 gibt 0; 0 UND 1 gibt 0;
1 UND 0 gibt 0; 1 UND 1 gibt 1)
Zur Kontrolle: Der Ausgangswert sei 0b01101001
0b01101001 original
0b11110111 Maske
UND ----------
0b01100001
Passt.
Also das geht.
Wie kommt man nun zu der Maske: SM0 hat den Wert 3.
Also nehmen wir eine binäre 1 und schieben sie 3 mal
nach links.
1 << SM0
macht genau das. Binär sieht das Ergebnis dann so aus:
0b00001000
Das sieht schon ganz gut aus. Allerdings müssen jetzt alle
Bits invertiert werden. Aus 0 muss 1 werden, aus 1 muss 0
werden. Das ist der Job von ~
~0b00001000 ergibt 0b11110111
Und das ist dann genau die Maske die wir brauchen.
Soweit so gut. Aber warum klappt dann das trotzdem nicht?
MCUCR &= ~(1 << SMO) | ~( 1 << SM1 ) | ~( 1 << SM2 );
Weil da nicht eine Maske benutzt wird, sondern deren 3. Und
die einzelnen Masken werden bitweise ODER verknüpft.
Probieren wir mal:
SM0 sei Bit 3, SM1 sei Bit 4, SM2 sei BIT 5
Die Maske die wir im Endeffekt für den UND haben wollen
muss also so aussehen:
0b11000111
Diese Maske, zusammen mit der UND Verknüpfung würde Bit 3, 4 und
5 auf 0 setzen.
~( 1 << SM0 ) = 0b11110111
~( 1 << SM1 ) = 0b11101111
ODER ----------
0b11111111
~( 1 << SM2 ) = 0b11011111
ODER ----------
0b11111111
Nicht ganz was wir wollen.
Wo liegt der Denkfehler?
Man kann schon ODER benutzen. Was wir aber verodern wollen,
ist was anderes. Wir wollen
( 1 << SM0 ) = 0b00001000
( 1 << SM1 ) = 0b00010000
ODER ----------
0b00011000
( 1 << SM2 ) = 0b00100000
ODER ----------
0b00111000
Und erst diese Ergebnis wird negiert: ~0b00111000 -> 0b11000111
Und das ist dann die Maske die wir haben wollen
Also muss letzteres lauten:
MCUCR &= ~( (1 << SMO) | ( 1 << SM1 ) | ( 1 << SM2 ) );
Der Autor hat wahrscheinlich auf die De'Morgan Regel vergessen
und ganz einfach die Klammern wie bei einer Multiplikation
aufgelöst. Das geht aber nicht, denn nach De'Morgan gilt
~( A | B ) <==> ~A & ~B
Man könnte obiges also auch so schreiben:
MCUCR &= ~(1 << SMO) & ~( 1 << SM1 ) & ~( 1 << SM2 ) );
Machen wir wieder die Probe:
~( 1 << SM0 ) = 0b11110111
~( 1 << SM1 ) = 0b11101111
UND ----------
0b11100111
~( 1 << SM2 ) = 0b11011111
UND ----------
0b11000111
Auch das liefert die richtige Maske.
Bei den Logikoperationen UND und ODER immer vorsichtig sein.
Wir verwenden diese Operationen im täglichen Leben nämlich
meistens falsch.
> Wie funktioniert der bitweise Oder-Operator"|" diesem Zusammenhang und> warum wird hier MCUCR erst gelesen?
Wie ODER funktioniert sollte jetzt klar sein
0 ODER 0 ergibt 0; 0 ODER 1 ergibt 1;
1 ODER 0 ergibt 1; 1 ODER 1 ergibt 1
> warum wird hier MCUCR erst gelesen?
Weil ja die restlichen Bits, also alle ausser SE, SM0, SM1
und SM2 so bleiben sollen wie sie gerade sind.
> DAs:>> DDRD=0x07> DDRB=0xc2 (Datenrichtungsregister)> PORTD=0xff> PORTB=0xff>> adressiert die POrts mit bestimmten Adressen. Kann diese Adressierung> was mit dem Timer zu tun haben, das wenn er überläuft (255=0xff)sollen> bei den Ports mit dem Programm weitergemacht werden, oder welche> Bedeutung kann diese Adressierung haben?
Nein. Kann es nicht.
Das setzt einfach nur die Datenrichtung (also Eingang oder
Ausgang) für die Ports D und B.
DDRD = 0x07;
ist dasselbe wie:
DDRD = 0b00000111;
Hier wird also vereinbart, dass die Bits 0, 1, 2 als Ausgang
benutzt werden und alle anderen Bits als Eingang
Das nachfolgende
PORTD = 0xff;
ist wieder äquivalent zu
PORTD = 0b11111111;
und hier gibt es einen kleinen Unterschied, je nachdem ob
ein Bit als Eingang oder als Ausgang definiert wurde.
Ist ein Bit als Ausgang, hier also die Bits 0, 1 und 2.
so setzten die entsprechenden Bits beim PORT setzen, direkt
den Ausgang am Chip. Hier werden also diese 3 Bits durch
PORTD = 0b.....111;
auf 1 gesetzt.
Ist ein Port Pin auf Eingang geschaltet, so wird mit der
zugehörigen Ausgabe auf PORT der zum PIN zugehörige
Pull-Up Widerstand eingeschaltet bzw. ausgeschaltet
PORTD = 0b11111...;
schaltet also für die Bits 3, 4, 5, 6, 7 den PullUp Widerstand
ein.
Alles zusammen;
DDRD = 0b00000111; // 0,1,2: Ausgang; 3,4,5,6,7: Eingang
PORTD = 0b11111111; // 0,1,2: auf 1 setzen
// 3,4,5,6,7: Pullup einschalten
Wow, du hast es drauf. Besser kann man es einfach nicht erklären. Nun
habe ich noch ein Problem mit dem Programmablauf.
Zunächst startet das Programm mit der Mainmode, danach werden die
Interupts nach Priorität abgearbeitet.
//////////
Programm Pseudocode:
1.Interrupt: ... enable ADC interrupts
2.Interrupt ... wähle der Reihe nach die Kanäle für ADC: wandele sie um
3.Interrupt ... sende zu UART
Dann folgt die main mit:
main
{
1. Ports einstellen (die von oben)
2. Sleepmode=IDLE
3. ADC einstellen und anschalten
4. UART einstellen
5. Timer einstellen und starten
dann werden die Interrupt noch mit
outb(TIMSK, 1 << TOIE0)
enabled. Dann wird noch GIE gesetzt und danach folgt:
while(1)
{
sleep
}
}
////////////
Das Hauptprogramm geht in IDLE mode Endlosschleife.
Mit outb(TIMSK, 1 << TOIE0) setzt das TOIE0 Bit ; also wenn der Timer
überläuft wird das Hauptprogramm unterbrochen und es wird die
Interuptroutine höchster Priorität eingeleitet.
1.Befindet sich das Hauptprogramm zu diesem Zeitpunkt bereits in
SLEEPmode?
2.Woher weiss dass Programm welcher Interrupt nach Overflow eingeleitet
werden soll und welcher als nächstes?
3.WEchselt das Programm zwischen den Interrupts nochmal zurück ins
Hauptprogramm?
mfg
> Mit outb(TIMSK, 1 << TOIE0) setzt das TOIE0 Bit ; also wenn der Timer> überläuft wird das Hauptprogramm unterbrochen und es wird die> Interuptroutine höchster Priorität eingeleitet.>> 1.Befindet sich das Hauptprogramm zu diesem Zeitpunkt bereits in> SLEEPmode?
Kann sein muss nicht sein. Das setzen des TOIE0 Bits
gibt nur die Behandlung des Timer-Overflows durch eine
Interrupt Funktion frei. Mehr nicht. Wann auch immer
dieser Overflow erfolgt wird die entsprechende Funktion
audgeführt. Es ist dabei unerheblich ob der µC dabei
gerade schläft oder nicht. Wenn er schläft, wird er
aufgeweckt und dir Overflow Funktion ausgeführt. Wenn
er nicht schläft, wird die Funktion auch ausggeführt.
Ob der µC also bereits schläft oder nicht, hängt davon ab
wie lange der Timer gebraucht hat um in den SleepMode zu gelangen.
> 2.Woher weiss dass Programm welcher Interrupt nach Overflow#> eingeleitet werden soll und welcher als nächstes?
Die Hardware weiss ja welches Ereignis zum Interrupt geführt
hat. Am Anfang des Programmes steht, für dich unsichtbar,
eine Tabelle, in der für jedes mögliche Ereignis eingetragen
wurde, welche Funktion dafür zuständig ist. Das macht der
Compiler für dich. Wenn du eine Interrupt Service Funktion (ISR)
schreibst, dann trägt der Compiler sie dort ein. Daher
schauen ISR auf C Ebene ein klein wenig anders aus als normale
Funktionen.
Welcher als nächster. Die Regelung ist ganz einfach: Es kann
immer nur eine ISR abgearbeitet werden. Tritt während der
ISR ein neuer Interrupt auf, so wird mit der Behandlung dieses
Interrupt gewartet bis der vorhergehende fertig ist. ISR
können sich also gegenseitig nicht unterbrechen (stimmt
nicht ganz). Wenn mehrere Interrupts liegen geblieben sind
dann bestimmt die Reihung in der erwähnten Tabelle
welcher zuerst abgearbeitet wird.
> 3.WEchselt das Programm zwischen den Interrupts nochmal zurück ins> Hauptprogramm?
Da bin ich mir nicht ganz sicher. Ich denke aber schon.
>> 3.WEchselt das Programm zwischen den Interrupts nochmal zurück ins>> Hauptprogramm?> Da bin ich mir nicht ganz sicher. Ich denke aber schon.
Bei den AVRs ist es grundsätzlich so, dass, wenn mehrere
Interrupt-Ereignisse gleichzeitig anstehen, nach der Abarbeitung einer
Interrupt-Routine wieder zurück ins Hauptprogramm gesprungen und dort
mindestens ein Befehl ausgeführt wird, bevor der nächste Interrupt (also
der mit der nächst-niederen "Priorität") bearbeitet wird.
>Wenn er schläft, wird er>aufgeweckt und dir Overflow Funktion ausgeführt.
In IDLemode wird nur die CPU angehalten. ISR, ADC, USART bleiben aktiv.
1. Angenommen der MCU befindet sich bereits in IDLEmode und dann läuft
der timer ab,dann kann doch der Interrupt ausgeführt werden, wobei die
CPU angehalten bleibt?
2. Wie übersetzt man 0x07,0xf2 in die Byteschreibweise 0b0000000?
3. Diese altmodischen ISRs werden verwendet:
SIGNAL(SIG_OVERFLOW0)
SIGNAL(SIG_ADC)
SIGNAL(SIG_UART_DATA)
Ist es der Fall, wenn das Timer enable bit ( auch GIE) gestetzt wurde
und der Timer läuft über, dann wechselt das Hauptprogramm hwm
(hardwaremäßig) zu SIGNAL(SIG_OVERFLOW0) und wenn in
SIGNAL(SIG_OVERFLOW0) das ADC enable bit gesetzt wird, wechselt das
Programm nach SIGNAL(SIG_OVERFLOW0) zurück
in die Mainmode, dort wird ein Befehl ausgeführt und dann wird nach
SIGNAL(SIG_ADC) gewechselt (hwm), usw.
Haben diese SIGNALS(...) auch was mit INT0 bzw. INT1 von ISCx in MCUCR
zu tun?
mfg