/*
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
}