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
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 | }
|
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
Definier doch einfach Bitvariablen: http://www.mikrocontroller.net/attachment/30300/lcd_drv.zip Peter
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.
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.
was gemeint ist, ist klar. Funktionieren wird es so nicht (zumindest die erste Variante).
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.