Risorse sulla scrittura di un codice C efficiente per i microcontroller? [chiuso]


15

Serve aiuto serio qui. Adoro programmare. Ultimamente ho letto un sacco di libri (come K&R) e articoli / forum online per il linguaggio C. Ho anche provato a esaminare il codice di Linux (anche se mi sono perso da dove cominciare, ma dare una sbirciatina a librerie di piccole dimensioni mi ha aiutato?).

Ho iniziato come programmatore Java e in Java è piuttosto semplice; Se i programmi diventano troppo grandi, suddividili in classi e poi ulteriormente in funzioni. Linee guida come, mantenere il codice leggibile e aggiungere commenti. Utilizzare tecniche di occultamento delle informazioni e OOP. Alcuni dei quali si applicano ancora per C.

Ho programmato il codice in C ora e finora riesco a far funzionare i programmi in un modo o nell'altro. Molte persone parlano di prestazioni / efficienza, algoritmo / progettazione, ottimizzazione e manutenibilità. Alcune persone insistono l'una sull'altra, ma per gli ingegneri del software non professionisti spesso senti qualcosa come ad esempio: lo sviluppatore del kernel Linux non prenderà semplicemente alcun codice.

La mia domanda è questa: ho intenzione di scrivere un codice per il microcontrollore a 8 bit senza sprecare risorse . Sappi che vengo da Java, quindi le cose non sono più le stesse ... risorse / libri / link / suggerimenti saranno molto apprezzati. Le prestazioni e le dimensioni ora contano. Risorse / Trucchi per un codice C efficiente (all'interno delle migliori pratiche) per i microcontroller a 8 bit?

Inoltre, inline assemblysvolge un ruolo vitale anche vicino allo standard dei microcontroller. Ma ci sono regole generali per l'efficienza che si applicano a tutti?

Ad esempio: register unsigned int variable_name;è preferito in charqualsiasi momento. O usando uint8_t se non hai bisogno di grandi numeri.

EDIT: Grazie mille per tutte le risposte e i suggerimenti. Apprezzo lo sforzo di tutti per la condivisione delle conoscenze.


2
Questo non è spesso il caso su un processore x86, ma su un microcontrollore, se vuoi assicurarti di ottenere tutte le ultime prestazioni, probabilmente vorrai usare invece assembly.
Rei Miyasaka,

3
@Rei: l'Assemblea realizzata a mano raramente, se mai, utilizza meno memoria ed è più veloce dei moderni compilatori C. È una perdita di tempo codificare in assembly quando può (come dovrebbe) essere fatto in C.
mattnz

1
@mattnz Per moderno, di quanto stai parlando? In tutta onestà non scrivo codice per un microcontrollore da quasi un decennio.
Rei Miyasaka,

2
Un semplice consiglio: in microncontrolli, è generalmente vero che "se è più semplice, è più veloce". Su chip complessi (ARM e superiori) l'hardware fa così tante ottimizzazioni che non si conoscono fino a quando non si esegue il test.
Javier

1
@ReiMiyasaka con un notevole aumento dei costi di manutenzione. Un buon compilatore C può produrre quasi lo stesso codice di un programmatore di grande esperienza.

Risposte:


33

Ho più di 20 anni di sistemi embedded, principalmente 8 e 16 micros. La risposta breve alla tua domanda è la stessa di qualsiasi altro sviluppo software: non ottimizzare fino a quando non sai di doverlo fare, quindi non ottimizzare fino a quando non sai cosa devi ottimizzare. Scrivi il tuo codice in modo che sia affidabile, leggibile e gestibile per primo. L'ottimizzazione precoce rappresenta un problema, se non maggiore, nei sistemi integrati

Quando programmi "senza sprecare risorse", consideri il tuo tempo una risorsa? In caso contrario, chi ti sta pagando per il tuo tempo e, se nessuno, hai qualcosa di meglio da fare con esso. Una volta scelta, qualsiasi progettista di sistemi embedded deve fare il costo dell'hardware rispetto al costo dei tempi di progettazione. Se spedirai 100 unità, usa un micro più grande, a 100.000 unità, un risparmio di $ 1,00 per unità equivale a 1 anno di sviluppo software (ignorando il time to market, i costi di opportunità ecc.), A 1 milione di unità, inizi ottenere il ROI per essere ossessivo sull'utilizzo delle risorse, ma fai attenzione perché molti progetti embedded non hanno mai raggiunto il segno di 1 milione perché hanno progettato di vendere 1 milione (investimento iniziale elevato con costi di produzione bassi) e sono falliti prima che arrivassero lì.

Detto questo, le cose che devi considerare e conoscere con i (piccoli) sistemi embedded, perché questi impediranno il suo funzionamento, in modi inaspettati, non solo lo faranno rallentare.

a) Stack: di solito hai solo una piccola dimensione dello stack e spesso dimensioni del frame dello stack limitate. Devi essere consapevole del tuo utilizzo dello stack in ogni momento. Attenzione, i problemi di stack causano alcuni dei difetti più insidiosi.

