Newsgroup: it.hobby.elettronica.digitale
Titolo: portabilita' codice in C

Salve!
Vorrei fare questa domanda rivolta a chi usa diverse famigli di micro (Marco 
Trapanese so che usa sia quelli della Microchip che queli AVR) scrivendo in 
C routine per i PIC queste sono utilizzabili con facilita' con gli AVR 
oppure bisogna riscriverle? 

Enzo wrote:

Qui c'è gente con molta più esperienza del sottoscritto. Volendo proprio 
dare i miei 0.02$ direi che la portabilità è relativa alla singola riga 
di programma considerata.
Funzioni in C che non interagiscono direttamente con il microcontroller 
possono essere utilizzate quasi senza modifiche non solo sugli AVR o PIC 
ma su qualsiasi compilatore C.
Quando invece si eseguono istruzioni che coinvolgono l'hardware 
specifico come ad esempio timer, ADC, interrupt, porte di I/O ecc... 
allora occorre riscrivere o adattare le istruzioni al nuovo target.
Di certo un codice C è più portabile di uno assembler, quanto lo sia in 
termini assoluti è difficile da valutare.
Ciao!
Marco / iw2nzm

 termini assoluti è difficile da valutare.
Quoto. Comunque a mio parere è una buona abitudine scrivere librerie e 
programmi con variabili definite internamente come UINT8, UINT16, UINT32 , 
ecc. e poi precederle con dei typedef (supportati da tutti i compilatori), 
definendo il tipo specifico. Per esempio, nel CCS C:
typedef unsigned int        UINT8;
typedef unsigned int16    UINT16;
typedef unsigned int32    UINT32;
e mikroC:
typedef unsigned short       UINT8;
typedef unsigned int           UINT16;
typedef unsigned long        UINT32;
aggiungo poi di evitare di utilizzare indirizzamenti immediati di argomenti, 
piuttosto usarne di diretti, che rendono il codice di gran lunga più 
leggibile:
// codice per il compilatore mikroC
#define  EXT_ADC_CS 
PORTA.F0
#define  EXT_ADC_ENABLE                                                 0
#define  EXT_ADC_DISABLE                                                1
#define  EXT_ADC_CHANNEL3_SELECT_MASK              0xF7   //numeri sparati a 
caso :)
#define  EXT_ADC_CHANNEL_SELECT_CMD                  0xFF
#define  EXT_ADC_START_CONVERSION_CMD             0x98
typedef unsigned long    UINT32;
UINT32 read_ext_adc() {
    UINT32 retval = 0;
    EXT_ADC_CS = EXT_ADC_ENABLE;
    spi_write(EXT_ADC_CHANNEL_SELECT_CMD & EXT_ADC_CHANNEL3_SELECT_MASK);
    spi_write(EXT_ADC_START_CONVERSION_CMD);
    //TODO : funzione lettura registro di conversione
    EXT_ADC_CS = EXT_ADC_DISABLE;
    return retval;
}
o addirittura definire una macro, per esempio, per chiamare la funzione di 
scrittura su spi hardware così da rendere il codice ancora più portabile 
potendolo modificare in testa al codice una tantum.
Forse per questo esempio ho un po' esagerato, l'ho scritto ora al volo :)
Occhio, infine, a come si usano strutture più avanzate, come per esempio le 
unioni, perchè si rischia di rendere di codice molto compatto e ottimizzato 
ma poco portabile (non tutti i compilatori le supportano, ed i tipi di dato 
possono essere molto diversi, quindi da trattare come una funzione di 
libreria, con typedef e compagnia bella), occhio anche alla ricorsione: non 
tutti i compilatori la supportano (anzi, quasi nessuno!), e occhio alle 
istruzioni NOP o delay_cycles(1), che dipendono dalla frequenza di lavoro 
del processore e del numero di colpi di clock per eseguire una istruzione!
spero di essere stato utile e non fuorviante
Ciao
-- 
____________________
      CG Engineering
 Lab. Ricerca & Sviluppo
********************
Skype : cg_ingegnering
MSN : 
cgaudiola...@interfree.it
ICQ UIN# : 104528066 

 }
Giusto. Diciamo forse meglio che *oltre* alla eventuale differenza di 
archietettura ci sono anche le differenze tra compilatori della stessa 
famiglia a rendere meno agevole il porting.
Ciao!
Marco / iw2nzm

 Marco / iw2nzm
Aggiungo la mia (deduzioni fatte leggendo le vostre risposte) in cambo 
embedded non e' proprio immediata la portabilita' 

 Salve!
Secondo me non puo' esserci portabilità, essendo il C per sistemi 
embedded, comunque, legato intimamente all'hardware.

 embedded, comunque, legato intimamente all'hardware.
