/*
Arduino UNO com um display de 25 pixels funcionando como dispostivo Master para uma 
controladora de mouse utilizando um protocolo de comunicação serial com fios diferente
do SPI, orientado a interrupts (interrupt-driven), utilizando apenas 2 linhas de 
comunicação, com o SCK e o MOSI compartilhando a mesma linha, através da utilização de
uma técnica de "clock aperiódico", e o MISO e a interrupt request compartilhando
a outra linha.

Autor: Dante Meira

Clique em mousecontroller.chip.c para ver a lógica interna da controladora.

Para um exemplo de como a controladora de mouse poderia ser implementada utilizando
um AtTiny85 e um registrador de deslocamento (shift register) PISO (Parallel In Serial Out)
consulte este meu outro projeto: https://wokwi.com/projects/386559980762074113

Este método de comunicação é adequado para transmissão de pequenos buffers de até 8 bits. 
Para transmissões maiores torna-se ineficiente por exigir comunicação duplex a cada bit 
transmitido, sendo preferível a utilização de métodos síncronos baseados em sampling periódico.
*/


int buffer[5] = {0, 0, 0, 0, 0};
// o buffer de comunicação serial armazena as seguintes informaçôes:
// em 0 - o mouse enviou um sinal para mover o ponteiro uma unidade para cima
// em 1 - o mouse enviou um sinal para mover o ponteiro uma unidade para baixo
// em 2 - o mouse enviou um sinal para mover o ponteiro uma unidade para a direita
// em 3 - o mouse enviou um sinal para mover o ponteiro uma unidade para a esquerda
// em 4 - o mouse enviou um sinal de click



const unsigned int X_maximo = 4;
const unsigned int Y_maximo = 4;

unsigned int posX = 2; //posição do ponteiro do mouse no eixo X
unsigned int posY = 2; //posição do ponteiro do mouse no eixo Y
char framebuffer[5][5]; // framebuffer do display

char etapaboot = 1;

void setup() {
  
  pinMode(3, INPUT); // MISO e interrupt request compartilham essa linha roxa
  pinMode(4, OUTPUT); // MOSI e SCK compartilham essa linha azul

 // colunas do display
  pinMode(5, OUTPUT); 
  pinMode(6, OUTPUT); 
  pinMode(7, OUTPUT); 
  pinMode(8, OUTPUT); 
  pinMode(9, OUTPUT); 

for (char coluna = 9; coluna >=5; coluna--){
   digitalWrite(coluna, HIGH);               
}  

 // linhas do display
  pinMode(15, OUTPUT);  // pino A1
  pinMode(16, OUTPUT);  // pino A2
  pinMode(17, OUTPUT);  // pino A3
  pinMode(18, OUTPUT);  // pino A4
  pinMode(19, OUTPUT);  // pino A5

  attachInterrupt(digitalPinToInterrupt(3), HandlerDoMouse, RISING); // interrupt do mouse

  Serial.begin(99000);


   // colocando o timer 1 no modo CTC (Clear Timer on Compare-match)
     TCCR1B &= ~(B00000001 << WGM13); // clear 
     TCCR1B |= (B00000001 << WGM12);  // set

     // setando prescaler do timer para 1024 (16 mHz / 1024 = 15.625 Hz)
     TCCR1B = TCCR1B | (B00000001 << CS12);  // set
     TCCR1B = TCCR1B & ( ~(B00000001 << CS11) ); // clear
     TCCR1B = TCCR1B | (B00000001 << CS10);  // set

     TCNT1 = 0;
     
     OCR1A = 125; // 8 milésimos de segundo para o Compare-match A

     TIMSK1 = (1 << OCIE1A); // habilita interrupt do Compare-match A

    sei(); // habilita interrupts
  
    for (char n = 0; n <= 3; n++){ 
      framebuffer[0][n] = 1;
      framebuffer[4][n] = 1;
    }
   framebuffer[1][1] = 1;
   framebuffer[2][1] = 1;
   framebuffer[3][1] = 1;
   framebuffer[1][4] = 1;
   framebuffer[2][4] = 1;
   framebuffer[3][4] = 1;


      while(true){ // loop infinito fazendo nada, só aguardando interrupts
          
            asm volatile( "nop \n\t" );
            
      }
}


ISR(TIMER1_COMPA_vect){ // Rotina de Serviço de Interrupção (ISR) do Timer 1

  // chamada a cada 8 milésimos de segundo

if (etapaboot == 1){
  if (millis() > 1500 ){
        for (char linha = 0; linha <= 5; linha++){
            for (char coluna = 0; coluna <= 5; coluna++){ framebuffer[linha][coluna] = 0; }                
              }

            for (char n = 0; n <= 4; n++){ 
              framebuffer[n][0] = 1;
              framebuffer[n][4] = 1;
            }
          framebuffer[1][1] = 1;
          framebuffer[2][2] = 1;
          framebuffer[1][3] = 1;
       etapaboot = 2;
       }
}

if (etapaboot == 2){
  if (millis() > 3000 ){
        for (char linha = 0; linha <= 5; linha++){
            for (char coluna = 0; coluna <= 5; coluna++){ framebuffer[linha][coluna] = 0; }                
              }

       framebuffer[2][2] = 1; 
       etapaboot = 3;
       }
}

    if (etapaboot == 3){ 
      RotinaDoCursor();   // chama a Rotina do cursor do mouse
    }

     RotinaDoDisplay(); // chama a Rotina do Display
}

