GAME BOX

In this tutorial, we will guide you through creating a versatile digital display project using the TM1637 4-digit 7-segment display, an Arduino, and basic electronic components. This project features multiple modes, including a 6-sided die, a 20-sided die, a heads-or-tails indicator, and a stopwatch. The mode is selectable via a potentiometer, and the function is triggered using a button.

What you'll need:

1 - 9 Volt Battery

1 - 9 Volt Battery Harness

1 - Arduino

1 - PBNO

1 - 100 K-Ohm Potentiometer

1 - TM1637 Display

1 - 6"x12" 1/8 MDF

1 - Laser Cutter/Engraver

1 - Soldering Pen

Building the Project

Step One

Understanding how to read the schematic for your HT16K33 display project is essential. It serves as a blueprint, detailing the connections and components needed, such as resistors, capacitors, buttons, and the display itself. By interpreting the schematic correctly, you can ensure accurate wiring and component placement, preventing common errors and ensuring your project functions as intended.

Step Two

Building a breadboard prototype is a critical step in bringing your HT16K33 display project to life. It allows you to test and verify your circuit design before finalizing it. Using a breadboard helps you easily connect and adjust components, ensuring that everything works correctly without the need for soldering. This hands-on process also aids in troubleshooting and refining your project.

Mini-Lesson: Understanding Multiplexing for the TM1637 Display

Multiplexing is a technique used to manage multiple signals over a shared medium or device, such as a TM1637 display, which is a 4-digit 7-segment display module. This method allows for efficient control of multiple segments without the need for individual wiring for each LED segment.

How Multiplexing Works in TM1637

The TM1637 display consists of four 7-segment digits, and each segment of each digit can be controlled independently. However, instead of having separate control lines for each segment, the TM1637 uses multiplexing to control all segments with fewer pins.

Shared Control Lines:

The TM1637 display has two main control lines: CLK (clock) and DIO (data input/output). These lines are used to send data to the display module, which then handles the multiplexing internally.

Time Division Multiplexing (TDM):

The display works by rapidly cycling through each of the four digits, turning each digit on and off in quick succession. This cycling happens so fast that the human eye perceives all digits as being on simultaneously. Due to the persistence of vision, where the eye retains an image for a fraction of a second after the object is gone, the rapid on-off cycling of each digit creates the illusion of a stable, continuous display.

Segment Control:

For each digit, the TM1637 controller sends a set of data that determines which segments of that digit should be lit. The controller sends data to each digit one at a time, enabling the correct segments for each digit in turn.

Data Transmission:

Data is transmitted serially to the TM1637 display. Each digit is addressed in turn, and the data for that digit’s segments is sent. The CLK line is used to synchronize the transmission, and the DIO line carries the actual segment data.

Example of Multiplexing in TM1637

Initialization:

The TM1637 module is initialized by setting the brightness and starting communication using the CLK and DIO pins.

TM1637Display display(CLK, DIO);

display.setBrightness(0x0f); // Max brightness

Sending Data:

The microcontroller sends data to the display. For example, to display the number 1234, the microcontroller will send data for each digit.

uint8_t digits[] = {0x06, 0x5b, 0x4f, 0x66}; // Representing 1, 2, 3, 4

display.setSegments(digits);


Updating Display:

The display controller updates each digit in sequence. While the first digit is being updated, the others are temporarily turned off. This process repeats for each digit. The setSegments function takes an array of segment data and updates the display accordingly.

Benefits of Multiplexing

In summary, multiplexing in the TM1637 display enables efficient control of multiple 7-segment digits with minimal wiring and pin usage, providing a seamless and effective way to display numeric data.

Step Three

Laser cut and assemble your finger joint box. Make the dimensions large enough to fit an Arduino, 9v battery, and the other components. Most simple threaded components like the Potentiometer and button require a hole just over a 1/4" to fit through. Measure your HT16K33 display to place a slot in the middle. Be sure to check the tolerances of your laser cut to make sure these holes are not too tight or loose. If you want, add symbols for your dial to point to to know the mode types ahead of time.

Step Four

Assembling your Game Box project with an Arduino in real life is an exciting step that brings your design from concept to reality. Carefully follow the schematic to connect components to the Arduino, ensuring each wire and part is correctly placed. This hands-on assembly helps verify that your circuit functions as intended and allows for real-time testing and debugging. It's a rewarding process that turns your theoretical knowledge into a working prototype.

Step Five

Writing a short code using Tinkercad's block coding or Arduino's IDE is an essential step to bring your project to life. In Tinkercad, you can use the intuitive block coding interface to drag and drop commands, making it easy to create a program that controls your display. Alternatively, using Arduino's IDE, you can write a concise and efficient code in C++, allowing for more advanced functionality and customization. This coding step ensures your components interact correctly and your project operates as intended. A completed version of the code is shown below.

Step Six

Assembling the box and placing the components inside is a crucial step to finalize your Game Box project. Carefully position each component within the enclosure according to your layout plan, ensuring the Arduino, display, buttons, and other parts fit snugly. Use hot glue to securely attach components in place, preventing movement and potential disconnections. This step not only protects your circuit but also gives your project a polished, professional appearance.

Variants

Staying flexible with variants of similar parts in your project is important for several reasons:

