Info su Interrupt0, Timer/Counter1, Input Capture e Output Compare

SAlve ragazzi, sto bazzicando con Interrupt0 e mi chiedevo, visto la mia QUASI ignoranza in materia, se esso funziona alla stessa frequenza del clock di sistema (quindi su una UNO sarebbe 16 MHz) o è ridotta causa l'utilizzo di prescaler, ho letto qualcosa al riguardo su Timer0 che se non erro ha a che vedere con Interrupt0 giusto? Scusatemi, ma è che è da poco che ho incominciato a fare sul serio con gli interrupt, e vorrei poter comprendere affondo ogni cosa, ho trovato dell'ottimo materiale (ho fatto le 3 di notte per leggerlo!!) su un sito che appartiene ad un moderatore del forum Internazionale arduino (Nick Gammon) l'argomento è ben spiegato, ma visto le mie basi poco solide in materia HW/SW vi prego di essere clementi :grin:

Se la risposta al quesito qui sopra è positiva, quindi ha un prescaler, allora sarebbe possibile ridurlo in modo da aumentare la frequenza di funzionamento del interrupt0, so que poi, le varie functions che si basano su Timer0 andrebbero di matte come delay() e forse anche la Serial.print() giusto? Se si, potete indicarmi eventuali altri problemi a cui si andrebbe in contro? Io nel frattempo, quando torno stasera, mi metto a studiare il materiale che vi ho indicato su e qualcos'altro.

Grazie per la pazienza!!

Timer ed interrupt esterni sono due cose completamente diverse.

L'interrupt 0, quando attivato, viene agganciato ad uno specifico pin fisico del chip (nella fattispecie dell'Arduino UNO il pin D2) e si solleva sono al verificarsi dell'evento che hai impostato tu con attachInterrupt.
http://arduino.cc/en/Reference/AttachInterrupt

I timer invece possono sollevare interrupt interni, che sono del tutto differenti ai primi.

Grazie Leo72, hai ragione, ho fatto confusione! Ma a che frequenza di clock lavorano gli interrupt esterni?

