Привет! Этот проект стал для меня важным шагом в мир : первое устройство с полностью собственной печатной платой, спроектированной с нуля в KiCad. Ранее я экспериментировал с готовыми модулями Arduino и макетными платами, но захотелось перейти к компактному, автономному решению на «голом» микроконтроллере.

Я новичок в проектировании PCB-устройств, так что могу ошибаться в некоторых нюансах. Хотя у меня уже есть большой опыт в электротехнике, с хорошим знанием схемотехники и прочего, если заметите неточности или улучшения — не стесняйтесь, указывайте в комментариях, буду рад конструктивной критике!

Получилась миниатюрная игра на реакцию для двух игроков на базе ATmega328P. Устройство питается от одной батарейки CR2032 (3 В), работает на внутреннем RC-генераторе 8 МГц и не требует внешнего кварца или стабилизатора напряжения. Проект доступен на GitHub.

В этой статье я разберу механику игры, выбор компонентов, разработку прошивки (с кодом), прототипирование, проектирование PCB и сборку. Давайте разберёмся по шагам.

Правила игры

Игра предназначена для двух игроков. У каждого — своя кнопка и индикаторный светодиод. Центральный светодиод сигнализирует о старте раунда.

  • Подготовка: Центральный LED мигает три раза коротко — сигнал готовности.

  • Задержка: Случайная пауза от 1 до 5 секунд (генерируется с помощью random() на основе аналогового шума).

  • Старт: Загорается центральный LED — игроки должны как можно быстрее нажать свою кнопку. Первый, кто успел, получает очко; его индикатор загорается на 1 секунду.

  • Анти-чит: Если во время ожидания игрок нажимает кнопку более двух раз, он блокируется на текущий раунд (чтобы избежать "спама").

  • Продолжительность раунда: 5 секунд на реакцию после сигнала.

  • Матч: Игра идёт до 3 очков. Победа отмечается пятью быстрыми миганиями индикатора победителя.

  • Сброс: После матча счёт автоматически сбрасыва��тся, и игра готова к новому раунду.

Механика простая — требует хорошей реакции и внимания. В версии 2.0 (в разработке) добавлены по три LED на игрока для визуализации счёта (загораются последовательно и остаются гореть до конца матча).

Выбор компонентов

Я стремился к минимализму: низкое потребление энергии, компактность и дешевизна. Питание от CR2032 (3 В) идеально подходит — ATmega328P стабильно работает на этой напруге с внутренним тактированием 8 МГц (без внешнего кварца, чтобы сэкономить место и ток).

Компоненты для версии 1.0:

  • ATmega328P — 1 шт.

  • Тактовые кнопки 6×6 мм — 2 шт.

  • Светодиоды 0805 — 3 шт.

  • Резисторы 330 Ом (для ограничения тока LED) — 3 шт.

  • Резистор 10 кОм (pull-up для кнопок или сброса) — 1 шт.

  • Конденсатор 1 мкФ (для фильтрации питания) — 1 шт.

  • Держатель батарейки CR2032 — 1 шт.

  • Миниатюрный выключатель питания — 1 шт. (опционально, для удобства).

Стоимость комплекта — около 350–450 рублей. Для версии 2.0 добавляются дополнительные LED (по 3 на игрока) и резисторы (всего 7 шт. 330 Ом).

В схеме использованы минимальные внешние компоненты: нет регуляторов напряжения, нет внешнего reset'а (используется внутренний pull-up). Кнопки подключены с debounce в коде.

Шаг 1: Разработка прошивки и программирование

Прошивка написана в Arduino IDE на C++ (используются только базовые функции, без библиотек). Это позволило быстро прототипировать логику, а затем скомпилировать в чистый HEX для "голого" AVR.

Ключевые особенности кода:

  • Рандомизация задержки: random(1000, 5000) с сидом от аналогового пина.

  • Debounce кнопок: простая задержка в 20 мс.

  • Анти-чит: счётчик ложных нажатий.

  • Энергосбережение: использование sleep_mode() в будущем (в v1.0 не реализовано полностью).

  • Fuse-биты: Low = 0xE2 (внутренний 8 МГц, без делителя).

Полный код для v1.0 (из GitHub: /src/v1/reaction_game_v1.ino):

// reaction_game_v1.ino

#define LED_CENTER 13  // Центральный  LED (PB5)
#define LED_P1 12      // LED Player 1 (PB4)
#define LED_P2 11      // LED Player 2 (PB3)
#define BTN_P1 2       // Кнопка Player 1 (PD2)
#define BTN_P2 3       // Кнопка Player 2 (PD3)

int scoreP1 = 0;
int scoreP2 = 0;
int falsePressP1 = 0;
int falsePressP2 = 0;
bool blockedP1 = false;
bool blockedP2 = false;

