Newsgroup: it.hobby.elettronica.digitale
Titolo: pid per controllo velocità

oltre all'application not 696 della microchip, c'è qualche esempio per un
controllo pid per la velocità di rotazione di un asse?
Quello che non mi è chiaro della application note è che loro
decodificano con un paio di flip-flop i segnali dell'encoder
e ottengono il numero di impulsi in entrambi i versi di rotazione. Lio
sommano e ottengono la velocità di rotazione. Ma gli impulsi dell'encoder
danno la posizione, per avere la velocità non si dovrebbe dividere per un
tempo? E quindi non capisco come funzioni.....

Sommano gli impulsi in un tempo determinato e poi dividerebbero
Uso il condizionale perché poi alla fine non serve realmente sapere la
velocità al PID basta solo avere il SetPoint (o rferimento) omogeneo in
dimensione con la lettura dell'encoder ovvero se voglio farlo andare a 20rpm
avrò calcolato a mano su un foglio di carta che col periodo di campionamento
che uso corrispondono a 130step e allora metto il setPoint a 130


l'avevo pensato, solo che non trovo traccia nel sorgente di un timer che
fa scattare la toutine ad un tempo bendefinito.
Comunque io ho ancora qualche dubbio che mi primane: il mio pid accetta
come velocità impostata a mano una velocità che varia tra -2^15+1 e
2^15-1, il motivo per cui escludo un valore della variaible a 16 bit è
lungo da spiegare. Quindi in un qualche modo devo normalizzare anche la
velocità che leggo dall'encoder, no?
sai pr caso come posso misurare il tempo che passa tra una chiamata e
l'altra ad un gestore di interrupt dentro ad un pic?

 l'altra ad un gestore di interrupt dentro ad un pic?
Il timer è prorpio l'interrupt che viene generato da un timer (in genere
timer0) In pratica hai una periferica hardware che è proprio un timer Lo
imposti e lui ogni "scadenza" ti genera un interrupt e nella isr gestisci
l'evento
Se vuoi posta il codice che ci do uno sguardo

 Se vuoi posta il codice che ci do uno sguardo
è proprio qui il problema: ce l'avessi...
Mi spiego: il ragazzo che ha fatto la scheda è molto bravo in
elettronica, ma a livello di programmazione è più modesto. Siccome io
programmo di più in C mi sono messo da poco a cercare di imbastire
qualcosa....
Per quello speravo di trovare qualcosa da cui prendere almeno ispirazione.
Comunque nel frattempo ho parlato al telefono con questo mio amico: mi ha
detto che c'è un timer a 16 bit che viene incrementato con una frequenza
di 40 MHz e che ad ogni overflow genera un interrupt che lancia la routine
di lettura dell'encoder. Domani mattina ci troviamo a fare del
brainstroming, casomai domani lo posto, così ci dai un'occhiata, se vuoi.

 qualcosa....
Se le cose stanno così allora c'è molto lavoro da fare
Come consiglio scaricati il compilatore c della Hitech in versione lite
(
www.htsoft.com
) e prendi un pic 16F877 che è supportato
Hai un ambiente c oldStyle ma va più che bene
Scaricati il datasheet del pic in questione e leggilo bene (la parte di
timer0 sopratutto e delle interruzioni di portb)
Poi se devi pilotare un motore cc leggi pure bene la parte riguardo il pwm
Se poi ti serve qualche esempio ho qualcosa nel cassetto
Ps Ma che devi realizzare? Che esame è?

 Ps Ma che devi realizzare? Che esame è?
