Jogo da Velha com Microcontrolador PIC e Eletrônica 2

Quem disse que nem mesmo o bom e velho Jogo da velha não poderia ficar digital? Chegou a hora de parar de gastar papel (ou giz, caso você jogue no quadro da escola) e aprender um pouco mais sobre programação e eletrônica. Neste post ensinaremos vocês a montar um jogo da velha com microcontrolador PIC e eletrônica usando componentes simples.

Lista de componentes

Para este projeto precisaremos dos seguintes componentes:

Perceba que não usaremos nenhum componente difícil de ser comprado, e o microcontrolador pode facilmente ser trocado por qualquer outro modelo ou até mesmo empregar uma placa Arduino, para aqueles que tem um pouco mais de conhecimento.

Montagem do circuito

Vamos ao circuito do projeto jogo da velha com microcontrolador PIC. A montagem é bem simples e rápida como pode ser visto na imagem a seguir:

Circuito jogo da velha com microcontrolador PIC

Recomendamos que vocês montem a matriz de LEDs na placa perfurada, pois tal montagem ficaria visualmente complicada tanto na protoboard quanto feita soldando fios aos terminais dos LEDs. Esta montagem não foi mostrada aqui usando o Fritzing pois ficava visualmente poluído e incompreensível.
Para montar a matriz, ligue todos os pinos da mesma cor de cada coluna juntos e os catodos de todos os LEDs da mesma linha juntos.

Usando o PIC16F628A, a ligação ficará da seguinte forma (todas as ligações podem ser alteradas no código facilmente):

Tabela de pinagem jogo da velha com PIC

A pinagem do PIC pode ser vista na seguinte imagem para facilitar a compreensão:

Pinagem do PIC16F627A

Ao finalizar a montagem, basta programar o PIC com o código abaixo e alimentar o circuito com tensões de 3 a 5 Volts no máximo, podendo vir de baterias, pilhas, carregador portátil ou porta USB.

Veja abaixo como ficou o projeto montado na placa perfurada:

Projeto Montado


Para jogar é simples: pressione o botão “move” para escolher a posição onde quer jogar e pressione o botão “joga” para fazer a jogada na posição. Cada vez que um jogador ganhar, a pontuação deste será exibida na matriz, que depois de um tempo apagará e o jogo irá começar novamente. A cada 9 ponto de qualquer um dos jogadores, a contagem da pontuação irá zerar. Caso dê velha (nenhum jogador ganhou na partida), a matriz se apaga e o jogo recomeça.

Programa do jogo da velha com microcontrolador PIC

O programa pode assustar porém é fácil de ser compreendido. Veja abaixo o programa para o Jogo da Velha com microcontrolador PIC. Cada parte está bem comentada e explicarei mais sobre ele abaixo. O código também pode ser encontrado no Github Gist.

// PIC16F628A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON       // Brown-out Detect Enable bit (BOD enabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#include <xc.h>
#define _XTAL_FREQ 4000000
#define SEL PORTAbits.RA3
#define MOVE PORTAbits.RA4

//declara as variáveis necessariamente globais
unsigned char verde[3][3], vermelho[3][3], cursor[3][3], linha, conta, cor;
bit pisca;

