Engenharia Reversa do Testador de Componentes GM328A + Tetris!

Bem vindo! Neste post eu falo sobre a engenharia reversa do testador de componentes GM328A. Eu desenhei os esquemas da placa e compilei um novo firmware para ela. Como bônus, eu também programei uma versão de Tetris usando Arduino.

Intro

Algum tempo atrás eu escrevi um artigo no qual programei um medidor LCR (indutância, capacitância e resistência) T3 para executar um clone do jogo t-rex do navegador chrome. Esse medidor LCR foi uma das ferramentas mais úteis que eu já tive, especialmente porque eu tenho o mesmo multímetro digital desde os 15 anos e este nem sequer mede capacitores. Infelizmente, o pobre testador não suportou o abuso constante (eu costumava usá-lo como crachá quando ia para conferências e tal) e o cabo do LCD quebrou.

Eu procurei no eBay por um substituto, e o sucessor do T3, o T4 pareceu ter piorado em qualidade, sem falar que os preços do T3 subiram também. Felizmente eu encontrei algo ainda melhor, esta versão foi a GM328A.

Este medidor LCR tem um LCD colorido, o que é ótimo no caso de eu continuar a usá-lo como um crachá. Ele também tem medição de frequência e tensão contínua, uma entrada de alimentação por fonte e saída PWM. Além disso, o que eu encontrei custou apenas em torno de 55 reais (com as taxas).

Dito isto, estes medidores LCR fazem ótimas placas de desenvolvimento, neste post detalho o processo que eu usei para desenhar os esquemas para este modelo e também mostro uma versão do Tetris que eu programei para ele. Os esforços de engenharia reversa aqui são principalmente relacionados à placa de circuito impresso e não ao firmware.

Antes de prosseguirmos, devo mencionar que todos esses testadores de transistores são baseados no mesmo projeto, originalmente criado por Markus Frejek e continuado por Karl-Heinz. Você pode encontrar informações e o código em mikrocontroller.net. Ainda assim, há diversas de diferentes variações do circuito vendido no eBay e afins, neste post eu fiz engenharia reversa dos esquemas apenas para esta placa.

Entao, para resumir. Neste projeto eu:

  • Fiz a engenharia reversa da placa de circuito impresso;
  • Corrigi um erro no circuito de medição de frequência;
  • Ajustei e compilei o código do projeto AVR  transitor tester para esta placa;
  • Programei Tetris para a mesma.

Antes de qualquer coisa, achei que era uma boa ideia fazer o backup do firmware original, mas foi aí que eu encontrei o primeiro obstáculo. Embora a placa pareça amigável para hackers, até mesmo possuindo um soquete para o microcontrolador, a leitura de seu firmware foi bloqueada. Então, em vez de copiar o firmware original para o computador, substituí o microcontrolador. Desta forma, eu sempre poderia ter o firmware original e ainda usar a placa se eu cometesse algum engano.

Engenharia reversa da PCI do GM328A

Como esperado, o gm328a também é baseado no Atmega328 (daí o nome, imagino). Este é o mesmo microcontrolador usado em muitas placas Arduino (UNO, Nano, Mini).

Eu poderia ter dessoldado tudo para ver as trilhas da PCI claramente, mas eu não queria estragar o testador, então eu só usei a função de teste de continuidade do meu multímetro para checar onde os componentes estavam conectados. Essa é uma abordagem difícil, já que um componente pode ser conectado a muitos outros. Ainda assim, o circuito é pequeno o suficiente para não me causar uma dor de cabeça.

O bom é que muitos dos componentes passivos tinham valores impressos na placa. Eu também usei uma lupa para ver as trilha e as marcações dos CIs. Para alguns componentes, eu tive que procurar on-line por pinagens compatíveis. O display, por exemplo, eu tive que encontrá-lo on-line (é o ST7735) para descobrir sua pinagem.

  Depois de muitos testes, e pesquisas online, eu criei o seguinte esquema. Você pode clicar na imagem para aumentar o tamanho.

Eu fiz o melhor que pude com apenas o teste de continuidade. Pode não ser perfeito, mas é bom o suficiente para o seu propósito, que é dizer onde as partes importantes estão conectadas. você pode encontrar esse esquema no formato Eagle no repositório do projeto.

Bônus: Conserto no layout da placa

Enquanto estudava a placa, me deparei com algo estranho. Uma via na placa não estava conectada a nada. Pela aparência, parecia que a trilha vinda do pino PD4 deveria ser conectado ao circuito de medição de frequência. Mais tarde, eu verifiquei o firmware de Markus e PD4 era de fato o pino usado para medição de frequência lá também.

Eu tentei testar a função de medição de frequência para ver se estava faltando alguma coisa, mas a mesma não funcionou. Eu concluí que era de fato um erro no layout do PCB. Eu consertei o problema removendo a máscara de solda da via e conectando-a com solda ao resistor mais próximo.

