Creative Commons BY-NC-ND 2.5Questo sito e tutto il suo contenuto sono distribuiti sotto la licenza Creative Commons Attribuzione - Non Commerciale - Non opere derivate 2.5 Italia e con le condizioni d'uso definite nel disclaimer: siete pregati di leggere entrambi questi documenti prima di usufruire dei contenuti di questo sito. Per alcuni contenuti è necessaria una registrazione gratuita: non è necessario pagare e non è necessario accumulare punteggi per accedere agli articoli e scaricare i sorgenti: basta solo essere onesti. Se volete che questo sito continui a rimanere attivo, a contribuire ogni giorno alla diffusione della cultura libera, non copiate il materiale per ripubblicarlo in altri luoghi. Se volete partecipare su settorezero e rendere le vostre idee, i vostri progetti, fruibili da tutti senza limitazioni potete farlo tranquillamente.

La gestione degli interrupt sui pic12 – pic16 – pic18 – pic24 – dspic

Autore: Giovanni Bernardo | Data pubblicazione: 11 dicembre 2010
Categorie: dsPIC / PIC24 PICmicro 10/12/16 PICmicro 18

La gestione degli interrupt è una cosa forse complicata ma che consente sicuramente di realizzare applicazioni molto complesse ed efficienti e spesso impensabili da eseguire tramite polling. Ho pensato quindi di fare cosa gradita eseguendo una comparazione tra le modalità di funzionamento e di gestione degli interrupt sulle varie fasce di pic. Questo, pertanto, vuole essere sia un articolo di approfondimento sugli interrupt che una terza raccolta di appunti dedicati all’esplorazione e alla migrazione verso i pic a 16 bit.

Riassumo brevemente cos’è un interrupt (o “interruzione” in italiano): è una condizione particolare, interna o esterna al pic, che consente di mettere in pausa il nostro programma principale e fare in modo che la CPU si occupi piuttosto di eseguire una porzione di codice associata alla particolare condizione che ha causato l’interruzione. Quando questo codice è stato eseguito (ovvero: l’interrupt è stato servito), la CPU può tornare ad eseguire il programma principale. Sfruttando opportunamente gli interrupt è possibile realizzare, verosimilmente, programmi multitasking, ovvero che eseguono più operazioni contemporaneamente.

Le differenze nella gestione degli interrupt variano, ovviamente, a seconda dell’architettura del pic e di conseguenza anche il codice da scrivere in C è differente. Per questa trattazione farò riferimento ai seguenti linguaggi:

  • PICC compiler (Hitec-C) per i pic12 e pic16 (fornito di serie con MPLAB)
  • MPLAB C18 per i pic18 (download)
  • MPLAB C30 per i pic24 e dsPic (download)

Se usate compilatori diversi da questi elencati, alcune cose, a livello di scrittura del codice, potrebbero essere diverse.

Ricordo qui alcuni concetti fondamentali riguardanti gli interrupt e da tenere bene a mente quando andremo ad affrontare la loro gestione. Le routine di gestione degli interrupt devono sempre avere i seguenti requisiti per tutte le tipologie di MCU:

  1. Non accettano parametri in ingresso e non restituiscono nulla in uscita (utilizzano sempre il tipo VOID)
  2. Non possono essere richiamate dal programma principale
  3. Devono essere più corte possibile. Qualora si richieda di eseguire istruzioni laboriose è sempre bene nell’ISR settare un flag che verrà poi controllato nel main per eseguire le operazioni associate all’evento di interruzione.
  4. Non devono richiamare altre funzioni

I punti 1 e 2 sono obbligatori, pena la mancata compilazione del programma, i punti 3 e 4 sono invece fortemente consigliati e ritenuti “moralmente” obbligatori.

Quando si verifica un interrupt, lo stato dei registri principali (che variano da pic a pic) viene generalmente salvato per poi essere ripristinato al termine della ISR (Interrupt Service Routine: ovvero la serie di istruzioni da eseguire nel momento in cui si verifica un’interruzione) in maniera tale da poter riprendere il programma nell’esatto punto e nell’esatto stato in cui era stato interrotto.

Al verificarsi di un interrupt, quindi, non viene eseguita subito la prima istruzione dell’ISR ma trascorre un certo lasso di tempo durante il quale vengono appunto eseguite queste operazioni di salvataggio di stato. Il tempo che intercorre tra il verificarsi dell’interrupt e la prima istruzione dell’ISR eseguita è detto tempo di latenza. I pic di fascia medio/alta hanno delle funzionalità hardware che permettono di diminuire i tempi di latenza velocizzando le operazioni di salvataggio di stato.

Ma come fa il pic a capire quali istruzioni deve eseguire al verificarsi di un interrupt e a quale punto tornare dopo che ne ha terminato l’esecuzione?

Il pic tiene conto delle istruzioni da eseguire nel nostro programma mediante un registro particolare chiamato PC (Program Counter – diviso in due parti). In tale registro la CPU carica la locazione di memoria nella quale è contenuta la successiva istruzione da eseguire: man mano che il programma va avanti, il PC viene incrementato.

Nel momento in cui si verifica un interrupt, la CPU deve mettere in pausa il programma principale e dedicarsi quindi ad eseguire le istruzioni definite nella nostra ISR: questo viene realizzato in automatico caricando nel PC il cosiddetto interrupt vector ovvero quella particolare locazione di memoria nella quale partono le istruzioni dell’ISR. In pratica la CPU viene dirottata in un’altra posizione per poter eseguire un codice diverso e poi ritornare al punto in cui si era fermata in precedenza.

E’ facile capire, quindi, che il PC può anche essere alterato via software ma le applicazioni che fanno uso di questa tecnica richiedono conoscenze molto approfondite.

La CPU per poi poter ritornare al punto in cui il programma è stato interrotto utilizza una serie di registri chiamati stack. In tali registri vengono memorizzate le locazioni di memoria a cui tornare dopo un’interruzione o dopo che è stato eseguito un salto qualsiasi (istruzioni GOTO o chiamate a funzioni esterne).

I registri dello stack sono sequenziali dall’alto verso il basso: ogni volta che si esegue un salto, l’ultima locazione di memoria a cui ritornare viene memorizzata verso il basso e a tal scopo c’è anche un registro aggiuntivo chiamato Stack Pointer (STKPTR) che permette di capire in che punto dello stack ritornare (o in quale punto dello stack memorizzare il prossimo ritorno). Si hanno quindi più livelli (più registri) di stack a seconda dell’architettura. Sui pic12 e pic16 di fascia bassa, ad esempio, ci sono 8 livelli di stack il che significa che possiamo annidare massimo 8 “salti”. Quando si annidano funzioni (una funzione che ne richiama un’altra, la quale ne richiama un’altra ancora e così via) si fa appunto utilizzo di salti e quindi dello stack. Nel momento in cui i salti diventano troppi e quindi si supera il livello massimo di stack offerto dalla MCU che stiamo usando, si verifica uno stack overflow che causa comportamenti imprevisti. Il compilatore in genere segnala questa possibilità con un warning durante la compilazione (possible stack overflow).

Alcuni pic utilizzano un set particolare di registri, chiamati registri shadow (o fast stack), che servono a memorizzare in maniera rapida lo stato di alcuni registri principali per poterli riportare alle condizioni precedenti l’interrupt una volta che questo è terminato. Non tutti i pic hanno questi registri e quelli che ce li hanno ne hanno uno per ogni registro da memorizzare durante un interrupt. La presenza di questa funzionalità rende le ISR più veloci (nel senso che hanno una latenza più bassa).

