Программирование AVR GM328A: реверс-инжиниринг, новое firmware и Тетрис Mon, February 26 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

GM328A: реверс-инжиниринг, новое firmware и Тетрис Печать
Добавил(а) microsin   

Автор статьи [1] произвел исследование популярного тестера GM328A, нарисовал его схему, и даже запрограммировал для него игру Тетрис. Здесь приведен перевод этой статьи.

Ранее автор экспериментировал с тестером T3 LCR meter, и запустил на нем клон игры t-rex [2] из браузера Chrome. Этот измеритель LCR был самым полезным инструментом.

GM328A reverse engineering T3 LCR meter t rex fig01

Рис. 1. Тестер T3 LCR meter с запущенной игрой t-rex.

Потом была найдена отличная альтернатива этому тестеру в виде замечательного проекта GM328A [3, 4]. Собранный тестер можно купить готовый на AliExpress и eBay. На момент написания статьи упрощенный вариант стоит 1037 рублей ($11.26), полнофункциональный вариант, где установлены все разъемы, стоит 1268 рублей ($13.76). Или можно собрать самостоятельно, для него даже есть печатная плата, которую можно заказать на PCBWay [5].

GM328A reverse engineering common view fig02

Рис. 2. Тестер GM328A.

Все эти тестеры основаны на проекте, созданном Markus Frejek [3], и доработанном Karl-Heinz [4].

Что было сделано автором:

• По печатной плате воссоздана схема устройства.
• Исправлена ошибка схемы измерения частоты.
• Портирован и скомпилирован код проекта AVR transistor tester, чтобы он подходил под эту плату.
• Запрограммирована игра Tetris.

GM328A reverse engineering schematic fig03

Рис. 3. Принципиальная схема тестера GM328A.

Очень понравился в этой схеме электронный выключатель питания. В выключенном состоянии ток потребления настолько мал, что его нельзя измерить с помощью микроамперметра. Так что аккумулятор  (или батарейка "Крона") может быть постоянно подключен к устройству, это его никак не разряжает.

Схему можно скачать в формате Eagle из репозитория [6].

Во время исследования платы автор обнаружил некоторую странность. Одно из переходных отверстий платы никуда не было подключено. Судя по всему, это проводник цепи от ножки порта PD4, который работает как вход частотомера.

GM328A reverse engineering broken via fig04

Рис. 4. Поврежденное переходное отверстие.

Тестирование функции измерения частоты показало, что она не работает вообще. С помощью перемычки из тонкого проводника удалось восстановить соединение, после чего частотомер заработал.

GM328A reverse engineering fixed via fig05

Рис. 5. Восстановление переходного отверстия.

В отличие от тестера T3, у платы GM328A ISP-порта для программирования. Конечно, можно было выдергивать чип из кроватки и переписывать его программатором, но это было неудобно. Поэтому было добавлен коннектор для подключения программатора USBASP.

GM328A reverse engineering add ISP connector fig06

Рис. 6. Установка ISP-коннектора.

Для платы GM328A было адаптировано firmware AVR Transistor tester [8] версии 1.34. Полученные результаты сведены в следующую таблицу.

Таблица 1. Сравнение работы старого и нового кода firmware.

Компонент Стоковое firmware Новое firmware
Конденсатор 47 мкФ, 25V C = 45.63 мкФ, ESR = 0.78 Ом C = 43.10 мкФ, ESR = 0.74 Ом
Конденсатор 22 мкФ, 25V C = 22.99 мкФ, ESR = 0.60 Ом C = 20.80 мкФ, ESR = 0.58 Ом
Транзистор BC548 Hfe = 307, Vbe = 649 mV Hfe = 307, Vbe = 649 mV
Диод UF4007 Uf = 634 mV, C = 22 пФ Uf = 634 mV, C = 22 пФ
Резистор 10 k R = 9929 Ом R = 9849 Ом
Индуктивность 220 мкГн L = 210.0 мкГн, R = 0.40 Ом L = 208.1 мкГн, R = 0.41 Ом
Индуктивность 10 мГн L = 10.3 мГн, R = 22.5 Ом L = 10.7 мГн, R = 22.4 Ом

