GPIOs mit C

Nomis3000

Neues Mitglied
29. Apr. 2008
415
0
0
Sprachen
Dieses vorerst kleine Geschreibsel sollte kein weiteres C-Tutorial darstellen, C sollte man schon vorher können und sich auch mit den dazugehörigen Operatoren auskennen. C-Tutorials gibt es zur Genüge im Netz. Es ist aber nur Fair schon jetzt darauf hinzuweisen dass jeder, der auch nur einen TimerCounter auf seinem AVR in Betriebnehmen kann, nicht weiterlesen braucht.

Mit dem ersten Teil will ich PORT PIN und DDR -Registerzugriffe näherbringen. Ich halte das für notwendig da Anfänger gerne auf auswendig gelernte Codeschnipsel zurückgreifen ohne sie zu verstehen. Weitere Teile von "Level1" werden einfache Probleme wie Entprellalgorithmen oder Lauflichter behandeln.

Level1:
Teil1: Verstehen wie man auf Registern Bitmanipulationen durchführt
Die folgenden 5 Codeschnipsel bewirken absolut das gleiche.


CodeBox C

DDRB=91;

Man kann sich zwar vorstellen was da gemacht wird, aber welche Pins nun als Ausgang geschalten werden ist nicht sofort ersichtlich.(DDRB ist ein 8 Bit-Register für den Port B in dem jedes der 8 Bits für einen Portpin des Ports steht. Ist für einen Portpin das jeweilige Bit im DDR-Register gesetzt gilt dieser als Ausgang und kann somit benutzt werden um LEDs zu treiben)



CodeBox C

DDRB=0x5B;

Das selbe in Hexadezimaler schreibweise, ebenfalls nicht ersichtlich welche Bits jetzt genau gesetzt sind.



CodeBox C

DDRB='k';

Was? Was hat k denn mit 0x5B oder 91 zu tun? Ist aber richtig so, wenn man besonders unleserlich programmieren will sucht man sich einfach ein ascii-Zeichen raus, das den Hex-Wert besitzt den man braucht. Danach schreibt den Buchstaben in die 2 Hochstriche ' ' um den Wert, den der Buchstabe im ascii-Code übernimmt, ins Register zu schreiben. In diesem Fall: ASCII "k" = HEX "5B" = DEC "91"



CodeBox C

DDRB=0b01011011;

Das Selbe, nun sieht man auch genau was für Bits gesetzt sind. Wenn man ungeübt ist muss man aber immernoch von rechts nach links zählen um zu erfahren an welchen Bits nun genau die 1er sind. Nun sind aber nicht alle Register wie die DDR-Register es gibt auch Register bei denen alle Bits komplett voneinander unabhängige Aufgaben haben, bei solchen Registern müsste man immer noch im Datenblatt nachlesen welchen Namen die entsprechenden Bits haben, und das nur um den so geschriebenen Code lesen zu können.
Daher:



CodeBox C

DDRB= (1<<PD6) | (1<<PD4) | (1<<PD3) | (1<<PD1) | (1<<PD0);

(das sind so stellen wo sich C-Neulinge schwer tuhn)
Das ist viel übersichtlicher. Makros(oder definierte Werte) wie PD6 PD3 usw. gibt es auch für andere Register, der Wert hinter dem Makro PD6 steht für die Nummer des Bits PD6. Genauergesagt steht der Wert hinter dem Makro dafür um wieviel ein 1er auf dem Niederwertigsten Bit in Richtung höherwertiges Bit(nach Links) geschoben werden muss damit er auf der Position des genannten Bits steht.
Bei Registern deren inhalt 1 zu 1 an Ausgänge weitergegeben wird macht es zwar nicht so viel sinn, aber bei einem Register wie dem "TIMSK0 – Timer/Counter Interrupt Mask Register" macht es Sinn das Makros für die Darin enthaltenen Bits erstellt worden sind. Für das Bit "TOIE0: Timer/Counter0 Overflow Interrupt Enable" heißt das Makro beispielsweise "TOIE0" sodass man den overflow Interrupt des TimerCounter0 ganz einfach über die Zeile