Le modalità con le quali i pic salvano il proprio stato al verificarsi di un interrupt sono sempre riportate sul datasheet alla voce Context saving during interrupts o Automatic contest saving (generalmente nel capitolo Special features of CPU o incluse nel capitolo riguardante gli interrupts.)

Dopo fatte queste premesse, analizzerò qui come i vari pic (e di conseguenza i loro compilatori) gestiscono gli interrupt.

Dispositivi Base-Line

Questa categoria non ha la funzionalità degli interrupt e ho incluso questo paragrafo giusto per spirito di completezza. A questa categoria appartengono i PIC10, che sono pic in formato SMD a 6 pin (o anche in formato DIP a 8 pin dei quali 2 però non sono connessi). Quindi se intendete utilizzare un pic10 per la vostra applicazione, sappiate che non avete a disposizione nessun tipo di interrupt.

Gestione degli interrupt sui PIC12 e PIC16

I pic12 e pic16 hanno un unico interrupt vector posizionato alla locazione 0x04. I dispositivi Midrange (PIC12 e PIC16Fxxx) hanno 8 livelli di stack, i dispositivi Midrange Enhanced (pic16F1xxx) hanno 16 livelli di stack.

Essendoci dopo la locazione 0x04 tutto il resto della memoria programma, le routine di interrupt possono essere anche molto lunghe purchè ovviamente il loro tempo di esecuzione non superi il lasso di tempo tra un interrupt e il successivo. Ricordo difatti che le routine di interrupt devono sempre essere il più corte possibile e, anche se abbiamo a disposizione tanto spazio in cui scrivere le nostre routine e una frequenza di clock molto elevata (che ci consente, cioè, di eseguire le istruzioni molto rapidamente), bisogna abituarsi a scrivere solo l’essenziale e a scriverlo in maniera efficiente.

I pic della serie MidRange al verificarsi di un interrupt, salvano in automatico il contenuto del Program Counter nello Stack e quello dei registri W e STATUS in registri temporanei (W_TEMP e STATUS_TEMP). I PIC dichiarati obsoleti e fuori produzione (come il 16F84 e il 16F877)  , ma anche altri di fasci bassa, salvano soltanto il Program Counter e il salvataggio di W e STATUS e/o altri registri importanti deve eventualmente essere realizzato via software.

Per questi pic, che registrano da sè solo il PC, l’Hitech-C esegue un controllo dell’ISR e delle funzioni eventualmente richiamate da questa per capire se è il caso o meno di eseguire il salvataggio di altri registri importanti. In caso affermativo aggiunge in automatico parti di codice per consentire il ripristino dei registri alla fine dell’Interrupt.

I pic della serie Midrange Enhanced (es.: il PIC16F1934 o il PIC16F1827 per citarne alcuni), oltre al program counter, salvano i registri W, STATUS, BSR, FSR, PCLATH nei registri SHADOW.

Il PICC (hitec-C) gestisce le routine di interrupt mediante l’attributo interrupt da anteporre al nome della funzione che fungerà da gestore interrupt:

void interrupt mia_isr(void)
{
// routine di gestione interrupt
}

Sui pic12/16 ogni interrupt ha un bit di abilitazione (suffisso -IE) che permette all’interrupt di essere catturato dall’ISR e un flag di avvenuto interrupt (suffisso -IF) che ci permette di capire se un interrupt si è verificato o meno. Dato che su questi pic qualsiasi interrupt causa il salto all’unico vettore di interruzione (e quindi il richiamo della nostra ISR), nell’ISR dovremo capire quale interrupt è scattato per poterlo servire e questo viene fatto testando, con una serie di IF, i flag di avvenuto interrupt che ci interessano.

Gli interrupt sui PIC12 e PIC16 hanno una sorta di interruttore generale: il bit GIE (Global Interrupt Enable) che consente di mascherare tutte le sorgenti di interrupt, ovvero di non far richiamare l’ISR anche se i bit di abilitazione sono settati. Vi è inoltre un altro bit di abilitazione per gli interrupt di periferica: PIE (Peripheral Interrupt Enable) che serve per mascherare tutti gli interrupt i cui flag di abilitazione si trovano nei registri PIEx. GIE maschera comunque anche i bit gestiti da PIE.

Gestione degli interrupt sui PIC18

Sui pic18 abbiamo due interrupt vector: quello ad alta priorità (posizionato alla locazione di memoria programma 0x08) e quello a bassa priorità (locazione 0x18). Ogni interrupt può quindi essere impostato per essere gestito in alta o in bassa priorità. A cosa serve la priorità?

Se il processore sta servendo un interrupt a bassa priorità e durante queste operazioni si verifica un interrupt ad alta priorità, l’ISR che gestisce quello a bassa priorità viene messa in pausa per consentire alla CPU di servire quello ad alta priorità. Quando la CPU avrà finito di servire l’ISR ad alta priorità, riprenderà l’esecuzione dell’ISR a bassa priorità e alla fine di quest’ultima riprenderà il programma principale.

L’utilizzo di tale caratteristica viene effettuato settando il bit IPEN del registro RCON. Quando IPEN=1 ci sono due bit che abilitano gli interrupt ad alta priorità (GIEH, bit 7 del registro INTCON) e bassa priorità (GIEL, bit 6 del registro INTCON). Il processore, al verificarsi di un interrupt, andrà a cercarsi le istruzioni da eseguire nelle locazioni 0x08 o 0x18 a seconda della priorità che è stata assegnata all’interrupt che ha causato l’interruzione del flusso del programma principale.

Quando si verifica un interrupt ad alta priorità, oltre a salvare il PC nello stack, il contenuto dei registri WREG, BSR e STATUS viene salvato nei registri shadow (Fast Register Stack), cosa, quest’ultima, che non avviene per gli interrupt a bassa priorità.

I pic18 possono gestire gli interrupt anche in maniera compatibile con i pic16: se poniamo IPEN=0 (condizione di default) non avremo distinzione di priorità. In questo caso abbiamo che il bit 7 del registro INTCON viene indicato come GIE ed abilita la gestione globale degli interrupt, mentre il bit 6 del registro INTCON prende il nome di PIE e serve per abilitare gli interrupt di periferica. In modalità compatibile, tutti gli interrupt vengono dirottati alla locazione 0x08 e non essendoci distinzioni di priorità verranno serviti in sequenza. In altre parole in modalità compatibile tutti gli interrupt sono sempre gestiti come se fossero ad alta priorità e quindi fanno anche uso del Fast Register Stack. Per memorizzare i salti i pic18 hanno ben 31 livelli di stack.

Gli interrupt sui PIC18, per poter essere gestiti, hanno anch’essi un bit di abilitazione (identificato dal suffisso -IE) che consente loro di essere rilevati e un flag che indica l’avvenuto interrupt (suffisso -IF) il quale viene settato anche se il relativo bit di abilitazione è posto a zero. In aggiunta qui abbiamo, per ogni interrupt, anche un bit di priorità (suffisso -IP) che permette di impostare per ogni interrupt una priorità bassa (bit posto a zero) o alta (bit a 1).