b) Heap - ancora una volta, heap di piccole dimensioni, quindi fai attenzione all'allocazione della memoria ingiustificata. La frammentazione diventa un problema. Con questi due, devi sapere cosa fai quando finisci - non succede su un sistema di grandi dimensioni a causa del paging fornito dal sistema operativo. cioè quando malloc restituisce NULL, lo controlli e cosa fai. Ogni malva ha bisogno di un assegno e di un gestore, codice gonfio ?. Come guida: non usarlo se esiste un'alternativa. La maggior parte dei sistemi di piccole dimensioni non utilizza la memoria dinamica per questi motivi.

c) Interrupt di processo - Devi sapere come gestirli in modo sicuro e tempestivo. Devi anche sapere come rendere sicuro il codice di rientro. Ad esempio, le librerie C standard generalmente non rientrano nuovamente, quindi non dovrebbero essere usate all'interno dei gestori di interrupt.

d) Assemblaggio - quasi sempre ottimizzazione prematura. Al massimo è necessaria una piccola quantità (in linea) per ottenere qualcosa che C non può fare. Come esercizio, scrivi un piccolo metodo nell'assemblaggio realizzato a mano (da zero). Fare lo stesso in C. Misurare le prestazioni. Scommetto che la C sarà più veloce, so che sarà più leggibile, mantenibile ed estendibile. Ora per la parte 2 dell'esercizio - scrivi un utile programma in assembly e C.
Come altro esercizio, dai un'occhiata a quanto kernal di Linux è assemblatore, poi leggi, il paragrafo seguente sul kernel di Linux.

Vale la pena sapere come farlo, potrebbe anche valere la pena di essere abili nelle lingue per uno o due micro comuni.

e) "register unsigned int nome_variabile", "register" è, ed è sempre stato, un suggerimento per il compilatore, non un'istruzione, nei primi anni '70 (40 anni fa), aveva senso. Nel 2012, è uno spreco di battute poiché i compilatori sono così intelligenti e le istruzioni per i microsfondi sono così complesse.

Tornando al tuo commento su Linux - il problema che hai qui è che non stiamo parlando di solo 1 milione di unità, stiamo parlando di centinaia di milioni, con una vita di sempre. Vale la pena dedicare tempo e costi di progettazione per ottenerlo il più umanamente possibile. Sebbene sia un buon esempio delle migliori pratiche ingegneristiche, sarebbe un suicidio commerciale per la maggior parte degli sviluppatori di sistemi embedded essere pedante come richiede Linux Kernal.


4
mattnz: questa è una delle risposte più belle sui siti .stackexchange.
Ahmed Masud

1
Non posso migliorare questa risposta. Potrei solo aggiungere che l'inserimento di un codice assembly raramente ha senso per le prestazioni, ma potrebbe avere senso per cose come colpire chip I / O o altri trucchi hardware che potrebbero non essere facili da fare in C.
Mike Dunlavey

@mattnz Grazie per la risposta ben scritta. +1
AceofSpades

1
L'assemblatore @MikeDunlavey è talvolta necessario per un tempismo esatto. Appena finito un overlay video che utilizza il bit banging per generare video NTSC su un pin I / O, il tempismo è in termini di - tensione alta per i cicli di 3 clock, quindi bassa per 6 quindi ....
Martin Beckett

@Martin: ha perfettamente senso. È da tanto tempo che non scrivo a quel livello.
Mike Dunlavey,

3

La tua domanda ("senza sprecare risorse") è troppo generica, quindi è difficile dare molti consigli. Preso alla lettera, se non vuoi sprecare risorse, forse dovresti fare un passo indietro e valutare se devi fare qualcosa, ovvero se puoi risolvere il problema in altri modi.

Inoltre, i consigli utili dipendono molto dai tuoi vincoli: che tipo di sistema stai costruendo e che tipo di CPU stai usando? È un sistema difficile in tempo reale? Quanta memoria hai per codice e dati? Supporta nativamente tutte le operazioni in C (in particolare, moltiplicazione e divisione) e per quali tipi? Più in generale: leggi l'intera scheda tecnica e capiscila .

Il consiglio più importante: mantienilo semplice.

Ad esempio: dimenticare strutture di dati complesse (hash, alberi, eventualmente anche elenchi collegati) e utilizzare array di dimensioni fisse. L'uso di strutture di dati più complicate è garantito solo dopo aver dimostrato mediante misurazione che gli array sono troppo lenti.

