Jogo da Velha com Microcontrolador PIC e Eletrônica 20

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 microcontrolador pic 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:

Jogo da velha com microcontrolador pic


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. 

Faça seu comentário

Acesse sua conta e participe

20 Comentários

  1. Quero fazer um jogo parecido com este, qto custa o suporte?

    1. Bom dia Décio.

      Favor entrar em contato conosco no WhastApp (48) 3307-3495.

      Att.
      Vitor Mattos.
      Suporte Técnico MakerHero.

  2. Lendo um comentário acima, eu baixei MPLABX – V 5.45 WINDOWS – INSLALER e XC8-V2-FULL-INST…EXE.
    Não sei se baixei a coisa certa, se sim, como utilizá-los para baixar o código?

    Obs.: desculpe a linguagem que estou utilizando pois sou iniciante no ramo.

    Att

    Carlos =Leonardo

    1. Olá, Carlos.

      Uma vez instalado o MPLABX e o XC8, basta criar um novo projeto (FIle-> New project se não me engano), copiar e colar o código. Se der erro na hora de compilar, pode ser que você tenha que alterar a linha 25 para void __interrupt ISR(), por conta da versão do compilador XC8.
      Aí só usar o PicKit2, 3 ou 4 ou outro programador de sua preferência para gravar o código no PIC16F628A

      Sucesso!

  3. Olá! Preciso da sua ajuda, Stephen. Sou inicinante no arduino e tenho dúvidas. Eu copie e colei o código do projeto no IDE nas versões 1.8.13 e 1.8.42.0 e deu erro sobre as bibliotecas. Eu procurei as bibliotecas em Sketch – Incluir biblioteca – gerenciar biblioteca e não encontrei. Teria como você me enviar as bibliotecas do código do projeto ou link para eu baixar do GitHub?

    A princípio seria essa a minha dificuldade inicial. Já comprei os componentes no site.

    Um abraço. Carlos Leonardo

    1. Olá Carlos,

      A IDE a que te referes é a do Arduino?

      Se sim, esse programa foi desenvolvido para o PIC, não vai funcionar no Arduino sem alterações.

      Abraço!
      Vinícius – Equipe MakerHero

  4. Estou querendo fazer um jogo da velha usando pic, teclado matricial, lcd 20×4,MikroC e Proteus, estou tendo diversos problemas. Vc estaria interessado em desenvolver um projeto adaptado do seu para esse caso meu?A gente pode tentar chegar num acordo de valor e eu faço uma transferência via PIX.

  5. Estou tentando fazer esse projeto, no MikroC e no proteus, entretanto o led bicolor no proteus funciona apenas com 2 terminais( muda a cor invertendo os terminais). No MikroC não consegui rodar o seu código, se possível teria como me enviar o código(msm que tenha feito em outra IDE).

    1. O código é o que está na publicação, mas ele de fato não rodará no MikroC sem alterações, pois cada IDE e compilador tem suas particularidades. O código acima foi escrito na IDE MPLABX com o compilador XC8 (as versões mais recentes possuem um cabeçalho de função de interrupção um pouco diferente da que está no código, mas nada que uma breve pesquisa por “interrupt routine” no Help da IDE não resolva).
      Sobre os LEDs, os de dois terminais deveriam funcionar perfeitamente com poucas alterações no código. Exceto nos momentos em que ambas as cores são ligadas, em todos os demais momentos, o LED de dois terminais funcionaria perfeitamente sem nenhuma mudança no código, mas lembrando que eles não podem assumir mais do que duas cores, sendo que neste projeto, precisamos de três (verde, vermelho e ambas as cores ligadas formando o laranja), por isso não recomendo o uso de LEDs bicolores de dois terminais.

  6. Boa noite, Não poderia disponibilizar o código em .HEX?, Não tenho compilador, Obrigado

    1. O download do MPLAB IDE e do XC8 são gratuitos e podem ser obtidos no site da própria Microchip! Não é preciso pagar nada e os programas são leves (:

      1. Ola, estou usando o MPLAB x IDE 5.15, quando vou passar o código para o PIC esta gerando 2 erros( referente a variável Bit e o outro referente a Void), estou utilizando o pickit3.15.

        1. Pode copiar a mensagem de erro? Só assim poderei te ajudar

    2. gostaria de ter o hex tambem

      1. poderia disponibilizar apenas o hex da compilação?

        1. O download do MPLAB IDE e do XC8 são gratuitos e podem ser obtidos no site da própria Microchip! Não é preciso pagar nada e os programas são leves (:

  7. Está dando erro, diz que: 22 304 error: Can’t open include file “xc.h” #include 1 error in preprocessor. C:\Users\Public\Documents\Mikroelektronika\mikroC PRO for PIC\Examples\Development Systems\EASYPIC7\Led Blinking\velha.c
    O que pode ser?

    1. Você parece estar usando o MikroC, e não o MPLAB como usei. Pra corrigir, creio que apenas removendo a linha com o incluxe xc.h vá resolver. Não lembro agora se o MikroC precisa de algum header próprio.

  8. 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!