sto provando a "captare" dei segnali onda quadra che simulano la ruota fonica di un'auto a 60 denti meno 2, Arduino vede il segnale RISING e puntualmente esegue la ISR corrispondente, che però, per il momento è lontana dall'essere perfetta. Purtroppo mi si è rotto l'oscilloscopio e non posso controllare l'esattezza del segnale in entrata e quello che genero io su un pin specifico per verificare fin dove riesce a seguire fedelmente il segnale in entrata, diciamo che usando solo il led, senza mettere Serial.print() da nessuna parte, sembra funzionare fino a fondo scala del simulatore, se fosse così, equivaldrebbe a circa 20-22000 rpm !! (una volta l'ho collaudato vicino ad una Centralina Mectronik da competizione e nel sw di gestione mi indicava appunto quei giri) purtroppo, senza oscilliscopio non posso verificare appunto la vera frequenza del simulatore e quindi di Arduino. Purtroppo, non appena metto un unica chiamata a Serial.print() (115200bps, provato in svariati modi e svariati posti del code), tutto va ok fino a circa 3800-4000 rpm (da calcoli nel Arduino basato sul periodo misurato tra due denti successivi), appena supero +/- quella soglia, incomincia a rallentare e diventare saltuario il refresh del serial monitor e incominciano ad apparire numeri a caso, ho verificato che quando questo accade, la variabile Tooth_Period viene corrotta, passando, per esempio da 200 uS al max di una variabile "unsigned long" o a 0. Forse la presenza del comando Serial.print() va ad interferire pesantemente sull'esecuzione del codice?

Agli inizi di questo problema, quando questo succedeva, Arduino si bloccava e basta, poi spostando alcuni pezzi di codice fuori dalla ISR riesco a far funzionare il Codice (almeno i led si vedono accesi/sfarfallanti al massimo) fino a fondo scala del simulatore senza blocco del codice, solo l'aggiornamento della seriale viene compromessa al punto da non avere più refresh. Ma basta scendere entro la soglia di giri ok, che tutto ricomincia a funzionare bene (o quasi, visto che senza oscilloscopio sono in "se" e "forse").

Quindi in vista di questo scoglio, volevo sapere se, eventualmente non riuscissi a trovare una soluzione di "codice" esisterebbe una soluzione di configurazione degli interrupt più veloce? o meglio, anche di una comunicazione seriale più veloce o non bloccante (al riguardo mi sembra di aver trovato qualcosa, ma per ora è tosta capirci qualcosa, me lo studierò prossimamente).

Grazie per qualunque idea o chiarimento al riguardo :wink:

PS. in realtà per il mio progetto, non serve poter leggere oltre 7000rpm (che per ora non vedo!!) ma sarebbe super poter andare oltre!!

Gli interrupt esterni non hanno una frequenza propria, sono sollevati nel momento in cui si verifica la condizione di segnale che hai selezionato. Il codice della ISR viene poi eseguito a 16 MHz, così come tutto il resto del codice.

Piuttosto, non si potrebbe vedere il codice che stai usando? Forse rallenti tu la ISR con qualche istruzione "illegale" per una ISR

Se non ricordo male entra 4 cicli di clock viene eseguita la ISR a seguito dell'evento, se il codice è nullo
termina all'instante ma l'uscita dalla ISR ha un costo che non ricordo, tra un evento e l'altro il manuale dice (forse non ricordo bene) che alcune istruzioni non legate alle ISR vengono eseguite.

Per come l'ho capita io è:
ISR{

// al clock 7 esce dala ISR
}

clock 0, 1, 2, 3 entra nella ISR
clock 8, esegue tot istruzioni, es 4, quindi clock 9, clock 10, clock 11
L'evento si è già presentato al clock 9 ma viene eseguito al clock 11+4

Quindi la frequenza dell'onda quadra non dipende da nessun prescaler, ma dalla velocità
di esecuzione es se nella ISR impieghi 600ms per spedire messaggi via seriale ti perderai tutti gli eventi
che si verificano in questi 600ms.

Alla entrata di una ISR viene eseguito qualcosa tipo cli() cioè clear interrupt e all'uscita sei() set interrupt
quindi finché il micro esegue codice dentro la ISR gli interrupt sono disabilitati, al termine della ISR vengono riabilitati gli interrupt.

In sostanza, nelle ISR, prendi nota di quello che devi fare dopo che la ISR termina, poi nel loop esegui secondo le note prese. Cose del tipo devo inviare questo, devo sottrarre questo a quello e spedirlo via ISP ecc, solo appunti di quello che devi fare dopo che la isr termina. Tutto ciò per evitare la perdita di eventi.

Devi fare un pausa con Arduino, scaricare il datasheet del ATmega328P e cominciare a leggere, diversamente
avrai sempre un mare di dubbi e false certezze e non è divertente.

Piuttosto, non si potrebbe vedere il codice che stai usando? Forse rallenti tu la ISR con qualche istruzione "illegale" per una ISR

Straquoto leo, che mi ha preceduto.

Ciao.

ok, nel frattempo, ho fatto qualche progresso, togliendo alcune cose presenti nella ISR sono riuscito a salire fino a 6700 RPM non male, ma vedo che questo valore dipende fortemente dal volume di carico della MCU, forse sto chiedendo troppo a questo Micro!!

Mauro, anch'io avevo letto sul datasheet del 328P che ogni Interrupt consuma un tot di cicli clock, per la precisione nel punto:

7.7.1 Interrupt Response Time
The interrupt execution response for all the enabled AVR interrupts is four clock cycles minimum. After four clock
cycles the program vector address for the actual interrupt handling routine is executed. During this four clock cycle
period, the Program Counter is pushed onto the Stack. The vector is normally a jump to the interrupt routine, and
this jump takes three clock cycles. If an interrupt occurs during execution of a multi-cycle instruction, this instruction
is completed before the interrupt is served...
A return from an interrupt handling routine takes four clock cycles. During these four clock cycles, the Program
Counter (two bytes) is popped back from the Stack, the Stack Pointer is incremented by two, and the I-bit in SREG
is set.

quindi se non mi sbaglio, per un interrupt in totale ci servono 11 cicli clock. 11*(1000000/16000000)=0.6875 uS quindi da questo posso dedurre che non ho ancora raggiunto il limite degli interrupt "teorici" ovviamente il resto del codice "consuma" cicli clock anc'esso!! forse è li che sto sbagliando!!

Mauro, sono d'accordo con te sul fatto di prendere il datasheet del 328p, qualcosina già lo sto facendo, ma ci devo andare piano, sono ancora molte le cose che devo imparare e sopratutto memorizzare (ho problemi di memoria purtroppo) altrimenti il cervello mi scoppia!! se ci arrivano troppe info tutte insieme. E per questo che tento di imparare dai miei stessi sbagli facendo le cose che mi motivano (applicazioni per il campo automotive) così ho maggiori chance di imparare. Infatti, nel momento che ho realizzato che forse un 328P non potra compiere con l'incarico del progetto ho valutato anche la possibilità di passare a MCU più performante (consiglio datomi in un mio 3D passato) e non nego che ci sto pensando, ma per evitare di buttare alle ortiche ciò che so già del 328P e dover partire quasi da 0, ho valutato anche l'opzione di usare due MCU una per i task di Timing critici e l'altra per il resto, comunicazione seriale compresa. Tra di loro comunicherebbero in SPI che dovrebbe essere molto più veloce della UART. Ma queste sono tutte supposizioni che pian piano ci arrivo, si andranno migliorando o "smontando" . Ovviamente molto del successo o meno dipende da ciò che farò io di codice e questo nel bene e nel male, mi darà comunque una certa soddisfazione considerando la mia situazione "autodidatta al 100%" .

Fatemi sapere come vedete questo "codiciotto a pane e puparuolo" come si dice dalle nostre parti, hahahaaaa!!!
Grazie per la pazienza!

comunque, ecco lo sketch, per favore non mi "mangiate" dopo tutto sono alle prime armi!! (e del tutto autodidatta al 100%)

//***********************************************************************************************************
//**      RICONOSCIMENTO RUOTA FONICA  ver 0.1
//**      primo tentativo di creare function sincronia ruota fonica...  
//***********************************************************************************************************

 volatile int Tooth=0;                         //Contatore denti ruota fonica
 volatile unsigned long Time_Actual=0;         //valore attuale in micros()
 volatile unsigned long Tooth_Period=0;        //periodo calcolato tra due denti,
 volatile unsigned long Last_Time=0;           //valore tempo precedente memorizzato per calcolo periodo dente
 volatile unsigned long Last_Tooth_Period=0;   //periodo dente precedente,
 volatile unsigned long Giri=0;                //RPM
 
 int Pin_Segnale_Out=13;                       //serve a controllare ad oscilloscopio se "triggera" correttamente sul 5° dente
 int Pin_Ripetitore_Segnale_In=12;             //serve a controllare ad oscilloscopio se "riceve" correttamente ogni segnale INTERRUPT, 
                                               //ad ogni segnale INTERRUPT, deve corrispondere uno su questo pin in uscita. (confrontare 
                                               //traccia Segnale su pin Interrupt e questo)
                                               
 int Numero_Denti=60;                          //numero denti ruota fonica contando anche i denti mancanti, es. 60, 36 ecc
 int Numero_Gaps=2;                            //numero denti ruota fonica mancanti, es. 1, 2 ecc (solitamente questi due sono i più usati).
 int Tooth_Gap_Ratio;                          //fattore di ampiezza del dente, per trovare il primo dente, il sistema confronta il periodo
                                               //attuale con quello precedente se questo fosse equivalente al numero di denti mancanti + una tolleranza
                                               //allora si avrebbe trovato il GAP e quindi il PMS potendo chiamare quel dente 1. 

 int Loop_Counter=0;                           //contatore per verifica loops disponibili tra Serial.print() nella function loop()... solo per Debugging                           
 
//***********************************************************************************************************
void setup(){

  Serial.begin(115200);
  
  pinMode(Pin_Segnale_Out,OUTPUT);             //Pin dove indica segnale che "triggera" su pin specifico.
  pinMode(Pin_Ripetitore_Segnale_In,OUTPUT);   //Pin dove "ripete" (ECHO) il segnale in ingresso sul pin Interrupt0.
  
  digitalWrite(Pin_Segnale_Out,HIGH);          //Metto alto il segnale out, (per debugging)       
  
  attachInterrupt(0, Conteggio_Ruota_Fonica, RISING);  //interrupt 0 = pin digitale 2
}
//***********************************************************************************************************
void loop(){
  
 Loop_Counter++;   //mi serve per misurare quanti loop esegue man mano che aumentano gli interrupt, tra dati monitorati stampa anche i cicli contati 
  
  if (Tooth >= (Numero_Denti - Numero_Gaps)) {    //serve a trasmettere una volta a giro durante il missing tooth (oovero dopo l'ultimo dente "VERO")
  //Serial.println(Time_Actual);                  //visualizza tempo corrente con -> micros()
  //Serial.println(Last_Tooth_Period);              //valore precedente salvato, serve per calcolo Tooth_Period
  //Serial.println(Tooth);                        //contatore Denti
  Serial.println(Giri);                           //giri/min calcolati in base al Tooth_Period (quindi, istantaneo)
  //Serial.println(Last_Time);                    //                  
  //Serial.println(Tooth_Period);                 //periodo tra due interrupt causati dal segnale di due denti consecutivi.
  //Serial.println(Loop_Counter);                   //l'ho inserito per verificare quanti cicli di "loop" esegue tra due stampate (1 giro completo)
  
  Loop_Counter=0;                                 //resetto contatore di loop al fine del Serial.print()
  
  //delay(2);
  delayMicroseconds(Tooth_Period/20);             //l'ho usato per mantenere acceso il led per un dutycycle del 5% in modo da non "frenare" troppo l'esecuzione
                                                  //altri metodi più efficaci da implementare, tipo "blink without delay()" sicuramente farebbero meglio!!
  
} //fine if
  
  digitalWrite(12,LOW);                    //spegne il led ripetitore del segnale in entrata...
  
    
  }
//***********************************************************************************************************
void Conteggio_Ruota_Fonica(){
 
  digitalWrite(12,HIGH);                   //accende il LED ripetitore del segnale in entrata...
  
  Time_Actual = micros();                  //assegna alla variabile il valore del timer...
  Tooth_Period = Time_Actual - Last_Time;  //sottrae al tempo del timer appena misurato del tempo precendentemente misurato
  Tooth_Gap_Ratio = Numero_Gaps * 10 ;
  
  if ( Tooth_Period > ((Last_Tooth_Period/10) * Tooth_Gap_Ratio) )   //se il tempo dente attuale è uguale o maggiore di 2 volte (ho prima 
                                                                     //diviso per 10 per poi moltiplicare per il Tooth_Gap)
                                                                     //(per i due denti mancanti) al valore precedente assegna alla variabile
                                                                     //Tooth il valore di 1 ovvero ha trovato il primo dente per la sincronia
     {Tooth= 1;    //una volta trovato il "gap", comincio il conteggio denti.
     
      } //fine if
 
 Last_Tooth_Period = Tooth_Period ;     //allo stesso tempo, salva il valore Tooth_Time nella variabile Last_Tooth_Time per il prossimo ciclo...
 
  if (Tooth ==5)                        //servirà a costatare se fa la sincronia con i denti in entrata...
  {digitalWrite(13,HIGH);               //accende LED simulazione segnale out di verifica sincronia con ruota fonica (per prove con oscilloscopio)
    delayMicroseconds(20);              //Solo delayMicroseconds() funziona dentro un ISR! (per prove con oscilloscopio)
     digitalWrite(13,LOW);              //spegne LED simulazione segnale out di verifica sincronia con ruota fonica (per prove con oscilloscopio)
       
} //fine if

 Last_Time = Time_Actual;               //copia la variabile Time_Actual nella variabile: Last_Time per essere usata nel calcolo dell'interrupt successivo...
 
 Tooth = Tooth++;                     //incrementa contatore denti ruota fonica... 
 
 
 if (Tooth > (Numero_Denti - Numero_Gaps )) {    //verifica il contatore denti, se supera la soglia possibile
         Tooth = 1; 
     }//fine if
 
 Giri = ((1000000 / Last_Tooth_Period)/Numero_Denti)*60 ;  //calcola i giri/min in base al Tooth_Period.

 //TX_Dati();  //solo per Debugging
 
 }

//**************************************************************************************************************
void TX_Dati(){    //solo per uso Debugging 
  
  //if (Tooth >= (Numero_Denti - Numero_Gaps)) {    //serve a trasmettere una volta a giro durante il missing tooth (oovero dopo l'ultimo dente "VERO")
  //Serial.println(Time_Actual);                  //visualizza tempo corrente con -> micros()
  //Serial.println(Tooth_Time);                   //tempo impiegato per un unico dente, serve al calcolo Tooth_Period
  //Serial.println(Last_Tooth_Time);              //valore precedente salvato, serve per calcolo Tooth_Period
  //Serial.println(Tooth);                        //contatore Denti
  //Serial.println(Giri);                           //giri/min calcolati in base al Tooth_Period (quindi, istantaneo)
  //Serial.println(Last_Time);                    //                  
  //Serial.println(Tooth_Period);                 //periodo tra due interrupt causati dal segnale di due denti consecutivi.
  //Serial.println(Loop_Counter);                   //l'ho inserito per verificare quanti cicli di "loop" esegue tra due stampate (1 giro completo)
  
}

Purtroppo a questo codice, dovrei aggiungere tantissime altre cose, come il calcolo dei gradi rotazione albero motore, ecc inizialmente li avevo messi dentro la ISR ma poi ho decisi di toglierla e pensare prima a far funzionare correttamente questa prima parte. Ovviamente, per applicazioni VERE, vanno aggiunti tantissimi sistema di verifica contro errore, che andranno a pesare sulle prestazioni anche loro...

Ciao hiperformance71, se fai una ricerca sul forum in data poco prima della tua iscrizione trovi dei post di un utente (che ti ha anche risposto) che ha fatto più o meno quello che stai tentando di fare tu. Nel suo caso ricordo un motore fuori bordo con il controllo accensione via Arduino e funzionava anche abbastanza bene.

Concordo con quasi tutto quello che dici e il modo di accrescimento delle competenze è corretto. Rimarco l'importanza di ciò che sta scritto nel datasheet del 328 e in genere in ogni datasheet è fonte primaria di conoscenza da acquisire prima possibile. Nel tuo caso tu stai affrontando quello che viene chiamato time critical application, che sostanzialmente vuol dire che l'applicazione embedded (quindi hardware/software) ha dei vincoli temporali stringenti da rispettare e questi vincoli devono in ogni condizione essere rispettati e ciò si ottiene sicuramente con codice di basso livello, principalmente perché è possibile calcolare con precisione quanti cicli impegna una porzione di codice. Il tutto si traduce in:

  • niente codice di funzioni di libreria, non per il fatto che una chiamata a funzione ha un costo, ma per il fatto che il tempo che la funzione impegna la cpu è sconosciuto e inoltre non hai conoscenza dettagliata del codice eseguito dalla funzione di libreria

  • Al posto di for(... ecc quando il numero di loop è conosciuto ed in numero non superiore a 10 (non fiscale) è meglio srotolare il for, cioè scrivere singole linee che svolgono la stessa funzione. Questo tipo di ottimizzazione viene attuata dal compilatore quando ??, appunto quando? in punti critici meglio quindi anticipare il compilatore e inoltre possiamo conteggiare con precisione il tempo cpu impiegato

  • Cercare soluzioni embedded (quindi hardware/software) che rispettino i vincoli temporali così da poter dire
    che la nostra applicazione embedded esegue funzioni (inteso come funzionalità) real-time che non vuol dire ciò che molti pensano ma vuol dire rispettare i vincoli temporali imposti dal progetto, in mancanza di ciò la nostra applicazione fallisce

Ci sarebbe molto ancora da dire, ma come sai il tempo e lo spazio impiegato si allargherebbero a dismisura. Alcune cose devo dirtele affinché tu non insegua chimere:

Arduino style non è pensato per applicazioni real-time e qui si scopre che la coperta è sempre corta, cioè semplicità e immediatezza di Arduino style ti coprono la faccia, ma scoprono i piedi. Di contro complessità, linguaggio Assembly, architettura di sistema real-time e competenze hardware/software non comuni ti coprono i piedi ma scoprono la faccia. Capirai che la faccenda è ancora aperta, ciò come allungare la coperta? Si discute tanto e scrive tanto è ancora una soluzione unica non è stata inventata e la complessità anche se cambia forma è sempre li, per esempio i generatori di codice, cambiano forma alla complessità promettendo alte e costanti ottimizzazioni del codice. Tra l'altro tu venendo dal mondo PLC di generatori di codice ne sai parlare.

Esistono microcontroller dedicati a rispettare maggiormente i vincoli temporali, specie nel caso degli eventi esterni che sollevano interrupt e questi non fanno parte della famiglia ATmega, da informazini da manuale ci sono gli XMega che con Arduino non possono essere usati e comunque Arduino farebbe da tappo comunque.

Arduino style tuttavia ti sta dando la possibilità di apprendere con semplicità, ma sappi che prima o poi verrà il momento di abbandonarlo e quindi preparati. Se la tua applicazione non fosse hard real-time ti avrei detto di rimanere in ambito Arduino, ma l'applicazione se manca i vincoli temporali l'1% del tempo la possiamo dichiarare fallita.

Conclusioni
Continua con Arduino, ma fai meno uso delle funzioni (pre)confezionate, niente del tutto dentro un blocco {} ISR.
Il costo cpu delle operazioni su i tipi float e double è maggiore di quello speso sugli interi. A costo di mal di testa continui e di ossessione durante i sogni trova il modo più conciso di prendere appunti dentro la ISR e poi esegui in base agli appunti i calcoli fuori da ISR. Ricorda che una ISR non è interrompibile da nulla, mentre il codice nel loop() è interrompibile da eventi esterni/interni come gli interrupt. Anche se il metodo della multimcu è applicabile, fattibile e pratica comune considera che dovrai essere in grado di portare a compimento il doppio del lavoro seguendo gli stessi consigli di cui sopra e anzi dovrai avere più competenza su sistemi real-time distribuiti, per cui al momento non fare il passo più lungo.... Vedi con Arduino board dove riesci ad arrivare, ottimizzando al massimo il codice e poi se sei arrivato al limite, passa ad altro sistema di sviluppo es qualcosa su Arm ma con FPU a bordo.
Sicuramente riuscirai a realizzare l'applicazione con il limite numero di giri accettabile, ma 22mila giri/min per ATmega sono sogni ad occhi aperti.

Ciao.

@Hiperformance:
ti do comunque (ti avevo detto appunto che forse la ISR era stata scritta con istruzioni che non andavano bene al suo interno, e dopo aver visto il codice, ma mi pare che hai un pò ignorato il mio msg) una mia personalissima valutazione sulla ISR: non va bene.

Non puoi mettere tutta quella mole di calcoli, addirittura delle divisioni quando la MCU non le esegue in hardware, poi ci sono tante digitalWrite che sono già lente di loro, addirittura dei delay.

Mauro, grazie della risposta, sono daccordo in tutto quello che dici, la tua è la voce dell'esperienza e della conoscenza in materia e non posso che prenderne atto, sono consapevole delle mie lacune e poche capacità, sicuramente, sto facendo il passo più lungo della gamba, ma come ti ho detto, tutto questo mi serve per imparare, e ho messo in conto che se mai un giorno un mio progetto del genere vedrà la luce commercialmente di sicuro non sarà per mio merito ma per quello di un professionista Embedded contrattato apposta per il lavoro, purtroppo per ora, le risorse economiche per commissionare un prodotto del genere non ci sono e poi, anche se ci fossero, mi piacerebbe capirci qualcosa leggendo il codice, per esempio,due anni fa ho letto il codice sorgente in C della centralina Microscquirt e non ci ho capito un tubo, oggi gli ho ridato un' occhiata e incomincio a vedere una luce dentro al tunnel, significa che qualcosa è cambiato, adesso, grazie alla facilità di arduino sto entrando nel mondo dei linguaggi C anche se concordo con te quando dici che Arduino style è troppo limitante, ne ho già letto al ruguardo e l'ho messo in conto è per questo che sto iniziando a fare preventivamente questi test, così vedo già i suoi limiti come linguaggio, poi tenterò di riprodurre questo lavoro "Arduinesco" in linguaggio 100% C (poco a poco ci capirò di più). Per l'assembly, beh, credo che questo vada molto oltre le mie capacità, ma mai dire mai...

Tornando al mio problema, credo di aver trovato il problema, da quello che ho letto sull'argomento sul sito di Nick Gammon, per eseguire un interrupt esterno con "attachInterrupt()" in realtà si spendono molti più cicli di clock di quanto pensavo, in un'esempio che fa, con chiamata a funzione SPI, fa il calcolo del dispendio di cicli clock di ogni comando e alla fine esce che per completare la chiamata interrupt esterna, si spendono circa 7 + 16 + 19 + 82 cicli di clock per un totale di 119 cicli clock per complessivi 10,25uS (solo attachInterrupt() ne consuma 82 cicli o 5,125uS) in un sistema a 16MHz...

Quindi a questi tempi, più o meno, vanno ancora aggiunti i cicli clock spesi nella nostra ISR, risultato, non è idoneo al task per cui l'ho pensato, magari facendo qualche ottimizzazione con plain C e meglio ancora con Assembly potrà avvicinarsi di più, ma incomincio a dubitare del buon esito finale, quindi mi vedrò costretto a ripiegare su altro sistema più potente (tipo il Tensy3.1?)

Al riguardo il mio codice, hai qualche suggerimento per snellire la parte di ISR in modo da tirare fuori da essa i calcoli, non so ancora come fare a processarli nel loop() o in altre function apposite chiamate dalla loop(). Anche se so di non poter avere successo con il 328P voglio comunque vedere fin dove sono IO capace di arrivare e ovviamente, fin dove sarà idoneo il Micro, poi si vedrà, il bello dei linguaggi C (ANSI intendo) è la portabilità no? basterà rivedere i dati tecnici specifici del nuovo micro, magari questo lo farà un'esperto al posto mio, a me mi basterà aver avuto un minimo di "success" nell' iniziare il progetto, già riuscire un giorno a far partire il motore e tenerlo a minimo sarà gratificante come scalare l'Everest per uno scalatore novello.

Per micro adatti al task, ho trovato molte info specifiche per uso automotive in dei micro Freescale che hanno incorporato dei "eTPU" ovvero "enhanced Timing Processor Unit" specificamente progettati per questi task real-time, ho letto la documentazione e da cui sto tentando anche di trarre qualche idea, ma al momento anche volendo, non potrei usarlo così com'è primo per le ovvie motivazioni (non sono all'altezza tecnicamente) e secondo perchè il codice attualmente sviluppato per queste eTPU non ha le caratteristiche di CAM richieste per il mio progetto (nel senso che può gestire solo un segnale CAM, mentre nel mio è richiesto che ne processi 7) Ovviamente a questa limitazione ci si potrebbe porre rimedio riprogrammando la parte di codice corrispondente, ma non so nemmeno se sia poi fattibile. Lascio questo arduo compito a persone più capaci di me.

di nuovo, grazie per la pazienza!! :wink:

leo72:
@Hiperformance:
ti do comunque (ti avevo detto appunto che forse la ISR era stata scritta con istruzioni che non andavano bene al suo interno, e dopo aver visto il codice, ma mi pare che hai un pò ignorato il mio msg) una mia personalissima valutazione sulla ISR: non va bene.

Non puoi mettere tutta quella mole di calcoli, addirittura delle divisioni quando la MCU non le esegue in hardware, poi ci sono tante digitalWrite che sono già lente di loro, addirittura dei delay.

Grazie Leo72 per la risposta, era quello che pensavo, stanotte mi sono letto un pò di cose al riguardo gli interrupt e ho capito di averla fatta GROSSA!! le ISR vanno tenute brevi!!! scusate la mia ignoranza. A questo punto, però, non so come fare, (almeno ora su due piedi, magari più tardi ci rifletto di più e provo) Come potrei far solo sapere al resto del programma che un'evento Interrupt è avvenuto? ed il timing con micros() dove lo dovrei mettere, dentro o fuori? PEnso dentro, magari il resto fuori no?

Leo non te la prendere se non ho seguito i tuoi consigli precedenti, in realtà li avevo seguiti! c'era tantissima roba ancora dentro, tra cui altra mole di calcoli che per ora ho eliminato (ma che andrà poi fatto fuori ISR per forza) e addirittura dei serial.print() e un delay(), poi leggendo un pò ho visto che queste functions non vanno usate dentro le ISR pena risultati inattesi, ho provato a fare ciò che consigliavano, usare delayMicroseconds() invece di delay() e a non usare Serial.print() dentro la ISR, ma comunque ho sempre gli stessi limiti, ma non credo siano solo delle routine ISR, da sola, senza chiamate Serial.print() sembra funzionare benissimo! ("Sembra" perchè come detto all'inizio, il mio DSO è rotto e non posso confermare al 100% , ma dai led che continuano ad accendersi...). Saprò dire di più solo quando avrò risolto il mio problema di DSO.

