Forum: Mikrocontroller und Digitale Elektronik ATmega88: Ports, PINs und DDR


von Christian N. (themoar)


Angehängte Dateien:

Lesenswert?

Servus,

erstmal ein paar Worte zu mir. Ich hab bereits ein wenig mit C 
"gearbeitet", d.h. ich habe es mir soweit angeeignet, dass ich das ein 
oder andere Programm schreiben kann und die Grundlagen recht sicher 
beherrsche. Nun möchte ich gern einen Schritt weiter gehen und C in 
Verbindung mit der Mikrocontroller-Programmierung weiterlernen. Dazu hab 
ich mir von Franzis das Lernpaket "Mikrocontroller in C programmieren" 
besorgt bzw. schenken lassen. Es mag zwar nicht das nonplusultra sein, 
aber ich denke für den Anfang kann man damit arbeiten.

Mein Stand sieht jetzt so aus, dass ich alle notwendigen Programme 
laufen habe (FT_Prog, Atmelstudio 6, HTerm und 8 Bit AVR-Bootloader) und 
in der Lage bin, Programme zu schreiben und auf dem µC zu bringen. Die 
ersten Schritte sind also gemacht.

Ein paar Sachen bereiten mir aber Kopfzerbrechen. In dem dazugehörigen 
Handbuch steht:

>Die Bezeichnung Pxn steht für:
>P = I/O-Bezeichnung PORT oder PIN
>X = Port-Name wie A, B, C, D
>N = Portbit wie 0, 1, 2, 3, 4, 5, 6, 7

Weiterhin steht im Text:

>Da unser ATmega88 ein Acht-Bit-Controller ist, haben die Port-Register eine 
>Breite von acht Bit. [...] Der ATmega88 besitzt die Ports PortA, PortB, PortC und 
>PortD, die man frei programmieren kann.

Wenn ich das Datenblatt richtig verstanden habe, gibt es keinen PortA, 
oder? Und PortC hat auch "nur" 7 Bit?

>Jeder einzelne I/O wird über die folgenden Register mit den Namen DDR, PORT 
>und PIN konfiguriert

Hier hab ich mein größtes Verständnisproblem. Was DDR bedeutet, glaube 
ich zu wissen. Damit sage ich doch nur, ob ein Beinchen am µC Eingang 
oder Ausgang ist, oder?

Wenn ich Beinchen #5 (PB6) als Ausgang konfigurieren möchte, würde das 
in C so aussehen:
1
DDRB |= (1<<6);
bzw. als Eingang
1
DDRB |= (0<<6);

Anschließend kann ich, so wie ich es verstanden habe, mit PORTB den 
Ausgang an Beinchen #5 (PB6) auf High oder Low setzen. Ist das dann der 
Ausgangszustand? Wenn die PB6 als Eingang konfiguriert wurde, kann ich 
mit PORTB die internen Pull-up-Widerstände aktivieren? Geht das NUR bei 
Eingängen?

PB6 High
1
PORTB |= (1<<6);
PB6 Low
1
PORTB |= (0<<6);

Und zu guter Letzt PIN. Damit kann ich schauen, welchen Zustand ein 
Eingang hat, also ob High oder Low?

Ich nehme mal den Code aus dem AVR-GCC-Tutorial 
(http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Zugriff_auf_IO-Ports)
1
#include <avr/io.h>
2
...
3
/* Fuehre Aktion aus, wenn Bit Nr. 1 (das "zweite" Bit) in PINC gesetzt (1) ist */
4
if ( PINC & (1<<PINC1) ) {
5
  /* Aktion */
6
}

Wenn ich von meinem Beispiel ausgehen würde, müsste ich PB6 als Eingang 
definieren und würde dann die if-Bedingung erfüllen, wenn ich einen 
High-Pegel an diesem Eingang hätte?

also im Grunde so
1
if(PINB & (1<<6))
2
{
3
//Aktion
4
}

