problema con Econder Incrementale

Buongiorno a tutti dopo un paio di mesi di lavoro sono in dirittura di arrivo con la mia tesi, cioè il controllo con arduino di un pendolo inverso. l'arduin deve prima con un algoritmo di swing ribaltare il pendolo e poi tenerlo in equilibrio... dati gli scarsi mezzi che ho a disposizione (un binario per porte scorrevoli, una cinghia che slitta e un motorino smontato da una stampante) non è stato facile...ma qualcosa funziona... il principio di funzionamento è fatto da 3 controllori in cascata il pendolo è attaccato ad un perneo che ruota quasi senza attrito, ed un encoder incrementale a 2000 settori mi resitituisce l'angolo con questo viene calcolato l'angolo e la velocità angolare il controllore PD mi passa poi un riferimento al controllore PI in velocità, che mi manda un riferimeto al PWM

tutto funziona..MA.. l'encoder conta attraverso interrupt sui pin 2 e 3 finche oscilla piano e non va tanto avanti e indietro non c'è problemi...quando si inizia a parlare di rapidi cambiamenti di direzione, (intoro alla posiz di equilibrio) l'arduino perde qualche conteggio e così l'angolo del pendolo mi risulta sbagliato anche di una decina di settori (quasi 2 gradi) e quindi non funziona più niente ho anche provato a far contare 1000 all'encoder e ridurre il tempo di campionamento (loop) a 20millisec, ma non è cambiato nulla!

quali sono le istruzioni che non vengono interrotte dall'interrupt? o come potrei fare per non perdere il conteggio di qualche settore? se volete vi allego il codicie, ma è molto lungo!

grazie

Tommaso

Tommaso:
quali sono le istruzioni che non vengono interrotte dall’interrupt? o come potrei fare per non perdere il conteggio di qualche settore?

Non esiste nessuna istruzione che non viene interrotta, parlando di Assembly, quando si verifica un interrupt al termine dell’istruzione in esecuzione, da 1 a 4 cicli macchina, il controllo viene trasferito alla isr.
Sarebbe il caso di vedere il vero codice C generato da wiring per capire se qualcosa disattiva temporaneamente l’interrupt esterno a favore di altri processi.
Comunque c’è un limite di velocità gestibile dagli interrupt esterni, se lo superi è normale che ti perdi dei conteggi, tocca farsi due conti con la risoluzione dell’encoder, la velocità angolare e la durata della ISR per capire se la frequenza degli impulsi supera la velocità massima gestibile dall’ATmega 328.
Altra fonte di problemi può essere come gestisci gli impulsi all’interno della ISR, se ci metti molto tempo è normale che perdi impulsi quando questi diventano veloci.
Non è che stai usando dei float all’interno della ISR ?

//---- ENCODER FUNCTIONS

void doEncoderA(){ 
  if (digitalRead(encoderPinA) == HIGH) { // low-to-high on channel A

    if (digitalRead(encoderPinB) == LOW) { encoder_counter++; } else { encoder_counter--; } }
  else {  // high-to-low on channel A

    if (digitalRead(encoderPinB) == HIGH) { encoder_counter++; } else { encoder_counter--; } }
} 

void doEncoderB(){ 
  if (digitalRead(encoderPinB) == HIGH) { // low-to-high on channel B

    if (digitalRead(encoderPinA) == HIGH) { encoder_counter++; } else {encoder_counter--; } }
  else {  // high-to-low on channel B

    if (digitalRead(encoderPinA) == LOW) { encoder_counter++; } else {  encoder_counter--; } }
}

queste sono le 2 ISR e encoder_counter è un ,long INT che sta nella RAM

pensavo che ci fossero delle istruzioni privilegiate come in altri uC che non venivano interrotte anche in caso di interrupt, per questo pensavo che qualche settore non venisse contato

Tommaso: queste sono le 2 ISR e encoder_counter è un ,long INT che sta nella RAM