Inoltre, non sovrascrivere (qualcosa che gli sviluppatori Java / C # hanno la tendenza a fare): scrivere un codice procedurale semplice, senza troppi livelli. L'astrazione ha un costo!

Prendi confidenza con l'idea di utilizzare variabili globali e vai a [molto utile per le pulizie in assenza di eccezioni];)

Se hai a che fare con interruzioni, leggi di nuovo sul rientro. Scrivere il codice rientrante è molto banale.


3

Sono d'accordo con la risposta di Mattnz - per la maggior parte. Ho iniziato a programmare su 8085 oltre 30 anni fa, poi su Z80, quindi sono rapidamente migrato su 8031. Successivamente sono passato ai microcontrollori serie 68300, quindi 80x86, XScale, MXL e (fino a tardi) PICS a 8 bit, che ho indovina significa che sono tornato al punto di partenza. Per la cronaca, posso affermare che i FAE presso diversi importanti produttori di microprocessori usano ancora l'assemblatore, sebbene in modo orientato agli oggetti per un riutilizzo mirato del codice.

Quello che non vedo nella risposta approvata è una discussione sul tipo di processore target e / o sull'architettura proposta. È un amaro da 0,50 $ 8 con memoria limitata? È un core ARM9 con pipeline e 8 MB di flash? Coprocessore di gestione della memoria? Avrà un sistema operativo? Un ciclo while (1)? Un dispositivo consumer con una produzione iniziale di 100000 unità? Una start-up con grandi idee e sogni?

Anche se concordo sul fatto che i compilatori moderni svolgono un ottimo lavoro di ottimizzazione, non ho mai lavorato a un progetto in 30 anni in cui non ho fermato il debugger e visualizzato il codice assembly generato per vedere esattamente cosa stava succedendo sotto il cofano ( certamente un incubo quando pipeline e ottimizzazione entrano in gioco), quindi la conoscenza dell'assemblaggio è importante.

E non ho mai avuto CEO, vicepresidente di Engineering, cliente che non mi ha spinto a cercare di mettere un gallone in un contenitore quart o di risparmiare 0,05 $ utilizzando una soluzione software per risolvere un problema hardware (ehi è solo un software giusto? Cosa c'è di così difficile?). L'ottimizzazione della memoria (codice o dati) conterà sempre.

Il mio punto è che se si visualizza il progetto da un puro punto di vista della programmazione, si otterrà una soluzione con ambito più ristretto. Mattnz ha ragione: farlo funzionare, quindi farlo funzionare più veloce, più piccolo, migliore, ma è ancora necessario dedicare MOLTO tempo ai requisiti e ai risultati prima ancora di pensare alla codifica.


Ciao Gio, ti preghiamo di evitare HTML non necessari nei tuoi post e utilizzare invece la sintassi Markdown . Perché <br />potresti semplicemente premere invio e per i paragrafi lasciare una riga vuota tra di loro. Anche quando fai riferimento a un'altra risposta, aggiungi un link ad essa. A questo punto potrebbero esserci solo poche risposte ma potrebbero essercene di più, distribuite su molte pagine, e non sarà molto chiaro quale risposta intendi. Controlla la cronologia delle revisioni per vedere le mie modifiche.
yannis,

@Gio Grazie per aver menzionato altri fattori importanti. +1 :)
AceofSpades

+1 - Bella espansione della mia risposta.
mattnz,

1

Manttz answer pone molto bene i punti più importanti su come eseguire la programmazione "vicino all'hardware". Dopo tutto, questo è C.

Tuttavia, vorrei aggiungere che mentre la parola chiave rigorosa di "Class" non esiste in C - è abbastanza semplice pensare in termini di programmazione orientata agli oggetti in C anche quando si è vicini all'hardware.

Puoi considerare questa risposta: le migliori pratiche OO per i programmi C che spiegano questo punto.

Ecco alcune risorse che ti aiuteranno a scrivere un buon codice orientato agli oggetti in C.

un. Programmazione orientata agli oggetti in C
b. questo è un buon posto dove le persone si scambiano idee
c. ed ecco il libro completo

Un'altra buona risorsa che vorrei suggerirti è:

The Write Great Code Series . Questo è un libro di due volumi. Il primo libro tratta aspetti molto essenziali delle macchine di livello inferiore. Il secondo libro tocca "Pensare a basso livello - scrivere ad alto livello"


1

Hai qualche problema. prima vuoi che questo progetto / codice sia portatile? La portabilità costa prestazioni e dimensioni, la tua piattaforma di scelta e attività che stai implementando può tollerare le dimensioni in eccesso e le prestazioni inferiori?

Sì, assolutamente su una macchina a 8 bit che restituisce caratteri senza segno anziché ints o short senza segno, è un modo per migliorare prestazioni e dimensioni. Allo stesso modo su una macchina a 16 bit usare short senza segno e una macchina a 32 bit senza segno. Puoi facilmente capire se, ad esempio, se hai utilizzato ints ovunque non firmati per la portabilità attraverso il sistema che sta prendendo il sopravvento (ARM, ad esempio, spingendo verso il basso nella più bassa potenza, i mercati dei dispositivi più piccoli) quel codice è un enorme rom rom su un micro a 8 bit. Ovviamente potresti semplicemente usare unsigned senza int o short o char e lasciare che il compilatore scelga la dimensione ottimale.

Non solo assembly inline, ma linguaggio di assembly in generale. L'assemblaggio in linea è molto non portatile e più difficile da scrivere piuttosto che chiamare semplicemente una funzione asm. Sì, la configurazione delle chiamate viene bruciata e restituita, ma a un costo di sviluppo più semplice, migliore manutenzione e controllo. La regola si applica ancora, scriverla in asm solo se è davvero necessario, hai fatto il lavoro per concludere che l'output del compilatore è il tuo problema in quest'area e quanto guadagno in termini di prestazioni puoi ottenere facendolo a mano. Quindi torna alla portabilità e alla manutenzione, non appena inizi a mescolare C e asm e ogni volta che mescoli C e asm potresti danneggiare la portabilità e rendere il progetto meno gestibile a seconda di chi ci sta lavorando o se questo è un prodotto che stai sviluppando ora e qualcun altro deve mantenere lungo la strada. Una volta effettuata tale analisi, sai automaticamente se devi andare in linea o con un assemblaggio diretto. Ho più di 25 anni di esperienza nel settore, scrivo ogni giorno miscele C e asm, vivo su / a livello hardware / software e, beh, non uso mai inline asm. Raramente vale la pena, troppo specifica per il compilatore, scrivo codice non specifico per il compilatore ogni volta che è possibile (quasi ovunque).

La chiave di tutta la tua domanda è smontare il tuo codice C. Scopri cosa fa il compilatore con il tuo codice e con il tempo, se lo desideri, puoi imparare a manipolare il compilatore in modo da generare il codice che desideri senza dover ricorrere a tanto. Con più tempo puoi imparare a manipolare il compilatore per produrre codice efficiente su più destinazioni, rendendo il codice più portatile senza dover ricorrere ad asm. Non dovresti avere problemi a capire perché il carattere senza segno funziona meglio come ritorno di stato da una funzione rispetto a un segno senza segno su un micro a 8 bit, allo stesso modo il carattere senza segno diventa più costoso su sistemi a 16 e 32 bit (alcune architetture ti aiutano fuori, alcuni non lo fanno).

Alcuni microcontrollori a 8 bit, tutti ?, sono molto ostili al compilatore e nessun compilatore produce un buon codice. Non c'è abbastanza domanda per creare un mercato di compilatori per quei dispositivi per creare un ottimo compilatore per quegli obiettivi, quindi i compilatori che sono lì sono lì per attrarre più programmatori business, non asm, e non perché il compilatore è meglio dell'asm scritto a mano . arm e mips che stanno entrando in questo mondo stanno cambiando quel modello dato che hai obiettivi che hanno compilatori che hanno fatto molto lavoro su di loro, compilatori che producono un codice abbastanza buono, ecc. Per i micro con processori come quelli ovviamente hai ancora nel caso in cui devi scendere ad asm, ma non è così spesso, è molto più semplice dire al compilatore cosa vuoi che faccia piuttosto che non usarlo. Tieni presente che manipolare un compilatore non è un brutto codice illeggibile, in realtà è il contrario, un bel codice pulito e semplice, forse riordinando alcuni elementi. Controllando le dimensioni delle tue funzioni e il numero di parametri, questo genere di cose fa una grande differenza nell'output del compilatore. Evita le caratteristiche del compilatore o del linguaggio, KISS, rendilo semplicemente stupido, spesso produce codice molto migliore e più veloce.


Non affermi il tipo di prodotti che produci, suppongo che sia un volume molto elevato, un margine basso o un mercato di nicchia specifico con margini folli. Questo è fondamentale per decidere a livello aziendale se dovresti usare un piccolo assemblatore micro a 8 bit e realizzato a mano, o un micro e C. più grande In risposta al tuo commento (cancellato?) - Lavoro con micro a 8 bit, tuttavia noi iniziare con un micro più grande del necessario e rivedere solo quando e se il costo della distinta base diventa un problema) Il time to market, i costi operativi e i costi di sviluppo ammortizzati ci consentono il lusso di aggiungere 10 o 20 centesimi alla distinta base.
Mattnz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.