2009-06-11 10 views
9

In molte applicazioni incorporate c'è un compromesso tra rendere il codice molto efficiente o isolare il codice dalla configurazione di sistema specifica per essere immune ai requisiti in evoluzione.Come rendere il vostro codice C incorporato immune alle modifiche dei requisiti senza aggiungere troppe spese generali e complessità?

Quali tipi di costrutti C si impiegano abitualmente per ottenere il meglio da entrambi i mondi (flessibilità e riconfigurabilità senza perdere efficienza)?

Se avete tempo, leggete per vedere esattamente di cosa sto parlando.

Quando stavo sviluppando SW embedded per i controllori di airbag, abbiamo avuto il problema di dover modificare alcune parti del codice ogni volta che il cliente cambiava idea riguardo ai requisiti specifici. Ad esempio, la combinazione di condizioni ed eventi che avrebbero innescato lo schieramento degli airbag è cambiata ogni due settimane durante lo sviluppo. Abbiamo odiato cambiare quel pezzo di codice così spesso.

A quel tempo, ho partecipato alla Conferenza sui sistemi embedded e ho ascoltato una brillante presentazione di Stephen Mellor intitolata "Far fronte ai mutevoli requisiti". Puoi leggere il documento here (ti fanno iscrivere ma è gratuito).

L'idea principale era quella di implementare il comportamento di base nel codice ma configurare i dettagli specifici sotto forma di dati. I dati sono qualcosa che puoi cambiare facilmente e possono anche essere programmabili in EEPROM o in una diversa sezione di flash.

Questa idea è sembrata ottima per risolvere il nostro problema. Ho condiviso questo con il mio collega e abbiamo immediatamente iniziato a rielaborare alcuni dei moduli SW.

Quando si tenta di utilizzare questa idea nella nostra codifica, abbiamo riscontrato alcune difficoltà nell'attuazione effettiva. I nostri costrutti di codice sono diventati terribilmente pesanti e complessi per un sistema embedded vincolato.

Per illustrare questo elaborerò l'esempio che ho citato sopra. Invece di avere una serie di istruzioni if ​​per decidere se la combinazione di input fosse in uno stato che richiedeva uno schieramento degli airbag, ci siamo spostati su una grande tabella di tabelle. Alcune delle condizioni non erano banali, quindi abbiamo usato molti indicatori di funzione per essere in grado di chiamare molte piccole funzioni di supporto che in qualche modo hanno risolto alcune delle condizioni. Abbiamo avuto diversi livelli di riferimento indiretto e tutto è diventato difficile da capire. Per farla breve, abbiamo finito per utilizzare un sacco di memoria, runtime e complessità del codice. Anche il debugging della cosa non era semplice. Il capo ci ha fatto cambiare alcune cose perché i moduli stavano diventando troppo pesanti (e forse aveva ragione!).

PS: C'è una domanda simile in SO, ma sembra che l'attenzione sia diversa. Adapting to meet changing business requirements?

+1

la sua introduzione è piuttosto lungo e non specifica la questione molto bene ... ma comunque è un buon compromesso. – jpinto3912

+0

concordato. Probabilmente otterrai più (e migliori) risposte se stringi un po 'la domanda. Rendilo facile da leggere. Poni la domanda per prima, e poi puoi darci il background pertinente e spiegare tutti i dettagli. Nessuno vuole leggere un romanzo prima ancora di scoprire quale sia la domanda * *) – jalf

+0

Penso che il titolo della domanda sia un po 'lungo: P –

risposta

3

Come un altro punto di vista sui requisiti di modifica ... i requisiti vanno a creando il codice. Allora perché non prendere una meta-approccio a questo:

  1. separare parti del programma che sono suscettibili di cambiare
  2. Creare uno script che incollare parti di fonte insieme

In questo modo si sono mantenendo blocchi logico di costruzione compatibili in C ... e poi attaccare le parti compatibili insieme alla fine:

/* {conditions_for_airbag_placeholder} */ 
if(require_deployment) 
    trigger_gas_release() 

poi mantenere condizioni indipendenti:

/* VAG Condition */ 
if(poll_vag_collision_event()) 
    require_deployment=1 

e un altro

/* Ford Conditions */ 
if(ford_interrupt(FRONT_NEARSIDE_COLLISION)) 
    require_deploymen=1 

vostro script di build potrebbe apparire come:

BUILD airbag_deployment_logic.c WITH vag_events 
TEST airbag_deployment_blob WITH vag_event_emitter 

Pensando ad alta voce davvero. In questo modo si ottiene un blob binario stretto senza leggere in config. Questo è un po 'come usare le sovrapposizioni http://en.wikipedia.org/wiki/Overlay_(programming) ma farlo in fase di compilazione.

+0

Grazie per l'intuizione. Ora che ne parli, questo approccio è quello che usiamo nelle centraline automotive per generare lo stack CAN o configurare il sistema operativo OSEK.Il fornitore del software CAN o OSEK offre uno strumento elaborato per generare codice configurato. Avremmo potuto fare qualcosa di simile nei moduli airbag. Lo strumento non ha bisogno di essere fantasioso. – guzelo

+0

I concetti di base a volte funzionano meglio, ho uno script Python che uso per tutti i tipi di cose come questa. Dai modelli CMS alla generazione del codice basata su emettitori di codice configurati. :) Risparmia un sacco di tempo quando hai solo bisogno di qualcosa di sporco e sporco –

2

Il nostro sistema è suddiviso in molti componenti, con configurazione a vista e punti di test. C'è un file di configurazione che viene letto all'avvio che in realtà ci aiuta a istanziare i componenti, collegarli tra loro e configurare il loro comportamento.