Premesso che in realtà quello è solo del codice che wiring include nella vera ISR, che già contiene altro codice, è possibile ottimizzarle per ridurre i tempi di esecuzione in modo da poter aumentare la frequenza massima in ingresso. Toccherebbe misurare in modo abbastanza preciso la durata complessiva della ISR quando gira l'encoder, in questo modo è possibile determinare la massima frequenza in ingresso. Se hai a disposizione un DSO setta un pin qualunque a 1 logico quando entri nella "doEncoderA" e lo resetti quando esci. Girando l'encoder, sempre nella stessa direzione, vedrai un'onda rettangolare sul pin controllato dalla funzione, la durata a ON ti dice quanto tempo ci mette il micro ad eseguire quella funzione, ma non la durata totale della ISR. Aumentando la velocità di rotazione dell'encoder a un certo punto l'onda rettangolare diventerà irregolare, cioè non hai più impulsi positivi a distanza fissa, avrai dei salti nella sequenza, questa è la frequenza limite e, implicitamente, la durata complessiva della ISR inclusa la latenza d'ingresso e uscita. Trovato questo valore puoi verificare sperimentalmente se è maggiore o minore della frequenza in uscita dall'encoder quando perdi i conteggi.

pensavo che ci fossero delle istruzioni privilegiate come in altri uC che non venivano interrotte anche in caso di interrupt, per questo pensavo che qualche settore non venisse contato

L'interrupt è un evento hardware e viene gestito dal micro, il linguaggio di programmazione non può interferire con questo processo. Però è possibile attivare e disattivare a piacere gli interrupt pertanto il programmatore, e non il linguaggio, può decidere che una certa funzione del programma non deve essere interrotta, il prezzo da pagare è la possibile perdita di eventi legati agli interrupt. In alcuni linguagggi di programmazione, p.e. wiring, gli interrupt sono filtrati dal linguaggio stesso perché sono già in uso da alcune funzioni di alto livello, quindi non è detto che un interrupt definito dall'utente venga eseguito sempre subito perché è possibile che quella tipologia di eventi sia momentaneamente disattivata da altre funzionalità del software. Se parliamo di C ANSI non esiste nessuna funzione di libreria standard che disattiva gli interrupt, se parliamo di compilatore non standard che utilizzano librerie fuori specifiche, sopratutto quando l'interrupt viene gestito dal compilatore e non dal programmatore, è facile che determinati interrupt vengono disattivati temporaneamente e questo porta alla possibile perdita di eventi.

Intanto, perdona la mia ignoranza ma...cosa è un DSO?

Quello che volevo sapere era appunto se si sapeva che alcune funzioni dell'arduino, tipo che ne so, l'analogRead,le Serial.print o simili, disattivavano gli interrupt, così andavo ad ottimizzare il codice eliminando o riducento tali chiamate! non so se mi sono spiegato

Tommaso: Intanto, perdona la mia ignoranza ma...cosa è un DSO?

DSO = Digital Storage Oscilloscope, cioè un oscilloscopio digitale, puoi fare la stessa misura anche con un normale oscilloscopio analogico, ma sarà meno precisa.

Quello che volevo sapere era appunto se si sapeva che alcune funzioni dell'arduino, tipo che ne so, l'analogRead,le Serial.print o simili, disattivavano gli interrupt

Se parliamo di wiring quasi sicuramente si, però quali e come lo fanno non lo so, il solo modo per saperlo con sicurezza è prendere il sorgente C generato da wiring, puoi vedere dove viene messo compilando in modo verbose (tasto shift premuto), controllare cosa fa esattamente la ISR e se ci sono, oltre a dove sono, istruzioni che attivano/disattivano gli interrupt.

P.S. ho provato con uno sketch che stampa solamente il valore di encoder_counter e i settori sono contati correttamente, è quindi qualche cosa nel progamma completo che mi disattiva temporaneamente gli interrupt!

Ho fatto quello che mi hai detto te, compilato con Shift premuto, e sono andato nella cartella usr/tmp..ecc ecc che file devo controllarE?

@ Tommaso

Una cosa non ho capito; come mai Ti servono 2 interrupt per controllare l'encoder. Basta un interrupt su una fase. Nel momento che si verifica l'interupt controlli se l'altro canale è L o H e da tutto questo puoi capire che è stato un impulso e la direzione.

