Programació en C del PIC 16F690

Referència Trucs Perifèrics   Recursos CITCEA
Tutorial Exemples Projectes   Inici

Exemple CS - Comunicació síncrona bidireccional

En aquest exemple es fan servir dues unitats de la placa de la matriu de vuit per vuit LED. El que es desitja és establir una comunicació bidireccional entre les dues plaques, de manera que es puguin enviar informació de l'una a l'altra. A més, s'ha fet de manera que totes dues plaques tenen el mateix programa i quan una detecta que l'altra envia dades, ella es posa en mode de recepció. Per fer-ho senzill, s'ha establert que la transmissió sigui sempre de tres bytes.

Abans d'explicar què fa el programa convé comentar que aquest és un programa força complex però això no ens ha de preocupar. No és imprescindible, però, entendre completament el funcionament del programa per poder emprar el mateix mètide en un exemple propi. En el programa que hi ha més avall, s'han escrit en color groc aquelles instruccions que són específiques de l'exemple; això vol dir que tota la resta, llevat de les funcions, són elements necessaris per fer la transmissió. Primer comentarem què fa l'exemple (el que s'ha marcat en groc) i més endavant s'explicarà com funciona el sistema de transmissió.

Quan no hi ha cap transmissió en marxa, el programa mira si l'usuari ha premut algun polsador. En cas que ho hagi fet, escriu a la pantalla sis caràcters; per exemple, si s'ha premut el polsador 4 s'escriurà E: Ps4. A més, enviarà Ps4 a l'altra placa.

Quan el programa detecta que l'altra placa envia informació, es posa en mode de recepció i, a l'acabar, escriu a la pantalla R: seguit dels tres caràcters rebuts.

Atès que durant la major part del temps el programa no fa res més que esperar que es premi un polsador, també s'ha posat un trosset de programa general que l'única cosa que fa és mantenir un LED intermitent.

Podem fer servir aquesta mateixa estructura de programa per a qualsevol aplicació que requereixi la comunicació entre dues plaques, tant si els dos programes són idèntics com si no. En aquells casos que només calgui comunicació en un sentit es podrà eliminar la part corresponent al sentit contrari (emissió en un programa i recepció en l'altre).

S'ha previst que la transmissió consti de tres bytes però no és imprescindible que tots tinguin sentit, sempre es pot posar un valor arbitrari en els bytes que no interessen i ignorar els valors rebuts. D'aquesta manera és possible transmetre un valor char (un byte) o int (dos bytes) sense més o bé enviar un o dos caràcters de control seguits d'un o dos valors, això permet cobrir la major part de les necessitats.

El programa per a aquest exemple és el següent:

#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF
#pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF
#include <xc.h>  // Carrega el fitxer de funcions necessari per al compilador XC8
#define _XTAL_FREQ  4000000  // La freqüència del rellotge és 4 MHz
#define T1H 255  // Precàrrega de TMR1 per a 0,25 ms
#define T1L 6  // Precàrrega de TMR1 per a 0,25 ms
#define setbit(var, bit) ((var) |= (1 << (bit)))
#define clrbit(var, bit) ((var) &= ~(1 << (bit)))
#define testbit(var, bit) ((var) & (1 << (bit)))
#define flipbit(var, bit) ((var) ^= (1 << (bit)))
// Definició de les funcions que farem servir 
void EnviaL(char Caracter);  // Envia un caràcter
void Esborra(void);  // Esborra la pantalla i posa el cursor a l'inici
char Polsador(void);  // Funció de lectura dels polsadors
unsigned char Control;  // Bits de control de la comunicació
unsigned char Enviar[3];  // Bytes a enviar (tres bytes, LSB primer)
unsigned char Rebut[3];  // Bytes rebuts (tres bytes, LSB primer)
unsigned char CompInt;  // Comptador d'interrupcions
unsigned char CompBits;  // Comptador de bits
unsigned char Portc;  // Control del port C
unsigned char Porta;  // Control del port A
char Polsad;  // Polsador que s'ha premut
void main (void) {
  __delay_ms(2000);  // Esperem que arrenqui la pantalla
  ANSEL = 0b00000101;  // Configura AN0 i AN2 com entrada analògica
  ANSELH = 0;  // Desactiva les altres entrades analògiques
  WPUB = 0;  // Port B sense resistències de pull-up
  IOCB = 0;  // No volem interrupcions per port B
  ADCON1 = 0b00010000;  // Posa el conversor a 1/8 de la freqüència
  clrbit(OPTION_REG, 7);  // Activa el control individual de pull-ups
  TRISA = 0xFF;  // Tot el port A és d'entrada
  TRISB = 0;  // Tot el port B és de sortida
  TRISC = 0xF0;  // Posa els bits de menys pes del port C com a sortida
  WPUA = 0b00100000;  // RA5 amb resistència de pull-up
  IOCA = 0b00100000;  // Volem interrupcions per RA5
  PIE1 = 0;  // De moment, no hi ha interrupció per timer 1
  BRGH = 1;  // Configuració de velocitat
  BRG16 = 0;  // Paràmetre de velocitat de 8 bits
  SPBRG = 25;  // Velocitat de 9600 baud
  SYNC = 0;  // Comunicació asíncrona
  TX9 = 0;  // Comunicació de 8 bits
  SPEN = 1;  // Activa comunicació sèrie
  TXEN = 1;  // Activa comunicació
  INTCON = 0b11001000;  // Permetem interrupcions pels ports A i B
                        // I les interrupcions per PIE amb control individual
                        // La resta d'interrupcions desactivades
  ADCON0 = 0b00001001;  // Activa el conversor A/D connectat a AN2
                        // amb el resultat justificat per l'esquerra
  Porta = 0b00100000;  // RA5 activat
  PORTA = 0b00100000;  // RA5 activat
  PORTB = 0;
  Portc = 0;  // LED apagats
  PORTC = 0;  // LED apagats
  Control = 0;  // Els bits de control a 0
  Esborra();  // Esborra la pantalla i posa el cursor a l'inici
  T1CON = 0b00000000;  // Configuració de Timer 1
                       // 0 - Factor d'escala d'1
                       // De moment, aturat
  TMR1IF = 0;  // Desactiva el bit d'interrupció per timer 1
  RABIF = 0;  // Desactivem el bit d'interrupció per port
  RABIE = 1;  // Volem interrupcions per port A
  while (1) {
    if(testbit(Control, 2)){  // Mira si ja tenim dades
      // Hem rebut dades
      Esborra();  // Esborra la pantalla i posa el cursor a l'inici
      EnviaL('R');  // Dades rebudes
      EnviaL(':');
      EnviaL(' ');
      EnviaL(Rebut[2]);  // Primer byte
      EnviaL(Rebut[1]);  // Segon byte
      EnviaL(Rebut[0]);  // Tercer byte
      Control = 0;  // Quedem a l'espera
      CompInt = 0;
      IOCA = 0b00100000;  // Volem interrupcions per RA5
      RABIE = 1;  // Activem interrupcions per port A
    }
    if(Control == 0){  // Si no hi ha cap transmissió activa ni dades pendents
                       // Podem mirar els polsadors
      Polsad = Polsador();  // Llegim els polsadors
      if(Polsad > 0){  // Si és 0, no s'ha premut
        // Si hi ha un polsador premut, primer esborrem la pantalla
        // ho escrivim a la pantalla, després ho enviem
        Esborra();  // Esborra la pantalla i posa el cursor a l'inici
        EnviaL('E');  // Dades enviades
        EnviaL(':');
        EnviaL(' ');
        EnviaL('P');
        EnviaL('s');
        EnviaL(Polsad + '0');
        Enviar[2] = 'P';  // Tercer byte
        Enviar[1] = 's';  // Segon byte
        Enviar[0] = Polsad + '0';  // Primer byte
        // Activem la transmissió
        Porta = 0;  // Desactivem RA4 i RA5
        TRISA = 0b11001111;  // RA4 i RA5 són sortides
        PORTA = Porta;  // I ho guarda al PORTA
        TMR1H = T1H;  // Inicialitza el Timer 1
        TMR1L = T1L;
        T1CON = 0b00000001;  // Configuració de Timer 1
                             // 0 - Factor d'escala d'1
                             // I el posem en marxa
        TMR1IF = 0;  // Desactiva el bit d'interrupció per timer 1
        CompInt = 0;  // Comptador d'interrupcions
        setbit(Control, 0);  // Entrem en mode emissor
        RABIE = 0;  // No volem interrupcions per port A
        TMR1IE = 1;  // Activa les interrupcions per timer 1
      }
    }
    __delay_ms(250);  // Retard de 0,25 s
    flipbit(Portc, 0);	// Invertim RC0
    PORTC = Portc;  // Ho posem als LED
  }
}
void __interrupt() inter(void){  // Gestió de les interrupcions
  if(RABIF && RABIE){  // Mira si la interrupció és per canvis als ports
    // És important que la comprovació de RA4 sigui abans que la de RA5
    // perquè quan s'iniciï la transmissió no hi ha dades,
    // cal esperar la següent interrupció.
    //
    // Recepció de dades
    if((!RA4) && (testbit(Control, 1))){  // Mira si s'ha desactivat RA4
                                          // i estem en mode receptor
      CompBits++;  // Arriba un bit
      if(CompBits <= 24){  // Si és <= 24 esperem bits
        Rebut[0] = Rebut[0] >> 1;  // Rodem els menys significatius
        if(testbit(Rebut[1], 0)){  // Mira si cal activar el de l'esquerra
          setbit(Rebut[0], 7);  // L'activa
        }
        Rebut[1] = Rebut[1] >> 1;
        if(testbit(Rebut[2], 0)){  // Mira si cal activar el de l'esquerra
          setbit(Rebut[1], 7);  // L'activa
        }
        Rebut[2] = Rebut[2] >> 1;  // Rodem els més significatius
        if(RA5){  // Mira si cal activar el de l'esquerra
          setbit(Rebut[2], 7);  // L'activa
        }
      }
      if(CompBits == 24){  // Si és 24 sortim del mode de recepció
        // Final de la comunicació
        clrbit(Control, 1);  // Fi del mode receptor
        setbit(Control, 2);	// Valors rebuts disponibles
      }
    }
    // És important que la comprovació de RA4 sigui abans que la de RA5 perquè
    // la interrupció per RA5 activa el mode recepció amb RA4 desactivat
    //
    // Detecció de que comença una transmissió de dades
    if((!RA5) && (Control == 0)){  // Mira si la interrupció ha estat per desactivació de RA5
                                   // i no estem enviant ni rebent
                                   // Ja hem llegit el port A, ja es podrà desactivar RABIF
                                   // Entrem en mode recepció
      IOCA = 0b00010000;  // Volem interrupcions per RA4
      setbit(Control, 1);  // Mode receptor
      Rebut[0] = 0;  // Inicialitzem variables de recepció
      Rebut[1] = 0;
      Rebut[2] = 0;
      CompBits = 0;  // Comptador de bits
    }
    RABIF = 0;  // Desactivem el bit (després d'haver llegit el port A)
  }
  //
  // Emissió de dades
  if(TMR1IE && TMR1IF && testbit(Control, 0)){  // Mira si la interrupció és 
                                                // per timer 1 i estem en mode emissor
    TMR1ON = 0;  // Atura momentàniament el Timer 1
    TMR1H = T1H;  // Inicialitza el Timer 1
    TMR1L = T1L;
    TMR1ON = 1;  // Torna a engegar el Timer1
    TMR1IF = 0;  // Desactiva el bit
    CompInt++;
    if(CompInt > 48){  // Si és 49 sortim del mode d'emissió
      // Alliberem la línia
      TRISA = 0xFF;  // Tot el port A és d'entrada
      WPUA = 0b00100000;  // RA5 amb resistència de pull-up
      TMR1IE = 0;  // Desactivem les interrupcions per timer 1
      TMR1ON = 0;  // Atura el Timer 1
      clrbit(Control, 0);  // Fi del mode emissor
      IOCA = 0b00100000;  // Volem interrupcions per RA5
      RABIE = 1;  // Admetem interrupcions del port A
    } else {
      // Cal permutar CK (RA4)
      if(testbit(Porta, 4)){  // Mirem l'estat de CK (RA4)
        clrbit(Porta, 4);  // Si està activat, desactivem
      } else {
        setbit(Porta, 4);  // Si està desactivat, activem
        // Toca enviar un bit
        if(testbit(Enviar[0], 0)){  // Mira si cal enviar un 1
          setbit(Porta, 5);  // És un 1
        } else {
          clrbit(Porta, 5);  // És un 0
        }
        Enviar[0] = Enviar[0] >> 1;  // Rodem els menys significatius
        if(testbit(Enviar[1], 0)){  // Mira si cal activar el de l'esquerra
          setbit(Enviar[0], 7);  // L'activa
        }
        Enviar[1] = Enviar[1] >> 1;
        if(testbit(Enviar[2], 0)){  // Mira si cal activar el de l'esquerra
          setbit(Enviar[1], 7);  // L'activa
        }
        Enviar[2] = Enviar[2] >> 1;  // Rodem els més significatius
      }
      PORTA = Porta;  // El copiem al port		  
    }
  }
}
char Polsador(void) {
  char Pols = 0;
  ADCON0bits.GO = 1;  // Posa en marxa el conversor
  while (ADCON0bits.GO == 1) // Mentre no acabi
    ;                        // ens esperem
  if (ADRESH < 220 && ADRESH > 200) {
    Pols = 1;  // Comprova polsador 1
  }
  if (ADRESH < 194 && ADRESH > 174) {
    Pols = 2;  // Comprova polsador 2
  }
  if (ADRESH < 163 && ADRESH > 143) {
    Pols = 3;  // Comprova polsador 3
  }
  if (ADRESH < 90 && ADRESH > 70) {
    Pols = 4;  // Comprova polsador 4
  }
  if (ADRESH < 55 && ADRESH > 35) {
    Pols = 5;  // Comprova polsador 5
  }
  return Pols;
}
void EnviaL(char Caracter) {
  TXREG = Caracter;  // Agafa el caràcter i l'envia
  __delay_ms(1);  // Donem temps
  while (PIR1bits.TXIF == 0)  // Esperem que s'acabi d'enviar
    ;                         // No fem res
}
void Esborra(void) {
  EnviaL(254);  // Caràcter de control
  EnviaL(1);  // Esborra la pantalla i posa el cursor a l'inici
}

Descripció del funcionament de la comunicació

Per a la comunicació, cal que hi hagi tres fils que uneixin les dues plaques, dos per a la pròpia transmissió i un que correspon al negatiu de l'alimentació i que actua com a referència. Les potes que emprarem són les següents:

Senyal Funció Pota
CK Rellotge de la transmissió RA4
DT Dades de la transmissió RA5
GND Massa o referència VSS

El programa està pensat per casos en els que el volum de dades a enviar és reduït i s'envien només de tant en tant, per això s'ha optat per una transmissió senzilla, sense protocol ni sistemes de detecció d'errors. S'envien sempre tres bytes (24 bits).

La transmissió es basa en un rellotge que va oscil·lant amb un determinat període. En la figura següent hem anomenat t al semiperíode. Durant la transmissió, aquest senyal de rellotge es troba a la pota CK. El transmissor envia un bit per DT cada cop que CK s'activa i el receptor el llegeix cada cop que CK es desactiva; d'aquesta manera ens assegurem que el valor ja es troba a la pota DT i és estable. El rellotge és controlat pel temporitzador 1.

PICkit 2

En l'exemple hem escollit un semiperíode t de 0,25 ms. Configurarem el temporitzador amb un prescalat d'1, de manera que el seu valor s'incrementarà cada 1 μs; això vol dir que el temporitzador ha de comptar 250 vegades per assolir els 250 μs (0,25 ms). Per posar aquest valor, farem:

	TMR1  = 65536 - 250 = 65286
	TMR1H = 65286 / 256 = 255
	TMR1L = 65286 % 256 = 6

En tot cas, hem fet unes definicions d'etiquetes que guarden aquests valors (T1H i T1L, respectivament); de manera que, si és desitja, és fàcil canviar el període del rellotge.

En diversos llocs del programa ens farà falta conèixer l'estat de la transmissió, per això s'ha creat una variable Control, de la qual en farem servir alguns bits.

Bit Significat
2 Dades de recepció vàlides
1 Mode receptor actiu
0 Mode emissor actiu

Tota la gestió de la comunicació es fa mitjançant interrupcions. En repòs, quan no hi ha cap transmissió en marxa, el temporitzador està aturat i les potes DT i CK estan configurades com a entrades. A més, a la pota DT se li ha activat la resistència de pull-up que conecta aquesta pota al positiu mitjançant una resistència interna; això fa que aquesta entrada estigui activada mentre cap de les dues plaques canviï res. La idea és que quan una de les dues plaques vulgui començar la transmissió, posarà a 0 la pota DT. Per això, en repòs està activada la interrupció per canvis en la pota DT.

A l'iniciar la transmissió, doncs, la placa emissora configura DT i CK com a sortides i les desactiva. Quan l'altra placa detecta un canvi en la pota DT, mira si s'ha desactivat; en cas afirmatiu, entra en mode de recepció.

La placa que fa d'emissora desactiva les interrupcions per DT i posa en marxa el temporitzador 1. A més, activa les interrupcions per aquest temporitzador. Cada cop que hi ha una interrupció pel temporitzador, s'incrementa una variable que compta les interrupcions; també es canvia l'estat de CK. Cada cop que s'activa CK es copia un bit a DT, començant pel bit de menys pes (bit menys significatiu). Quan el comptador d'interrupcions arriba a 49 (ja s'han enviat els 24 bits) es dona per acabada la transmissió, s'atura el temporitzador, es posa DT com a entrada amb resistència de pull-up (s'allibera la línia), es desactiven les interrupcions pel temporitzador i es tornen a activar les interrupcions per DT.

