Come mantenere la modularità in C?

3

Sto scrivendo codice C con più moduli come display LCD, memoria flash e modulo GSM, ecc. Il progetto consiste di migliaia di righe di codice, in file diversi.

Il comportamento del sistema può essere modificato con i parametri di runtime (es. luminosità LCD) usando un pulsante. E durante la compilazione ridefinendo #define direttive e const variabili.

Attualmente tutte queste variabili e costanti si trovano con il resto dei moduli nei rispettivi file. Sto trovando molto difficile mantenere le impostazioni senza perdere la modularità.

La mia domanda è in due parti:

  1. Come posso rendere più accessibili i parametri del tempo di compilazione? Spostare tutte le variabili in un file separato renderebbe la vita più semplice rendendo tutte le impostazioni disponibili in un unico posto. Anche alcune impostazioni vanno in gruppi, quindi ha più senso cambiare un #define in un file invece di pescare per molti #defines in più file. Ma non sembra giusto distruggere la modularità in questo modo.
  2. Ho bisogno di scrivere un codice che salverà tutti i parametri di runtime sulla memoria flash e li recupererà al riavvio. Idealmente questo dovrebbe essere fatto nelle funzioni getter e setter. Ma il problema è che tutte le impostazioni e le configurazioni sono memorizzate in un settore nella memoria flash. Un parametro non può essere semplicemente riscritto senza cancellare la memoria flash. Quindi il modulo LCD non può cambiare la sua luminosità senza essere a conoscenza di tutti gli altri parametri. Ha più senso mantenere tutte le impostazioni in una struttura, ma il costo è spostare le variabili lontano dai moduli.

Qual è il modo migliore per affrontare questo problema senza compromettere la leggibilità e la modularità del codice.

    
posta Hassan Nadeem 12.06.2015 - 10:06
fonte

3 risposte

4

Posizionando tutti gli interruttori orari di compilazione in 1 file centrale, è facile individuarli e modificarli. Detto questo, gli switch temporali compilati possono creare confusione, in particolare se ne esiste una quantità significativa.

Ho lavorato su un progetto embedded in cui c'erano diverse schede per controllare hardware diversi, ma disponevano dello stesso MCU, protocolli di comunicazione, memoria flash e altre proprietà simili. Anziché utilizzare #defines per passare dalla creazione delle diverse versioni del firmware, ho inserito tutti i moduli core in una directory "Core" e i moduli specifici della scheda nelle proprie directory. Ho impostato il processo di compilazione per creare tutte le versioni del firmware contemporaneamente. Questo aiuta a prevenire un cambio di rottura quando si lavora sul componente "Core".

Una parte dello stesso progetto comportava il salvataggio di vari parametri in flash. Ogni modulo che doveva scrivere in flash aveva i propri parametri flash struct , insieme a una voce univoca in moduleID enum . Quando un modulo voleva salvare i suoi dati, passava un puntatore al parametro flash struct , la dimensione del struct (in byte) e il suo moduleID al modulo di memoria flash.

Il modulo di archiviazione flash guarda il puntatore struct passato come un blocco di memoria. Copierà i dati in flash e aggiungerà un'intestazione piccola che contiene moduleID e la dimensione della memoria. Quando un modulo desiderava i suoi dati da flash, passava il suo moduleID al modulo di archiviazione flash, e quindi il modulo di archiviazione flash sottoponeva a scansione il blocco flash per moduleID , caricava i dati nella RAM e quindi restituiva a il param chiamante. In questo modo, la memoria flash non aveva bisogno di sapere chi l'avesse chiamata. Tutto ciò che importava era il moduleID passato ad esso e la dimensione dei dati che stava consegnando.

    
risposta data 12.06.2015 - 14:55
fonte
1

(Suggerisco di eseguire una cross-compilazione su un Linux in esecuzione sul tuo laptop, ha molti buoni strumenti utili per lo sviluppo incrociato)

I parametri del tempo di compilazione sono probabilmente i migliori da inserire in un file di intestazione con #define , ma quel file di intestazione potrebbe essere generato (al momento della compilazione) con qualche configurazione script. (guarda forse in GNU autoconf per l'ispirazione, ma è spesso sufficiente generare tale header di configurazione da solo script di shell).

Le impostazioni potrebbero effettivamente rimanere in una memoria flash e in concreto le accederai come un struct singolo (contenente tutti i parametri impostabili). Probabilmente suggerirò di avere una funzione di impostazione che carica quel struct da flash a RAM, modificare i dati nella RAM e scriverlo in flash alla fine (BTW, la maggior parte dei BIOS su laptop o PC desktop lo fanno).

Potresti anche utilizzare alcune tecniche metaprogramming . Potresti generare (ad esempio usando qualche script in Guile o Python ...) al momento della compilazione un codice C per quel struct , la funzione di accesso, i messaggi di aiuto, ecc ...

Si noti che le versioni recenti GNU make sono molto configurabili (anche estendibili con Guile) , quindi la tua procedura di compilazione (cioè il tuo Makefile ) può fare cose abbastanza elaborate, in particolare generare parte del tuo codice C (nell'intestazione generata *.h file o generato *.c file)

    
risposta data 12.06.2015 - 12:57
fonte
1

In primo luogo, riconsiderare come si definiscono i limiti del modulo. "Impostazioni in fase di compilazione" è un modulo perfettamente ragionevole. Se ti sembra più pulito dividere una responsabilità, dovrebbe essere scisso. Come sottolineato da Basile, GNU autoconf è estremamente comune a questo scopo. Genera un file di intestazione che puoi includere altrove, se necessario. Fai attenzione a rendere il #ifdef blocchi il più grande possibile. Cerca di non condurli ovunque, e cerca di usare alternative se possibile.

In secondo luogo, è necessario definire le proprie astrazioni. Per qualche ragione, C ha la tendenza a creare un punto cieco nei programmatori, specialmente i programmatori che sono abituati a programmi di alto livello in lingue con enormi librerie.

Ciò significa che se desideri avere funzioni come write_setting_to_flash(LCD_BRIGHTNESS, brightness) e read_setting_from_flash(LCD_BRIGHTNESS) , allora scrivile . Scegli l'interfaccia che desideri e fallo. La persistenza delle impostazioni è una responsabilità separata da un driver. Dovrebbero essere gestiti separatamente.

Essenzialmente, ciò che vedi come un modulo (LCD) è in realtà 3 o 4 (driver, impostazioni di compilazione, persistenza flash). Se inizi a guardarlo in questo modo, il design dovrebbe cadere in posizione molto più pulita.

    
risposta data 12.06.2015 - 15:05
fonte

Leggi altre domande sui tag