Domanda:
Utilizzo dello stack ISR ATTiny2313
JohnC
2009-11-10 04:35:04 UTC
view on stackexchange narkive permalink

Sto usando un ATTiny2313 per fungere da concentratore seriale. Ha solo 128 byte di RAM. Penso di stare esaurendo la RAM durante l'ISR. La mia domanda è quanta RAM (stack) utilizza un ISR per salvare il contesto (registri). Cioè se utilizzo gli ISR, quanto avrò lasciato su 128 byte. C'è un modo per rilevare l'overflow dello stack?

Una risposta:
Craig Trader
2009-11-10 06:15:52 UTC
view on stackexchange narkive permalink

Bene, controllando la documentazione ATTiny2313, a pagina 15, si afferma:

La risposta di esecuzione dell'interruzione per tutti gli interrupt AVR abilitati è di almeno quattro cicli di clock. Dopo quattro cicli di clock, viene eseguito l'indirizzo del vettore del programma per la routine di gestione degli interrupt effettiva. Durante questo periodo di quattro cicli di clock, il Program Counter viene inserito nello Stack. Il vettore è normalmente un salto alla routine di interrupt e questo salto richiede tre cicli di clock. Se si verifica un interrupt durante l'esecuzione di un'istruzione a più cicli, questa istruzione viene completata prima che l'interrupt venga servito. Se si verifica un interrupt quando l'MCU è in modalità sleep, il tempo di risposta dell'esecuzione dell'interrupt viene aumentato di quattro cicli di clock. Questo aumento si aggiunge al tempo di avvio dalla modalità sleep selezionata.

Un ritorno da una routine di gestione degli interrupt richiede quattro cicli di clock. Durante questi quattro cicli di clock, il Program Counter (due byte) viene ritirato dallo Stack, lo Stack Pointer viene incrementato di due e l'I-bit in SREG viene impostato.

Così stai davvero guardando solo 2 byte sullo stack durante un interrupt (il PC); qualsiasi altra cosa che un ISR mette in pila dipende dall'ISR stesso. Non mi aspetto che un gestore di interrupt ben scritto abbia bisogno di molto spazio nello stack.

Per quanto riguarda lo Stack Pointer stesso, a pagina 13, afferma:

Lo Stack viene utilizzato principalmente per memorizzare dati temporanei, per memorizzare variabili locali e per memorizzare indirizzi di ritorno dopo interrupt e chiamate di subroutine. Lo Stack Pointer Register punta sempre in cima allo Stack. Si noti che lo Stack viene implementato crescendo da posizioni di memoria superiori a posizioni di memoria inferiori. Ciò implica che un comando Stack PUSH riduce il puntatore dello stack.

Il puntatore dello stack punta all'area dello stack SRAM dei dati in cui si trovano gli stack di subroutine e interrupt. Questo spazio dello stack nella SRAM dei dati deve essere definito dal programma prima che vengano eseguite le chiamate di subroutine o che gli interrupt siano abilitati. Lo Stack Pointer deve essere impostato in modo che punti sopra 0x60. Lo Stack Pointer viene decrementato di uno quando i dati vengono inseriti nello Stack con l'istruzione PUSH e viene decrementato di due quando l'indirizzo di ritorno viene inserito nello Stack con la chiamata o l'interruzione della subroutine. Lo Stack Pointer viene incrementato di uno quando i dati vengono estratti dallo Stack con l'istruzione POP e viene incrementato di due quando i dati vengono estratti dallo Stack con il ritorno dalla subroutine RET o dal ritorno dall'interrupt RETI.

L'AVR Stack Pointer è implementato come due registri a 8 bit nello spazio I / O. Il numero di bit effettivamente utilizzati dipende dall'implementazione. Si noti che lo spazio dati in alcune implementazioni dell'architettura AVR è così piccolo che è necessario solo SPL. In questo caso, il registro SPH non sarà presente.

Nel tuo caso, penso che sia presente solo SPL (128 byte di RAM = 7 bit).

Oltre all'hardware, dipende dal tuo framework, che per la maggior parte delle parti AVR coinvolgerà GCC, GNU Binutils e avr-libc. Una rapida occhiata alle FAQ avr-libc ha portato a due buone domande:

http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage

Quali registri sono usati dal compilatore C?

  • Tipi di dati: char è di 8 bit, int è 16 bit, long è 32 bit, long long è 64 bit, float e double sono 32 bit (questo è l'unico formato in virgola mobile supportato), i puntatori sono 16 bit (i puntatori a funzione sono indirizzi di parole, per consentire l'indirizzamento fino a 128 KB del programma spazio di memoria). C'è un'opzione -mint8 (vedi Opzioni per il compilatore C avr-gcc) per rendere int 8 bit, ma non è supportata da avr-libc e viola gli standard C (int deve essere almeno 16 bit). Potrebbe essere rimosso in una versione futura.

  • Registri utilizzati tramite chiamata (r18-r27, r30-r31): Può essere allocato da gcc per i dati locali. Puoi usarli liberamente nelle subroutine dell'assembler. La chiamata di subroutine C può bloccarne una qualsiasi: il chiamante è responsabile del salvataggio e del ripristino.

  • Registri salvati delle chiamate (r2-r17, r28-r29): possono essere allocati da gcc per i dati locali. Chiamare le subroutine C le lascia invariate. Le subroutine assembler sono responsabili del salvataggio e del ripristino di questi registri, se modificati. r29: r28 (puntatore Y) viene utilizzato come puntatore al frame (punta ai dati locali sullo stack) se necessario. Il requisito per il chiamato di salvare / preservare il contenuto di questi registri si applica anche nelle situazioni in cui il compilatore li assegna per il passaggio di argomenti.

  • Registri fissi (r0, r1): Mai allocato da gcc per i dati locali, ma spesso usato per scopi fissi:

    r0 - registro temporaneo, può essere bloccato da qualsiasi codice C (eccetto i gestori di interrupt che lo salvano), può essere usato per ricordare qualcosa per un mentre all'interno di un pezzo di codice assembler

    r1 - supposto essere sempre zero in qualsiasi codice C, può essere usato per ricordare qualcosa per un po 'all'interno di un pezzo di codice assembler, ma deve poi essere cancellato dopo l'uso ( clr r1). Ciò include qualsiasi utilizzo delle istruzioni [f] mul [s [u]], che restituiscono il risultato in r1: r0. I gestori di interrupt salvano e cancellano r1 all'ingresso e ripristinano r1 all'uscita (nel caso fosse diverso da zero).

  • Convenzioni per le chiamate di funzione: Argomenti - allocati da sinistra a destra, r25 a r8. Tutti gli argomenti sono allineati per iniziare in registri con numeri pari (gli argomenti di dimensioni dispari, incluso char, hanno un registro libero sopra di loro). Ciò consente di fare un uso migliore dell'istruzione movw sul core avanzato.

Se sono troppe, quelle che non rientrano vengono passate in pila.

Valori di ritorno: 8 bit in r24 (non r25!), 16 bit in r25: r24, fino a 32 bit in r22-r25, fino a 64 bit in r18-r25. I valori di ritorno a 8 bit sono zero / segno estesi a 16 bit dalla funzione chiamata (il carattere senza segno è più efficiente del carattere con segno - solo clr r25). Gli argomenti delle funzioni con elenchi di argomenti variabili (printf ecc.) Vengono tutti passati in pila e char viene esteso a int.

http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_ramoverlap

Come rilevare la memoria RAM e i problemi di sovrapposizione delle variabili? Puoi semplicemente eseguire avr-nm sul tuo file di output (ELF). Eseguilo con l'opzione -n ​​e ordinerà i simboli numericamente (per impostazione predefinita, sono ordinati alfabeticamente).

Cerca il simbolo _end, che è il primo indirizzo nella RAM che non è allocato da un variabile. (avr-gcc aggiunge internamente 0x800000 a tutti gli indirizzi delle variabili data / bss, quindi ignora questo offset.) Quindi, il codice di inizializzazione run-time inizializza il puntatore dello stack (per impostazione predefinita) in modo che punti all'ultimo indirizzo disponibile nella SRAM (interna) . Pertanto, la regione tra _end e la fine di SRAM è ciò che è disponibile per lo stack. (Se la tua applicazione usa malloc (), che ad esempio può accadere anche all'interno di printf (), anche l'heap per la memoria dinamica si trova lì. Vedi Aree di memoria e Uso di malloc ().)

La quantità di stack richiesto per la tua applicazione non può essere determinato così facilmente. Ad esempio, se chiami ricorsivamente una funzione e dimentichi di interrompere quella ricorsione, la quantità di stack richiesta è infinita. :-) Puoi guardare il codice assembler generato (avr-gcc ... -S), c'è un commento in ogni file assembler generato che ti dice la dimensione del frame per ogni funzione generata. Questa è la quantità di stack richiesta per questa funzione, devi sommarla per tutte le funzioni in cui sai che le chiamate potrebbero essere annidate.

Sì, ma il compilatore avr gcc programmerà un salvataggio / ripristino del registro nell'ISR, no?
Forse, guarda cosa ho aggiunto su registro, stack e utilizzo della RAM. Se sei davvero preoccupato, ti suggerirei di generare il sorgente dell'assembly (gcc -S foo.c) e di esaminarlo in dettaglio.


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