percui Ti basta come funzione interrupt: presumo che hai attivato l'interrupt con il passaggio L-H della fase A del encoder:

interrupt() { if (faseB) { encoder_counter++; } else { encoder_counter--; } }

Ciao Uwe

Mi servono 2 canali perchè sarebbe un 500 Settori, con 2 led quindi riesco a moltiplicare per 4 la risoluzione! entrambe le funzioni si attivano con CHANGE

Dai un’occhiata a questo sito dove vengono eseguite delle misure sui tempi di risposta di Arduino per gli interrupt esterni e come bypassare wiring per accorciarli notevolmente.

uno dell'HP a 500 settori, si chiama HEDS5500

è questo

http://it.rs-online.com/web/search/searchBrowseAction.html?method=getProduct&R=2056869

Tommaso: finche oscilla piano e non va tanto avanti e indietro non c'è problemi...quando si inizia a parlare di rapidi cambiamenti di direzione, (intoro alla posiz di equilibrio) l'arduino perde qualche conteggio e così l'angolo del pendolo mi risulta sbagliato anche di una decina di settori (quasi 2 gradi) e quindi non funziona più niente

Ma come fai ad essere certo di questa cosa ? Non è che il problema, in realtà, sia il pid che non riesce a stare dietro i movimenti ? Ovvero, come misuri la reale inclinazione del pendolo e la compari con la lettura dell'encoder eseguita da Arduino, sopratutto come fai a farlo in real time ?

Che ha problemi per alte velocità l'ho trovato in modo sperimentale. ho preso il programma completo e con una Serial .print stampo a video solamente il valore encoder_counter, se a mano muovo il pendolo lentamente non c'è problema,se inizio a fare movimenti rapidi, quando il pendolo torna in posizione verticale di riposo non si è azzerato il valore encoder counter e neppure è un multiplo di 2000! se invece tolgo tutte le altre funzioni, e lascio comunque la stampa a video dei settori, questo problema non c'è

A questo punto il modo più semplice per scoprire il problema è quello empirico. Attiva le varie funzioni singolarmente, o a blocchi se tra loro concatenati, fino a che non trovi quella che ti fa sbagliare i conteggi, fatto questo si vede di capire perché succede e di trovare una soluzione.

esatto, domani provo....oggi ho fatto le 20 ed ero fuso...il sospetto ce l'ho sull'analogRead

Non mi pare che la analogRead usi gli interrupt, il sorgente è wiring_analog.c, si vede chiaramente che attende la fine conversione con un normale polling.

// start the conversion
    sbi(ADCSRA, ADSC);

    // ADSC is cleared when the conversion finishes
    while (bit_is_set(ADCSRA, ADSC));

ti posto il codice…così magari se ci dai un occhio ci sta che trovi qualcosa che non va, e non è colpa di arduino, ma mia :slight_smile:

#include <math.h>
#define encoderPinA 2 
#define encoderPinB 3 

volatile long encoder_counter = 0;
float gradi;

float MATLAB_data[100];
int MATLAB_num=0;

int Tsam=20; // sample time (ms)
float Ts=Tsam/float(1000);

float  velocita;

float posiz;
float T=0;
float rif_velocita;
long T_int;
long passo=0;
float pwm=0;
float velocita_media;
float rif_gradi;
long target_time,wait_time,init_delay;
float velocita_angolare =0;



void setup() { 
  Serial.begin(9600);

  pinMode(encoderPinA, INPUT); 
  pinMode(encoderPinB, INPUT);
   //offset_encoder(); 
  attachInterrupt(0, doEncoderA, CHANGE);
  attachInterrupt(1, doEncoderB, CHANGE);
  
  avvio();
  
  init_delay=millis();  

}





