Programació en C del PIC 16F690

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

Desenvolupament de jocs senzills

Reversi

El joc és una adaptació del joc clàssic Reversi, també anomenat Othello. En aquest cas les fitxes són de colors blau i vermell. L'ús dels polsadors és el següent:

Polsador Finalitat
0 Reinici
1 Desplaçament amunt
2 Desplaçament avall
3 Desplaçament a l'esquerra
4 Desplaçament a la dreta
5 Selecció

Pantalla del joc

El programa és el següent:

// Joc "Reversi" o "Othello". En el codi parlarem d'Othello.
// Configuració inicial del PIC. Igual per a tots els projectes.
// Carrega el fitxer de funcions necessaries per al compilador
#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>
#define _XTAL_FREQ 4000000
// Constants del joc.
// Dono noms als números per entendre el codi.
#define FITXA_BUIDA 0
#define FITXA_BLAVA 1
#define FITXA_VERMELLA 2
#define CURSOR 3
// Variables globals.
// "Char" crea variables per guardar informació de la matriu LED.
char Port;              
char Compta;  // Comptador de BITS enviats.         
char Sortida[6];
// Valors a enviar per les dades de colors.    
char Sorti[6];  // Valors a enviar per la interrupció de colors.      
char Actiu;  // Variable que diu quin color està actiu.       
// Variables del joc Othello.
char tauler[8][8];  // Genera la graella 8x8. 
char cursor_x, cursor_y;  // On situem el cursor, coordenada X i Y. 
char jugador_actual;  // Variable que marca de qui és el torn del jugador.
char comptador_moviment;  // Variable que compta per fer el parpelleig. 
char mode_test;  // Allterna entre fitxes vermelles i blaves
char mirar;              
char moviment_invalid;  // Controla animació d'error
char joc_acabat;  // Variable per detectar si el joc ha acabat
char btn0_anterior = 0;  // Per al control de BTN0 - detecta canvi d'estat
// Declaro funcions seguint ST. 
void Envia3max(char Valor[]);  // Envia colors a la matriu LED.
void Envia_max(void);           
void Ini3max(void);  // Configura la matriu en la situació inicial.
void Apaga(void);
char Polsador1(void);  // Funció analògica de botons que funciona.
char detectaBTN0(void);  // Detecta el flanc descendent de BTN0 (RA3)
void inicialitzaOthello(void);  // Posa les 4 fitxes inicials del joc.
void actualitzaTauler(void);  // Mostra el tauler a la matriu LED.
void llegeixBotons(void);  // Mira si s'han premut botons
void mouCursor(signed char dx, signed char dy); // Mou el cursor verd pel tauler.
void animacioError(void);  // Mostra l'error visual
char esMovimentValid(unsigned char x, unsigned char y); // Valida moviments
// Busca flanc en una direcció
char cercaiFlanqueja(unsigned char x, unsigned char y, signed char dx, signed char dy, char jugador);
void capturaFitxes(unsigned char x, unsigned char y, char jugador); // Captura fitxes en totes les direccions
char taulerComplet(void);  // Comprova si el tauler està completament ple
void comptaFitxes(unsigned char *blaves, unsigned char *vermelles); // Compta fitxes de cada color
void mostraGuanyador(char guanyador);  // Mostra el guanyador a la pantalla
// Funció principal
void main(void) {
    OPTION_REG = 0b10000101;
    TRISC = 0;
    TRISB = 0;
    TRISA = 0xFF;  
    ANSEL = 0b00000101;
    ANSELH = 0;
    PORTC = 0;
    PORTB = 0;
    ADCON1 = 0b00010000;  
    ADCON0 = 0b00001001;
    // Inicialització matriu amb RGB  
    Ini3max();
    Actiu = 1;  
    TMR0 = 100;
    INTCON = 0b10100000;
    // Activa interrupcions
    // Inicialització variables del joc
    cursor_x = 2;
    cursor_y = 2;
    comptador_moviment = 0;
    btn0_anterior = 1;  // Inicialitza BTN0 en estat repos (sense pressió)
    // Test inicial: Parpelleig per confirmar que funciona
    for (unsigned char i = 0; i < 2; i++) {
        // Encén tot vermell
        for (unsigned char filera = 1; filera <= 8; filera++) {
            Sortida[1] = filera;
            Sortida[3] = filera; Sortida[5] = filera;
            Sortida[0] = 0xFF; Sortida[2] = 0x00; Sortida[4] = 0x00;
            Envia3max(Sortida);
        }
        __delay_ms(300);
        Apaga();
        __delay_ms(300);
    }
    // Inicialització del joc Othello
    inicialitzaOthello();
    // Bucle principal del joc
    while (1) {
        comptador_moviment++;
        // Incrementa per efectes de parpelleig
        // Evita desbordament del comptador
        if (comptador_moviment >= 128) {
            comptador_moviment = 0;
            // Reinicia el comptador
        }
        llegeixBotons();
        // Processa entrada botons analògics i BTN0 (RA3)
        actualitzaTauler();
        // Actualitza visualització
        __delay_ms(50);
    }
}
// Interrupció.
void __interrupt() temporit(void) {
    if (INTCONbits.T0IF) {
        TMR0 = 100;
        INTCONbits.T0IF = 0;
        if (Actiu != 0) {
            Actiu--;
            if (Actiu == 0) {
                Actiu = 3;
            }
        }
        Sorti[0] = 0x00;
        Sorti[2] = 0x00; Sorti[4] = 0x00;
        if (Actiu == 1) Sorti[0] = 0x01;  // Vermell
        if (Actiu == 2) Sorti[2] = 0x01;  // Verd
        if (Actiu == 3) Sorti[4] = 0x01;  // Blau
        Sorti[1] = 0x0C;
        Sorti[3] = 0x0C; Sorti[5] = 0x0C;
        Envia_max();
    }
}
// Funcions bàsiques.
// 1.Funció Envia3max.
void Envia3max(char Valor[]) {
    INTCONbits.T0IE = 0;  // Desactiva interrupcions durant enviament
    char Port = 0;  // Variable on guardem l'estat del  port B. 
    char Temp;  // Variable temporal
    for (signed char j = 5; j >= 0; j--) {  // Enviem 6 bits
        for (signed char k = 1; k < 9; k++) {  // De 8 bits
            Temp = Valor[j] & 0b10000000;  // Agafem només el primer bit
            // Si el bit és 0, desactiva el pin de dades.
            // Si el bit és 1, activa el pin de dades.
            // Mirar si l'interruptor està encès o apagat.
            if (Temp == 0) {  
                Port = Port & 0b11101111;
            } else {  
                Port = Port |
                0b00010000;  
            }
            Valor[j] = (char)(Valor[j] << 1);
            // Desplaça els bits per situar el següent. 
            PORTB = Port;  
            Port = Port | 0b00100000;  
            PORTB = Port;
            Port = Port & 0b11011111;  
            PORTB = Port;  
        }
    }
    Port = Port |
    0b01000000;  
    PORTB = Port;  
    INTCONbits.T0IE = 1;  // Reactiva interrupcions
}
void Envia_max(void) {
    asm("banksel _Port");
    asm("bcf (_Port&7fh),5");
    asm("bcf (_Port&7fh),6");
    asm("movf (_Port&7fh),w");
    asm("banksel PORTB");
    asm("movwf PORTB");
    asm("banksel _Compta");
    asm("movlw 48");
    asm("movwf (_Compta&7fh)");
    asm("Bucle:");
    asm("banksel _Port");
    asm("bcf (_Port&7fh),4");
    asm("banksel _Sorti");
    asm("rlf (_Sorti&7fh),f");
    asm("rlf ((_Sorti+1)&7fh),f");
    asm("rlf ((_Sorti+2)&7fh),f");
    asm("rlf ((_Sorti+3)&7fh),f");
    asm("rlf ((_Sorti+4)&7fh),f");
    asm("rlf ((_Sorti+5)&7fh),f");
    asm("banksel _Port");
    asm("btfsc STATUS,0");
    asm("bsf (_Port&7fh),4");
    asm("movf (_Port&7fh),w");
    asm("banksel PORTB");
    asm("movwf PORTB");
    asm("banksel _Port");
    asm("bsf (_Port&7fh),5");
    asm("movf (_Port&7fh),w");
    asm("banksel PORTB");
    asm("movwf PORTB");
    asm("banksel _Port");
    asm("bcf (_Port&7fh),5");
    asm("movf (_Port&7fh),w");
    asm("banksel PORTB");
    asm("movwf PORTB");
    asm("banksel _Compta");
    asm("decfsz (_Compta&7fh),f");
    asm("goto (Bucle&7ffh)");
    asm("banksel _Port");
    asm("bsf (_Port&7fh),6");
    asm("movf (_Port&7fh),w");
    asm("banksel PORTB");
    asm("movwf PORTB");
}
void Ini3max(void) {
    char Bytes[6];
    // Shutdown mode
    Bytes[0] = 0x00;
    Bytes[1] = 0x0C;
    Bytes[2] = 0x00; Bytes[3] = 0x0C;
    Bytes[4] = 0x00; Bytes[5] = 0x0C;
    Envia3max(Bytes);
    // Decode mode
    Bytes[0] = 0x00; Bytes[1] = 0x09;
    Bytes[2] = 0x00; Bytes[3] = 0x09;
    Bytes[4] = 0x00; Bytes[5] = 0x09;
    Envia3max(Bytes);
    // Scan limit
    Bytes[0] = 0x07; Bytes[1] = 0x0B;
    Bytes[2] = 0x07; Bytes[3] = 0x0B;
    Bytes[4] = 0x07; Bytes[5] = 0x0B;
    Envia3max(Bytes);
}
void Apaga(void) {
    char Bytes[6];
    for (unsigned char j = 1; j <= 8; j++) {
        Bytes[1] = j;
        Bytes[3] = j; Bytes[5] = j;
        Bytes[0] = 0x00; Bytes[2] = 0x00; Bytes[4] = 0x00;
        Envia3max(Bytes);
    }
}
// 6. Funció POLSADOR.
// Llegeix botons analògics:
char Polsador1(void) {
    char Pols = 0;
    ADCON0bits.GO = 1;
    while (ADCON0bits.GO == 1)  
        ;
    // Mirarà quin voltatge ha llegit i decidirà el botó que estem utilitzant.
    if (ADRESH < 220 && ADRESH > 200) {
        Pols = 1;
    }
    if (ADRESH < 194 && ADRESH > 174) {
        Pols = 2;
    }
    if (ADRESH < 163 && ADRESH > 143) {
        Pols = 3;
    }
    if (ADRESH < 90 && ADRESH > 70) {
        Pols = 4;
    }
    if (ADRESH < 55 && ADRESH > 35) {
        Pols = 5;
    }
    return Pols; // Retorna el botó premut.
}
char detectaBTN0(void) {
    // Llegeix només el bit 3 de PORTA (RA3)
    char btn0_actual = (PORTA & 0b00001000) >> 3;
    // Detecta flanc descendent: transició de 1 a 0 (quan es prem el botó)
    // Compara l'estat anterior amb l'actual
    if (btn0_anterior == 1 && btn0_actual == 0) {
        // Flanc descendent detectat - botó premut
        btn0_anterior = btn0_actual;
        return 1;  // BTN0 ha estat premut
    }
    // Actualitza l'estat anterior per a la propera consulta
    btn0_anterior = btn0_actual;
    return 0;  // BTN0 no ha estat premut
}
// Funció de validació.
char esMovimentValid(unsigned char x, unsigned char y) {
    // Comprova si la casella està buida
    if (tauler[y][x] == FITXA_BUIDA) {
    // Comprova les 8 caselles adjacents
        for (unsigned char dy = 0; dy < 3; dy++) {
            for (unsigned char dx = 0; dx < 3; dx++) {
                // Salta la casella central (1,1)
                if (dx == 1 && dy == 1) {
                    continue;
                }
                // Calcula les coordenades del veí de manera segura
                unsigned char vei_x = (unsigned char)(x + dx - 1);
                unsigned char vei_y = (unsigned char)(y + dy - 1);
                // Comprova si el veí està dins dels límits del tauler
                if (vei_x < 8 && vei_y < 8) {
                    if (tauler[vei_y][vei_x] == FITXA_BLAVA || tauler[vei_y][vei_x] == FITXA_VERMELLA) {
                        return 1;  // Moviment vàlid - hi ha fitxa adjacent
                    }
                }
            }
        }
    }
    return 0;  // Moviment invàlid
}
char cercaiFlanqueja(unsigned char x, unsigned char y, signed char dx, signed char dy, char jugador) {
    // Busca fitxes enemigues en la direcció (dx, dy)
    // Si troba una fitxa del jugador, flanqueja les fitxes enemigues del mig
    char enemiga = (jugador == FITXA_BLAVA) ?
    FITXA_VERMELLA : FITXA_BLAVA;
    signed char posicio_x = (signed char)(x + dx);
    signed char posicio_y = (signed char)(y + dy);
    unsigned char fitxes_capturades = 0;
    // Camina en la direcció mentre trobi fitxes enemigues
    while (posicio_x >= 0 && posicio_x < 8 && posicio_y >= 0 && posicio_y < 8) {
        if (tauler[posicio_y][posicio_x] == enemiga) {
            fitxes_capturades++;
        } else if (tauler[posicio_y][posicio_x] == jugador) {
            // Trobat fitxa del jugador!
            // Flanqueja les del mig
            if (fitxes_capturades > 0) {
                // Captura totes les fitxes enemigues del mig
                signed char x_captura = (signed char)(x + dx);
                signed char y_captura = (signed char)(y + dy);
                for (unsigned char i = 0; i < fitxes_capturades; i++) {
                    tauler[y_captura][x_captura] = jugador;
                    x_captura = (signed char)(x_captura + dx);
                    y_captura = (signed char)(y_captura + dy);
                }
                return 1;  // S'han capturat fitxes
            }
            return 0;  // No hi havia fitxes del mig
        } else {
            // Trobat casella buida sense fitxes del jugador
            return 0;  // Flanqueig no vàlid
        }
        posicio_x = (signed char)(posicio_x + dx);
        posicio_y = (signed char)(posicio_y + dy);
    }
    return 0;  // S'ha sortit del tauler sense trobar flanc
}
void capturaFitxes(unsigned char x, unsigned char y, char jugador) {
    // Comprova les 8 direccions possibles (horitzontals, verticals i diagonals)
    // Direccions: (dx, dy)
    // Les 8 direccions del tauler
    signed char direccions[8][2] = {
        {1, 0},  // Dreta
        {-1, 0},  // Esquerra
        {0, 1},  // Avall
        {0, -1},  // Amunt
        {1, 1},  // Diagonal avall-dreta
        {-1, -1}, // Diagonal amunt-esquerra
        {1, -1},  // Diagonal amunt-dreta
        {-1, 1}  // Diagonal avall-esquerra
    };
    // Busca flanqueig en cada direcció
    for (unsigned char i = 0; i < 8; i++) {
        cercaiFlanqueja(x, y, direccions[i][0], direccions[i][1], jugador);
    }
}
// Funció animació d'error.
// Es generarà a la matriu tres pantalles vermelles en cas d'error.
void animacioError(void) {
    // Animació d'error: parpelleig vermell de tota la matriu
    for (unsigned char i = 0; i < 3; i++) {
        // Encén tot en vermell
        for (unsigned char filera = 1; filera <= 8; filera++) {
            Sortida[1] = filera;
            Sortida[3] = filera; Sortida[5] = filera;
            Sortida[0] = 0xFF;  // Tot vermell
            Sortida[2] = 0x00;  // Verd apagat
            Sortida[4] = 0x00;  // Blau apagat
            Envia3max(Sortida);
        }
        __delay_ms(150);
        // Apaga tot
        Apaga();
        __delay_ms(150);
    }
}
char taulerComplet(void) {
    // Recorre tot el tauler buscant caselles buides
    for (unsigned char y = 0; y < 8; y++) {
        for (unsigned char x = 0; x < 8; x++) {
            if (tauler[y][x] == FITXA_BUIDA) {
                return 0;  // Hi ha almenys una casella buida
            }
        }
    }
    return 1;  // Tauler complet
}
void comptaFitxes(unsigned char *blaves, unsigned char *vermelles) {
    *blaves = 0;
    *vermelles = 0;
    // Recorre tot el tauler comptant fitxes
    for (unsigned char y = 0; y < 8; y++) {
        for (unsigned char x = 0; x < 8; x++) {
            if (tauler[y][x] == FITXA_BLAVA) {
                (*blaves)++;
            } else if (tauler[y][x] == FITXA_VERMELLA) {
                (*vermelles)++;
            }
        }
    }
}
void mostraGuanyador(char guanyador) {
    // Lletra "B" per BLAVES
    const unsigned char lletra_B[8] = {
        0b11111100,
        0b11000110,
        0b11000110,
        0b11111100,
        0b11000110,
        0b11000110,
        0b11111100,
        0b00000000
    
    };
    // Lletra "V" per VERMELLES  
    const unsigned char lletra_V[8] = {
        0b11000011,
        0b11000011,
        0b11000011,
        0b01100110,
        0b01100110,
        0b00111100,
        0b00011000,
        0b00000000
    };
    // Animació de guanyador: parpelleig amb el color guanyador
    for (unsigned char cicle = 0; cicle < 5; cicle++) {
        // Mostra la lletra del guanyador
        for (unsigned char filera = 1; filera <= 8; filera++) {
            Sortida[1] = filera;
            Sortida[3] = filera; 
            Sortida[5] = filera;
            if (guanyador == FITXA_BLAVA) {
                Sortida[0] = lletra_V[filera-1];
                Sortida[2] = 0x00;
                Sortida[4] = 0x00;
            } else {
                Sortida[0] = 0x00;
                Sortida[2] = 0x00;
                Sortida[4] = lletra_B[filera-1];
            }
            Envia3max(Sortida);
        }
        __delay_ms(500);
        // Apaga
        Apaga();
        __delay_ms(300);
    }
    // Mostra tauler final amb totes les fitxes del color guanyador
    for (unsigned char filera = 1; filera <= 8; filera++) {
        Sortida[1] = filera;
        Sortida[3] = filera; 
        Sortida[5] = filera;
        if (guanyador == FITXA_BLAVA) {
            Sortida[0] = 0xFF;  // Tot vermell
            Sortida[2] = 0x00;
            Sortida[4] = 0x00;
        } else {
            Sortida[0] = 0x00;
            Sortida[2] = 0x00;
            Sortida[4] = 0xFF;  // Tot blau
        }
        Envia3max(Sortida);
    }
    // Manté la pantalla encesa
    __delay_ms(3000);
}
// Funcions del joc.
// 1.Funció inicialitzaOthello. Inicia el joc.
void inicialitzaOthello(void) {
    // Inicialitza el tauler buit
    for (unsigned char y = 0; y < 8; y++) {
        for (unsigned char x = 0; x < 8; x++) {
            tauler[y][x] = FITXA_BUIDA;
        }
    }
    // Primera part de la funció, neteja el tauler.
    // Configuració inicial del joc.
    // Situa les 4 fitxes centrals segons les regles oficials.
    // Utilitzo com a noms de variables els colors del joc actual: vermell i blau.
    tauler[3][3] = FITXA_VERMELLA;  // (3,3) d4 - vermella (visualment blau)
    tauler[3][4] = FITXA_BLAVA;  // (3,4) e4 - blava (visualment vermell)
    tauler[4][3] = FITXA_BLAVA;  // (4,3) d5 - blava (visualment vermell)  
    tauler[4][4] = FITXA_VERMELLA;  // (4,4) e5 - vermella (visualment blau)
    // Posició inicial del cursor
    cursor_x = 2;
    cursor_y = 2;  // Posició c3
    jugador_actual = FITXA_BLAVA;
    mode_test = 0;
    mirar = 1;
    // Inicialitza control de botons
    moviment_invalid = 0;
    joc_acabat = 0;  // Joc en curs
}
void actualitzaTauler(void) {
    // Actualitza la visualització completa del tauler
    for (unsigned char filera = 1; filera <= 8; filera++) {
        unsigned char y = filera - 1;
        // Converteix a índex 0-7
        // Prepara dades per aquesta filera
        Sortida[1] = filera;
        Sortida[3] = filera; Sortida[5] = filera;
        Sortida[0] = 0x00;   
        Sortida[2] = 0x00;
        Sortida[4] = 0x00;
        for (unsigned char x = 0; x < 8; x++) {
            unsigned char contingut = tauler[y][x];
            // Cursor amb parpelleig estable (només si el joc no ha acabat)
            if (x == cursor_x && y == cursor_y && joc_acabat == 0) {
                if ((comptador_moviment & 0x0F) < 12) {  
                    contingut = CURSOR;
                }
            }
            // Assigna colors segons contingut
            switch(contingut) {
                case FITXA_BLAVA:
                    Sortida[0] |= (1 << (7-x));  // Vermell per fitxes blaves
                    break;
                case FITXA_VERMELLA:
                    Sortida[4] |= (1 << (7-x));  // Blau per fitxes vermelles
                    break;
                case CURSOR:
                    Sortida[2] |= (1 << (7-x));  // Verd per cursor
                    break;
                default:  // FITXA_BUIDA - no fa res (deixa LED apagat)
                    break;
            }
        }
        // Envia aquesta filera als MAX7221
        Envia3max(Sortida);
    }
}
// 2. Funció llegeix botons.
void llegeixBotons(void) {
    if (detectaBTN0()) {
        // BTN0 ha estat premut - reinicia el joc completament
        // Apaga la matriu LED
        Apaga();
        __delay_ms(200);  // Petita pausa per a visibilitat
        // Reinicialitza totes les variables del joc
        inicialitzaOthello();
        joc_acabat = 0;
        comptador_moviment = 0;
        mirar = 1;
        return;
        // Surt de la funció per evitar processar altres botons
    }
    // Si el joc ha acabat i no es prem BTN0, no processa més botons
    if (joc_acabat == 1) {
        return;
    }
    // Sistema de control igual que el joc de memòria
    char polsador_premut = Polsador1();
    // Llegeix polsadors analògics
    if (mirar == 1) {
        if (polsador_premut == 1) {  // BTN1 - Amunt
            mouCursor(0, -1);
            mirar = 0;
        }
        if (polsador_premut == 2) {  // BTN2 - Avall
            mouCursor(0, 1);
            mirar = 0;
        }
        if (polsador_premut == 3) {  // BTN3 - Esquerra  
            mouCursor(-1, 0);
            mirar = 0;
        }
        if (polsador_premut == 4) {  // BTN4 - Dreta
            mouCursor(1, 0);
            mirar = 0;
        }
        if (polsador_premut == 5) {  // BTN5 - Selecció amb flanqueig
            if (esMovimentValid(cursor_x, cursor_y)) {
                tauler[cursor_y][cursor_x] = jugador_actual;
                capturaFitxes(cursor_x, cursor_y, jugador_actual);
                if (taulerComplet()) {
                    unsigned char fitxes_blaves, fitxes_vermelles;
                    comptaFitxes(&fitxes_blaves, &fitxes_vermelles);
                    // Determina el guanyador
                    char guanyador;
                    if (fitxes_blaves > fitxes_vermelles) {
                        guanyador = FITXA_BLAVA;
                    } else {
                        guanyador = FITXA_VERMELLA;
                    }
                    // Mostra el guanyador
                    joc_acabat = 1;
                    __delay_ms(500);  // Petita pausa abans de mostrar resultat
                    mostraGuanyador(guanyador);
                    // El joc queda en estat finalitzat
                    return;
                }
                // Canvia el torn al següent jugador
                jugador_actual = (jugador_actual == FITXA_BLAVA) ?
                FITXA_VERMELLA : FITXA_BLAVA;
            } else {
                animacioError();
                // Passa el torn automàticament
                jugador_actual = (jugador_actual == FITXA_BLAVA) ?
                FITXA_VERMELLA : FITXA_BLAVA;
            }
            mirar = 0;
        }
    } else {
        if (polsador_premut == 0) {  // Si no s'ha premut cap polsador
            mirar = 1;
            // Permet que es detectin nous premuts
        }
    }
}
void mouCursor(signed char dx, signed char dy) {
    unsigned char nova_x = (unsigned char)(cursor_x + dx);
    unsigned char nova_y = (unsigned char)(cursor_y + dy);
    // Comprova límits i actualitza posició
    if (nova_x < 8) {
        cursor_x = nova_x;
    }
    if (nova_y < 8) {
        cursor_y = nova_y;
    }
}

 

 

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