CodeBox C
TIMSK0|=(1<<TOIE0);
aktivieren kann.
Auf die Art muss sich niemand darum kümmern ob das TOIE0 -Bit das 0te oder 4te bit im TIMSK0-Register ist, und der Code wird dadurch leichter lesbar.

Manche Programmierneulinge kapieren allerdings nicht sofort was solche Zeilen eigentlich bewirken oder wie es abgearbeitet wird.
Gewöhnliche Zuweisungen:


CodeBox C

DDRB= (1<<PD6) | (1<<PD4) | (1<<PD3) | (1<<PD1) | (1<<PD0);

Was Passiert da genau im Controller(naja! Es wird meist vom Compiler Wegoptimiert)
"DDRB" ist ein Register angenommen sein Wert beträgt im Moment 0b01010101;
"=" ist eine Zuweisung, das heißt der aktuelle Wert des DDRB wird überschrieben.
(1<<PD6) lässt sich weiter zerteilen in "1" was dem Wert "0b00000001" entspricht. Das "<<", eine Linksverschiebung. Alle Bits links von "<<" werden um den Wert rechts von "<<" nach links verschoben. Das "PD6" rechts von "<<" ist übrigens ein Makro hinter dem der Wert "6" steckt. Der Abschnitt "(1<<PD6)" macht daher folgendes:
0b00000001 << 6
=
0b01000000

Die "|" zwischen den Teilen in der Klammer sind übrigens bitweise Oder. Das heißt es werden alle Werte, die in den Abschnitten in den Klammern erzeugt werden, auf 1 Byte zusammen verodert. etwa so:
0b01000000 //(1<<PD6)
|
0b00010000 //(1<<PD4)
|
0b00001000 //(1<<PD3)
|
0b00000010 //(1<<PD1)
|
0b00000001 //(1<<PD0)
=
0b01011011

Der Compiler verkürzt diese lange Phrase"(1<<PD6) | (1<<PD4) | (1<<PD3) | (1<<PD1) | (1<<PD0)" sogar wirklich auf soetwas:


CodeBox C

DDRB=0b01011011;


Der Nachteil an dem "=" ist dass der bisher aktuelle Wert von DDRB überschrieben wird. In anden Registern, die warscheinlich Konfigurationsbits für mehrere Hardwareelemente auf einmal besitzen, würde das die Konfiguration der Restlichen Hardware gefärden.

Das Setzen von einzelnen Bits:


CodeBox C
TIMSK0 |= (1<<TOIE0);
Das lässt sich für Anfänger sofort etwas leserlicher gestalten:
aus "x |= y;" wird nämlich "x = x | y;"
daher:


CodeBox C
TIMSK0 = TIMSK0 | (1<<TOIE0);

Angenommen das TIMSK0 hatte vorher den Wert 0b00000100;, dann war anscheinend schon vorher der OutputCopareInterrupt aktiviert. Mit einer einfachen "=" Zuweisung wäre dieser deaktiviert worden. daher geht man so vor:
0b00000100 //Der alte Wert des TIMSK0 -Registers.
|
0b00000001 //(1<<TOIE0) das TOIE0 -Bit ist nämlich das 0te Bit
=
0b00000101 //Der neue Wert des TIMSK0-Registers


Das Löschen bestimmter Bits: angenommen wir wollen eine LED Einschalten, die in Invertierender Logik an einem Pin unseres AVRs hängt.


CodeBox C
PORTC &= ~ (1<<PC2);

PORT sind die Ausgangsregister der AVRs, eine 1 an einem bestimmten Bit gibt einen High-Pegel aus eine 0 Einen Low-Pegel. Außerdem kann man mit dem PORT-Register, im Falle dass ein Portpin im DDR-Register als Eingang definiert ist, die internen PullUp Widerstände aktivieren
Das "&=" lässt sich wieder in der Art vereinfachen "x &= y" wird zu "x = x & y".
Darum:


