mikrocontroller.net

Forum: Compiler & IDEs #define-Pinnamen beim Setzen von Pins


Autor: Thomas Gulden (praio)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

in meinem C-Programm für den ATMega64 habe ich folgendes Problem:

Zu Beginn habe ich folgendes #define-Statement:

  #define LED  PA2

Möchte ich jetzt diesen Pin setzen, geht dies ja über
  PORTA |= (1 << LED);

Nun kann es aber sein, dass sich vom aktuellen Prototyp zum 
Serienexemplar die Pin-Zuordnung am Controller aufgrund eines 
einfacheren Layouts noch ändert.
Genau deshalb (und zur einfacheren Lesbarkeit natürlich ;-) ) verwende 
ich ja auch #define-Statements.

Wenn sich jetzt jedoch die LED aufgrund eines anderen Layouts z.B. an 
Port C befindet, nützt mir das ganze Define nichts mehr da ich ja aus
  PORTA |=...
nun
  PORTC |=...
machen muss!

Git es keine andere Möglichkeit? Zuvor hatte ich im Studium mit dem 
CCS-PIC-C-Compiler gearbeitet, da gibt es beispielsweise die Funktion
  output_high (LED)
und das zugehörige #define lautet
  #define LED PIN_A2
d.h. sowohl Port als auch Pin werden mittels #define-Statement 
übergeben.
Geht das nicht auch beim AVR?

Danke,
Thomas

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Aus dem stdiodemo der avr-libc:

Headerdatei, die die Hardware-Beziehungen beschreibt:
/* HD44780 LCD port connections */
#define HD44780_RS A, 6
#define HD44780_RW A, 4
#define HD44780_E  A, 5
/* The data bits have to be in ascending order. */
#define HD44780_D4 A, 0

Implementierung:
/* Hilfsmakros */
#define GLUE(a, b)     a##b

/* single-bit macros, used for control bits */
#define SET_(what, p, m) GLUE(what, p) |= (1 << (m))
#define CLR_(what, p, m) GLUE(what, p) &= ~(1 << (m))
#define GET_(/* PIN, */ p, m) GLUE(PIN, p) & (1 << (m))
#define SET(what, x) SET_(what, x)
#define CLR(what, x) CLR_(what, x)
#define GET(/* PIN, */ x) GET_(x)

/* nibble macros, used for data path */
#define ASSIGN_(what, p, m, v) GLUE(what, p) = (GLUE(what, p) & \
                                                ~((1 << (m)) | (1 << ((m) + 1)) | \
                                                  (1 << ((m) + 2)) | (1 << ((m) + 3)))) | \
                                                ((v) << (m))
#define READ_(what, p, m) (GLUE(what, p) & ((1 << (m)) | (1 << ((m) + 1)) | \
                                            (1 << ((m) + 2)) | (1 << ((m) + 3)))) >> (m)
#define ASSIGN(what, x, v) ASSIGN_(what, x, v)
#define READ(what, x) READ_(what, x)

/* ... */

/* Benutzung: */
static void
hd44780_outnibble(uint8_t n, uint8_t rs)
{
  CLR(PORT, HD44780_RW);
  if (rs)
    SET(PORT, HD44780_RS);
  else
    CLR(PORT, HD44780_RS);
  ASSIGN(PORT, HD44780_D4, n);
  (void)hd44780_pulse_e(false);
}

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Leider nicht direkt, Port und Pin müssen beide angegeben
werden - als zwei Werte.
Genau genommen bracuht man dann noch einen dritten Wert
für DDR, und ggf. einen vierten zum Lesen (PIN...) z.B.
#define LED_DDR    DDRA
#define LED_PORT   PORTA
#define LED_PIN    PC2

...

// Richtung setzen:
LED_DDR |= 1 << LED_PIN;

// Ausgang setzen:
LED_PORT |= 1 << LED_PIN;

// Ausgang löschen:
LED_PORT &= ~( 1 << LED_PIN );