heißt das soviel wie if(PB6==1)?


Ich hoffe diese Probleme sind nicht zu trivial um darauf zu antworten, 
aber ich bin jetzt seit gerade einmal 24h dabei und wäre froh über jede 
hilfreiche Antwort :)

von Oliver (Gast)


Lesenswert?

Christian N. schrieb:
> ...

Anscheinend hast du das Tutorial ja schon gefunden. Lies es aber 
trotzdem nochmals durch. Eigentlich steht da alles dazu drin.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Zugriff_auf_IO-Ports

http://www.mikrocontroller.net/articles/Bitmanipulation

Und ja, eine 1 im entsprechenden Register entspricht einem "high" am 
Hardware-Pin.

Oliver

von M. N. (Gast)


Lesenswert?

Christian N. schrieb:
> PB6 Low
> PORTB |= (0<<6);

Gelöscht wird das Bit mit: PORTB &= ~(1<<6);,
was man auch so formuliern könnte: PORTB &= 0xbf;
Ansonsten hast Du schon eine Menge verstanden.

Der Ausgangszustand der Register nach einem Reset (Programmstart) ist im 
Datenblatt beschrieben.
Bei AVRs mit wenigen Anschlüssen, entfallen einzelne Portpin oder auch 
ganze Ports. So ist das Leben!

von reieg99 (Gast)


Lesenswert?

Hallo Christian,

Christian N. schrieb:
>
> Wenn ich das Datenblatt richtig verstanden habe, gibt es keinen PortA,
> oder? Und PortC hat auch "nur" 7 Bit?

stimmt so


> Damit sage ich doch nur, ob ein Beinchen am µC Eingang
> oder Ausgang ist, oder?

ja, korrekt


> Wenn ich Beinchen #5 (PB6) als Ausgang konfigurieren möchte, würde das
> in C so aussehen:
> DDRB |= (1<<6);
ja


<bzw. als EingangDDRB |= (0<<6);

nein => DDRB &= ~(1<<6);

> Anschließend kann ich, so wie ich es verstanden habe, mit PORTB den
> Ausgang an Beinchen #5 (PB6) auf High oder Low setzen. Ist das dann der
> Ausgangszustand?

ja

> Wenn die PB6 als Eingang konfiguriert wurde, kann ich
> mit PORTB die internen Pull-up-Widerstände aktivieren?
ja


> Geht das NUR bei
> Eingängen?

ja

>
> PB6 HighPORTB |= (1<<6);PB6 LowPORTB |= (0<<6);
siehe oben   => &= ~(1<<6)  // für low

> Und zu guter Letzt PIN. Damit kann ich schauen, welchen Zustand ein
> Eingang hat, also ob High oder Low?

ja

>
>
> also im Grunde soif(PINB & (1<<6))
> {
> //Aktion
> }
> heißt das soviel wie if(PB6==1)?

im Prinzip ja, aber bitte nicht so schreiben :-)


Bei so vielen Frage habe ich mal die Kurzform gewählt :-)

Gruß Andi

von Karl H. (kbuchegg)


Lesenswert?

Christian N. schrieb:

>>Da unser ATmega88 ein Acht-Bit-Controller ist, haben die Port-Register eine
>>Breite von acht Bit. [...] Der ATmega88 besitzt die Ports PortA, PortB, PortC 
und
>>PortD, die man frei programmieren kann.
>
> Wenn ich das Datenblatt richtig verstanden habe, gibt es keinen PortA,

Richtig.

> oder? Und PortC hat auch "nur" 7 Bit?

Er hat schon 8. Aber nur 7 davon sind herausgeführt.


> Hier hab ich mein größtes Verständnisproblem. Was DDR bedeutet, glaube
> ich zu wissen. Damit sage ich doch nur, ob ein Beinchen am µC Eingang
> oder Ausgang ist, oder?

