Breadboard games. Christmas 2023

This event’s focus is not so much on delivering a finished game as delivering a platform for learning micropython.

The board consists of a SeedStudio/Xiao RPI2040 which has just enough pins for this project. It also has a built-in WS2812 RGB Led along with additional LED’s on board.

A set of construction images is shown below. Be careful to place the wires and components exactly as shown. The breadboard has rows labelled a to j and columents 1 to 63 which may help when inserting components. If you would like to know more about breadboards this video may help:

Construction image gallery

The following three images show the placement of the display wires and grounds on this board

The image below shows the placement of the gamepad wiring. Be sure to run the wires in the slot as shown. The left-most button is equivalent to the A button on a game controller. Its left pin lines up with a pin on the microcontroller development board so no wire is needed for it (apart from ground).

The long black and blue wires are shown below. Be careful to leave space for the microcontroller board when routing the blue wire.

The microcontroller boards is placed as shown. It fits in to the left-most holes of the breadboard.

The buzzer placement is shown below. One pin goes into the ground track of the breadboard, the other wire goes to the leftmost column of the breadboard just below the microcontroller board.

Finally, fit the display as shown below. Be careful to line up the pins with the wires as shown.

Programming

Our game console is programmed in Micropython which is best accessed using the Thonny development environment. There are three ready-made games which will hopefully help your learning of micropython and the hardware on our board. There is also a place for you to create your own game. When the board starts you are presented with a menu which allows you select from these (press A to select). There are lots of micropython and python learning resources on the Internet. The basics of Python can be studied here: https://www.w3schools.com/python/. Micropython tutorials tend to be board specific. A tutorial for our XIAO board can be found here: https://wiki.seeedstudio.com/XIAO-RP2040-with-MicroPython/. Board documentation is available here (check the rpi2040 chapter): https://files.seeedstudio.com/wiki/XIAO/Seeed-Studio-XIAO-Series-SOM-Datasheet.pdf

The code you will use in this exercise includes additionaly libraries to manage our specific hardware. This hardware includes the display, the buzzer, the various buttons and the onboard RGB led. Here is a list of the functions within these libraries.

Functions that control the display
putPixel(x,y,colour): lights up a display pixel with the specified location and colour
drawLine(x0,y0,x1,y1,colour): x0,y0 = start point, x1,y1 = end point
fillRectangle(x1,y1,w,h,colour): x1,y1 = top left corner, w=width, h=height
drawRectangle(x1,y1,w,h,Colour): x1,y1 = top left corner, w=width, h=height
clear(): clear the screen to black
putImage(x,y,w,h,img,horiz,vert): put image at x,y. Width is w, height is h. Image data is in img and h,v specify whether image is inverted in horizontal or vertical axes
setOrientation(h,v): set display orientation (0,0) = default
print(text, x, y, forecolour, backcolour): print text at x,y with foreground and background colours
drawCircle(x0,y0,radius,colour): circle with
fillCircle(x0,y0,radius,colour):
RGBToWord(r,g,b): convert 8 bit red, green and blue values to a 16 bit colour value

Functions that use the gamepad buttons
leftPressed(): returns 1 if the left button was pressed
rightPressed(): returns 1 if the right button was pressed
downPressed(): returns 1 if the down button was pressed
upPressed(): returns 1 if the up button was pressed
aPressed(): returns 1 if the A button was pressed
buttonPressed(): returns a bit patten for the various buttons (0 if nothing pressed)

Functions that manage sound
sound.tune.append() : append a note to the sound array
sound.note(frequency,duration,pause)

Sprite functions
sprite(x,y,w,h,image,display): creates at sprite with the initial position x,y. Bounding rectangle height=h,width=w
show(): show the sprite on screen
hide(): hide the sprite
move(newx, newy): move the sprite (erase at the previous location)
move_no_erase(newx, newy): move the sprite (don’t at the previous location)
setOrientation(horiz, vert): set sprite horizontal and vertical orientation (default 0,0)
isOverlapping(sprite2): do this sprite overlap another?

RGB Led support
set_colour(self,red,green,blue): light up the onboard LED with the particular color

Once you have finished building the board (and playing the games) we will explore how you might write your own game. Starter code for this event is available below:

Breadboard Games 2022 Assembly

Note on board co-ordinates:

There are two sets of co-ordinates shown on the board. We will be using the ones that are the “right way up” i.e. the ones shown on the left side of the image. Some of the images can be a little misleading because of the angle the photos were taken. This is particularly true for the red and black wires at the top left of the image. The red wire connects to 3V3 to any hole just above the red line. The black wire connects GND to any hole just below the blue wire.

Display wiring:

White : Column 32, Row A to any hole just above the red line (keep it left of Column 30)

Purple : Column 31, Row A to GP22

Pink : Column 30, Row A to GP20

Blue : Column 29, Row A to GP21

Orange : Column 28, Row A to GP19

Yellow : Column 27, Row A to GP18

Red : Column 26, Row A to any hole just above the red line (3.3V)

Black : Column 25, Row A to any hole just below the blue line (0V)