Um das prägnanter zu schreiben, muß man schon etwas in die
Trickkiste greifen. So ist es hier aber üblich; mir gefällt es nicht.

Autor: Uwe ... (uwegw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Man kommt mit zwei defines pro LED (Port und Pin) aus. Das liegt daran, 
dass die Position der DDR- und PIN-Register relativ zum PORT-Register 
immer gleich ist.

Erst mal
#define DDR(x) (*(&x - 1))      /* address of data direction register of 
port x */
#define PIN(x) (*(&x - 2))    /* address of input register of port x */

Dann für jede LED

#define LED_PORT   PORTA
#define LED_PIN    PC2

// Richtung setzen:
DDR(LED_PORT) |= 1 << LED_PIN;

// Ausgang setzen:
LED_PORT |= 1 << LED_PIN;

// Ausgang löschen:
LED_PORT &= ~( 1 << LED_PIN );

Und wenn ein Pin als Eingang dient, mit
PIN(LED_PORT)...


Zusätzlich mach ich mir immer noch die Makros
LED_on
LED_off
LED_toggle

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Definier doch einfach Bitvariablen:

http://www.mikrocontroller.net/attachment/30300/lcd_drv.zip


Peter

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Uwe ... schrieb:
> Man kommt mit zwei defines pro LED (Port und Pin) aus. Das liegt daran,
> dass die Position der DDR- und PIN-Register relativ zum PORT-Register
> immer gleich ist.

Ziemlich schräger Hack, für den es keine wirkliche Notwendigkeit
gibt.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dafür hängt deine Variante sehr von einer absolut korrekten 
Implementierung des Präprozessors ab. Was Microchip beim C18 nicht 
gelungen ist (offiziell zugegeben).

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jörg Wunsch schrieb:
> Uwe ... schrieb:
>> Man kommt mit zwei defines pro LED (Port und Pin) aus. Das liegt daran,
>> dass die Position der DDR- und PIN-Register relativ zum PORT-Register
>> immer gleich ist.
>
> Ziemlich schräger Hack, für den es keine wirkliche Notwendigkeit
> gibt.

Naja, das ist Geschmackssache.
Ich persönlich finde es schon ziemlich befremdlich, wenn man
für die Nutzung eines Signals drei oder 4 defines braucht (und
ggf. parallel ändern und konsistent halten muss):
- DDR
- PIN
- PORT
- Pinnummer

Dadurch, daß DDR, PIN und PORT direkt voneinander ableitbar sind,
kann man sich zwei Definitionen sparen und durch Berechnung zur
Übersetzungszeit ersetzen.

Zwinghend notwendig ist das jetzt nicht, das stimmt.
Aber schön oder elegant ist der "klassische" Weg m.E. auch nicht.

Noch interessanter wird das automatische Berechnen dann, wenn
man gar nicht mit #define arbeiten will, weil man z.B. die
LCD-Funktionen (für die ja nicht ein Satz (DDR,PORT,Pinnnummer)
nötig ist, sondern gleich 7 oder 8) nicht fest verdrahten will,
sondern als Parameter an eine Initialisierungsfunktion übergeben
will. Wieso soll ich dann z.B. 16 Parameter übergeben, die sich
paarweise nur um 1 unterscheiden?
Da reichen auch 8, die anderen werden intern berechnet.

Autor: Thomas Gulden (praio)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wow danke für das umfangreiche Feedback!
Eigentlich hatte ich zwar gehofft dass es ganz einfach geht und von mir 
nur überlesen wurde, aber es sind hier ja einige gute Vorschläge dabei 
die ich mal umsetzen werde.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>> Ziemlich schräger Hack, für den es keine wirkliche Notwendigkeit
>> gibt.

> Naja, das ist Geschmackssache.

> Dadurch, daß DDR, PIN und PORT direkt voneinander ableitbar sind,

Das setzt aber voraus, daß diese Annahme stimmt, ohne daß das irgendwo 
so spezifiziert wäre. Und genau deshalb ist es ein schräger Hack. Solche 
Annahmen sind nämlich oft das, was einem dann (manchmal erst Jahre 
später) ein Bein stellt, wenn's dann irgendwo doch anders ist.

> Aber schön oder elegant ist der "klassische" Weg m.E. auch nicht.

Er trifft zumindest keine unnötigen Annahmen über das System.

> Noch interessanter wird das automatische Berechnen dann, wenn
> man gar nicht mit #define arbeiten will, weil man z.B. die
> LCD-Funktionen (für die ja nicht ein Satz (DDR,PORT,Pinnnummer)
> nötig ist, sondern gleich 7 oder 8) nicht fest verdrahten will,
> sondern als Parameter an eine Initialisierungsfunktion übergeben
> will.

Wozu braucht man dafür eine Initialisierungsfunktion? Die 
Pin-Zuordnungen stehen doch zur Compilezeit fest.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:

> Wozu braucht man dafür eine Initialisierungsfunktion? Die
> Pin-Zuordnungen stehen doch zur Compilezeit fest.

Damit der Nutzer sein LCD im Betrieb umstöpseln kann. :-)

