Building a Casio F-91W Style Desk Clock on the Classic Game Boy with C and GBDK

A classic Game Boy console running a digital desk clock program in Casio F-91W style with the actual Casio F-91W watch on its side.

Have you ever thought of turning a Nintendo Game Boy into a digital desk clock? That’s exactly what I did. By combining two of my favorite childhood gadgets—the Game Boy and the Casio F-91W watch—I built a Casio-style clock program using C and GBDK (Game Boy Development Kit).


First Things First

This post won’t cover every single detail—it’s more about sharing my journey, design choices, and the coding challenges I ran into. I’ll walk you through the important parts: graphics, logic, and how I structured the program with a state machine.


Disclaimer

This is a personal passion project. I’m not affiliated with Nintendo or Casio, and the software isn’t commercially available. It’s just something I built for fun out of nostalgia.


Why I started this project?

As a kid, I always wanted a Game Boy but never owned one. Years later, I finally got a few, and instead of just playing games, I thought—why not write my own software for it?

I’ve also always loved the Casio F-91W. It’s such an iconic gadget from the same era. Combining the two felt natural: a Game Boy clock wrapped in the F-91W’s familiar look.


Game Boy basics and limitations

The Game Boy’s screen is tiny—just 160×144 pixels, arranged in 20×18 tiles of 8×8 pixels each. Graphics come in two parts:

  • Background → static elements (UI frame, labels).
  • Sprites → dynamic elements (digits, icons, blinking cursors).

But there are limits:

  • Max 255 background tiles.
  • Max 40 sprites total, with no more than 10 per horizontal line.

Sprites are either 8×8 or 8×16 pixels. Bigger objects (like digits) are built from multiple sprites (a “metasprite”).

Because of these limits, I couldn’t rely only on sprites for large digits. Instead, I combined them with a Casio-style frame in the background, which makes the numbers stand out and avoids empty-looking space.

The Game Boy definitely has its quirks, but that’s part of the fun. With a little creativity, there’s always a workaround. So yeah, I could’ve made a simple digital clock that only showed day, date, and digits purely out of sprites. It would’ve worked fine—but with those limits, the numbers would’ve looked tiny and a bit lonely. That’s why I wrapped them in the familiar face of the Casio F-91W. The watch frame doesn’t just “fill the empty space,” it actually makes the time display pop.

The result: a functional Casio F-91W style desk clock running on a Game Boy.

A classic Game Boy console running a digital desk clock program in Casio F-91W style with the actual Casio F-91W watch on its side.
Casio F-91W desk clock on Game Boy and the Actual Casio F-91W

Development Tools & Concepts

  • Programming Language: C
  • Game Boy SDK: GBDK (a C library with tools for retro game development)
  • Graphic Design: Aseprite (a great software for creating pixel arts)
  • IDE: Visual Studio Code (one of the best free IDEs for many programming languages)
  • State Machine Design Pattern: To systematically manage modes like time-setting, time-keeping, and alarm-setting
  • Memory Banking: To switch ROM banks to fit large graphics (or code) into the Game Boy’s limited memory
  • Hardware Interrupt: Use Game Boy’s timer interrupt for accurate clock functionality

Features of the Game Boy Desk Clock

The program isn’t just a visual mockup—it works like a real clock. Here are the main features:

  • Time Setting – Adjust day, date, hour, and minute.
  • Time Keeping – The clock runs in real-time with seconds, minutes, hours, day, and date.
  • Alarm Setting – Set an alarm through the time-setting state.
  • Hourly Chime – Toggle on/off at any time.

What it doesn’t have (compared to the real F-91W):

  • No chronometer/stopwatch. – Essentially, it’s a game console, not a precision watch.
  • No 12-hour mode (only 24-hour).
  • Not meant to be ultra-accurate—the Game Boy has no RTC.

For a bit of flair, I added blinking behavior to the separator dots.

A screenshot of the time setting screen from a homebrew Casio F-91W desk clock program developed with GBDK and C that runs on the classic Game Boy console
Time setting mode of my homebrew Casio F-91W style digital clock running on the Game Boy

The Flow of the Program and Button Controls