È molto simile a OO, in C, con l'occasionale trucco per implementare qualcosa come l'ereditarietà.

Nel mondo della difesa/avionica gli aggiornamenti del software sono controllati molto rigorosamente, e non è possibile aggiornare SW per risolvere i problemi ... tuttavia, per qualche strano motivo è possibile aggiornare un file di configurazione senza uno scontro importante. Quindi è stato davvero utile per noi essere in grado di specificare molte delle nostre implementazioni in quei file di configurazione.

Non c'è magia, solo una buona separazione delle preoccupazioni durante la progettazione del sistema e un po 'di lungimiranza da parte degli sviluppatori.

+0

Significa che stai utilizzando l'allocazione dinamica della memoria? Come si istanziano i componenti all'avvio? – guzelo

+0

Sì, allochiamo la memoria in modo dinamico, soprattutto all'avvio del sistema. I file di configurazione vengono letti, i componenti vengono allocati, le configurazioni vengono applicate e quindi i processi vengono generati per quei componenti che li richiedono. –

0

L'aggancio in un linguaggio dinamico può essere un vero toccasana, se si dispone della memoria e del processore.

Chiedi al C di parlare con l'hardware, quindi passa a un gruppo di eventi noti in una lingua come Lua. Chiedi allo script Lua di analizzare l'evento e richiamare le funzioni C appropriate.

Dopo aver eseguito correttamente il codice C, non sarà necessario toccarlo nuovamente a meno che l'hardware non cambi. Tutta la logica di business diventa parte della sceneggiatura, che a mio parere è molto più facile da creare, modificare e mantenere.

+2

Fintanto che PHP non si prende cura dei miei sistemi di airbag! –

+2

Questo sarebbe un problema in quanto le cose devono andare il più velocemente possibile. Questo è un problema in tempo reale. Se va un po 'più lentamente, la cosa peggiore che può capitare non è "un piccolo ritardo", piuttosto è una morte perché un airbag è stato schierato un millisecondo più tardi di quanto avrebbe dovuto. C è il livello più alto che puoi ottenere per questo tipo di sistemi. – Earlz

+0

La domanda non era formulata come specifica per i sistemi embedded in tempo reale, anche se l'esempio era uno. – patros

1

Suppongo che si possa specificare diversi comportamenti validi basati su un byte o una parola di dati che è possibile recuperare dalla EEPROM o da una porta I/O se necessario e quindi creare codice generico per gestire tutti gli eventi possibili descritti da quei byte.

Per esempio, se si ha un byte che specifica i requisiti per il rilascio l'airbag potrebbe essere qualcosa di simile:

Bit 0: collisione posteriore

Bit 1: Velocità sopra 55 mph (punti bonus per generalizzare il valore della velocità)

Bit 2:! passeggero in auto

...

Etc

Quindi si inserisce un altro byte che indica quali eventi sono accaduti e confronta i due. Se sono uguali, esegui il comando, altrimenti no.

+0

Se ricordo bene, questo è quello che abbiamo finito per fare. – guzelo

2

Cosa stai cercando di salvare esattamente? Sforzo di rielaborazione del codice? La burocrazia di una versione di software rilasciata?

È possibile che la modifica del codice sia ragionevolmente diretta e molto probabilmente più semplice rispetto alla modifica dei dati nelle tabelle. Spostare la logica che cambia spesso dal codice ai dati è utile solo se, per qualche motivo, è meno sforzo di modificare i dati piuttosto che il codice. Questo potrebbe essere vero se le modifiche sono meglio espresse in un modulo dati (ad esempio parametri numerici memorizzati in EEPROM). Oppure potrebbe essere vero se le richieste del cliente rendono necessario rilasciare una nuova versione del software, e una nuova versione del software è una procedura costosa da compilare (un sacco di documenti, o forse chip OTP masterizzati dal produttore di chip).

La modularità è un ottimo principio per questo tipo di cose. Sembra che tu lo stia già facendo in qualche modo. È bene mirare a isolare il codice che cambia spesso in un'area il più piccola possibile e cercare di mantenere separato il resto del codice (funzioni "helper") (modulare) e il più stabile possibile.

1

Per adattarsi alle mutevoli esigenze mi concentrerei sul rendere il codice modulare e facile da modificare, ad es. utilizzando macro o funzioni inline per i parametri che potrebbero cambiare.

W.r.t. una configurazione che può essere modificata indipendentemente dal codice, mi auguro che i parametri che sono riconfigurabili siano specificati anche nei requisiti. Soprattutto per cose importanti per la sicurezza come i controllori di airbag.

2

Non faccio in modo che il codice sia immune alle modifiche dei requisiti di per sé, ma taggo sempre una sezione di codice che implementa un requisito inserendo una stringa univoca in un commento. Con i tag dei requisiti in atto, posso facilmente cercare quel codice quando il requisito richiede un cambiamento. Questa pratica soddisfa anche un processo CMMI.

Ad esempio, nel documento dei requisiti:

Quello che segue è un elenco di requisiti relativi alla RST:

  • [RST001] Giulietta avvia la RST con 5 minuto di ritardo, quando l'accensione è disattivata.

E nel codice:

/* Delay for RST when ignition is turned off [RST001] */ 
#define IGN_OFF_RST_DELAY 5 

...snip... 

         /* Start RST with designated delay [RST001] */ 
         if (IS_ROMEO_ON()) 
         { 
          rst_set_timer(IGN_OFF_RST_DELAY); 
         } 
Problemi correlati