Depois disso, a medição de frequência funcionou como deveria.

Novo firmware

Diferentemente do T3, o GM328A não possui uma porta de gravação. Embora o MCU possua soquete e possa ser removido, isso é difícil de fazer e, com eu frequentemente entorno os pinos acidentalmente. Eu decidi então adicionar uma porta de gravação temporária para conectar diretamente a placa a um dos meus ​​programadores USBASP.

De qualquer forma, agora com as informações de pinagem da engenharia reversa em mãos, consegui compilar um novo firmware para a placa. Primeiramente eu baixei a versão 1.34 do código AVR transitortester em mikrocontroller.net. Você pode encontrar todas as versões do firmware do testador AVR aqui.

Então, eu apenas ajustei a pinagem de acordo com o esquema mostrado acima, selecionei o tipo de LCD e configurei as funções disponíveis e logo logo consegui fazer o testador funcionar com o novo código . Depois de alguns ajustes, comparei os resultados do novo código compilado com anterior o que veio com a placa. Estes resultados foram coletados após a calibração da placa para ambas as versões de firmware.

Você pode encontrar o firmware editado no repositório do github. Lá você também pode ver o histórico dos arquivos config.h, config328.h e Makefile para ver o que eu mudei em relação ao fork principal.

Novo firmware rodando na placa (transistor 2N3904)

Tetris

É claro que eu tinha que programar um jogo depois de chegar até aqui (mesmo que eu não saiba quase nada sobre programação de jogos). Se o código parece um código espaguete, você sabe por quê.

Este gm328a tem um encoder com um botão embutido. Então, isso é como ter três botões. Essa configuração seria muito boa para arkanoid ou talvez pong, mas eu escolhi o Tetris por duas razões. Primeiro, os níveis são os mesmos quando progredimos, a única coisa que muda é a velocidade em que os blocos caem e a pontuação. Segundo: Rafael disse no passado que era possível programar um tetris funcional  em um único dia, então eu estava realmente ansioso para ver quanto tempo isso me levaria. Mesmo usando Arduino e bibliotecas, Demorei alguns dias.

Na última vez eu me “diverti muito” programando o jogo do t-rex usando apenas C. Eu decidi que desta vez eu usaria o Arduino. A propósito, eu traduzi toda a documetação do Arduino para o português, então eu meio que conheço as funções de cor.

Eu também usei algumas bibliotecas externas, listadas abaixo:

Programar o jogo ainda foi um grande desafio. Para começar, o LCD e o encoder rotativo são conectados nos mesmos pinos, então eu tinha que alternar entre gravar dados no LCD ou verificar o encoder. Além disso, tinha que escrever dados no LCD muito rapidamente e monitorar o encoder no resto do tempo. Caso contrário, o jogo perderia as voltas do encoder. O fato de que comunicação SPI é feita por software não ajudou muito. Por causa disso, optei por usar quadrados muito simples para representar os tetrominos, e não perder tempo preenchendo-os também.

Para programar o gm328a usando o Arduino, eu apenas seleciono o Arduino Pro ou Pro mini no menu da placa na IDE. Então eu escolho Atmega328 (3.3V, 8Mhz). Observe que eu escolhi 3.3V mesmo se a placa for 5V. Tudo bem, já que eu não uso o ADC neste projeto e não há uma opção de 5V, 8Mhz. Dessa forma, não tenho que editar arquivos de placa fora da IDE ou qualquer coisa do tipo, pois detesto esse tipo de gambiarra. Para gravar o código eu uso um gravador USBASP com a opção Upload using programmer (Ctrl + Shift + U).

Eu não garanti o timing certo da última vez. Mas agora eu estudei um pouco, isso me levou a implementar um loop controlador por interrupção.

#include "game.h"
#include "sound.h"

void setup(void) { 
  gameInit();
}

void loop() {
  gameTick();
  drawBuffer();
  waitInterrupt();
}

O loop do jogo é atualizado principalmente a cada 60Hz, enquanto a tela não é. Embora a tela seja SPI, ela é controlada por software através de  GPIOs. Além disso, o MCU funciona em 8Mhz, enquanto ele poderia funcionar em 20MHz sem problemas. Assim, atualizar a tela, o que acontece somente quando algo precisa mudar algo na mesma, leva muito tempo comparado à computação do próximo estado do jogo. A função gameTick () verifica as entradas e calcula o próximo frame, você pode vê-lo abaixo:

void gameTick(void) {
  side = getEncoderPos();
  if (side == S_LEFT)  tet.move(S_LEFT);
  if (side == S_RIGHT) tet.move(S_RIGHT);
  if (buttonWasPressed()) tet.rotate();
  tet.update();
  soundTick();  
}

