Compilazione del codice da eseguire da RAM esterna


13

Sto prendendo in considerazione i progetti per un sistema di gioco minimalista basato su un PIC18F85J5. Parte del mio progetto è che i giochi possono essere caricati da una scheda SD senza riprogrammare il chip o eseguire il flashing della memoria del programma. Ho scelto quel chip perché ha un'interfaccia di memoria esterna che mi permetterà di eseguire il codice da una SRAM esterna.

L'idea di base è che la memoria interna del programma conterrà un'interfaccia per sfogliare la scheda SD, e una volta che l'utente seleziona un programma copierà un file esadecimale dalla scheda SD al ram esterno, quindi salterà l'esecuzione nello spazio del ram esterno .

La memoria interna del programma avrà anche varie librerie per la grafica, l'input del controller e altre varie utilità.

Sono abbastanza sicuro di sapere come far funzionare bene le parti interne del firmware. Il problema è la creazione di programmi da eseguire dalla RAM esterna. Non ha lo stesso effetto del targeting di una foto normale e deve essere consapevole delle funzioni di libreria disponibili nella memoria interna, ma non ricompilarle, ma solo collegarle. Deve anche iniziare a utilizzare gli indirizzi subito dopo i 32k di flash interno, non a zero. C'è un buon modo per compilare un programma usando questi tipi di vincoli?

Sto usando l'IDE MPLab, ma non ne ho molta familiarità o come fare questo tipo di personalizzazione.


Una delle migliori domande che ho visto qui da un po '... Non vedo l'ora di ascoltare le idee e le risposte.
Jon L

Alcuni interessanti approcci alternativi sono discussi in electronics.stackexchange.com/questions/5386/…
Toby Jaffey,

L'ho visto, ma per lo più raccomandano di scrivere su Flash, cosa che vorrei evitare. Il mio design hardware assomiglierà alla figura 6 nella nota dell'applicazione nella risposta accettata.
captncraig,

Risposte:


8

Hai due problemi separati:

  1. Creazione del codice per l'intervallo di indirizzi RAM esterni.

    Questo è in realtà molto semplice. Tutto quello che devi fare è creare un file linker che contenga solo gli intervalli di indirizzi che vuoi che il codice occupi. Nota che non è solo necessario riservare un determinato intervallo di indirizzi di memoria del programma per queste app esterne, ma anche un po 'di spazio RAM. Questo spazio RAM, come gli indirizzi di memoria del programma, deve essere fisso e noto. Basta rendere disponibili solo quegli intervalli di indirizzi fissi e noti per l'uso nel file linker. Non dimenticare di renderli NON disponibili nel file linker del codice di base.

    Dopo che il codice di base ha caricato una nuova app nella memoria esterna, deve sapere come eseguirla. La cosa più semplice è probabilmente avviare l'esecuzione nella prima posizione RAM esterna. Ciò significa che il tuo codice avrà bisogno di una sezione CODICE a quell'indirizzo iniziale assoluto. Questo contiene un GOTO sull'etichetta giusta nel resto del codice, che sarà tutto trasferibile.

  2. Collegamento di app alle routine di libreria nel codice di base.

    Non esiste un modo immediatamente semplice per farlo con gli strumenti Microchip esistenti, ma non è poi così male.

    Un problema molto più grande è come si desidera gestire le modifiche al codice di base. La strategia semplicistica consiste nel costruire il codice di base, eseguire un programma sul file della mappa risultante per raccogliere gli indirizzi globali, quindi scrivere un file di importazione con istruzioni EQU per tutti i simboli definiti a livello globale. Questo file di importazione verrebbe quindi incluso in tutto il codice dell'app. Non c'è nulla da collegare, poiché il codice sorgente dell'app contiene essenzialmente i riferimenti di indirizzo fisso ai punti di ingresso del codice di base.

    È facile da fare e funzionerà, ma considera cosa succede quando cambi il codice di base. Anche una piccola correzione di bug potrebbe causare lo spostamento di tutti gli indirizzi, quindi tutto il codice dell'app esistente non sarebbe buono e dovrebbe essere ricostruito. Se non prevedi mai di fornire aggiornamenti del codice di base senza aggiornare tutte le app, forse puoi farcela, ma penso che sia una cattiva idea.

    Un modo migliore è avere un'area di interfaccia definita in corrispondenza di un indirizzo noto fisso scelto nel codice di base. Ci sarebbe un GOTO per ogni subroutine che il codice dell'app può chiamare. Questi GOTO verrebbero posizionati su indirizzi noti fissi e le app esterne chiamerebbero solo quelle posizioni, che poi salterebbero ovunque la subroutine finisse effettivamente con quella build del codice base. Questo costa 2 parole di memoria di programma per subroutine esportata e due cicli extra in fase di esecuzione, ma penso che ne valga la pena.

    Per fare ciò, è necessario automatizzare il processo di generazione dei GOTO e il risultante file di esportazione che le app esterne importeranno per ottenere gli indirizzi della subroutine (in realtà GOTO redirector). Potresti avere a che fare con un uso intelligente delle macro MPASM, ma se lo facessi, utilizzerei sicuramente il mio preprocessore poiché può scrivere su un file esterno in fase di preelaborazione. È possibile scrivere una macro di preprocessore in modo che ogni redirector possa essere definito da una singola riga di codice sorgente. La macro fa tutte le cose cattive sotto il cofano, che è generare GOTO, il riferimento esterno alla routine di destinazione effettiva e aggiungere la riga appropriata al file di esportazione con l'indirizzo costante noto di quella routine, tutti con nomi appropriati. Forse la macro crea solo un mucchio di variabili preprocessore con nomi regolari (una specie di array espandibile di runtime), e quindi il file di esportazione viene scritto una volta dopo tutte le chiamate macro. Una delle molte cose che il mio preprocessore può fare che le macro MPASM non possono fare è fare la manipolazione di stringhe per costruire nuovi nomi di simboli da altri nomi.

    Il mio preprocessore e un sacco di altre cose correlate sono disponibili gratuitamente su www.embedinc.com/pic/dload.htm .