void  RotinaDoCursor(){
  // essa rotina verifica o buffer de entrada de dados enviados pela controladora do mouse
  // e realiza as operações necessárias de mudança de posição do cursor do mouse e de click

  cli(); // desabilita interrupts

  if (buffer[0] == 1){ // cima
       if(posY > 0){
            framebuffer[posY][posX] = 0; 
            posY =  posY - 1;
            framebuffer[posY][posX] = 1;
          } 
       buffer[0] = 0;
   }

     if (buffer[1] == 1){  // baixo
       if(posY < Y_maximo){ 
        framebuffer[posY][posX] = 0; 
        posY =  posY + 1; 
        framebuffer[posY][posX] = 1; 
        } 
       buffer[1] = 0;
   }  

  if (buffer[2] == 1){  // direita
       if(posX < X_maximo){ 
        framebuffer[posY][posX] = 0; 
        posX =  posX + 1; 
        framebuffer[posY][posX] = 1; 
        } 
       buffer[2] = 0;
   }

   if (buffer[3] == 1){    // esquerda 
      if(posX > 0){ 
       framebuffer[posY][posX] = 0; 
       posX =  posX - 1;
       framebuffer[posY][posX] = 1; 
       } 
       buffer[3] = 0;
   }  

   if (buffer[4] == 1){  // click
        Serial.println("click");
       buffer[4] = 0;
   }

 sei(); // habilita interrupts
}


void RotinaDoDisplay(){ 
  char y = 0;
  char x = 0;

    for (char linha = 19; linha >=15; linha--){
              digitalWrite(linha, HIGH);

              for (char coluna = 9; coluna >=5; coluna--){
                      if(framebuffer[y][x] == 1){
                            digitalWrite(coluna, LOW);
                            asm volatile( "nop \n\t" );
                            digitalWrite(coluna, HIGH);
                         }else{
                            asm volatile( "nop \n\t" );
                         }
                     x++;  
              }
        digitalWrite(linha, LOW);       
        x = 0; 
        y++;
    }

}


void HandlerDoMouse() { //ISR do pino 3 (linha roxa)  

  // o clock é "aperiódico" no sentido de que não há uma contagem precisa de tempo
  // para o envio de pulsos na linha SCK / MOSI, não há um intervalo (período)
  // regulado com precisão


   //pulso enviado para o pino OUVE do controlador, solicitando 1º bit do buffer
    digitalWrite(4, HIGH);
    digitalWrite(4, LOW);
    
    asm volatile( "nop \n\t" ); // dando tempo para o Slave ajustar a saída

    if(digitalRead(3) ==  HIGH){
        buffer[0] = 1;
    }else{
        buffer[0] = 0;
    }


   //pulso enviado para o pino OUVE do controlador, solicitando 2º bit do buffer
    digitalWrite(4, HIGH);
    digitalWrite(4, LOW);

  asm volatile( "nop \n\t" );

    if(digitalRead(3) ==  HIGH){
        buffer[1] = 1;
    }else{
        buffer[1] = 0;
    }


   //pulso enviado para o pino OUVE do controlador, solicitando 3º bit do buffer
    digitalWrite(4, HIGH);
    digitalWrite(4, LOW);

  asm volatile( "nop \n\t" );

    if(digitalRead(3) ==  HIGH){
        buffer[2] = 1;
    }else{
        buffer[2] = 0;
    }


   //pulso enviado para o pino OUVE do controlador, solicitando 4º bit do buffer
    digitalWrite(4, HIGH);
    digitalWrite(4, LOW);

  asm volatile( "nop \n\t" );

    if(digitalRead(3) ==  HIGH){
        buffer[3] = 1;
    }else{
        buffer[3] = 0;
    }


   //pulso enviado para o pino OUVE do controlador, solicitando 5º bit do buffer
    digitalWrite(4, HIGH);
    digitalWrite(4, LOW);

  asm volatile( "nop \n\t" );

    if(digitalRead(3) ==  HIGH){
        buffer[4] = 1;
    }else{
        buffer[4] = 0;
    } 

   
   // pulso enviado para o pino OUVE do controlador informando fim de comunicação
   // para que o controlador coloque a linha FALA em LOW
    digitalWrite(4, HIGH);
    digitalWrite(4, LOW);

/*
   Antes de sair da ISR é preciso limpar a flag INTF1 no registrador EIFR, 
   escrevendo o valor lógico 1 nela, para evitar que a ISR seja chamada novamente
   em virtude da presença da interrupt "na fila" gerada pelo valor HIGH no pino 3
   durante a execução da ISR
*/
  EIFR = B00000010 ; // limpando a flag INTF1 para limpar a fila
}
Mouse ControllerBreakout