It’s good practice to plan a program’s flow before coding. Here’s how my desk clock works:

  • Time Setting Mode (default on start):
    • Select → cycle through fields (day, date, hour, minute).
    • Up/Down → change value (wraps around).
    • B → toggle hourly chime.
    • A → enter Alarm Setting Mode.
    • Start → begin timekeeping.
  • Alarm Setting Mode:
    • Adjust hour/minute like above.
    • A cancels, Start saves alarm.
    • If alarm already exists, A removes it.
  • Time Keeping Mode:
    • B toggles chime.
    • A removes alarm.
    • Select returns to Time Setting.

Under the Hood: Graphics

I designed the layout and digits in Aseprite, then exported them into .c and .h files with a plugin. Have a look at how to install it in Aseprite here.

  • Backgrounds → Casio-style watch frame and hour markers (one per hour to respect limits, swapped in with memory banking).
  • Sprites → Digits, alarm icon, chime icon, and blinking cursor.

Why no seconds digits? The Game Boy can’t display them cleanly—each digit uses 3×3 tiles, and adding seconds would push some scanlines past the 10-sprite-per-line limit. Instead, I just sync start time with an external clock.

A screenshot from Aseprite that shows pixel art digits that will be used as hour, minute, and second digit sprites in a Casio F-91W inspired desk clock homebrew software that runs on the classic Game Boy console
Aseprite time digit spriters screenshot

Sprites and backgrounds are kept in arrays and swapped in as time changes. For example, hour backgrounds are stored in an array of 24 and loaded when the hour rolls over.

The display workflow:

  1. Load background (watch frame).
  2. Load sprites (digits/icons).
  3. Position sprites at their coordinates.

This way, only changing parts update each second, while the rest stays fixed.

Adding graphics information to the main code:

#include <gb/gb.h>
#include <stdio.h>

// balnk watch face
#include "blank_bkg_24hr.h"

// sprites, including intro sprites
#include "./trim_sprites/watchface_sprites.h"

// 24HR background data
#include "./background_24hr/watch_face_hr_00.h"
#include "./background_24hr/watch_face_hr_01.h"
...
...
...
#include "./background_24hr/watch_face_hr_22.h"
#include "./background_24hr/watch_face_hr_23.h"

Creating array pointers from the included graphics information:

// big number sprite array
const unsigned char *big_num_sprite_data[10][3] = {
	{big_0_sprite_tileset_size, big_0_sprite_tileset, big_0_sprite_tilemap},
	{big_1_sprite_tileset_size, big_1_sprite_tileset, big_1_sprite_tilemap},
...,
...,
...,
	{big_8_sprite_tileset_size, big_8_sprite_tileset, big_8_sprite_tilemap},
	{big_9_sprite_tileset_size, big_9_sprite_tileset, big_9_sprite_tilemap}};

// small number sprite array
const unsigned char *small_num_sprite_data[10][3] = {
	{small_0_sprite_tileset_size, small_0_sprite_tileset, small_0_sprite_tilemap},
	{small_1_sprite_tileset_size, small_1_sprite_tileset, small_1_sprite_tilemap},
...,
...,
...,
	{small_8_sprite_tileset_size, small_8_sprite_tileset, small_8_sprite_tilemap},
	{small_9_sprite_tileset_size, small_9_sprite_tileset, small_9_sprite_tilemap}};

// background 24hr data
const unsigned char *hr_bg_data[24][3] = {
	{watch_face_hr_00_tileset_size, watch_face_hr_00_tileset, watch_face_hr_00_tilemap},
	{watch_face_hr_01_tileset_size, watch_face_hr_01_tileset, watch_face_hr_01_tilemap},
...,
...,
...,
	{watch_face_hr_22_tileset_size, watch_face_hr_22_tileset, watch_face_hr_22_tilemap},
	{watch_face_hr_23_tileset_size, watch_face_hr_23_tileset, watch_face_hr_23_tilemap},
};

Load and display background:

// Load and draw initial background 

// load background information 
set_bkg_data(0, blank_bkg_24hr_tileset_size, blank_bkg_24hr_tileset); 
// draw background
set_bkg_tiles(0, 0, 20, 18, blank_bkg_24hr_tilemap);

