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


von Thomas G. (praio)


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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Aus dem stdiodemo der avr-libc:

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

Implementierung:
1
/* Hilfsmakros */
2
#define GLUE(a, b)     a##b
3
4
/* single-bit macros, used for control bits */
5
#define SET_(what, p, m) GLUE(what, p) |= (1 << (m))
6
#define CLR_(what, p, m) GLUE(what, p) &= ~(1 << (m))
7
#define GET_(/* PIN, */ p, m) GLUE(PIN, p) & (1 << (m))
8
#define SET(what, x) SET_(what, x)
9
#define CLR(what, x) CLR_(what, x)
10
#define GET(/* PIN, */ x) GET_(x)
11
12
/* nibble macros, used for data path */
13
#define ASSIGN_(what, p, m, v) GLUE(what, p) = (GLUE(what, p) & \
14
                                                ~((1 << (m)) | (1 << ((m) + 1)) | \
15
                                                  (1 << ((m) + 2)) | (1 << ((m) + 3)))) | \
16
                                                ((v) << (m))
17
#define READ_(what, p, m) (GLUE(what, p) & ((1 << (m)) | (1 << ((m) + 1)) | \
18
                                            (1 << ((m) + 2)) | (1 << ((m) + 3)))) >> (m)
19
#define ASSIGN(what, x, v) ASSIGN_(what, x, v)
20
#define READ(what, x) READ_(what, x)
21
22
/* ... */
23
24
/* Benutzung: */
25
static void
26
hd44780_outnibble(uint8_t n, uint8_t rs)
27
{
28
  CLR(PORT, HD44780_RW);
29
  if (rs)
30
    SET(PORT, HD44780_RS);
31
  else
32
    CLR(PORT, HD44780_RS);
33
  ASSIGN(PORT, HD44780_D4, n);
34
  (void)hd44780_pulse_e(false);
35
}

von Klaus W. (mfgkw)


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.
1
#define LED_DDR    DDRA
2
#define LED_PORT   PORTA
3
#define LED_PIN    PC2
4
5
...
6
7
// Richtung setzen:
8
LED_DDR |= 1 << LED_PIN;
9
10
// Ausgang setzen:
11
LED_PORT |= 1 << LED_PIN;
12
13
// Ausgang löschen:
14
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.

von Uwe .. (uwegw)


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

von Peter D. (peda)


Lesenswert?

Definier doch einfach Bitvariablen:

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


Peter

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.

von (prx) A. K. (prx)


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

von Klaus W. (mfgkw)


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.

von Thomas G. (praio)


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.

von Rolf Magnus (Gast)


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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.

von Peter D. (peda)


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

von Karl H. (kbuchegg)


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.

von Klaus W. (mfgkw)


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

von Peter D. (peda)


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

von Rolf Magnus (Gast)


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.

von Simon K. (simon) Benutzerseite


Lesenswert?

Bisschen mehr Kreativität Leute!
1
#define LCD_PORT   B
2
3
.
4
.
5
.
6
7
#define LCD_PORTREG     PORT##LCD_PORT
8
#define LCD_DDRREG      DDR##LCD_PORT
9
//Oder
10
#define PORTREG(which)  PORT##which
11
#define DDRREG(which)   DDR##which
12
.
13
.
14
.
15
PORTREG(LCD_PORT) = 0x55;
16
DDRREG(LCD_PORT) = 0xAA;

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

von Klaus W. (mfgkw)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.

von Johann L. (gjlayde) Benutzerseite


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

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.