lunedì 21 febbraio 2011

termostato PIC-based con isteresi

premessa: questo articolo è presente anche su grix.it.
Quello che vi mostro oggi è un semplice termostato/attuatore a isteresi basato su PIC 16F690 (questo avevo e questo ho usato) e il solito LM35. Il circuito è semplice, quindi l'attenzione vorrei porla sulla spiegazione del codice da caricare sul PIC, che sarà scritto in mikroC e abbondantemente commentato per permettere anche a chi non ne sa niente di capire di cosa si sta parlando.





Descrizione
Ho utilizzato un 16F690 perché, tra i PIC che avevo a disposizione, è l'unico con un ingresso analogico. In questo modo nonho dovuto utilizzare altri componenti per leggere la temperatura rilevata dal sensore. Il circuito risultante è pulito e semplice, in grado di resistere nel tempo senza dover subire manutenzione di nessun tipo (verrà posizionato in un posto poco accessibile).
Che vuol dire isteresi? Con l'isteresi si definisce una "finestra" di funzionamento, in cui un attuatore viene azionato senza il pericolo di avere instabilità attorno ad un valore. Mi spiego meglio: diciamo che vogliamo far accendere una lampadina se con un sensore si misurano 25 gradi. Che succede se la temperatura comincia ad oscillare tra 25,001 e 24,999 gradi? succede che la lampadina comincia a "sfrigolare" (termine altamente tecnico/elettronico che rende bene l'idea). La soluzione è creare un sistema che, superata una certa soglia S1 non si spegne fino al raggiungimento di una soglia S2, dove S2<S1. Tornando all'esempio precedente, la lampada non si spegne fino, ad esempio, al raggiungimento dei 23 gradi, cioè una soglia minore di 25.
Come funziona un LM35? Questo sensore è, secondo me, il più facile in assoluto da interfacciare con un pic (ma anche con un Arduino, in realtà). E' il solo alimentato tra 0 e +5 V e può misurare solo temperature positive. Sul datasheet, comunque, sono presenti vari metodi per ottenere anche la misurazione di temperature negative. Nel mio caso questo non era necessario, e ho utilizzato il sensore semplicemente interfacciandolo al pic, come visto in molti altri tutorial in rete.
L'idea di base del progetto che volevo realizzare è semplice: il sensore misura la temperatura ad intervalli regolari. Quando si preme un pulsante apposito, la temperatura corrente viene memorizzata. Il sistema cerca poi di mantenersi attorno al valore rilevato in precedenza, accendendo una pompa di ricircolo per il raffreddamento se si dovessero rilevare temperature superiori a quella memorizzata e spegnendola in caso di temperature inferiore.

Circuito
Come dicevo il circuito è molto semplice, ci sono dei 3 led che indicano lo stato del sistema: un led lampeggia quando si è impostata la temperatura di riferimento, altri due led indicano se l'attuatore è acceso o spento (come fosse una configurazione binaria: o è acceso un led oppure l'altro).
Con un pulsante (in pullup) è possibile impostare la temperatura che dovrà poi essere mantenuta.
Un comune relais a 6V è comandato da un transistor "general purpose", l'alimentazione (5V) è fornita da un alimentatore esterno, quindi nessun regolatore di tensione è presente a bordo del circuito.
In più, ho previsto un connettore per poter caricare il firmware all'interno del pic senza doverlo smontare (ICSP).


 
Codice
Qui di seguito il codice da utilizzare, diviso in base alla funzione svolta e commentato. Le porte del PIC sono impostate come sullo schema precedente.
La funzione di init del PIC ha solamente il compito di inizializzare i registri interni e di abilitare gli interrupt (servirà poi per capire quando è stato premuto il pulsante per impostare la temperatura da mantenere).
 void init()  {
       //abilita interrupt sulla porta RA4 + globali
       INTCON.GIE=1;
       INTCON.INTE=1;

       ANSEL=0b00000010;              // configura RA1 come ingresso analogico
       ANSELH=0;
       ADCON0=1;                         //abilita ADC
       CM1CON0=0;                        //disabilita tutti i comparatori
       CM2CON0=0;

       //PORTA input il resto output
       TRISA=0xFF;
       PORTA=0;

       TRISB=0;
       PORTB=0;

       TRISC=0;
       PORTC=0;
     }