Nee, wenn man die Funktion in einer Bibliothek unterbringen will,
kann das schon sinnvoll sein.  Leider erkauft man sich die
Flexibilität mit einer nicht unbeträchtlichen Performanceeinbuße:
nicht nur, dass man nicht mehr die schnellen IN/OUT/SBI/CBI-
Befehle nehmen kann, sondern alles über LDS/STS machen muss,
nein, der Zugriff auf eine x-beliebige Bitposition braucht entweder
aufwändige Schleifen zum Bitschieben oder switch/case für jede
mögliche Bitposition.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jörg Wunsch schrieb:
> Damit der Nutzer sein LCD im Betrieb umstöpseln kann. :-)

Das ist ja leicht, aber wie bringt man dem AVR dann die neue Belegung 
bei?

Das LCD funktioniert ja noch nicht mit der falschen Belegung, also muß 
man über die Tastatur blind die neue Belegung eintippen.

Es ist also unsinnig, die Pinbelegung zur Laufzeit festlegen zu können.


Peter

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> Jörg Wunsch schrieb:
>> Damit der Nutzer sein LCD im Betrieb umstöpseln kann. :-)
>
> Das ist ja leicht, aber wie bringt man dem AVR dann die neue Belegung
> bei?

zb Konfiguration über UART.
Aber geh mal weg vom LCD.
Bei machen Projekten wäre es schon schön, wenn man als Endbenutzer 
festlegen könnte, dass zb. der DS1820 an diesem und jenen Pin hängt.

Aber du hast schon recht: normalerweise wird man das selten machen.
Allerdings ist der Fall, dass man derartige Module in einer richtigen 
Library hat, und nicht jedesmal bei jedem Projekt neu compilieren muss 
interessanter. Dann bräuchte man eine Init-Funktion, in der man die in 
diesem Programm benutzte Belegung bekannt gibt. Und da sind sie dann 
wieder, die Probleme.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
>>> Ziemlich schräger Hack, für den es keine wirkliche Notwendigkeit
>>> gibt.
>
>> Naja, das ist Geschmackssache.
>
>> Dadurch, daß DDR, PIN und PORT direkt voneinander ableitbar sind,
>
> Das setzt aber voraus, daß diese Annahme stimmt, ohne daß das irgendwo
> so spezifiziert wäre. Und genau deshalb ist es ein schräger Hack. Solche
> Annahmen sind nämlich oft das, was einem dann (manchmal erst Jahre
> später) ein Bein stellt, wenn's dann irgendwo doch anders ist.
>
>> Aber schön oder elegant ist der "klassische" Weg m.E. auch nicht.
>
> Er trifft zumindest keine unnötigen Annahmen über das System.

Die Annahme, daß sich bei den Atmegas die Register nicht mehr
verschieben, kann man wohl vertreten.