Como o encoder foi usado para entrada, girar ele faz mover a peça para os lados, enquanto apertar o botão faz o tetromino girar 90 graus. Um botão opcional pode ser conectado entre os pinos 1 e 3 do soquete de teste para atuar como um botão de drop, o botão que acelera a queda dos blocos.

Infelizmente eu tenho que desabilitar o encoder quando não o estiver usando. O encoder compartilha os pinos do display LCD e está ativo somente dentro da função waitInterrupt (). Esta função, juntamente com a rotina de serviço de interrupção do timer 1, pode ser vista abaixo:

void waitInterrupt(void) {
  intFlag = 1;
  enableEncoder();
  while (intFlag) {
    encoder.tick();
  }
  disableEncoder();
}

ISR(TIMER1_OVF_vect) { // Timer 1 interrupt service routine (60Hz)
  TCNT1 = 65015;   // preload timer
  intFlag = 0;
}

Então, depois que o jogo é termina de calcular tudo para o frame atual, ele aguarda o próximo frame enquanto monitora o codificador. É óbvio que quando o LCD está sendo usado, algumas voltas do codificador serão perdidas, mas não há como contornar este problema.

Voltemos para o problema da tela demorando muito tempo para se comunicar. Para diminuir o tempo de comunicação o máximo possível, apenas as partes da tela que precisam ser atualizadas são reescritas no LCD. Por exemplo, vamos ver a função que move um tetromino:

void Tetromino::move(int side) {
    ... 
    //parte do código não relevante para este exemplo foi removida, 
    //veja o código completo no Github 
    ...
    //pinta as posições antigas do tetromino de preto
    buffer[block[0]] = C_BLACK;
    buffer[block[1]] = C_BLACK;
    buffer[block[2]] = C_BLACK;
    buffer[block[3]] = C_BLACK;
    //adiciona esses blocos a fila
    queueInsert(block[0]);
    queueInsert(block[1]);
    queueInsert(block[2]);
    queueInsert(block[3]);

    //atualiza a posição dos blocos
    block[0] += side; block[1] += side; block[2] += side; block[3] += side;

    // preenche as novas posições dos blocos com a cor da peça e adiciona a fila 
    // para serem desenhados na tela
    buffer[block[0]] = color;
    buffer[block[1]] = color;
    buffer[block[2]] = color;
    buffer[block[3]] = color;

    queueInsert(block[0]);
    queueInsert(block[1]);
    queueInsert(block[2]);
    queueInsert(block[3]);

  }
}

Um tetromino é composto de quatro blocos, e esses blocos só podem ser colocados em 200 posições possíveis (o “campo” de tetris é 10×20 blocos). Essas 200 posições são mantidas na variável “Buffer”, que contém a cor de cada bloco. Assim, a posição antiga de um bloco do tetronimo ativo, que deve ser apagada, e a nova posição, que deve ser colorida, são adicionadas a uma fila. A cada quadro a função de atualização da tela verifica a fila e somente se houverem blocos presentes, eles são desenhados na tela:

//desenha apenas os blocos listados na fila, chamada a cadaframe
void drawBuffer(void) {
  while (!queueIsEmpty()) {
    int i = queueRemoveData();
    tft.drawRect(X0 + (i % 10)*BLOCK_SIZE, Y0 + (i / 10)*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE, buffer[i]);
  }
  // se game over, mostra tela de gameover
  if (gameover) {
    showGameOverScreen();
  }
}

O restante dos elementos da tela são atualizados assincronamente. De qualquer forma, quando estes são atualizados, o fluxo do jogo é geralmente bloqueado, então tudo bem. Por exemplo, quando uma linha é apagada, a pontuação é atualizada, todos os blocos acima dessa linha têm que descer e o jogador tem que esperar que o próximo novo tetromino apareça.

Eu não poderia terminar o jogo sem a icônica música korobeiniki, então tive que implementar o som. Para este propósito, eu uso o terminal de saída PWM. Essa parte foi muito simples, dado que eu já escrevi dezenas de sketches de músicas para o arduino como um exercício de leitura de partituras. A função tone() do Arduino pode gerar uma onda quadrada em segundo plano, usando o timer 2. Então eu simplesmente verifico cada frame se é a hora de mudar a nota sendo tocada ou pausar entre as notas.

/ notas da melodia seguidas da duração.
// um 4 significa uma semínima, um 8 uma colcheia , 16 semicolcheia, etc
// !!números negativos são usados para representar notas pontuadas,
// então -4 significa uma semínima pontuada, isto é, uma semínima mais uma colcheia!!
int melody[] = {
  NOTE_E5, 4,  NOTE_B4, 8,  NOTE_C5, 8,  NOTE_D5, 4,  NOTE_C5, 8,  NOTE_B4, 8,
  NOTE_A4, 4,  NOTE_A4, 8,  NOTE_C5, 8,  NOTE_E5, 4,  NOTE_D5, 8,  NOTE_C5, 8,
  NOTE_B4, -4,  NOTE_C5, 8,  NOTE_D5, 4,  NOTE_E5, 4,
  NOTE_C5, 4,  NOTE_A4, 4,  NOTE_A4, 8,  NOTE_A4, 4,  NOTE_B4, 8,  NOTE_C5, 8,
  ...
};

