Domanda:
Comprimi il file .hex per il microcontrollore
Danial
2019-10-12 15:53:18 UTC
view on stackexchange narkive permalink

Attualmente sto scrivendo un programma in AVR Studio , ecco la build * Uso della memoria: *

  Dispositivo: atmega32
    Programma: 9304 byte (28,4% pieno)
    (.text + .data + .bootloader)
    Dati: 334 byte (16,3% pieno)
    (.data + .bss + .noinit)
 

Dato che sto usando un microcontrollore ATmega32 , non sembra essere un problema, ma voglio usare lo stesso codice e lo uso per un micro ATmega8 -controllore.

Quindi voglio ridurre le dimensioni del programma meno di 8192 bytes.

Come posso farlo?

Stai usando la libreria in virgola mobile?
@Danial questo non risolve il tuo problema, ma noterò solo che il file .hex è solo un formato in cui memorizzare il codice del programma. Sono solo testo, quindi si comprimerebbero bene.Ma non è quello che finisce nel flash dell'MCU, il codice del programma effettivo lo è.Quindi _questo è_ quello che vuoi rimpicciolire, non il file .hex in quanto tale.
Sette risposte:
Oldfart
2019-10-12 15:59:56 UTC
view on stackexchange narkive permalink

Puoi non comprimere il codice esadecimale, puoi solo provare a ridurlo.

  • Provare in modo diverso?impostazioni del compilatore (ottimizzazione massima e ottimizzazione delle dimensioni)
  • Scegli il codice sorgente e guarda cosa può essere ottimizzato o omesso.
  • Controlla se viene inserito del codice di libreria non necessario. (Non dovrebbe esserlo, ma chi lo sa)

