In this article, Maxwell and Noah, two Cornell University undergrads, describe the design and implementation of a retro-inspired, side-scroller game, modeled after Super Mario Bros. It is built on a Raspberry Pi Pico with VGA output and custom controller inputs. The project recreates the first level’s physics, enemy behaviors, tile graphics, and scoring, with smooth scrolling, sprite animations, and responsive controls engineered subject to the Pico’s resource constraints.
As the four-week final project for the course, Digital Systems Design Using Microcontrollers, at Cornell University, we designed and built a retro-inspired, side-scroller game. Working as a team, we had to create a system from scratch that demonstrated everything we had earned throughout the semester. We decided to recreate the first level of Super Mario Bros. on a Raspberry Pi Pico with video graphics array (VGA) output and custom controller inputs. Those who are unfamiliar with the original game and the character names and other terminology used in this article can refer to the entry about it in Wikipedia [1]. The final gameplay is shown in Figure 1.
Retro gaming remains popular in the 21st century, largely due to its simplicity, nostalgia, and charm. For many, classic games represent a golden era of creativity constrained by limited technology—where clever design mattered more than raw processing power. Developers had to think ingeniously to make games engaging, responsive, and fun within severe technical limits.
We were particularly drawn to that balance between constraint and creativity. Having both grown up playing older titles, we wanted to explore what made them so enduring, and whether we could recreate that experience ourselves, using modern hardware. This personal connection to retro gaming, alongside a shared curiosity for how those systems achieved so much with so little, made it a natural fit for our final project.
Our interest in this project also stemmed from experiences with earlier projects that emphasized basic collision physics and graphical interfaces running off the Pico. We wanted to take that concept further and build something both playful and technically challenging. By choosing to create a tribute to the first level of Super Mario Bros., we could combine nostalgic design with hands-on embedded programming, pushing ourselves to handle the challenges of timing, graphics, and input management on a microcontroller.
With limited time, we had to carefully prioritize which game mechanics and visual details to include. Our system was built around the Raspberry Pi Pico, which forced us to think critically about performance, memory management, and display updates. While additional time might have allowed us to expand beyond the first level or add sound and animations, the short timeframe kept us focused on creating a faithful, fully-functional recreation, within tight hardware and schedule constraints. The result was a project that reflected both our passion for retro gaming and our growing understanding of embedded systems.
SYSTEM DESIGN & ARCHITECTURE
Microcontroller: The hardware platform we used for this project was the Raspberry Pi Pico (RP2040), a dual-core ARM Cortex-M0+ microcontroller clocked at 250MHz. The RP2040 provides 264KB of SRAM and supports up to 2MB of external flash memory. These specifications are modest compared to modern computing systems, but ample for exploring the challenges of constrained embedded design—and notably, much more powerful than the original 8-bit, cartridge-based titles created for the Nintendo Entertainment System. That system operated at under 2MHz and had just 2KB of SRAM and 2KB of video memory.
In considering all of this, the Pico was an ideal choice for a project focused on reproducing retro game performance within tight memory and processing limits. The entire circuit fits cleanly onto a standard solderless breadboard, allowing us to integrate the microcontroller, input buttons, and video output components into a compact and accessible system (Figure 2).
Display: For the display, we used VGA output running at 640×480 resolution and 30 frames per second. Because of strict memory limits, we could only afford 4 bits per pixel—just enough to store 16 colors in total. Within that budget, two of those bits were devoted to the green channel, using separate “green-high” and “green-low” signals alongside single-bit red and blue channels. Spending the extra bit on green gave us a palette composed of maximally distinct, visually balanced colors, since the human eye is most sensitive to differences in green. This setup let us achieve a rich color range, while staying within the Pico’s limited GPIO and memory constraints.
A key feature of the VGA implementation was that the graphics driver [2], written and provided by our professor, V. Hunter Adams, ran entirely through the RP2040’s programmable I/O (PIO) subsystem and DMA Channels. This meant that video generation required no CPU intervention, leaving both cores free to handle game logic, physics updates, and user input in real time.
From our application’s perspective, drawing to the screen was as simple as modifying values in an array—with each element representing a pixel’s color in the 4-bit palette. This made it easy to update graphics efficiently, while the PIO handled the continuous video output in the background.
Controls: For user input and controls, we built a crude but effective controller from scratch using three tactile buttons (left, right, jump), plus a reset button, mounted on a block of wood (Figure 3). Buttons were active-high, and we utilized a common ground for them.
SOFTWARE & GAME ARCHITECTURE
Our game software was written in embedded C for the Raspberry Pi Pico, and organized into modular components for drawing, physics, game state, and level management. These “modules” were largely just a means of organization, and also made it easy for us to work independently and then merge our respective parts of the project. All software for the project is available in the resources section of circuitcellar.com and in our code repository at https://github.com/maxwell5963/ECE4760Lab3_NoahMax.
Each subsystem communicates through shared data structures (namely arrays) representing the player, enemies, and world state. This modular structure allowed us to test and modify specific parts of the game rapidly, without breaking others—a critical factor under our four-week timeline.
One of the logistical challenges of working as a two-person team was that we were largely “flying blind” for the first couple of weeks. While I worked on the graphics, which was something tangible that could be seen as it came together, Noah was working on the physics. Testing the collision logic in a low-level, bare-bones system proved to be incredibly challenging, especially because this was a design course, and “test cases” were not provided to us for our unique project. We had to take great care in thinking through our logic before implementing it, which required a deep understanding of basic Newtonian mechanics. However, once we were able to marry our respective pieces together and get some semblance of a working prototype, we were able to make progress much faster.
At the highest level, our game operates on a discrete-time update loop, synchronized to the VGA’s VSYNC. Each frame, user inputs are sampled from GPIO, physics and collision updates are computed, and the visible portion of the level is redrawn.
Graphics and Animations: We use Protothreads [3], a lightweight threading library created by Adam Dunkels, to coordinate animation and timing events. Protothreads provide a simple way to achieve cooperative multitasking without full operating system support, allowing multiple routines to share a single stack efficiently. This is particularly critical in an interactive video game. In this project, they serve as a lightweight scheduling mechanism that let multiple parts of the game—such as physics updates, sprite animation, input handling, and the timer—run cooperatively within a single core on the Raspberry Pi Pico. The game state machine (Figure 4) manages transitions between “Playing,” “Game Over,” and “Reset” states, and each frame is responsible for maintaining visual and mechanical consistency within tight timing constraints.
Our graphics and animations were simple but time-consuming to implement. Concept art for our sprites and tiles were drawn up on a tablet, using the app PixilArt [4]. Once we were happy with the designs, they were converted into Python/Numpy arrays for testing in Jupyter Notebook, then into C arrays for the final build. This was easily achieved by replacing curly braces with square brackets. To convert this into our pre-existing 4-bit color palette, we utilized Macros in place of the official color names, and chose the colors that most accurately reflected the concept art.
Our physics model follows basic Newtonian motion, discretized per frame. Mario’s position is updated according to the equations of motion:
Vy = vy + gΔt
where g is a constant gravitational acceleration (approximately 0.3 pixels per frame²) and Δt corresponds to a 1/60-second frame step. When the “jump” button is pressed while Mario is on the ground, an instantaneous upward “impulse” sets Vy = -−Vj, where Vj is the jump velocity constant. This gives Mario a satisfying, parabolic jump arc. His downward velocity is capped to prevent runaway acceleration and ensure predictable landings.
Horizontal motion is handled similarly, but with discrete steps rather than continuous acceleration. Pressing left or right directly adds or subtracts a fixed vx per frame, provided there are no solid tiles blocking movement. This method keeps controls responsive and predictable, preserving the “tight” feel of the original game.
Goombas—iconic, weak, mushroom-like enemies in the Mario series [1]—use a simplified version of this physics engine. They inherit the same gravity and collision routines as Mario but include a constant horizontal patrol speed that reverses direction upon collision with a wall tile. These work much the same as our other collision logic; that is, each Goomba has a 30×30 “box” of pixels, and we check to see whether the Goomba’s box overlaps with either Mario’s or other tiles.
For tiles, our logic checks whether “this is a tile;” if it is not, then the Goomba treats it as empty space (sky). Animation frames for both Mario and the Goombas are tied to a frame counter modulo a small integer (for example, every eight frames), ensuring that their motion stays synchronized with the 60Hz update loop.
The level is represented as a large, two-dimensional matrix (16 rows × 421 columns), stored as a constant array in flash memory and copied into RAM at runtime. Each element of the matrix is a single char symbol corresponding to a specific tile type (such as sky, brick, question block, or ground). Using this approach allowed us to minimize memory usage while retaining easy indexing. During rendering, we iterate over this matrix, read each symbol, and draw its corresponding 30×30 pixel tile to the VGA buffer. After drawing a tile, we skip ahead by 30 pixels horizontally, avoiding redundant computations and achieving efficient block-based rendering.
Simulated Scrolling:To simulate a scrolling world much larger than the 640×480 display, we implemented a global world_x offset. When Mario’s horizontal position exceeds a center threshold, the offset increases rather than moving Mario off-center. Each frame, the game redraws only the tiles that fall within the visible window determined by Mario’s horizontal position in the level. This method gives the illusion of a vast side-scrolling world, while keeping the computational cost proportional to the number of visible tiles. We explored the possibility of including even more elements, such as tiles and sprites, in the display as shown in Figure 5, but time constraints for the project meant that we were unable to include them in the final product.

