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
}
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.

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.

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