Non è per un esame, ma per un progetto di robotica che staimo portando
avanti per interese personale. L'idea iniziale era di dividersi il lavoro
in tre:
1) motori / scheda di potenza / scheda di controllo
2) software di gestione del robot
3) software di motion planing e interpretazione dei dati da telecamera.
Io mi sono fatto la parte 2, la 3 procede mentre la 1 va a rilento, per
cui gli sto dando una mano. Mi sono scaricato l'mplab e il compilatore
mcc18 in versione demo dalla microchip e mi sono scritto questo sorgente
(vedi fine).
Abbiamo però un po' di problemi realizzativi del controllo: il pic
(18f452) è clockato a 40MHz (e non possiamo clockarlo più lentamente per
una serie di motivi), il motore da controllare fa 2750 giri al minuto e
l'encoder ha 512 tacche. Noi volevamo misurare la velocità ad ogni
olverflow di TIMER3, cioè ogni 2^16/40 = 1638 us.
Ma 45,83 * 512 : 1000000 = x : 1638 , quindi ad ogni overflow leggiamo al
massimo 38 tacche, per cui la granularità della misura della velocità ci
piace molto poco (le tacche vengono messe in TIMER0 e TIMER1 come
previsto dall'application note 696 decodificando l'encoder con un
flip-flop). Allora abbiamo pensato di estendere via software TIMER3,
incrementando ad ogni overflow una variabile interna e facendo la misura
della velocità solo una volta ogni 8 o 16 e resettando la variabile
interna. Riduciamo la frequenza di campionamento, però abbiamo un errore
meno grossolano.
Poi ci è venuto un dubbio atroce, riguardo l'esecuzione dell'algoritmo
pid. Questo mio compagnio ha avuto l'idea di mettere nel main del pic un
while(1) che chiama il calcolo del pid e impostazione del dutycycle.
Secondo me questo è sbagliato perché in questo modo il pid viene
ricalcolato un sacco di volte sempre con gli stessi dati. Non dovrebbe
essere calcoltato solo una volta per ogni modifica delle variabili di
stato? cioé ogni volta che leggiamo una nuova vellcità? Anche perché mi
sembra che se lo richiamiamo più volte otteniamo un risultato simile a
metere delle costanti più grosse....
Comunque giusto per allungare questo già lungo messaggio ti incollo il
codice (un po' tagliato togliendo le funzioni come quella per la seriale
che non centrano nulla), ma prima ti metto qualche nota. La scheda che
monta il pic, in verità ne monta due, che sono tra di loro connesse con
spi. Il primo pic fa il controllo di un asse (ed è quello su cui ci
stiamo scervellando) e gestisce la seriale cui è collegato il pc del
robot, il secondo legge la copia dei dati della seriale che gli arriva dal
primo mediante lo spi e gestisce due motori (ecco perché ci serve il
clock di 40MHz, come fatto notare dall'a.p. 696). La comunicazione tra pc
e schede va con un protocollo che viene gestito dalla funzione DoComm.
Attualmente lo spi non viene ancora usato, ma credo che sia banale,
perché all'inizio di DoComm metto un comando che copi il dato sullo spi.
Ah, il protocollo prevde che la velocità di rotazione sia normalizzata
tra -2^15+1 e 25^15-1 .
Che ne pensi? è corretto chiamare il pid solo una volta per ogni nuova
velocità?
#include <p18f452.h>    /* for the special function register declarations */
#include <portb.h>              /* for the RB0/INT0 interrupt */
#include <stdlib.h>
#include <delays.h>
#include <string.h>
#include <spi.h>
#include <pwm.h>                /* PWM library functions */
#include <adc.h>                /* ADC library functions */
#include <timers.h>             /* Timer library functions */
#include <usart.h>              /* Register definitions */^M
#include <i2c.h>
....
 
/*
 * Variabile  per  rallentare  la  lettura  degli  impulsi  dell'encoder *
 siccome stiamo operando troppo velocemente TIMER3 rischi di andare in *
 overflow troppo frequentemente e di avere poche tacche ri risoluzione *
 per la velocità letta. In questo  modo non eseguiamo la lettura tutte *
 le volte che TIMER3 in in overflow, ma solo una ogni ESTENSIONETIMER. */
#define ESTENSIONETIMER 8
unsigned char extraTimer;
 
/*
 * Calcoliamo  il tempo  di  overflow che  condiziona  la lettura  della *
 velocità degli  assi e il  numero di tacche  che vengono lette  in un *
 intervallo di tempo quando il motore gira alla massima velocità. */
#define MHZ_FREQUENZA   40
#define DELTA_T_MICRO_S 2^16/MHZ_FREQUENZA #define TACCHE_ENC      512
#define GIRIMASSIMI     2750
#define TACCHE_MAX      ((int) GIRIMASSIMI / 60 * TACCHE_ENC *
DELTA_T_MICRO_S / 1000000 * ESTENSIONETIMER)
unsigned char azione = NIENTE;
unsigned char blocco;
unsigned char calcolaPID = 0;
 