Buttons: Place as shown.

Black wires (0V) These are used connect GND (0V) signals together.

Link wires between buttons

Column 42, Row E to Column 43, Row D

Column 42, Row F to Column 43, Row G

Button wiring. Try to get at least some of the button wires into the channel between the top and bottom halves of the breadboard.

Brown : GP26 to Column 55, Row A

Grey : GP27 to Column 50, Row A

Purple : GP17 to Column 14, Row E

Buzzer: GP9 to any hole just below the blue line

Yellow: GP16 to Column 36, Row C

White : GP15 to Column 45, Row C

Green: GP14 to Column 40, Row F

WARNING : ASK FOR ASSISTANCE WITH THIS. DISPLAY ARE FRAGILE!

Fit the display as shown in the row of holes just below the display wires. The rightmost pin of the display should line up with the small white wire.

Breaboard games 2022

Breadboard Games 2022 is approaching. This year we will have 16 students assemble the game console shown above. The console consists of a Raspberry Pi Pico linked to an ST7735, some buttons and a piezo speaker. The schematic is shown below

Two simple games are featured: A version of break out and a simple Santa Clause game. These games are written in Micropython with an emphasis on ease of understanding rather than game play. Nevertheless, the graphics module is reasonably optimized with an inline assembler module that executes very quickly and which is used by API functions as much as possible. Furthermore, the sound module makes use of the second core in the Pico which allows sound to be be played at the same time as the game.

The game sprites are edited in a bitmap editor such as Windows Paint or KolourPaint etc. The sprites are created as 24 bit bitmap (bmp) files. A python script converts these bitmaps to python arrays on the host computer. These arrays are coded as 16 bit RGB (5-6-5) colour values which are compatible with the ST7735. These arrays are then included in a sprite module in the game.

Programs were edited using Thonny which is a nice cross-platform editor that works with a number of operating systems. It can be installed without administrator permissions on Windows and Linux systems.

Code is over here on github.

Speeding up some micropython with a touch of inline assembly on the Raspberry Pi Pico

I have been working on a graphics library for the ST7735 and the Raspberry Ri Rico. The first version was written in pure micropython and worked well enough but was quite slow – especially when writing out blocks of colour (fillRectangle). This was the original code for fillRectangle:

def fillRectangle(self,x1,y1,w,h,colour):
        self.openAperture(x1,y1,x1+w-1,y1+h-1)        
        pixelcount=h*w
        self.command(0x2c)
        self.a0.value(1)        
        msg=bytearray()
        while(pixelcount >0):
            pixelcount = pixelcount-1          
            msg.append(colour >> 8)
            msg.append(colour & 0xff)
        self.spi.write(msg)

Not only was this slow, it also required that a buffer be created that held the filled rectangle in RAM. This was slow and memory intensive.

The new version looks like this:

def fillRectangle(self,x1,y1,w,h,colour):
        self.openAperture(x1,y1,x1+w-1,y1+h-1)        
        pixelcount=h*w
        self.command(0x2c)
        self.a0.value(1)
        self.fill_block(colour,pixelcount) 

It makes use of an inline assembler function the source code of which is as follows:

@micropython.asm_thumb
    def fill_block(r0,r1,r2):
        # pointer to self passed in r0
        # r1 contains the 16 bit data to be written
        # r2 countains count
        # Going to use SPI0.
        # Base address = 0x4003c000
        # SSPCR0 Register OFFSET 0
        # SSPCR1 Register OFFSET 4
        # SSPDR Register OFFSET 8
        # SSPSR Register OFFSET c
        push({r1,r2,r3,r4,r7})
        # Convoluted load of a 32 value into r7
        mov(r7,0x40)
        lsl(r7,r7,8)
        add(r7,0x03)
        lsl(r7,r7,8)
        add(r7,0xc0)
        lsl(r7,r7,8)
        add(r7,0x00)
        mov(r4,2)        
        label(fill_block_loop_start)
        cmp(r2,0)
        beq(fill_block_exit)        
        mov(r3,r1) # read next byte
        lsr(r3,r3,8)
        strb(r3,[r7,8]) # write to SPI
        label(fill_block_spi_wait1)        
        ldr(r3,[r7,0xc]) # read next byte
        and_(r3,r4)
        beq(fill_block_spi_wait1)
        
        mov(r3,r1) # read next byte        
        strb(r3,[r7,8]) # write to SPI        
        sub(r2,r2,1) # decrement count                
        label(fill_block_spi_wait2)        
        ldr(r3,[r7,0xc]) # read next byte
        and_(r3,r4)
        beq(fill_block_spi_wait2)
        b(fill_block_loop_start)
        
        label(fill_block_exit)
        pop ({r1,r2,r3,r4,r7})

This writes the colour value directly to the SPI port the required number of times. It needs to pause when the SPI FIFO fills up (hence he need for the labels fill_block_spi_wait1/2).

The performance improvement is about a factor of 20!

Code is available over on gihub and is likely to change lots in the next couple of weeks while I prepare for a STEM event.