Mi piace molto l'idea di un tavolo da salto fisso. Potrei semplicemente iniziare dalla fine della memoria flash e avere posizioni fisse per ogni chiamata di sistema. Potrei anche probabilmente cavarmela con un file di intestazione gestito manualmente con gli indirizzi di tutte le subroutine se non riesco a capire tutto quel voodoo del preprocessore che descrivi.
captncraig,

5

Opzione 1: lingue interpretate

Questo non risponde direttamente alla domanda (che è un'ottima domanda, a proposito, e spero di imparare da una risposta che la indirizzi direttamente), ma è molto comune quando si realizzano progetti che possono caricare programmi esterni in cui scrivere i programmi esterni un linguaggio interpretato. Se le risorse sono limitate (quali saranno su questo processore, hai pensato di utilizzare un PIC32 o un piccolo processore ARM per questo?), È comune limitare la lingua a un sottoinsieme della specifica completa. Ancora più in basso nella catena sono linguaggi specifici di dominio che fanno solo alcune cose.

Ad esempio, il progetto elua è un esempio di linguaggio interpretato a bassa risorsa (64 kB RAM). Puoi comprimerlo fino a 32k di RAM se rimuovi alcune funzionalità (Nota: non funzionerà sul tuo attuale processore, che è un'architettura a 8 bit. L'uso della RAM esterna sarà probabilmente troppo lento per la grafica). Fornisce un linguaggio veloce e flessibile in cui i nuovi utenti potrebbero programmare facilmente i giochi se si fornisce un'API minima. C'è molta documentazione disponibile per la lingua online. Ci sono altre lingue (come Forth e Basic) che potresti usare in modo simile, ma penso che Lua sia l'opzione migliore al momento.

Allo stesso modo, potresti creare il tuo linguaggio specifico per il dominio. Dovresti fornire un'API più completa e una documentazione esterna, ma se i giochi fossero tutti simili, non sarebbe troppo difficile.

In ogni caso, il PIC18 probabilmente non è il processore che userei per qualcosa che coinvolge programmazione / scripting e grafica personalizzati. Potresti avere familiarità con questa classe di processori, ma suggerirei che sarebbe un buon momento per usare qualcosa con un driver dello schermo e più memoria.

Opzione 2: basta riprogrammare il tutto

Se, tuttavia, stai già programmando di programmare tutti i giochi da solo in C, non preoccuparti di caricare solo la logica di gioco dalla scheda SD. Hai solo 32kB di Flash da riprogrammare e potresti facilmente ottenere una scheda microSD da 4 GB per questo. (Nota: le schede più grandi sono spesso SDHC, che è più difficile da interfacciare). Supponendo che tu usi ogni ultimo byte del tuo 32 kB, che lasci spazio sulla scheda SD per 131.072 copie del tuo firmware con qualsiasi logica di gioco ti serva.

Esistono molte note per scrivere bootloader per PIC, come AN851 . Dovresti progettare il tuo bootloader per occupare un'area specifica di memoria (probabilmente la parte superiore dell'area di memoria, specificala nel linker) e specifica che i progetti firmware completi non raggiungono questa area. L'appnote lo spiega in modo più dettagliato. Sostituisci semplicemente "Sezione di avvio del PIC18F452" con "Sezione di avvio specificata nel linker" e tutto avrà senso.