GM328A reverse engineering AVR transistor tester fig07

Рис. 7. Работа нового firmware.

Отредактированный код вы сможете найти в репозитории [6].

У GM328A есть энкодер с кнопкой, который может работать как 3 кнопки. Для тетриса использовалось несколько внешних библиотек:

Adafruit’s GFX library
Adafruit’s ST7735 LCD library
Matthias Hertel’s Rotary Encoder library

Программирование игры для GM328A было довольно сложной задачей. LCD и контакты поворотного энкодера использовали одни и те же выводы портов микроконтроллера, так что необходимо быстро переключаться между обновлением картинки и опросов сигналов энкодера. Для этого была организована очень быстрая запись данных в LCD, и все остальное время микроконтроллера было посвящено опросу энкодера. В противном случае игра могла пропустить повороты энкодера. Интерфейс SPI для LCD организован путем программного управления выводами (bit-bang), что не помогало в быстром выводе картинки. По этой причине пришлось обойтись без закраски квадратиков падающих фигур.

Для программирования GM328A использовалась среда разработки Arduino IDE, и в качестве целевой платы была выбрана Arduino Pro или Pro mini. Затем был выбран Atmega328 (3.3V, 8 МГц). Обратите внимание, что было выбрано питание 3.3V, хотя реально на плате использовалось 5V. Это нормально, поскольку в проекте тетриса не использовалось ADC, и не оказалось варианта 5V, 8 МГц. Таким способом удалось обойти лишнего редактирования файлов вне среды Arduino. Для выгрузки кода использовалась опция USBASP, Upload Using Programmer (Ctrl + Shift + U).

#include "game.h"
#include "sound.h"
 
void setup(void)
{
   gameInit();
}
 
void loop()
{
   gameTick();
   drawBuffer();
   waitInterrupt();
}

Цикл игры обновлялся главным образом с частотой 60 Гц, но не экран. Обновление экрана происходит только тогда, когда необходимо на нем что-то поменять, что из-за bit-bang реализации SPI занимает довольно много времени по сравнению с вычислением состояния игры. Функция gameTick() проверяет входы и вычисляет следующий кадр:

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();  
}

Энкодер используется для ввода: повороты вправо или влево перемещает падающую фигуру вправо или влево, а клик энкодера выполняет переворот фигуры. Опционально может быть подключена кнопка между выводами 1 и 3 сокета, которая работает как кнопка для сброса фигуры вниз.

К сожалению, энкодер должен быть запрещен, когда не используется. Энкодер совместно используется с экраном LCD, и активен только внутри функции waitInterrupt(). Эта функция вместе с обработчиком прерывания timer 1 выглядит следующим образом:

void waitInterrupt(void)
{
   intFlag = 1;
   enableEncoder();
   while (intFlag)
   {
      encoder.tick();
   }
   disableEncoder();
}
 
ISR(TIMER1_OVF_vect)
{  // Обработчик прерывания Timer 1 (60 Гц).
   TCNT1 = 65015;      // Предварительная загрузка таймера.
   intFlag = 0;
}

После того, как вычисление для текущего кадра закончено, происходит ожидание следующего кадра, в течение этого времени опрашивается энкодер. Очевидно, что когда используется LCD, некоторые повороты энкодера будут пропущены, и нет никакого способа для текущей схемы обойти эту проблему.

Чтобы максимально уменьшить время обновления, на LCD перезаписываются только те части экрана, которые необходимо обновить. Для примера рассмотрим функцию, перемещающую фигуру тетриса:

void Tetromino::move(int side)
{
   ... 
   // Часть кода, не относящаяся к этому пример, была удалена.
   // Полный код см. в репозитории Github.
   ...
   // Закрашивает старое положение фигуры черным цветом:
   buffer[block[0]] = C_BLACK;
   buffer[block[1]] = C_BLACK;
   buffer[block[2]] = C_BLACK;
   buffer[block[3]] = C_BLACK;
   // Добавление фигуры в очередь рисования:
   queueInsert(block[0]);
   queueInsert(block[1]);
   queueInsert(block[2]);
   queueInsert(block[3]);
 
   block[0] += side; block[1] += side; block[2] += side; block[3] += side;
 
   // Заполнение новой позиции и добавление этого в очередь рисования:
   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]);
}