>
>> Noch interessanter wird das automatische Berechnen dann, wenn
>> man gar nicht mit #define arbeiten will, weil man z.B. die
>> LCD-Funktionen (für die ja nicht ein Satz (DDR,PORT,Pinnnummer)
>> nötig ist, sondern gleich 7 oder 8) nicht fest verdrahten will,
>> sondern als Parameter an eine Initialisierungsfunktion übergeben
>> will.
>
> Wozu braucht man dafür eine Initialisierungsfunktion? Die
> Pin-Zuordnungen stehen doch zur Compilezeit fest.

Erstens verdrahtet man (um bei dem lcd-routines-Beispiel zu
bleiben) die Pinzuordnungen in der lcd-routines.h, wo sie
eigentlich nicht hingehören. Denn lcd-routines.c und
lcd-routines.h sind ein sich sich abgeschlossenes Modul;
die Pinbelegung dagegen gehört zum Projekt, in dem lcd-routines
verwendet wird und die Zuordnung sollte in einer Datei stehen,
die zum Projekt selbst gehört.
Zweitens kann man relativ leicht mehrere LCDs an einem
AVR betreiben, aber nicht wenn man die Belegung für genau ein
LCD in der lcd-routines.h macht. Mit wenig Mehraufwand und
einer Initialisierungsfunktion kann man die Routinen für mehrere
Displays gleichzeitig nutzen, muß dann aber bei der
Initialisierung natürlich die Zuordnung übergeben.
Und genau dann ist es doch etwas unschön und auch nicht
effizient, für alle Pins jeweils DDR, PIN, PORT und Pinnummer
übergeben zu müssen, zudem unnötig fehleranfällig (z.B. wenn man
versehentlich zu einem Signal DDRB und PORTA stehen hat).

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Zweitens kann man relativ leicht mehrere LCDs an einem
> AVR betreiben, aber nicht wenn man die Belegung für genau ein
> LCD in der lcd-routines.h macht.

Ich mach die Hardwaredefinitionen lieber in der main.h.

Bei mehreren LCDs muß man nur den E-Pin unterschiedlich anschließen, 
bzw. größere LCDs haben schon 2 E-Pins.
Man muß also nicht die Treiber für jedes LCD extra schreiben.

Eine globale Variable enthält die aktive LCD-Nummer und damit wird dann 
in den Routinen der richtige E-Pin auf high gesetzt.
Oder ne Maske mit einem Bit pro LCD, z.B. um die Initialisierung auf 
allen LCDs gleichzeitig zu machen.


Peter

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>> Er trifft zumindest keine unnötigen Annahmen über das System.
>
> Die Annahme, daß sich bei den Atmegas die Register nicht mehr
> verschieben, kann man wohl vertreten.

Was heißt "nicht mehr verschieben"? Die sind je nach Typ an 
unterschiedlichen Stellen. Beim Atmega88 sind die Port-Register z.B. wo 
anders als beim Atmega8. Die Reihenfolge PIN/DDR/PORT ist zwar gleich, 
aber ich sehe deshalb noch keinen Grund anzunehmen, daß das bei allen 
aktuellen und zukünftigen AVRs auch so sein wird.

> Erstens verdrahtet man (um bei dem lcd-routines-Beispiel zu
> bleiben) die Pinzuordnungen in der lcd-routines.h, wo sie
> eigentlich nicht hingehören.

Dafür kann das Projekt auch einen eigenen Header zur Verfügung stellen, 
den die LCD-Lib eben voraussetzt. Ob man nun voraussetzt, daß man eine 
init-Funktion aufruft, der man den ganzen Kram übergeben muß oder daß 
dieser in einem Header steht, macht auch nicht so viel Unterschied.

> Zweitens kann man relativ leicht mehrere LCDs an einem
> AVR betreiben, aber nicht wenn man die Belegung für genau ein
> LCD in der lcd-routines.h macht.

Das stimmt allerdings.