Richtig

> Wenn ich Beinchen #5 (PB6)

Bitte. Wenn du C programmieren willst, dann gewöhn dich daran, dass wir 
bei 0 anfangen zu zählen. WEnn du hier deine eigene Zählweise, die bei 1 
beginnt, durchdrücken willst, dann gibt das nur Chaos.

> als Ausgang konfigurieren möchte, würde das
> in C so aussehen:
>
>
1
DDRB |= (1<<6);
> bzw. als Eingang
>
1
DDRB |= (0<<6);

Nein.
Sagtest du nicht, du wärst in C schon ein wenig sattelfest.
Eine 0 kannst du nach links schieben, sooft du willst. auch 6 mal. An 
der 0 ändert sich dadurch nichts. Denn 0 mal irgendeine Zahl ergibt 
wieder 0.

Was passiert denn hier
1
1 << 6

Es wird eine 1 gebildet. Hier mal in Bitschreibweise aufgeschrieben
00000001
und dieses Bitmuster wird jetzt 6 mal jewweils 1 Bitposition nach links 
verschoben

00000001    Ausgansgssituation
00000010    1 mal
00000100    2 mal
00001000    3 mal
00010000    4 mal
00100000    5 mal
01000000    6 mal

Voila. Ergebnis ist ein Byte, welches an der Bitposition 6 ein 1-Bit hat 
und alle anderen Bits sind 0.

Jetzt das ganze mit einer 0, binär als 00000000 geschrieben
00000000   Ausgansgsituation
00000000   1 mal
00000000   2 mal
00000000   3 mal
00000000   4 mal
00000000   5 mal
00000000   6 mal

Ähm. Das Ergebnis ist dasselbe Bitmuster wie das mit dem gestartet 
wurde.
Ob du daher
1
DDRB |= (0<<6);
schreibst, oder gleich einfach
1
DDRB |= 0;
oder, weil das verodern mit 0 einen 0-Operation ist, die nichts 
verändert, du das ganze ganz einfach weg lässt, schenkt sich also 
nichts. Ist alles dasselbe

Bit setzen
   register |= ( 1 << Bitposition );

Bit löschen
   register &= ~( 1 << Bitposition );


> Anschließend kann ich, so wie ich es verstanden habe, mit PORTB den
> Ausgang an Beinchen #5 (PB6) auf High oder Low setzen.

Korrekt

> Ist das dann der
> Ausgangszustand?

Ja.
Wenn du mit einem Voltmeter am Pin (bitte, wir sagen Pin und nicht 
Beinchen) nachmisst, dann misst du dort 0V oder 5V, je nachdem ob du das 
Bit im Portregister auf 0 oder auf 1 gesetzt hast.

> Wenn die PB6 als Eingang konfiguriert wurde, kann ich
> mit PORTB die internen Pull-up-Widerstände aktivieren?

Ganz genau

> Geht das NUR bei
> Eingängen?

Logisch. Pullup machen nur bei Eingängen sind. Ein Pullup 'zieht' den 
Pin auf 1, wenn er nicht angesteuert wird. WEnn du selber aber den Pin 
als Ausgang einstellst, dann steuerst du ihn ja an.


> Und zu guter Letzt PIN. Damit kann ich schauen, welchen Zustand ein
> Eingang hat, also ob High oder Low?

Ganz genau


>
1
#include <avr/io.h>
2
> ...
3
> /* Fuehre Aktion aus, wenn Bit Nr. 1 (das "zweite" Bit) in PINC gesetzt 
4
> (1) ist */
5
> if ( PINC & (1<<PINC1) ) {
6
>   /* Aktion */
7
> }
>
> Wenn ich von meinem Beispiel ausgehen würde, müsste ich PB6 als Eingang
> definieren und würde dann die if-Bedingung erfüllen, wenn ich einen
> High-Pegel an diesem Eingang hätte?