//rotina da interrupção
void interrupt ISR()
{
    //confere se a interrupção é devido a overflow no Timer0
    if(T0IF)
    {
        //zera o PORTB pra evitar "sombras" nos LEDs
        PORTB = 0;
        
        //ativa o pino do PORTA correspondente a cada linha
        if(linha == 0)
            PORTA = 1;
        else if(linha == 1)
            PORTA = 2;
        else if(linha == 2)
            PORTA = 4;

        //Lógica para acender cada LED
        PORTBbits.RB0 = vermelho[linha][0] | (cursor[linha][0] & pisca & cor);
        PORTBbits.RB1 = vermelho[linha][1] | (cursor[linha][1] & pisca & cor);
        PORTBbits.RB2 = vermelho[linha][2] | (cursor[linha][2] & pisca & cor);
        PORTBbits.RB3 = verde[linha][0] | (cursor[linha][0] & pisca & !cor);
        PORTBbits.RB4 = verde[linha][1] | (cursor[linha][1] & pisca & !cor);
        PORTBbits.RB5 = verde[linha][2] | (cursor[linha][2] & pisca & !cor);
        
        //incrementa a variável linha para que na próxima interrupção a 
        //próxima linha seja ativada
        linha++;
        if(linha == 3)
            linha = 0;
        
        //incrementa a variável conta para contar o tempo e fazer o
        //cursor piscar.
        conta++;
        if(conta == 100)
        {
            conta = 0;
            pisca = !pisca;
        }
        
        //reseta os parâmetros do Timer0 para a interrupção ocorrer novamente
        T0IF = 0;
        TMR0 = 131;
    }
}

//rotina para verificar se houve uma combinação válida em alguma matriz
unsigned char testa_ganhou(unsigned char teste[3][3])
{
    //verifica se existe alguma coluna completa
    if(teste[0][0] && teste[1][0] && teste[2][0])
        return 1;
    if(teste[0][1] && teste[1][1] && teste[2][1])
        return 1;
    if(teste[0][2] && teste[1][2] && teste[2][2])
        return 1;
    
    //verifica se existe alguma linha completa
    if(teste[0][0] && teste[0][1] && teste[0][2])
        return 1;
    if(teste[1][0] && teste[1][1] && teste[1][2])
        return 1;
    if(teste[2][0] && teste[2][1] && teste[2][2])
        return 1;
    
    //verifica as duas diagonais
    if(teste[0][0] && teste[1][1] && teste[2][2])
        return 1;
    if(teste[0][2] && teste[1][1] && teste[2][0])
        return 1;   
    
    //retorna zero se não há combinação válida
    return 0;
}

//rotina para verificar se "deu velha"
//(todas as posições marcadas e ninguém ganhou)
unsigned char testa_velha(unsigned char teste1[3][3], unsigned char teste2[3][3])
{
    unsigned char i, j, soma;
    
    soma = 0;
    
    //soma o valor de todas as posições das duas matrizes
    //se todas as 9 posições estiverem completas, o valor dá nove,
    //indicando que deu velha
    for(i = 0; i < 3; i++)
    {
        for(j = 0; j < 3; j++)
            soma += teste1[i][j] + teste2[i][j];
    }
    
    if(soma == 9)
        return 1;
    else
        return 0;
}

//zera todas as posições na matriz
void limpa_matriz(unsigned char matriz[3][3])
{
    unsigned char i, j;
    
    for(i = 0; i < 3; i++)
    {
        for(j = 0; j < 3; j++)
            matriz[i][j] = 0;
    }
}