void loop() {
  passo++;
  

  T_int=millis()-init_delay;
  T=float(T_int);
  


 posiz = letturaPosizione(); 
 gradi = lettura_angolo();

 velocita_media = vel_media(posiz);
 velocita_angolare = vel_angolare(gradi);

// rif_gradi = -gradi_desiderati(posiz);
 rif_gradi=0;


if ( abs(gradi)> 10) {
   rif_velocita = swing2(velocita_angolare, gradi);
  }
 else {
 rif_velocita = controllo_PD(gradi,rif_gradi);
 rif_velocita=rif_velocita/2;
  }

 pwm = controlloPI(velocita_media, rif_velocita);
 



 if (passo%100==0) {
//   Serial.print("settori encoder: ");
   Serial.println(encoder_counter);
 }

 if (abs(posiz)>50) pwm=0; // 0 se oltre i 50 cm
 controlloPWM(pwm); //aziona il PWM discriminando segnali positivi e negativi



  target_time=passo*Tsam;
  wait_time=target_time-(millis()-init_delay);
  if (wait_time>0) { delay(wait_time); } 

}





// ------- FUNZIONI -------- //

float letturaPosizione(){ 
  static const int pinPos=0; // pin sensore posione 
  static const float bitpcm = 0.19531; //195.3125 ;
  static const float offset = 666;
  
  float posizione =(float(analogRead(pinPos))-offset)*bitpcm;
  return posizione;
  }
  



float vel_media(float pos){
  const static int n = 10;
  static float posizioni[n];
  static int p=0;
  float velocita_media;

  if (p==0) {
    for (int i = 0; i < n; i++) (posizioni[i]=pos);
  }
  p=(p%n);
  velocita_media = ((pos-posizioni[p])/float(n*Tsam))*1000;
  posizioni[p]=pos;
  p++;
  return velocita_media;

  
}

float vel_angolare(float gradi){
  const static int n = 3;
  static float angoli[n];
  static int k=0;
  float velocita_media;
  float angolo;
  if (gradi>=0) angolo = gradi-180;
  if (gradi<0) angolo =  gradi+180;

  if (k==0) {
    for (int k = 0; k < n; k++) (angoli[k]=angolo);
  }
  k=(k%n);
  velocita_media = ((angolo-angoli[k])/float(n*Tsam))*1000;
  angoli[k]=angolo;
  k++;
  return velocita_media;

  
}


void  controlloPWM (float pwm_float){
  static const int pwmPin1 = 9; // pin del PWM
  static const int pwmPin2 = 10;
  static const int sat=255;
  int pwm;
  
  pwm =int(pwm_float);
    if (pwm>sat) pwm= sat;
    if (pwm<-sat) pwm=-sat;
    
   if (pwm >= 0) {
    analogWrite(pwmPin2,0);
    analogWrite(pwmPin1,pwm);    
    } 
  
  if (pwm < 0 ) {
   analogWrite(pwmPin1,0);
   analogWrite(pwmPin2,-pwm);
  }
    
                     

}




float controlloPI(float y, float y0){
  
  float err;
  static float err_t=0;
  float P;

  static  float I=0;
  float u_att;
  static float u_old=0;
  const float kp=2.8; 
  //const static float Ts=0.010;
  const static float Ti=100;   // 0.15;
  const static float Tt=2.8;
  float u;
   
  err = y0-y;
  
  P= kp*err;

  I= I+((kp*Ts)/Ti)*(err-err_t);
   I=0;
  u = P+I;
  

//antiwindup
 if (u > 255) u_att = 255;
 if (u< -255) u_att = -255;
 
 err_t = u-u_att;
 err_t = err_t/Tt;

 return u;
}



float controllo_PD (float y,float y0){
  float err;
  const static float Kp=90; //90
  const static float Td = 0.025 ;//0.025
  //const static float Ts = 0.010;
  const static float N = 15; 
  float P;
  float D;
  float D1;
  float D2;
  static float D_old=0;
  static float y_old;
  float u;
  
  err=y0-y;
  P = Kp*err;
  
  D1 = Td/(Td+(N*Ts))*D_old ;
  D2 = ((Kp*N*Td)/(Td+(N*Ts)))*(y-y_old);
  D=D1+D2;
  D_old = D;
  y_old = y;

  u = P-D;
  return -u;
 }


 
 