Na ja, komm.
Genau das steht doch im Kommentar sowieso schon dort.
Und im Zweifelsfall geht (auf dieser Ebene) nichts über: den µC 
verkabeln und ausprobieren. Der µC sagt dir dann schon, ob deine 
Gedankengänge richtig waren. Ein bischen Grundlagen muss man natürlich 
können, das ist schon klar. Aber wenn du die mal hast, dann ist der µC 
dein bester, weil gnadenlosester, Lehrmeister um das Gelernte zu 
vertiefen.

>
1
if(PINB & (1<<6))
2
> {
3
> //Aktion
4
> }
>
> heißt das soviel wie if(PB6==1)?

Nein.
PB6 ist einfach nur ein anderes Wort für 6. Da würde dann if( 6 == 1 ) 
stehen. UNd das ist ja dann weniger sinnvoll.

von Christian N. (themoar)


Lesenswert?

Oliver schrieb:
>
> Anscheinend hast du das Tutorial ja schon gefunden. Lies es aber
> trotzdem nochmals durch. Eigentlich steht da alles dazu drin.
>
> http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Zugriff_auf_IO-Ports
>
> http://www.mikrocontroller.net/articles/Bitmanipulation
>
> Und ja, eine 1 im entsprechenden Register entspricht einem "high" am
> Hardware-Pin.
>
> Oliver

Ich glaube, wenn man mir etwas nicht vorwerfen kann, dann, dass ich 
nicht gelesen habe. Aber trotzdem danke.


> im Prinzip ja, aber bitte nicht so schreiben :-)
>
> Bei so vielen Frage habe ich mal die Kurzform gewählt :-)
>
> Gruß Andi

Die Kurzform war vollkommen in Ordnung. Es waren ja im Grunde nur 
Verständnisfragen für die ein Ja/Nein reicht.

DDRB |= (0<<6);
DDRB &= ~(1<<6);

Das ist das einzige, was ich nicht verstehe. Beim ersten schiebe ich 
doch eine Null sechs Stellen nach links, oder nicht? Beim zweiten 
schiebe ich eine Eins sechs Stellen nach links und negiere. Ist die 
erste Variante denn nur von der Syntax falsch oder verstehe ich da 
grundlegend etwas falsch? Ich tu mich etwas schwer mit den logischen 
Operatoren |= und &=, damit hab noch nie was zu tun gehabt. Die 
Bitmanipulation 
(http://www.mikrocontroller.net/articles/Bitmanipulation) verstehe ich 
nicht so richtig.

M. N. schrieb:
> Der Ausgangszustand der Register nach einem Reset (Programmstart) ist im
> Datenblatt beschrieben.
> Bei AVRs mit wenigen Anschlüssen, entfallen einzelne Portpin oder auch
> ganze Ports. So ist das Leben!

Ich hätte gedacht, das, wenn ich einen Pin als Ausgang definiere und ihn 
meinetwegen mittels PortX auf High setze, dieser High-Pegel auch nach 
einem Reset vorhanden ist.

Es gibt also auch ATmega88 mit PortA?

Karl Heinz Buchegger schrieb:
> Sagtest du nicht, du wärst in C schon ein wenig sattelfest.
> Eine 0 kannst du nach links schieben, sooft du willst. auch 6 mal. An
> der 0 ändert sich dadurch nichts. Denn 0 mal irgendeine Zahl ergibt
> wieder 0.

Wie gesagt, die Grundlagen (logische Ablaufstrukturen etc.) sitzen 
eigentlich ganz gut. Aber zusammengesetzte Zuweisungen wie &= hab ich 
bisher noch nicht benötigt bzw. zu Gesicht bekommen. Aber dein Beispiel 
ist sehr gut und hilft mir da weiter. Ich war gerade noch dabei

>(Bsp: a<<b ist das gleiche wie a * 2^b; bzw. bei 1<<3 wird die 1 um drei Stellen 
>nach links geschoben)

mit dem Taschenrechner nachzuvollziehen. Hat auch ganz gut geklappt und 
zum Verständnis beigetragen.

Karl Heinz Buchegger schrieb:
> (bitte, wir sagen Pin und nicht
> Beinchen)

Ich bin immer durcheinander gekommen, das "Beinchen" war nur beim 
Schreiben eine Unterscheidungshilfe.

Ich denke mal, mit diesen Antworten kann ich erstmal weitermachen. Ich 
bin erstaunt, wie umfangreich und schnell geantwortet wurde. Jedesmal 
wenn ich auf "Vorschau" geklickt habe, stand bereits der nächste Beitrag 
für mich bereit. Ganz großen Lob und vielen Dank für die Hilfe :)