Quindi, il tuo bootloader deve solo consentire all'utente di selezionare un programma da eseguire dalla scheda SD e copiare il tutto. Un'interfaccia utente potrebbe essere che l'utente deve tenere premuto un pulsante per accedere alla modalità di selezione. Di solito, il bootloader controlla semplicemente lo stato di questo pulsante al ripristino e, se non viene tenuto premuto, si avvia nel gioco. Se viene tenuto premuto, dovrebbe consentire all'utente di scegliere un file sulla scheda SD, copiare il programma e continuare l'avvio nel [nuovo] gioco.

Questa è la mia attuale raccomandazione.

Opzione 3: Magia profonda che coinvolge la memorizzazione di solo una parte del file esadecimale

Il problema con il meccanismo previsto è che il processore non si occupa delle API e delle chiamate di funzione, si occupa di numeri - indirizzi ai quali il puntatore dell'istruzione può saltare e si aspetta che ci sia un codice che esegue una chiamata di funzione secondo una specifica API. Se provi a compilare solo una parte del programma, il linker non saprà cosa fare quando chiami check_button_status()o toggle_led(). Potresti sapere che quelle funzioni esistono nel file esadecimale sul processore, ma deve sapere esattamente a quale indirizzo risiedono.

Il linker già suddivide il codice in più sezioni; potresti teoricamente suddividerlo in sezioni aggiuntive con alcuni -sectione #pragmaincantesimi. Non l'ho mai fatto e non so come. Fino a quando i due metodi precedenti non mi mancheranno (o qualcuno pubblicherà una risposta fantastica qui), probabilmente non imparerò questo meccanismo e quindi non posso insegnartelo.


La mia obiezione al numero 2 è che la memoria flash ha un numero limitato di cicli di cancellazione nella durata del chip. Non voglio usare un MCU più robusto perché sto andando per il minimalismo a 8 bit.
captncraig,

@CMP - Ha almeno 10.000 cicli di cancellazione. Se qualcuno gioca un gioco diverso ogni giorno, durerà fino all'anno 2039. Il Flash quasi sicuramente sopravvivrà al resto del circuito. Non penso che tu debba preoccuparti di questo a meno che non vada in sala giochi per essere giocato dozzine di volte ogni giorno.
Kevin Vermeer,

1
In secondo luogo, il minimalismo a 8 bit può sembrare interessante, ma fa schifo per la programmazione. È molto più facile ottenere un robusto microcontrollore e farlo sembrare retrò piuttosto che essere costretto a farlo sembrare retrò perché stai spingendo i limiti del tuo processore.
Kevin Vermeer,

1
Entrambi i punti molto giusti. Anche se si tratta di un basso numero di parti, un pic32 non è così diverso in termini di costi o componenti esterni rispetto a questo, e se ha 512K di flash interno, vince persino.
captncraig,

Sembra una soluzione pratica sarebbe usare un pic32 e scrivere un bootloader per il reflash dalla scheda SD. Avrei difficoltà a riutilizzare le funzioni di cui avrebbero bisogno sia il bootloader che il codice utente, ma fintanto che lo tengo nel flash di avvio 12k, dovrebbe fornire al codice utente l'intero chip e possono includere tutte le librerie che vogliono alla fonte livello.
captncraig,
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.