float lettura_angolo (){

float ang;
float settori;
settori=encoder_counter%2000;
ang= float(settori)*360/2000;

if (settori>=0) ang = 180-ang;
if (settori<0) ang = -180-ang;
return ang;
}



float gradi_desiderati(float pos) {
float gradi;
gradi = pos*0.02;
  //gradi= abs(pos)*pos*0.004;
 return gradi;
} 





float swing2(float velocita_angolare, float gradi)  {
  int quadrante;
  float u;
  const static float ku =70;
  
  velocita_angolare=-velocita_angolare;
  if (gradi<90 && gradi>=0) quadrante =1;
  if (gradi<0 && gradi >=-90) quadrante =2;
  if (gradi <-90 &&gradi>=-180) quadrante =3;
  if (gradi<180 && gradi>=90) quadrante = 4;  


  u=0;
  if (velocita_angolare >0 && quadrante == 3) u=ku;
  if (velocita_angolare >0 && quadrante == 1) u=ku*1.5;
  if (velocita_angolare <0 && quadrante == 4) u=-ku;
  if (velocita_angolare <0 && quadrante == 2) u=-ku*1.5;
  
  
  return u;


}      
void  avvio() {
  delay(5000);
  controlloPWM(-100);
  delay(500);
  controlloPWM(0);
  delay(500);  

}
  
  
  




//---- ENCODER FUNCTIONS

void doEncoderA(){ 
  if (digitalRead(encoderPinA) == HIGH) { // low-to-high on channel A
    // check channel B to see which way encoder is turning
    if (digitalRead(encoderPinB) == LOW) { encoder_counter++; } else { encoder_counter--; } }
  else {  // high-to-low on channel A
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoderPinB) == HIGH) { encoder_counter++; } else { encoder_counter--; } }
} 

void doEncoderB(){ 
  if (digitalRead(encoderPinB) == HIGH) { // low-to-high on channel B
    // check channel A to see which way encoder is turning
    if (digitalRead(encoderPinA) == HIGH) { encoder_counter++; } else {encoder_counter--; } }
  else {  // high-to-low on channel B
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoderPinA) == LOW) { encoder_counter++; } else {  encoder_counter--; } }
}

//------

Tommaso: Mi servono 2 canali perchè sarebbe un 500 Settori, con 2 led quindi riesco a moltiplicare per 4 la risoluzione! entrambe le funzioni si attivano con CHANGE

Percui Tu vuoi triggerare sia sul fianco salente che su quello discendente di tutte due le fasi?

Tommaso: Che ha problemi per alte velocità l'ho trovato in modo sperimentale. ho preso il programma completo e con una Serial .print stampo a video solamente il valore encoder_counter, se a mano muovo il pendolo lentamente non c'è problema,se inizio a fare movimenti rapidi, quando il pendolo torna in posizione verticale di riposo non si è azzerato il valore encoder counter e neppure è un multiplo di 2000! se invece tolgo tutte le altre funzioni, e lascio comunque la stampa a video dei settori, questo problema non c'è

La seriale a 9600 Baud blocca il programma per ogni carattere spedito ca 1mSecondo. Sappi questo che è un causa di esecuzione lenta di programmi. Alza la baudrate a 115200.

Ciao Uwe

Ciao, scordati Serial.print e tutte le altre, salva i valori di test in array, disabilita le isr e spara i dati in seriale. La seriale usa gli interrupt, di default quando entra in una isr tutti gli altri eventi sono disabilitati, cioè quando entra nella isr, viene eseguito un cli() clear Interrupt, quindi gli interruput 0 e 1 vengono disabilitati.

poi se vuoi velocizzare le isr, diciamo le funzioni doEncodrA e B, prova ad usare queste:

void doEncoderA()
{
    if ((PIND & _BV(2)) ^ (PIND & _BV(3)))
    {
        encoder_counter++;
    }
    else
    {
        encoder_counter--;
    } 
}

void doEncoderB()
{
    if ((PIND & _BV(2)) ^ (PIND & _BV(3)))
    {
        encoder_counter--;
    }
    else
    {
        encoder_counter++;
    }
}

mmmm.. se ti conta al contrario attacca le funzioni al contrario.

Ciao.