// retorna o numero de notas no vetor
int notes = sizeof(melody) / sizeof(melody[0]) / 2;

// isso calcula a duração da nota semibreve em ms (60s/tempo)*4semibreves
int wholenote = (60000 * 4) / tempo;
int divider = 0, noteDuration = 0;

unsigned int index = 0;
unsigned long next=0;

void soundTick(void) {
  if (millis() > next) {
    if (index < notes * 2) {
      // para a geração de tom antes de mover pra próxima nota.
      noTone(buzzer);

      // calcula a duração de cada nota
      divider = melody[index + 1];

      if (divider > 0) {
        // nota comum, apenas continua
        noteDuration = (wholenote) / divider;
      } else if (divider < 0) {
        // notas ponteadas são representadas com números negativos!!
        noteDuration = (wholenote) / abs(divider);
        noteDuration *= 1.5; // aumenta a duração pela metade para notas pontuadas
      }
      next = millis() + noteDuration;

      // apenas tocamos a nota por 90% da duração, deixando 10% como uma pausa
      // Isso é feito para "separar" duas notas iguais tocadas em seguida
      tone(buzzer, melody[index], noteDuration * 0.9);
      index += 2;
    } else {
      index = 0;
      next = millis();
    }
  }
}

Eu não vou discutir o código inteiro, do contrário este post se tornaria ainda mais longo. Você pode ver o código completo no Github. Não espere que seja bom, mas é bem comentado!

Abaixo deixo um pequeno vídeo do jogo rodando:

 

Ainda existem alguns bugs que raramente aparecem e são irritantes de se debugar. Mas não vou perder o sono, já que o jogo foi apenas uma prova de conceito e não o principal objetivo do projeto.

Por enquanto é isso. Obrigado por ler até aqui.

Até o próximo post  o/

Robson Couto

Recentemente graduado Engenheiro Eletricista. Gosto de dedicar meu tempo a aprender sobre computadores, eletrônica, programação e engenharia reversa. Documento meus projetos no Dragão quando possível.

7 thoughts to “Engenharia Reversa do Testador de Componentes GM328A + Tetris!”

  1. Salve Robson Couto…

    Ficaria imensamente grato se você me dissesse qual IDE você usou para compilar a sua versão do software para o GM328. Estou brigando há dias com o CodeBlocks nas versões genéricas e especial para Arduino. Não vai…

    Será que pode me ajudar ?

    Obrigado,

    Antonio Testa

    1. Olá Antonio, não uso IDE. Apenas o avr-gcc (compilador para avr) + avr libc (biblioteca ) no linux.
      Compilar esse tipo de projeto no windows é uma dor de cabeça (que eu evito).
      Provavelemente é possível usar o Atmel studio para isso, mas não deve ser simples.
      Recomendo que se precisa muito compilar o código, crie uma máquina virtual com ubuntu, instale os pacotes mencionados compile usando make no diretório.

      Agora, se você apenas precisa de um arquivo hex já compilado, eu adicionei o mesmo na pasta firmware. Basta gravar no gm328a e deve funcionar.

      Abraço

      1. ôpa… Obrigado Robson. Infelizmente o seu .hex não rodou, apesar do meu GM328A ser idêntico ao seu, conforme as fotos do artigo. Faço o upload em um ATMega328PU usando um Arduino UNO e o seu BURN.bat devidamente alterado. O upload se dá normalmente. Este chip, ao ser instalado no GM328 mostra pontos aleatórios no display. Em seguida o display é limpo e depois disso nada mais acontece. Bem… Vou batalhar mais um pouco. Obrigado por tudo.

  2. Hi Robson,
    Googling around i find your impressive article on the reverse engineering of the GM328.
    I wonder if you can help me in finding the right configuration (Makefile) for a clone i have. Looking at your schematic it seems it very simiral to your, the main difference is package format of the MCU.
    Im faicing a problem with the screen that is blank. I tried different configurations but no one is working.
    thank you for your support.

    regards, storto

    1. Hi Storto, sorry for the delay.

      What happened? Did your tester stop working after you reprogrammed it? Or it never worked at all? Do you have a backup of the original program?

      Anyways, maybe the pins are not correct on your project or the LCD is a different one. Could you replay with a link to a unit exactly like yours?

      Best regards, Robson

Deixe um comentário

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