2010-03-10 10 views
15

Ich schreibe System-Level-Code für ein Embedded-System ohne Speicherschutz (auf einem ARM Cortex-M1, Kompilieren mit gcc 4.3) und muss direkt in ein Memory-Mapped-Register lesen/schreiben. Bisher sieht mein Code wie folgt aus:Wie lautet der kürzeste Code zum direkten Schreiben auf eine Speicheradresse in C/C++?

#define UART0  0x4000C000 
#define UART0CTL (UART0 + 0x30) 

volatile unsigned int *p; 
p = UART0CTL; 
*p &= ~1; 

Gibt es einen kürzeren Weg (kürzer in Code, ich meine), die nicht einen Zeiger nicht verwenden? Ich nach einer Möglichkeit, den tatsächlichen Zuordnung Code so kurz wie dies zu schreiben (es wäre in Ordnung, wenn ich mehr #defines habe zu verwenden):

*(UART0CTL) &= ~1; 

Alles, was ich versuchte, so weit endet mit gcc beschweren, dass es könnte nicht etwas zu dem L-Wert zuweisen ...

+4

'#define UART0CT' zu' #define X'? –

+0

@The Machine Charmer: +1, lustig. Ich glaube nicht, dass das OP in einer Code-Golf-Mode kürzer gemeint ist, aber, schön. :-) –

+0

Standard C * erfordert * eine explizite Umwandlung in ordetr, um dem Zeiger eine Ganzzahl ungleich Null zuzuweisen. Ihr Code ist in seiner aktuellen Form nur in GCC gültig, aber aus pedantischer C Sichtweise gebrochen. Also, ob Sie es wollen oder nicht, Sie müssen es länger machen, wenn Sie es wirklich als gültig behalten wollen. Natürlich, wenn Sie eine GCC-spezifische Lösung wollen, ist es eine andere Geschichte. – AnT

Antwort

14

Ich möchte ein Nitpick sein: Sprechen wir C oder C++?

Wenn C, schiebe ich bereitwillig auf Chris Antwort (und ich möchte, dass das C++ - Tag entfernt wird).

Bei C++ rate ich von der Verwendung dieser fiesen C-Casts und #define insgesamt ab.

Die idiomatische C++ Art und Weise ist eine globale Variable zu verwenden:

volatile unsigned int& UART0 = *((volatile unsigned int*)0x4000C000); 
volatile unsigned int& UART0CTL = *(&UART0 + 0x0C); 

Ich erkläre einen globalen Variable eingegeben, welche Regeln Umfang befolgt werden (im Gegensatz zu Makros).

Es kann einfach verwendet werden (keine Notwendigkeit *() zu verwenden) und ist somit noch kürzer!

UART0CTL &= ~1; // no need to dereference, it's already a reference 

Wenn Sie es wollen, Zeiger sein, dann wäre es:

volatile unsigned int* const UART0 = 0x4000C000; // Note the const to prevent rebinding 

Aber was ist der Punkt, einen const Zeiger zu verwenden, die nicht null sein kann? Dies ist semantisch, warum Referenzen für erstellt wurden.

+0

, weil Sie einen Const-Zeiger in eine Header-Datei einfügen können und es von C und C++ einschließen. Könnte es auch als "statisch" deklarieren in C (das ist auch in C++ harmlos), da es nicht notwendig ist, dass es in der Symboltabelle endet. –

+0

Richtig, ich dachte nur an den 'C++' Aspekt der Frage, da '# define' in' C' Programmen traditionell sind. –

+0

Nun, das scheint mir die beste Antwort für mein Projekt zu sein, da ich keinen Grund habe, die Behauptung über den "idiomatischen C++ Weg" zu leugnen ... – orithena

19
#define UART0CTL ((volatile unsigned int *) (UART0 + 0x30)) 

:-P

Edited hinzufügen: Oh, als Antwort auf alle Bemerkungen darüber, wie die Frage getaggt C++ sowie C, hier ist ein C++ Lösung. :-P

inline unsigned volatile& uart0ctl() { 
    return *reinterpret_cast<unsigned volatile*>(UART0 + 0x30); 
} 

Dies kann gerade in einer Header-Datei stecken, genau wie der C-Stil Makro, aber Sie müssen Funktionsaufruf Syntax verwenden, um es aufzurufen.

+0

cool, das funktioniert, danke. Ich vermisse immer noch die Feinheiten von C/C++ ... – orithena

+0

@Maligree: Casting ist eine der grundlegenden Operationen in C, definitiv kein "feinerer Punkt". :-P (Dies ist nicht so sehr ein Fehler bei dir, als die Tatsache, dass C scheint viel Casting zu erfordern, um Real Work getan. :-P) –

+0

