Forum: Mikrocontroller und Digitale Elektronik Problem mit Programm


von Mico (Gast)


Lesenswert?

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

von Mico (Gast)


Lesenswert?

natürlich vorausgesetzt GIE in SREG und die anderen ENABLE bits sind 
gleich 1.

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von Karl H. (kbuchegg)


Lesenswert?

> 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.




von Karl H. (kbuchegg)


Lesenswert?

> 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

von Mico (Gast)


Lesenswert?

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



von Karl H. (kbuchegg)


Lesenswert?

> 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.



von johnny.m (Gast)


Lesenswert?

>> 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.

von Mico (Gast)


Lesenswert?

>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

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.