The “test palette” of tiles and sprites once they were coded up. The green boxes were meant to be pipes, but were scrapped due to time constraints.
Scoring: For scoring, whenever Mario hits a mystery box by jumping upward, a short coin animation goes off. The coin spawns in above the box, rises up according to the same logic as a Mario jump, and then disappears. For each coin collected, the coin count increases by 1, while the score increases by 10.
The titles for the scoreboard are initialized once at the beginning of the game and stay permanently. We avoided redrawing approximately the top 90 pixels of the screen to prevent the scoreboard from getting deleted. When Mario destroys a goomba, he gets +20 score. At the end of the game, spare time is added to score and a “Game Over” screen reads out the coin and score totals.
To prevent Mario and the Coins from accidentally overwriting the scoreboard, we cap their maximum positions at 90, by forcing their velocities to zero (and in the case of the coin, despawning it) when they get to this height. The timer is a simple counter in its own thread, and it can be adjusted in the gameinit() function. We decided that 180 seconds was a reasonable starting time.
CHALLENGES & SOLUTIONS
Initially, the large number of redraws caused flicker, especially with Mario’s constantly updated sprite. This was solved by restricting redraw regions—only blanking/drawing minimal parts when necessary, by comparing the pixels that actually changed from frame to frame with those that didn’t.
As mentioned earlier in this article, the Pico is powerful for its size, but still has performance limitations from being such a small platform. To squeeze more performance out of the chip, we overclocked the Pico to 250MHz. We also optimized drawing routines to reduce per-frame work, minimized tile coverage by drawing only what’s visible, and carefully scheduling sprite updates and background updates.
Another major challenge was getting our collisions to be precise. At times, the sprites would float, Mario wouldn’t be able to fit through a 1-tile height gap, and Goombas would fall out of the world.
Getting tight collision detection (especially for small gaps) required trial and error. Edge cases, where Mario could slightly clip corners or where Goomba hit detection was imprecise, persisted. This was worked around by level design (for instance, opening up tight areas) to reduce offending configurations. We also reduced Mario’s hitbox from 30×30 to about 30×28, to allow him to squeeze through gaps easier.
A Faithful Recreation
After ironing out design complications, we met and maybe even exceeded our expectations for this project. We achieved smooth level scrolling at 60Hz, and eliminated flicker and other visual glitches. Mario’s movement (left, right, jump), collision with blocks, goombas visible, interaction with “mystery” boxes (coins), and block-breaking behavior were implemented as we envisioned. We ended with an adapted recreation of Level 1, including voids, floating blocks, stairs, “parkour” sections. We achieved all of this within the limits of the Pico’s hardware.
Our project demonstrates that a faithful recreation of Super Mario Bros. (first level) is more possible than ever on modern hardware. With careful engineering, optimizing redraws, managing resources, and modular software design, the game delivers responsive gameplay, credible graphics, and solid performance.
This work highlights both the joys and trade-offs of retro game engineering, and serves to demonstrate the vast capabilities of low-level, barebones microcontroller programming in modern times.
REFERENCES
[1] Super Mario Bros. https://en.wikipedia.org/wiki/Super_Mario_Bros.
[2] V. Hunter Adams, VGA Driver for the RP2040. https://vanhunteradams.com/Pico/VGA/VGA.html
[3] Adam Dunkels, Protothreads. https://dunkels.com/adam/pt/
[4] PixilArt: https://www.pixilart.com/
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • APRIL 2026 #429 – Get a PDF of the issue