Il numero di locazioni tra l’interrupt ad alta priorità e quello a bassa priorità (0x18 – 0x08 = 0x10) non consente di scrivere delle ISR più o meno lunghe per l’interrupt ad alta priorità, per cui generalmente in queste locazioni di memoria si inserisce un salto (GOTO) ad un’altra routine che effettivamente gestirà l’interrupt. Per poter gestire quindi gli interrupt sui PIC18, il codice da scrivere è piuttosto laborioso (anche più di quello necessario rispetto ai pic a 16 bit come vedremo tra poco) e forse anche un po’ brutto perchè il C18 fa utilizzo delle direttive #pragma che non sono conformi allo standard ANSI C ma di sicuro facilitano di molto le cose:

// prototipi di funzione
void interrupt_priorita_bassa(void);
void interrupt_priorita_alta(void);
 
// specifico che la successiva porzione di codice deve essere messa
// nella locazione 0x08, ovvero quella destinata all'interrupt
// ad alta priorità. I nomi che assegno sia al vettore che
// alla funzione li posso dare a piacere e comunque di questi
// nomi non ne avrò più bisogno nel resto del codice
#pragma code vettore_alta_priorita = 0x08
void ISRH(void)
{
// imposto, in assembler, un salto alla mia routine di
// gestione dell'interrupt a priorità alta
_asm GOTO interrupt_priorita_alta _endasm
}
 
// specifico che la successiva porzione di codice deve essere messa
// nella locazione 0x18, ovvero quella destinata all'interrupt
// a bassa priorità.
#pragma code vettore_bassa_priorita = 0x18
void ISRL(void)
{
// imposto, in assembler, un salto alla mia routine di
// gestione dell'interrupt a priorità bassa
_asm GOTO interrupt_priorita_bassa _endasm
}
 
// specifico che il codice seguente andrà in altre locazioni
// di memoria scelte dal compilatore
#pragma code
 
// specifico che la funzione seguente è associata ad un interrupt
#pragma interrupt interrupt_priorita_alta
void interrupt_priorita_alta(void)
{
// codice per gestire interrupt a priorità alta
}
 
// specifico che la funzione seguente è associata ad un interrupt
// a bassa priorità
#pragma interruptlow interrupt_priorita_bassa
void interrupt_priorita_bassa(void)
{
// codice per gestire interrupt a priorità bassa
}
 
void main(void)
{
// main
}

Nel caso in cui si lavora in modalità compatibile basta eliminare le porzioni di codice che gesticono gli interrupt a bassa priorità. Nelle routine di interrupt, infine, come sui pic12/16 dovremo mettere una serie di IF per verificare quale particolare interrupt ha causato il salto alla ISR. Si controlleranno quindi i vari flag di interrupt -IF. Appena finito di servire l’interrupt, dovremo ricordarci come sempre di azzerarne il flag.

Gestione degli interrupt sui pic a 16bit (PIC24 e dsPIC)

I pic a 16 bit (PIC24 e dsPIC) hanno una moltitudine di interrupt vectors a partire dalla locazione 0x000004. A differenza dei pic ad 8 bit, qui ogni sorgente di interrupt, sia esterna che interna, ha il proprio esclusivo interrupt vector. Chi è abituato con i pic ad 8 bit si trova davanti ad una cosa nuova. Per questi pic abbiamo una Interrupt Vector Table (IVT) nella quale sono contenuti tutti i vettori di interrupt. La tabella è possibile trovarla nel datasheet del dispositivo o nel family reference manual:

estratto dal dsPic33 Family Reference Manual - Parte 1, sezione 6

Chiariremo dopo il significato dei valori delle colonne di questa tabella.

Apparentemente riuscire a gestire oltre un centinaio di sorgenti di interrupt su questi pic (ben 126!) potrebbe apparire una cosa molto complicata. In realtà la gestione degli interrupt sui pic a 16bit è ancora più semplice rispetto a quella dei cugini ad 8 bit; il C30 ci viene in aiuto con una funzione di interrupt per ogni richiesta di interruzione: non avremo più, quindi, un’unica ISR (o due come nel caso dei pic18) all’interno della quale dovremo andare a discernere l’interrupt che si è verificato bensì tante funzioni di interrupt separate. Vedremo tra poco come tutto questo si traduce in codice.

Lo stack, inoltre, sui pic a 16 bit è gestito in maniera completamente diversa e non ha un limite fisso. Abbiamo visto che sui pic18 lo stack è gestito via hardware mediante una pila di 32 registri e uno stack pointer. Sui dsPic e pic24 lo stack è gestito via software nella memoria RAM a partire dalla locazione 0x0800 (appena al di sotto dell’area dedicata agli SFR) e il suo limite viene imposto tramite il registro SPLIM. La funzione di stack pointer è assolta dal working register W15.

Le funzioni di interrupt e la parola chiave __attribute__

La scrittura delle funzioni di interrupt sul C30 utilizza una parola chiave facente parte del set avanzato di istruzioni del C Ansi: __attribute__ seguita da uno o più attributi separati da virgole inclusi in parentesi tonde doppie. Questa parola chiave serve per specificare degli attributi particolari associati alle funzioni (o alle variabili) che ne fanno utilizzo. Per farla breve, la funzione di interrupt associata al Timer1 su un pic a 16 bit, utilizzando MPLAB C30, andrà scritta come:

void __attribute__((interrupt)) _T1Interrupt(void)
{
// funzioni da eseguire sull'interrupt del Timer1
}

Con le parole chiave __attribute__((interrupt)) stiamo specificando che questa non è una normale funzione, bensì è una funzione da richiamare in automatico al verificarsi del particolare interrupt che abbiamo specificato dopo: _T1Interrupt.

L‘identificatore _T1Interrupt è associato all’interrupt vector dell’overflow sul timer1 (come specificato nella IVT), non è una parola che ho scelto io in maniera arbitraria ma è definita nel file linker (che per i pic a 16 bit ha estensione .GLD anzichè .LKR) del pic in questione.

Ad esempio, per il dsPIC33FJ128GP802 il file di linker si trova in:

C:\programmi\Microchip\MPLAB C30\support\dsPIC33F\gld\

ed ha più o meno lo stesso nome del dsPIC in questione: p33FJ128GP802.gld. Andando ad aprire tale file con un normale editor di testo (io consiglio sempre Notepad++), vediamo che da riga 236 partono le definizioni della interrupt vector table.

A parte i primi 8 interrupt che sono particolari e di cui parlerò dopo, possiamo individuare dei nomi abbastanza semplici da capire, l’unica differenza è che in questo file i vettori di interrupt sono indicati con due underscore prima del nome, mentre noi nel codice ne andremo a mettere uno solo. Dal momento che consultare il file di linker è abbastanza noioso, l’elenco dei nomi mnemonici associati ai vettori di interrupt è anche contenuto nella guida utente dell’ MPLAB C30 (capitolo 8): vedete che sono riportati i nomi mnemonici per le varie famiglie di pic a 16bit.

La guida utente dell’ MPLAB C30, come tutte le guide dei compilatori, si trova nella cartella docs del percorso di installazione.

Ogni interrupt è identificato anche da un numero, indicato in tabella come IRQ# (Interrupt Request number): difatti gli interrupt li possiamo chiamare, oltre che con il loro nome mnemonico, anche come _Interrupt72 ad esempio. Il numero di IRQ, inoltre, stabilisce anche un certo livello di priorità naturale come vedremo dopo.

Nota per gli “anziani”: ricordate quando sui vecchi 386/486 ecc per far funzionare la scheda audio nei giochi dovevamo per forza settare manualmente gli IRQ? Se due dispositivi condividevano lo stesso numero di IRQ ecco che qualcosa non funzionava…

La tabella interrupt alternativa