Si ma in qualche post di tempo fa si diceva fra le cose positive nell' usare 
il C per sistemi embedded, fosse proprio l'estrema portabilita' del codice 
rispetto ad esempio al basic 

 
la mia esperienza spazia su tre architerrure ed almeno 5 o 6 compilatori 
differenti ( 8051 PIC18 8086 )
e' abbastanza chiaro che il C non puo' assicurare la portabilita' 
diretta di codice che accede all'hardware, non fosse altro per il fatto 
che l'HW e' differente da micro a micro ( anche all'interno della stessa 
famiglia nel caso degli 8051 )
ma esistono una serie di tecniche che rendono effettivamente possibile 
compilare un sorgente per piu' di una architettura.
una e' stata gia' citata : l'uso di tipi di dimensione nota al posto dei 
'generici' da realizzarsi con un opportuno lavoro di preprocessore ( 
compilazione condizionale ... )
un'altra tecnica che si puo' adottare e' l'interposizione di uno 
strat[erell]o d'interfaccia ed astrazione per l'hardware realizzabile 
spesso con un attento lavoro di macro.
in questa maniera e' possibile relegare la dipendenza dall'hardware in 
zone limitate e BEN nascoste ! dei sorgenti mantenendo la logica 
principale del codice pulito e portabile.
sarebbe piu' giusto dire che il C piu' che portabile in se permette di 
ricompilare facilmente per piu' di una piattaforma.
cosa invece piu' difficilmente realizzabile con i compilatori Basic per 
la scarsa standardizzazione degli stessi sia a livello di sintassi dell 
linguaggio sia a livello di primitive di programmazione di libreria
domanda : gli attuali compilatori Basic permettono l'uso di routine in 
altri linguaggi in fase di linking ed e' documentate la procedura da 
seguire o sono monolitici ?
tenete conto che questa opportunita' e' da sempre un punto di forza dei 
linguaggi di programmazione ( C in primis )

 embedded non e' proprio immediata la portabilita' 
Direi che la portabilità non è immediata in nessun campo se non scrivi 
codice pensandoci un po' prima.
Io scrivo tanto firmware per architetture, compilatori e processori 
diversi e devo dire che ottengo una portabilità del codice di un buon 90%.
Per ottenere la portabilità e riusabilità ci sono delle piccole cose da 
seguire:
- Il codice deve essere il più possibile modulare: spezzetta il 
programma in tanti file e tante funzioni corte che fanno poche cose 
semplici: sarà più facile riusarle.
- Quando scrivi il codice, pensalo e implementalo nel modo più generico 
possibile, senza fare assunzioni. Devi cercare di non pensare al fatto 
che la tua funzione girerà su quello specifico processore/compilatore. 
Evita quindi ottimizzazioni e workaround specifici a meno che non siano 
assolutamente richiesti. Prima scrivi codice generico, poi al limite lo 
ottimizzerai. Ricorda la massima: "l'ottimizzazione preventiva è 
l'origine di tutti i mali" :-)
- Quando ti ritrovi a dover fare una cosa simile a una già fatta in 
precedenza, cerca sempre di riusare il più possibile di quello che hai 
già. Invece di riscrivere e duplicare codice, modifica e rendi più 
generico quello vecchio. Cerca sempre di trovare un "minimo comune 
multiplo". All'inizio forse ci metterai più tempo che riscrivere tutto, 
ma con il tempo ti ritroverai tantissime cose già fatte. Questo è un 
esercizio che aiuta molto, con il tempo imparerai a scrivere codice già 
generico e riusabile all'inizio.
- Non usare *mai* estensioni proprietarie allo standard ANSI. Se proprio 
non è possibile fare senza, wrappa tutto con delle macro ridefinibili a 
seconda del compilatore.
- Quando scrivi dei driver cerca di saperare la parte di controllo 
dell'hardware da quella di gestione. Questo si può fare molto 
semplicemente ricorrendo a macro e costanti (#define).
Io per esempio ho un driver per un display LCD alfanumerico (hd44780) 
che funziona su 5-6 processori e compilatori diversi. La parte di 
gestione della logica di scrittura è comune a tutti, mentre ogni 
processore ha un file specifico per l'accesso ai pin di I/O. Quando 
cambio processore/compilatore devo solo riscrivere una 20na di righe di 
#define varie in un solo file.
- Vale sempre la regola: "ogni problema (informatico) può essere risolto 
con uno strato di astrazione in più".
Ciao!

 ecc. e poi precederle con dei typedef (supportati da tutti i compilatori), 
Molti anni fa adottavo un procedimento analogo, per i tipi di dati.
Dalla specifica C99 (ISO/IEC 9899:1999) preferisco di gran lunga utilizzare i
tipi gią suggeriti da ISO, come uint8_t, uint16_t, uint32_t, che alcuni
compilatori forniscono gią, con l'header stdint.h
 
http://en.wikipedia.org/wiki/Stdint.h
Con i compilatori che non lo supportano definisco io gli stessi tipi, cosģ da
mantenere uno standard di portabilitą certificato.
~ Lou