Noah B. Portfolio
Electrical Engineering Project

Blackjack Handheld System

Embedded Systems • Digital Electronics • PCB Design • Microcontroller Systems

Project Overview

This project is a handheld blackjack game implemented on a custom through-hole PCB using an ATmega328P microcontroller. It is my second PCB design and focuses on embedded system integration, real-time user interaction, and digital display control.

The system features seven-segment multiplexing for score display, dedicated input buttons for gameplay (hit/stay), and status LEDs for player and dealer feedback. The firmware is written in embedded C for the AVR architecture and implements blackjack game logic with rule-based dealer decisions.

The design emphasizes reliable digital I/O handling, efficient display multiplexing, and time-based input filtering for responsive gameplay in a resource-constrained embedded environment.

Assembled PCB

Blackjack handheld system PCB

Hardware Design

Core Components
ATmega328P Microcontroller 7-Segment Displays (Multiplexed) Tactile Push Buttons Status LEDs Through-Hole PCB

Blackjack Schematic

Open schematic PDF

PCB Layout

Open PCB layout PDF

Firmware

The firmware targets the ATmega328P microcontroller and manages the blackjack round flow, button input, dealer logic, score tracking, LEDs, and multiplexed seven-segment displays using non-blocking timing.

Key Features
Display Multiplexing Software-Debounced Inputs Rule-Based Dealer Logic State Machine Game Logic

Main Control Loop


void loop() {
  // If the round is over, show the score animation instead of
  // accepting new button presses.
  if (showScore) {
    handleBlink();
    return;
  }

  // Update the seven-segment display often enough that all digits
  // appear lit at the same time.
  if (millis() - lastUpdateTime >= refreshRate) multiplex();

  // If either side busts, pause briefly so the player can see it,
  // then let the bust handler finish the round.
  if (dHandleBust()) return;
  if (pHandleBust()) return;

  // Deal the starting cards once when a new turn begins.
  if (beginTurn) initTurn();

  // After the player stays, the dealer draws automatically.
  if (dAllowHit) handleDealerHit();

  // The hit button reads LOW when pressed. The time check keeps
  // one press from being counted more than once.
  if (digitalRead(hitPin) == LOW &&
      (millis() - pLastHitTime) > debounceDelay) {
    handlePlayerHit();
  }

  // The stay button uses the same debounce check before ending the player turn.
  if (digitalRead(stayPin) == LOW &&
      (millis() - lastStayTime) > debounceDelay) {
    handleStay();
  }
}
    

Player and Dealer Logic


void handlePlayerHit() {
  // Remember when the button was pressed for debounce timing.
  pLastHitTime = millis();

  if (pAllowHit) {
    // Add a random card value to the player's hand.
    int randNum = random(1, 11);
    pTotal += randNum;

    if (pTotal > 21) {
      // If an ace-style value is used, reduce it before calling it a bust.
      if (randNum == 11) pTotal--;
      else {
        // Otherwise, mark the player as busted and start the delay timer.
        pAllowBust = 1;
        pLastBustTime = millis();
      }
    }

    // Convert the total into the two digits shown on the display.
    pPreDigits(pTotal);
  }
}

void handleDealerHit() {
  // Wait between dealer draws so the automatic turn is visible.
  if (millis() - dLastHitTime < 700) return;

  if (dTotal < 17) {
    // Dealer keeps drawing until reaching at least 17.
    int randNum = random(1, 11);
    dTotal += randNum;

    if (dTotal > 21) {
      // Apply the same ace-style adjustment before marking a dealer bust.
      if (randNum == 11) dTotal--;
      else {
        // Stop dealer hits and let the bust handler end the round.
        dLastBustTime = millis();
        dAllowHit = 0;
        dAllowBust = 1;
      }
    }

    dPreDigits(dTotal);
    dLastHitTime = millis();
  } else {
    // Once the dealer reaches 17, compare scores and end the round.
    dAllowHit = 0;
    results();
  }
}
    

Display Multiplexing


void multiplex() {
  // Save the refresh time for the next display update.
  lastUpdateTime = millis();

  // Turn all digits off before changing the segment pins.
  for (int i = 0; i < 4; i++) {
    digitalWrite(commonPins[i], LOW);
  }

  // Pick the player or dealer digit for this refresh pass.
  int digit = cDisplay == 0 ? pOnesPlace :
              cDisplay == 1 ? pTensPlace :
              cDisplay == 2 ? dOnesPlace :
                               dTensPlace;

  // Write the segment pattern, then turn on only the selected digit.
  sendDigit(digit);
  digitalWrite(commonPins[cDisplay], HIGH);

  // Move to the next digit for the next fast refresh.
  cDisplay = (cDisplay + 1) % 4;
}
    

Project Outcome

The hardware design, PCB layout, and core firmware are complete. The completed board handles gameplay flow, display multiplexing, button input, score tracking, and LED feedback. Remaining work is focused on bug fixing, testing, and gameplay refinement on the assembled system.