2010-03-10 1 views
15

Sto scrivendo il codice a livello di sistema per un sistema embedded senza protezione della memoria (su un ARM Cortex-M1, compilando con gcc 4.3) e ho bisogno di leggere/scrivere direttamente in un registro mappato in memoria. Finora, il mio codice è simile al seguente:Qual è il codice più breve per scrivere direttamente su un indirizzo di memoria in C/C++?

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

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

C'è un modo più breve (più breve in codice, voglio dire) che non utilizza un puntatore? Io alla ricerca di un modo per scrivere il codice vero e proprio incarico più breve questo (sarebbe bene se ho dovuto usare più #defines):

*(UART0CTL) &= ~1; 

Tutto ciò che ho provato finora è conclusa con gcc lamentano che poteva non assegnare qualcosa al lvalue ...

+4

'#define UART0CT' a' X' #define? –

+0

@ The Machine Charmer: +1, divertente. Non penso che l'OP significhi più breve in un modo di giocare al golf, ma, bello. :-) –

+0

Standard C * richiede * un cast esplicito in ordetr per assegnare un intero diverso da zero al puntatore. Il tuo codice, nella sua forma attuale, è valido solo in GCC, ma è rotto dal punto di vista C pedante. Quindi, che tu lo voglia o meno, dovrai allungarlo se vuoi davvero mantenerlo valido C. Naturalmente, se vuoi una soluzione specifica per GCC, è una storia diversa. – AnT

risposta

14

Mi piacerebbe essere un pisolino: stiamo parlando di C o C++?

Se C, differisco volentieri la risposta di Chris (e vorrei che il tag C++ venisse rimosso).

Se C++, sconsiglio vivamente l'uso di quei brutti C-Cast e del #define del tutto.

Il modo idiomatico C++ è quello di utilizzare una variabile globale:

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

dichiaro una digitato variabile globale, che obbedire a regole di visibilità (contrariamente alle macro).

Può essere utilizzato facilmente (non è necessario utilizzare *()) ed è quindi ancora più breve!

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

Se si vuole che sia puntatore, allora sarebbe:

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

Ma qual è il punto di utilizzare un puntatore const che non può essere nullo? Questo è semanticamente il motivo per cui sono stati creati i riferimenti.

+0

perché è possibile inserire un puntatore const in un file di intestazione e # includerlo sia in C che in C++. Potrebbe anche dichiararlo "statico" anche in C (questo è anche innocuo in C++) in quanto non c'è bisogno che finisca nella tabella dei simboli. –

+0

Vero, ho solo pensato all'aspetto 'C++' della domanda dato che '# define' sono tradizionali nei programmi' C'. –

+0

Bene, mi sembra che sia la migliore risposta per il mio progetto in quanto non ho motivo di non credere alle affermazioni sul "modo idiomatico in C++" ... – orithena

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

:-P

a cura di aggiungere: Oh, in risposta a tutti i commenti su come la questione è aggiunto C++ e C, ecco un C++ soluzione. :-P

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

Questo può essere attaccato direttamente in un file di intestazione, proprio come la macro-stile C, ma è necessario utilizzare la sintassi chiamata di funzione per invocarlo.

+0

cool, funziona, grazie. Mi sembra che manchi ancora i punti più sottili di C/C++ ... – orithena

+0

@maligree: Il cast è una delle operazioni fondamentali in C, sicuramente non un "punto migliore". :-P (Questo non è tanto un colpo su di te, quanto il fatto che C sembra richiedere un sacco di casting per ottenere un vero lavoro. :-P) –

+0

Certo, ma intendevo l'arte di mettere tutto insieme in una singola, ma ancora linea "leggibile". Con la tua soluzione, IMO ho finito con un bel codice la cui semantica poteva essere vista in un istante (beh, okay, potrei ancora #definire UARTDISABLE ~ 1 per aggiungere più semantica a quella linea ...) – orithena

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

Perché '+ 10'? ___ – kennytm

+0

@dkrueger: 0x0C funziona solo con puntatori a 32 bit. Certo, questo è probabile che sia il caso di ARM, ma ancora altamente inderogabile. :-P –

+0

@Chris: Non è così che importa, l'intera faccenda è altamente non vendibile per cominciare. (Tuttavia, '0x30/sizeof (int)' sarebbe più chiaro, IMHO.) –

1

Si può andare oltre una risposta di Chris, se si vuole fare i registri hardware sembrano vecchi variabili semplici:

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

UART0CTL &= ~1; 

E 'una questione di gusti che potrebbe essere preferibile. Ho lavorato in situazioni in cui il team voleva che i registri assomigliassero a variabili, e ho lavorato su codice in cui il dereferenziamento aggiunto era considerato "nascosto troppo", quindi la macro per un registro sarebbe rimasta come un puntatore che doveva essere deferenziati esplicitamente (come nella risposta di Chris).

1

Mi piace specificare i bit di controllo effettivi in ​​una struttura, quindi assegnarlo all'indirizzo di controllo.Qualcosa di simile:

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

(Mi scuso se la sintassi non è giusto, non ho in realtà scritto in C per un bel po '...)

+0

Sono dubbioso sull'ordine dei bit in un bitfield è specificato dallo standard. Immagino che questo codice sarebbe un comportamento definito dall'implementazione. Ma l'idea di usare una 'struct' è buona: definire un' struct uart' che contiene il layout di memoria del dispositivo mappato in memoria, assegnare l'indirizzo a un singolo puntatore 'uart * const uart_regs = 0x4000c000;', e quindi accedere tramite quell'oggetto con 'uart_regs-> ctl &= ~1;'. – cmaster

1

Un'altra opzione che ho un po' come per le applicazioni embedded è utilizzare il linker per definire le sezioni per i dispositivi più complessi e mappare la variabile su tali sezioni. Questo ha il vantaggio che se si prendono di mira più dispositivi, anche dallo stesso fornitore come TI, in genere è necessario modificare i file linker su base dispositivo per dispositivo. Ad esempio, diversi dispositivi della stessa famiglia hanno quantità diverse di memoria interna diretta mappata, e board-board potrebbe avere diverse quantità di ram e hardware in posizioni diverse. Ecco un esempio dalla documentazione GCC:

Normalmente, il compilatore inserisce gli oggetti che genera nelle sezioni come i dati e bss. A volte, tuttavia, sono necessarie sezioni aggiuntive, o è necessario che determinate variabili particolari vengano visualizzate in speciali sezioni , ad esempio per mappare su hardware speciale. L'attributo della sezione specifica che una variabile (o funzione) risiede in una particolare sezione . Ad esempio, questo piccolo programma utilizza diversi nomi delle sezioni specifiche:

 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); 
     } 

utilizzare l'attributo sezione con le variabili globali e le variabili non locali, come mostrato nell'esempio.

È possibile utilizzare l'attributo sezione con variabili globali inizializzati o non inizializzati ma il linker richiede ogni oggetto da definire una volta, con l'eccezione che le variabili non inizializzate provvisoriamente vanno nella sezione comune (o BSS) e possono essere moltiplicano “ definito”. Utilizzando l'attributo sezione cambierà quale sezione variabile e va in può causare il linker di emettere un errore se una variabile non inizializzata ha più definizioni. È possibile forzare una variabile da inizializzare con il flag -fno-common o l'attributo nocommon.