Al disotto dello spazio di memoria occupato dalla IVT, fisicamente, c’è la AIVT (Alternate Interrupt Vector Table). La tabella alternativa per gli interrupt viene utilizzata per applicazioni particolari o in fase di test/debug. Settando il bit ALTIVT nel registro INTCON2 tale tabella viene abilitata con la conseguenza che gli interrupt non saranno più serviti dalle funzioni che utilizzano i vettori della IVT ma da quelle che utilizzano i vettori della AIVT (ogni interrupt ha difatti un vettore sia nella IVT che nella AIVT).

Il vettore della AIVT viene identificato con il prefisso Alt- dopo l’underscore. Volendo possiamo quindi definire, nello stesso programma, due ISR per lo stesso interrupt:

void __attribute__((interrupt)) _T1Interrupt(void)

e

void __attribute__((interrupt)) _AltT1Interrupt(void)

ovviamente di default al verificarsi dell’interrupt sul timer1 verrà eseguita unicamente la prima funzione, se invece mettiamo a 1 il bit ALTIVT verrà eseguita unicamente la seconda.

Macro per le ISR

Ricordarsi ogni volta di scrivere __attribute__((interrupt)) è abbastanza noioso, lungo e difficile da ricordare, per questo motivo la Microchip ha inserito alcune macro nei file header dei pic a 16bit che ci permettono di scrivere le funzioni di interrupt come:

void _ISR _T1Interrupt(void)

vediamo difatti che nel file header di un qualsiasi pic a 16 bit è appunto definita la macro:

#define _ISR __attribute__((interrupt))

che ci permette di scrivere la stessa cosa ma in maniera più corta!

Le ISR “rapide”

Quando si verifica un interrupt, sappiamo che la CPU deve salvarsi lo stato di tutti i registri vitali (in questo caso i 4 registri di lavoro e il registro di stato SR) in maniera tale da poter ripristinare la situazione operativa alle condizioni precedenti una volta che l’interrupt è terminato.

I pic a 16bit, come i pic18, hanno un set di registri shadow da utilizzare per il salvataggio rapido dello stato. Utilizzando tali registri l’interrupt viene servito nella maniera più rapida possibile. Per sfruttare questa caratteristica si aggiunge un altro attributo all’isr che serve appunto a specificare la volontà di utilizzare questi registri shadow:

void __attribute__((interrupt,shadow)) _T1Interrupt(void)

oppure utilizzando la macro _ISRFAST:

void _ISRFAST _T1Interrupt(void)

Il set di registri shadow è uno solo per cui una, ed una sola, routine di interrupt può utilizzare questa caratteristica. In altre parole, se nel nostro programma intendiamo gestire più di un interrupt, una sola ISR potrà fare uso della _ISRFAST e tutte le altre dovranno usare la normale _ISR.

I livelli di priorità e l’annidamento

Abbiamo visto che i pic18 hanno due livelli di priorità. Sui pic a 16 bit ne abbiamo ben 7 impostabili per i “nostri” interrupt. Le cose quindi si stanno complicando! Anche qui se si verificano contemporaneamente due interrupt a diversi livelli di priorità, la CPU servirà prima quello a priorità più alta. Nel caso in cui si verifichino contemporaneamente due interrupt allo stesso livello di priorità, verrà servito per primo quello con l’IRQ più basso, ecco perchè prima parlavo di “priorità naturale”.

Sui pic a 16 bit anche la CPU ha un suo livello di priorità che ha la funzione di mascherare gli interrupt indesiderati: gli interrupt che hanno un livello di priorità minore o uguale al livello di priorità impostato per il processore (IPL), vengono ignorati.

Ad esempio se IPL si trova a 4, verranno serviti soltanto gli interrupt con un livello da 5 a salire. Al powerup IPL si trova a zero e il livello di priorità di tutti gli interrupt è posto a 4, per cui di default tutti gli interrupt vengono serviti.

Il livello di priorità del processore è impostabile manualmente via software agendo sui bit IPL0,IPL1,IPL2 del registro SR (CPU Status Register):

Avendo 3 bit a disposizione, possiamo impostare livelli di priorità da 0 a 7 (8 livelli). IPL può essere impostato utilizzando alcune macro (definite nel file H di ogni pic a 16bit):

SET_CPU_IPL(ipl) // imposta ad ipl il livello di priorità del processore. ipl da 0 a 7
 
SET_AND_SAVE_CPU_IPL(save_to, ipl) // salva il livello corrente nella locazione di memoria indicata da save_to e imposta il livello attuale ad ipl
 
RESTORE_CPU_IPL(saved_to) // reimposta il livello di priorità a quello salvato nella locazione saved_to, esegue la stessa operazione della macro SET_CPU_IPL

Il livello di priorità degli interrupt, invece, viene settato nei registri IPCx. Ad esempio, per l’interrupt su Timer1, il livello di priorità viene impostato nel registro IPC0 agendo sui bit <14:12>. Il livello di priorità degli interrupt può variare da 1 a 7 (7 livelli), non può quindi assumere valore 0: impostare a zero il livello di priorità di un interrupt equivale a disabilitarlo.

La descrizione dei registri IPCx purtroppo non si trova nel datasheet ma nel Family Reference Manual della famiglia del pic a 16 bit in uso (per i dsPic33 fare riferimento al dsPIC33 Family Reference Manual – Part 5 – Section 47, per i pic24F fare riferimento al PIC24F Family Reference ManualSection 8).

Ricordo di nuovo che ponendo livello di priorità del processore (IPL) a 0, tutti gli interrupt vengono serviti in quanto i livello di priorità degli interrupt vale minimo 1.

I pic a 16 bit supportano l’annidamento degli interrupt: se il processore sta servendo un interrupt ad un certo livello di priorità e durante queste operazioni si verifica un interrupt ad un livello di priorità più alto, l’esecuzione dell’interrupt a livello più basso viene messa in pausa per poter servire l’interrupt a livello alto. Questo comportamento è quello di default e può essere alterato settando il bit NSTDIS del registro INTCON1. Mettendo ad 1 il bit NSTDIS (Nested Interrupt Disable), gli interrupt vengono serviti in sequenza.

In altre parole, ponendo NSTDIS a 1, un interrupt, anche se ha un livello di priorità più alto di quello correntemente servito, dovrà attendere che l’interrupt precedente sia stato completato. Tuttavia nel caso in cui si verifichino contemporaneamente due interrupt, verrà servito per prima quello a priorità più alta e quindi il livello di priorità, in questo particolare caso, viene sfruttato unicamente per risolvere eventuali conflitti.

Il processore realizza questa funzione ponendo in automatico a 7 il livello di priorità del processore non appena un interrupt si verifica ed inizia ad essere servito: in questo modo se si verificano altri interrupt durante questo tempo, non saranno serviti. IPL viene resettato non appena l’interrupt corrente cessa di essere servito consentendo alla CPU di catturare gli altri interrupt. Con NSTDIS a 1, inoltre, IPL può essere soltanto letto ma non scritto.

Impostare manualmente IPL a 7 causa la disattivazione degli interrupt in quanto il livello di priorità dei normali interrupt non può essere superiore a 7 e un interrupt per essere servito deve avere un livello di priorità superiore a quello del processore.

Possiamo assumere che con NSTDIS=1 il comportamento è simile a quello dei pic16.

Abilitare e rilevare gli interrupt sui pic a 16bit

Sui pic a 16bit, come sugli altri pic, ogni interrupt può essere attivato/disattivato tramite il proprio bit di abilitazione (che ha suffisso -IE). Però qui la situazione è più ordinata: i bit di abilitazione si trovano nei registri IECx, quindi non c’è più distinzione tra interrupt “normali” e interrupt di periferica.

