Let’s make a Snake game on a Melopero Cookie RP2040

The Melopero Cookie RP2040 is a nice development board made by Melopero Electronics, with a Raspberry Pico RP2040 chip on board, a 5×5 NeoPixel grid and a couple of I/O buttons (plus a reset and boot select button which come in handy when flashing the board with u2f firmware). As soon as I got one my first thought was to make a small game for it, and since the display is a square array of 25 NeoPixels I thought it would be a nice idea to add some feedback by displaying some messages on it. Let’s see how.

Using the onboard LED

There’s a LED on the Melopero Cookie that can be set or cleared calling SetLed():

Displaying messages and drawing on the NeoPixel display

The Pico has two PIOs peripherals and a DMA controller, so I used both to display pixels on the NeoPixel array. The PIO program takes a 32-bit word from the configured state machine’s FIFO and sends it with appropriate timings to the NeoPixel  display; the PIO’s FIFO is fed by the DMA controller, that is configured to send 25 32-bit words at a time. When a message is sent, a buffer is filled with data to display the message (the scrolling direction matters, there’s a case for each of the four scrolling directions), the DMA is configured to read from the buffer and a timer is started to control the scrolling speed. A timer callback is installed, which is called every time the timer triggers, and updates the address from which the DMA reads:

Calling ShowMessage() with the message to display shows the message on the display, with the direction set by a call to SetMessageDirection()  (Some row and column manipulations are performed and an additional framebuffer is used in order to display the text based on the direction choosed).

Drawing on the display is also possible, by calling StopMessage() (if any message is being currently shown on the display), and using ClearDisplay(), SetPixel() and ShowDisplay() functions:

Getting input: polling vs event driven

When it comes to get input from a device, two approaches are viable: continuously polling the device for input, or letting the device generate events whenever it wants to send input to the processor via interrupts. The Cookie imlements both ways, for getting input from the two buttons: you can call the function IsButtonPressed() with the name of the button (an enum called Button, A or B), which returns if the specified button has been pressed. This public function calls a private helper function, GetButtonState(), that checks the current state of the button, based on the button state during the previous loop (the state is stored into the private data members mButtonStateA and mButtonStateB); the button can be in one of four states, represented in the ButtonState enum: it can be just pressed, kept pressed, just released or kept released (other functions can be implemented using this helper function, to check if the button ha been just released, for example):

Calling IsButtonPressed() returns if the button is currently pressed at the time the function is called, so we check for the state of the button in each loop; doing so can lead to miss some button presses, expecially if the loop is busy doing other things, such as sleeping for any amount of time. This is where interrupts and events come in.

Interrupt-driven events cannot be missed, since every button press or release triggers an interrupt, which generates the corresponding event; this event is stored in an event queue and at the beginning of each loop we retrieve the occurred events from the queue. The queue is a FIFO, so the order in which each event occurred is preserved:

To get an event we can call Get() on the instance of the event queue inputEventQueue. When the GPIO interrupt service routine triggers, it stores the relevant event into the queue calling Insert(). In this way, asynchronous input events are latched into the FIFO and retrieved when needed, even when the loop is busy tending to other devices or sleeping.

Making a Snake game on the Melopero Cookie

This little framework can be used to make a game, displayed on the NeoPixel array and controlled by the two buttons on the board. I used it to make a simple Snake clone: a state machine controls the state of the game and when in the play state the buttons are used to control the direction of the snake; when the game ends, a game over message is displayed on the screen, and pressing the A button resets the game:

and this is the final result:


Leave a Reply

Your email address will not be published. Required fields are marked *