The background information is loaded with the function set_bkg_data(); by setting its first tile at index 0 with the number of tiles and tile information represented by blank_bkg_24hr_tileset_size and blank_bkg_24_hr_tileset. Then, the background is drawn with the function set_bkg_tiles(); starting at tile position x= 0, y = 0 on the 20 x 18 tiles canvas according to the map information represented by blank_bkg_24hr_tilemap.

Similarly the following code snippet show an example of how a sprite is drawn on the screen:

Load and display sprites:

// Load chime and alarm sprite icon information

// Load Chime sprite icon data
set_sprite_data(34, chime_sprite_tileset_size, chime_sprite_tileset); 

// Load Alarm sprite icon data
set_sprite_data(35, alarm_sprite_tileset_size, alarm_sprite_tileset); // Alarm

// Display Chime sprite icon data
set_sprite_tile(34, 34);
move_sprite(34, 48, 72);

// Display Alarm sprite icon data
set_sprite_tile(35, 35);
move_sprite(35, 56, 72);

set_sprite_tile(34, 34); prepare to show the sprite indexed 34 stored in the VRAM’s tile indexed 34. The function move_sprite(34, 48, 72); shows the sprite indexed 34 at tile position x= 48, y = 72

A helper function to draw a sprite:

Instead of writing two functions consecutively to show sprites on the screen, we can also write up a function that will show any sprites of different sizes at any position on the screen as the following example:

If we want to draw a sprite which occupies 3 x 3 tiles (rows and columns) with the starting sprite index of 8 and starting tile index of 8 at the tile position x = 44 and y = 88, we would used the function above like this: draw_sprite(8, 8, 3, 3, 44, 88);

static void draw_sprite(uint8_t sprite_start, uint8_t tile_start, uint8_t row_tiles, uint8_t, col_tiles, uint8_t x, uint8_t y)
{
    uint8_t idx = sprite_start;
    uint8_t tile = tile_start;
    for (uint8_t row = 0; row != row_tiles; row++)
	{
	    for (uint8_t col = 0; col != col_tiles; col++)
	        {
		    set_sprite_tile(idx, tile);
		    move_sprite(idx, x + (col * 8), y + (row * 8));
		    idx++;
		    tile++;
		}
	}
}
A screenshot of the timekeeping screen from a homebrew Casio F-91W desk clock program developed with GBDK and C that runs on the classic Game Boy console displaying all available sprites
Casio F-91W homebrew Game Boy classic desk clock screenshot from Emulicious with all sprites displayed

How Backgrounds and Sprites Work Together

Think of the screen as layers:

  • Background (Casio frame, labels).
  • Sprites (digits, icons, blinking cursor).
  • Hardware display blends them together.

This lets me update only what changes (minutes ticking, alarm toggling) without redrawing everything each frame.


Under the Hood: State Machine

I structured the logic with three states:

  1. Time Setting
  2. Time Keeping
  3. Alarm Setting

Each state has its own button mappings and update logic.

// Defining an enumerated data type for time setting

typedef enum {
    TIMESET_DAY,
    TIMESET_DATE,
    TIMESET_HOUR,
    TIMESET_MINUTE
} TimeSetField;

// Defining the variable 'timeset_field' with its 
// default value

TimeSetField timeset_field = TIMESET_DAY;

// Defining the variable 'joy_read' to get input
uint8_t joy_read = joypad();

// If the 'select' button is pressed, cycle through 
// the values in the TimeSetField data type

if (joy_read & J_SELECT) {
    timeset_field++;
    if (timeset_field > TIMESET_MINUTE) {
        timeset_field = TIMESET_DAY;
    }
}    

Timer Accuracy

The Game Boy doesn’t have a built-in RTC, so I used its hardware timer interrupt.

  • CPU runs at 4096 Hz.
  • An 8-bit counter overflows at 256 (28).
  • 4096 / 256 = 16 overflows → 1 second.

I track these overflows with cpu_cyc_count. Each time it reaches 16, one second passes. From there, minutes, hours, days, and dates tick up in the main loop.

// Timer interrupt handler
void timer_isr(void) {
    cpu_cyc_count++;
}