Quan una placa està en repòs i detecta que es desactiva la pota DT, es posa en mode de recepció, desactivant les interrupcions per la pota DT i activant les de la pota CK. Cada cop que varia la pota CK es produeix una interrupció. Si CK s'ha desactivat, es llegeix un bit a DT i es guarda; tenint en compte que el primer bit que arriba és el de menys pes (bit menys significatiu). Hi ha una variable que compta els bits rebuts i quan arriba a 24 (tres bytes) es deixa de llegir. Un cop s'han rebut els tres bytes, es surt del mode de recepció i es marquen les dades com a vàlides.

Anem a comentar els trossos del programa. Comencem pel programa principal. Al començament, tenim les configuracions dels ports, el temporitzador i les interrupcions. També es configuren l'entrada analògica, que controla els polsadors, i la pantalla LCD.

El bucle del programa principal fa tres coses. Si veu que han arribat dades (bit 2 de Control activat) s'esborra la pantalla i s'escriu R: seguit dels tres caràcters rebuts. Després es desactiva l'avís de dades vàlides i es configura la placa en mode de repòs.

Si no hi ha cap transmissió en marxa, es miren els polsadors. En cas que hi hagi un polsador premut, s'escriu a la pantalla E: Ps, seguit del número de polsador. També entra en mode emissor per transmetre tres bytes: P, s i el número de polsador que s'ha premut.

En qualsevol cas, cada cop que s'arriba al final del bucle s'inverteix la sortida RC0, per tal que el led estigui intermitent, indicant que el programa funciona.

D'entrada, la funció d'interrupció mira si aquesta ha estat per canvis al port o pel temporitzador 1. Hi podria haver més fonts d'interrupció, que també s'haurien de mirar. Si la interrupció ha estat per canvis al port (s'activa RABIF) cal veure si és per DT (RA5) o per CK (RA4). Si la interrupció és per desactivació de CK i estem en mode recepció, es fa la recepció del bit corresponent.

Si la interrupció és per desactivació de DT i no estàvem en cap mode de transmissió, vol dir que comença una transmissió i es canvia la configuració convenientment.

Finalment, si la interrupció és pel temporitzador 1 i estem en mode d'emissió, s'envia el bit corresponent; o, si ja s'han enviat tots, es surt del mode d'emissió i s'alliberà la línia.

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.