CodeBox C
PORTC = PORTC & ( ~(1<<PC2));

Das "&" steht in diesem Fall für eine Bitweise Und-Verknüpfung und das "~" kehrt alle Bits in einer Variable um.
Das hieße unter der Annahme das "PORTC" vorher den Wert "0b11100110" hatte:

0b00000100 //(1<<PC2)
~
0b11111011 //~(1<<PC2)

und weiter:
0b11111011 //~(1<<PC2)
&
0b11100110 //das bisherige PORTC
=
0b11100010 //das neue PORTC mit gelöschtem PC2


Das Toggeln von Bits:


CodeBox C
PORTC ^=  (1<<PC2);

Das "^=" lässt sich wieder in der Art vereinfachen "x ^= y" wird zu "x = x ^ y".
Daher:


CodeBox C
PORTC = PORTC ^ (1<<PC2);


Das "^" Stellt ein bitweises XOR dar, also ein bitweises exklusives Oder. Wer nicht weiß was gemeint ist kann das ganz leicht in Wikipedia nachlesen. Da es sich um ein Toggeln handelt werde ich den Prozess in beide Richtungen aufzeigen:
0b00000000 //PORTC Vor dem ersten Toggeln
^
0b00000100 //(1<<PC2)
=
0b00000100 //PORTC Nach dem erstenToggeln

Die zweite Richtung:
0b00000100 //PORTC Nach dem erstenToggeln
^
0b00000100 //(1<<PC2)
=
0b00000000 //PORTC Nach dem 2ten Toggeln


Das Abfragen von gewissen Pins oder Flags:



CodeBox C

