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