Mauro mi ha indicato un post riguardante un progetto simile precedente alla mia iscrizione sul forum con problematiche analoghe riguardo un'accensione per motore fuoribordo, vedrò di dare un'occhiata.
In attesa di vostre nuove, vedrò di rivedere pesantemente questo "sch..." di ISR che ho fatto per renderlo appena più "decente".

Nuovamente, grazie per la pazienza!! :wink:

hiperformance71:
Come potrei far solo sapere al resto del programma che un'evento Interrupt è avvenuto?

Con una variabile di stato dichiarata globalmente e con la direttiva volatile davanti.
Credo però ti risponderà @Leo (che è l'esperto degli interrupt) in maniera più esauriente . :smiley:

Sul sito di Gammon, sezione "Writing an ISR", usa una variabile flag:

@Leo ma sul tuo sito non avevi una pagina che spiega gli interrupt? Mi confondo?

MauroTec:
Tra l'altro tu venendo dal mondo PLC di generatori di codice ne sai parlare.

Mauro, ma io non vengo dal mondo PLC, so appena cosa sono!! io ho competenze tecniche specifiche del settore Automotive, è la mia passione da oltre 30 anni, negli ultimi 20 ho coltivato conoscenze nell'elettronica dell'auto e da circa 15 anni lavoro con le centraline delle auto, stradali e da competizione, da circa 5-6 anni è nata la passione per i sistemi a microcontrollore, ai primi passi optai per i Basicx Bx24, erano programmabili in Basic e questo era un linguaggio che stavo imparando sommariamente oltre 20 anni prima andando appresso ad un'amico di gioventù. Causa incidenti di percorso con le breadboard, ne ho bruciati 2 e quindi sono passato a sistemi più econimici, i picaxe, i quali sono sembrati molto più facili da programmare ma al costo pesante della velocità e delle grandi limitazioni matematiche/aritmetiche o numero di variabili disponibili, e quindi, dopo aver fatto vari progettini con essi, un anno fa ho deciso di provare la strada arduino, prima di allora, non sapevo quasi cosa fosse il C!! e per questo che dico sono "autodidatta al 100%" perchè non ho nessun tipo di background "Elettronico/Informatico" appreso sui banchi di scuola o università.

Per questo è che sono così "pasticcione", quello che so, l'ho sviluppato in anni di assimilazione di esempi e articoli web o più recentemente, di libri sul tema, di applicazioni che fanno ciò che voglio ne ho trovate con arduino, anche se magari fanno uso mssiccio di ANSI C, ma se non sono in grado di capire cosa stanno facendo, preferisco arrivarci con le mie stesse forze.

Adesso, grazie a Voi sto progredendo molto e sono fiducioso che un giorno potro fare cose migliori (tempo permettendo purtroppo!!)

Ciao!

nid69ita:

hiperformance71:
Come potrei far solo sapere al resto del programma che un'evento Interrupt è avvenuto?

Con una variabile di stato dichiarata globalmente e con la direttiva volatile davanti.
Credo però ti risponderà @Leo (che è l'esperto degli interrupt) in maniera più esauriente . :smiley:

Grazie Nid69ita, stavo giusto pensando a qualcosa del genere, ma attendo "istruzioni" per maggior comprensione e per evitare di fare altre "cavolate" !!!

Mauro, ma io non vengo dal mondo PLC, so appena cosa sono!!

Allora ho confuso utente.

Il problema per me è non poter scrivere concisamente per descrivere come affrontare il problema, perché il rischio di non essere compreso è alto, ma mi preoccupa di più essere frainteso.

Se ti dicessi di creare una struct TimeEvent snella e uno buffer che usi come descrivo di seguito tu cosa capisci?
Crei un buffer circolare di struct TimeEvent, es 50 elementi. Nella isr compili una struct e la inserisci nel buffer.
Nel loop consumi il buffer un evento per volta e ci fai quello che serve.

Il timing con millis() va bene per le normali applicazioni non real-time, in sua vece occorre creare un system timer tick tramite la isr del timer hardware, in questo leo è bravissimo.

La comodità abbiamo deciso di non usarla, quindi niente funzione che ritorna il valore del contatore, ma direttamente un extern volatile uint32_t currentSysTimer per accedere alla variabile contatore senza l'overhead della chiamata a funzione.

Le lookup table sono amiche, quindi occorre studiare il problema per vedere dove e come possono essere usate, al fine di velocizzare il tutto. Cioè ad esempio se devi ricavare la radice quadrata di un numero intero e sai il range di questo, crei una lookup table con tutti i risultati e quando serve lo peschi dalla tabella senza calcolarlo.

PS: mi rendo conto che è comprensibile solo da chi ha una base di programmazione software

L'utente del fuori bordo è "lucaleo", qui il thread centralina accensione motore - Software - Arduino Forum
e qui il video del fuori bordo in vasca http://www.youtube.com/watch?v=YxuASj69tMs&feature=youtu.be

Se guardi il suo codice nelle isr è ridotto al minimo, segue un estratto:

void startt()//interrupt startt
{
  statestartt = !statestartt;  //inverti startt
}

Ciao.

nid69ita:
@Leo ma sul tuo sito non avevi una pagina che spiega gli interrupt? Mi confondo?

Ho pubblicato tanta roba, progetti che fanno uso di molte cose delle MCU. Gli interrupt li ho usati in diversi progetti, ma non gli ho mai dedicato un articolo sul mio sito. Però gli interrupt potete scaricare lo zip allegato a questo post:
http://forum.arduino.cc//index.php?topic=167655.msg1303328#msg1303328

Sono le 2 presentazioni che ho fatto a Bassano lo scorso anno, una tratta anche degli interrupt, dal cosa sono al come usarli al meglio.

MauroTec:
Il timing con millis() va bene per le normali applicazioni non real-time, in sua vece occorre creare un system timer tick tramite la isr del timer hardware, in questo leo è bravissimo.

Il SAM3X dell'Arduino Due ha un timer dedicato a questo preciso scopo, generare un segnale di tick usato proprio per applicazioni realtime tipo RTOS o altro.
Sull'Atmega si può usare un timer con relativo interrupt che incrementi un contatore da usare come sys tick e che faccia anche il passaggio da un task all'altro.

Ma il compito da far eseguire alla ISR in fin dei conti quale dovrebbe essere? Magari, capendo questo, potremmo vedere di ottimizzarla al max.

Grazie Leo per le indicazioni, vrdrò di dargli un'occhiata più tardi.

Diciamo che nella function ISR sono riuscito a tirare fuori tutto, mettendoci solo l'attivazione di una variabile_flag poi nel loop() controllo se quella variabile è =1 se si eseguo quello che prima stava dentro la ISR.

L'ho fatta questa modifica insieme ad altre cose, funziona fino ad un certo punto, ma vedo delle inconsistenze man mano salgo con i giri. Mentre scrivevo queste righe, forse ho capito perchè, tirando fuori dalla ISR anche la parte di codice che calcola il timing ovvero i vari Tooth_Period e Last_Tooth_Period ecc rischio di corrompere i dati in SRAM se un nuovo interrupt avvenisse mentre sta lavorando con i dati precedenti (se presente una comunicazione seriale rallenta di molto e quindi il rischio aumenta), quindi rifaccio questa parte e poi la posto per conoscere la vostra opinione.

ok, sembra funzionare MOLTO meglio, infatti, come dicevo, tutta la parte di calcolo dei timing va fatta dentro la ISR, visto che non può essere interrotta da un'altro interrupt i dati ottenuti sono più stabili. Adesso riesco a raggiungere senza incertezze i 6500 rpm, ma il limite attuale è salito fino a 11200 rpm!! non mi serve arrivare oltre i 6500/6700 rpm, propio esagerando i 7000 rpm, quindi adesso incomincio ad avere un po più di fiducia (in me più che nel Micro) e vero che è un 8 bit 16MHz, ma io penso ai vari MCU presenti su centraline gloriose del passato come le IAW montate sulle Delta HF per esempio, erano anch'esse a 8 bit, avevano un cristallo da 4 MHz (ma a quanto sembra con prescaler per lavorare a 1 MHz) , era sequenziale fasata, ma ancora con accensione con distributore meccanico, una ruota fonica a 4 tacche (nei PMS di ogni cilindro) e di una in camma con due tacche (una per PMS cilindro 1 e l'altra per conoscere i gradi di triggering con maggiore precisione. Ovvamente con così poche risorse MCU (motorola MC6803U se non ricordo male, ma dipende dal modello di ECU) disponeva di IC hardware dedicati che facilitavano certi compiti ma che io non saprei come usare al momento (come il Timer 6840 per gestire gli inniettori ecc), ebbene, questa gloriosa ECU di quasi 30 anni fa ancora oggi è in grado di andare oltre i 7500 rpm!! Quindi sono fiducioso che con arduino o meglio, con il 328p Semplificato al massimo di codice, possa farcela senza grosse pretese ovviamente!! In realtà, qui la vera incognita non è ne le capacità Hardware o software necessarie, bensì le capacità (scarse) del programmatore, ovvero me!! :blush: spero di sovvertire questo entro qualche anno!! (almeno ci provo).

Ti chiedevo perché ci sarebbe un modo per poter far fare ad un timer una parte di lavoro.
Il timer 1 può essere configurato anche nella funzione Input Capture, ossia all'arrivo di un segnale sul suo pin esterno (ICP1), può memorizzare il valore del suo contatore in un determinato registro e sollevare un interrupt.

Leggi il datasheet, cap. 16.6