void main(void)
{
    //cria as variáveis locais
    unsigned char i, j, posicao, MOVE_Old, SEL_Old, pontuacao_vermelho, pontuacao_verde;
    
    //inicializa as matrizes e variáveis
    limpa_matriz(verde);
    limpa_matriz(vermelho);
    limpa_matriz(cursor);
    
    pisca = 1;
    linha = 0;
    posicao = 0;
    MOVE_Old = 1;
    SEL_Old = 1;
    cursor[0][0] = 1;
    cor = 0;
    pontuacao_vermelho = 0;
    pontuacao_verde = 0;
    
    //configura o PIC
    CMCON = 0xFF;
    VRCON = 0;
    TRISA = 0x18;
    TRISB = 0x00;
    PORTA = 1;
    PORTB = 0;
    OPTION_REG = 0xC2;
    TMR0 = 131;
    INTCON = 0xA0;
    T0IF = 0;
    
    while(1)
    {
        //testa se o botão que move o cursor está pressionado agora
        //mas não estava antes. Isso impede que ele se mova várias vezes em uma
        //unica pressionada. Se for verdade, muda a posição
        //e limpa a posição anterior.
        if(MOVE == 0 && MOVE_Old == 1)
        {
            cursor[posicao/3][posicao%3] = 0;
            posicao++;
            if(posicao == 9)
                posicao = 0;
            cursor[posicao/3][posicao%3] = 1;
            MOVE_Old = 0;
        }
        //verifica se o jogador soltou o botão
        if(MOVE == 1 && MOVE_Old == 0)
            MOVE_Old = 1;
        
        //Marca na matriz correspondente a posição da jogada
        //caso o jogador tenha pressionado o botão de jogada
        if(SEL == 0 && SEL_Old == 1)
        {
            if(cor == 0 && vermelho[posicao/3][posicao%3] == 0 && verde[posicao/3][posicao%3] == 0)
            {
                verde[posicao/3][posicao%3] = 1;
                cor = 1;
            }
            else if(cor == 1 && verde[posicao/3][posicao%3] == 0 && vermelho[posicao/3][posicao%3] == 0 )
            {
                vermelho[posicao/3][posicao%3] = 1;
                cor = 0;
            }
            
            cursor[posicao/3][posicao%3] = 0;
            posicao++;
            if(posicao == 9)
                posicao = 0;
            cursor[posicao/3][posicao%3] = 1;
            
            SEL_Old = 0;
        }
        //verifica se o jogador soltou o botão
        if(SEL == 1 && SEL_Old == 0)
            SEL_Old = 1;      

        //confere se o jogador da cor verde ganhou
        //se sim, mostra a pontuação com efeito,
        //reseta as matrizes e as variáveis de controle
        if(testa_ganhou(verde))
        {
            limpa_matriz(vermelho);
            limpa_matriz(verde);
            limpa_matriz(cursor);
            pontuacao_verde++;
            cor = 0;
            
            for(i = 0; i < pontuacao_verde; i++)
            {
                verde[i/3][i%3] = 1;
                __delay_ms(200);
            }
            __delay_ms(1000);
            
            if(pontuacao_verde == 9)
            {
                for(i = pontuacao_verde+1; i > 0; i--)
                {
                    verde[(i-1)/3][(i-1)%3] = 0;
                    __delay_ms(100);
                }
                pontuacao_vermelho = 0;
                pontuacao_verde = 0;   
            }
            __delay_ms(500);
            
            limpa_matriz(verde);
            linha = 0;
            posicao = 0;
            cursor[0][0] = 1;
            cor = 1;
        }
        //confere se o jogador da cor vermelha ganhou
        //se sim, mostra a pontuação com efeito,
        //reseta as matrizes e as variáveis de controle
        if(testa_ganhou(vermelho))
        {
            limpa_matriz(vermelho);
            limpa_matriz(verde);
            limpa_matriz(cursor);
            pontuacao_vermelho++;
            cor = 1;
            
            for(i = 0; i < pontuacao_vermelho; i++)
            {
                vermelho[i/3][i%3] = 1;
                __delay_ms(200);
            }
            __delay_ms(1000);
            
            if(pontuacao_vermelho == 9)
            {
                for(i = pontuacao_vermelho+1; i > 0; i--)
                {
                    vermelho[(i-1)/3][(i-1)%3] = 0;
                    __delay_ms(100);
                }
                pontuacao_vermelho = 0;
                pontuacao_verde = 0;  
            }
            __delay_ms(500);
            
            limpa_matriz(vermelho);
            linha = 0;
            posicao = 0;
            cursor[0][0] = 1;
            cor = 0;
        }
        //confere se deu velha
        //se sim, apaga a matriz por um segundo,
        //reseta as matrizes e as variáveis de controle
        if(testa_velha(verde, vermelho))
        {
            limpa_matriz(verde);
            limpa_matriz(vermelho);
            limpa_matriz(cursor);
            __delay_ms(1000);
            linha = 0;
            posicao = 0;
            cursor[0][0] = 1;
            cor = 0;            
        }
    }
}