Per poter accendere l'utilizzatore (che in questo caso, come già detto è una pompa di ricircolo) utilizzo la seguente funzione:
void accendiPompa(){
     //ACCENDI LA POMPA
     PORTC.F6=1;

     //STATO POMPA ACCESA
     PORTC.F4=0;
     PORTC.F5=1;
    
     while(adc_rd>temperaturaImpostata-1) {
           delay_ms(2000);
           adc_rd  = ADC_read(1);
           adc_rd *= 5;
           adc_rd /= 10;

           if (flag)
              return;
     }
    
     //la temperatura si è abbassata
     //spegni la pompa e esci
     PORTC.F6=0;
    
     //STATO POMPA SPENTA
     PORTC.F4=1;
     PORTC.F5=0;
    
     return;
}
lmportante, qui e nel seguito, è notare la presenza della variabile "flag" che serve per poter entrare nella procedura di impostazione della temperatura a seguito della pressione del pulsante collegato al PIC.
Il tutto è richiamato da una procedura main:
void main() {
       short int i;
       int valore;

       //lettura della temperatura salvata in memoria (con protezione)
       ee_read(0,&temperaturaImpostata,sizeof(int));
       temperaturaImpostata=min(temperaturaImpostata,MAX_TEMP);

       flag=0; //interrupt non ancora avvenuto
       while(1)   {
                     //stato: POMPA SPENTA
                     PORTC.F4=1;
                     PORTC.F5=0;
                     PORTC.F6=0;
                    
                     //leggi il valore dal sensore
                     adc_rd  = ADC_read(1);
                     adc_rd *= 5;
                     adc_rd /= 10;
                    
                     //decidi se accendere o no la pompa
                     if(adc_rd>=temperaturaImpostata+1){
                        contatore=0;
                        accendiPompa();
                     }

                     //se c'è stato un interrupt allora salva il nuovo valore
                     if (flag){
                                 flag=0;

                                 //valori MIN e MAX di sicurezza
                                 valore=min(adc_rd,MAX_TEMP);
                                 valore=max(adc_rd,MIN_TEMP);

                                 //salva valore in memoria
                                 ee_write(0,&valore,sizeof(int));

                                 //e nella variabile (evita accessi in EEPROM)
                                 temperaturaImpostata=valore;

                                 //lampeggia
                                 for(i=0;i<4;i++){
                                    PORTB.F6=1;
                                    delay_ms(125);
                                    PORTB.F6=0;
                                    delay_ms(125);
                                  }
                     }
          //letture ogni mezzo secondo CIRCA
          delay_ms(500);
          }
}
Sulle prime istruzioni per il salvataggio e il ripristino della temperatura tornerò tra poco. Intanto vorrei commentare quello che succede "a regime". La temperatura viene rilevata ogni mezzo secondo circa. La precisione non mi interessava molto, infatti uso l'oscillatore interno del PIC invece del quarzo esterno, sempre per avere un circuito il più semplice e meno soggetto ad usura. Bene, è arrivato il momento di discutere un problema che mi si è posto durante la progettazione: Il funzionamento del circuito, come ho detto, è semplice: si preme un pulsante e il sistema cerca di mantenere la temperatura attorno a quella rilevata in quel momento. Che succede quando si spegne il circuito? la temperatura memorizzata viene persa. Per questo motivo ho introdotto le seguenti due routine:

void ee_write(unsigned int ee_addr, int *p, int size){
     while (size-- != 0) {
         EEPROM_Write(ee_addr++, *p++);
         Delay_ms(20);                   /* 20ms is microC recommendation */
     }
 }

 void ee_read(unsigned int ee_addr, int *p, int size){
     while (size-- != 0) {
         *p++ = EEPROM_Read(ee_addr++);
     }
 }
Queste hanno il compito di "spezzare" un int in byte e memorizzarlo in locazioni di memoria sequenziali (l'indirizzamento qui è sempre al byte). Ovviamente lo stesso vale anche per ripristinare il valore dalla memoria. Utilizzo una variabile globale per non dover accedere ogni volta in memoria (visto che comunque nei pic questo tipo di letture si possono fare solo un numero finito di volte, molto elevato ma sempre finito).
Chiudo spendendo due parole sulla routine di gestione dell'interrupt: questa viene richiamata in automatico da mikroc quando si genera un interrupt esterno, e non fa altro che settare una variabile globale che poi viene "catturata" durante l'esecuzione del programma (vedere funzione main).
void interrupt(void){
    flag=1;
    delay_ms(1000);        //evita problemi con la pressione del pulsante
    INTCON.INTF=0;
}
Realizzazione
Ecco il circuito stampato finito e assemblato (con la famosa tecnica dello stira e ammira). La pinzetta che si vede la stavo utilizzando per dialogare con il pic tramite seriale (per debug).

Nessun commento:

Posta un commento