void setup() {
  pinMode(LED_CENTER, OUTPUT);
  pinMode(LED_P1, OUTPUT);
  pinMode(LED_P2, OUTPUT);
  pinMode(BTN_P1, INPUT_PULLUP);
  pinMode(BTN_P2, INPUT_PULLUP);
  
  randomSeed(analogRead(0));  // Сид для рандома
}

void loop() {
  resetRound();  // Сброс раунда
  
  // Сигнал готовности: 3 мигания
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED_CENTER, HIGH);
    delay(200);
    digitalWrite(LED_CENTER, LOW);
    delay(200);
  }
  
  // Случайная задержка
  delay(random(1000, 5000));
  
  // Старт: зажигаем центр
  digitalWrite(LED_CENTER, HIGH);
  unsigned long startTime = millis();
  
  while (millis() - startTime < 5000) {  // 5 сек на реакцию
    checkFalsePresses();  // Проверка анти-чита
    
    if (!blockedP1 && digitalRead(BTN_P1) == LOW) {
      scoreP1++;
      digitalWrite(LED_P1, HIGH);
      delay(1000);
      digitalWrite(LED_P1, LOW);
      checkWin();
      break;
    }
    
    if (!blockedP2 && digitalRead(BTN_P2) == LOW) {
      scoreP2++;
      digitalWrite(LED_P2, HIGH);
      delay(1000);
      digitalWrite(LED_P2, LOW);
      checkWin();
      break;
    }
    
    delay(20);  // Debounce
  }
  
  digitalWrite(LED_CENTER, LOW);
}

void resetRound() {
  falsePressP1 = 0;
  falsePressP2 = 0;
  blockedP1 = false;
  blockedP2 = false;
}

void checkFalsePresses() {
  if (digitalRead(BTN_P1) == LOW) {
    falsePressP1++;
    if (falsePressP1 > 2) blockedP1 = true;
    delay(20);
  }
  
  if (digitalRead(BTN_P2) == LOW) {
    falsePressP2++;
    if (falsePressP2 > 2) blockedP2 = true;
    delay(20);
  }
}

void checkWin() {
  if (scoreP1 >= 3) {
    for (int i = 0; i < 5; i++) {
      digitalWrite(LED_P1, HIGH);
      delay(100);
      digitalWrite(LED_P1, LOW);
      delay(100);
    }
    scoreP1 = 0;
    scoreP2 = 0;
  } else if (scoreP2 >= 3) {
    for (int i = 0; i < 5; i++) {
      digitalWrite(LED_P2, HIGH);
      delay(100);
      digitalWrite(LED_P2, LOW);
      delay(100);
    }
    scoreP1 = 0;
    scoreP2 = 0;
  }
}

Процесс прошивки:

  1. Скомпилировать в Arduino IDE → получить .hex.

  2. Использовать программатор T48 .

  3. Записать HEX и fuse-биты.

Шаг 2: Прототипирование на макетной плате

С прошитым чипом собрал схему на breadboard: ATmega в панельке, кнопки и LED по пинам, питание от CR2032 или лабораторного БП (3 В).

Тестировал:

  • Стабильность на 3 В (ток в idle ~1 мА).

  • Рандомизацию (без повторяющихся паттернов).

  • Анти-чит и debounce (чтобы избежать ложных срабатываний).

После отладки — переход к PCB.

Шаг 3: Проектирование платы в KiCad

Первый опыт полноценного дизайна в KiCad. Шаги:

  1. Eeschema: Нарисовал схему (простая: MCU + LED + кнопки + пассивка).

  2. Pcbnew: Разводка трасс (2 слоя, минимальные зазоры 0.25 мм). Учитывал компактность.

  3. 3D-viewer: Визуализация для проверки (см. рендер на GitHub: images/v1/3D Model.png).

  4. Генерация Gerber-файлов.

Схема (из GitHub: images/v1/Schematic.png):

Шаг 4: Заказ плат и сборка

Gerber загрузил на PCBWay: 2 слоя, чёрная маска, HASL, 1.6 мм, 5 ш��. Стоимость с доставкой ~2160 руб. (пришли через 2,5 месяца).

Пайка: Ручная, SMD .

Первый опыт — не идеально, но плата заработала почти с первого раза.

Итог и планы

Проект дал опыт полного цикла: от идеи до готового устройства. Узнал нюансы AVR, KiCad, пайки SMD. Теперь увереннее в embedded.

В v2.0: Три LED на игрока для счёта, улучшенная шелкография. Есть один нюанс, который я не исправил : неправильное расположение кнопок на PCB — футпринт оказался перевернутым.

Если статья полезна — лайкните, подпишитесь или оставьте комментарий. Вопросы по коду или схеме — в issues репозитория. Удачи в ваших проектах! 🚀