if(PINA &(1<<PA0))
{
..........

"PINA" Ist ein Register dessen Bits die Werte der entsprechenden Portpins annehmen. Liegt an einem Portpin ein High-Pegel an ist das entsprechende Bit im PIN -Register auch auf "1"(Korrekterweise müsste man auch hier HIGH sagen).
Also nehmen wir die Abfrage mal unter die Lupe:
Angenommen das 0te Bit(PA0) im PINA-Register ist 0(am entsprechenden Pin liegt ein LOW-Pegel an):
0b01000000//PINA0: am PA0 liegt ein LOW-Pegel an aber an einem anderen PIN scheint sich etwas zu tuhn
&
0b00000001//(1<<PA0)
ergibt:
0b00000000// Das ist eindeutig ein FALSE! der Code in der "if"-Abfrage wird nicht ausgeführt

Diesmal nehmen wir an dass am PA0 ein High anliegt:
0b01000001//PINA0: am PA0 liegt ein HIGH-Pegel an
&
0b00000001//(1<<PA0)
ergibt:
0b00000001// Das ist eindeutig ein TRUE! der Code in der "if"-Abfrage wird ausgeführt



Die GPIO-Register genauer beschrieben:
Dazu sollte man sich folgende Skizze Ansehen
Ausgangsregister.png
Skizze1:
Eine typische LED Schaltung. Da der interne Transistor S1(hier als Schalter dargestellt) nicht so hohe Ströme treiben kann, werden LEDs bei AVRs meistens über den Transistor S2(der kann mehr) gegen Masse geschalten.
Das DDR Register ermöglicht es erst, die Transistoren S1 und S2 Anzusprechen. Ist das Entsprechende Bit im DDR Register auf 0 können die Transistoren nicht angesteuert werden, weshalb das Entsprechende Bit im DDR Register auf 1 gesetzt ist.
Wenn das entsprechende Bit im DDR Register gesetzt ist können die Transistoren S1 und S2 mit dem PORT Register angeteuert werden. Ist das entsprechende Bit im PORT Register auf 0(Rot) ist der Schalter S2 Geschlossen und S1 geöffnet->ein Strom fließt von VCC über den Vorwiderstand durch die LED und den schalter S2 gegen Masse-> die LED Leuchtet.
Ist das Entsprechende Bit im PORT Register auf 1(Grün), so ist der Schalter S1geschlossen und Schalter S2 geöffnet-> es kann kein Strom dürch die LED fließen da am Ausgang VCC niveau herrscht.

Skizze2:
Um einen Portpin als Eingang zu definieren müssen wir das entsprechende Bit im DDR Register deaktivieren. Damit können über das PORT Register zwar nicht mehr die Transistoren S1 und S2 angesprochen werden, aber dafür der Transistor für den internen PullUp Widerstand. Ist das Bit im PORT Register auf 1 ist der Transistor für den Internen PullUp Widerstand durchgeschalten, ist das entsprechende Bit auf 0 muss man einen Externen PullUp einsetzen.
Die PIN Register können eigentlich nicht beschrieben werden, sie können nur ausgelesen werden und liefern dabei, in den Bits enthaltene, Informationen ob an den jeweiligen Pins ein High oder Low -Pegel anliegen.
In die Felder der Skizze, welche die Bits des PIN Registers darstellen, fließt kein Strom, sie sind absolut passiv und messen nur ob an ihnen ein High oder Low -Pegel anliegen.

Rechtschreibfehler werden von mir baldest möglich beseitigt.
 
Es sieht nicht so aus als ob ich hier noch weitere Erklärungen bezüglich AVRs posten werde.
Es wäre daher logisch den Thread in "GPIOs mit C" umzubenennen, außerdem steht es jedem frei Kritik zu äußern oder Fehler auszubessern(Moderatoren können das am besten).
 
Hallo Nomis,

dein Tutorial finde ich gut gelungen, es behandelt alle grundlegenden Adressierungsarten für die GPIO-Register. Fehler habe ich keine gefunden und hier gibt es eigentlich auch nichts zu ergänzen, zumindest würde alles weitere den Rahmen des Themas sprengen.

Das Tutorial wird bestimmt dem Einen oder Anderen eine große Hilfe sein. Wenn mir wieder ein bisschen mehr Zeit bleibt, werde ich auch mal eins schreiben, mal sehen, entweder etwas mit Timern, PWM und Interrupts oder eine Art Grundgerüst (Template) für Assembler.

Achso, den Fehler im C-Highlighting mit der öffnenden Klammer habe ich nicht vergessen ... muss ich noch abarbeiten :rolleyes:

Grüße,
Dirk
 
Achso, den Fehler im C-Highlighting mit der öffnenden Klammer habe ich nicht vergessen ... muss ich noch abarbeiten :rolleyes:

Finde ich gut.
Ich hab auch vor in den nächsten Wochen ein kleines Interrupt Tutorial für den AVR32(ohne Linux) zu schreiben. Also ohne die Mitgelieferten Treiber, welche leider etwas schwer zu verstehen sind, aber dafür etwas unflexibler.

Ich halte es für sinnvoll weil ich selbst ungewöhnlich lange gebraucht habe um den Interrupt-Driver der AVR32-Umgebung zu verstehen.

Außerdem hab ich meine Niederlage Bascom gegenüber auf der AVR-Platform schon längst erkannt, und kann so versuchen auf den AVR32 etwas mehr C-Programmierer zu locken. :p
 
Hi Nomis,

"AVR32" finde ich gut und "ohne Linux" finde ich noch besser ;) Ich freue mich schon auf das Tutorial :pcguru:

Grüße,
Dirk
 
Man könnte das Makro _BV() noch hinzufügen. Das nutze ich ganz gerne

Code:
TIMSK = _BV(CS0) | _BV(CS2)

Das erzeugt dann das entsprechende Bitmuster für das jeweilige zu setzende Bit, das verodert man mit weiteren und voilá. Ich finde das recht übersichtlich weil man so auf einen Blick sieht welche Bits man setzt und welche nicht (man hat sie beim Namen).

Grüße
 
Das Makro kenn ich noch garnicht, aber jetzt wo du es gepostet hast brauche ich es eh nicht mehr erwähnen.
 
Ich kannte es bis zu diesem Semester auch nicht :)
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)