> Mit wenig Mehraufwand und einer Initialisierungsfunktion kann man die
> Routinen für mehrere Displays gleichzeitig nutzen, muß dann aber bei
> der Initialisierung natürlich die Zuordnung übergeben.
> Und genau dann ist es doch etwas unschön und auch nicht
> effizient, für alle Pins jeweils DDR, PIN, PORT und Pinnummer
> übergeben zu müssen,

Ich würde dann der Funktion einfach einen Zeiger auf eine Struktur als 
Parameter geben, und diese Struktur muß halt vorher passend befüllt 
werden.

> zudem unnötig fehleranfällig (z.B. wenn man
> versehentlich zu einem Signal DDRB und PORTA stehen hat).

Der ist aber relativ einfach zu finden. Wenn auf einmal bei irgendeinem 
AVR eins unter PORTA nicht mehr DDRA ist, sondern UCSRB, dann ist der 
Grund für das seltsame Verhalten nicht mehr ganz so einfach zu finden.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bisschen mehr Kreativität Leute!
#define LCD_PORT   B

.
.
.

#define LCD_PORTREG     PORT##LCD_PORT
#define LCD_DDRREG      DDR##LCD_PORT
//Oder
#define PORTREG(which)  PORT##which
#define DDRREG(which)   DDR##which
.
.
.
PORTREG(LCD_PORT) = 0x55;
DDRREG(LCD_PORT) = 0xAA;

Ist aber jetzt nicht getestet. Ich denke es wird klar was ich meine.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
was gemeint ist, ist klar.
Funktionieren wird es so nicht (zumindest die erste Variante).

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:

> Funktionieren wird es so nicht (zumindest die erste Variante).

Die funktionierende Variante geht über Hilfsmakros, wie ich weiter
oben schon einmal gepostet habe.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Peter Dannegger schrieb:
>
> zb Konfiguration über UART.
> Aber geh mal weg vom LCD.
> Bei machen Projekten wäre es schon schön, wenn man als Endbenutzer
> festlegen könnte, dass zb. der DS1820 an diesem und jenen Pin hängt.
>
> Aber du hast schon recht: normalerweise wird man das selten machen.
> Allerdings ist der Fall, dass man derartige Module in einer richtigen
> Library hat, und nicht jedesmal bei jedem Projekt neu compilieren muss
> interessanter. Dann bräuchte man eine Init-Funktion, in der man die in
> diesem Programm benutzte Belegung bekannt gibt. Und da sind sie dann
> wieder, die Probleme.

Sowas ginge durch neue RELOCs in elf32-avr, d.h. erst zur Linkzeit würde 
festgelegt werden, wo welcher Port hin angebilden wird -- und nicht 
schon zur Compilezeit. Dadurch könnte dann per avr-ld Option jedem 
Port-Symbol der passende Wert zugeordnet werden. Dadurch würde der Code 
auch atomar bleiben, im Gegensatz zu Code der erst zur Laufzeit 
entscheidet, wo welcher Port hin abgebildet wird.

Auch Bitfelder können zu nicht-atomatem Code führen, denn PORT |= X 
führe zu ZWEI Zugriffen wegen PORT = PORT | X. Da PORT volatile ist, 
müssen eigentlich alle 8 Bit gelesen und geschrieben werden, was eine 
IN-ORI-OUT-Sequenz entspricht und nicht einem SBI.

Meine Lösung für das Port-Problem entspricht am ehesten der von Jörg: 
Jeder Port ist nur eine Nummer, die per Makro auf den Befehl abgebildet 
wird.

Ein Beispiel dazu sind die Portdefinitionen in
   Beitrag "Eieruhr mit ATtiny24V/ATtiny2313V"
   http://www.gjlay.de/pub/eiuhr/EiUhr.zip


Die Port-Definitionen befinden sich in ports.h und Verwendung zB in 
eiuhr-attiny24.c:eiuhr_set_leds() oder eiuhr-attiny2313.c:beeper() 
(Makros: SET, CLR, MAKE_IN, MAKE_OUT, IS_SET, IS_CLR)

Johann

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.