int velocitaPrimoAsse = 0;
int velocitaSecondoAsse = 0;
int velocitaTerzoAsse = 0;
 
int velocitaCopia;
 
int velocitaLettaPrimoAsse;
 
int Ki, Kp, Kd;                 /* costanti per il pid */
 
long termineP = 0;              /* termine proporzionale */
long termineI = 0;              /* termine integrale */
long termineD = 0;              /* termine derivativo */
long errore;
long DutyCycle;^M
long DC;
 
int risultato;                  /* il dato letto dall' usart */
 
/*
 * un  po'  di dichiarazione  delle  funzioni,  giusto per  non  doverle
 * spostare nel sorgente...
 */
void DoComm (int data);
/**
 * Parser dell'input dalla linea seriale dell'usart.
 */
void ^M
DoComm (int data)
{
.....
}
/*
 * For high interrupts, control is transferred to address 0x8.
 */
void CalcolaVelocita (void);            /* prototype needed for 'goto' below */
void Usart (void);                      /* prototype needed for 'goto' below */
#pragma code HIGH_INTERRUPT_VECTOR = 0x8
void
high_ISR (void)
{
  if (PIR2bits.TMR3IF == 1)
    {
      _asm goto CalcolaVelocita _endasm
    }
  else if (PIR1bits.RCIF == 1)
    {
      _asm goto Usart _endasm
    }
}
#pragma code                    /* allow the linker to locate the remaining code */
#pragma interrupt CalcolaVelocita
void
CalcolaVelocita (void)
{
  if (extraTimer < ESTENSIONETIMER - 1)
    {
      extraTimer++;
    }
  else
    {
      /* wmax = 2^15 -1 ;
       *
       * wmax : TACCHE_MAX = w : TACCHELETTE
       *
       * w = wmax * TACCHELETTE / TACCHE_MAX
       *
       */
      int tacche_lette;
      long long tmp;
 
      tacche_lette = TMR1 - TMR0;
 
      TMR0 = 0;
      TMR1 = 0;
 
      tmp = (2^15 - 1) * tacche_lette / TACCHE_MAX;
 
      if (tmp >= 2^15 - 1)
        /* non dovrebbe succedere, però se si va in saturazione... */
        velocitaLettaPrimoAsse = 2^15 - 1;
      else if (tmp <= - 2^15 + 1)
        /* anche qui correzzione di una saturazione che non dovrebbe esistere */
        velocitaLettaPrimoAsse = - 2^15 + 1;
      else
        velocitaLettaPrimoAsse = (int) tmp;
 
      extraTimer = 0;
      calcolaPID = 1;
    }
 
  PIR2bits.TMR3IF = 0;
}
/* USART interrupt */
 
#pragma code                    /* allow the linker to locate the remaining code */
#pragma interrupt Usart
void
Usart (void)
{
  risultato = ReadUSART ();
  DoComm (risultato);
  PIR1bits.RCIF == 0;
}
 
void
EnableHighInterrupts (void)
{
  RCONbits.IPEN = 1;            /* enable interrupt priority levels */
  INTCONbits.GIEH = 1;          /* enable all high priority interrupts */
}
/**
 * Imposta il duty cycle del pwm del primo asse.
 */
void
ImpostaDutyCyclePWM1 (unsigned int dutycycle)
{
  union PWMDC DCycle;
 
  /* Save the dutycycle value in the union */
  DCycle.lpwm = dutycycle << 6;
 
  /* Write the high byte into CCPR1L */
  CCPR1L = DCycle.bpwm[1];
 
  /* Write the low byte into CCP1CON5:4 */
  CCP1CON = (CCP1CON & 0xCF) | ((DCycle.bpwm[0] >> 2) & 0x30);
}
/**
 * Calcolo del pid.
 */