Neste projeto utilizaremos o conceito de varredura para acionar os LEDs da matriz utilizando o mínimo de pinos do microcontrolador.

Para conseguir organizar os LEDs, utilizaremos 3 matrizes (vetores de duas dimensões) 3 x 3, ou seja, cada posição na matriz representa um LED. Duas matrizes armazenam as informações das posições marcadas por cada jogador (matriz verde e vermelho) e uma para gerenciar a posição do cursor na matriz de LEDs(matriz cursor).

Para transformar a variável “posicao” em índices para a matriz e simplificar o código, veja que usei “posicao/3″ para as linhas e “posicao%3″ para as colunas. Isto porque quando dividimos um número inteiro por outro inteiro, o programa arredondará o valor resultante retornando sempre um inteiro que é o que precisamos, já que a matriz não aceita índices decimais. O operador % nos retorna o resto da divisão de “posicao” por 3, ou seja, sempre 0, 1 ou 2. Quando “posicao” for, por exemplo, 3, a divisão por 3 dará 1 e o resto 0, ou seja, estou na primeira coluna da segunda linha.

A imagem a seguir mostra um exemplo em C desta lógica funcionando pra deixar as coisas um pouco mais claro:

Exemplo da lógica do programa

A parte preta mostra a saída no terminal. A orientação é a mesma do programa: [linha][coluna]

A variável “cor” serve para controlar qual será o jogador a jogar na rodada atual.

Na nossa rotina de interrupção temos basicamente quatro tarefas sendo executadas:

  1. Limpar o PORTB, apagando as colunas, e ativar as linhas correspondentes à variável “linha“;
  2. Ativar ou não a saída de cada pino do PORTB correspondente a cada coluna em função da linha;
  3. fazer a contagem do tempo e, baseado nesta, controlar a variável responsável por fazer o LED do cursor piscar;
  4. Limpar a interrupção e recarregar o Timer0 para que a interrupção ocorra no tempo desejado.

A lógica para acender cada LED é a seguinte:

  1. Acende o LED se naquela posição atual da matriz há uma jogada feita OU (“|”);
  2. Acende o LED se o cursor está na posição atual E (“&”) a variável pisca é diferente de zero E a cor é a correta para a matriz atual (cor = 0 significa verde e cor = 1 significa vermelho).

Para saber se alguém ganhou, temos a rotina “testa_ganhou“. Nela o programa verifica se existe uma linha inteira, uma coluna inteira ou uma diagonal inteira preenchida naquela cor informada. Veja que a rotina pede uma matriz para analisar e retorna o valor um se alguém ganhou ou zero se não.

Para testar se “deu velha”, a rotina “testa_velha” pega ambas as matrizes e soma o valor de cada posição. Se todas as posições estiverem marcadas, esta soma terá que dar nove, afinal, temos nove posições valendo um ou zero. Assim como a outra rotina, esta retorna o valor um ou zero.

Veja abaixo o projeto em funcionamento:

Funcionamento do projeto

Gostou do projeto Jogo da velha com microcontrolador PIC? Deixe seu comentário logo abaixo. Em caso de dúvidas, caso queira trocar uma ideia, ou até mesmo dividir seu projeto, acesse nosso Fórum!

Posts Relacionados

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

2 Comentários

  1. Stephen, que hardware e software você usa para passar o código para o microcontrolador? Estou procurando um específico para *nix. Parabéns pelo tutorial! Obrigado desde já.
    ML

    1. Olá, Marconi. Eu uso o MPLAB IDE ou o MPLAB IPE, que são próprios da Microchip e suportam os famosos pickit 2 e 3 (uso o pickit 3). Se usar um programador JDM, aquele que vai na porta serial, recomendo o ic-prog.
      Obrigado e boa sorte nos projetos!