von Karl H. (kbuchegg)


Lesenswert?

Christian N. schrieb:
>
> DDRB |= (0<<6);
> DDRB &= ~(1<<6);
>
> Das ist das einzige, was ich nicht verstehe.

Welchen Teil von
1
Bit setzen
2
    register |= ( 1 << Bitposition );
3
Bit löschen
4
    register &= ~( 1 << Bitposition );
hast du nicht verstanden.

> Beim ersten schiebe ich
> doch eine Null sechs Stellen nach links, oder nicht? Beim zweiten
> schiebe ich eine Eins sechs Stellen nach links und negiere. Ist die
> erste Variante denn nur von der Syntax falsch oder verstehe ich da
> grundlegend etwas falsch?

grundlegend.

> Ich tu mich etwas schwer mit den logischen
> Operatoren |= und &=,

das wundert mich nicht. Denn das sind keine logischen Operatoren sonder 
bitweise-Operatoren.

Du musst unterscheiden zwischen den
logischen Operationen &&, || und !, die auf Wahrheitswerten operieren
und den
binären Operatoren &, |, ^ und ~, die auf Bitebene operieren.

> damit hab noch nie was zu tun gehabt. Die
> Bitmanipulation
> (http://www.mikrocontroller.net/articles/Bitmanipulation) verstehe ich
> nicht so richtig.


Oder.  Gegeben zwei Bits a und b
1
    a     b    Ergebnis
2
  ----------------------
3
    0     0   ->  0
4
    1     0   ->  1
5
    0     1   ->  1
6
    1     1   ->  1
Das Ergebnis ist 1, wenn entweder Bit a ODER Bit b oder beide auf 1 
sind.

Und.   Gegeben zwei Bits a und b
1
    a     b    Ergebnis
2
  ----------------------
3
    0     0   ->  0
4
    1     0   ->  0
5
    0     1   ->  0
6
    1     1   ->  1
Das Ergebnis ist 1, wenn Bit a UND Bit b auf 1 sind.


Hast du 2 8-Bit Werte, sagen wir mal 9 und 3 mit den Bitdarstellungen
1
      00001001       (dezimale 9)
2
      00000011       (dezimale 3)
dann werden die beiden binär verundet, also  8 & 3
indem einfach auf jede Spalte die obige UNd-Operation angewendet wird
1
      00001001
2
      00000011
3
             ^
4
             |
5
             Und Operation.  1 UND 1 ergibt 1    .......1
6
7
      00001001
8
      00000011
9
            ^
10
            |
11
            Und Operation.   0 UND 1 ergibt 0    ......01
12
13
      00001001
14
      00000011
15
           ^
16
           |
17
           Und Operation.    0 UND 0 ergibt 0    .....001
18
19
      00001001
20
      00000011
21
          ^
22
          |
23
          Und Operation.     1 UND 0 ergibt 0    ....0001
24
25
      00001001
26
      00000011
27
         ^
28
         |
29
         Und Operation.      0 UND 0 ergibt 0    ...00001
30
31
(und die restlichen 3 Bits sind auch alle 0).

Das Ergebnis der Rechnung ist also
1
    00001001
2
 &  00000011
3
   ----------
4
    00000001
oder eben in dezimaler Schreibweise
1
      9 & 3  -> 1

Bei Oder ist das auch nicht anders. Nur wird hier eben nicht 
stellenweise eine Und-Operation angewendet, sondern eine Oder-Operation.


Nicht zu verwechseln ist das ganze mit den logischen Operatoren && und 
||. Die beiden Verknüpfen Wahrheitswerte. TRUE und FALSE.

Eine Zahl, die 0 ist, gilt als logisch FALSE
Eine Zahl, die ungleich 0 ist, gilt als logisch TRUE
1
   9 ist ungleich 0, daher logisch TRUE
2
   3 ist ungleich 0, daher logisch TRUE
3
   TRUE && TRUE ergibt TRUE    (das ist wieder die UND-Tabelle von weiter
4
                                oben. Aber Achtung: hier wurden vorher
5
                                die Zahlen als Wahrheitswert bewertet und
6
                                mit diesem Wahrheitswert wird dann die
7
                                Operation gemacht!

daher:
1
    9 && 3  -> TRUE
(wobei TRUE wiederrum der Zahlenwert 1 zugewiesen wurde. Von daher kommt 
jetzt zufällig dasselbe raus wie bei der binären Operation. Aber das ist 
Zufall und ich will jetzt nicht nochmal alles mit anderen Zahlenwerten 
umschreiben)

von Christian N. (themoar)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Christian N. schrieb:
>>
>> DDRB |= (0<<6);
>> DDRB &= ~(1<<6);
>>
>> Das ist das einzige, was ich nicht verstehe.
>
> Welchen Teil vonBit setzen
>     register |= ( 1 << Bitposition );
> Bit löschen
>     register &= ~( 1 << Bitposition );
> hast du nicht verstanden.
>

Ich hatte deinen Beitrag schon verstanden. Nur als ich beim Beantworten 
von  reieg99's Beitrag war, ist deiner schon dazugekommen, welchen ich 
direkt gelesen und mit einfließen lassen habe. Deswegen ist in meiner 
Antwort auch ein kleiner Schwenk von Unwissen am Anfang hin zur 
Erleuchtung durch deine Antwort :D

Durch den letzten Teil deiner Antwort hat es jetzt klick gemacht. Ich 
habe tatsächlich versucht, mit logischen Operationen an die Sache zu 
gehen.

Deswegen auch
> heißt das soviel wie if(PB6==1)?
womit ich PB6 als logische 1 verstanden habe und meinte if(1==1), also 
PB6 als Variable angenommen habe.

von Dietrich L. (dietrichl)


Lesenswert?

Christian N. schrieb:
> Ich hätte gedacht, das, wenn ich einen Pin als Ausgang definiere und ihn
> meinetwegen mittels PortX auf High setze, dieser High-Pegel auch nach
> einem Reset vorhanden ist.

Nein, ein Hardware-Reset setzt alle Register auf den im Datenblatt 
angegebenen Ruhezustand. Das bewirkt auch, dass alle Ports Eingänge 
sind.

Aber indirekt hast Du insofern recht: nach dem Reset läuft ja das 
Programm los, und das setzt bei der Initialisierung die Register auf die 
gewünschten Werte. Aber in dieser kurzen Zeit sind Ausgänge erstmal 
hochohmige Eingänge.

Bei einem Software-Reset (Sprung zu Adresse 0) passiert das Rücksetzen 
auf den Ruhezustand allerdings nicht.

> Es gibt also auch ATmega88 mit PortA?

Wohl nicht der ATmega88, aber ein ähnlicher aus der Familie.
Die gleichen µC-Typen gibt es oft auch in verschiedenen Gehäusen mit 
unterschiedlicher Pinanzahl. Da fehlen dann schon mal Signale.

Gruß Dietrich

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.