Фигура состоит из 4 блоков, и эти блоки могут быть размещены только в 200 возможных позициях ("поле" тетриса занимает 10 × 20 блоков). Эти 200 позиций хранятся в переменной Buffer, которая содержит цвет каждого блока. Таким образом, старая позиция перемещающейся фигуры должна быть стерта, и должна быть закрашена цветом новая позиция, эти действия добавляются в очередь рисования. На каждом кадре функция рисования проверяет эту очередь на наличие данных, и если в ней присутствуют блоки, то они рисуются на экране:

// Рисуются только те блоки, которые перечислены в очереди. Эта функция
// вызывается на каждом кадре.
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]);
   }
   // Если игра закончена, то рисуется экран "game over".
   if (gameover)
   {
      showGameOverScreen();
   }
}

Остальные элементы экрана обновляются асинхронно. Всякий раз, когда они обновляются, рабочий поток игры обычно блокируется, так что это нормально. Например, когда строка стакана очищается, обновляется значение очков, все блоки выше должны переместиться на одну строку вниз, и на экране должна появиться картинка следующей фигуры.

Без канонической мелодии "Коробейники" игра была бы не завершенной, так что нужно как-то реализовать звук. Это было для автора довольно просто, потому что у него уже был опыт программирования музыки [9]. Для этой цели использовался выход PWM. Функция tone Arduino может генерировать прямоугольный сигнал в фона с помощью timer 2. Затем на каждом кадре делается проверка, нужно ли играть следующую ноту или делать паузу между нотами.

// Ноты мелодии и их длительность. Здесь 4 означает четверть ноты, 8 одну восьмую,
// 16 одну шестнадцатую, и т. д. Отрицательные числа используются для представления
// пунктирных нот, так что -4 означает пунктирную четверть ноты, т. е. четверть
// плюс восемнадцатая.
int melody[] =
{
   // Основано на аранжировке https://www.flutetunes.com/tunes.php?id=192
   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,
   ...
};
 
// sizeof дает количество байт, каждое значение int составлено из 2 байт (16 бит).
// Два таких значения тратятся на ноту (высота и длительность), так что получается
// 4 байта на ноту.
int notes = sizeof(melody) / sizeof(melody[0]) / 2;
// Вычисление длительности целой ноты в мс (60s/tempo)*4 "битов"
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)
      {
         // Остановка генерации перед следующей нотой:
         noTone(buzzer);
         // Вычисление длительности ноты:
         divider = melody[index + 1];
         if (divider > 0)
         {
            // Обычная нота, с обычной обработкой:
            noteDuration = (wholenote) / divider;
         }
         else if (divider < 0)
         {
            // Пунктирные ноты представлены отрицательными длительностями:
            noteDuration = (wholenote) / abs(divider);
            // Увеличение длительности наполовину для пунктирных нот:
            noteDuration *= 1.5;
         }
         next = millis() + noteDuration;
         // Мы проигрываем ноту в течение 90% от её длительности,
         // оставляя 10% на паузу:
         tone(buzzer, melody[index], noteDuration * 0.9);
         index += 2;
      }
      else
      {
         index = 0;
         next = millis();
      }
   }
}

[Ссылки]

1. GM328A reverse engineering, new firmware and Tetris! site:dragaosemchama.com.
2. Getting the T-Rex Endless Runner to work on a Component Tester site:dragaosemchama.com.
3. markus-seidl / component-tester site:github.com.
4. kubi48 / TransistorTester-source site:github.com.
5. Печатная плата GM328A на PCBWay site:pcbway.com.
6. gm328a_rev_eng Reverse engineering of the gm328a transistor tester site:github.com.
7. Универсальный тестер GM328A. ПОЛНЫЙ обзор. Калибровка, тестирование, разбор принципиальной схемы site:youtube.com.
8. Mikrocontroller-net / transistortester site:github.com.
9. Songs for Arduino site:dragaosemchama.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page