void
PIDprimoAsse (long velocitaDesiderataPrimoAsse)
{
  errore = velocitaDesiderataPrimoAsse - velocitaLettaPrimoAsse;
  termineP = Kp * errore;       /* Termine proporzionale */
 
  DutyCycle = velocitaDesiderataPrimoAsse + termineP;
  /* controlliamo la saturazione del DutyCicle. */
  if (DutyCycle > 2^15 - 1)
    DutyCycle = 2^15 - 1;
  else if (DutyCycle < - 2^15 + 1)
    DutyCycle = - 2^15 + 1;
 
  DC = DutyCycle / 128;
 
  if (DC > 0)
    {
      PORTE |= 0x04;
      ImpostaDutyCyclePWM1 (DC);
    }
  else
    {
      DC = DC * (-1);
      PORTE &= 0xFB;
      ImpostaDutyCyclePWM1 (DC);
    }
}
/**
 * Funzione di inizializzazione del pic.
 */
void
Init_pic (void)
{
  /* Input Output port Setting */
  TRISA = 0xFF;
  TRISB = 0x0F;
  TRISD = 0x00;
  TRISE = 0x00;
 
  /* Init port */
  PORTE = 0;                    // Clear PORTE
  PORTC = 0;                    // Clear PORTE
 
  /* Timer2 Setting */
  PR2 = 0xFF;
  TRISC = 0x91;
  TMR2 = 0x04;
  T2CON = 0x04;
 
  /* Timer3 Setting */
  T3CON = 0x81;
 
  /* USART */
  SPBRG = 0x40;                 /* 9600bps@40Mhz */
  RCSTAbits.SPEN = 1;
  TXSTA = 0x24;
  //TXSTAbits.TRMT= Transmit register empty/full 1/0
  RCSTA = 0x94;
 
  // PIE1bits
  /* PWM Setting */
  PIE1bits.RCIE = 1;
  CCP1CON = 0x0F;
  //TXREG   data buffer
  /* ADC Setting */
  ADCON0 = 0x81;                //For more info, see related .txt
  ADCON1 = 0xCE;
 
  /* SPI data bus */
  OpenSPI(SPI_FOSC_16, MODE_00, SMPEND);
  SSPSTAT = 0x00;
  SSPCON1 = 0x00;
  SSPCON1|=0x0;
}
void
main (void)
{
  extraTimer = 0;
  EnableHighInterrupts ();
  Init_pic ();
  PORTD = 4;
 
  Kp = 10;
  Ki = 1;
  Kd = 10;
 
  TMR0 = 0;
  TMR1 = 0;
  TMR3 = 0;
 
  while (1)
    {
      while (blocco == VELOCITA1NONVALIDA);
      velocitaCopia = velocitaPrimoAsse;        /* copiamo per non avere una race condition */
      if (calcolaPID)
        {
          PIDprimoAsse (velocitaCopia);
          calcolaPID = 0;
        }
    }
}

 }
Ritiro lo schiaffo di prima all'amico
Dal punto di vista logico lo schiaffo è meritato ma qui non state sbagliando
Continuamente testate il valore calcolaPID e lo calcolate solo se è a 1
ovvero solo se lo avete messo a 1 dopo la lettura della velocità e quindi a
finale calcolate il PID solo se avete nuovi dati
PS Di dove siete? Al limite ci si vede!

 velocità?
Come già detto hai ragione tu

 schiaffi all'amico! ;-)
eh!... già faccio la parte di quello cattivo nel gruppetto, se in più mi
metto pure a menare... :-)

 PS Di dove siete? Al limite ci si vede!
Io di Vicenza, stuio a Padova e gli altri ragazzi sono sparsi un po' per
il veneto.

 il veneto.
Beh, io sto più giù, Campania

 Beh, io sto più giù, Campania
la vedo dura allora... :-)

 passo 3: desiderata 10, misurata 5, errore 5 pwm 5
Il pwm è l'ingresso che dai al sistema ed è normale che se la velocità del
motore è proprio quella che tu vuoi non dei dare ulteriore potenza al motore
mentre se sei sotto allora avrai un valore diverso da 0 anche se di poco
Pensa ad esempio che una qualceh coppia esterna agisca sul motore (il tuo
amico cui hai dato gli schiaffi che ti dimostra che il pid è sbagliato) e lo
mantenga giusto giusto alla velocità che tu hai settato
Allora il tuo pid invece di dare 0 potenza al motore che gira già bene per
fatti suoi gli da una extra potenza che lo porterà a girare più forte del
dovuto
Convinciti del fatto che il termine additivo non ci vuole oppure fai un atto
di fede e fammi contento e non ce lo mettere

 dovuto