Sicher, aber ich meinte die Kunst, alles zusammen zu setzen eine einzelne, aber dennoch "lesbare" Zeile. Mit Ihrer Lösung, IMO, endete ich mit einem schönen Code, dessen Semantik sofort zu sehen war (okay, ich konnte immer noch # UARTDISABLE ~ 1 definieren, um mehr Semantik zu dieser Zeile hinzuzufügen ...) – orithena

1
#define UART0 ((volatile unsigned int*)0x4000C000) 
#define UART0CTL (UART0 + 0x0C) 
+0

Warum '+ 10'? ___ – kennytm

+0

@dkrueger: 0x0C funktioniert nur für 32-Bit-Zeiger. Zugegeben, dies ist wahrscheinlich für ARM der Fall, aber immer noch sehr unportabel. :-P –

+0

@Chris: Nicht so wichtig, das Ganze ist anfangs höchst unportabel. (Allerdings wäre '0x30/sizeof (int)' klarer, IMHO.) –

1

können Sie gehen eine weitere als Antwort Chris, wenn Sie die Hardware-Register sehen aus wie normale alte Variablen machen wollen:

#define UART0  0x4000C000 
#define UART0CTL (*((volatile unsigned int *) (UART0 + 0x30))) 

UART0CTL &= ~1; 

Es ist eine Frage des Geschmacks, die vorzuziehen sein könnte. Ich habe in Situationen gearbeitet, in denen das Team wollte, dass die Register wie Variablen aussehen, und ich habe an Code gearbeitet, bei dem die hinzugefügte Dereferenz als zu viel Verstecken betrachtet wurde, so dass das Makro für ein Register als Zeiger übrig blieb explizit dereferenziert werden (wie in Chris 'Antwort).

1

Ich möchte die tatsächlichen Steuerbits in einer Struktur angeben und dann diese Steueradresse zuweisen.Etwas wie:

typedef struct uart_ctl_t { 
    unsigned other_bits : 31; 
    unsigned disable : 1; 
}; 
uart_ctl_t *uart_ctl = 0x4000C030; 
uart_ctl->disable = 1; 

(Entschuldigt, wenn die Syntax nicht ganz richtig ist, habe ich nicht wirklich in C codiert für eine ganze Weile ...)

+0

Ich bin nicht sicher, ob die Reihenfolge der Bits in einem Bitfeld ist ist durch den Standard spezifiziert. Ich denke, dieser Code wäre ein implementierungsdefiniertes Verhalten. Aber die Idee, eine "struct" zu verwenden, ist gut: Definieren Sie eine 'struct uart', die das Speicherlayout des Memory-Mapped-Devices enthält, weisen Sie die Adresse einem einzelnen Zeiger 'uart * const uart_regs = 0x4000c000;' zu und greifen Sie dann via zu das Objekt mit 'uart_regs-> ctl &= ~1;'. – cmaster

1

Eine weitere Option, die ich irgendwie wie für Embedded-Anwendungen ist Verwenden Sie den Linker, um Abschnitte für Ihre Hardward-Geräte zu definieren, und ordnen Sie Ihre Variable diesen Abschnitten zu. Dies hat den Vorteil, dass Sie die Linker-Dateien in der Regel für jedes Gerät einzeln ändern müssen, wenn Sie mehrere Geräte, auch von demselben Hersteller wie TI, verwenden. d. h. verschiedene Geräte in der gleichen Familie haben unterschiedliche Mengen an internem direkt zugeordnetem Speicher, und von Board zu Board können Sie auch unterschiedliche Mengen an RAM und Hardware an verschiedenen Orten haben. Hier ein Beispiel aus der GCC-Dokumentation:

Normalerweise platziert der Compiler die Objekte, die er erzeugt, in den Abschnitten wie data und bss. Manchmal benötigen Sie jedoch zusätzliche Abschnitte, , oder Sie benötigen bestimmte bestimmte Variablen, die in speziellen Abschnitten angezeigt werden, z. B. um spezielle Hardware zuzuordnen. Der Abschnitt Attribut gibt an, dass eine Variable (oder Funktion) in einem bestimmten Abschnitt lebt. Zum Beispiel verwendet dieses kleine Programm mehr spezifische Abschnittsnamen:

 struct duart a __attribute__ ((section ("DUART_A"))) = { 0 }; 
     struct duart b __attribute__ ((section ("DUART_B"))) = { 0 }; 
     char stack[10000] __attribute__ ((section ("STACK"))) = { 0 }; 
     int init_data __attribute__ ((section ("INITDATA"))); 

     main() 
     { 
     /* Initialize stack pointer */ 
     init_sp (stack + sizeof (stack)); 

     /* Initialize initialized data */ 
     memcpy (&init_data, &data, &edata - &data); 

     /* Turn on the serial ports */ 
     init_duart (&a); 
     init_duart (&b); 
     } 

Verwenden Sie den Abschnitt Attribut mit globalen Variablen und nicht auf lokalen Variablen, wie im Beispiel gezeigt.

Sie können den Abschnitt Attribut mit initialisierte oder nicht initialisierten globalen Variablen verwenden, aber der Linker erfordert jedes Objekt einmal definiert werden, mit der Ausnahme, dass nicht initialisierten Variablen gehen vorläufig in dem häufig (oder bss) Abschnitt und können mehrfach sein „ definiert ". Mit dem Attribut wird der Abschnitt geändert, in den die Variable eingefügt wird, und kann dazu führen, dass der Linker einen Fehler ausgibt, wenn eine nicht initialisierte Variable mehrere Definitionen enthält. Sie können erzwingen, dass eine Variable mit dem Flag -fno-common oder dem Attribut nocommon initialisiert wird.

Verwandte Themen