La rilevazione, come abbiamo visto, viene invece eseguita tramite le ISR dedicate. In ogni caso anche qui, ovviamente, ogni interrupt ha il proprio flag che indica se l’interrupt si è verificato o meno. I flag di avvenuto interrupt hanno il suffisso -IF, si trovano nei registri IFSx, e vengono settati comunque anche se l’interrupt non è stato abilitato.

La descrizione dei registri IECx e IFSx purtroppo non si trova nel datasheet ma nel Family Reference Manual della famiglia del pic a 16 bit in uso (per i dsPic33 fare riferimento al dsPIC33 Family Reference Manual – Part 5 – Section 47, per i pic24F fare riferimento al PIC24F Family Reference ManualSection 8).

I flag di interrupt anche qui vanno azzerati non appena l’interrupt è stato servito, altrimenti non si esce mai dalle ISR. Nel caso del timer1, quindi, la routine di interrupt sarà:

void _ISR _T1Interrupt(void)
{
// istruzioni
_T1IF=0; // azzero il flag di interrupt su Timer1
}

A differenza dei pic di fascia inferiore, qui non abbiamo il bit GIE che permette il mascheramento degli interrupt (nè tantomeno un bit PIE per abilitare gli interrupt di periferica): ogni interrupt ha il suo bit di abilitazione, punto e basta. Agendo sui livelli di priorità, però, dal momento che nessun interrupt può avere priorità 7, basta mettere a 7 il livello di priorità del processore per mascherare gli interrupt.

Esiste inoltre un’istruzione assembler (DISI) che permette di disabilitare tutti gli interrupt per un determinato numero di cicli:

asm volatile ("disi #0x3FFF");

in alternativa è possibile utilizzare una builtin:

__builtin__disi(0x3FFF);

Questa istruzione in pratica disabilita gli interrupt per i prossimi 0x3FFF (16383) cicli macchina (valore massimo). L’istruzione DISI ferma temporaneamente solo gli interrupt aventi livelli di priorità da 1 a 6, gli interrupt con livelli superiori non vengono fermati.

Se i cicli macchina impostati non sono ancora passati ma vogliamo comunque ripristinare gli interrupt, si può farlo azzerando il registro di conteggio dei cicli macchina dopo i quali riattivare gli interrupt (DISICNT : Disable Interrupt Control Register):

DISICNT=0;

Il registro DISICNT si trova normalmente a zero, se lo impostiamo su un valore diverso da zero (operazione che in pratica viene eseguita dall’istruzione DISI), gli interrupt vengono disabilitati temporaneamente e il contatore esegue un conto alla rovescia man mano che i cicli macchina procedono; arrivato a zero gli interrupt vengono riabilitati. L’eventuale entrata in funzione dell’istruzione DISI è anche indicata dal bit DISI nel registro INTCON2.

Questa istruzione diventa necessaria nei punti in cui modifichiamo al volo i livelli di priorità nel nostro programma, in quanto durante queste operazioni gli interrupt devono essere temporaneamente disattivati oppure può tornare utile in quei punti del programma in cui sono richieste operazioni critiche che richiedono di essere eseguite in un certo tempo e quindi non devono essere interrotte.

Le trappole

Quando abbiamo visto la IVT vi ho anticipato che i primi 8 interrupt sono particolari. Questi interrupt vengono chiamati trappole (traps) e sono utilizzati per catturare le condizioni di errore del processore o malfunzionamenti (guasto dell’oscillatore, operazioni matematiche errate ecc).

Dal momento che i problemi rilevati da questi interrupt sono abbastanza gravi e compromettono il funzionamento della nostra applicazione, le trappole hanno un livello di priorità al di sopra di tutti gli altri interrupt (hanno un livello da 8 a 15), non vengono mai disattivati dall’istruzione DISI e non vengono rallentati dal flag NSTDIS (passano sempre avanti). Le trappole sfruttano comunque l’annidamento perchè è ovvio che una condizione di allarme a priorità più alta ne scavalchi una a priorità più bassa. Le trappole vengono anche dette interrupt non mascherabili.

Dal momento che le trappole hanno un livello di priorità da 8 a 15, oltre ai 3 bit IPL visti prima (che possono quindi contenere un numero fino a 7), c’è anche un bit IPL3 nel registro CORCON, che serve appunto a rilevare questa condizione di interrupt particolare (direi più condizione di allarme). Il bit IPL3, difatti, a differenza degli IPL0:2, può solo essere letto o resettato ma non posto a 1.

La trappola a priorità più alta è il guasto dell’oscillatore (sebbene sopra di lei ci sia una trappola attualmente inutilizzata, probabilmente prevista per usi futuri). La trappola del controller DMA (livello 10), la trappola di errore matematico (livello 11) e la trappola di errore dello stack (livello 12) sono anche dette soft traps, le altre sono dette hard traps. La differenza sta nel fatto che le trappole “hard” forzano la CPU ad interrompere l’esecuzione del codice appena dopo che l’istruzione che ha causato la trappola viene completata, mentre le trappole soft si prendono un ciclo in più per confermare la condizione di errore.

MPLAB C30 associa, di default, le trappole ad una routine di reset del processore ma nulla vieta di dirottare la gestione delle trappole scrivendo delle nostre routine come per i normali interrupt. Anche le trappole, quindi, hanno il loro vettore alternativo nella AIVT.
Quando una trappola causa il reset del processore viene settato il bit TRAPR (Trap Reset Flag) del registro RCON (Reset Control register).

Come vedete sui pic a 16bit la gestione degli interrupt è molto raffinata ed orientata alla massima sicurezza.

Bibliografia

L'articolo ti è piaciuto o ti è stato utile per risolvere un problema? SettoreZero è realizzato soltanto con contenuti originali: tutti gli articoli sono curati con passione dagli autori e nulla viene copiato da altri siti. Supporta e mantieni in vita SettoreZero con una donazione: basta soltanto un caffè o una birra. Puoi supportare SettoreZero anche con uno dei progetti elencati nella sezione servizi o partecipare anche tu con un tuo articolo/progetto personale.

Se desiderate che SettoreZero continui a rimanere gratuito e fruibile da tutti, non copiate il nostro materiale e segnalateci se qualcuno lo fa.