Buon punto da Jeroen3: controlla se hai bisogno / hai un punto mobile.Soprattutto funzioni come printf a volte inseriscono il codice in virgola mobile per poter gestire printf ("% f" ... . Se non usi mai numeri in virgola mobile nonne ho bisogno.

Michel Keijzers
2019-10-12 16:04:03 UTC
view on stackexchange narkive permalink

L'MCU non può eseguire codice compresso.

Tuttavia, ci sono alcune cose che puoi fare:

  • Invece di utilizzare le funzioni di libreria a tutti gli effetti, crea tu stesso alcune o tutte le funzioni;in questo modo puoi ottimizzare le funzioni della libreria che sono per lo più (troppo) flessibili per le tue esigenze specifiche.
  • Rimuovi il codice duplicato nel tuo codice.Utilizza i parametri per il codice quasi duplicato per renderlo flessibile.
  • Utilizza il tipo più piccolo per le costanti e in particolare per gli array di costanti.
  • Rimuovi il codice morto "ovvio" (codice che non può mai essere eseguito), vedi l'osservazione di Jeroen3 e Dakkaron di seguito.(controllare se ciò si verifica per funzioni complete o parte di funzioni).Vedi anche questo link.
  • Diminuisci le stringhe (nel caso in cui utilizzi molte istruzioni print, minimizza queste stringhe costanti se possibile).
La rimozione del codice morto è una [funzione del linker] (https://renenyffenegger.ch/notes/development/languages/CC-plus-plus/GCC/options/Wl/index): ** - Wl, --gc-section ** che si applica anche alle funzioni di libreria inutilizzate.
@Jeroen3 Grazie, non sapevo fosse così intelligente.Ho aggiornato la mia risposta (con un commento al tuo nome).
@Jeroen3 Funziona solo per codice morto "duro", quindi codice in cui il compilatore può dedurre che è morto.Non funziona per codice morto che il compilatore non può determinare, ad es.del codice che viene eseguito solo se la funzione viene chiamata con un parametro specifico, ma la funzione non viene mai chiamata con quel parametro.Quindi è sicuramente utile usare il controllo del linker, ma è anche utile analizzare il codice a mano per codice morto.
@Dakkaron Grazie ... ho aggiunto il tuo nome al commento (in realtà avevo qualcosa di simile a quello che hai scritto prima).
Michael Karas
2019-10-12 19:41:12 UTC
view on stackexchange narkive permalink

Oltre agli ottimi suggerimenti forniti nelle altre risposte qui, voglio commentare che può esserci un'enorme differenza nel modo in cui i compilatori (e linker) possono ottimizzare il codice.

Ho lavorato in un'azienda alcuni anni fa in cui il prodotto utilizzava ATMega8. Quando sono arrivato sulla scena questo prodotto aveva tre diverse build di codice sorgente per fornire set separati di funzionalità per varie configurazioni di prodotto. Il codice sorgente è stato compilato utilizzando un compilatore C a basso costo e ogni set di codice rientrava a malapena negli 8K byte della memoria FLASH del dispositivo.

Ho chiesto all'azienda di acquistare un compilatore di fascia alta da un'azienda ben nota nella comunità AVR. Poi sono andato a lavorare sui codici sorgente del software e ho impostato le opzioni del compilatore per la massima ottimizzazione.

Quando ho terminato lo sforzo, tutte le opzioni del prodotto si adattavano a un'unica immagine che era inferiore agli 8K byte. In effetti c'era abbastanza spazio per me per aggiungere un UART software di sola trasmissione al codice che il software potrebbe riversare informazioni interne che sono state utilizzate per aiutare a calibrare i parametri del prodotto. L'uscita UART è stata attivata quando un segnale da 28 V è stato applicato a uno dei canali A / D tramite un partitore di tensione. Il trigger era necessario perché l'uscita UART del software utilizzava un GPIO che normalmente era un segnale che era un'uscita dal prodotto.

Bella storia.I compilatori possono fare del buon lavoro, ma lo sforzo umano può aiutare.In modo simile ho rilevato un progetto utilizzando un processore PIC a 8 bit di un cliente in cui versioni diverse erano separate in tre codici sorgente simili: la memoria del programma era praticamente piena per ogni versione (memoria del programma 8k, piena all'85% circa).Ho unito i codici, ottimizzato il codice C stesso controllando il codice assembly generato e aggiunto molte funzioni nel tempo (circa il 98% pieno).Lanciare un compilatore migliore avrebbe potuto aiutare un po ', ma non tanto quanto le ottimizzazioni del codice.
Graham
2019-10-13 01:02:23 UTC
view on stackexchange narkive permalink

Il primo passo per qualsiasi tipo di ottimizzazione è scoprire cosa sta facendo .

La tua prima mossa dovrebbe essere quella di far sì che il linker esegua il dump dell'indirizzo di ogni identificatore nella build.Sono tutte funzioni e tutte le variabili.Il tuo linker dovrebbe anche essere in grado di segnalare le dimensioni delle funzioni;probabilmente non farà le dimensioni delle variabili, ma puoi dedurre quelle dall'indirizzo della variabile successiva nell'elenco.

Una volta che sai dove sta andando il tuo spazio, puoi fare qualcosa al riguardo.Le probabilità sono abbastanza buone, la soluzione sarà ovvia quando inizierai a guardare ciò che è più grande.

Fino a quando non lo saprai, stai solo sparando alla cieca, e non è mai un buon piano.

filo
2019-10-13 15:39:09 UTC
view on stackexchange narkive permalink

Non esiste un modo "pratico" per eseguire codice compresso su un AVR, quindi il problema diventa "come ottimizzo le dimensioni del mio firmware".

Trucchi della toolchain (cioè non è necessario modificare il codice):

  1. Qual è il livello di ottimizzazione del compilatore? In gcc l'opzione per ottimizzare per la dimensione minima si chiama -Os

  2. Esiste una funzione chiamata ottimizzazione del tempo di collegamento che può ulteriormente ottimizzare le dimensioni. È già menzionato in questa risposta.

  3. Il linker può ottimizzare i dati e i simboli inutilizzati. È abilitato utilizzando -Wl, - gc-sezioni -ffunction-sezioni -fdata-sezioni

  4. Abilita -mcall-prologues . Spiegato qui.

Trucchi per codici generici:

  1. Esegui nm -S --size-sort -t d your_output_file.elf . Questo comando mostrerà quanto è grande ogni simbolo (simbolo in linker-speak significa dati o codice). Puoi quindi scoprire qual è la maggiore opportunità di ottimizzazione.

  2. Prova a trovare parti di codice che potrebbero diventare funzioni da sole.

  3. Evita di utilizzare printf . Se hai solo bisogno di convertire numeri interi in stringhe, itoa è un'opzione. Puoi anche controllare xprintf che è un'alternativa più leggera allo standard printf.

  4. Se stai usando numeri in virgola mobile (float, double), prova a convertire il codice in numeri interi. Ad esempio, se hai bisogno di 2 cifre decimali puoi usare un semplice ridimensionamento di 100 (cioè 2,5 diventa 250). Qualsiasi operazione in virgola mobile (semplice come float x = a + b ) su un AVR attira una pila di codice di libreria, perché la CPU non supporta tali operazioni nell'hardware.

sktpin
2019-10-14 19:50:09 UTC
view on stackexchange narkive permalink

Se stai cercando modi per ridurre la dimensione del codice del tuo programma - oltre ad avere il compilatore ottimizzato & linker che ne taglia via alcuni e non usare le funzioni di libreria standard, come altri hanno notato, dipende anche dalla composizione del codice del programma quanto sarà grande.

  • per prima cosa, cerca di trovare un modo per mostrarti quali parti del tuo programma sono più offensive in termini di dimensioni. Purtroppo non ho familiarità con AVR studio, ma qui, user skeeve, post # 7 elenca le dimensioni dei singoli file oggetto. Se il tuo programma ha diversi moduli, saprai almeno quali di questi sono i più grandi e buoni candidati per l ' ottimizzazione manuale .
    • perché il refactoring manuale del codice, dici? Se puoi ottenere il codice più piccolo in questo modo, invece di utilizzare l'ottimizzazione del compilatore per quello, il debug (come nel passaggio attraverso il codice con GDB o simili) funzionerà molto meglio di quando l'ottimizzatore crea una netta differenza tra il codice reale eseguito e il tuo codice sorgente, rendendo le cose meno facili da seguire
  • ecco alcuni suggerimenti: vedi il capitolo "3 Suggerimenti e trucchi per ridurre la dimensione del codice"
  • evita il codice "copia & incolla"
    • cioè usa una routine per una sequenza di azioni che devi compiere in più di un posto, e poi chiamala da quei posti
    • questo può sembrare ovvio ("Ho usato funzioni, duh!"), ma può essere utile cercare posti nel codice che fanno quasi esattamente la stessa cosa, fanno diversi passaggi ogni volta, e tutto ciò che è diverso dipende solo da un paio di parametri che potrebbero essere argomenti di funzioni
    • Nota a margine: ci sono anche altri motivi per farlo: usa le routine regolarmente
  • (più disperato, lotta per un paio di byte extra ...) considera la conversione di switch / case più grandi o if..else..if..else ... blocchi (da cui vengono chiamate le funzioni) in un array const conpuntatori a funzione.Funziona solo se tutte le funzioni hanno la stessa firma, ovvero lo stesso tipo di puntatore, e calcolare un indice di matrice dal valore che decide cosa viene chiamato non aggiunge molto codice da solo.L'indicizzazione di un array e la chiamata a un puntatore a funzione possono produrre codice più piccolo rispetto a molti switch / case o if / else per lo stesso scenario.(per me lo ha fatto - su un Microblaze a 32 bit, però. YMMV)
Dvidunis
2019-10-13 02:34:56 UTC
view on stackexchange narkive permalink

Se il tuo progetto è costituito da più file sorgente (come la maggior parte dei progetti), puoi anche guardare Link Time Optimization (comunemente abbreviato LTO).

Apporta ottimizzazioni extra tra gli oggetti (al momento del collegamento, come suggerisce il nome).

Potresti cercare una specifica opzione "Ottimizzazione del tempo di collegamento" / "LTO" nel tuo IDE, o cercare un posto dove aggiungere i flag del compilatore.

Se il tuo compilatore è basato su GCC o Clang, puoi aggiungere il flag -flto sia durante la compilazione che durante il collegamento (sia CFLAGS che LDFLAGS se pertinente).

Questo può ottimizzare interi blocchi di codice (all'interno di funzioni) che non vengono chiamati, o ottimizzarli per uno specifico pattern di input.

Nota che se utilizzi la libreria standard, devi assicurarti di compilarla con LTO per ottenere grandi risparmi.

Puoi leggere ulteriori informazioni su LTO qui: https://en.wikipedia.org/wiki/Interprocedural_optimization
E un esempio dal kernel Linux qui: https://lwn.net/Articles/744507/



Questa domanda e risposta è stata tradotta automaticamente dalla lingua inglese. Il contenuto originale è disponibile su stackexchange, che ringraziamo per la licenza cc by-sa 4.0 con cui è distribuito.
Loading...