PicoSystem API Cheatsheet

While you're developing games for PicoSystem you may find keeping this API function reference open handy.

The PicoSystem API is intentionally designed to be lightweight and get out of your way when you're developing games. The entire API is exposed as around 40 functions which provide access to the hardware and help with drawing, audio, and user input.

Game loop

The PicoSystem game loop works by calling three functions declared in your code.

The basic game loop

  • init() is called once when your program starts so that you can perform any setup that's required like generating map data or initialising the player state.
  • update(tick) called every frame to allow you to process user input, trigger sound effects, do collision detection, and update the player state.
  • draw(tick) called every frame and is where you should perform all drawing such as the map, player and enemy sprites, and HUD.

The game loop will run at fifty frames per second (the same refresh rate as the screen) so long as your update() and draw() function don't take too long to complete. Every cycle round the game loop will increment tick by one.

The reason update() and draw() are implemented as separate functions is because it both helps you separate your game processing logic from your drawing code (good practise!) and it allows the game processing code to run in the background while the screen is updating (if you did drawing operations during the screen updates you'd potentially see an effect called "tearing").

The full game loop (including the internal bits) looks like this:

The full game loop

From the diagram you can see that there are points where we must stall the game loop to wait for external events like the screen update and vertical sync from the display.


PicoSystem has a 240x240 resolution screen and can either run in full resolution or a pixel doubled mode. The decision of which resolution to use is both aesthetic and limitation driven - you may want the retro look of larger pixels, or need the extra RAM saved by having a smaller framebuffer, or need lots of cycles per pixel to calculate visual effects.

  • Full resolution
    240x240 @ 16bpp = 112KB framebuffer & ~100 cycles per pixel @ 40fps.
  • Pixel doubled
    120x120 @ 16bpp = 28KB framebuffer & ~450 cycles per pixel @ 40fps.

The pixel format is the same in both modes with 16 bits per pixel split up into 4 bits per channel (red, green, blue, and alpha) allowing for 4,096 different colours on screen at any time. Because pixels are 4 bits per channel all colour values passed into and returned by API functions are between 0 and 15.

Drawing state

PicoSystem's graphics library is designed to be simple to work with - it has a single global set of state (such as pen colour, camera offset, etc) which all drawing operations adhere to.

Note: All drawing state methods can be called with no parameters to reset to default (i.e. startup) state.

pen(r, g, b, [a]) or pen(color)

Set the colour of the pen. Either passing in red, green, blue (with optional alpha) or a pre-baked colour created with the rgb or hsv functions.


Set a global alpha level for drawing operations. Only relevant if the current blend mode supports alpha blending.

clip(x, y, w, h)

Set the clipping rectangle - pixels outside of this rectangle will not be drawn.


Set the blend mode to use when drawing semi-transparent pixels. Using the most appropriate blend mode can help with performance. See the section on blend modes for more detail.


Set the target buffer for future drawing operations. The default target is the screen buffer but you can create your own off-screen buffers if needed. Just remember to set the target back to SCREEN when you're done...

camera(x, y)

Offsets all future drawing operations. Ideal for managing a two-dimensional camera.


Set a buffer to use as the spritesheet for future calls to sprite(). PicoSystem comes with a default spritesheet which can be disabled or replaced if needed.

cursor(x, y)

Move the position of the current cursor position used in calls to text() to x, y.

font(w, [h], [s], [data])

Set metrics for text rendering.

  • w: fixed character width in pixels or -1 for variable character widths.
  • h: line height in pixels.
  • s: space between characters in pixels.
  • data: buffer of font data (96 * 9 bytes long).

Drawing operations

All drawing operations take into account the drawing state and will be performed on the currently selected target. As a general rule you should only ever call these functions during the draw() callback.


Fills all pixels.

pixel(x, y)

Sets the pixel at x, y to the current pen color.

rect/frect(x, y, w, h)

Draw an outline or filled rectangle with width w and height h at x, y.

circle/fcircle(x, y, r)

Draw an outline or filled circle centred at x, y with radius r.

ellipse/fellipse(x, y, rx, ry)

Draw an outline or filled ellipse centred at x, y with radius rx, ry.

poly/fpoly({x1, y1, x2, y2, ...})

Draw an outline or filled polygon defined by the points x1, y1 to xn, yn.

line(x1, y1, x2, y2)

Draw a line from x1, y1 to x2, y2.

hline/vline(x, y, l)

Draw a horizontal or vertical line starting at x, y of length l. Much faster than line().

text(message, [x, y,] [wrap])

Print the contents of message at position x, y or at the current cursor position. Newline characters will reset the cursor to the start of the next line. If wrap is specified then the text will be wrapped on word boundaries if it exceeds wrap width in pixels.

sprite(i, x, y, [flags])

Draw the sprite at index i in the current sprite sheet to position x, y on the screen. The sprite can be flipped and mirrored by passing VFLIP, or HFLIP, or both in as flags.

blit(source, sx, sy, sw, sh, dx, dy, [dw, dh,] [flags])

Copy an area of width sw and height sh from sx, sy in the source buffer to dx, dy in the current target buffer. Optionally scales if you provide a destination width and height in dw and dh. The image can be flipped and mirrored by passing VFLIP, or HFLIP, or both in as flags.


voice(attack, decay, sustain, release, [bend], [bend_ms], [reverb], [noise], [distort])

Returns a new voice based on the parameters supplied. Voices can be passed into play().

  • attack: duration of the attack phase of the ADSR envelope in milliseconds.
  • decay: duration of the decay phase of the ADSR envelope in milliseconds.
  • sustain: volume of the sustain phase (of duration hold milliseconds) between 0 and 100.
  • release: duration of the release phase of the ADSR envelope in milliseconds.
  • bend / bend_ms: number of hz to shift frequency by every bend_ms milliseconds.
  • reverb: number of milliseconds before reverb is triggered.
  • noise: percentage of noise to mix into sound from 0 to 100.
  • distort: level of distortion (bit crushing) to apply from 0 to 100.

play(voice, frequency, [duration], [volume])

Play the supplied voice at frequency. Optionally set the duration in milliseconds and volume between 0 and 100.


Returns the current audio playback position in milliseconds or -1 if no audio is playing.


These functions provide access to the PicoSystem hardware features.


Returns true if input b has just been pressed. This function only returns true when called from the first update() after the key press occurred - ideal for events that trigger on press like jumping. b can be one of A, B, X, Y, UP, DOWN, LEFT, and RIGHT.


Returns true if input b is currently pressed. This function will always return the current input state - useful for events that require long presses like walking. b can be one of A, B, X, Y, UP, DOWN, LEFT, and RIGHT.


Returns the current battery level as a number between 0 and 100.


Set the backlight brightness to b - between 0 and 100.

led(r, g, b)

Set the brightness of the RGB LED - each channel is between 0 and 100.


A collection of helpful functions that provide features which are a bit of a chore to implement yourself.

str(n, [d])

Convert the number n into a string optionally limiting the number of decimal places to d.

time() or time_us()

Return the time since PicoSystem started in milliseconds or microseconds.

sleep(d) or sleep_us(d)

Sleep for d milliseconds or microseconds.

rgb(r, g, b, [a]) or hsv(h, s, v, [a])

Returns a new colour created from red, green, and blue values or hue, saturation, and value - both with optional alpha.

buffer(w, h, [data])

Returns a new buffer of width w and height h. If data is supplied then it will be used as the storage for pixel data and must be at least w * h * 2 bytes long.

intersects(x1, y1, w1, h1, x2, y2, w2, h2)

Returns true if the first rectangle intersects with the second rectangle.

intersection(x1, y1, w1, h1, x2, y2, w2, h2)

Modifies the first rectangle to be the intersection between the first rectangle and the second rectangle.

contains(x1, y1, x2, y2, w2, h2)

Returns true if the point x1, y1 is inside the rectangle.

contains(x1, y1, w1, h1, x2, y2, w2, h2)

Returns true if the first rectangle is completely inside the second rectangle.

measure(message, w, h, [wrap])

Modifies w and h to be the width and height of the bounding box that would contain message if it was drawn with text().

fsin(v) or fcos(v)

Fast, low resolution, implementations of sin and cos functions.

mix(c1, c2, m)

Returns a mixed colour between c1 and c2 based on the value of m which is between 0 and 15.


Blend modes

  • COPY: Copy source pixels to destination ignoring alpha. The fastest blend mode due to its simplicity, ideal if you're drawing with solid colours or blitting images without transparency.
  • ALPHA: Blend the source pixels with the destination using the source alpha. Allows for "overlaying" transparency effects such as glass or water. Relatively slow due to the complexity of the calculations needed per pixel.
  • MASK: Copy source pixels to destination only if source alpha is not zero. Ideal for blitting images that just need 1-bit transparency.
  • PEN: Same as ALPHA but uses the current pen colour instead of source colour when blitting. Useful for masking sprites or drawing their shape in a single colour (e.g. shadows).
  • DARKEN: Compares source and destination colour channels and selects the darker of the two.
  • LIGHTEN: Compares source and destination colour channels and selects the lighter of the two.
  • ADD: Adds source and destination channels clamping for overflow if needed.
  • SUBTRACT: Subtracts source and destination channels clamping for underflow if needed.
  • MULTIPLY: Multiplies source and destination channels clamping for overflow if needed.
  • DISSOLVE: Selects either source or destination colour depending on a comparison of a hash of the destination pointer and the source/global alpha. Similar to the "air brush" effect in early 90s paint programs.
That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.