Overall, flexibility in component selection helps ensure the success and longevity of your projects by adapting to cost, availability, and compatibility considerations

When switching from the HT16K33 backpack to another clock display module, the build and coding processes will slightly differ. The new display may have different pin configurations, requiring adjustments in wiring and connections. Additionally, the code will need to be updated to accommodate any new libraries or functions specific to the new display module. This might involve changes in initialization, segment control, and display updates. Understanding the datasheet and documentation for the new display is crucial to ensure proper integration and functionality within your project.

TM1637 Game Box Code

#include <TM1637Display.h>


// Define the connections pins for the TM1637

#define CLK 3

#define DIO 2


// Define the button pin

const int buttonPin = 4;  // Main button


// Define the potentiometer pin

const int potPin = A0;


// Create an instance of the TM1637 display

TM1637Display display(CLK, DIO);


// Modes

enum Mode {

  MODE_6_SIDED_DIE,

  MODE_20_SIDED_DIE,

  MODE_HEADS_TAILS,

  MODE_TIMER

};


Mode currentMode = MODE_6_SIDED_DIE;


bool stopwatchRunning = false;

unsigned long stopwatchStartTime = 0;

unsigned long countdownDuration = 180000; // Initial countdown duration: 3 minutes in milliseconds

unsigned long elapsedTime = 0;


void setup() {

  // Initialize the display

  display.setBrightness(0x0f); // Max brightness


  // Initialize the button pin as input with pull-up resistor

  pinMode(buttonPin, INPUT_PULLUP);


  // Seed the random number generator

  randomSeed(analogRead(0));

}


void loop() {

  // Read the potentiometer value and map it to the mode range

  int potValue = analogRead(potPin);

  int modeIndex = map(potValue, 0, 1023, 0, 3);

  currentMode = static_cast<Mode>(modeIndex);


  // Display the mode (0, 1, 2, 3)

  display.showNumberDec(modeIndex);


  // Read the state of the button

  int buttonState = digitalRead(buttonPin);


  // Check if the button is pressed

  if (buttonState == LOW) {

    switch (currentMode) {

      case MODE_6_SIDED_DIE:

        rollDice(6);

        break;

      case MODE_20_SIDED_DIE:

        rollDice(20);

        break;

      case MODE_HEADS_TAILS:

        displayHeadsTails();

        break;

      case MODE_TIMER:

        startTimer(countdownDuration);

        break;

    }


    // Debounce delay

    delay(200);


    // Wait until the button is released to avoid multiple actions

    while (digitalRead(buttonPin) == LOW) {

      delay(10);

    }

  }

}


void rollDice(int sides) {

  int roll = random(1, sides + 1);

  display.showNumberDec(roll);


  // Wait until either the button is pressed again or the potentiometer is adjusted

  while (true) {

    int potValue = analogRead(potPin);

    int modeIndex = map(potValue, 0, 1023, 0, 3);

    if (static_cast<Mode>(modeIndex) != currentMode || digitalRead(buttonPin) == LOW) {

      return;

    }

  }

}


void displayHeadsTails() {

  bool result = random(0, 2);

  if (result) {

    displayHead(); // Display "HEAD"

  } else {

    displayTail(); // Display "TAIL"

  }

}


void displayHead() {

  uint8_t head[4] = {

    SEG_G | SEG_B | SEG_C | SEG_E | SEG_F,              // H

    SEG_A | SEG_F | SEG_G | SEG_E | SEG_D,              // E

    SEG_A | SEG_B | SEG_F | SEG_G | SEG_E | SEG_C,       // A

    SEG_A | SEG_B | SEG_F | SEG_E | SEG_C | SEG_D,       // D

  };

  display.setSegments(head);

}


void displayTail() {

  uint8_t tail[4] = {

    SEG_G | SEG_F | SEG_E | SEG_D,                       // t

    SEG_A | SEG_B | SEG_F | SEG_G | SEG_E | SEG_C ,       // A

    SEG_B | SEG_C ,                                     // I

    SEG_F | SEG_E | SEG_D ,                             // L

  };

  display.setSegments(tail);

}


void startTimer(unsigned long countdownDuration) {

  // Calculate countdown duration based on potentiometer value

  stopwatchRunning = true;

  stopwatchStartTime = millis();

  elapsedTime = 0;


  while (stopwatchRunning) {

    unsigned long currentTime = millis() - stopwatchStartTime;


    // Calculate remaining time

    unsigned long remainingTime = countdownDuration - currentTime;


    // Calculate minutes and seconds

    unsigned int minutes = (remainingTime / 60000) % 60;

    unsigned int seconds = (remainingTime / 1000) % 60;


    // Display the time in MM:SS format

    uint8_t timeDisplay[4];

    timeDisplay[0] = display.encodeDigit(minutes / 10);  // Tens of minutes

    timeDisplay[1] = display.encodeDigit(minutes % 10);  // Units of minutes

    timeDisplay[2] = display.encodeDigit(seconds / 10);  // Tens of seconds

    timeDisplay[3] = display.encodeDigit(seconds % 10);  // Units of seconds


    // Set colon ':' between minutes and seconds

    timeDisplay[1] |= 0x80; // Add colon to the second digit (units of minutes)


    display.setSegments(timeDisplay);


    delay(200); // Update display every 200 milliseconds

  }

}