void main() {
    // Timer ISR initialization
    disable_interrupts();

    // Timer setup: 16 Hz base
    TMA_REG = 0x00;    // reload value
    TIMA_REG = 0x00;   // start from 0
    TAC_REG = 0x04;    // enable timer, 
                       // input clock = 4096 Hz

    add_TIM(timer_isr);
    set_interrupts(TIM_IFLAG | VBL_IFLAG);
    enable_interrupts();

    // Time calculation logic
    while (1) {
        wait_vbl_done();
        // time-keeping
	if (cpu_cyc_count >= 16) {
	    cpu_cyc_count = 0;
	    sec_count++;
	    sec_tick = 1;
	    if (sec_count >= 60) {
	        sec_count = 0;
		min_count++;
		min_tick = 1;
		if (min_count >= 60) {
		    min_count = 0;
		    hr_count++;
		    hr_tick=1;
		    if (hr_count >= 24) {
		        hr_count = 0;
			day_count++;
			date_count++;
			day_tick = 1;
			date_tick = 1;
			if (day_count >= 7) {
			    day_count = 0;
			}
			if (date_count >= 31) {
			    date_count = 0;
			}
		    }
	        }
            }
	}
        // The rest of the code...
        // The rest of the code...
        // The rest of the code...
    } // Close the game loop
} // Close main function

This provides a reliable 1 Hz tick without overloading the CPU.

Extra details:

Blinking Effect → toggle every 20 frames at ~60 FPS.

void main() {
    while (1) { // main game loop
        wait_vbl_done();

        // code ...
        // code ...
    
        blink_counter++;
        if (blink_counter >= 40) {
            blink_counter = 0;
        }

        // code ...
        // code ...
        
        // draw blinking day sprite 
        // (during time setting):

        if (blink_counter >= 20) {
	    draw_daydate(0, 0, 0, 0);
	} else {
	    draw_daydate(0, 0, 80, 72);
	}
	
        // code ...
        // code ...

    } // close game loop
} // close main

The Game Boy screen updates at ~59.7 FPS. When the loop is controlled by wait_vbl_done(); The counter (blink_counter) goes up to ~60 at the end of the loop. But this time, we reset it when it reaches 40. The conditional block draws the day sprite while the counter is below 20 and remove it while the counter is above 20. This way, we can create the ‘blinking effect’.

Input Sensitivity → button delay counter prevents “jumpy” input.

void main() {
    while(1) {

        // code ...
        // code ...

        // input delay
        if (input_delay > 0) input_delay--;

       // code ...
       // code ...
       
       if (input_delay == 0 && joy_read & J_START) {
           input_delay = 12; // reset input delay
           game_state = STATE_TIMEKEEPING;
       }
    
       // code ...
       // code ...

    } // close game loop
} // close main

From the code above, if the ‘start’ or any button is pressed, the input_delay is reset to 20 and no button can be pressed before it reaches 0 again. Without this mechanism, the button control will be too responsive and feel ‘jumpy’.


What I Learned

Working on this project taught me a lot about:

  • Embedded programming concepts with limited hardware.
  • Interrupt handling on the Game Boy.
  • Using a state machine for clean, scalable game/software design.
  • The fun of merging retro hardware with nostalgic design.

Why This Project Matters

For me, this isn’t just about writing code—it’s about connecting with childhood dreams. Owning a Game Boy now and programming it into something unique feels incredibly rewarding. Pairing it with the Casio F-91W aesthetic makes it even more special.

Projects like these are proof that old consoles can be more than just collectibles. They can be platforms for learning, creativity, and fun programming experiments.

A classic Game Boy console running a homebrew digital desk clock program in Casio F-91W style with its development code showing on a monitor in the background
The behind-the-scene C/GBDK development code for the homebrew Casio F-91W Game Boy desk clock

Final Thoughts

If you’re into retro programming, C development with GBDK, or simply love the Game Boy as much as I do, I hope this project inspires you to try building something of your own.

Stay tuned—I’ll be posting screenshots, photos of the setup, and snippets of the C code that make this Game Boy desk clock tick.

Comments

One response to “Building a Casio F-91W Style Desk Clock on the Classic Game Boy with C and GBDK”

  1. A WordPress Commenter Avatar

    Hi, this is a comment.
    To get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.
    Commenter avatars come from Gravatar.