ma se io gli dò potenza 0, perché il motore gira? Solo per l'inerzia?
PS: se vuoi vedere la scheda guarda qua, anche sono file un po'
grandicelli e il server è un po' lento...
http://www.campana.vi.it/ottavio/robocup/3.jpg
http://www.campana.vi.it/ottavio/robocup/4.jpg

 ma se io gli dò potenza 0, perché il motore gira? Solo per l'inerzia?
Mi cadono le braccia Anni e anni di studi fatti da persone diverse e tu dici
come fa a funzionare?
Ma che vi hanno fatto studiare a controlli?
Qualche simulazione fetente in Matlab l'avete fatta?
Guarda ceh sta cosa del motore si studia in un sacco di esami
TeoriaDeiSistemi, ControlliAutomatici 1 e 2, ControlloDeiProcessi e un altro
pacco di esami e tu mi dici che devi meterci un termine additivo a piacere
tuo?
Ma dove siamo andati a finire!
Convinciti di sta cosa Molto probabilmente il controllo funzionerà lo stesso
perché il PID vede un sistema con una non linearità ma dovrebbe cavarsela lo
stesso

 stesso
mi sto convincendo. Anche perché sembra che con quel termine vada in
saturazione tutto e gira a massima velocità.... stasera il mio compagno
prova il nuvo firmware e speriamo che la cosa funzioni.
Le simulazioni col simulink ce le hanno fatte vedere, ma sai, vedere
qualcuno che smanetta e fare la cose da soli sono due cose distinte. Nel
primo caso dormicchi, nel secondo devi pensare..... :-)

Ma non vi hanno fatto fare qualche esercitazione da soli?
Avete portato qualche elaborato all'esame?
Come ve li fanno fare sti esami, teoria infinita?
Comunque se volete vi mando l'algoritmo che uso io per il PID con
desaturazione dell'azione integrale


intanto ho una bella notizia: mi sono convinto che l'ingresso del pid
è solo l'errore, mi ero incasinato perché volevo fare le cose semplici e
senza tenere conto dell'integratore, per cui non mi andava mai a ragime....
Mi sono messo con calma, ho fatto delle prove col simulink e le cose
funzionano come dovrebbero.
Purtroppo durante il corso non c'è stata nessuna attività di
laboratorio, le simulazioni le proiettava una volta a settimana il prof,
quindi fino a quando non ci sbatti la testa non impari poi molto....
Sono contento, almeno adesso ho capito :-)
Giusto mentre ero in piscina stavo pensando al problema dell'integratore
che rischi di andare costantemente in overflow... Pensavo di usare
qualcosa tipo un filtro a media mobile... Tu come fai?
Se sei così gentile da mandarmi il tuo algoritmo il mio indirizzo è
ottavio @ 
campana.vi.it
 (sperando di non beccarmi un mare di spam...)
grazie

 che rischi di andare costantemente in overflow...
Io in piscina penso alle donne che mi corcondano succinte ai problemi
preferisco pensare quando sto a casa nella stanza di ceramica (si, quella
stanza con quella sedia bianca col grosso foro al centro)


purtroppo sono miope, in piscina o lavoro di fantasia o rinuncio a nuotare...
Appropositò di retroazione, lo sai che lo sciacquone del bagno è stato
inventato verso il 600 avanticristo da un greco di nome Ktesibio? E' stato
il primo esempio che ci hanno spiegato al corso di controlli, come esempio
di retroazione.... :-)
Lo vedi che non ho rubato l'esame? :-)

 di retroazione.... :-)
Ah, ho capito! Ti alzi, tiri lo scarico e te ne vai L'azione ovviamente
rimane dietro da cui retroazione
Chissà però quando è negativa che succede Esce troppa acqua con troppa foga
e ti inonda di liquidi non ben definiti tutta la stanza Quella si che è una
situazione negativa

 PS Di dove siete? Al limite ci si vede!
noi studiamo a Padova, ma io sono di vicenza, mentre il ragazzo che ha
fatto l'elettronica è di treviso. Tu?