Puoi andare alla fine dell'articolo e lasciare un commento. I trackback e i ping non sono attualmente consentiti.

  1. #1 da Giovanni il 13 dicembre 2010

    Buone e utile l’articolo. Se posso ti faccio un’osservazione. Io utilizzo Hitech C e la dimensione del codice di interrupt viene gestito automaticamente dal compilatore. Non come sembta nel tuo articolo quando affermi che la lunghezza del codice ISR ad alta priorità non può superare l’intervallo di indirizzi 0x0008 – 0x0018. Questo forse vale per assembler e forse per PIC c 18.
    Oppure ho capito male io quanto hai scritto?

    • #2 da Giovanni Bernardo il 13 dicembre 2010

      Hai scritto la stessa cosa che ho detto io o forse non hai letto bene l’articolo. Sui PIC12 e PIC16 (e quindi con il PICC) ho scritto che problemi di spazio non ce ne sono perchè dopo il vettore di interruzione viene tutta la memoria programma. Il problema sta sui PIC18 (e di conseguenza con il C18) che hanno un range limitato per l’interrupt ad alta priorità che parte da 0x08 però poi, dopo 0x10 locazioni c’è il vettore di bassa priorità… quindi o non ci “allarghiamo” o usiamo un’istruzione goto per ridirezionare le istruzioni… L’istruzione goto ce la metti tu, il compilatore poi gestisce l’allocazione del tutto e quindi pure qua, a questo punto, problemi di spazio non ce l’hai. Ma ti devi ricordare di usare il goto. Questo com MPLAB C18. L’hitec per PIC18 non lo so come si comporta, comunque sia ho specificato all’inizio che per i pic18 faccio riferimento a MPLAB C18.

  2. #3 da Haru il 14 marzo 2011

    Ciao, ho una piccola richiesta: potresti fare un esempio di interrupt per i pic16? Non ho capito come rilevare “cosa” ha generato l’interrupt, e come farlo capire al programma stesso.

    Nel senso, se il programma prevede un timer con interrupt e interrupt abilitati su tutta la portB, come fà il programma a capire se l’interrupt è causato da un timer o da una porta? ci vogliono delle if o è meglio controllare lo stato dei registri in assembly?

    • #4 da Giovanni Bernardo il 14 marzo 2011

      Si controlla con una serie di IF sui pic a 8 bit. I pic a 16 bit hanno una funzione distinta per ogni interrupt. Nelle lezioni di programmazione dei picmicro hai voglia di esempi di interrupt che ci sono, io uso esclusivamente gli interrupt per ogni cosa.

  3. #6 da Stefano il 4 maggio 2011

    saluti a tutti.
    Correggimi se sbaglio, in questo capitolo spieghi che al momento dell’initerrupt il pic salva in una memoria temporanea il contenuto una serie di registri tra i quali il PC. Ma non tutti pic lo fanno, quelli più obsoleti registrano solamente il PC, come il 16F877A.
    Leggendo nel datascheet anche il più recente 16F887 a quanto pare non salva automaticamente gli altri registri.
    Quindi sarà il compilatore stesso a provvedere a questi salvataggi con la funzione “interrupt” che userermo poi nel nostro programma?

    • #7 da Giovanni Bernardo il 5 maggio 2011

      Bastava dare un occhio al manuale dell’Hitech-C. Il manuale dice che su questi pic che salvano solo PC, è il compilatore, in base al contenuto delle istruzioni nella ISR, a decidere se aggiungere o meno delle istruzioni che prevedono il salvataggio di altri registri chiave:

      Enhanced Mid-Range PIC devices save the W, STATUS, BSR and FSRx registers in
      hardware (using special shadow registers) and hence these registers do not need to
      be saved by software. In fact, the compiler will never have to produce code to save any
      other registers when compiling for an Enhanced Mid-Range as no additional registers
      are ever used. This makes interrupt functions on Enhanced Mid-Range PIC devices
      very fast and efficient.

      Other Mid-Range PIC processors only save the entire PC (excluding the PCLATH register)
      when an interrupt occurs. The the W, STATUS, FSR and PCLATH registers and
      the BTEMP1 pseudo register must be saved by code produced by the compiler, if
      required. The compiler fully determines which registers and objects are used by an interrupt function,
      or any of the functions that it calls (based on the call graph generated by the compiler),
      and saves these appropriately.

  4. #8 da Romeo Sibau il 16 dicembre 2011

    Ciao, complimenti per il sito e tutte le informazioni utili che ci sono.
    Non sono un’abituè dei forum e tento generalmente di risolvere da me i problemi che mi si presentano ma questa volta davvero non ne esco.
    Trovando questo articolo molto ben fatto mi sono deciso a chiedere aiuto.
    Spiego brevemente ciò che devo fare e con quale Pic, allora, la mia applicazione deve catturare, utilizzando tassativamente gli interrupt due impulsi uno su RB0 e l’altro su RB1, non è importante se sul fronte di salita o sul fronte di discesa.
    Avendo già utilizzato in precedenti applicazioni gli interrupt da periferica ritenevo erroneamente che la gestione degli stessi avesse la stessa semplicità, anche per quelli esterni … mi sono dovuto ricredere.
    Sto utilizzando un PIC 18F87J60 che mi sono ritrovato su una scheda di sviluppo, che mi permette di simulare attraverso i soliti pulsanti la chiusura dei contatti che dovrebbero scatenare l’interrupt.
    Dopo vari tentativi e diverse configurazioni sono approdato al codice pubblicato su questo sito e ne ho preso spunto per scrivere quanto segue :

    #include
    #include

    // Variabili globali
    char InterruptRB0;
    char InterruptRB1;
    char InterruptRB2;
    char InterruptRB3;
    // prototipi di funzione

    void interrupt_priorita_bassa(void);
    void interrupt_priorita_alta(void);

    #pragma code vettore_alta_priorita = 0x08
    void ISRH(void)
    {
    // imposto, in assembler, un salto alla mia routine di
    // gestione dell’interrupt a priorità alta
    _asm GOTO interrupt_priorita_alta _endasm
    }

    #pragma code vettore_bassa_priorita = 0x18
    void ISRL(void)
    {
    _asm GOTO interrupt_priorita_bassa _endasm
    }

    #pragma code

    #pragma interrupt interrupt_priorita_alta
    void interrupt_priorita_alta(void)
    {
    // codice per gestire interrupt a priorità alta
    // Alta priorità RC0 : RC1

    if (INTCONbits.INT0IF==1) {
    InterruptRB0=1;
    INTCONbits.INT0IF=0;
    }
    if (INTCON3bits.INT1IF==1) {
    InterruptRB1=1;
    INTCON3bits.INT1IF=0;
    }
    }

    #pragma interruptlow interrupt_priorita_bassa
    void interrupt_priorita_bassa(void)
    {
    // codice per gestire interrupt a priorità bassa
    // Bassa priorità RC6 : RC7

    if (INTCON3bits.INT2IF==1) {
    InterruptRB2=1;
    INTCON3bits.INT2IF=0;
    }
    if (INTCON3bits.INT3IF==1) {
    InterruptRB3=1;
    INTCON3bits.INT3IF=0;
    }
    }

    void setup (void){

    TRISC = 0b00000000; // Tutti output
    PORTC = 0b00000000; // Reset della porta C

    TRISB = 0b11111111;

    INTCONbits.INT0IE = 1; //Sempre ad alta priorità
    INTCON2bits.INTEDG0=1; // Rileva interrupt sul fronte di salita

    INTCON3bits.INT1IE= 1;
    INTCON3bits.INT1IP= 1; //Abilito interrupt ad alta priorità
    INTCON2bits.INTEDG1=1; // Rileva interrupt sul fronte di salita

    INTCON3bits.INT2IE= 1;
    INTCON3bits.INT2IP= 0; //Abilito interrupt a bassa priorità
    INTCON2bits.INTEDG2=1; // Rileva interrupt sul fronte di salita

    INTCON3bits.INT3IE= 1;
    INTCON2bits.INT3IP= 0; //Abilito interrupt a bassa priorità
    INTCON2bits.INTEDG3=1; // Rileva interrupt sul fronte di salita

    InterruptRB0 = 0;
    InterruptRB1 = 0;
    InterruptRB2 = 0;
    InterruptRB3 = 0;

    RCONbits.IPEN=1;
    INTCONbits.RBIE=0;
    INTCONbits.GIEH=1;
    INTCONbits.GIEL=1;
    INTCONbits.GIE=1;
    }

    void main(void) {
    setup();

    while (1) {

    if (InterruptRB0 == 1) {
    PORTCbits.RC0=1;
    Delay10TCYx(1);
    PORTCbits.RC0=0;
    InterruptRB0 = 0;

    }

    if (InterruptRB1 == 1) {
    PORTCbits.RC1=1;
    Delay10TCYx(1);
    PORTCbits.RC1=0;
    InterruptRB1 = 0;
    }

    if (InterruptRB2 == 1) {
    PORTCbits.RC6=1;
    Delay10TCYx(1);
    PORTCbits.RC6=0;
    InterruptRB2 = 0;
    }

    if (InterruptRB3 == 1) {
    PORTCbits.RC7=1;
    Delay10TCYx(1);
    PORTCbits.RC7=0;
    InterruptRB3 = 0;
    }

    }
    }
    La conclusione è che simulando con MPASM tutto funziona correttamente mentre se carico il tutto sul PIC non succede assolutamente nulla.
    Qualcuno è in grado di dirmi se sbaglio qualcosa nella sezione setup() ?
    Ringrazio anticipatamente per eventuali suggerimenti.

    • #9 da Giovanni Bernardo il 16 dicembre 2011

      Delay nell’interrupt?

      • #10 da Romeo Sibau il 16 dicembre 2011

        Mancava una piccola precisazione forse: si tratta solo codice di prova, il delay mi serve solo per accendere e spegnere un led per dimostrare che l’interrupt è avvenuto

        • #11 da Giovanni Bernardo il 16 dicembre 2011

          ah aspetta ma i delay non li hai messi nell’interrupt… ma perchè l’hai strutturata in questo modo? Metti le funzioni da eseguire nell’interrupt e non con tutto questo giro di flag

  5. #12 da nmira il 16 gennaio 2012

    ciao a tutti,avrei un quesito da sottoporvi ,sono in fase di progettazione di un emulatore di un vecchio lettore di nastri dati utilizzando chipkit max32,sono a buon punto avendo già realizzato attraverso Mpide 0023 una soluzione funzionante effettuando il polling delle porte interessate.
    Purtroppo mi sono accorto di perdere delle preziose informazioni che dovrebbero essere lette dopo max 100-200 ns.
    Quale potrebbe essere il suggerimento per avere il minor tempo di latenza possibile modificando il codice utilizzando un interrupt?

    • #13 da Giovanni Bernardo il 19 gennaio 2012

      Ciao,
      la tua idea è davvero molto interessante. C’è il Commodore64 di mezzo? Utilizzare gli interrupt può aiutarti a rendere il tutto più efficiente ma non più veloce. Il MAX32 può girare a ben 80MHz con un tempo per ciclo di 12.5nS … mi sembra strano che non ti basti anche se in realtà non ho idea delle velocità che ci sono in gioco. Stai loggando il progetto da qualche parte così che gli possa dare un occhio?

  6. #14 da nmira il 21 gennaio 2012

    grazie per la risposta,la scheda che utilizzava il lettore che devo emulare utilizza uno Z80 a 4.8Mhz e fa parte di un vecchio dispositivo tipo CNC che devo far ripartire per riprendere i vecchi programmi memorizzati sui nastri (molto simili alle vecchie cassette audio) ed eventualmente ripristinare il macchinario collegandolo ad un Pc.Avevo iniziato il lavoro con Arduino e poi avendo notato che la velocità del polling era troppo bassa ho cercato e mi sono imbattuto nel chipkit Max32.Ho dovuto comunque cambiare la frequenza dello Z80 (2Mhz max) per poter avere un risultato soddisfacente, purtroppo perdo dei collegamenti ad altri dispositivi collegati in RS232 e questo mi penalizza.Il progetto è molto semplice, ho due porte da 8bit,una di comandi e l’altra di dati,a seconda di quando viene selezionato il dispositivo vengono scatenate delle risposte che abilitano la scrittura o la lettura dei
    ti posto il codice del programma,scusate gli orrori ma sono abituato ad altri sistemi di programmazione che non hanno problemi di RAM,STACK ecc..
    il ciclo di selezione avviene ad una frequenza di circa 180khz per la durata di 1microsec,all’interno del quale devo attendere il fronte di discesa del clock per il sincronismo di lettura scrittura dati.

    boolean tea = false;
    boolean emulator=false;
    boolean firstTime=false;

    boolean client=false;
    unsigned char c=0x0;

    //———————————————————
    // definizione degli output per comandi
    //———————————————————

    const int dma_ack = 44;
    const int dma_req = 45;
    const int G = 46;
    const int DIR = 47;
    const int MAX_BUFF_WRT =1024;
    byte dato=0;
    byte datiDaLeggere=0xfd;
    byte datiLetti[0xff];
    byte data[0xff];
    byte dataRead[0xff];
    byte indexItr=0;
    boolean comunicazione=false;//false =seriale true =tcp
    boolean ciclo=true;
    byte bufSerialeWrite[MAX_BUFF_WRT];
    byte bufSerialeWriteTot[MAX_BUFF_WRT];
    byte bufSerialeRead[MAX_BUFF_WRT];
    byte bufSerialeReadTot[MAX_BUFF_WRT];
    byte bufRicezione[100];
    static long contatore=0;
    static boolean controllo=true;
    byte i=0;
    byte tipoOperazione=0;
    byte type=2;
    byte inByte=0x0;
    byte indice=0x0;
    byte maxElementi=8;
    byte elemento=0;

    void setup()
    {
    // disabilitazione ingressi analogici
    AD1PCFG=0xffff;
    // porta dati
    TRISE=0xff;
    //porta comandi
    TRISB=0xff;
    pinMode(G,OUTPUT);
    pinMode(DIR,OUTPUT);
    digitalWrite(G,HIGH);
    digitalWrite(DIR,HIGH);
    pinMode( dma_ack,OUTPUT);
    pinMode(dma_req,INPUT);
    memset(bufSerialeWrite,0x0,MAX_BUFF_WRT);
    bufSerialeWrite[0]=0xdb;
    bufSerialeWrite[51]=0xfb;
    memset(bufSerialeRead,0x0,MAX_BUFF_WRT);
    bufSerialeRead[0]=0xdb;
    bufSerialeRead[51]=0xfb;
    memset(bufSerialeWriteTot,0x0,MAX_BUFF_WRT);
    memset(bufSerialeReadTot,0x0,MAX_BUFF_WRT);
    memset(bufRicezione,0x0,sizeof(unsigned char)*100);
    Serial.begin(115000);
    Serial.flush();
    digitalWrite( dma_ack,HIGH);
    memset(dataRead,0x0,0xff);
    memset(data,0x0,0xff);
    }

    void loop()
    {

    while (ciclo)
    {
    if(!firstTime)
    {
    Version(2);
    firstTime=true;
    digitalWrite(G, HIGH);

    memset(bufRicezione,0x0,sizeof(unsigned char)*100);
    char myString[] = “**** READY ****”;
    memcpy(bufRicezione,myString,strlen(myString));
    Serial.flush();
    Serial.write(bufRicezione,100);
    delay(100);
    memset(bufRicezione,0x0,sizeof(unsigned char)*100);
    }
    dato=0;

    int a=Serial.available();

    if(a >99)
    {
    for(byte ricezione=0;ricezione<a;ricezione++)
    {
    bufRicezione[ricezione]=Serial.read();
    }
    Serial.flush();

    c = bufRicezione[0];

    if (c == 0x41 && !emulator)//A
    {
    digitalWrite(G, HIGH);
    delay(200);
    pinMode( dma_ack,INPUT);
    TRISB=0xf7;
    TRISE=0xff;
    emulator=true;
    digitalWrite(DIR, LOW);
    digitalWrite(G, LOW);
    tea=false;
    char myString[] = "**** EMULATOR ****";
    memcpy(bufRicezione,myString,strlen(myString));
    Serial.flush();
    Serial.write(bufRicezione,100);
    delay(100);
    }

    if(c == 0x71 )//q
    {

    digitalWrite(G,HIGH);
    char myString[] = "**** END TAPE ****";
    memcpy(bufRicezione,myString,strlen(myString));
    Serial.flush();
    Serial.write(bufRicezione,100);
    delay(200);
    Serial.end();
    ciclo=false;
    tea=false;
    emulator=false;
    }

    if(!tea)
    {

    if (c == 0x43 && emulator)//C
    {

    controllo=true;
    contatore=0;
    Leggo(dato);
    delay(1500);

    }

    // azzera i buffer globali
    if ((c == 0x25) && emulator)
    {
    memset(bufSerialeWriteTot,0x0,MAX_BUFF_WRT);
    memset(bufSerialeReadTot,0x0,MAX_BUFF_WRT);
    memset(bufRicezione,0x0,sizeof(unsigned char)*100);
    }

    // ritorna il cmd inviato x verificare che l'operazione precedente sia conclusa
    if ((c == 0x20) && emulator)
    {
    Serial.flush();
    Serial.write(bufRicezione,100);
    memset(bufRicezione,0x0,sizeof(unsigned char)*100);
    }

    }

    }

    }
    Serial.println("**** !!!!!!!!!!!!!! ****");
    delay(10);
    Serial.end();

    }
    /////////////////////////////////////////////////////////////
    void Version(byte flag)
    {
    byte i=0x0;
    digitalWrite(G, HIGH);
    for (i=0x0;i<=flag;i++)
    {
    //Clock();
    digitalWrite(DIR, LOW);
    delay(100);
    digitalWrite(DIR, HIGH);
    delay(400);
    }
    digitalWrite(DIR, LOW);
    memset(bufRicezione,0x0,sizeof(unsigned char)*100);
    char myString[] = "**** Ver. 1.0 ****";
    memcpy(bufRicezione,myString,strlen(myString));
    Serial.write(bufRicezione,100);
    delay(500);

    }

    //////////////////////////////////////////////////////////////////////////
    void Leggo(byte flag)
    {
    byte val=0x0;
    byte slk=0x0;
    byte ck=0x0;
    boolean dt=false;
    byte asa=0x0;
    boolean flago= true;
    byte fISR=0x0;
    byte bufSeriale[256];
    int indexBuf=0;
    int ix=0x0;
    byte date[1024];
    byte sette=0x0;
    boolean datoPreparato=false;
    boolean dueCk=false;
    int i=0;
    memset(date,0x0,1024);
    if(controllo)dt=false;
    fISR=2;

    do
    {
    do
    {
    val=PORTB;
    slk=(val&0x20);
    if(!controllo)contatore++; //controllo in caso di errore

    }while((slk==0x20)&&(contatore<200000)); // fronte discesa bit selezione

    if(contatore=200000)
    {
    ck=1;
    dt=true;
    }

    if((!ck )&&(flago))
    {
    flago=false;
    date[ix]=val;
    ix=ix+1;
    if(val==0x83)//cassette status register
    {
    TRISE=0x00;//dati in lettura
    PORTE=0xff;
    }

    if(val==0x81)//Isr
    {
    TRISE=0x00;//dati in lettura

    if(fISR==1)
    {
    PORTE=0x7f;
    if(flag==7)dt=true;
    }
    }

    if(val==0x95)//cmd
    {
    TRISE=0xff; //dati in scrittura
    asa=PORTE;
    if(asa==0x3e)
    {
    fISR=1;
    }
    date[ix]=asa;
    ix=ix+1;
    }

    if(val==0x97)//cmd
    {
    TRISE=0xff;
    bufSerialeWrite[indexBuf]=PORTE;
    indexBuf=indexBuf+1;
    if(indexBuf>94)dt=true;
    }

    }

    do
    {
    val=PORTB;
    ck=(val&0x40);
    }while(ck!=0x40); // fronte salita clock

    flago=true;
    }
    while(!dt);
    TRISE=0x00;
    PORTE=0x0;
    TRISE=0xff;

    Serial.write(date,100);

    }

    /////////////////////////////////////////////////////////////
    void IniComandi(void)
    {
    digitalWrite(G, HIGH);
    delay(200);
    digitalWrite(DIR, HIGH);
    TRISB=0x8;
    digitalWrite(G, LOW);
    delayMicroseconds(1);
    }
    /////////////////////////////////////////////////////////////
    void IniDati(void)
    {
    PORTE=0xff;
    delayMicroseconds(1);
    }
    /////////////////////////////////////////////////////////////
    void WrtComandi(unsigned int val)
    {
    //unsigned int app=0x0;
    //app=PORTB>>8;
    //PORTB=val|(app << 8);
    PORTB=val;
    delayMicroseconds(1);
    }
    //////////////////////////////////////////////////////////
    void WrtDati(byte val)
    {
    PORTE=val;
    delayMicroseconds(1);
    }
    //////////////////////////////////////////////////////////
    byte RdDati(void)
    {
    byte valore=0x0;
    valore=PORTE;
    //valore=LATSESET;
    return valore;

    }
    /////////////////////////////////////////////////////////////

    int Setto_dati(int mode)
    {
    if(mode==0)
    {
    TRISE=0xffff;
    }
    else
    {
    TRISE=0x00;
    }
    delayMicroseconds(1);
    return 0;
    }

  7. #15 da slavin89 il 6 luglio 2012

    selve a tutti ho una domanda ma per esporvela vi porto il mio esempio..
    allora ho una funzione main che può richiamare a sua volta le funzioni prova1(); e prova2(); poi ho la funzione interrupt..se io dal main richiamo prova1(); come posso far lavorare prova1(); assieme all’interrupt?
    perchè al richiamo della funzione io andrò a fare tutto ciò che ne è all’interno ma essendo l’interrupt un’ulteriore funzione non la eseguirà se non nel main.
    avevo pensato di copiare il listato all’interno di interrupt all’interno del ciclo in prova1 ma non funziona correttamente forse perchè devono obbligatoriamente lavorare su due funzioni differenti quindi come potrei fare?
    saluti

Devi essere collegato per lasciare un commento.

settorezero.com e il logo Zroid™ ©2007÷2015 Giovanni Bernardo - E' vietata la copia e la distribuzione anche parziale dei contenuti di questo sito web senza l'esplicito consenso dell'autore.
I contenuti di settorezero.com sono distribuiti sotto una licenza Creative Commons Attribuzione-Non Commerciale-Non Opere derivate 2.5 Italia a cui vanno aggiunte le condizioni d'uso definite nel disclaimer.
settorezero.com e tutti i suoi contenuti sono tutelati dalla legge sul diritto d'autore per cui i trasgressori sono perseguibili a norma di legge. Settorezero fa uso dei cookie leggi l'informativa estesa.
Creative Commons BY-NC-ND 2.5
Il tema di questo sito è basato sul tema Fusion per wordpress, realizzato originariamente da digitalnature e fa uso del plugin Wassup per il computo delle statistiche. Per contattare l'autore siete pregati di utilizzare la sezione contatti.
Per essere aggiornato con tutte le novità di settorezero.com seguici anche anche su Facebook Twitter Tumblr Blogspot Youtube.